Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ breaking changes may land in a minor release.

### Added

- The TEA plugin now prints a one-time notice at activation about the extra agent sessions its default settings add per story.

- Prove real-tmux teardown reaps the exact detached child after identity publication fails (DW-149).

- **The accepted-park arm's `_harvest_gate_exclude` join is now graded engine-side**
Expand Down
33 changes: 33 additions & 0 deletions src/bmad_loop/data/plugins/tea/tea_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any

Expand All @@ -42,6 +43,19 @@
# advisory by design and are deliberately absent — they are never gate-enforced.
GATE_STEPS = ("trace", "nfr", "review")

# The six step-enable settings that gate the extra agent sessions TEA injects
# per story. All default to true (see plugin.toml); when every one of them is
# still at that default, validate() surfaces a one-time cost notice (see
# _warn_if_defaults_costly) -- visibility only, never a default change.
COST_SETTINGS = (
"td_enabled",
"atdd_enabled",
"automate_enabled",
"trace_enabled",
"nfr_enabled",
"review_enabled",
)

# Verdicts that escalate a blocking gate. PASS / WAIVED (an explicit human
# approval to proceed) / an unknown-or-not-evaluated verdict never block.
BLOCKING_VERDICTS = frozenset({"FAIL", "CONCERNS"})
Expand Down Expand Up @@ -138,6 +152,7 @@ def validate(self, policy: Any) -> None:
"require_tea = false under [plugins.tea] in .bmad-loop/policy.toml "
"to run the TEA workflows advisory-only without it."
)
self._warn_if_defaults_costly()

# --------------------------------------------------------- enforcement

Expand Down Expand Up @@ -191,6 +206,24 @@ def on_pre_commit(self, ctx: "HookContext") -> None:
def _require_tea(self) -> bool:
return bool(self.settings.get("require_tea", True))

def _warn_if_defaults_costly(self) -> None:
"""One-time, non-blocking notice on stderr: when all six step-enable
settings (COST_SETTINGS) are still at their true default, enabling tea
injects 6 extra agent sessions per story (3 post_dev_phase + 3
pre_commit_gate). Never raises and never changes a default -- this is a
visibility fix only; see docs/tea-plugin-guide.md for the cost
breakdown and how to disable steps that aren't needed."""
if not all(bool(self.settings.get(key, True)) for key in COST_SETTINGS):
return # an operator already opted out of at least one step
sys.stderr.write(
"plugin 'tea': all six step settings (td_enabled, atdd_enabled, "
"automate_enabled, trace_enabled, nfr_enabled, review_enabled) are "
"at their true default -- enabling tea this way adds 6 extra agent "
"sessions per story (3 post_dev_phase + 3 pre_commit_gate). See "
"docs/tea-plugin-guide.md for the cost breakdown and how to disable "
"steps you don't need.\n"
)

def _project_root(self) -> Path:
"""The project root, resolved the way the engine resolves it: the run's
working directory (``bmad-loop``'s ``--project`` defaults to cwd, and the
Expand Down
20 changes: 20 additions & 0 deletions tests/test_plugin_tea.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,26 @@ def test_readiness_skipped_when_require_tea_false(project, monkeypatch):
_tea_instance(require_tea=False).validate(Policy()) # no raise


def test_readiness_emits_cost_notice_when_defaults_in_effect(project, monkeypatch, capsys):
# All six step-enable settings still at their true default -> validate()
# surfaces the one-time cost notice on stderr, and still activates normally.
install_tea(project)
monkeypatch.chdir(project.project)
_tea_instance(require_tea=True).validate(Policy()) # no raise
err = capsys.readouterr().err
assert "6 extra agent sessions per story" in err
assert "docs/tea-plugin-guide.md" in err


def test_readiness_skips_cost_notice_when_a_step_is_disabled(project, monkeypatch, capsys):
# An operator who already opted out of at least one step (e.g. disabled
# ATDD) no longer has all six settings at default -> no notice.
install_tea(project)
monkeypatch.chdir(project.project)
_tea_instance(require_tea=True, atdd_enabled=False).validate(Policy()) # no raise
assert capsys.readouterr().err == ""


def test_engine_construction_fails_fast_without_tea(project, monkeypatch):
"""The engine runs registry.validate() at startup; an enabled tea plugin with
no TEA install fails construction before any story runs."""
Expand Down