From a89ddb30bd0275db3931d0ad211aae94b043897a Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Thu, 16 Jul 2026 22:44:01 -0700 Subject: [PATCH] =?UTF-8?q?fix(ci):=20seedgo=20gate=20back=20to=20100%=20?= =?UTF-8?q?=E2=80=94=20hooks=20handler=20logging=20+=20silent=20catches,?= =?UTF-8?q?=20memory=20governance=20modules/handlers=20split=20(import=20p?= =?UTF-8?q?ath=20frozen,=20bridge=20E2E=20green),=20devpulse=20README=20te?= =?UTF-8?q?st=20count.=20All=2017=20branches=20100%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 9 + src/aipass/devpulse/README.md | 2 +- .../apps/handlers/prompt/compass_recall.py | 8 +- src/aipass/memory/.seedgo/bypass.json | 10 ++ src/aipass/memory/README.md | 6 +- .../apps/handlers/governance/__init__.py | 0 .../memory/apps/handlers/governance/engine.py | 125 ++++++++++++++ src/aipass/memory/apps/modules/governance.py | 157 ++---------------- 8 files changed, 169 insertions(+), 148 deletions(-) create mode 100644 src/aipass/memory/apps/handlers/governance/__init__.py create mode 100644 src/aipass/memory/apps/handlers/governance/engine.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ab05e2df..48eaffb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,15 @@ PyPI version — not the changelog header. ### Fixed +- **CI seedgo gate back to 100% across all 17 branches.** The Track 2 compass + recall code left three branches at 99%: @hooks' compass_recall handler was + missing json_handler operation logging and had two silent catches (now + logged); @memory's governance module held its implementation in modules/ + (moved to handlers/governance/engine.py with modules/governance.py as the + thin re-export — the cross-branch import path is unchanged and live-E2E + verified through the real bridge); devpulse's README test count had drifted + (309 → 348). Audits re-run per branch: 100% overall, all suites green. + - **Plan-number memory search hits the exact plan.** Searching a plan ID ('DPLAN-0244', 'fplan 0332' — any case, dash or space) now pins the exact plan as the top result at 100%, via a metadata lookup on the vector store's diff --git a/src/aipass/devpulse/README.md b/src/aipass/devpulse/README.md index 44723600..e79e306d 100644 --- a/src/aipass/devpulse/README.md +++ b/src/aipass/devpulse/README.md @@ -44,7 +44,7 @@ src/aipass/devpulse/ │ │ └── watchdog/ # Agent, timer, schedule, registry │ └── plugins/ # Plugin extension point ├── devpulse_json/ # JSON handler storage (config, data, logs per module) -├── tests/ # 309 tests +├── tests/ # 348 tests ├── artifacts/ # Birth certificate, reports ├── dropbox/ # Received files, archived plans, install audit ├── docs/ # Transition notes diff --git a/src/aipass/hooks/apps/handlers/prompt/compass_recall.py b/src/aipass/hooks/apps/handlers/prompt/compass_recall.py index 59d8497f..2fc17eb6 100644 --- a/src/aipass/hooks/apps/handlers/prompt/compass_recall.py +++ b/src/aipass/hooks/apps/handlers/prompt/compass_recall.py @@ -17,6 +17,7 @@ import tempfile from pathlib import Path from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.hooks.apps.handlers.json import json_handler _STATE_DIR = Path(tempfile.gettempdir()) @@ -38,7 +39,8 @@ def _load_state(hook_data: dict | None = None) -> dict: return _fresh_state() try: return json.loads(path.read_text(encoding="utf-8")) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as exc: + logger.info("[HOOKS] compass_recall: state read failed: %s", exc) return _fresh_state() @@ -47,7 +49,8 @@ def _fresh_state() -> dict: from aipass.memory.apps.modules.governance import new_state return new_state() - except Exception: + except Exception as exc: + logger.info("[HOOKS] compass_recall: governance import failed: %s", exc) return {"surfaces_count": 0, "messages_since_last": 0, "last_surface_time": 0.0, "surfaced_ids": []} @@ -105,6 +108,7 @@ def handle(hook_data: dict) -> dict: lines.append(f"[{rating}] #{c['id']}: {c['decision']}") mark_surfaced([c["id"] for c in approved]) + json_handler.log_operation("compass_recall", {"count": len(approved)}) return {"stdout": "\n".join(lines), "exit_code": 0} diff --git a/src/aipass/memory/.seedgo/bypass.json b/src/aipass/memory/.seedgo/bypass.json index 2bff3ff9..b01b259b 100644 --- a/src/aipass/memory/.seedgo/bypass.json +++ b/src/aipass/memory/.seedgo/bypass.json @@ -462,6 +462,16 @@ "functions": ["render_all_meta_tabs"], "reason": "Cross-branch public API — called by @spawn's build_replacements_dict to resolve {{*_META}} placeholders at branch creation." }, + { + "file": "apps/modules/governance.py", + "standard": "unused_function", + "reason": "Cross-branch public API — should_surface, record_message, new_state re-exported for @hooks compass_recall and @devpulse recall query." + }, + { + "file": "apps/handlers/governance/engine.py", + "standard": "unused_function", + "reason": "Implementation functions re-exported via modules/governance.py for cross-branch consumers." + }, { "file": "apps/modules/templates.py", "standard": "deep_nesting", diff --git a/src/aipass/memory/README.md b/src/aipass/memory/README.md index abe78592..c94b06d5 100644 --- a/src/aipass/memory/README.md +++ b/src/aipass/memory/README.md @@ -45,15 +45,17 @@ drone @memory watch # Auto-rollover watcher daemon (Ctrl+ memory/ ├── apps/ │ ├── memory.py # Entry point — auto-discovers modules -│ ├── modules/ # 6 modules +│ ├── modules/ # 7 modules +│ │ ├── governance.py # Surfacing governance — re-exports from handlers │ │ ├── lint.py # Entry limit violation scanner (read-only) │ │ ├── rollover.py # Rollover orchestration, status, sync-lines │ │ ├── search.py # Semantic query routing │ │ ├── symbolic.py # Fragmented memory extraction and search │ │ ├── templates.py # Template push, diff, status │ │ └── verify.py # Plan vectorization check -│ └── handlers/ # 14 handler groups +│ └── handlers/ # 15 handler groups │ ├── archive/ # indexer.py +│ ├── governance/ # engine.py — surfacing decision logic │ ├── intake/ # plans_processor.py, pool_processor.py │ ├── json/ # json_handler.py, memory_files.py, entry_limits.py, lint_handler.py, config_loader.py │ ├── learnings/ # manager.py diff --git a/src/aipass/memory/apps/handlers/governance/__init__.py b/src/aipass/memory/apps/handlers/governance/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/aipass/memory/apps/handlers/governance/engine.py b/src/aipass/memory/apps/handlers/governance/engine.py new file mode 100644 index 00000000..000482ed --- /dev/null +++ b/src/aipass/memory/apps/handlers/governance/engine.py @@ -0,0 +1,125 @@ +# =================== AIPass ==================== +# Name: engine.py +# Description: Surfacing governance engine — implementation +# Version: 1.0.0 +# Created: 2026-07-16 +# Modified: 2026-07-16 +# ============================================= + +""" +Surfacing Governance Engine + +Pure decision functions for controlling when recalled items should be +surfaced. Implementation logic — public API re-exported from +modules/governance.py for cross-branch consumers. +""" + +from typing import Any, Dict, Tuple + +from aipass.prax import logger +from aipass.memory.apps.handlers.json import json_handler + + +# ============================================================================= +# CONSTANTS — default config values +# ============================================================================= + +DEFAULT_CONFIG: Dict[str, Any] = { + "enabled": True, + "threshold": 0.3, + "max_surfaces_per_session": 5, + "min_messages_between": 10, + "cooldown_seconds": 300, +} + + +# ============================================================================= +# STATE FACTORY +# ============================================================================= + + +def new_state() -> Dict[str, Any]: + """Create a fresh governance state dict.""" + return { + "surfaces_count": 0, + "messages_since_last": 0, + "last_surface_time": 0.0, + "surfaced_ids": [], + } + + +# ============================================================================= +# CORE GOVERNANCE +# ============================================================================= + + +def should_surface( + item_id: str, + relevance_score: float, + state: Dict[str, Any], + config: Dict[str, Any] | None = None, + *, + current_time: float | None = None, +) -> Tuple[bool, str, Dict[str, Any]]: + """ + Decide whether an item should be surfaced, given current state. + + Pure function — does not mutate the input state dict. + """ + import time + + cfg = {**DEFAULT_CONFIG, **(config or {})} + now = current_time if current_time is not None else time.time() + + if not cfg.get("enabled", True): + return False, "Surfacing disabled", state + + threshold = cfg.get("threshold", 0.3) + if relevance_score < threshold: + return False, f"Below threshold ({relevance_score:.2f} < {threshold})", state + + max_surfaces = cfg.get("max_surfaces_per_session", 5) + if state.get("surfaces_count", 0) >= max_surfaces: + return False, f"Session budget exhausted ({max_surfaces}/{max_surfaces})", state + + min_messages = cfg.get("min_messages_between", 10) + messages_since = state.get("messages_since_last", 0) + last_time = state.get("last_surface_time", 0.0) + if last_time > 0 and messages_since < min_messages: + return False, f"Spacing not met ({messages_since}/{min_messages} messages)", state + + cooldown = cfg.get("cooldown_seconds", 300) + elapsed = now - last_time + if last_time > 0 and elapsed < cooldown: + remaining = int(cooldown - elapsed) + return False, f"Cooldown active ({remaining}s remaining)", state + + surfaced_ids = state.get("surfaced_ids", []) + if item_id in surfaced_ids: + return False, "Already surfaced this session", state + + updated = { + "surfaces_count": state.get("surfaces_count", 0) + 1, + "messages_since_last": 0, + "last_surface_time": now, + "surfaced_ids": list(surfaced_ids) + [item_id], + } + logger.info(f"[governance] Surfacing {item_id} (score={relevance_score:.2f}, surfaces={updated['surfaces_count']})") + json_handler.log_operation( + "governance_surface", + {"item_id": item_id, "relevance_score": relevance_score, "surfaces_count": updated["surfaces_count"]}, + ) + return True, "Ready to surface", updated + + +# ============================================================================= +# MESSAGE TRACKING +# ============================================================================= + + +def record_message(state: Dict[str, Any]) -> Dict[str, Any]: + """Record that a message was processed. Pure — returns updated state.""" + return { + **state, + "messages_since_last": state.get("messages_since_last", 0) + 1, + } diff --git a/src/aipass/memory/apps/modules/governance.py b/src/aipass/memory/apps/modules/governance.py index 2eab3790..e092967a 100644 --- a/src/aipass/memory/apps/modules/governance.py +++ b/src/aipass/memory/apps/modules/governance.py @@ -1,7 +1,7 @@ # =================== AIPass ==================== # Name: governance.py # Description: Surfacing governance module — public API -# Version: 1.0.0 +# Version: 1.1.0 # Created: 2026-07-16 # Modified: 2026-07-16 # ============================================= @@ -9,24 +9,14 @@ """ Surfacing Governance Module — Public API -Pure decision functions for controlling when recalled items (compass -rulings, plans, fragments) should be surfaced to the user. Retrieval- -backend-agnostic: works with any store that produces an item_id and -a relevance_score. +Thin module re-exporting governance engine from handlers/governance/engine.py. +Cross-branch consumers import from here: -All functions are PURE — state dict in, decision + updated state out. -The caller owns persistence (file, JSONL, in-memory). - -Public API: - should_surface(item_id, relevance_score, state, config) - -> (bool, reason, updated_state) - record_message(state) -> updated_state - new_state() -> state dict + from aipass.memory.apps.modules.governance import should_surface, record_message, new_state """ import os import sys -from typing import Any, Dict, Tuple if sys.platform == "win32": os.environ.setdefault("PYTHONUTF8", "1") @@ -35,137 +25,16 @@ if sys.platform == "win32": if _reconfigure is not None: _reconfigure(encoding="utf-8", errors="replace") +from aipass.prax import logger # noqa: F401 from aipass.memory.apps.handlers.json import json_handler +from aipass.memory.apps.handlers.governance.engine import ( + DEFAULT_CONFIG, + new_state, + record_message, + should_surface, +) - -# ============================================================================= -# CONSTANTS — default config values -# ============================================================================= - -DEFAULT_CONFIG: Dict[str, Any] = { - "enabled": True, - "threshold": 0.3, - "max_surfaces_per_session": 5, - "min_messages_between": 10, - "cooldown_seconds": 300, -} - - -# ============================================================================= -# STATE FACTORY -# ============================================================================= - - -def new_state() -> Dict[str, Any]: - """ - Create a fresh governance state dict. - - Returns: - State dict with zeroed counters and empty surfaced set. - """ - return { - "surfaces_count": 0, - "messages_since_last": 0, - "last_surface_time": 0.0, - "surfaced_ids": [], - } - - -# ============================================================================= -# CORE GOVERNANCE -# ============================================================================= - - -def should_surface( - item_id: str, - relevance_score: float, - state: Dict[str, Any], - config: Dict[str, Any] | None = None, - *, - current_time: float | None = None, -) -> Tuple[bool, str, Dict[str, Any]]: - """ - Decide whether an item should be surfaced, given current state. - - Pure function — does not mutate the input state dict. Returns an - updated copy reflecting the decision (surfaced id recorded, counters - bumped) only when the answer is True; otherwise returns state unchanged. - - Args: - item_id: Unique identifier of the item to surface. - relevance_score: Float score from the retrieval backend (0-1). - state: Current governance state (from new_state() or prior call). - config: Override config; merged with DEFAULT_CONFIG for missing keys. - current_time: Epoch seconds for cooldown check. Injected for testability; - falls back to time.time() in production. - - Returns: - (should_surface, reason, updated_state) - """ - import time - - cfg = {**DEFAULT_CONFIG, **(config or {})} - now = current_time if current_time is not None else time.time() - - if not cfg.get("enabled", True): - return False, "Surfacing disabled", state - - threshold = cfg.get("threshold", 0.3) - if relevance_score < threshold: - return False, f"Below threshold ({relevance_score:.2f} < {threshold})", state - - max_surfaces = cfg.get("max_surfaces_per_session", 5) - if state.get("surfaces_count", 0) >= max_surfaces: - return False, f"Session budget exhausted ({max_surfaces}/{max_surfaces})", state - - min_messages = cfg.get("min_messages_between", 10) - messages_since = state.get("messages_since_last", 0) - last_time = state.get("last_surface_time", 0.0) - if last_time > 0 and messages_since < min_messages: - return False, f"Spacing not met ({messages_since}/{min_messages} messages)", state - - cooldown = cfg.get("cooldown_seconds", 300) - elapsed = now - last_time - if last_time > 0 and elapsed < cooldown: - remaining = int(cooldown - elapsed) - return False, f"Cooldown active ({remaining}s remaining)", state - - surfaced_ids = state.get("surfaced_ids", []) - if item_id in surfaced_ids: - return False, "Already surfaced this session", state - - updated = { - "surfaces_count": state.get("surfaces_count", 0) + 1, - "messages_since_last": 0, - "last_surface_time": now, - "surfaced_ids": list(surfaced_ids) + [item_id], - } - json_handler.log_operation( - "governance_surface", - {"item_id": item_id, "relevance_score": relevance_score, "surfaces_count": updated["surfaces_count"]}, - ) - return True, "Ready to surface", updated - - -# ============================================================================= -# MESSAGE TRACKING -# ============================================================================= - - -def record_message(state: Dict[str, Any]) -> Dict[str, Any]: - """ - Record that a message was processed. Pure — returns updated state. - - Args: - state: Current governance state. - - Returns: - New state dict with messages_since_last incremented. - """ - return { - **state, - "messages_since_last": state.get("messages_since_last", 0) + 1, - } +__all__ = ["should_surface", "record_message", "new_state", "DEFAULT_CONFIG"] # ============================================================================= @@ -194,6 +63,8 @@ def handle_command(command: str, args: list) -> bool: if command != "governance": return False + json_handler.log_operation("governance_command", {"args": args}) + if not args: print_introspection() return True