claude-dev-env 8.11.0 → 8.12.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 (47) hide show
  1. package/.agents/skills/e-code-review/SKILL.md +24 -71
  2. package/.agents/skills/e-code-review/reference/fix.md +7 -78
  3. package/.agents/skills/e-code-review/reference/high.md +129 -0
  4. package/.agents/skills/e-code-review/reference/low.md +20 -51
  5. package/.agents/skills/e-code-review/reference/max.md +169 -0
  6. package/.agents/skills/e-code-review/reference/medium.md +7 -191
  7. package/.agents/skills/e-code-review/reference/xhigh.md +47 -91
  8. package/_shared/pr-loop/scripts/fix_pr_test_proof.py +113 -12
  9. package/_shared/pr-loop/scripts/pr_loop_shared_constants/fix_pr_test_proof_constants.py +5 -1
  10. package/_shared/pr-loop/scripts/test_fix_pr_test_proof.py +63 -3
  11. package/bin/codex-skill-load-block.mjs +67 -19
  12. package/bin/codex-skill-load-block.test.mjs +87 -25
  13. package/bin/install.agents-home.test.mjs +9 -9
  14. package/bin/install.mjs +4 -2
  15. package/bin/install.test.mjs +20 -0
  16. package/commands/sr-loop.md +3 -1
  17. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +3 -3
  18. package/hooks/blocking/tests/test_repository_exemption.py +7 -0
  19. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -1
  20. package/package.json +1 -1
  21. package/rules/build-needs-a-user.md +1 -1
  22. package/rules/testing.md +1 -1
  23. package/scripts/policy_lint/config/constants.py +1 -1
  24. package/scripts/repository_checks/tracked_private_terms.py +15 -1
  25. package/scripts/tests/test_policy_lint_rules_callers.py +14 -0
  26. package/scripts/tests/test_tracked_private_terms.py +35 -0
  27. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +0 -20
  28. package/.agents/skills/e-code-review/reference/effort-evaluation.md +0 -35
  29. package/.agents/skills/e-code-review/reference/loop.md +0 -261
  30. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +0 -67
  31. package/.agents/skills/e-code-review/reference/preflight-proposal.md +0 -75
  32. package/.agents/skills/e-code-review/reference/runner-selection.md +0 -40
  33. package/.agents/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +0 -41
  34. package/.agents/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +0 -40
  35. package/.agents/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +0 -1
  36. package/.agents/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +0 -49
  37. package/.agents/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +0 -55
  38. package/.agents/skills/e-code-review/scripts/effort_defaults_evidence.json +0 -186
  39. package/.agents/skills/e-code-review/scripts/effort_evaluation.py +0 -362
  40. package/.agents/skills/e-code-review/scripts/finding_pipeline.py +0 -140
  41. package/.agents/skills/e-code-review/scripts/fixtures/demanding.json +0 -26
  42. package/.agents/skills/e-code-review/scripts/fixtures/easy.json +0 -14
  43. package/.agents/skills/e-code-review/scripts/fixtures/medium.json +0 -20
  44. package/.agents/skills/e-code-review/scripts/grok_code_review.py +0 -230
  45. package/.agents/skills/e-code-review/scripts/test_effort_evaluation.py +0 -180
  46. package/.agents/skills/e-code-review/scripts/test_finding_pipeline.py +0 -197
  47. package/.agents/skills/e-code-review/scripts/test_grok_code_review.py +0 -289
@@ -1,198 +1,14 @@
1
- `medium effort → 3+5 angles → 1-vote verify`
1
+ `minimal prompt → single careful diff pass → ≤15 findings`
2
2
 
