core/tests/test_discourse_planner_helper.py
Shay f03d7d04b3 refactor(runtime): collapse cold+warm planner hooks into one helper
Pre-cleanup before extending intent classification.  Extracts
``ChatRuntime._maybe_apply_discourse_planner(text, source_tag) ->
str | None`` and replaces the two duplicated blocks (cold-start
pack-grounded branch + warm post-walk branch) with single-line
``planned = ...; if planned is not None: assign`` call sites.

Signature locked: takes only the prompt and the already-classified
grounding source tag; returns the replacement surface or None.
Callers own assignment — the helper neither reads nor writes any
surface or articulation state.  The warm site additionally does the
``articulation = replace(articulation, surface=planned)`` follow-up
which the cold site does not need.

Gating discipline unchanged (re-pinned in 9 new tests):
* Returns None when ``self.config.discourse_planner`` is False.
* Returns None unless source_tag ∈ {"pack", "teaching"}.
* Returns None when the classified intent has no subject.
* Returns None on single-move plans (BRIEF mode / empty bundle).
* Returns None on empty rendered string.

Behavior is byte-identical to the pre-dedup state — same metrics:
  flag off: multi=0.1429, primed_multi=0.0000, conn=0.0769
  flag on : multi=0.5238, primed_multi=0.5000, conn=0.2308
cognition eval byte-identical: public 100/100/91.7/100.
smoke suite 67/67.

The two paths now cannot drift; the upcoming intent classifier
extension lifts both branches in lockstep.
2026-05-19 12:04:15 -07:00

69 lines
2.4 KiB
Python

"""Tests for ``ChatRuntime._maybe_apply_discourse_planner``.
Pins the single-helper contract that the cold and warm runtime hooks
both call. These tests are unit-level — they exercise the helper
directly with a real ``ChatRuntime``, without driving the full
``chat`` pipeline.
"""
from __future__ import annotations
import pytest
from chat.runtime import ChatRuntime
from core.config import RuntimeConfig
@pytest.fixture()
def runtime_flag_off() -> ChatRuntime:
return ChatRuntime(config=RuntimeConfig(discourse_planner=False))
@pytest.fixture()
def runtime_flag_on() -> ChatRuntime:
return ChatRuntime(config=RuntimeConfig(discourse_planner=True))
class TestPlannerHelperGating:
def test_flag_off_returns_none(self, runtime_flag_off: ChatRuntime) -> None:
result = runtime_flag_off._maybe_apply_discourse_planner(
"Tell me about truth.", "teaching"
)
assert result is None
@pytest.mark.parametrize("tag", ["vault", "none", "oov", "", "unknown"])
def test_non_grounded_source_returns_none(
self, runtime_flag_on: ChatRuntime, tag: str
) -> None:
result = runtime_flag_on._maybe_apply_discourse_planner(
"Tell me about truth.", tag
)
assert result is None
def test_empty_subject_returns_none(self, runtime_flag_on: ChatRuntime) -> None:
# An unclassified prompt has no head-noun subject.
result = runtime_flag_on._maybe_apply_discourse_planner("", "pack")
assert result is None
class TestPlannerHelperEngagement:
def test_returns_multi_clause_surface_on_grounded_subject(
self, runtime_flag_on: ChatRuntime
) -> None:
result = runtime_flag_on._maybe_apply_discourse_planner(
"Tell me about truth.", "teaching"
)
assert result is not None
# Multi-clause: at least one connective from the canonical table.
assert "Furthermore," in result or "In turn," in result
def test_returns_none_for_single_move_plan(
self, runtime_flag_on: ChatRuntime
) -> None:
# BRIEF mode (default for "What is X?") collapses to ANCHOR-only;
# helper must return None so callers don't replace the byte-
# identical single-sentence pack-grounded surface.
result = runtime_flag_on._maybe_apply_discourse_planner(
"What is truth?", "pack"
)
assert result is None