Skip to content
Draft
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
7 changes: 5 additions & 2 deletions engine/hooks/prove-it-ship-gate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions engine/hooks/prove-it-ship-gate/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions engine/hooks/prove-it-ship-gate/tests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading