Skip to content

Include/Coverage Redesign + VocabBoost Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Replace the flat-boost Include/Coverage mechanism with density-weighted boosting, add VocabBoost, add session-level coverage tracking, and fix 6 governor-wide gaps.

Architecture: Unified boost-with-coverage pattern — one weight function per constraint type, two modes (static LogitBoost or stateful coverage projection). Session-level coverage tracking via CoverageTracker on the session store, priors injected through GovernorContext. New VocabBoostConstraint parallels IncludeConstraint with binary membership weighting.

Tech Stack: Python (diffusion_governors engine, FastAPI dashboard), TypeScript/React (dashboard frontend), PyTest, Vitest

Spec: docs/superpowers/specs/2026-03-17-include-coverage-redesign.md


Chunk 1: Engine Core — Density-Weighted Include + Coverage Unification

Task 1: Add prior_coverage to GovernorContext

Files: - Modify: packages/governors/src/diffusion_governors/core.py:12-22

  • [ ] Step 1: Add prior_coverage field to GovernorContext
# In core.py, add to GovernorContext dataclass:
prior_coverage: dict[str, tuple[int, int]] | None = None  # key → (content_count, target_count)
  • [ ] Step 2: Run existing tests to verify no regressions

Run: uv run pytest packages/governors/tests/ -v Expected: All existing tests pass (new field has default None)

  • [ ] Step 3: Commit
git add packages/governors/src/diffusion_governors/core.py
git commit -m "feat: add prior_coverage field to GovernorContext"

Task 2: Make _parse_phono importable from constraints.py

Files: - Modify: packages/governors/src/diffusion_governors/constraints.py:75-82

  • [ ] Step 1: Rename _parse_phono to parse_phono (drop underscore)

In constraints.py, rename the function at line 75 from _parse_phono to parse_phono. Update all call sites within constraints.py (search for _parse_phono and replace with parse_phono).

Also rename _MSH_STAGES to MSH_STAGES and _word_msh_stage to word_msh_stage (these will be needed by _check_compliance in Task 10). Update all internal call sites.

  • [ ] Step 2: Run existing tests

Run: uv run pytest packages/governors/tests/ -v Expected: All pass

  • [ ] Step 3: Commit
git add packages/governors/src/diffusion_governors/constraints.py
git commit -m "refactor: make parse_phono public for cross-module use"

Task 3: Write failing tests for density-weighted IncludeConstraint

Files: - Modify: packages/governors/tests/test_include.py

  • [ ] Step 1: Replace TestIncludeConstraint tests with density-aware versions

Replace the entire TestIncludeConstraint class with these tests. The key difference: instead of flat +strength for any matching token, the boost is density * strength where density = count(target ∩ token) / len(token_phonemes).

