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
2 changes: 1 addition & 1 deletion src/agent_comms/commander/rules/tests-and-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ type: feedback
- An order check counts over the whole source tree (`grep -r ... src`), never one file.
- Run verb gate loops under `bash -c`: zsh passes a quoted "order idle" as one argument.
- A wait or kill by process pattern brackets its first letter (`grep -q "[v]erify"`) or waits on a file or PID, or it matches itself.
- Lint, every commit: run `ruff check` on the .py files in your diff (`git diff --name-only origin/main -- '*.py'`) and fix only the findings on lines you added or changed. Never run `ruff format` on a whole file, never rename a class or function to quiet a lint rule, and never touch lines the order didn't name. Main's existing lint debt is counted by the ratchet; sweeping it is an off-order edit. Your reply includes `git diff --stat origin/main...HEAD`.
- Lint warns, never blocks: the debt ratchet reports ruff and Black growth as warnings on the file and gates only restated facts. Read the warnings on lines you added or changed; fix one when it names a real defect, such as an undefined name or dead code. Never run `ruff format` on a whole file, never rename a class or function to quiet a lint rule, and never touch lines the order didn't name. Your reply includes `git diff --stat origin/main...HEAD`.
17 changes: 15 additions & 2 deletions tests/guards/test_debt_ratchet.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,6 @@ async def second(value):
(AnyName, "from typing import Any\ndef read(value: Any) -> None: ...\n"),
(AnyName, "from typing import Any\nvalue = Any\n"),
(UnannotatedParameter, "def read(value): ...\n"),
(Unformatted, "value=1\n"),
(LintFindings, "import os\n"),
(TypeErrors, 'value: int = "text"\n'),
],
)
Expand All @@ -712,6 +710,21 @@ def test_per_file_increase_cannot_be_offset_or_moved(
)


@pytest.mark.parametrize(
("measure", "source"), [(Unformatted, "value=1\n"), (LintFindings, "import os\n")]
)
def test_advisory_growth_is_measured_and_warned_never_failed(
repo: Repository, measure: type[Measure], source: str
) -> None:
base = repo.commit({"new.py": ""})
head = repo.commit({"new.py": source})
status, report = repo.compare(base, head, measure)
key = measure.key(f"{repo.root}/new.py")
assert status == 0
assert report["delta"][key] > 0
assert measure.grew(key, 1).startswith(f"::warning file={repo.root}/new.py::")


def test_a_renamed_file_compares_with_its_old_path(repo: Repository) -> None:
kept = "".join(f"def keep{i}(value: int) -> int:\n return value\n" for i in range(8))
source = kept + "def read(value): ...\n"
Expand Down
30 changes: 25 additions & 5 deletions tools/debt_ratchet.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ def owns(cls, key: str) -> bool:
def blocks(cls, delta: dict[str, int]) -> bool:
return any(value > 0 for key, value in delta.items() if cls.owns(key))

@classmethod
def grew(cls, key: str, grown: int) -> str:
"""The line reporting one key's growth."""
return f"grew {key} +{grown}"

@classmethod
def run_by(cls) -> type[Measure]:
"""The measure a comparison runs to take this one's measurements."""
Expand Down Expand Up @@ -688,7 +693,21 @@ def combine(cls, results: list, repo: Path, base: str, head: str, changed: set[s
return before, after


class Unformatted(CheckedRevision):
class Advisory(Measure):
"""Growth that restates no fact, such as a lint convention or layout: the ratchet warns
on the file and never fails on it. Only measures of restated facts block a merge."""

@classmethod
def blocks(cls, delta: dict[str, int]) -> bool:
return False

@classmethod
def grew(cls, key: str, grown: int) -> str:
path = key.partition(":")[2]
return f"::warning file={path}::{cls.__name__} +{grown} (warns, never blocks)"


class Unformatted(Advisory, CheckedRevision):
"""1 when Black would rewrite the file: a formatted file stays formatted."""

finding = re.compile(r"^would reformat (.+)$", re.MULTILINE)
Expand Down Expand Up @@ -732,7 +751,7 @@ def findings(cls, repo: Path, ref: str, head: str, root: str, selected: list[str
return side, Counter(found)


class LintFindings(CheckedRevision):
class LintFindings(Advisory, CheckedRevision):
"""Ruff diagnostics under the selected rules."""

finding = re.compile(r"^(.+?\.py):\d+:\d+: ", re.MULTILINE)
Expand Down Expand Up @@ -2630,9 +2649,10 @@ def main() -> int:
print(json.dumps(FieldCodec.project(result, "report"), indent=2), flush=True)
# The failing step's last lines become check annotations (.github/run-step.sh merges
# stderr), so what grew goes to stderr last; stdout stays the one JSON report.
for name, grown in sorted(result.delta.items()):
if grown > 0:
print(f"grew {name} +{grown}", file=sys.stderr)
for measure in Measure.members_with(Measure):
for key, grown in sorted(result.delta.items()):
if grown > 0 and measure.owns(key):
print(measure.grew(key, grown), file=sys.stderr)
for name, sites in sorted(result.found.items()):
print(f"found {name} at {' '.join(f'{site.path}:{site.line}' for site in sites)}",
file=sys.stderr)
Expand Down
Loading