diff --git a/HERALD.md b/HERALD.md index 3e66f60b..a8c29d16 100644 --- a/HERALD.md +++ b/HERALD.md @@ -2,7 +2,7 @@ > The living record. What happened, what's changing, what matters. -**Last updated:** 2026-03-31 | **Session:** 67 | **PRs merged:** 158 +**Last updated:** 2026-03-31 | **Session:** 68 | **PRs merged:** 158 (PR #162 pending) --- @@ -18,6 +18,9 @@ ## Recent Sessions +### S68 — Night Shift: Zero Log Scanner Output (2026-03-31) +Autonomous night shift executed FPLAN-0161. Log scanner went from 981 errors / 1805 warnings / 2000 failures to absolute zero. Clean-slate approach: archived all logs, ran 15 branches fresh (3,425 tests, 0 failures), triaged — 100% of noise was test-generated. Built test log isolation system: AIPASS_TEST_LOG_DIR env var in 14 conftest.py files + prax config/load.py check. Fixed 4 audit items: api magic number + list_providers, trigger errors detail crash + branch_log_events help. PR #162. 22 files changed. + ### S67 — Plan Cleanup, APLAN Template, Log Scanner (2026-03-31) Major housekeeping session. Closed 8 devpulse root plans (DPLAN-0078/0080/0081/0083/0085/0088, FPLAN-0152/0154). Created APLAN (Audit Plan) template and registered with flow — standardized format with Quick Status table, Open/Resolved checkboxes, owner-split todos, Dispatch Log, and TTS Listen section. 15 agents reformatted all branch audits to the new standard simultaneously. System baseline established: 104 open items across 15 branches, 110 resolved. 4 GREEN (API, CLI, Daemon, Skills), 11 YELLOW, 0 RED. Built log_scanner_v1.py (tool #21) — scans all branch log directories for errors, warnings, and failures with Rich output. Full scan baseline: 981 errors, 1805 warnings, 2000 failures (mostly test-generated and stale). Investigated FPLAN auto-close gap — templates have the instruction but agents don't follow through. Strengthened close instruction in FPLAN template. Verified 20 potential night shift items — only 5 confirmed still open (api x2, backup x1, trigger x2). Backup audit updated from 18 to 12 open items after agent investigation confirmed 6 items already done. Deny rules expanded to catch cd && git bypass pattern. Local prompt updated with drone git workflow section. Night shift designed: DPLAN-0089 (clean-slate strategy) and FPLAN-0161 (8-phase master plan for zero-error goal). PR #158 merged. @@ -52,14 +55,15 @@ Every branch, every standard, 100%. Seven agents deployed overnight. PR #137 (16 | Plan | Subject | Status | |------|---------|--------| -| DPLAN-0089 | Night shift: system log cleanup design | Ready for execution | -| FPLAN-0161 | Night shift: 8-phase master plan | Ready for execution | +| DPLAN-0089 | Night shift: system log cleanup design | Complete | +| FPLAN-0161 | Night shift: 8-phase master plan | Phases 1-7 complete, PR #162 pending | | 15 branch audits | Living docs (APLAN format) | 104 open, 110 resolved | ## Key Milestones | Date | Milestone | |------|-----------| +| 2026-03-31 | Zero log scanner output — test log isolation system built (S68 night shift) | | 2026-03-31 | APLAN template — standardized branch audits across 15 branches | | 2026-03-31 | Log scanner built — system-wide error/warning/failure visibility | | 2026-03-31 | Night shift planned — 8-phase log cleanup for zero-error baseline | @@ -89,7 +93,7 @@ Branches: 15 Standards: 32 Tests: 3,410+ PRs merged: 158 -Sessions: 67 +Sessions: 68 Compliance: 100% Diagnostic tools: 21 Open audit items: 104 diff --git a/STATUS.md b/STATUS.md index 845b3455..06c6f2f4 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,7 +2,7 @@ > Auto-generated by `drone @prax status sync`. Do not edit manually. -**Last sync:** 2026-03-31 01:59 +**Last sync:** 2026-03-31 03:57 **Summary:** 9 operational | 0 in-progress | 0 not started --- diff --git a/src/aipass/ai_mail/tests/conftest.py b/src/aipass/ai_mail/tests/conftest.py index 9968fa2e..89e9a972 100644 --- a/src/aipass/ai_mail/tests/conftest.py +++ b/src/aipass/ai_mail/tests/conftest.py @@ -15,9 +15,16 @@ # ============================================= """Shared pytest fixtures for ai_mail tests""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import shutil -import tempfile from pathlib import Path from typing import Generator from unittest.mock import MagicMock diff --git a/src/aipass/api/apps/handlers/usage/tracking.py b/src/aipass/api/apps/handlers/usage/tracking.py index c394c2e7..66616bf4 100644 --- a/src/aipass/api/apps/handlers/usage/tracking.py +++ b/src/aipass/api/apps/handlers/usage/tracking.py @@ -48,6 +48,7 @@ GENERATION_ENDPOINT = f"{OPENROUTER_BASE_URL}/generation" # Default configuration values DEFAULT_GENERATION_CHECK_DELAY = 2 # seconds to wait before querying metrics +DEFAULT_REQUEST_TIMEOUT = 30 # seconds for HTTP request timeout # ============================================= @@ -148,7 +149,7 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st GENERATION_ENDPOINT, params={"id": generation_id}, headers=headers, - timeout=30 + timeout=DEFAULT_REQUEST_TIMEOUT ) # Check response status diff --git a/src/aipass/api/apps/modules/api_key.py b/src/aipass/api/apps/modules/api_key.py index a68b4c20..c98742c7 100644 --- a/src/aipass/api/apps/modules/api_key.py +++ b/src/aipass/api/apps/modules/api_key.py @@ -140,10 +140,13 @@ def validate_key(args: List[str]): def list_providers(): """Orchestrate list providers workflow""" + from aipass.api.apps.handlers.config.provider import PROVIDER_DEFAULTS + header("Available Providers") console.print() - console.print(" - openrouter") + for provider_name in sorted(PROVIDER_DEFAULTS): + console.print(f" - {provider_name}") console.print() diff --git a/src/aipass/api/tests/conftest.py b/src/aipass/api/tests/conftest.py index 85bc3305..1668730f 100644 --- a/src/aipass/api/tests/conftest.py +++ b/src/aipass/api/tests/conftest.py @@ -16,6 +16,14 @@ # ============================================= """Shared pytest fixtures for api tests""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import importlib import logging import sys diff --git a/src/aipass/backup/tests/conftest.py b/src/aipass/backup/tests/conftest.py index 0ad85a6f..80d54578 100644 --- a/src/aipass/backup/tests/conftest.py +++ b/src/aipass/backup/tests/conftest.py @@ -1,8 +1,15 @@ """Shared pytest fixtures for backup branch tests.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import shutil import sys -import tempfile from collections.abc import Generator from pathlib import Path from unittest.mock import MagicMock diff --git a/src/aipass/cli/tests/conftest.py b/src/aipass/cli/tests/conftest.py index 40ea4892..07c00599 100644 --- a/src/aipass/cli/tests/conftest.py +++ b/src/aipass/cli/tests/conftest.py @@ -7,6 +7,13 @@ # ============================================= """Shared pytest fixtures for CLI tests.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") import pytest diff --git a/src/aipass/daemon/tests/conftest.py b/src/aipass/daemon/tests/conftest.py index 65a768f3..fdc115fc 100644 --- a/src/aipass/daemon/tests/conftest.py +++ b/src/aipass/daemon/tests/conftest.py @@ -15,9 +15,16 @@ #@Meta header not seedgo standards """Shared pytest fixtures for daemon tests""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import shutil -import tempfile from pathlib import Path from typing import Generator from unittest.mock import MagicMock diff --git a/src/aipass/drone/tests/conftest.py b/src/aipass/drone/tests/conftest.py index 4c05ba55..6cb44d91 100644 --- a/src/aipass/drone/tests/conftest.py +++ b/src/aipass/drone/tests/conftest.py @@ -1,9 +1,15 @@ """Shared pytest fixtures for drone tests.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") import json import logging import shutil -import tempfile from pathlib import Path from typing import Generator from unittest.mock import MagicMock diff --git a/src/aipass/flow/tests/conftest.py b/src/aipass/flow/tests/conftest.py index 280dd6eb..0e224003 100644 --- a/src/aipass/flow/tests/conftest.py +++ b/src/aipass/flow/tests/conftest.py @@ -1,8 +1,15 @@ """Shared pytest fixtures for flow tests""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import json import shutil -import tempfile from pathlib import Path from typing import Generator from unittest.mock import MagicMock, patch diff --git a/src/aipass/memory/tests/conftest.py b/src/aipass/memory/tests/conftest.py index 4bc1e60d..e8b2c7f1 100644 --- a/src/aipass/memory/tests/conftest.py +++ b/src/aipass/memory/tests/conftest.py @@ -7,10 +7,17 @@ # ============================================= """Shared pytest fixtures for memory tests.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import json import pytest import shutil -import tempfile from pathlib import Path from typing import Generator from unittest.mock import MagicMock diff --git a/src/aipass/prax/apps/handlers/config/load.py b/src/aipass/prax/apps/handlers/config/load.py index 9dc73e82..f43f26d1 100755 --- a/src/aipass/prax/apps/handlers/config/load.py +++ b/src/aipass/prax/apps/handlers/config/load.py @@ -29,6 +29,7 @@ Usage: import json import logging +import os logger = logging.getLogger(__name__) from pathlib import Path from typing import Dict, Any @@ -64,6 +65,11 @@ def get_system_logs_dir() -> Path: Central aggregation: all branches log here for system-wide monitoring. Per-module logs use get_module_logs_dir() for local debugging. """ + test_log_dir = os.environ.get("AIPASS_TEST_LOG_DIR") + if test_log_dir: + p = Path(test_log_dir) / "system" + p.mkdir(parents=True, exist_ok=True) + return p global _system_logs_dir_cache if _system_logs_dir_cache is None: repo_root = _find_repo_root() @@ -87,6 +93,11 @@ def get_module_logs_dir(module_name: str) -> Path: Returns: Path to the module's branch-root logs directory """ + test_log_dir = os.environ.get("AIPASS_TEST_LOG_DIR") + if test_log_dir: + p = Path(test_log_dir) / module_name + p.mkdir(parents=True, exist_ok=True) + return p # Standard: src/aipass/{module}/logs branch_dir = ECOSYSTEM_ROOT / module_name if branch_dir.exists(): diff --git a/src/aipass/prax/tests/conftest.py b/src/aipass/prax/tests/conftest.py index ced9ef1d..f7a9d774 100644 --- a/src/aipass/prax/tests/conftest.py +++ b/src/aipass/prax/tests/conftest.py @@ -11,6 +11,13 @@ Provides infrastructure mocking so test modules can import prax code without triggering real logging, file watching, or CLI dependencies. """ +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") import sys import pytest diff --git a/src/aipass/prax/tests/test_config.py b/src/aipass/prax/tests/test_config.py index e05806f3..4ae48b1d 100644 --- a/src/aipass/prax/tests/test_config.py +++ b/src/aipass/prax/tests/test_config.py @@ -49,6 +49,8 @@ def _fresh_import_load(monkeypatch, tmp_path): monkeypatch.setattr(load_mod, "PRAX_LOGGER_CONFIG_FILE", prax_json_dir / "prax_logger_config.json") # Reset the lazy cache so get_system_logs_dir() re-resolves monkeypatch.setattr(load_mod, "_system_logs_dir_cache", None) + # Clear test log redirect so tests exercise real path resolution + monkeypatch.delenv("AIPASS_TEST_LOG_DIR", raising=False) return load_mod diff --git a/src/aipass/seedgo/tests/conftest.py b/src/aipass/seedgo/tests/conftest.py index 7c598544..dada6a12 100644 --- a/src/aipass/seedgo/tests/conftest.py +++ b/src/aipass/seedgo/tests/conftest.py @@ -8,9 +8,16 @@ # Modified: 2026-03-05 # ============================================= +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import shutil -import tempfile from pathlib import Path from typing import Generator diff --git a/src/aipass/spawn/tests/conftest.py b/src/aipass/spawn/tests/conftest.py index 2c162c8f..4a87256b 100644 --- a/src/aipass/spawn/tests/conftest.py +++ b/src/aipass/spawn/tests/conftest.py @@ -1,4 +1,11 @@ """Shared test fixtures for spawn test suite.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") import json import shutil diff --git a/src/aipass/trigger/apps/handlers/json/json_handler.py b/src/aipass/trigger/apps/handlers/json/json_handler.py index 7209e8bd..b56e8df6 100644 --- a/src/aipass/trigger/apps/handlers/json/json_handler.py +++ b/src/aipass/trigger/apps/handlers/json/json_handler.py @@ -7,13 +7,18 @@ # ============================================= import json +import os from pathlib import Path from datetime import datetime, timezone from typing import Dict, List, Any, Optional import inspect -# Infrastructure -_LOG_FILE = Path(__file__).parent.parent.parent.parent / "logs" / "json_handler.log" +# Infrastructure — redirect to temp dir during tests +_test_log_dir = os.environ.get("AIPASS_TEST_LOG_DIR") +if _test_log_dir: + _LOG_FILE = Path(_test_log_dir) / "trigger" / "json_handler.log" +else: + _LOG_FILE = Path(__file__).parent.parent.parent.parent / "logs" / "json_handler.log" def _log_warning(msg: str) -> None: diff --git a/src/aipass/trigger/apps/modules/branch_log_events.py b/src/aipass/trigger/apps/modules/branch_log_events.py index 89f240bd..b727ca2b 100644 --- a/src/aipass/trigger/apps/modules/branch_log_events.py +++ b/src/aipass/trigger/apps/modules/branch_log_events.py @@ -158,6 +158,11 @@ def handle_command(command: str, args: list) -> bool: remaining = args[1:] return handle_command(subcommand, remaining) + # Help gate — catch --help passed as direct command + if command in ['--help', '-h', 'help']: + print_help() + return True + # Handle direct subcommands if command not in ["start", "stop", "status", "reset"]: return False diff --git a/src/aipass/trigger/apps/modules/errors.py b/src/aipass/trigger/apps/modules/errors.py index 5ed686a7..42b04727 100644 --- a/src/aipass/trigger/apps/modules/errors.py +++ b/src/aipass/trigger/apps/modules/errors.py @@ -19,6 +19,7 @@ Phase 5: Suppress triggers source fix pipeline (email + fix status tracking) Architecture: Module orchestrates, error_registry handler manages data """ +import json import sys import time from pathlib import Path @@ -84,12 +85,15 @@ def _parse_args(args: list) -> dict: def _find_by_id_or_fp(identifier: str) -> Optional[dict]: """Look up error by fingerprint (prefix ok) or short ID field.""" - entry = get_entry(identifier) - if entry: - return entry - for entry in query(limit=1000): - if entry.get("id") == identifier: + try: + entry = get_entry(identifier) + if entry: return entry + for entry in query(limit=1000): + if entry.get("id") == identifier: + return entry + except (json.JSONDecodeError, KeyError, TypeError) as exc: + logger.warning("Failed to look up error '%s': %s", identifier, exc) return None @@ -238,7 +242,12 @@ def _cmd_detail(console, args: list) -> bool: error("Missing error ID or fingerprint", suggestion="Usage: drone @trigger errors detail ") return True - entry = _find_by_id_or_fp(args[0]) + try: + entry = _find_by_id_or_fp(args[0]) + except (json.JSONDecodeError, TypeError, KeyError) as exc: + error(f"Failed to read error registry: {exc}", suggestion="Registry may be corrupted — try 'drone @trigger errors list' first") + return True + if not entry: error(f"Error not found: {args[0]}", suggestion="Try a fingerprint prefix, full fingerprint, or short ID") return True diff --git a/src/aipass/trigger/tests/conftest.py b/src/aipass/trigger/tests/conftest.py index 3cccc54b..ee79b5ca 100644 --- a/src/aipass/trigger/tests/conftest.py +++ b/src/aipass/trigger/tests/conftest.py @@ -14,9 +14,16 @@ # ============================================= """Shared pytest fixtures for cortex tests""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + import pytest import shutil -import tempfile from pathlib import Path from typing import Generator diff --git a/src/commons/apps/handlers/rooms/space_ops.py b/src/commons/apps/handlers/rooms/space_ops.py index 69fc721b..5e540047 100644 --- a/src/commons/apps/handlers/rooms/space_ops.py +++ b/src/commons/apps/handlers/rooms/space_ops.py @@ -13,7 +13,7 @@ Data retrieval and mutation for spatial room commands: enter, look, decorate, vi Returns structured dicts for module-layer rendering. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Dict, Any from aipass.prax.apps.modules.logger import system_logger as logger @@ -52,7 +52,7 @@ def get_room_enter_data(room_name: str) -> Dict[str, Any]: "SELECT COUNT(*) FROM posts WHERE room_name = ?", (room_name,) ).fetchone()[0] - cutoff = (datetime.utcnow() - timedelta(hours=48)).strftime("%Y-%m-%dT%H:%M:%SZ") + cutoff = (datetime.now(timezone.utc) - timedelta(hours=48)).strftime("%Y-%m-%dT%H:%M:%SZ") recent_count = conn.execute( "SELECT COUNT(*) FROM posts WHERE room_name = ? AND created_at > ?", (room_name, cutoff), @@ -205,7 +205,7 @@ def get_visitors_data(room_name: str) -> Dict[str, Any]: result["error"] = f"Room '{room_name}' not found" return result - cutoff = (datetime.utcnow() - timedelta(hours=48)).strftime("%Y-%m-%dT%H:%M:%SZ") + cutoff = (datetime.now(timezone.utc) - timedelta(hours=48)).strftime("%Y-%m-%dT%H:%M:%SZ") # Primary source: explicit room visits visit_rows = conn.execute( diff --git a/src/commons/tests/conftest.py b/src/commons/tests/conftest.py index 2608532c..34d53713 100644 --- a/src/commons/tests/conftest.py +++ b/src/commons/tests/conftest.py @@ -19,8 +19,14 @@ The Commons - Test Configuration Provides pytest fixtures for database setup, teardown, and test isolation using temporary databases. """ - +import os import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") + from pathlib import Path import pytest diff --git a/src/skills/tests/conftest.py b/src/skills/tests/conftest.py index b4877340..707e1695 100644 --- a/src/skills/tests/conftest.py +++ b/src/skills/tests/conftest.py @@ -15,6 +15,13 @@ # ============================================= """Skills test configuration.""" +import os +import tempfile + +# Redirect prax logs to temp directory during tests +# Must be set before any prax imports to catch logger initialization +if "AIPASS_TEST_LOG_DIR" not in os.environ: + os.environ["AIPASS_TEST_LOG_DIR"] = tempfile.mkdtemp(prefix="aipass_test_logs_") import importlib import logging