Skip to content
Closed
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
14 changes: 11 additions & 3 deletions src/agent_comms/phase_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ def has(self, path: str) -> bool:
return self.git("cat-file", "-e", f"{self.commit}:{path}").returncode == 0

def grep(self, *options: str, pattern: str, paths: tuple[str, ...]) -> list[str]:
"""``git grep -E`` rows under ``paths``; git pathspecs such as ``:!file`` exclude."""
run = self.git("grep", *options, "-E", "-e", pattern, self.commit, "--", *paths)
"""``git grep -E`` rows in this tree, like ``module`` and ``modules``.

A submodule is another repository's tree, regardless of local recurse settings.
Git pathspecs such as ``:!file`` exclude.
"""
run = self.git("grep", "--no-recurse-submodules", *options,
"-E", "-e", pattern, self.commit, "--", *paths)
if run.returncode not in (0, 1):
run.check_returncode()
return run.stdout.splitlines()
Expand Down Expand Up @@ -497,18 +502,21 @@ class ClassFlagsAbsent(MainEvidence):

pattern: str
paths: tuple[str, ...] = ("src",)
class_names: tuple[str, ...] = ()

def read(self, main: MainTree) -> bool:
matched = main.matching_lines(self.pattern, self.paths)
return not any(
(path, statement.lineno) in matched
for path in {path for path, _ in matched}
for declared in Classes.of((main.module(path).tree,))
if not self.class_names or declared.name in self.class_names
for statement in declared.body
)

def describe(self) -> str:
return f"no class body line {self.pattern!r} in {' '.join(self.paths)} on main"
owners = f" of {', '.join(self.class_names)}" if self.class_names else ""
return f"no class body line {self.pattern!r}{owners} in {' '.join(self.paths)} on main"


class NamedClasses(ast.NodeVisitor):
Expand Down
18 changes: 15 additions & 3 deletions src/agent_comms/plan_phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,9 +652,21 @@ class KindFamiliesPhase(RemainingSlopPhase):


class RostersPhase(RemainingSlopPhase):
"""6.4: rosters beside families are derived from the family."""

evidence = PatternAbsent(r"PI_0_85_1_EVENT_KINDS|THREAD_CAPABILITIES|serves_new_threads")
"""6.4: remove the known internal registries; external producer ABI stays independent."""

evidence = AllEvidence((
PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"),
ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"),
*(ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,))
for owner, attribute in (
("Carry", "declared"),
("ThreadRename", "kinds"),
("BirthlessIncarnation", "members"),
("CliToolArguments", "types"),
("PiTurnPhase", "_start_phases"),
("PiTurnPhase", "_end_events"),
)),
))


class RecordsPhase(RemainingSlopPhase):
Expand Down
53 changes: 52 additions & 1 deletion tests/test_phase_evidence.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import subprocess
from pathlib import Path

from agent_comms.field_codec import FieldCodec
from agent_comms.head_review import HeadReview
Expand Down Expand Up @@ -27,7 +28,7 @@
UnreadableEvidenceError,
)
from agent_comms.pinned_audit import GodFunction
from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase
from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase, RostersPhase
from agent_comms.repo import Repo
from agent_comms.repository_name import RepositoryName

Expand Down Expand Up @@ -100,6 +101,56 @@ def main_of(tmp_path, files):
Repo(checkout=tmp_path, github=RepositoryName.from_wire("OpenHCSDev/agent-comms"))))


def test_roster_evidence_keeps_external_abi_and_each_remaining_internal_registry(tmp_path):
source = Path(__file__).parents[1] / "src/agent_comms"
reading = main_of(tmp_path, {
"src/phase_evidence.py": (source / "phase_evidence.py").read_text(),
"src/plan_phases.py": (source / "plan_phases.py").read_text(),
"src/producer.py": "PI_0_85_1_EVENT_KINDS = frozenset({'alpha', 'beta'})\n"
"class Runtime:\n event_kinds = PI_0_85_1_EVENT_KINDS\n",
"src/unrelated.py": "class OtherOwner:\n members = {}\n"
"def local():\n types = {}\n",
})
assert reading.holds(RostersPhase.evidence)
for remaining in (
"THREAD_CAPABILITIES: tuple[type, ...] = (Steers, Compacts)\n",
"class Backend:\n serves_threads = True\n",
"class Backend:\n serves_new_threads: ClassVar[bool] = True\n",
"class Carry:\n declared: ClassVar[dict] = {}\n",
"class ThreadRename:\n kinds = {}\n",
"class BirthlessIncarnation:\n members = {}\n",
"class CliToolArguments:\n types = {}\n",
"class PiTurnPhase:\n _start_phases = {}\n",
"class PiTurnPhase:\n _end_events = {}\n",
):
(tmp_path / "src/remaining.py").write_text(remaining)
git(tmp_path, "add", ".")
git(tmp_path, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", "registry")
git(tmp_path, "update-ref", "refs/remotes/origin/main", git(tmp_path, "rev-parse", "HEAD"))
changed = PlanReading((), MainTree.of(reading.main.repo))
assert not changed.holds(RostersPhase.evidence), remaining


def test_main_tree_grep_uses_the_same_repository_tree_as_its_module_reader(tmp_path):
dependency = tmp_path / "dependency"
dependency.mkdir()
main_of(dependency, {"src/debt.py": "class Carry:\n declared = {}\n"})
checkout = tmp_path / "owner"
checkout.mkdir()
reading = main_of(checkout, {"src/owner.py": "class OtherOwner:\n members = {}\n"})
git(checkout, "-c", "protocol.file.allow=always", "submodule", "add", "-q",
str(dependency), "src/dependency")
git(checkout, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qam", "dependency")
git(checkout, "update-ref", "refs/remotes/origin/main", git(checkout, "rev-parse", "HEAD"))
git(checkout, "config", "submodule.recurse", "true")
assert "src/dependency" in git(checkout, "grep", "--recurse-submodules", "-n",
"-e", "declared", "HEAD", "--", "src")
current = PlanReading((), MainTree.of(reading.main.repo))
assert current.holds(RostersPhase.evidence)
assert not current.main.matching_lines(r"declared", ("src",))
assert len(tuple(current.main.modules(("src",)))) == 1


def test_source_shape_evidence_reads_declarations_on_main(tmp_path):
copy = " def __init__(self, path):\n self.path = path\n"
reading = main_of(tmp_path, {
Expand Down
Loading