From b3a1fa0ed9a6499fe9e91f0087acfc8529a01aec Mon Sep 17 00:00:00 2001 From: Edbert Chan <1916223+EdbertChan@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:28:14 -0700 Subject: [PATCH] prove-it-ship-gate: the same-command rule reads only the current edit cycle The rule collected every product command the session ever ran, so after an edit followed by a test run it demanded reruns of programs from hours earlier and never cleared. It now reads the commands between the previous file edit and the last one (subagent transcripts stay whole), so the demand names the repro of this edit only. Co-Authored-By: Claude Fable 5.1 --- engine/hooks/prove-it-ship-gate/README.md | 7 +++++-- engine/hooks/prove-it-ship-gate/detect.py | 12 +++++++----- .../prove-it-ship-gate/tests/test_hooks.py | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/engine/hooks/prove-it-ship-gate/README.md b/engine/hooks/prove-it-ship-gate/README.md index 7f9c88f9..89eebe2a 100644 --- a/engine/hooks/prove-it-ship-gate/README.md +++ b/engine/hooks/prove-it-ship-gate/README.md @@ -54,8 +54,11 @@ The stop is wired for Claude, Cursor, and Codex. Each harness gets that harness's own block: Claude exits 2, Cursor returns a followup message on its stop event, and Codex returns a block decision. A product command (a hyphenated program such as `invoker-cli doctor`) that -already ran in the session, including a subagent transcript beside it, has -to run again after the last file edit. Codex `exec_command` rows count as +ran between the previous file edit and the last one, or anywhere in a +subagent transcript beside the session, has to run again after the last +file edit. Commands from earlier edit cycles are that cycle's repro, not +this one's, so a long session does not accumulate every program it ever +ran into one demand. Codex `exec_command` rows count as that command, and `apply_patch` counts as the edit. A later `vitest` / `pnpm test` / `pytest` run does not replace it. The hook names the missing program and subcommand and blocks. Unix pipelines, `node -e` scripts, and a session that diff --git a/engine/hooks/prove-it-ship-gate/detect.py b/engine/hooks/prove-it-ship-gate/detect.py index 9552cbfc..917115b8 100644 --- a/engine/hooks/prove-it-ship-gate/detect.py +++ b/engine/hooks/prove-it-ship-gate/detect.py @@ -397,19 +397,21 @@ def same_repro_gap(transcript_path: str) -> list[str] | None: if primary is None: return None last_edit = None + previous_edit = -1 for index, (kind, _command) in enumerate(primary): if kind == "edit": - last_edit = index + previous_edit, last_edit = (last_edit if last_edit is not None else -1), index if last_edit is None: return [] if not any(kind == "cmd" and _is_test_runner(command) for index, (kind, command) in enumerate(primary) if index > last_edit): return [] seen: set[str] = set() for path in _session_paths(transcript_path): - events = primary if path == transcript_path else _read_events(path) - if events is None: - continue - for kind, command in events: + if path == transcript_path: + window = primary[previous_edit + 1:last_edit] + else: + window = _read_events(path) or [] + for kind, command in window: if kind == "cmd": seen.update(surface_keys(command)) rerun: set[str] = set() diff --git a/engine/hooks/prove-it-ship-gate/tests/test_hooks.py b/engine/hooks/prove-it-ship-gate/tests/test_hooks.py index a6faa139..5dbd187d 100644 --- a/engine/hooks/prove-it-ship-gate/tests/test_hooks.py +++ b/engine/hooks/prove-it-ship-gate/tests/test_hooks.py @@ -601,6 +601,24 @@ def test_fires_when_the_real_command_ran_in_a_subagent(self): self.assertIsNotNone(feedback) self.assertIn("invoker-cli --help", feedback) + def test_only_the_commands_since_the_previous_edit_count_as_the_repro(self): + with tempfile.TemporaryDirectory() as directory: + path = write_cursor_transcript(directory, "session.jsonl", [ + ("shell", "invoker-cli doctor"), + ("edit", ""), + ("shell", "pnpm test"), + ("shell", "other-tool run"), + ("edit", ""), + ("shell", "pnpm test"), + ]) + feedback = detect.decide({ + "last_assistant_message": "the test passed", + "transcript_path": path, + }) + self.assertIsNotNone(feedback) + self.assertIn("other-tool run", feedback) + self.assertNotIn("invoker-cli doctor", feedback) + def test_fails_open_when_the_same_repro_transcript_is_missing(self): self.assertIsNone(detect.same_repro_gap("/nonexistent/same-repro.jsonl")) self.assertIsNone(detect.decide({