class TestIncludeConstraint:
    """Tests for density-weighted IncludeConstraint."""

    def test_compiles_to_boost_in_static_mode(self):
        """Without target_rate, produces a LogitBoost mechanism."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"})
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        assert len(gov.boosts) == 1
        assert len(gov.gates) == 0
        assert len(gov.projections) == 0
        assert isinstance(gov.boosts[0], LogitBoost)

    def test_density_weighting_single_phoneme(self):
        """Boost = (target_hits / total_phonemes) * strength.

        Token 0 "cat" [k,æ,t]: density = 1/3, boost = 1/3 * 2.0 ≈ 0.667
        Token 1 "rat" [ɹ,æ,t]: density = 0/3, boost = 0.0
        """
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"}, strength=2.0)
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, VOCAB_SIZE)
        ctx = GovernorContext()
        out = gov(logits, ctx)

        # "cat" [k,æ,t] → density 1/3 * 2.0
        assert out[0, 0, 0].item() == pytest.approx(2.0 / 3, abs=1e-5)
        # "rat" [ɹ,æ,t] → no /k/ → 0.0
        assert out[0, 0, 1].item() == pytest.approx(0.0)
        # "the" — no phono → 0.0
        assert out[0, 0, 3].item() == pytest.approx(0.0)

    def test_density_weighting_multi_occurrence(self):
        """Token with target phoneme appearing twice gets higher density."""
        from diffusion_governors.include import IncludeConstraint

        # "kick" has /k/ twice: [k, ɪ, k] → density = 2/3
        lookup = _make_lookup(**{
            "5": TokenFeatures(
                word="kick",
                phono=PhonoFeatures(phonemes=["k", "ɪ", "k"]),
            ),
        })
        c = IncludeConstraint(phonemes={"k"}, strength=3.0)
        gov = Governor.from_constraints(c, vocab_size=6, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, 6)
        ctx = GovernorContext()
        out = gov(logits, ctx)

        # "kick" [k,ɪ,k] → density 2/3 * 3.0
        assert out[0, 0, 5].item() == pytest.approx(2.0, abs=1e-5)
        # "cat" [k,æ,t] → density 1/3 * 3.0
        assert out[0, 0, 0].item() == pytest.approx(1.0, abs=1e-5)

    def test_multi_target_phonemes(self):
        """Multiple targets: density = count(targets ∩ token) / len(token)."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        # Targeting {k, t}: "cat" [k,æ,t] → 2 hits / 3 total = 0.667
        c = IncludeConstraint(phonemes={"k", "t"}, strength=3.0)
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, VOCAB_SIZE)
        ctx = GovernorContext()
        out = gov(logits, ctx)

        # "cat" [k,æ,t] → 2/3 * 3.0
        assert out[0, 0, 0].item() == pytest.approx(2.0, abs=1e-5)
        # "rat" [ɹ,æ,t] → 1/3 * 3.0 (just /t/)
        assert out[0, 0, 1].item() == pytest.approx(1.0, abs=1e-5)

    def test_no_matching_tokens_gives_zero_boost(self):
        """When no tokens match, all logits unchanged."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"ʒ"})
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, VOCAB_SIZE)
        ctx = GovernorContext()
        out = gov(logits, ctx)
        assert torch.allclose(out, logits)

    def test_mechanism_kind_static(self):
        """Static mode: mechanism_kind is 'boost'."""
        from diffusion_governors.include import IncludeConstraint

        c = IncludeConstraint(phonemes={"k"})
        assert c.mechanism_kind == "boost"

    def test_mechanism_kind_coverage(self):
        """Coverage mode: mechanism_kind is 'projection'."""
        from diffusion_governors.include import IncludeConstraint

        c = IncludeConstraint(phonemes={"k"}, target_rate=0.2)
        assert c.mechanism_kind == "projection"

    def test_default_strength(self):
        """Default strength is 2.0."""
        from diffusion_governors.include import IncludeConstraint

        c = IncludeConstraint(phonemes={"k"})
        assert c.strength == 2.0

    def test_uses_parse_phono(self):
        """Phono access uses parse_phono, not raw dict access."""
        from diffusion_governors.include import IncludeConstraint

        # Malformed phono dict that parse_phono would reject but raw dict.get would accept
        lookup = {"0": {"word": "test", "phono": "not_a_dict", "norms": {}}}
        c = IncludeConstraint(phonemes={"k"}, strength=2.0)
        # Should not crash — parse_phono returns None for non-dict phono
        gov = Governor.from_constraints(c, vocab_size=1, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, 1)
        out = gov(logits, GovernorContext())
        assert out[0, 0, 0].item() == pytest.approx(0.0)

    def test_exported_from_package(self):
        """IncludeConstraint importable from top-level package."""
        from diffusion_governors import IncludeConstraint  # noqa: F401
  • [ ] Step 2: Run tests to verify they fail

Run: uv run pytest packages/governors/tests/test_include.py::TestIncludeConstraint -v Expected: FAIL — density weighting tests fail against the current flat-boost implementation

  • [ ] Step 3: Commit failing tests
git add packages/governors/tests/test_include.py
git commit -m "test: add failing tests for density-weighted IncludeConstraint"

Task 4: Write failing tests for unified coverage mode

Files: - Modify: packages/governors/tests/test_include.py

  • [ ] Step 1: Replace TestCoverageConstraint with unified coverage tests

Replace the TestCoverageConstraint class. The key change: coverage is now a mode of IncludeConstraint (via target_rate parameter), not a separate class. Also tests prior_coverage on GovernorContext.

class TestIncludeCoverage:
    """Tests for IncludeConstraint in coverage mode (target_rate set)."""

    def test_compiles_to_projection(self):
        """With target_rate, produces a projection mechanism."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"ɹ"}, target_rate=0.5)
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        assert len(gov.projections) == 1
        assert len(gov.gates) == 0
        assert len(gov.boosts) == 0

    def test_boosts_when_below_target_with_density(self):
        """Below target → boost proportional to gap AND density."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"ɹ"}, target_rate=0.8, max_boost=5.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        # "sat" generated — no /ɹ/ → coverage = 0/1 = 0, gap = 0.8
        ctx = GovernorContext(token_ids=torch.tensor([[2]]))
        out = mechanism.apply(logits, ctx)

        # "rat" [ɹ,æ,t] has density 1/3 for /ɹ/
        # boost = density * max_boost * gap = (1/3) * 5.0 * 0.8
        expected_rat = (1.0 / 3.0) * 5.0 * 0.8
        assert out[0, 0, 1].item() == pytest.approx(expected_rat, abs=1e-4)
        # "cat" — no /ɹ/ → 0
        assert out[0, 0, 0].item() == pytest.approx(0.0)

    def test_no_boost_at_target_rate(self):
        """At or above target → zero boost."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"ɹ"}, target_rate=0.5, max_boost=5.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        # 1/2 content tokens have /ɹ/ → coverage = 0.5 = target
        ctx = GovernorContext(token_ids=torch.tensor([[1, 0]]))
        out = mechanism.apply(logits, ctx)
        assert torch.allclose(out, logits)

    def test_prior_coverage_seeds_gap(self):
        """Prior coverage counts from session shift the gap computation."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"}, target_rate=0.5, max_boost=4.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        # Prior: 10 content, 5 target → rate = 0.5, at target
        ctx = GovernorContext(
            token_ids=torch.tensor([[2]]),  # "sat" — 1 content, 0 target
            prior_coverage={mechanism.coverage_key: (10, 5)},
        )
        out = mechanism.apply(logits, ctx)
        # Total: 11 content, 5 target → rate = 5/11 ≈ 0.45 < 0.5 → small boost
        expected_gap = 0.5 - (5 / 11)
        assert out[0, 0, 0].item() > 0.0  # "cat" has /k/ → boosted
        assert out[0, 0, 1].item() == pytest.approx(0.0)  # "rat" no /k/

    def test_prior_coverage_at_target_suppresses_boost(self):
        """If prior coverage already meets target, no boost even with zero current."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"}, target_rate=0.3, max_boost=4.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        # Prior: 10 content, 5 target → rate = 0.5, above 0.3 target
        ctx = GovernorContext(
            token_ids=torch.tensor([[2]]),  # "sat"
            prior_coverage={mechanism.coverage_key: (10, 5)},
        )
        out = mechanism.apply(logits, ctx)
        # Total: 11 content, 5 target → 0.45 > 0.3 → no boost
        assert torch.allclose(out, logits)

    def test_count_tokens(self):
        """_CoverageMechanism.count_tokens returns content and target counts."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"}, target_rate=0.5)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        # Tokens: cat(0)=content+target, rat(1)=content, the(3)=not content
        content, target = mechanism.count_tokens([0, 1, 3])
        assert content == 2  # cat and rat have phonemes
        assert target == 1   # only cat has /k/

    def test_coverage_key_deterministic(self):
        """Same phonemes produce the same coverage key."""
        from diffusion_governors.include import IncludeConstraint

        c1 = IncludeConstraint(phonemes={"k", "t"}, target_rate=0.3)
        c2 = IncludeConstraint(phonemes={"t", "k"}, target_rate=0.3)
        lookup = _make_lookup()
        m1 = c1.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        m2 = c2.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        assert m1.coverage_key == m2.coverage_key

    def test_empty_generation_full_gap(self):
        """No tokens yet → full gap → full boost."""
        from diffusion_governors.include import IncludeConstraint

        lookup = _make_lookup()
        c = IncludeConstraint(phonemes={"k"}, target_rate=0.5, max_boost=4.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        ctx = GovernorContext(token_ids=None)
        out = mechanism.apply(logits, ctx)

        # Full gap = target_rate = 0.5
        # "cat" [k,æ,t] density 1/3 * 4.0 * 0.5
        expected = (1.0 / 3.0) * 4.0 * 0.5
        assert out[0, 0, 0].item() == pytest.approx(expected, abs=1e-4)
  • [ ] Step 2: Run tests to verify they fail

Run: uv run pytest packages/governors/tests/test_include.py::TestIncludeCoverage -v Expected: FAIL — IncludeConstraint doesn't accept target_rate yet

  • [ ] Step 3: Commit failing tests
git add packages/governors/tests/test_include.py
git commit -m "test: add failing tests for unified Include coverage mode with priors"

Task 5: Implement density-weighted IncludeConstraint with unified coverage

Files: - Rewrite: packages/governors/src/diffusion_governors/include.py

  • [ ] Step 1: Rewrite include.py with density weighting and unified dual-mode

Replace the entire file:

"""Include constraint — density-weighted boost with optional coverage tracking.

    IncludeConstraint(phonemes={"k"})               → static LogitBoost (density-weighted)
    IncludeConstraint(phonemes={"k"}, target_rate=0.2) → stateful coverage projection
