From c91bd0b1e8183814f248b0751d88a8e422a905e8 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 9 Jun 2026 17:16:08 -0500 Subject: feat(workflows): generalize broadcast into announcement + situational modes cross-project-broadcast handled tooling and rule announcements but had no shape for the situational case: a life or work event I want every project's agent to know, said once so none is missing context when I next talk to them. I renamed it to broadcast (helper and test alongside) and split it into two modes over the same fan-out plumbing. Announcement keeps the rigid capability template. Situational carries a general-not-comprehensive summary plus a fixed receiving-agent contract: record it in notes.org, hold it time-boxed or standing, apply on the project's own judgment, ask follow-ups at startup. The broadcasting agent does no per-project relevance analysis. Each receiving agent decides what the event means for its own work. --- .ai/scripts/tests/test_broadcast.py | 116 ++++++++++++++++++++++ .ai/scripts/tests/test_cross_project_broadcast.py | 116 ---------------------- 2 files changed, 116 insertions(+), 116 deletions(-) create mode 100644 .ai/scripts/tests/test_broadcast.py delete mode 100644 .ai/scripts/tests/test_cross_project_broadcast.py (limited to '.ai/scripts/tests') diff --git a/.ai/scripts/tests/test_broadcast.py b/.ai/scripts/tests/test_broadcast.py new file mode 100644 index 0000000..a0decf5 --- /dev/null +++ b/.ai/scripts/tests/test_broadcast.py @@ -0,0 +1,116 @@ +"""Tests for broadcast.py: project fingerprinting + discovery. + +Plain python3 script. The pure-ish helpers are driven against tmp project +trees; discovery is exercised with SEARCH_ROOTS monkeypatched to the tree, and +the cwd-based helpers with monkeypatch.chdir. +""" +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).resolve().parents[1] / "broadcast.py" + + +@pytest.fixture(scope="module") +def bcast(): + spec = importlib.util.spec_from_file_location("broadcast", SCRIPT) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _make_project(root: Path, name: str, with_inbox: bool = True, + with_protocols: bool = True) -> Path: + p = root / name + (p / ".ai").mkdir(parents=True) + if with_protocols: + (p / ".ai" / "protocols.org").write_text("#+TITLE: protocols\n") + if with_inbox: + (p / "inbox").mkdir() + return p + + +# --- is_broadcastable --- + +def test_is_broadcastable_true_with_protocols_and_inbox(bcast, tmp_path): + assert bcast.is_broadcastable(_make_project(tmp_path, "proj")) is True + + +def test_is_broadcastable_false_without_inbox(bcast, tmp_path): + p = _make_project(tmp_path, "proj", with_inbox=False) + assert bcast.is_broadcastable(p) is False + + +def test_is_broadcastable_false_without_protocols(bcast, tmp_path): + p = _make_project(tmp_path, "proj", with_protocols=False) + assert bcast.is_broadcastable(p) is False + + +def test_is_broadcastable_false_on_plain_dir(bcast, tmp_path): + assert bcast.is_broadcastable(tmp_path) is False + + +# --- discover (SEARCH_ROOTS monkeypatched onto the tmp tree) --- + +def test_discover_finds_broadcastable_subprojects(bcast, tmp_path, monkeypatch): + root = tmp_path / "code" + root.mkdir() + _make_project(root, "alpha") + _make_project(root, "beta") + _make_project(root, "no-inbox", with_inbox=False) # not broadcastable + monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root]) + assert [p.name for p in bcast.discover()] == ["alpha", "beta"] + + +def test_discover_handles_root_that_is_itself_a_project(bcast, tmp_path, monkeypatch): + root = _make_project(tmp_path, ".emacs.d") + monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root]) + assert [p.name for p in bcast.discover()] == [".emacs.d"] + + +def test_discover_dedups_by_basename_across_roots(bcast, tmp_path, monkeypatch): + root1 = tmp_path / "code" + root1.mkdir() + root2 = tmp_path / "projects" + root2.mkdir() + _make_project(root1, "dup") + _make_project(root2, "dup") + monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root1, root2]) + assert [p.name for p in bcast.discover()] == ["dup"] + + +def test_discover_skips_missing_roots(bcast, tmp_path, monkeypatch): + monkeypatch.setattr(bcast, "SEARCH_ROOTS", [tmp_path / "does-not-exist"]) + assert bcast.discover() == [] + + +# --- sender_project / inbox_send_path (cwd-based) --- + +def test_sender_project_returns_basename_inside_an_ai_project(bcast, tmp_path, monkeypatch): + p = _make_project(tmp_path, "myproj") + monkeypatch.chdir(p) + assert bcast.sender_project() == "myproj" + + +def test_sender_project_none_outside_an_ai_project(bcast, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + assert bcast.sender_project() is None + + +def test_inbox_send_path_found_in_project(bcast, tmp_path, monkeypatch): + p = _make_project(tmp_path, "myproj") + (p / ".ai" / "scripts").mkdir() + helper = p / ".ai" / "scripts" / "inbox-send.py" + helper.write_text("# stub\n") + monkeypatch.chdir(p) + assert bcast.inbox_send_path() == helper + + +def test_inbox_send_path_raises_when_missing(bcast, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + with pytest.raises(SystemExit): + bcast.inbox_send_path() diff --git a/.ai/scripts/tests/test_cross_project_broadcast.py b/.ai/scripts/tests/test_cross_project_broadcast.py deleted file mode 100644 index 5919fbf..0000000 --- a/.ai/scripts/tests/test_cross_project_broadcast.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Tests for cross-project-broadcast.py: project fingerprinting + discovery. - -Plain python3 script. The pure-ish helpers are driven against tmp project -trees; discovery is exercised with SEARCH_ROOTS monkeypatched to the tree, and -the cwd-based helpers with monkeypatch.chdir. -""" -from __future__ import annotations - -import importlib.util -from pathlib import Path - -import pytest - -SCRIPT = Path(__file__).resolve().parents[1] / "cross-project-broadcast.py" - - -@pytest.fixture(scope="module") -def bcast(): - spec = importlib.util.spec_from_file_location("cross_project_broadcast", SCRIPT) - assert spec and spec.loader - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def _make_project(root: Path, name: str, with_inbox: bool = True, - with_protocols: bool = True) -> Path: - p = root / name - (p / ".ai").mkdir(parents=True) - if with_protocols: - (p / ".ai" / "protocols.org").write_text("#+TITLE: protocols\n") - if with_inbox: - (p / "inbox").mkdir() - return p - - -# --- is_broadcastable --- - -def test_is_broadcastable_true_with_protocols_and_inbox(bcast, tmp_path): - assert bcast.is_broadcastable(_make_project(tmp_path, "proj")) is True - - -def test_is_broadcastable_false_without_inbox(bcast, tmp_path): - p = _make_project(tmp_path, "proj", with_inbox=False) - assert bcast.is_broadcastable(p) is False - - -def test_is_broadcastable_false_without_protocols(bcast, tmp_path): - p = _make_project(tmp_path, "proj", with_protocols=False) - assert bcast.is_broadcastable(p) is False - - -def test_is_broadcastable_false_on_plain_dir(bcast, tmp_path): - assert bcast.is_broadcastable(tmp_path) is False - - -# --- discover (SEARCH_ROOTS monkeypatched onto the tmp tree) --- - -def test_discover_finds_broadcastable_subprojects(bcast, tmp_path, monkeypatch): - root = tmp_path / "code" - root.mkdir() - _make_project(root, "alpha") - _make_project(root, "beta") - _make_project(root, "no-inbox", with_inbox=False) # not broadcastable - monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root]) - assert [p.name for p in bcast.discover()] == ["alpha", "beta"] - - -def test_discover_handles_root_that_is_itself_a_project(bcast, tmp_path, monkeypatch): - root = _make_project(tmp_path, ".emacs.d") - monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root]) - assert [p.name for p in bcast.discover()] == [".emacs.d"] - - -def test_discover_dedups_by_basename_across_roots(bcast, tmp_path, monkeypatch): - root1 = tmp_path / "code" - root1.mkdir() - root2 = tmp_path / "projects" - root2.mkdir() - _make_project(root1, "dup") - _make_project(root2, "dup") - monkeypatch.setattr(bcast, "SEARCH_ROOTS", [root1, root2]) - assert [p.name for p in bcast.discover()] == ["dup"] - - -def test_discover_skips_missing_roots(bcast, tmp_path, monkeypatch): - monkeypatch.setattr(bcast, "SEARCH_ROOTS", [tmp_path / "does-not-exist"]) - assert bcast.discover() == [] - - -# --- sender_project / inbox_send_path (cwd-based) --- - -def test_sender_project_returns_basename_inside_an_ai_project(bcast, tmp_path, monkeypatch): - p = _make_project(tmp_path, "myproj") - monkeypatch.chdir(p) - assert bcast.sender_project() == "myproj" - - -def test_sender_project_none_outside_an_ai_project(bcast, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - assert bcast.sender_project() is None - - -def test_inbox_send_path_found_in_project(bcast, tmp_path, monkeypatch): - p = _make_project(tmp_path, "myproj") - (p / ".ai" / "scripts").mkdir() - helper = p / ".ai" / "scripts" / "inbox-send.py" - helper.write_text("# stub\n") - monkeypatch.chdir(p) - assert bcast.inbox_send_path() == helper - - -def test_inbox_send_path_raises_when_missing(bcast, tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - with pytest.raises(SystemExit): - bcast.inbox_send_path() -- cgit v1.2.3