"""Session-quality filter (T01). The capture layer ingests *every* session it finds — including API health-checks, smoke-tests, and interrupted runs (e.g. ``llm-connect`` firing "Say hello in one word", or a transcript that is just ``[Request interrupted by user]``). These are not real coding work, but the outcome heuristic labels the short ones ``abandoned`` and the clusterer then mints false-positive "problem" patterns from them. :func:`is_real_coding_session` gates those out so Detect signals/clusters form only over genuine coding sessions. It is intentionally conservative — a session counts as real if it shows substantive activity, and is dropped only on clear trivial markers. Thresholds come from ``[detect.quality]`` in ``config.toml``. """ from __future__ import annotations from dataclasses import dataclass from typing import Optional # Prompt prefixes/markers that indicate a non-coding or interrupted session. _TRIVIAL_PROMPTS = ( "say hello", "hello", "[request interrupted", "return only this json", "ping", "ok", "", ) # Tool buckets that count as "substantive" coding activity. _SUBSTANTIVE_TOOLS = ( "Edit", "Write", "Read", "Bash", "search_replace", "write", "read_file", "run_terminal_command", "grep", "Grep", "glob", "Glob", "NotebookEdit", ) @dataclass class QualityConfig: min_events: int = 20 # below this, not a real coding session min_substantive: int = 3 # >= this many substantive tool calls required min_prompt_len: int = 25 # first prompt shorter than this is suspect def quality_config(config: Optional[dict] = None) -> QualityConfig: d = (config or {}).get("detect", {}).get("quality", {}) if config else {} return QualityConfig( min_events=d.get("min_events", 20), min_substantive=d.get("min_substantive", 3), min_prompt_len=d.get("min_prompt_len", 25), ) def _substantive_calls(digest: dict) -> int: hist = digest.get("tool_histogram") or {} return sum(n for t, n in hist.items() if t in _SUBSTANTIVE_TOOLS) def is_real_coding_session(digest: dict, config: Optional[QualityConfig] = None) -> bool: cfg = config or QualityConfig() if not digest.get("repo"): return False if digest.get("event_count", 0) < cfg.min_events: return False if _substantive_calls(digest) < cfg.min_substantive: return False prompt = (digest.get("first_prompt") or "").strip().lower() if len(prompt) < cfg.min_prompt_len: return False if any(prompt.startswith(p) for p in _TRIVIAL_PROMPTS): return False return True def filter_real(digests: list[dict], config: Optional[QualityConfig] = None) -> list[dict]: cfg = config or QualityConfig() return [d for d in digests if is_real_coding_session(d, cfg)]