Files
Claude 4d178e2b7e feat(debug): record per-module run detail for WP Debug
Canvas Context runs now set an explain flag. With it, each trace row carries
the module's display name and a handler detail dict: which derivation branch
fired, with every test's result and the value it read; a wildcard's option,
odds, pool and the constraints that re-weighted it; a constraint's reach and
rule counts. Warnings a module raised name it as their owner, and trace rows,
warnings and nested @{} picks are tagged with the Context node's graph id.
WP Debug forwards the nested-pick log, node seeds, internal flags, rolled
axes, constraint hits and multi-pick items.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wwo837fQ1tm6RacaQGYom1
2026-09-29 14:00:48 +00:00

25 lines
914 B
Python

"""Per-module run detail for the WP Debug node.
A run opts in by setting ``ctx["__wp_explain__"] = True`` (the canvas Context
node does; the Test Runner's thousand-seed runs don't). The pipeline then gives
each module an empty ``ctx["__wp_module_detail__"]`` dict before it resolves and
files whatever the handler put there on that module's trace row as ``detail``.
Handlers call :func:`module_detail` and write only when it returns a dict, so
an un-opted run pays one dict lookup per module and nothing else.
"""
from __future__ import annotations
from typing import Any
EXPLAIN_KEY = "__wp_explain__"
DETAIL_KEY = "__wp_module_detail__"
def module_detail(ctx: Any) -> dict[str, Any] | None:
"""The active module's detail sink, or None when nobody asked for one."""
if not isinstance(ctx, dict):
return None
sink = ctx.get(DETAIL_KEY)
return sink if isinstance(sink, dict) else None