Skip to content
Merged
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
12 changes: 7 additions & 5 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
from integration_source_fixture import MutualSources
from source_revision_fixture import SourceRepository
from fixture_git import ENVIRONMENT, SETTINGS
from debt_ratchet import ClientTypeErrors, compare
from agent_comms_project import AgentCommsProject, ClientTypeErrors
from agent_comms.pinned_audit import audit
from module_layers import MaterializedClients, Package

pytest_plugins = ("github_fixture", "test_holder_standing_rules", "integration_source_fixture")
Expand Down Expand Up @@ -372,8 +373,9 @@ def test_real_client_checker_compares_the_matched_tuple_in_its_one_interpreter(
source = inputs.source(pair.core.github)
head = pair.core.git(pair.checkout.path, "rev-parse", "HEAD").strip()
clients = materialized_clients(pair.output, inputs)
assert len(ClientTypeErrors.parts(pair.core.checkout, source.baseline.commit, head,
{"src/agent_comms/api.py"}, "src/", clients)) == 2
project = AgentCommsProject(pair.core.checkout, clients=clients)
assert len(ClientTypeErrors.parts(project, source.baseline.commit, head,
{"src/agent_comms/api.py"}, "src/")) == 2
for side in (BaseSide, HeadSide):
client, = clients.at(pair.core.checkout, source.baseline.commit, head, side)
assert client.import_paths == tuple(str(client.checkout / path) for path in ("src", "tools"))
Expand All @@ -387,8 +389,8 @@ def test_real_client_checker_compares_the_matched_tuple_in_its_one_interpreter(
assert entry.returncode == 0, entry.stdout + entry.stderr
# Both namespace files are selected under --explicit-package-bases: a duplicate
# performance/tools.performance identity aborts the real checker, not a zero count.
result = compare(pair.core.checkout, source.baseline.commit, head, "src", 1,
measured=(ClientTypeErrors,), clients=clients)
result = audit.ratchet.compare(project, source.baseline.commit, head, "src", 1,
(ClientTypeErrors,))
assert not result.failed
assert all(value == 0 for value in result.delta.values())

Expand Down
7 changes: 4 additions & 3 deletions tests/test_module_layers_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import tomli_w

from module_layers import MaterializedClients, OutgoingWire, Package, StackClient
from debt_ratchet import ClientTypeErrors
from agent_comms_project import AgentCommsProject, ClientTypeErrors
from agent_comms.head_review import BaseSide, HeadSide, IntegrationInput, Side
from agent_comms.todos import DraftListedPullRequest
from github_fixture import GitHubFixture
Expand Down Expand Up @@ -102,8 +102,9 @@ def test_actual_client_membership_stays_absent_on_its_own_cut(
clients = resource.at(pair.core.checkout, core.baseline.commit, tested, side)
assert len(clients) == int(present)
assert all(client.checkout == side.checkout(pair.output, pair.client.github) for client in clients)
assert len(ClientTypeErrors.parts(pair.core.checkout, core.baseline.commit, tested,
{"src/agent_comms/api.py"}, "src/", resource)) == 1
assert len(ClientTypeErrors.parts(AgentCommsProject(pair.core.checkout, clients=resource),
core.baseline.commit, tested,
{"src/agent_comms/api.py"}, "src/")) == 1


def test_materialized_client_files_come_from_each_actual_cut(
Expand Down
60 changes: 37 additions & 23 deletions tools/agent_comms_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from __future__ import annotations

import argparse
import ast
import atexit
import dataclasses
import inspect
import json
Expand All @@ -26,7 +26,7 @@
from subprocess import CompletedProcess

import stored_formats
from module_layers import PACKAGE, StackClient
from module_layers import PACKAGE, ClientSources, MaterializedClients, StackClient

import agent_comms
from agent_comms import (
Expand All @@ -40,10 +40,11 @@
from agent_comms.child_process import ForkedProcessPool, SpawnedProcess
from agent_comms.core_stamp import WheelStamp
from agent_comms.declared_family import DeclaredFamily
from agent_comms.head_review import Side as CutSide
from agent_comms.field_codec import boundary_decoder
from agent_comms.installer.carry import STEPS
from agent_comms.installer.commands import InstallCommands
from agent_comms.installer.runtime_resolution import StackSources
from agent_comms.installer.carry import STEPS
from agent_comms.installer.probes.carry import Carry
from agent_comms.phase_evidence import MainTree, PlanReading
from agent_comms.pinned_audit import SKILL, audit
Expand Down Expand Up @@ -103,6 +104,24 @@ class AgentCommsProject(audit.project.Project[PlanPhase]):
"""agent-comms' own facts, read from the modules that declare them."""

reader: StoredFormatReader = RevisionReader()
clients: ClientSources = ClientSources()
"""Where the compared revisions' stack clients come from: the head's declared stack,
cloned once, unless CI already materialized each side's exact cut."""

@classmethod
def from_arguments(cls, root: Path, commands: InstallCommands) -> tuple[AgentCommsProject, list[str]]:
"""This checkout measured with the clients its command line names, and the arguments
left for the audit's own command line. --ci-inputs names the integration entrypoint's
already-materialized exact source input."""
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
"--ci-inputs", dest="clients", default=ClientSources(),
type=lambda directory: MaterializedClients.read(
Path(directory).resolve(), uv=StackSources.frontend(),
python=Path(sys.executable), commands=commands),
help="use the integration entrypoint's already-materialized exact source input")
known, rest = parser.parse_known_args()
return cls(root, clients=known.clients), rest

probe = (
"import importlib.util, os, sys; sys.path[:0] = sys.argv[2].split(os.pathsep); "
Expand Down Expand Up @@ -240,20 +259,13 @@ class ClientTypeErrors(audit.checkers.SideCounts):
only the running app found it."""

@staticmethod
@cache
def clients(repo: audit.project.Project, head: str) -> tuple[StackClient, ...]:
"""The head's stack clients, cloned once per run and read by both sides; a revision
declaring no stack has none."""
stack = "stack/pyproject.toml"
if not repo.tracks(head, stack):
return ()
into = Path(tempfile.mkdtemp(prefix="ratchet-clients-"))
atexit.register(shutil.rmtree, into, True)
(into / "stack").mkdir()
(into / stack).write_bytes(repo.blob(head, stack))
with InstallCommands() as commands:
return StackClient.fetched(into / "stack", into, uv=StackSources.frontend(),
python=Path(sys.executable), commands=commands)
def cut(repo: AgentCommsProject, base: str, head: str,
side: type[audit.ratchet.Side]) -> tuple[StackClient, ...]:
"""The clients of the materialized cut that selects the same revision as this compared
side. The audit and head_review each declare the base/head pair; until they are one
family the cut is chosen by the revision its own ref selects, never by its name."""
return tuple(client for cut in CutSide.sides() if cut.ref(base, head) == side.ref(base, head)
for client in repo.clients.at(repo.path, base, head, cut))

@staticmethod
def selected(client: StackClient) -> list[str]:
Expand Down Expand Up @@ -285,15 +297,17 @@ def findings(
if site.path in selected)

@classmethod
def parts(cls, repo: audit.project.Project, base: str, head: str, changed: set[str],
def parts(cls, repo: AgentCommsProject, base: str, head: str, changed: set[str],
root: str) -> tuple[Callable[[], object], ...]:
# Only a change to the package the clients import can change what they see.
if not any(path.startswith(f"src/{PACKAGE}/") for path in changed):
# Only a change to the package the clients import can change what they see, unless the
# clients are materialized per side: a companion can change while Core does not.
if not (repo.clients.requires_clients()
or any(path.startswith(f"src/{PACKAGE}/") for path in changed)):
return ()
return tuple(partial(cls.findings, repo, side.ref(base, head), client, side)
for client in cls.clients(repo, head)
if cls.selected(client)
for side in audit.ratchet.Side.sides())
for side in audit.ratchet.Side.sides()
for client in cls.cut(repo, base, head, side)
if cls.selected(client))

@classmethod
def measured(
Expand Down
8 changes: 6 additions & 2 deletions tools/debt_ratchet.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env python3
"""The debt ratchet's CI entry: this checkout, measured as AgentCommsProject by the refactor-audit
it pins. Every measure, the command line, the comparison, its JSON report and its exit status are
the audit's; the one fact this entry owns is which project it measures."""
the audit's; the one fact this entry owns is which project it measures, and the project reads
where its own stack clients come from."""

from __future__ import annotations

from pathlib import Path

from agent_comms_project import AgentCommsProject
from agent_comms.installer.commands import InstallCommands
from agent_comms.pinned_audit import audit

if __name__ == "__main__":
raise SystemExit(audit.ratchet.main(AgentCommsProject(Path.cwd())))
with InstallCommands() as commands:
project, arguments = AgentCommsProject.from_arguments(Path.cwd(), commands)
raise SystemExit(audit.ratchet.main(project, arguments))