"""

from __future__ import annotations

from typing import Any

import torch

from diffusion_governors.constraints import Constraint, parse_phono
from diffusion_governors.core import GovernorContext, Mechanism
from diffusion_governors.lookups import Lookup


def _compute_density_scores(
    phonemes: set[str], lookup: Lookup, vocab_size: int,
) -> tuple[dict[int, float], set[int], set[int]]:
    """Compute per-token density scores and identify content/target sets.

    Returns:
        scores: {token_id: density} for tokens with density > 0
        content_ids: all token IDs with non-empty phoneme lists
        target_ids: content tokens containing any target phoneme
    """
    scores: dict[int, float] = {}
    content_ids: set[int] = set()
    target_ids: set[int] = set()

    for tid_str, entry in lookup.items():
        tid = int(tid_str)
        if tid >= vocab_size:
            continue
        phono = parse_phono(entry)
        if phono is None or not phono.phonemes:
            continue

        content_ids.add(tid)
        token_phonemes = phono.phonemes
        hits = sum(1 for p in token_phonemes if p in phonemes)
        if hits > 0:
            target_ids.add(tid)
            scores[tid] = hits / len(token_phonemes)

    return scores, content_ids, target_ids


class _CoverageMechanism:
    """Stateful projection: tracks phoneme coverage and applies density-weighted boost.

    At each step, computes running coverage from prior session counts plus
    current generation tokens. When below target, boosts target tokens
    proportionally to gap and density.
    """

    def __init__(
        self,
        weights: torch.Tensor,
        target_ids: set[int],
        content_ids: set[int],
        target_rate: float,
        max_boost: float,
        coverage_key: str,
    ):
        self.weights = weights
        self.target_ids = target_ids
        self.content_ids = content_ids
        self.target_rate = target_rate
        self.max_boost = max_boost
        self.coverage_key = coverage_key

    def apply(self, logits: torch.Tensor, ctx: GovernorContext) -> torch.Tensor:
        gap = self._compute_gap(ctx)
        if gap <= 0.0:
            return logits
        return logits + self.weights.to(logits.device) * (self.max_boost * gap)

    def count_tokens(self, token_ids: list[int]) -> tuple[int, int]:
        """Count content and target tokens for post-generation tracker update."""
        content = sum(1 for t in token_ids if t in self.content_ids)
        target = sum(1 for t in token_ids if t in self.target_ids)
        return content, target

    def _compute_gap(self, ctx: GovernorContext) -> float:
        """Compute gap below target rate, incorporating session priors."""
        prior_content, prior_target = 0, 0
        if ctx.prior_coverage and self.coverage_key in ctx.prior_coverage:
            prior_content, prior_target = ctx.prior_coverage[self.coverage_key]

        current_content, current_target = self._count_current(ctx.token_ids)

        total_content = prior_content + current_content
        total_target = prior_target + current_target

        if total_content == 0:
            return self.target_rate

        current_rate = total_target / total_content
        return max(0.0, self.target_rate - current_rate)

    def _count_current(self, token_ids: torch.Tensor | None) -> tuple[int, int]:
        if token_ids is None:
            return 0, 0
        ids = token_ids.flatten().tolist()
        content = sum(1 for t in ids if t in self.content_ids)
        target = sum(1 for t in ids if t in self.target_ids)
        return content, target


class IncludeConstraint(Constraint):
    """Density-weighted boost for tokens containing target phonemes.

    Boost magnitude is proportional to phoneme density:
        weight(token) = count(target ∩ token_phonemes) / len(token_phonemes)

    Two modes:
        Static:   boost = weight * strength         (mechanism_kind = "boost")
        Coverage: boost = weight * max_boost * gap   (mechanism_kind = "projection")

    Coverage mode is activated by providing target_rate. Session-level coverage
    tracking is supported via GovernorContext.prior_coverage.
    """

    def __init__(
        self,
        phonemes: set[str],
        strength: float = 2.0,
        target_rate: float | None = None,
        max_boost: float = 3.0,
    ):
        self.phonemes = phonemes
        self.strength = strength
        self.target_rate = target_rate
        self.max_boost = max_boost

    @property
    def mechanism_kind(self) -> str:
        return "projection" if self.target_rate is not None else "boost"

    def build(self, **kwargs: Any) -> Mechanism:
        from diffusion_governors.boosts import LogitBoost

        vocab_size: int = kwargs["vocab_size"]
        lookup: Lookup = kwargs["lookup"]

        scores, content_ids, target_ids = _compute_density_scores(
            self.phonemes, lookup, vocab_size,
        )

        # Build weight tensor from density scores
        weights = torch.zeros(vocab_size)
        for tid, density in scores.items():
            weights[tid] = density

        if self.target_rate is not None:
            # Coverage mode — stateful projection
            coverage_key = f"include:{','.join(sorted(self.phonemes))}"
            return _CoverageMechanism(
                weights=weights,
                target_ids=target_ids,
                content_ids=content_ids,
                target_rate=self.target_rate,
                max_boost=self.max_boost,
                coverage_key=coverage_key,
            )
        else:
            # Static mode — precomputed LogitBoost
            return LogitBoost(weights, scale=self.strength)
  • [ ] Step 2: Run the Include tests

Run: uv run pytest packages/governors/tests/test_include.py -v Expected: All TestIncludeConstraint and TestIncludeCoverage tests pass

  • [ ] Step 3: Run all governor tests for regressions

Run: uv run pytest packages/governors/tests/ -v Expected: All pass

  • [ ] Step 4: Commit
git add packages/governors/src/diffusion_governors/include.py
git commit -m "feat: density-weighted IncludeConstraint with unified coverage mode"

Task 6: Remove Density, update exports

Files: - Modify: packages/governors/src/diffusion_governors/constraints.py:235-305 (delete Density class) - Modify: packages/governors/src/diffusion_governors/__init__.py (remove Density, CoverageConstraint exports)

  • [ ] Step 1: Delete Density class from constraints.py

Remove the entire Density class (lines 235-305). Also remove its import of CDDConstraint and CDDProjection if those imports become unused (check — they're likely still used by Bound).

  • [ ] Step 2: Update __init__.py exports

Remove Density and CoverageConstraint from imports and __all__. Keep IncludeConstraint (already exported).

  • [ ] Step 3: Update the Governor docstring example in core.py

In core.py line 50, replace Density("θ", min=0.30) with a valid current constraint example (e.g., IncludeConstraint(phonemes={"θ"}, target_rate=0.3)).

  • [ ] Step 4: Run all governor tests

Run: uv run pytest packages/governors/tests/ -v Expected: All pass

  • [ ] Step 5: Commit
git add packages/governors/src/diffusion_governors/constraints.py \
       packages/governors/src/diffusion_governors/__init__.py \
       packages/governors/src/diffusion_governors/core.py
git commit -m "refactor: remove deprecated Density, update exports for unified Include"

Task 7: Governor.get_coverage_mechanisms()

Files: - Modify: packages/governors/src/diffusion_governors/core.py

  • [ ] Step 1: Add get_coverage_mechanisms method to Governor
def get_coverage_mechanisms(self) -> dict[str, Any]:
    """Return coverage mechanisms keyed by coverage_key.

    Used by the route handler post-generation to call count_tokens().
    """
    result = {}
    for proj in self.projections:
        if hasattr(proj, "coverage_key") and hasattr(proj, "count_tokens"):
            result[proj.coverage_key] = proj
    return result
  • [ ] Step 2: Run all governor tests

Run: uv run pytest packages/governors/tests/ -v Expected: All pass

  • [ ] Step 3: Commit
git add packages/governors/src/diffusion_governors/core.py
git commit -m "feat: add Governor.get_coverage_mechanisms() for post-generation counting"

Chunk 2: VocabBoost Engine + Cleanup Fixes

Task 8: Write failing tests for VocabBoostConstraint

Files: - Create: packages/governors/tests/test_vocab_boost.py

  • [ ] Step 1: Write VocabBoost tests
"""Tests for VocabBoostConstraint."""

from __future__ import annotations

import torch
import pytest

from diffusion_governors.core import Governor, GovernorContext
from diffusion_governors.boosts import LogitBoost
from diffusion_governors.lookups import PhonoFeatures, TokenFeatures


def _make_lookup(**overrides) -> dict[str, dict]:
    """Lookup with vocab memberships for testing."""
    entries = {
        "0": TokenFeatures(
            word="cat",
            phono=PhonoFeatures(phonemes=["k", "æ", "t"]),
            vocab_memberships={"ogden_basic", "dolch"},
        ),
        "1": TokenFeatures(
            word="rat",
            phono=PhonoFeatures(phonemes=["ɹ", "æ", "t"]),
            vocab_memberships={"ogden_basic"},
        ),
        "2": TokenFeatures(
            word="sat",
            phono=PhonoFeatures(phonemes=["s", "æ", "t"]),
            vocab_memberships=set(),
        ),
        "3": TokenFeatures(
            word="the",
            vocab_memberships={"dolch", "ogden_basic"},
        ),
        "4": TokenFeatures(
            word="bat",
            phono=PhonoFeatures(phonemes=["b", "æ", "t"]),
            vocab_memberships=set(),
        ),
    }
    entries.update(overrides)
    return {k: v.to_dict() for k, v in entries.items()}


VOCAB_SIZE = 5


class TestVocabBoostStatic:
    """Tests for VocabBoostConstraint in static mode."""

    def test_compiles_to_boost(self):
        from diffusion_governors.include import VocabBoostConstraint

        lookup = _make_lookup()
        c = VocabBoostConstraint(lists={"ogden_basic"})
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        assert len(gov.boosts) == 1
        assert isinstance(gov.boosts[0], LogitBoost)

    def test_boosts_list_members(self):
        from diffusion_governors.include import VocabBoostConstraint

        lookup = _make_lookup()
        c = VocabBoostConstraint(lists={"ogden_basic"}, strength=2.0)
        gov = Governor.from_constraints(c, vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        logits = torch.zeros(1, 1, VOCAB_SIZE)
        out = gov(logits, GovernorContext())

        # cat(0)=ogden, rat(1)=ogden, the(3)=ogden → boosted
        assert out[0, 0, 0].item() == pytest.approx(2.0)
        assert out[0, 0, 1].item() == pytest.approx(2.0)
        assert out[0, 0, 3].item() == pytest.approx(2.0)
        # sat(2), bat(4) not in ogden → 0
        assert out[0, 0, 2].item() == pytest.approx(0.0)
        assert out[0, 0, 4].item() == pytest.approx(0.0)

    def test_mechanism_kind_static(self):
        from diffusion_governors.include import VocabBoostConstraint

        c = VocabBoostConstraint(lists={"ogden_basic"})
        assert c.mechanism_kind == "boost"

    def test_mechanism_kind_coverage(self):
        from diffusion_governors.include import VocabBoostConstraint

        c = VocabBoostConstraint(lists={"ogden_basic"}, target_rate=0.6)
        assert c.mechanism_kind == "projection"

    def test_requires_lists_or_words(self):
        from diffusion_governors.include import VocabBoostConstraint

        with pytest.raises(ValueError):
            VocabBoostConstraint()

    def test_words_require_tokenizer(self):
        from diffusion_governors.include import VocabBoostConstraint

        lookup = _make_lookup()
        c = VocabBoostConstraint(words={"cat", "bat"})
        with pytest.raises(ValueError, match="tokenizer"):
            c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)


class TestVocabBoostCoverage:
    """Tests for VocabBoostConstraint in coverage mode."""

    def test_boosts_when_below_target(self):
        from diffusion_governors.include import VocabBoostConstraint

        lookup = _make_lookup()
        c = VocabBoostConstraint(lists={"dolch"}, target_rate=0.8, max_boost=4.0)
        mechanism = c.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)

        logits = torch.zeros(1, 1, VOCAB_SIZE)
        # Token 2 "sat" not in dolch → coverage = 0/1 = 0
        ctx = GovernorContext(token_ids=torch.tensor([[2]]))
        out = mechanism.apply(logits, ctx)

        # cat(0) in dolch → boosted (weight=1.0 * max_boost * gap)
        assert out[0, 0, 0].item() > 0.0
        # sat(2) not in dolch → not boosted
        assert out[0, 0, 2].item() == pytest.approx(0.0)

    def test_coverage_key_deterministic(self):
        from diffusion_governors.include import VocabBoostConstraint

        c1 = VocabBoostConstraint(lists={"dolch", "ogden_basic"}, target_rate=0.5)
        c2 = VocabBoostConstraint(lists={"ogden_basic", "dolch"}, target_rate=0.5)
        lookup = _make_lookup()
        m1 = c1.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        m2 = c2.build(vocab_size=VOCAB_SIZE, device="cpu", lookup=lookup)
        assert m1.coverage_key == m2.coverage_key

    def test_exported_from_package(self):
        from diffusion_governors import VocabBoostConstraint  # noqa: F401
  • [ ] Step 2: Run tests — verify they fail

Run: uv run pytest packages/governors/tests/test_vocab_boost.py -v Expected: FAIL — VocabBoostConstraint doesn't exist yet

  • [ ] Step 3: Commit failing tests
git add packages/governors/tests/test_vocab_boost.py
git commit -m "test: add failing tests for VocabBoostConstraint"

Task 9: Implement VocabBoostConstraint

Files: - Modify: packages/governors/src/diffusion_governors/include.py - Modify: packages/governors/src/diffusion_governors/__init__.py

  • [ ] Step 1: Add VocabBoostConstraint to include.py

Append after IncludeConstraint:

class VocabBoostConstraint(Constraint):
    """Soft vocabulary targeting — boost tokens from target word lists.

    Binary membership weighting: tokens in the target vocab get weight 1.0,
    others get 0.0. Same dual-mode pattern as IncludeConstraint.

    Args:
        lists: Named vocab sets (matched against vocab_memberships in lookup).
        words: Explicit word strings (single-token resolution via tokenizer).
        strength: Static mode boost. Default 2.0.
        target_rate: Coverage mode target (0.0-1.0). Activates stateful tracking.
        max_boost: Maximum boost in coverage mode. Default 3.0.
    """

    def __init__(
        self,
        lists: set[str] | None = None,
        words: set[str] | None = None,
        strength: float = 2.0,
        target_rate: float | None = None,
        max_boost: float = 3.0,
        include_punctuation: bool = True,
    ):
        if not lists and not words:
            raise ValueError("VocabBoostConstraint requires at least one of 'lists' or 'words'")
        self.lists = lists or set()
        self.words = words or set()
        self.strength = strength
        self.target_rate = target_rate
        self.max_boost = max_boost
        self.include_punctuation = include_punctuation

    @property
    def mechanism_kind(self) -> str:
        return "projection" if self.target_rate is not None else "boost"

    def build(self, **kwargs) -> Mechanism:
        from diffusion_governors.boosts import LogitBoost

        vocab_size: int = kwargs["vocab_size"]
        lookup: Lookup = kwargs["lookup"]
        tokenizer = kwargs.get("tokenizer")

        if self.words and tokenizer is None:
            raise ValueError("VocabBoostConstraint with 'words' requires tokenizer in build kwargs")

        # Build target token set
        target_ids: set[int] = set()
        content_ids: set[int] = set()

        # From lists: check vocab_memberships
        for tid_str, entry in lookup.items():
            tid = int(tid_str)
            if tid >= vocab_size:
                continue
            content_ids.add(tid)
            if self.lists:
                memberships = set(entry.get("vocab_memberships", []))
                if memberships & self.lists:
                    target_ids.add(tid)

        # From words: single-token resolution
        if self.words and tokenizer is not None:
            for word in self.words:
                ids = tokenizer.encode(word, add_special_tokens=False)
                if len(ids) == 1 and ids[0] < vocab_size:
                    target_ids.add(ids[0])

        # Build weight tensor (binary: 1.0 for target, 0.0 for others)
        weights = torch.zeros(vocab_size)
        for tid in target_ids:
            weights[tid] = 1.0

        if self.target_rate is not None:
            coverage_key_parts = [
                f"vocab_boost",
                ",".join(sorted(self.lists)) if self.lists else "",
                ",".join(sorted(self.words)) if self.words else "",
            ]
            coverage_key = ":".join(coverage_key_parts)
            return _CoverageMechanism(
                weights=weights,
                target_ids=target_ids,
                content_ids=content_ids,
                target_rate=self.target_rate,
                max_boost=self.max_boost,
                coverage_key=coverage_key,
            )
        else:
            return LogitBoost(weights, scale=self.strength)
  • [ ] Step 2: Add VocabBoostConstraint to __init__.py exports

Add to imports and __all__.

  • [ ] Step 3: Run VocabBoost tests

Run: uv run pytest packages/governors/tests/test_vocab_boost.py -v Expected: All pass

  • [ ] Step 4: Run all governor tests

Run: uv run pytest packages/governors/tests/ -v Expected: All pass

  • [ ] Step 5: Commit
git add packages/governors/src/diffusion_governors/include.py \
       packages/governors/src/diffusion_governors/__init__.py
git commit -m "feat: add VocabBoostConstraint with binary membership weighting"

Task 10: Cleanup fixes (MSH compliance, MaxOpp 422, Density docstring)

Files: - Modify: packages/dashboard/server/model.py:33-73 (add MSH to _check_compliance) - Modify: packages/dashboard/server/governor.py:57-92 (catch MaxOpp ValueError)

  • [ ] Step 1: Add MSH to _check_compliance in model.py

After the ComplexityConstraint check block (after line 65), add:

        elif isinstance(c, MSHConstraint):
            if phono:
                from diffusion_governors.constraints import MSH_STAGES
                for phoneme in phono.get("phonemes", []):
                    stage = MSH_STAGES.get(phoneme, 5)
                    if stage > c.max_stage:
                        violations.append(f"/{phoneme}/ is MSH stage {stage} > {c.max_stage}")
                        break

Add MSHConstraint to the imports at the top of model.py.

  • [ ] Step 2: Catch MaxOpp ValueError in governor.py

Wrap the MaxOppositionBoost conversion in _to_dg_constraint:

    elif isinstance(c, MaxOppositionBoostConstraint):
        try:
            return MaxOppositionBoost(target=c.target, contrast=c.contrast, boost=c.strength)
        except ValueError as e:
            from fastapi import HTTPException
            raise HTTPException(status_code=422, detail=str(e))
  • [ ] Step 3: Run dashboard server tests

Run: uv run pytest packages/dashboard/server/tests/ -v Expected: All pass

  • [ ] Step 4: Commit
git add packages/dashboard/server/model.py packages/dashboard/server/governor.py
git commit -m "fix: add MSH to compliance check, MaxOpp validation returns 422"

Chunk 3: Dashboard Server — Schema, Governor Bridge, Session Coverage

Task 11: Update server schemas

Files: - Modify: packages/dashboard/server/schemas.py

  • [ ] Step 1: Modify IncludeConstraint schema — add target_rate and max_boost

Replace the current IncludeConstraint (lines 38-41) with:

class IncludeConstraint(BaseModel):
    type: Literal["include"] = "include"
    phonemes: list[str]
    strength: float = 2.0
    target_rate: float | None = None
    max_boost: float = 3.0

    @field_validator("target_rate")
    @classmethod
    def validate_target_rate(cls, v):
        if v is not None and not (0.0 <= v <= 1.0):
            raise ValueError("target_rate must be between 0.0 and 1.0")
        return v

    @model_validator(mode="after")
    def check_mode_exclusivity(self):
        if self.target_rate is not None and self.strength != 2.0:
            raise ValueError("Cannot set both strength (non-default) and target_rate")
        return self

Add field_validator, model_validator to imports from pydantic.

  • [ ] Step 2: Remove CoverageConstraint schema (lines 44-48)

Delete the class entirely.

  • [ ] Step 3: Add VocabBoostConstraint schema
class VocabBoostConstraint(BaseModel):
    type: Literal["vocab_boost"] = "vocab_boost"
    lists: list[str] | None = None
    words: list[str] | None = None
    strength: float = 2.0
    target_rate: float | None = None
    max_boost: float = 3.0
    include_punctuation: bool = True

    @field_validator("target_rate")
    @classmethod
    def validate_target_rate(cls, v):
        if v is not None and not (0.0 <= v <= 1.0):
            raise ValueError("target_rate must be between 0.0 and 1.0")
        return v
  • [ ] Step 4: Update Constraint union (lines 91-102)

Remove CoverageConstraint, add VocabBoostConstraint.

  • [ ] Step 5: Run schema tests

Run: uv run pytest packages/dashboard/server/tests/test_schemas.py -v Expected: Pass (may need test updates if tests reference CoverageConstraint)

  • [ ] Step 6: Commit
git add packages/dashboard/server/schemas.py
git commit -m "feat: unified Include schema, add VocabBoost, remove CoverageConstraint"

Task 12: Update governor bridge (governor.py)

Files: - Modify: packages/dashboard/server/governor.py

  • [ ] Step 1: Update imports

Replace:

from diffusion_governors import (
    ...
    IncludeConstraint as DGInclude,
    CoverageConstraint as DGCoverage,
    ...
)
from server.schemas import (
    ...
    IncludeConstraint, CoverageConstraint as SchemaCoverage,
    ...
)

With:

from diffusion_governors import (
    ...
    IncludeConstraint as DGInclude,
    VocabBoostConstraint as DGVocabBoost,
    ...
)
from server.schemas import (
    ...
    IncludeConstraint, VocabBoostConstraint,
    ...
)

  • [ ] Step 2: Update _to_dg_constraint for unified Include

Replace the Include + Coverage cases:

    elif isinstance(c, IncludeConstraint):
        kwargs = dict(phonemes=set(c.phonemes), strength=c.strength)
        if c.target_rate is not None:
            kwargs["target_rate"] = c.target_rate
            kwargs["max_boost"] = c.max_boost
        return DGInclude(**kwargs)
    elif isinstance(c, VocabBoostConstraint):
        kwargs = {}
        if c.lists is not None:
            kwargs["lists"] = set(c.lists)
        if c.words is not None:
            kwargs["words"] = set(c.words)
        kwargs["strength"] = c.strength
        if c.target_rate is not None:
            kwargs["target_rate"] = c.target_rate
            kwargs["max_boost"] = c.max_boost
        return DGVocabBoost(**kwargs)

Remove the old SchemaCoverage case.

  • [ ] Step 3: Update HFGovernorProcessor.reset() to accept prior_coverage
def reset(self, total_steps: int | None = None,
          prior_coverage: dict[str, tuple[int, int]] | None = None) -> None:
    """Reset step counter for a new generation, optionally inject session priors."""
    self._step = 0
    self._prior_coverage = prior_coverage
    if total_steps is not None:
        self.total_steps = total_steps

Update __init__ to initialize self._prior_coverage = None.

Update __call__ to pass prior_coverage into GovernorContext:

ctx = GovernorContext(
    step=self._step,
    total_steps=self.total_steps,
    token_ids=input_ids,
    device=scores.device,
    prior_coverage=self._prior_coverage,
)

  • [ ] Step 4: Add get_coverage_counters() to HFGovernorProcessor
def get_coverage_counters(self) -> dict:
    """Delegate to Governor.get_coverage_mechanisms()."""
    return self.governor.get_coverage_mechanisms()
  • [ ] Step 4b: Add coverage_key_for() utility function

Add a module-level function in governor.py that computes the same deterministic key from schema constraints. This is needed by the route handler for tracker lifecycle (dropping trackers when constraints are removed):

def coverage_key_for(c) -> str | None:
    """Compute coverage key from a schema constraint. Returns None if not a coverage constraint."""
    if isinstance(c, IncludeConstraint) and c.target_rate is not None:
        return f"include:{','.join(sorted(c.phonemes))}"
    elif isinstance(c, VocabBoostConstraint) and c.target_rate is not None:
        lists_part = ",".join(sorted(c.lists)) if c.lists else ""
        words_part = ",".join(sorted(c.words)) if c.words else ""
        return f"vocab_boost:{lists_part}:{words_part}"
    return None
  • [ ] Step 5: Update test_governor.py for CoverageConstraint removal

In packages/dashboard/server/tests/test_governor.py:

Replace the import (line 7): remove CoverageConstraint, it no longer exists.

Replace test_build_governor_coverage (lines 158-162) with:

def test_build_governor_include_coverage(lookup):
    """Include with target_rate produces a projection."""
    constraints = [IncludeConstraint(type="include", phonemes=["s"], target_rate=0.3)]
    gov = build_governor(constraints, lookup, VOCAB_SIZE)
    assert gov is not None
    assert len(gov.projections) == 1

  • [ ] Step 6: Run server tests

Run: uv run pytest packages/dashboard/server/tests/ -v Expected: Pass (update any tests that referenced CoverageConstraint)

  • [ ] Step 6: Commit
git add packages/dashboard/server/governor.py
git commit -m "feat: update governor bridge for unified Include, VocabBoost, prior_coverage"

Task 13: Add CoverageTracker and wire into generation route

Files: - Modify: packages/dashboard/server/sessions.py - Modify: packages/dashboard/server/routes/generate.py

  • [ ] Step 1: Add CoverageTracker to sessions.py

Add after imports:

@dataclass
class CoverageTracker:
    """Tracks phoneme/vocab coverage across session turns."""
    content_count: int = 0
    target_count: int = 0

Add a coverage tracking dict to SessionStore:

def __init__(self, persistence_path=None):
    self._sessions: dict[str, Session] = {}
    self._coverage: dict[str, dict[str, CoverageTracker]] = {}  # session_id → {key → tracker}
    ...

def get_coverage(self, session_id: str) -> dict[str, CoverageTracker]:
    return self._coverage.get(session_id, {})

def update_coverage(self, session_id: str, key: str, content: int, target: int):
    if session_id not in self._coverage:
        self._coverage[session_id] = {}
    if key not in self._coverage[session_id]:
        self._coverage[session_id][key] = CoverageTracker()
    tracker = self._coverage[session_id][key]
    tracker.content_count += content
    tracker.target_count += target

def reset_coverage(self, session_id: str):
    """Drop all coverage trackers for a session."""
    self._coverage.pop(session_id, None)

Add from dataclasses import dataclass to imports.

  • [ ] Step 2: Wire coverage into /generate route

In routes/generate.py, before generation:

# Build prior coverage from session trackers
trackers = session_store.get_coverage(req.session_id)
prior_coverage = {
    key: (t.content_count, t.target_count) for key, t in trackers.items()
} if trackers else None

processor = governor_cache.get_processor(
    constraints, lookup, vocab_size, tokenizer=model.get_tokenizer(),
)

# Reset processor with priors before generation
if processor is not None:
    for p in processor:
        if hasattr(p, "reset"):
            p.reset(prior_coverage=prior_coverage)

After generation, update trackers:

# Update session coverage trackers
if processor is not None:
    for p in processor:
        if hasattr(p, "get_coverage_counters"):
            for key, mechanism in p.get_coverage_counters().items():
                content, target = mechanism.count_tokens(gen_ids)
                session_store.update_coverage(req.session_id, key, content, target)
  • [ ] Step 2b: Add reset() to /generate-single route

The /generate-single endpoint is sessionless, so prior_coverage=None, but the step counter still needs resetting (the GovernorCache reuses processor instances):

# In generate_single(), before generation:
if processor is not None:
    for p in processor:
        if hasattr(p, "reset"):
            p.reset(prior_coverage=None)
  • [ ] Step 3: Run server tests

Run: uv run pytest packages/dashboard/server/tests/ -v Expected: All pass

  • [ ] Step 4: Commit
git add packages/dashboard/server/sessions.py \
       packages/dashboard/server/routes/generate.py
git commit -m "feat: session-level coverage tracking with CoverageTracker"

Chunk 4: Frontend — Types, Parser, Compiler, ConstraintBar

Task 14: Update frontend types

Files: - Modify: packages/dashboard/frontend/src/types.ts

  • [ ] Step 1: Remove CoverageConstraint type, update IncludeConstraint

Replace the IncludeConstraint and CoverageConstraint interfaces (lines 52-62) with:

export interface IncludeConstraint {
  type: "include";
  phonemes: string[];
  strength: number;
  target_rate?: number;
  max_boost?: number;
}

Delete CoverageConstraint interface.

  • [ ] Step 2: Add VocabBoostConstraint interface
export interface VocabBoostConstraint {
  type: "vocab_boost";
  lists?: string[];
  words?: string[];
  strength: number;
  target_rate?: number;
  max_boost?: number;
}
  • [ ] Step 3: Update Constraint union and ConstraintType

Remove "coverage" from ConstraintType, add "vocab_boost". Update the Constraint union to include VocabBoostConstraint and remove CoverageConstraint.

  • [ ] Step 4: Update StoreEntry union

Replace the "include" and "coverage" variants:

  | { type: "include"; phoneme: string; strength: number; targetRate?: number }

Add vocab-boost:

  | { type: "vocab_boost"; lists?: string[]; words?: string[]; targetRate?: number }

Remove the "coverage" variant.

  • [ ] Step 5: Commit
git add packages/dashboard/frontend/src/types.ts
git commit -m "feat: update frontend types — unified Include, add VocabBoost, remove Coverage"

Task 15: Update parser

Files: - Modify: packages/dashboard/frontend/src/commands/parser.ts - Modify: packages/dashboard/frontend/src/commands/registry.ts

  • [ ] Step 1: Update parseInclude to emit unified entries

Replace the current parseInclude (lines 111-164). The key change: instead of emitting separate "include" and "coverage" entries, emit "include" entries with optional targetRate.

The changes are minimal — keep the existing structure, just change the entry types:

  1. Replace type: "coverage" as const with type: "include" as const in the coverage-mode branch (line 142)
  2. Add strength: 2.0 to coverage-mode entries
  3. Keep targetRate on the entry (renamed from a separate type to an optional field)
  4. Remove the separate coverage branch — both paths emit type: "include" entries

The existing parseInclude function (lines 111-164) already handles percentage detection correctly. The only change is replacing lines 139-151 (coverage branch):

  if (targetRate !== null) {
    // Coverage mode — same entry type, with targetRate
    const entries: StoreEntry[] = phonemes.map((p) => ({
      type: "include" as const,
      phoneme: p,
      strength: 2.0,
      targetRate,
    }));
    return {
      type: "add",
      entries,
      confirmation: `Include ${formatPhonemeList(phonemes)} ~${targetRate}% (approximate)`,
    };
  }

The bare-include branch (lines 153-163) stays unchanged.

  • [ ] Step 2: Add parseVocabBoost function
// Known vocab list names (from vocab_memberships in the lookup).
// Used by parseVocabBoost to distinguish list names from ad-hoc words.
const KNOWN_VOCAB_LISTS = new Set([
  "ogden_basic", "ogden_international", "ogden_all",
  "dolch", "fry_1000", "swadesh_207",
  "gsl_1000", "gsl_2000", "gsl_all",
  "avl_all", "roget_1000",
]);

function parseVocabBoost(args: string[]): ParseResult {
  if (args.length === 0) {
    return { type: "error", message: "Usage: /vocab-boost <list|word>... [N%] — type /help vocab-boost for syntax" };
  }

  // Check for percentage suffix
  let targetRate: number | undefined;
  const lastArg = args[args.length - 1];
  const pctMatch = lastArg.match(/^(\d+(?:\.\d+)?)%$/);
  if (pctMatch) {
    targetRate = parseFloat(pctMatch[1]);
    args = args.slice(0, -1);
  }

  if (args.length === 0) {
    return { type: "error", message: "No lists or words specified" };
  }

  // Distinguish list names from words: known list names or underscored args → lists
  const lists: string[] = [];
  const words: string[] = [];
  for (const arg of args) {
    if (KNOWN_VOCAB_LISTS.has(arg.toLowerCase()) || arg.includes("_")) {
      lists.push(arg.toLowerCase());
    } else {
      words.push(arg.toLowerCase());
    }
  }

  const entry: StoreEntry = {
    type: "vocab_boost" as const,
    ...(lists.length > 0 ? { lists } : {}),
    ...(words.length > 0 ? { words } : {}),
    ...(targetRate !== undefined ? { targetRate } : {}),
  };

  const label = [...lists, ...words].join(", ");
  const mode = targetRate !== undefined ? ` ~${targetRate}%` : "";
  return {
    type: "add",
    entries: [entry],
    confirmation: `Vocab boost: ${label}${mode}`,
  };
}
  • [ ] Step 3: Add "vocab-boost" to command dispatch in parseCommand

In the switch statement, add:

case "vocab-boost":
  return parseVocabBoost(args);

  • [ ] Step 3b: Add vocab-boost to parseRemove

In the parseRemove switch statement (around line 361), add:

    case "vocab-boost":
      return { type: "remove", targetType: "vocab_boost", confirmation: "Removed Vocab boost" };

Also: the existing /remove include case (lines 396-411) currently has a comment about removing "coverage" entries too. Remove that comment — after unification, /remove include removes all Include entries (with or without targetRate). The matchFields logic already handles this correctly since it matches on { phoneme: p } only.

  • [ ] Step 4: Add "vocab-boost" to VERBS in registry.ts

Add to the VERBS tuple and add a help text entry.

  • [ ] Step 5: Expand NORM_COMMANDS in registry.ts

Add new norm entries to the NORM_COMMANDS array:

// Psycholinguistic norms
{ verb: "dominance", normKey: "dominance", defaultDirection: "min", description: "Dominance rating" },
{ verb: "socialness", normKey: "socialness", defaultDirection: "min", description: "Socialness rating" },
{ verb: "boi", normKey: "boi", defaultDirection: "min", description: "Body-object interaction" },
{ verb: "iconicity", normKey: "iconicity", defaultDirection: "min", description: "Iconicity rating" },
{ verb: "semantic-diversity", normKey: "semantic_diversity", defaultDirection: "max", description: "Semantic diversity" },
{ verb: "contextual-diversity", normKey: "contextual_diversity", defaultDirection: "min", description: "Contextual diversity" },
{ verb: "lexical-decision-rt", normKey: "lexical_decision_rt", defaultDirection: "max", description: "Lexical decision RT (ms)" },
// Sensorimotor dimensions (Lancaster norms)
{ verb: "auditory", normKey: "auditory", defaultDirection: "min", description: "Auditory strength" },
{ verb: "gustatory", normKey: "gustatory", defaultDirection: "min", description: "Gustatory strength" },
{ verb: "haptic", normKey: "haptic", defaultDirection: "min", description: "Haptic strength" },
{ verb: "interoceptive", normKey: "interoceptive", defaultDirection: "min", description: "Interoceptive strength" },
{ verb: "olfactory", normKey: "olfactory", defaultDirection: "min", description: "Olfactory strength" },
{ verb: "visual", normKey: "visual", defaultDirection: "min", description: "Visual strength" },
{ verb: "foot-leg", normKey: "foot_leg", defaultDirection: "min", description: "Foot/leg action strength" },
{ verb: "hand-arm", normKey: "hand_arm", defaultDirection: "min", description: "Hand/arm action strength" },
{ verb: "head", normKey: "head", defaultDirection: "min", description: "Head action strength" },
{ verb: "mouth", normKey: "mouth", defaultDirection: "min", description: "Mouth action strength" },
{ verb: "torso", normKey: "torso", defaultDirection: "min", description: "Torso action strength" },

  • [ ] Step 6: Run parser tests

Run: cd packages/dashboard/frontend && npx vitest run Expected: Some tests may fail due to "coverage" entry type removal — update those next

  • [ ] Step 7: Commit
git add packages/dashboard/frontend/src/commands/parser.ts \
       packages/dashboard/frontend/src/commands/registry.ts
git commit -m "feat: update parser — unified Include, add vocab-boost, expand norms"

Task 16: Update compiler and ConstraintBar

Files: - Modify: packages/dashboard/frontend/src/commands/compiler.ts - Modify: packages/dashboard/frontend/src/components/ConstraintBar/index.tsx - Modify: packages/dashboard/frontend/src/store/constraintStore.ts (if needed)

  • [ ] Step 1: Update compileConstraints in compiler.ts

Replace the Include and Coverage compilation sections (lines 46-59). Key changes: - Remove the separate "coverage" case - "include" entries with targetRate emit target_rate: entry.targetRate / 100 (percentage to rate conversion) - Add "vocab_boost" compilation

// Include: group by phoneme, each entry becomes one constraint
const includes = entries.filter((e) => e.type === "include");
for (const inc of includes) {
  const c: any = { type: "include", phonemes: [inc.phoneme], strength: inc.strength };
  if (inc.targetRate !== undefined) {
    c.target_rate = inc.targetRate / 100;
    c.max_boost = 3.0;
  }
  result.push(c);
}

// VocabBoost
const vocabBoosts = entries.filter((e) => e.type === "vocab_boost");
for (const vb of vocabBoosts) {
  const c: any = { type: "vocab_boost" };
  if (vb.lists) c.lists = vb.lists;
  if (vb.words) c.words = vb.words;
  if (vb.targetRate !== undefined) {
    c.target_rate = vb.targetRate / 100;
    c.max_boost = 3.0;
  } else {
    c.strength = 2.0;
  }
  result.push(c);
}
  • [ ] Step 2: Update chipCategory and chipLabel in ConstraintBar

Remove all "coverage" cases from chipLabel (line 20-21), chipCategory (line 53), and matchFields (line 76-77). TypeScript will error on unreachable cases once the StoreEntry union drops "coverage".

Update the "include" case in chipLabel to show targetRate when present:

case "include":
  return entry.targetRate ? `/${entry.phoneme}/ ~${entry.targetRate}%` : `/${entry.phoneme}/`;

Add "vocab_boost" handling to chipCategory:

case "vocab_boost":
  return "vocab";

Add to chipLabel:

case "vocab_boost": {
  const items = entry.words?.join(", ") || entry.lists?.join(", ") || "";
  return entry.targetRate ? `${items} ~${entry.targetRate}%` : items;
}

Add to matchFields:

case "vocab_boost":
  return {};  // Remove all vocab-boost entries at once

  • [ ] Step 3: Update parseRemove to handle vocab-boost

Add a case for "vocab-boost" in the remove parser that removes entries with type: "vocab_boost".

  • [ ] Step 4: Run frontend tests

Run: cd packages/dashboard/frontend && npx vitest run Expected: All pass (update any tests that reference "coverage" entry type)

  • [ ] Step 5: Commit
git add packages/dashboard/frontend/src/commands/compiler.ts \
       packages/dashboard/frontend/src/components/ConstraintBar/index.tsx
git commit -m "feat: update compiler and ConstraintBar for unified Include + VocabBoost"

Task 17: Update frontend tests

Files: - Modify: packages/dashboard/frontend/src/commands/__tests__/parser.test.ts - Modify: packages/dashboard/frontend/src/commands/__tests__/compiler.test.ts

  • [ ] Step 1: Update parser tests

Replace any tests that assert type: "coverage" entries with type: "include" entries that have targetRate. Add tests for /vocab-boost.

  • [ ] Step 2: Update compiler tests

Replace tests for coverage compilation. Add tests for VocabBoost compilation and percentage-to-rate conversion.

  • [ ] Step 3: Run all frontend tests

Run: cd packages/dashboard/frontend && npx vitest run Expected: All pass

  • [ ] Step 4: Commit
git add packages/dashboard/frontend/src/commands/__tests__/
git commit -m "test: update parser and compiler tests for unified Include + VocabBoost"

Chunk 5: Integration + Final Verification

Task 18: Integration smoke test

  • [ ] Step 1: Run all Python tests

Run: uv run pytest packages/governors/tests/ packages/dashboard/server/tests/ -v Expected: All pass

  • [ ] Step 2: Run all frontend tests

Run: cd packages/dashboard/frontend && npx vitest run Expected: All pass

  • [ ] Step 3: Verify package exports
# Quick import check
python -c "
from diffusion_governors import IncludeConstraint, VocabBoostConstraint, Governor, GovernorContext
from diffusion_governors.include import _CoverageMechanism
print('IncludeConstraint:', IncludeConstraint)
print('VocabBoostConstraint:', VocabBoostConstraint)
print('All imports OK')
"
  • [ ] Step 4: Verify Density is gone
python -c "
try:
    from diffusion_governors import Density
    print('FAIL: Density still exported')
except ImportError:
    print('OK: Density removed')
"
  • [ ] Step 5: Commit any final fixes

Task 19: Update CLAUDE.md and product plan

Files: - Modify: CLAUDE.md - Modify: docs/product-plan.md

  • [ ] Step 1: Update CLAUDE.md

Update the Governed Generation section to reflect: - Density-weighted Include (replaces flat boost) - VocabBoostConstraint (new) - Session-level coverage tracking - Expanded norm allowlist (15+ norms)

  • [ ] Step 2: Update product plan

Mark the Include/Coverage redesign as complete. Note VocabBoost as shipped.

  • [ ] Step 3: Commit
git add CLAUDE.md docs/product-plan.md
git commit -m "docs: update CLAUDE.md and product plan for Include redesign + VocabBoost"