3
- You are reviewing for **precision** at medium effort: assign severity carefully
4
- so a later consumer can select maintainer-action findings. **Collection
5
- reports every finding** (including `low` and `nit`); do not drop by
6
- severity during collection. Severity filtering is a separate consumer stage
7
- after the collection record is complete.
8
-
9
- ## Phase 0 — Gather the diff
10
-
11
- Run `git diff @{upstream}...HEAD` (or `git diff main...HEAD` / `git diff HEAD~1`
3
+ You are reviewing a pull request for bugs. Run `git diff @{upstream}...HEAD` (or `git diff main...HEAD` / `git diff HEAD~1`
12
4
  if there's no upstream) to get the unified diff under review. If there are
13
5
  uncommitted changes, or the range diff is empty, also run `git diff HEAD` and
14
6
  include the working-tree changes in scope — the review often runs before the
15
- commit. If a target was passed as an argument, review that target instead. A
16
- target names one or more items, each a PR number, a branch name, a file path, or
17
- `default-range` — the diff this phase gathers when no target is passed — and it
18
- may mix those forms. A loop round widens a target by adding a path to whatever
19
- it started as, and it names `default-range` as an item whenever the round it
20
- widened was given no target argument, so the original scope stays under review.
21
- When a target names more than one item, gather each item's diff and take their
22
- union — a shared hunk counted once, an empty one adding nothing — as the
23
- target's diff. Treat this diff as the review scope.
24
-
25
- ## Phase 1 — Find candidates (3 correctness angles + 3 cleanup angles + 1 altitude angle + 1 conventions angle)
26
-
27
- Run **8 independent finder angles** via the Agent tool. Each surfaces
28
- candidate findings with `file`, `line`, a one-line `summary`, and a concrete
29
- `failure_scenario`. If the Agent tool is not available in your current tool
30
- set, do not error — perform each angle (and each verification) yourself,
31
- sequentially, in this context.
32
-
33
- ### Angle A — line-by-line diff scan
34
-
35
- Read every hunk in the diff, line by line. Then Read the enclosing function for
36
- each hunk — bugs in unchanged lines of a touched function are in scope (the PR
37
- re-exposes or fails to fix them). For every line ask: what input, state, timing,
38
- or platform makes this line wrong? Look for inverted/wrong conditions,
39
- off-by-one, null/undefined deref, missing `await`, falsy-zero checks,
40
- wrong-variable copy-paste, error swallowed in catch, unescaped regex metachars.
41
-
42
- ### Angle B — removed-behavior auditor
43
-
44
- For every line the diff DELETES or replaces, name the invariant or behavior it
45
- enforced, then search the new code for where that invariant is re-established.
46
- If you can't find it, that's a candidate: a removed guard, a dropped error
47
- path, a narrowed validation, a deleted test that was covering a case.
48
-
49
- ### Angle C — cross-file tracer
50
-
51
- For each function the diff changes, find its callers (Grep for the symbol) and
52
- check whether the change breaks any call site: a new precondition, a changed
53
- return shape, a new exception, a timing/ordering dependency. Also check callees:
54
- does a parallel change in the same PR make a call unsafe?
55
-
56
- ### Reuse
57
-
58
- The angles above hunt for bugs; this one and the next two hunt for cleanup in
59
- the changed code. Flag new code that re-implements something the codebase
60
- already has — Grep shared/utility modules and files adjacent to the change,
61
- and name the existing helper to call instead.
62
-
63
- ### Simplification
64
-
65
- Flag unnecessary complexity the diff adds: redundant or derivable state,
66
- copy-paste with slight variation, deep nesting, dead code left behind. Name
67
- the simpler form that does the same job.
68
-
69
- ### Efficiency
70
-
71
- Flag wasted work the diff introduces: redundant computation or repeated I/O,
72
- independent operations run sequentially, blocking work added to startup or
73
- hot paths. Also flag long-lived objects built from closures or captured
74
- environments — they keep the entire enclosing scope alive for the object's
75
- lifetime (a memory leak when that scope holds large values); prefer a
76
- class/struct that copies only the fields it needs. Name the cheaper
77
- alternative.
78
-
79
- ### Altitude
80
-
81
- Check that each change is implemented at the right depth, not as a fragile
82
- bandaid. Special cases layered on shared infrastructure are a sign the fix
83
- isn't deep enough — prefer generalizing the underlying mechanism over adding
84
- special cases.
85
-
86
- ### Conventions (CLAUDE.md)
87
-
88
- Find the CLAUDE.md files that govern the changed code: the user-level
89
- ~/.claude/CLAUDE.md, the repo-root CLAUDE.md, plus any CLAUDE.md or
90
- CLAUDE.local.md in a directory that is an ancestor of a changed file (a
91
- directory's CLAUDE.md only applies to files at or below it). Read each one
92
- that exists, then check the diff for clear violations of the rules they state.
93
-
94
- Only flag a violation when you can quote the exact rule and the exact line
95
- that breaks it — no style preferences, no vague "spirit of the doc"
96
- inferences. In the finding, name the CLAUDE.md path and quote the rule so the
97
- report can cite it. If no CLAUDE.md applies, return nothing for this angle.
98
-
99
- Cleanup, altitude, and conventions candidates use the same
100
- `file`/`line`/`summary` shape; in `failure_scenario`, state the concrete
101
- cost (what is duplicated, wasted, harder to maintain, or which CLAUDE.md rule
102
- is broken) instead of a crash. Correctness bugs always outrank cleanup,
103
- altitude, and conventions findings.
104
-
105
- Pass every candidate with a nameable failure scenario through — finders that
106
- silently drop half-believed candidates bypass the verify step and are the
107
- dominant cause of misses.
108
-
109
- ## Phase 2 — Verify (1-vote, 3-state)
110
-
111
- Dedup candidates that point at the same line/mechanism, keeping the one with
112
- the most concrete failure scenario. For each remaining candidate, run **one
113
- verifier** via the Agent tool: give it the diff, the relevant
114
- file(s), and the candidate, and have it return exactly one of:
115
-
116
- - **CONFIRMED** — can name the inputs/state that trigger it and the wrong
117
- output or crash. Quote the line.
118
- - **PLAUSIBLE** — mechanism is present in the code, trigger is uncertain (timing, env,
119
- config). State what would confirm it.
120
- - **REFUTED** — factually wrong (code doesn't say that) or guarded elsewhere.
121
- Quote the line that proves it.
122
-
123
- Keep candidates where the vote is CONFIRMED or PLAUSIBLE.
124
-
125
- ## Output
126
-
127
- **Collection stage first.** Keep every CONFIRMED or PLAUSIBLE finding in the
128
- collection record, including `low` and `nit`. Drop only REFUTED candidates.
129
- Do not drop a finding because its severity is low.
130
-
131
- Report this review's results — `{level, findings}` — through the structured
132
- findings-report call: the mechanism that renders a review's results as a typed
133
- list in the host UI, ranked most-severe first. Each **retained** entry carries
134
- every field below. Do not emit a finding that lacks `severity` or `verdict`.
135
-
136
- **Severity filter stage (later consumer).** After the collection record is
137
- complete, a separate consumer may filter by minimum severity for action or
138
- display. That filter must not rewrite or discard fields on the collection
139
- record itself.
140
-
141
- | Field | Required | Value |
142
- |---|---|---|
143
- | `file` | yes | path under review |
144
- | `line` | yes | 1-based line number |
145
- | `summary` | yes | full claim |
146
- | `short_summary` | yes | claim compressed to ≤60 characters, no rationale or consequence clause |
147
- | `failure_scenario` | yes | concrete trigger and wrong outcome, or concrete cost for cleanup |
148
- | `category` | yes | short kebab-case slug for the angle (`correctness`, `simplification`, `efficiency`, `reuse`, `altitude`, `conventions`, or a tighter slug like `test-coverage`) |
149
- | `severity` | yes | one of `blocker`, `high`, `medium`, `low`, `nit` |
150
- | `verdict` | yes | `CONFIRMED` or `PLAUSIBLE` from Phase 2 |
151
-
152
- **Severity rules.** Assign exactly one token from the frozen set above. Use
153
- `nit` only when all five hold: the change is localized clarity, formatting, or
154
- a typo; correctness, security, data, and API behavior stay identical; test
155
- coverage and operability stay identical; dependencies and rollout stay
156
- identical; the fix is mechanical and bounded. Any other retained finding is
157
- `low` or higher. Correctness bugs outrank cleanup, altitude, and conventions.
158
-
159
- If nothing survives verification, make that call with an empty array. Do not
160
- also print the findings as text, and do not create or publish an artifact of
161
- the review — the structured call is the report.
162
-
163
- ## Applying fixes (--fix)
164
-
165
- The `--fix` flag was passed. Follow `reference\fix.md` (relative
166
- to this skill's folder) for the exact fix, code-rules-gate, and skip-handling
167
- behavior — it governs which agent applies each fix, how the code-rules gate
168
- runs, how a skip is logged, and how outcomes get reported. Do not repeat the
169
- findings as text; follow that document's reporting rules once fixes land.
170
-
171
- When `loop` is also set, skip this section.
172
-
173
- ## If findings are fixed later
174
-
175
- Whenever a reported finding is fixed later in this session — the user asks you
176
- to fix it, or later work fixes it incidentally — follow `reference\fix.md`'s
177
- reporting rules again: report the same findings through the structured
178
- findings-report call, each carrying an `outcome`. Do not repeat the findings
179
- as text. Make that call immediately after the fixes land, before any prose
180
- summary; the host UI's per-finding status updates only from that call.
181
-
182
- ## Looping (`loop`)
7
+ commit. If a PR number, branch name, or file path was passed as an argument,
8
+ review that target instead. Treat this diff as the review scope.
183
9
 
184
- The `loop` arg was passed. Follow `reference\loop.md` (relative to this
185
- skill's folder) for how to re-run Phases 0–2 and Output repeatedly — including
186
- its exit condition and re-invocation rules. Schedule no fix pass of your own
187
- here: when `--fix` is also present, `reference\loop.md`'s gate sequence owns the
188
- round's fixing and loads `reference\fix.md` for the mechanics. Do not treat a
189
- single pass through this document as complete while `loop` is active; hand
190
- control to that document, and do not stop at Output.
10
+ Review the diff as a careful senior engineer would: read every hunk, open the surrounding files for context as needed (Read, Grep, git log/blame/show), and hunt for correctness issues — wrong or inverted conditions, off-by-one, null/undefined dereference, missing `await`, dropped error handling, removed guards or validations, broken callers of changed functions, races. Report failure modes and leave style out; every finding needs a concrete scenario in which the code misbehaves.
191
11
 
192
- That hand-off applies when this document is entered directly. When a loop round
193
- is already running and has handed this document its target, the round owns the
194
- loop: end at Output with the findings report and return those findings to
195
- `reference\loop.md`'s gate sequence, rather than handing control to that
196
- document again from here.
12
+ When you are done, submit at most 15 findings via the ReportFindings tool, filling its fields as defined — for each: the file path and start line, a severity, and a comment that states the issue and the concrete scenario in which the code misbehaves. Quality over quantity: include every issue you believe is a bug, and nothing you don't.
197
13
 
198
- When `loop` was not passed, skip this section.
14
+ After the tool call, also restate the findings in your final reply — one line each, `file:line — summary` — so they stay visible in sessions that do not render tool output.
@@ -1,7 +1,7 @@
1
- `xhigh effort → 5+5 angles → 1-vote verify → sweep`
1
+ `xhigh effort → 10 inline angles → dedup (no verify) → sweep → ≤15 findings`
2
2
 
3
3
  You are reviewing for **recall** at extra-high effort: catch every bug. At
4
- this level, catching bugs matters more than avoiding false positives — a
4
+ this level, surface a finding even when it may be a false positive. A
5
5
  missed bug ships. Err on the side of surfacing.
6
6
 
7
7
  ## Phase 0 — Gather the diff
@@ -10,23 +10,15 @@ Run `git diff @{upstream}...HEAD` (or `git diff main...HEAD` / `git diff HEAD~1`
10
10
  if there's no upstream) to get the unified diff under review. If there are
11
11
  uncommitted changes, or the range diff is empty, also run `git diff HEAD` and
12
12
  include the working-tree changes in scope — the review often runs before the
13
- commit. If a target was passed as an argument, review that target instead. A
14
- target names one or more items, each a PR number, a branch name, a file path, or
15
- `default-range` — the diff this phase gathers when no target is passed — and it
16
- may mix those forms. A loop round widens a target by adding a path to whatever
17
- it started as, and it names `default-range` as an item whenever the round it
18
- widened was given no target argument, so the original scope stays under review.
19
- When a target names more than one item, gather each item's diff and take their
20
- union — a shared hunk counted once, an empty one adding nothing — as the
21
- target's diff. Treat this diff as the review scope.
22
-
23
- ## Phase 1 — Find candidates (5 correctness angles + 3 cleanup angles + 1 altitude angle + 1 conventions angle)
24
-
25
- Run **10 independent finder angles** via the Agent tool. Each surfaces
26
- candidate findings. Do NOT let one angle's conclusions suppress another's — if
27
- two angles flag the same line for different reasons, record both. If the Agent
28
- tool is not available in your current tool set, do not error — perform each
29
- angle (and each verification) yourself, sequentially, in this context.
13
+ commit. If a PR number, branch name, or file path was passed as an argument,
14
+ review that target instead. Treat this diff as the review scope.
15
+
16
+ ## Phase 1 — Find candidates (5 correctness angles + 3 cleanup angles + 1 altitude angle + 1 conventions angle, up to 8 each)
17
+
18
+ Run **10 independent finder angles** in sequence yourself, in THIS context — do NOT spawn subagents for them. Each
19
+ surfaces **up to 8 candidate findings**. Do NOT let one angle's conclusions
20
+ suppress another's — if two angles flag the same line for different reasons,
21
+ record both.
30
22
 
31
23
  ### Angle A — line-by-line diff scan
32
24
 
@@ -39,10 +31,10 @@ wrong-variable copy-paste, error swallowed in catch, unescaped regex metachars.
39
31
 
40
32
  ### Angle B — removed-behavior auditor
41
33
 
42
- For every line the diff DELETES or replaces, name the invariant or behavior it
34
+ For every line the diff DELETES or rewrites, name the invariant or behavior it
43
35
  enforced, then search the new code for where that invariant is re-established.
44
36
  If you can't find it, that's a candidate: a removed guard, a dropped error
45
- path, a narrowed validation, a deleted test that was covering a case.
37
+ path, a narrowed validation, a deleted test that was covering a live case.
46
38
 
47
39
  ### Angle C — cross-file tracer
48
40
 
@@ -63,7 +55,7 @@ timezone/DST drift; float equality. Flag any instance the diff introduces.
63
55
  When the PR adds or modifies a type that wraps another (cache, proxy, decorator,
64
56
  adapter): check that every method routes to the wrapped instance and not back
65
57
  through a registry/session/global — e.g. a caching provider holding a
66
- `delegate` field that resolves IDs via `session.get(...)` instead of
58
+ `delegate` field that resolves IDs via `session.get(...)` where it should call
67
59
  `delegate.get(...)` will re-enter the cache or recurse. Also check that the
68
60
  wrapper forwards all the methods the callers use.
69
61
 
@@ -92,10 +84,11 @@ alternative.
92
84
 
93
85
  ### Altitude
94
86
 
95
- Check that each change is implemented at the right depth, not as a fragile
96
- bandaid. Special cases layered on shared infrastructure are a sign the fix
97
- isn't deep enough — prefer generalizing the underlying mechanism over adding
98
- special cases.
87
+ Check that each change fixes the root cause at the right depth. A fix that
88
+ patches a symptom with a fragile bandaid is a finding. Special cases layered on shared
89
+ infrastructure are a sign the fix isn't deep enough — prefer the simpler, more
90
+ general change to the underlying mechanism over adding special cases, and name
91
+ that change.
99
92
 
100
93
  ### Conventions (CLAUDE.md)
101
94
 
@@ -113,31 +106,16 @@ report can cite it. If no CLAUDE.md applies, return nothing for this angle.
113
106
  Cleanup, altitude, and conventions candidates use the same
114
107
  `file`/`line`/`summary` shape; in `failure_scenario`, state the concrete
115
108
  cost (what is duplicated, wasted, harder to maintain, or which CLAUDE.md rule
116
- is broken) instead of a crash. Correctness bugs always outrank cleanup,
117
- altitude, and conventions findings.
118
-
119
- ## Phase 2 — Verify (1-vote, 3-state)
109
+ is broken) in the place a crash would go. Correctness bugs always outrank cleanup,
110
+ altitude, and conventions findings when the output cap forces a cut.
120
111
 
121
- Dedup candidates that point at the same line/mechanism, keeping the one with
122
- the most concrete failure scenario. For each remaining candidate, run **one
123
- verifier** via the Agent tool: give it the diff, the relevant
124
- file(s), and the candidate, and have it return exactly one of:
112
+ ## Phase 2 — Dedup only (no verify)
125
113
 
126
- - **CONFIRMED** — can name the inputs/state that trigger it and the wrong
127
- output or crash. Quote the line.
128
- - **PLAUSIBLE** — mechanism is present in the code, trigger is uncertain (timing, env,
129
- config). State what would confirm it.
130
- - **REFUTED** — factually wrong (code doesn't say that) or guarded elsewhere.
131
- Quote the line that proves it.
132
-
133
- Keep candidates where the vote is CONFIRMED or PLAUSIBLE.
134
-
135
- This is recall mode — a single non-REFUTED vote carries the finding. Do NOT
136
- drop on uncertainty.
114
+ Pool all candidates. Dedup near-duplicates only (same defect, same location, same reason → keep one). Do NOT run verifiers; do NOT re-judge. Sort by severity. Do NOT drop on uncertainty.
137
115
 
138
116
  ## Phase 3 — Sweep for gaps
139
117
 
140
- Run **one more finder** as a fresh reviewer who has the verified list. Re-read
118
+ Take one more pass (same context — no subagent) as a fresh reviewer who has the deduplicated list. Re-read
141
119
  the diff and enclosing functions looking ONLY for defects not already listed.
142
120
  Do not re-derive or re-confirm anything already there — the job is gaps. Focus
143
121
  on what the first pass tends to miss: moved/extracted code that dropped a guard
@@ -145,56 +123,34 @@ or anchor; second-tier footguns (dataclass default evaluated once, `hash()`
145
123
  non-determinism, lock-scope shrink, predicate methods with side effects);
146
124
  setup/teardown asymmetry in tests; config defaults flipped.
147
125
 
148
- Surface additional candidates, each naming a defect not already on the list.
149
- If nothing new, return an empty sweep — do not pad.
126
+ Surface **up to 8 additional candidates**, each naming a defect not already on
127
+ the list. If nothing new, return nothing from this phase — do not pad.
150
128
 
151
129
  ## Output
152
130
 
153
- Report this review's results — `{level, findings}` — through the structured
154
- findings-report call: the mechanism that renders a review's results as a typed
155
- list in the host UI, ranked most-severe first. Each entry has `file`, `line`,
156
- `summary`, `short_summary` — the claim compressed to ≤60 characters, no
157
- rationale or consequence clause — `failure_scenario`, and `category` — a short
158
- kebab-case slug for the angle that produced it (`correctness`,
159
- `simplification`, `efficiency`, `reuse`, `altitude`, `conventions`, or a more
160
- specific slug like `test-coverage` when one fits better) — plus `verdict` when
161
- a verify pass produced one. If nothing survives verification, make that call
162
- with an empty array. Do not also print the findings as text, and do not create
163
- or publish an artifact of the review — the structured call is the report.
164
-
165
- ## Applying fixes (--fix)
131
+ Target **at least 7 findings**. If fewer findings exist, emit what you have — do not invent to hit the floor.
166
132
 
167
- The `--fix` flag was passed. Follow `reference\fix.md` (relative
168
- to this skill's folder) for the exact fix, code-rules-gate, and skip-handling
169
- behavior — it governs which agent applies each fix, how the code-rules gate
170
- runs, how a skip is logged, and how outcomes get reported. Do not repeat the
171
- findings as text; follow that document's reporting rules once fixes land.
133
+ Call the ReportFindings tool once to report this review's results
134
+ with `{level, findings}`. `findings` is at most 15 entries ranked
135
+ most-severe first; each entry has `file`, `line`, `summary`,
136
+ `short_summary` — the claim compressed to ≤60 characters, no rationale
137
+ or consequence clause — `failure_scenario`, and `category` — a short kebab-case slug for the angle
138
+ that produced it (`correctness`, `simplification`, `efficiency`,
139
+ `reuse`, `altitude`, `conventions`, or a more specific slug like
140
+ `test-coverage` when one fits better) — plus `verdict` when a verify pass
141
+ produced one. If more than 15 survive, keep the 15 most severe. If
142
+ nothing survives, call it with an empty array. Do not also print
143
+ the findings as text, and do not create or publish an artifact of the review -
144
+ the tool call is the report.
172
145
 
173
- When `loop` is also set, skip this section.
174
146
 
175
147
  ## If findings are fixed later
176
148
 
177
- Whenever a reported finding is fixed later in this session — the user asks you
178
- to fix it, or later work fixes it incidentally — follow `reference\fix.md`'s
179
- reporting rules again: report the same findings through the structured
180
- findings-report call, each carrying an `outcome`. Do not repeat the findings
181
- as text. Make that call immediately after the fixes land, before any prose
182
- summary; the host UI's per-finding status updates only from that call.
183
-
184
- ## Looping (`loop`)
185
-
186
- The `loop` arg was passed. Follow `reference\loop.md` (relative to this
187
- skill's folder) for how to re-run Phases 0–3 and Output repeatedly — including
188
- its exit condition and re-invocation rules. Schedule no fix pass of your own
189
- here: when `--fix` is also present, `reference\loop.md`'s gate sequence owns the
190
- round's fixing and loads `reference\fix.md` for the mechanics. Do not treat a
191
- single pass through this document as complete while `loop` is active; hand
192
- control to that document, and do not stop at Output.
193
-
194
- That hand-off applies when this document is entered directly. When a loop round
195
- is already running and has handed this document its target, the round owns the
196
- loop: end at Output with the findings report and return those findings to
197
- `reference\loop.md`'s gate sequence, rather than handing control to that
198
- document again from here.
199
-
200
- When `loop` was not passed, skip this section.
149
+ Whenever reported findings get fixed later in this session - the user asks you
150
+ to fix them, or later work fixes them incidentally - you MUST call ReportFindings again with the same findings, each
151
+ carrying an `outcome`: `fixed`, `no_change_needed` (the finding was wrong or
152
+ already handled), or `skipped` (valid but not applied). Do not repeat the
153
+ findings as text.
154
+ Make that call immediately after the fixes land, before any prose summary; the
155
+ host UI's per-finding status updates only from it, and without it the findings
156
+ stay marked unresolved.
@@ -5,22 +5,33 @@
5
5
  title "docs: ..." -> pass, nothing to prove
6
6
  title "fix: ..." changing only docs or .github -> pass, nothing to prove
7
7
  title "fix: ..." with no changed Python test -> fail
8
+ title "fix: ..." with no changed Python or Node test -> fail
8
9
  changed test passes on head, fails on base -> pass
9
10
  changed test passes on both -> fail
11
+ any changed test fails on head -> fail
10
12
 
11
- The base run reuses the regression gate's detached worktree: the changed test
13
+ Python tests reuse the regression gate's detached worktree: the changed test
12
14
  files are copied onto the base checkout, which runs them with import isolation.
13
- Python tests only.
15
+ Node tests (``*.test.mjs``, ``*.test.js``, ``*.test.cjs``) run through
16
+ ``node --test`` on the head, then on a detached base checkout that holds a copy
17
+ of the changed test files. The fix is proven when at least one language's
18
+ changed tests fail on the base.
14
19
  """
15
20
 
16
21
  import argparse
22
+ import shutil
23
+ import subprocess
17
24
  import sys
25
+ import tempfile
26
+ from enum import Enum
18
27
  from pathlib import Path
19
28
 
20
29
  from code_rules_gate_parts import staged_test_regression, staged_test_running
21
30
  from code_rules_gate_parts.git_file_sets import paths_from_git_diff
22
31
  from code_rules_gate_parts.wrapper_plumb_check import is_test_path
23
32
  from pr_loop_shared_constants.fix_pr_test_proof_constants import (
33
+ ALL_NODE_TEST_COMMAND,
34
+ ALL_NODE_TEST_SUFFIXES,
24
35
  ALL_PRODUCTION_CODE_SUFFIXES,
25
36
  CI_CONFIG_DIRECTORY_NAME,
26
37
  FAILED_EXIT_CODE,
@@ -29,6 +40,8 @@ from pr_loop_shared_constants.fix_pr_test_proof_constants import (
29
40
  NO_BASE_FAILURE_MESSAGE,
30
41
  NO_CHANGED_TEST_MESSAGE,
31
42
  NO_PRODUCTION_CHANGE_MESSAGE,
43
+ NODE_BASE_WORKTREE_DIRECTORY_NAME,
44
+ NODE_BASE_WORKTREE_TEMP_DIRECTORY_PREFIX,
32
45
  NOT_A_FIX_MESSAGE,
33
46
  PASSED_EXIT_CODE,
34
47
  PROVEN_MESSAGE,
@@ -36,6 +49,15 @@ from pr_loop_shared_constants.fix_pr_test_proof_constants import (
36
49
  )
37
50
 
38
51
 
52
+ class ProofVerdict(Enum):
53
+ """The result of running one language's changed tests on the head and the base."""
54
+
55
+ HEAD_FAILED = "head_failed"
56
+ BASE_UNAVAILABLE = "base_unavailable"
57
+ PASSES_ON_BASE = "passes_on_base"
58
+ PROVEN = "proven"
59
+
60
+
39
61
  def _parse_arguments(all_arguments: list[str]) -> argparse.Namespace:
40
62
  parser = argparse.ArgumentParser(prog="fix_pr_test_proof")
41
63
  parser.add_argument("--title", required=True)
@@ -72,9 +94,17 @@ def _changed_python_tests(all_changed_paths: list[Path]) -> list[Path]:
72
94
  ]
73
95
 
74
96
 
75
- def _proof_exit_code(
97
+ def _changed_node_tests(all_changed_paths: list[Path]) -> list[Path]:
98
+ return [
99
+ each_path
100
+ for each_path in all_changed_paths
101
+ if each_path.name.endswith(ALL_NODE_TEST_SUFFIXES) and each_path.is_file()
102
+ ]
103
+
104
+
105
+ def _python_proof_verdict(
76
106
  all_changed_tests: list[Path], repository_root: Path, base_revision: str
77
- ) -> int:
107
+ ) -> ProofVerdict:
78
108
  all_targets = staged_test_running._pytest_target_paths(all_changed_tests)
79
109
  all_tests_by_root = staged_test_running._group_staged_tests_by_root(
80
110
  all_targets, repository_root
@@ -87,18 +117,78 @@ def _proof_exit_code(
87
117
  for each_group_root, each_outcome in sorted(head_outcomes.items()):
88
118
  if each_outcome.exit_code != 0:
89
119
  sys.stderr.write(HEAD_FAILURE_MESSAGE.format(group_root=each_group_root) + "\n")
90
- return FAILED_EXIT_CODE
120
+ return ProofVerdict.HEAD_FAILED
91
121
  if base_outcomes is None:
92
122
  sys.stderr.write(WORKTREE_FAILED_MESSAGE.format(revision=base_revision) + "\n")
93
- return FAILED_EXIT_CODE
123
+ return ProofVerdict.BASE_UNAVAILABLE
94
124
  base_failure_count = sum(
95
125
  len(each_outcome.failing_identities) for each_outcome in base_outcomes.values()
96
126
  )
97
127
  if base_failure_count == 0:
98
- sys.stderr.write(NO_BASE_FAILURE_MESSAGE.format(revision=base_revision) + "\n")
99
- return FAILED_EXIT_CODE
128
+ return ProofVerdict.PASSES_ON_BASE
100
129
  sys.stdout.write(PROVEN_MESSAGE.format(count=base_failure_count) + "\n")
101
- return PASSED_EXIT_CODE
130
+ return ProofVerdict.PROVEN
131
+
132
+
133
+ def _node_tests_pass(working_directory: Path, all_relative_tests: list[Path]) -> bool:
134
+ node_run = subprocess.run(
135
+ [*ALL_NODE_TEST_COMMAND, *(each_path.as_posix() for each_path in all_relative_tests)],
136
+ cwd=working_directory,
137
+ capture_output=True,
138
+ text=True,
139
+ check=False,
140
+ )
141
+ return node_run.returncode == 0
142
+
143
+
144
+ def _copy_tests_onto(
145
+ base_worktree: Path, repository_root: Path, all_relative_tests: list[Path]
146
+ ) -> None:
147
+ for each_relative_test in all_relative_tests:
148
+ base_test_path = base_worktree / each_relative_test
149
+ base_test_path.parent.mkdir(parents=True, exist_ok=True)
150
+ shutil.copy2(repository_root / each_relative_test, base_test_path)
151
+
152
+
153
+ def _node_proof_verdict(
154
+ all_changed_tests: list[Path], repository_root: Path, base_revision: str
155
+ ) -> ProofVerdict:
156
+ all_relative_tests = [each_path.relative_to(repository_root) for each_path in all_changed_tests]
157
+ if not _node_tests_pass(repository_root, all_relative_tests):
158
+ sys.stderr.write(HEAD_FAILURE_MESSAGE.format(group_root=repository_root) + "\n")
159
+ return ProofVerdict.HEAD_FAILED
160
+ with tempfile.TemporaryDirectory(
161
+ prefix=NODE_BASE_WORKTREE_TEMP_DIRECTORY_PREFIX, ignore_cleanup_errors=True
162
+ ) as base_parent_text:
163
+ base_worktree = Path(base_parent_text) / NODE_BASE_WORKTREE_DIRECTORY_NAME
164
+ if not staged_test_regression._add_baseline_worktree(
165
+ repository_root, base_worktree, base_revision
166
+ ):
167
+ sys.stderr.write(WORKTREE_FAILED_MESSAGE.format(revision=base_revision) + "\n")
168
+ return ProofVerdict.BASE_UNAVAILABLE
169
+ try:
170
+ _copy_tests_onto(base_worktree, repository_root, all_relative_tests)
171
+ passes_on_base = _node_tests_pass(base_worktree, all_relative_tests)
172
+ finally:
173
+ staged_test_regression._remove_baseline_worktree(repository_root, base_worktree)
174
+ if passes_on_base:
175
+ return ProofVerdict.PASSES_ON_BASE
176
+ sys.stdout.write(PROVEN_MESSAGE.format(count=len(all_relative_tests)) + "\n")
177
+ return ProofVerdict.PROVEN
178
+
179
+
180
+ def _proof_exit_code(
181
+ all_verdicts: list[ProofVerdict], base_revision: str
182
+ ) -> int:
183
+ if any(
184
+ each_verdict in (ProofVerdict.HEAD_FAILED, ProofVerdict.BASE_UNAVAILABLE)
185
+ for each_verdict in all_verdicts
186
+ ):
187
+ return FAILED_EXIT_CODE
188
+ if ProofVerdict.PROVEN in all_verdicts:
189
+ return PASSED_EXIT_CODE
190
+ sys.stderr.write(NO_BASE_FAILURE_MESSAGE.format(revision=base_revision) + "\n")
191
+ return FAILED_EXIT_CODE
102
192
 
103
193
 
104
194
  def main(all_arguments: list[str]) -> int:
@@ -121,11 +211,22 @@ def main(all_arguments: list[str]) -> int:
121
211
  ):
122
212
  sys.stdout.write(NO_PRODUCTION_CHANGE_MESSAGE + "\n")
123
213
  return PASSED_EXIT_CODE
124
- all_changed_tests = _changed_python_tests(all_changed_paths)
125
- if not all_changed_tests:
214
+ all_changed_python_tests = _changed_python_tests(all_changed_paths)
215
+ all_changed_node_tests = _changed_node_tests(all_changed_paths)
216
+ if not all_changed_python_tests and not all_changed_node_tests:
126
217
  sys.stderr.write(NO_CHANGED_TEST_MESSAGE + "\n")
127
218
  return FAILED_EXIT_CODE
128
- return _proof_exit_code(all_changed_tests, repository_root, parsed_arguments.base_revision)
219
+ base_revision = parsed_arguments.base_revision
220
+ all_verdicts: list[ProofVerdict] = []
221
+ if all_changed_python_tests:
222
+ all_verdicts.append(
223
+ _python_proof_verdict(all_changed_python_tests, repository_root, base_revision)
224
+ )
225
+ if all_changed_node_tests:
226
+ all_verdicts.append(
227
+ _node_proof_verdict(all_changed_node_tests, repository_root, base_revision)
228
+ )
229
+ return _proof_exit_code(all_verdicts, base_revision)
129
230
 
130
231
 
131
232
  if __name__ == "__main__":
@@ -15,6 +15,10 @@ ALL_PRODUCTION_CODE_SUFFIXES: tuple[str, ...] = (
15
15
  ".ps1",
16
16
  ".sh",
17
17
  )
18
+ ALL_NODE_TEST_SUFFIXES: tuple[str, ...] = (".test.mjs", ".test.js", ".test.cjs")
19
+ ALL_NODE_TEST_COMMAND: tuple[str, ...] = ("node", "--test")
20
+ NODE_BASE_WORKTREE_TEMP_DIRECTORY_PREFIX: str = "fix_pr_test_proof_node_base_"
21
+ NODE_BASE_WORKTREE_DIRECTORY_NAME: str = "tree"
18
22
  CI_CONFIG_DIRECTORY_NAME: str = ".github"
19
23
  PASSED_EXIT_CODE: int = 0
20
24
  FAILED_EXIT_CODE: int = 1
@@ -24,7 +28,7 @@ NO_PRODUCTION_CHANGE_MESSAGE: str = (
24
28
  "fix_pr_test_proof: the fix changes no production code; nothing to prove."
25
29
  )
26
30
  NO_CHANGED_TEST_MESSAGE: str = (
27
- "fix_pr_test_proof: this fix changes production code and no Python test. "
31
+ "fix_pr_test_proof: this fix changes production code and no Python or Node test. "
28
32
  "Add a test that fails on the base and passes on the head."
29
33
  )
30
34
  WORKTREE_FAILED_MESSAGE: str = (