core/proposal_review/summary.py: idle_summary(root=None) -> ProposalReviewIdleSummary composes the landed scan/build_report/dry_check into one JSON-safe primitives-only value (safe, total, review_needed, malformed, by_family tuple-of-pairs, errors). Pure read — no mutation, no clock, no runtime touch. errors carries the dry-check violations (why not safe); IT-b reuses it for a captured reporter exception. 7 tests incl. read-only + deterministic + json-serializable.
52 lines
2 KiB
Python
52 lines
2 KiB
Python
"""Pure idle-use summary of the proposal sink (IT-a).
|
|
|
|
``idle_summary`` composes the landed read-only pieces — ``scan`` (RPT-a) + ``build_report``
|
|
(RPT-b) + ``dry_check`` (RPT-c) — into one small, JSON-safe value the runtime's ``idle_tick``
|
|
can surface (IT-b) without importing the reporter's internals. Pure read: no mutation, no clock.
|
|
|
|
The summary is deliberately primitives-only (no paths, no raw file content, no mutable dicts) so
|
|
it is trivially serializable if ``IdleTickResult`` is ever persisted. ``errors`` carries the
|
|
reason the sink is not ``safe`` — the dry-check violations here; IT-b additionally uses it to
|
|
record a captured reporter exception (``proposal_review_failed:<type>``).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from core.proposal_review.report import build_report
|
|
from core.proposal_review.safety import dry_check
|
|
from core.proposal_review.scan import scan
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProposalReviewIdleSummary:
|
|
"""A JSON-safe snapshot of the proposal sink for idle surfacing. ``safe`` is the dry-check
|
|
verdict; ``errors`` is empty iff safe (or carries ``proposal_review_failed:<type>`` when the
|
|
reporter itself raised, set by the runtime sub-pass)."""
|
|
|
|
safe: bool
|
|
total: int
|
|
review_needed: int
|
|
malformed: int
|
|
by_family: tuple[tuple[str, int], ...]
|
|
errors: tuple[str, ...] = ()
|
|
|
|
|
|
def idle_summary(root: Path | None = None) -> ProposalReviewIdleSummary:
|
|
"""Scan → report → dry-check the proposal sink into a JSON-safe idle summary. Pure read."""
|
|
proposals, malformed = scan(root)
|
|
report = build_report(proposals, malformed)
|
|
verdict = dry_check(proposals, malformed, root=root)
|
|
return ProposalReviewIdleSummary(
|
|
safe=verdict.ok,
|
|
total=report.total,
|
|
review_needed=len(report.review_needed),
|
|
malformed=report.malformed,
|
|
by_family=tuple(report.by_family.items()),
|
|
errors=verdict.violations,
|
|
)
|
|
|
|
|
|
__all__ = ["ProposalReviewIdleSummary", "idle_summary"]
|