fix(ci): seedgo gate back to 100% — hooks handler logging + silent catches, memory governance modules/handlers split (import path frozen, bridge E2E green), devpulse README test count. All 17 branches 100%
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user