claude-dev-env 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/_shared/CLAUDE.md +1 -0
  2. package/_shared/advisor/advisor-protocol.md +19 -9
  3. package/_shared/pr-loop/audit-contract.md +4 -4
  4. package/_shared/pr-loop/precatch-rubric.md +2 -2
  5. package/_shared/process-tree/CLAUDE.md +41 -0
  6. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
  7. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
  8. package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
  9. package/_shared/process-tree/scripts/pyproject.toml +16 -0
  10. package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
  11. package/agents/code-quality-agent.md +6 -5
  12. package/agents/deep-research.md +7 -24
  13. package/agents/docs-agent.md +1 -27
  14. package/agents/issue-tracker.md +1 -7
  15. package/agents/skill-writer-agent.md +1 -2
  16. package/agents/test_agent_frontmatter.py +309 -12
  17. package/hooks/blocking/CLAUDE.md +2 -0
  18. package/hooks/blocking/fable_spawn_gate.py +187 -0
  19. package/hooks/blocking/piped_pytest_blocker.py +1223 -0
  20. package/hooks/blocking/plain_language_blocker.py +287 -15
  21. package/hooks/blocking/test_fable_spawn_gate.py +374 -0
  22. package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
  23. package/hooks/blocking/test_plain_language_blocker.py +277 -2
  24. package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
  25. package/hooks/git-hooks/CLAUDE.md +2 -2
  26. package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
  27. package/hooks/git-hooks/pre_push.py +343 -54
  28. package/hooks/git-hooks/test_pre_push.py +852 -6
  29. package/hooks/hooks.json +9 -19
  30. package/hooks/hooks_constants/CLAUDE.md +2 -0
  31. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
  32. package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
  33. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
  34. package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
  35. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
  36. package/hooks/hooks_constants/shell_command_segments.py +1 -1
  37. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
  38. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
  39. package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
  40. package/package.json +1 -1
  41. package/rules/CLAUDE.md +1 -0
  42. package/rules/ask-user-question-required.md +26 -0
  43. package/rules/claims-as-quotes.md +65 -0
  44. package/scripts/CLAUDE.md +1 -1
  45. package/scripts/_code_review_test_support.py +6 -0
  46. package/scripts/check.ps1 +18 -5
  47. package/scripts/claude_chain_runner.py +203 -31
  48. package/scripts/codec_forwarding_test_support.py +2 -0
  49. package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
  50. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
  51. package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
  52. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +28 -24
  53. package/scripts/grok_headless_runner.py +16 -83
  54. package/scripts/spawn_grok_batch.py +38 -1
  55. package/scripts/test_claude_chain_runner.py +358 -0
  56. package/scripts/test_grok_headless_runner.py +18 -63
  57. package/scripts/test_invoke_code_review.py +298 -0
  58. package/scripts/test_resolve_worker_spawn.py +6 -0
  59. package/scripts/test_spawn_grok_batch.py +101 -0
  60. package/scripts/tests/CLAUDE.md +1 -0
  61. package/scripts/tests/test_grok_worker_constants.py +59 -0
  62. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
  63. package/skills/autoconverge/workflow/converge.contract.test.mjs +105 -2
  64. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +34 -1
  65. package/skills/autoconverge/workflow/converge.mjs +66 -33
  66. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
  67. package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
  68. package/skills/codex-review/scripts/run_codex_review.py +16 -64
  69. package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
  70. package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
  71. package/skills/e-code-review/SKILL.md +9 -8
  72. package/skills/e-code-review/reference/fix.md +29 -7
  73. package/skills/e-code-review/reference/loop.md +230 -14
  74. package/skills/e-code-review/reference/low.md +33 -15
  75. package/skills/e-code-review/reference/medium.md +55 -21
  76. package/skills/e-code-review/reference/xhigh.md +30 -12
  77. package/skills/fresh-branch/CLAUDE.md +5 -5
  78. package/skills/fresh-branch/SKILL.md +14 -6
  79. package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
  80. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
  81. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
  82. package/skills/grok-spawn/SKILL.md +5 -0
  83. package/skills/orchestrator/SKILL.md +4 -1
  84. package/skills/orchestrator-refresh/SKILL.md +5 -1
  85. package/skills/team-advisor/SKILL.md +4 -1
@@ -34,9 +34,6 @@ from codex_review_scripts_constants.codex_usage_probe_constants import (
34
34
  USAGE_REPORT_KEY_WINDOW_RESET,
35
35
  WEEKLY_USAGE_GATE_THRESHOLD_PERCENT,
36
36
  WEEKLY_WINDOW_DURATION_MINUTES,
37
- WINDOWS_OS_NAME,
38
- WINDOWS_TASKKILL_COMMAND,
39
- WINDOWS_TASKKILL_TREE_FLAG,
40
37
  )
41
38
 
42
39
  PROBE_PATH = SCRIPTS_DIRECTORY / "codex_usage_probe.py"
@@ -300,58 +297,66 @@ class TestParseTextStatus:
300
297
 
301
298
 
302
299
  class TestProcessTreeTeardown:
303
- def should_run_taskkill_tree_when_os_name_is_windows(
304
- self,
305
- monkeypatch: pytest.MonkeyPatch,
306
- ) -> None:
300
+ def should_end_a_running_app_server_and_reap_it(self) -> None:
301
+ """A real sleeping child is gone once teardown returns, on any platform."""
307
302
  probe = load_probe_module()
308
- all_taskkill_commands: list[list[str]] = []
309
- fake_process = subprocess.Popen(
303
+ sleeping_process = subprocess.Popen(
310
304
  [sys.executable, "-c", "import time; time.sleep(30)"],
311
305
  stdout=subprocess.DEVNULL,
312
306
  stderr=subprocess.DEVNULL,
307
+ start_new_session=probe.should_start_new_session(),
313
308
  )
314
309
  try:
315
- monkeypatch.setattr(probe.os, "name", WINDOWS_OS_NAME)
316
-
317
- def capture_run(
318
- command: list[str],
319
- **_kwargs: object,
320
- ) -> subprocess.CompletedProcess[str]:
321
- all_taskkill_commands.append(list(command))
322
- fake_process.kill()
323
- fake_process.wait(timeout=PROCESS_TREE_WAIT_SECONDS)
324
- return subprocess.CompletedProcess(command, 0)
325
-
326
- monkeypatch.setattr(probe.subprocess, "run", capture_run)
327
- probe._terminate_process_tree(fake_process)
310
+ probe._terminate_process_tree(sleeping_process)
311
+ assert sleeping_process.poll() is not None
328
312
  finally:
329
- if fake_process.poll() is None:
330
- fake_process.kill()
331
- fake_process.wait(timeout=PROCESS_TREE_WAIT_SECONDS)
332
- assert len(all_taskkill_commands) == 1
333
- assert all_taskkill_commands[0][0] == WINDOWS_TASKKILL_COMMAND
334
- assert WINDOWS_TASKKILL_TREE_FLAG in all_taskkill_commands[0]
335
- assert str(fake_process.pid) in all_taskkill_commands[0]
336
-
337
- def should_kill_process_directly_when_os_name_is_not_windows(
313
+ if sleeping_process.poll() is None:
314
+ sleeping_process.kill()
315
+ sleeping_process.wait(timeout=PROCESS_TREE_WAIT_SECONDS)
316
+
317
+ def should_hand_the_whole_tree_to_the_shared_kill(
338
318
  self,
339
319
  monkeypatch: pytest.MonkeyPatch,
340
320
  ) -> None:
321
+ """Teardown routes the server process through the shared tree kill.
322
+
323
+ ::
324
+
325
+ shared tree kill ok: the cmd.exe shim's app-server child dies too
326
+ Popen.kill() flag: the app-server grandchild holds stdout open
327
+ """
341
328
  probe = load_probe_module()
342
- fake_process = subprocess.Popen(
343
- [sys.executable, "-c", "import time; time.sleep(30)"],
329
+ all_killed_processes: list[object] = []
330
+ finished_process = subprocess.Popen(
331
+ [sys.executable, "-c", ""],
344
332
  stdout=subprocess.DEVNULL,
345
333
  stderr=subprocess.DEVNULL,
346
334
  )
347
- try:
348
- monkeypatch.setattr(probe.os, "name", "posix")
349
- probe._terminate_process_tree(fake_process)
350
- assert fake_process.poll() is not None
351
- finally:
352
- if fake_process.poll() is None:
353
- fake_process.kill()
354
- fake_process.wait(timeout=PROCESS_TREE_WAIT_SECONDS)
335
+ finished_process.wait(timeout=PROCESS_TREE_WAIT_SECONDS)
336
+ monkeypatch.setattr(
337
+ probe,
338
+ "terminate_process_tree",
339
+ lambda target_process: all_killed_processes.append(target_process),
340
+ )
341
+
342
+ probe._terminate_process_tree(finished_process)
343
+
344
+ assert all_killed_processes == [finished_process]
345
+
346
+ def should_return_when_a_killed_child_never_reaps(
347
+ self,
348
+ monkeypatch: pytest.MonkeyPatch,
349
+ ) -> None:
350
+ """A child that outlives its grace window ends teardown, never hangs it."""
351
+ probe = load_probe_module()
352
+
353
+ class _UnreapableProcess:
354
+ def wait(self, timeout: float | None = None) -> int:
355
+ raise subprocess.TimeoutExpired(cmd="codex", timeout=timeout or 0)
356
+
357
+ monkeypatch.setattr(probe, "terminate_process_tree", lambda _process: None)
358
+
359
+ probe._terminate_process_tree(_UnreapableProcess())
355
360
 
356
361
 
357
362
  class TestReaderThreadLifecycle:
@@ -890,38 +890,6 @@ def test_run_command_kills_grandchild_tree_on_timeout_without_hanging(
890
890
  assert _wait_until_process_stops(grandchild_identifier, wall_clock_ceiling_seconds)
891
891
 
892
892
 
893
- def test_windows_process_tree_kill_builds_taskkill_argv(
894
- monkeypatch: pytest.MonkeyPatch,
895
- ) -> None:
896
- """The Windows kill path issues taskkill /T /F /PID for the given process id."""
897
- target_process_identifier = 4242
898
- recorded_argv: list[list[str]] = []
899
-
900
- def record_argv(all_arguments: list[str], **_keywords: object) -> None:
901
- recorded_argv.append(all_arguments)
902
-
903
- monkeypatch.setattr(wrapper.subprocess, "run", record_argv)
904
- wrapper._kill_windows_process_tree(target_process_identifier)
905
-
906
- assert recorded_argv == [
907
- ["taskkill", "/T", "/F", "/PID", str(target_process_identifier)]
908
- ]
909
-
910
-
911
- def test_windows_process_tree_kill_swallows_taskkill_timeout(
912
- monkeypatch: pytest.MonkeyPatch,
913
- ) -> None:
914
- """A hung taskkill must not raise — caller falls back to Popen.kill + drain."""
915
-
916
- def raise_taskkill_timeout(
917
- _all_arguments: list[str], **_keywords: object
918
- ) -> None:
919
- raise subprocess.TimeoutExpired(cmd="taskkill", timeout=1)
920
-
921
- monkeypatch.setattr(wrapper.subprocess, "run", raise_taskkill_timeout)
922
- wrapper._kill_windows_process_tree(4242)
923
-
924
-
925
893
  def test_drain_joins_pipes_when_direct_kill_raises_process_lookup_error() -> None:
926
894
  """Even when the process is already gone, drain still joins pipe readers."""
927
895
  all_communicate_timeouts: list[float | None] = []
@@ -990,7 +958,7 @@ def test_run_command_surfaces_timeout_when_tree_kill_is_noop(
990
958
  def leave_process_alive(_review_process: object) -> None:
991
959
  return None
992
960
 
993
- monkeypatch.setattr(wrapper, "_terminate_process_tree", leave_process_alive)
961
+ monkeypatch.setattr(wrapper, "terminate_process_tree", leave_process_alive)
994
962
  start_time = time.monotonic()
995
963
  with pytest.raises(subprocess.TimeoutExpired):
996
964
  wrapper._run_command(
@@ -13,11 +13,12 @@ description: >-
13
13
 
14
14
  ## Gotchas
15
15
 
16
- - **`low` stays single-pass.** Do not spawn subagents. One diff read, one findings pass.
16
+ - **`low` stays single-pass.** No subagents, no full-file reads: one read pass per target item, one findings pass.
17
17
  - **`medium` favors precision, `xhigh` favors recall.** At `medium` (8 angles) surface only findings a maintainer would act on. At `xhigh` (10 angles plus a gap sweep) a single non-REFUTED vote carries the finding; do not drop on uncertainty.
18
- - **`--fix` applies findings once.** Load `reference/fix.md` and follow it it owns the fix agent, the commit gate, skip logging, and outcome reporting.
19
- - **`loop` never asks.** After findings, fix nits or stop on bugs. Load `reference/loop.md` and follow it.
20
- - **`--fix` and `loop` combine.** With both, each loop round runs the level file, then `reference/fix.md`, then re-reviews.
18
+ - **Every retained finding carries `severity` and `verdict`.** Severity is one of `blocker`, `high`, `medium`, `low`, `nit`. Verdict is `CONFIRMED` or `PLAUSIBLE`. Drop REFUTED candidates; never emit an unclassified retained finding.
19
+ - **`--fix` applies findings once.** Load `reference/fix.md` and follow it — it owns the fix agent, the code-rules gate, skip logging, and outcome reporting. Commits are lead-owned; fix agents never commit or push.
20
+ - **`loop` never asks.** A round with bug findings validates them with an advisor, fixes, and re-reviews. Terminals are exactly `clean`, `nits_fixed`, and `advisor_blocked`. There is no reviewed-head count limit; a new head increments the count once, a re-review of the same head does not. Load `reference/loop.md` and follow it.
21
+ - **`--fix` and `loop` combine.** With both, each loop round runs the level file, and the round's fixing happens inside `reference/loop.md`'s gate sequence, which loads `reference/fix.md` for the mechanics. There is no separate fix pass around the round.
21
22
 
22
23
  ## When this skill applies
23
24
 
@@ -31,8 +32,8 @@ Triggers: `/e-code-review <level> [--fix] [loop]`. `<level>` is `low`, `medium`,
31
32
 
32
33
  1. Read `<level>` and the optional `--fix` and `loop` flags. Apply the refusal first.
33
34
  2. Load `reference/low.md`, `reference/medium.md`, or `reference/xhigh.md`. Run that file as one review cycle, ending in its structured findings report.
34
- 3. If `--fix` is set, load `reference/fix.md` and apply it to that cycle's findings.
35
- 4. If `loop` is set, load `reference/loop.md` and follow it. Each round re-runs the same level file from step 2, plus step 3 when `--fix` is set.
35
+ 3. If `--fix` is set and `loop` is not set, load `reference/fix.md` and apply it to that cycle's findings. This path has no commit step: the fixes stay uncommitted in the working tree for the user to review and commit.
36
+ 4. If `loop` is set, load `reference/loop.md` and follow it with that cycle's findings still unfixed. Round 1's findings are the ones step 2 already produced; from the second round on, the round re-runs that same level file end to end. End to end means the level file's review phases, up to and including its findings report — not its *Looping* section, which hands control to `loop.md` and would re-enter the loop the round is already inside. `loop.md`'s gate sequence owns when the round fixes.
36
37
  5. Without `--fix` or `loop`, return the cycle findings and stop.
37
38
 
38
39
  ## File index
@@ -40,10 +41,10 @@ Triggers: `/e-code-review <level> [--fix] [loop]`. `<level>` is `low`, `medium`,
40
41
  | File | Purpose |
41
42
  |---|---|
42
43
  | `SKILL.md` | Route by level; dispatch `--fix` and `loop` |
43
- | `reference/low.md` | low review procedure — 1 diff pass, no verify |
44
+ | `reference/low.md` | low review procedure — 1 diff pass per target item, no verify |
44
45
  | `reference/medium.md` | medium review procedure — 8 angles, 1-vote verify |
45
46
  | `reference/xhigh.md` | xhigh review procedure — 10 angles, 1-vote verify, gap sweep |
46
- | `reference/fix.md` | Fix application, commit gate, skip logging, outcome reporting |
47
+ | `reference/fix.md` | Fix application, code-rules gate, skip logging, outcome reporting |
47
48
  | `reference/loop.md` | Repeat review/fix rounds until clean |
48
49
 
49
50
  ## Folder map
@@ -12,16 +12,38 @@ At `low` — and any time the Agent tool is unavailable — no finder agent exis
12
12
  to resume. Apply the fixes yourself, sequentially, in this context, holding to
13
13
  every rule below.
14
14
 
15
- ## Commit gate
15
+ ## Code-rules gate
16
16
 
17
- Before returning, the resumed agent attempts a commit on its own change:
17
+ Before returning, the resumed agent runs the code-rules gate with the same bare
18
+ call every other surface in this skill uses:
18
19
 
19
20
  1. Run `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo
20
- root> <changed/added files>` against every file it changed or added.
21
- 2. If the gate reports violations, fix them and re-run the exact same command.
22
- 3. Repeat until the gate returns clean.
23
- 4. Only after a clean gate result does the agent return control and report
24
- its outcome.
21
+ root>` with no file paths and no `--only-under` prefix.
22
+ 2. If the gate reports violations **on lines this fix already owns** (the files
23
+ and added lines the fix changed), fix them and re-run the exact same command.
24
+ 3. A violation on a path or added line outside this fix's own work is reported
25
+ or skipped — it is not force-fixed into unrelated files. Log each skip with
26
+ the path and reason, then continue.
27
+ 4. Repeat until every violation on this fix's own work is clean (or skipped with
28
+ a logged reason for out-of-scope hits).
29
+ 5. Only after that result does the agent return control and report its outcome.
30
+
31
+ A file path named on the command line puts the gate in whole-file scope and can
32
+ churn on untouched lines; keep the call bare.
33
+
34
+ This gate commits nothing. The fix lands in the working tree and stays
35
+ uncommitted. **Commit ownership is lead-only:** a fix agent, patch worker, or
36
+ resumed finding agent never runs `git commit` or `git push`. The lead that owns
37
+ the branch creates the commit after gates pass. The bare code-rules call covers
38
+ the merge-base surface and staged added lines only — working-tree-only lines
39
+ enter that scope after the lead stages or commits them.
40
+
41
+ Under a bare `--fix` — this document invoked without `loop` — nothing
42
+ downstream commits either, and that is the intended outcome: a one-shot fix
43
+ pass leaves its fixes uncommitted in the working tree for the lead to review
44
+ and commit (stage first if you re-run the gate on those lines). Under `loop`,
45
+ the round tail in `reference\loop.md` stages and runs required checks; the lead
46
+ still owns the commit and push.
25
47
 
26
48
  ## Skip candidates
27
49
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Act
4
4
 
5
- `loop` on the hub command authorizes the full cycle. After the effort level procedure returns findings, take the matching branch below immediately.
5
+ `loop` on the hub command authorizes the full cycle. After the effort level procedure returns findings, run the gate sequence below immediately.
6
6
 
7
7
  Do not ask whether to fix, which nits to keep, whether to commit or push, or whether to re-review. Do not open a plan fork. Do not end the turn on a recommendation.
8
8
 
@@ -10,11 +10,18 @@ Report progress while you work. Stop for the user only on a terminal outcome bel
10
10
 
11
11
  ## Where fixes come from
12
12
 
13
- When `--fix` is also set, each round's fix pass runs `reference\fix.md`
14
- (relative to this skill's folder) it owns which agent applies each fix, the
15
- commit gate, skip logging, and outcome reporting. Apply the branch rules below
16
- on top of it: they decide whether a round fixes, commits, pushes, and
17
- re-reviews.
13
+ A round fixes whether or not `--fix` is set. Gate 2 below applies the round's
14
+ fixes on every run, and its three cases read the same way with the flag and
15
+ without it. What `--fix` adds is `fix.md`'s mechanics, not permission to fix:
16
+ the flag decides where the mechanics come from, never whether a round fixes at
17
+ all.
18
+
19
+ When `--fix` is also set, the round's fixing happens inside the gate sequence
20
+ below. Gate 2 is the only place a round applies a fix, and there is no separate
21
+ fix pass sitting around the round. Gate 2 loads `reference\fix.md` (relative to
22
+ this skill's folder) for the mechanics — which agent applies each fix, agent
23
+ resume, the code-rules gate, skip logging, and outcome reporting — while the
24
+ gate sequence decides whether a round fixes, commits, pushes, and re-reviews.
18
25
 
19
26
  ## Scope stays narrow
20
27
 
@@ -22,22 +29,231 @@ Auto-fix only verified findings on the review target. Leave deferred PR-body fol
22
29
 
23
30
  ## How to class each finding
24
31
 
25
- Use the finding's verified `severity` when the level emits one.
32
+ Every retained finding carries a verified `severity` and a verification
33
+ `verdict` (`CONFIRMED` or `PLAUSIBLE`). The frozen severity set is exactly
34
+ `blocker`, `high`, `medium`, `low`, `nit`.
35
+
36
+ A finding is a `nit` only when that severity is `nit`. Runtime-correctness,
37
+ security, data-loss, compatibility, and every other non-nit finding is a
38
+ `bug`.
26
39
 
27
- A finding is a `nit` only when that severity is `nit`. Runtime-correctness, security, data-loss, compatibility, and every other non-nit finding is a `bug`.
40
+ A finding that lacks severity is **unclassified**. Consult the assigned
41
+ advisor to classify it before Gate 2. When that advisor is unreachable,
42
+ terminate with `advisor_blocked` and preserve draft state.
28
43
 
29
- If the level emits no severity (for example untagged `low` lines), consult your advisor to determine classification.
44
+ A finding this document has already classed keeps that class at every level. An
45
+ on-target shape-reader break is `bug` under *A shape change names its readers*;
46
+ no advisor call reopens that.
30
47
 
31
48
  ## Required checks
32
49
 
33
- "Run required checks" means: run `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo root> <changed/added files>` against every file changed or added in the round. On any violation, fix it and re-run the exact same command again — repeat until it reports clean.
50
+ "Run required checks" means: run
51
+ `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo root>`
52
+ with no file paths and no `--only-under` prefix. On a violation on lines this
53
+ round already owns, fix it and re-run the exact same command until clean. A
54
+ violation outside the review target is reported or skipped — not force-fixed into
55
+ unrelated files — and the skip is logged with path and reason.
56
+
57
+ That bare call gates the git diff since the merge-base (and any staged added
58
+ lines the gate already includes). Working-tree lines that are neither staged
59
+ nor committed sit outside that scope until they are staged or committed. Gate 2
60
+ leaves its edits uncommitted, so the round tail stages those edits before
61
+ required checks when this round produced edits.
62
+
63
+ Two failure scenarios keep the call bare. A file path named on the command line
64
+ puts the gate in whole-file scope on that file: a CODE_RULES violation on a line
65
+ this round never touched fails the round and churns the loop. An `--only-under`
66
+ prefix that matches nothing in the diff leaves the gate zero files to inspect and
67
+ exits clean, so a run that inspected nothing reads as a pass.
68
+
69
+ ## Each round reviews new code
70
+
71
+ A repair diff is new code. From the second round on, the round runs the level
72
+ file end to end at the new head. End to end means that file's review phases, up
73
+ to and including its findings report; the round stops there and brings those
74
+ findings back to the gate sequence below. It does not run the level file's
75
+ *Looping* section — that section hands control to this document, and the round
76
+ is already inside it. The round's scope is the level's own review
77
+ target — the diff or path the level gathers up front, called Phase 0 in
78
+ `medium.md` and `xhigh.md` — taken against that target's base. A repair edit
79
+ landing outside that target widens the next round's scope to cover it: the next
80
+ round's review target is the original target **plus** that path, and the round
81
+ reviews both. A later widening adds to that target the same way.
82
+
83
+ The widened target is what the round hands the level file. When a round runs the
84
+ level file end to end, it passes the current review target — the original target
85
+ plus every path a widening has since added — as that run's target argument, in
86
+ place of the argument the first round was given. When the first round was given
87
+ no target argument, the original target is the item `default-range` — the level
88
+ file's own default gather — so the widened target is `default-range` plus every
89
+ added path, and the range the first round reviewed stays in scope. The level
90
+ file gathers what the round hands it, so a widened path is gathered and reviewed
91
+ like any other part of the target.
92
+
93
+ ## Dangerous diffs take two full rounds
94
+
95
+ A diff is dangerous when it touches deletion paths, locks or other concurrency
96
+ control, or shared mutable state. A deletion path is a runtime path that removes
97
+ data or files; a dead-code cleanup is not one. Each round names whether the diff
98
+ it reviewed is dangerous. A dangerous diff holds the loop open until two full
99
+ rounds have reviewed it. A repair that rewrites the dangerous surface restarts
100
+ the two-round count at the first round that reviews the rewritten surface.
101
+
102
+ The round's progress report is where both facts are recorded: the dangerous
103
+ classification, and the dangerous-round count written as `N of M`.
104
+
105
+ ## A shape change names its readers
106
+
107
+ When a repair changes a key, an identifier format, or a data shape, list every
108
+ reader of the shape it changed and state how each one reads the new shape. The
109
+ list goes in the round's progress report.
110
+
111
+ The round that follows a posted list checks each reader on that list against the
112
+ new shape and names each reader with its result in that round's progress report.
113
+
114
+ Discharge then turns on where the reader sits.
115
+
116
+ - An **off-target** reader discharges once it is named with a result. This
117
+ branch cannot repair it.
118
+ - An **on-target** reader that reads the new shape correctly discharges on that
119
+ result.
120
+ - An **on-target** reader that reads the new shape wrong is a bug-severity
121
+ finding of the round that checked it. It joins that round's findings, and it
122
+ discharges on exactly one of three results, each of them an outcome gate 2
123
+ already produces: it is repaired, it is recorded as a skipped finding, or the
124
+ advisor refutes it. Naming the break discharges nothing on its own, and
125
+ neither does a further round merely happening.
126
+
127
+ The list discharges once every reader on it has discharged.
128
+
129
+ A broken reader outside the review target does not block the loop and does not
130
+ widen scope. Hand it off as a reported finding. Two separate things are owed for
131
+ that hand-off, and they land at different times:
132
+
133
+ - **The round-scoped record** — what a round produces. Check the reader, record
134
+ its result, and name it in that round's progress report. All three are done
135
+ inside the round that checks the reader, and nothing outside that round is
136
+ needed to complete them. This is the record gate 2 reads.
137
+ - **The termination-time disclosure** — what terminating requires. The
138
+ ready-for-review message names every broken off-target reader and every
139
+ skipped finding that still exists, and the pull request body carries the same
140
+ names when the target is a pull request. A target with no pull request owes
141
+ the ready message alone. Gate 3 enforces this at the moment the loop
142
+ terminates; no earlier round owes it.
34
143
 
35
144
  ## Terminal outcomes
36
145
 
37
- Repeat the same-level review/fix cycle until one of these holds:
146
+ The loop emits exactly one of these terminals when it stops:
147
+
148
+ | Terminal | When |
149
+ |---|---|
150
+ | `clean` | Zero retained findings on the current head, and required checks pass |
151
+ | `nits_fixed` | Every retained finding is a nit with severity and a retained verdict, every nit is fixed, and required checks pass |
152
+ | `advisor_blocked` | Classification needs the assigned advisor and that advisor is unreachable |
153
+
154
+ Every round does this round's own work first, then runs the three gates below,
155
+ in order: gate 1, then gate 2, then gate 3.
156
+
157
+ **This round's work — before the gates.** Record this round's dangerous
158
+ classification and the dangerous-round count as `N of M`; and when a shape-change
159
+ list is open, check each reader on that list and name each reader with its
160
+ result, adding any on-target reader that reads the new shape wrong to this
161
+ round's findings as *A shape change names its readers* directs. Then run the
162
+ gates.
163
+
164
+ **Gate 1 — obligations.** Gate 1 is evaluated first in the sequence, and its
165
+ answer turns on the round's open obligations alone — the findings are in hand by
166
+ now, and no content they carry changes it. Ask only: does any obligation remain
167
+ open? Two kinds exist.
168
+
169
+ - A dangerous diff that has had fewer than two full rounds.
170
+ - A posted shape-change list that no round has discharged.
171
+
172
+ Gate 1 states its answer and stops there: an obligation remains open, or none
173
+ does. It states no re-entry, no continuation, and no routing. Gate 3 is the sole
174
+ router — every path out of a round passes through it.
175
+
176
+ **Gate 2 — findings.** When `--fix` is set, load `reference\fix.md` here and
177
+ follow it for the mechanics of every fix this gate applies — the fix agent,
178
+ agent resume, the code-rules gate, skip logging, and outcome reporting. When `--fix` is
179
+ absent, apply each fix in this session yourself, and log each skip in this
180
+ round's progress report. Then take the one case that matches the round's
181
+ findings.
182
+
183
+ - Any bug-severity finding: validate each bug with an advisor before touching
184
+ code — confirm it's real and confirm the intended fix — then fix every
185
+ validated bug and every nit on the review target. A refuted bug removes only
186
+ itself from the round's work; the nits are fixed either way.
187
+ - Nits only, with at least one nit present: fix all of them on the review
188
+ target.
189
+ - No findings at all: make no edits.
190
+
191
+ Gate 2 then ends by stating one of exactly two outcomes: unresolved findings
192
+ remain, or none remain. A refuted bug is resolved. A fixed nit is resolved. A
193
+ fixed validated bug is resolved. A handed-off off-target finding is resolved once
194
+ this round has made its round-scoped record as *A shape change names its readers*
195
+ defines that record — checked, result recorded, named in this round's progress
196
+ report — whether or not the problem behind it is solved. Gate 2 reads the record
197
+ and nothing else; the termination-time disclosure belongs to gate 3.
198
+
199
+ A skipped finding — a finding deliberately not applied, because fixing it would
200
+ change intended behavior, would reach beyond the review target, or the finding
201
+ itself is judged a false positive — is resolved once its skip is logged in this
202
+ round's progress report, naming the finding and the reason it was skipped. That
203
+ report is the sink every run has, with or without `--fix`. When `--fix` is set,
204
+ the skip handling `fix.md` carries runs inside this gate and adds to this log
205
+ rather than replacing it.
206
+
207
+ Gate 3 reads that stated outcome, never a case label.
208
+
209
+ **Gate 3 — exit test.** Resolve the terminal from the table above, then stop or
210
+ continue:
211
+
212
+ - zero retained findings and required checks pass → `clean`;
213
+ - nits only (each with severity and a retained verdict), all fixed, required checks pass → `nits_fixed`;
214
+ - advisor needed for classification and unreachable → `advisor_blocked`;
215
+ - otherwise continue.
216
+
217
+ Also require, for `clean` and `nits_fixed`:
218
+
219
+ - gate 1 shows no open obligation;
220
+ - gate 2 states no unresolved findings remain;
221
+ - this round produced no edits after the gates settled.
222
+
223
+ Any other combination runs the round tail and re-enters the loop.
224
+
225
+ Terminating with `clean` or `nits_fixed` carries one further condition — the
226
+ termination-time disclosure: the ready-for-review message names every broken
227
+ off-target reader and every skipped finding that still exists. When the target
228
+ is a pull request, the pull request body carries the same names; a target with
229
+ no pull request owes the ready message alone. Every surface this condition
230
+ names is written at termination — the ready message always, the pull request
231
+ body too when the target is a pull request — so each one is available to the
232
+ terminating round. A round that cannot name them does not terminate; it runs
233
+ the round tail and re-enters the loop, the same as any other non-terminating
234
+ round. With that condition met, post the proof-of-work PR comment when the
235
+ target is a PR, then run `gh pr ready` for a draft PR, or state ready
236
+ otherwise.
237
+
238
+ `advisor_blocked` keeps the pull request draft. It does
239
+ not run `gh pr ready`. It reports every surviving structured finding.
240
+
241
+ Gate 3 points at gate 1 for the obligation answer. It does not restate the
242
+ two-round rule or the shape-reader rule; each of those keeps its one home in its
243
+ own section above.
244
+
245
+ **The round tail.** Every round runs required checks here, in the one form
246
+ *Required checks* gives: the bare command, no file paths. When this round
247
+ produced edits, stage those edits first so they enter the bare gate's scope,
248
+ then run the checks. When this round produced no edits, run the same checks.
249
+ Should those checks produce repairs, this round has produced edits — stage
250
+ them and re-run the checks.
38
251
 
39
- - **Clean.** Findings are `[]` or `(none)`. Post the proof-of-work PR comment when the target is a PR. Run `gh pr ready` for a draft PR; otherwise state ready.
40
- - **Nits only.** Every surviving finding is severity `nit`. Fix all of them on the review target. Run required checks. Commit once per loop round. Push. Consider this clean — post the proof-of-work PR comment and mark ready as in the Clean branch above.
41
- - **Any bug.** Validate each bug-severity finding with an advisor before touching code — confirm it's real and confirm the intended fix. Then fix all validated findings (bugs and nits) on the review target. Run required checks. Commit once per loop round. Push. Run the same effort level on the new head. Repeat until clean, then mark ready as above.
252
+ **Commit and push are lead-owned.** Gate 2 and the round tail leave every fix
253
+ unstaged-or-staged in the working tree for the lead that owns the branch. A
254
+ fix agent, patch worker, or resumed finding agent never creates the commit and
255
+ never pushes. The lead stages, commits once per review round, and pushes after
256
+ the gates pass. Start the next round under *Each round reviews new code* only
257
+ after that lead commit lands a new head.
42
258
 
43
259
  Do not drop findings to force ready. Without `loop`, run one review at the selected level, fix, and return every validated finding.
@@ -1,13 +1,20 @@
1
- `low effort → 1 diff pass → no verify → ≥min(files,4) findings`
1
+ `low effort → 1 diff pass per target item → no verify → ≥min(files,4) findings`
2
2
 
3
3
  ## Turn 1 — read
4
4
 
5
- One tool call: read the unified diff (`git diff @{upstream}...HEAD; git diff HEAD`
5
+ Read the unified diff (`git diff @{upstream}...HEAD; git diff HEAD`
6
6
  to cover both committed and uncommitted changes, or `git diff main...HEAD` /
7
- the target passed as an argument). Skip test/fixture
8
- hunks (`test/`, `spec/`, `__tests__/`, `*_test.*`, `*.test.*`,
7
+ the target passed as an argument). A target names one or more items, each a PR
8
+ number, a branch name, a file path, or `default-range` — the default `git diff`
9
+ read named above, which is what this step reads when no target is passed — and
10
+ it may mix those forms. A loop round widens a target by adding a path to whatever it
11
+ started as, and it names `default-range` as an item whenever the round it
12
+ widened was given no target argument, so the original scope stays under review.
13
+ When a target names more than one item, read each item's diff and review their
14
+ union — a shared hunk counted once, an empty one adding nothing. Skip
15
+ test/fixture hunks (`test/`, `spec/`, `__tests__/`, `*_test.*`, `*.test.*`,
9
16
  `fixtures/`, `testdata/`) — test-file changes are not reviewed at this level.
10
- No subagents, no full-file reads.
17
+ One read pass per target item, and no more: no subagents, no full-file reads.
11
18
 
12
19
  ## Turn 2 — findings
13
20
 
@@ -32,11 +39,13 @@ also print the findings as text.
32
39
 
33
40
  ## Applying fixes (--fix)
34
41
 
35
- The `--fix` flag was passed. Follow `reference\fix.md` (relative to this
36
- skill's folder) for the exact fix, commit-gate, and skip-handling behavior —
37
- it governs which agent applies each fix, how a fix gets committed, how a skip
38
- is logged, and how outcomes get reported. Do not repeat the findings as text;
39
- follow that document's reporting rules once fixes land.
42
+ The `--fix` flag was passed. Follow `reference\fix.md` (relative
43
+ to this skill's folder) for the exact fix, code-rules-gate, and skip-handling
44
+ behavior — it governs which agent applies each fix, how the code-rules gate
45
+ runs, how a skip is logged, and how outcomes get reported. Do not repeat the
46
+ findings as text; follow that document's reporting rules once fixes land.
47
+
48
+ When `loop` is also set, skip this section.
40
49
 
41
50
  ## If findings are fixed later
42
51
 
@@ -50,8 +59,17 @@ summary; the host UI's per-finding status updates only from that call.
50
59
  ## Looping (`loop`)
51
60
 
52
61
  The `loop` arg was passed. Follow `reference\loop.md` (relative to this
53
- skill's folder) for how to re-run Turn 1 (read), Turn 2 (findings), and (if
54
- `--fix` is also present) `reference\fix.md`'s fix pass, repeatedly
55
- including its exit condition, iteration cap, and re-invocation rules. Do not
56
- treat a single pass through this document as complete while `loop` is active;
57
- hand control to that document instead of stopping at Turn 2.
62
+ skill's folder) for how to re-run Turn 1 (read) and Turn 2 (findings)
63
+ repeatedly including its exit condition and re-invocation rules. Schedule no
64
+ fix pass of your own here: when `--fix` is also present, `reference\loop.md`'s
65
+ gate sequence owns the round's fixing and loads `reference\fix.md` for the
66
+ mechanics. Do not treat a single pass through this document as complete while
67
+ `loop` is active; hand control to that document, and do not stop at Turn 2.
68
+
69
+ That hand-off applies when this document is entered directly. When a loop round
70
+ is already running and has handed this document its target, the round owns the
71
+ loop: end at Turn 2 with the findings report and return those findings to
72
+ `reference\loop.md`'s gate sequence, rather than handing control to that
73
+ document again from here.
74
+
75
+ When `loop` was not passed, skip this section.