claude-dev-env 2.19.0 → 2.20.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 (48) hide show
  1. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +31 -1
  2. package/.agents/skills/e-code-review/SKILL.md +12 -1
  3. package/.agents/skills/e-code-review/reference/fix.md +5 -1
  4. package/.agents/skills/e-code-review/reference/loop.md +4 -0
  5. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +66 -0
  6. package/.agents/skills/e-code-review/reference/preflight-proposal.md +40 -0
  7. package/.agents/skills/e-code-review/reference/runner-selection.md +1 -0
  8. package/.agents/skills/pr-cleanup/SKILL.md +109 -11
  9. package/_shared/pr-loop/scripts/code_rules_gate.py +29 -6
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +15 -3
  11. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +4 -0
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +47 -0
  13. package/docs/CODE_RULES.md +2 -0
  14. package/hooks/advisory/conftest.py +10 -0
  15. package/hooks/advisory/refactor_guard.py +250 -144
  16. package/hooks/advisory/refactor_guard_test_support.py +46 -0
  17. package/hooks/advisory/test_refactor_guard_advisory.py +171 -0
  18. package/hooks/advisory/test_refactor_guard_eligibility.py +166 -0
  19. package/hooks/blocking/block_main_commit.py +66 -33
  20. package/hooks/blocking/code_rules_blast_radius.py +194 -0
  21. package/hooks/blocking/code_rules_enforcer.py +12 -0
  22. package/hooks/blocking/test_block_main_commit.py +145 -0
  23. package/hooks/blocking/test_code_rules_blast_radius.py +161 -0
  24. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +1 -0
  25. package/hooks/blocking/test_destructive_command_blocker.py +154 -138
  26. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +52 -9
  27. package/hooks/blocking/test_destructive_command_blocker_patterns.py +133 -0
  28. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +71 -5
  29. package/hooks/git-hooks/AGENTS.md +1 -1
  30. package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -0
  31. package/hooks/git-hooks/post_commit.py +160 -51
  32. package/hooks/git-hooks/pre_commit.py +3 -3
  33. package/hooks/git-hooks/test_post_commit.py +203 -0
  34. package/hooks/git-hooks/test_pre_commit.py +2 -2
  35. package/hooks/hooks_constants/blast_radius_constants.py +14 -0
  36. package/hooks/hooks_constants/refactor_guard_constants.py +75 -0
  37. package/hooks/hooks_constants/test_refactor_guard_constants.py +21 -0
  38. package/hooks/observability/test_instructions_loaded_logger.py +54 -0
  39. package/hooks/session/test_plugin_data_dir_cleanup.py +70 -0
  40. package/hooks/session/test_session_edit_tracker_cleanup.py +16 -3
  41. package/hooks/validation/mypy_validator.py +213 -80
  42. package/hooks/validation/test_mypy_validator.py +288 -13
  43. package/hooks/workflow/auto_formatter.py +225 -93
  44. package/hooks/workflow/investigation_tracker_reset.py +2 -0
  45. package/hooks/workflow/test_auto_formatter.py +261 -12
  46. package/hooks/workflow/test_investigation_tracker_reset.py +90 -0
  47. package/package.json +1 -1
  48. package/rules/failure-blast-radius.md +126 -0
@@ -21,6 +21,14 @@ const capabilityTaskSeedsSource = readFileSync(
21
21
  resolve(THIS_DIRECTORY, '../../pr-name-by-capability/reference/task-seeds.md'),
22
22
  'utf8',
23
23
  )
24
+ const reviewSkillSource = readFileSync(
25
+ resolve(THIS_DIRECTORY, '../../e-code-review/SKILL.md'),
26
+ 'utf8',
27
+ )
28
+ const reviewProposalSource = readFileSync(
29
+ resolve(THIS_DIRECTORY, '../../e-code-review/reference/preflight-proposal.md'),
30
+ 'utf8',
31
+ )
24
32
 
25
33
  const allSkillContracts = [
26
34
  {
@@ -77,13 +85,35 @@ function assertSharedProposalContract(contractSource) {
77
85
  assert.match(contractSource, /Reapplication uses exactly the selected records/)
78
86
  }
79
87
 
88
+ function assertReviewProposalAdapter() {
89
+ const preflightRoutingOffset = reviewSkillSource.indexOf('`preflight-proposal`')
90
+ const refusalRoutingOffset = reviewSkillSource.indexOf('**Refusal — first match wins:**')
91
+ const allProposalEntryMatches = reviewSkillSource.match(
92
+ /Route the selected mode through \[reference\/preflight-proposal.md\]\(reference\/preflight-proposal.md\) to establish proposal context/g,
93
+ ) ?? []
94
+
95
+ assert.ok(preflightRoutingOffset >= 0)
96
+ assert.ok(preflightRoutingOffset < refusalRoutingOffset)
97
+ assert.equal(allProposalEntryMatches.length, 1)
98
+ assert.match(reviewSkillSource, /e-code-review preflight-proposal/)
99
+ assert.match(reviewSkillSource, /`--level <low\|medium\|xhigh>`/)
100
+ assert.match(reviewSkillSource, /@~\/.claude\/_shared\/pr-loop\/preflight-proposal.md/)
101
+ assert.match(reviewSkillSource, /Normal mode follows the current level/)
102
+ assert.match(reviewProposalSource, /Keep Gate 1, Gate 2, the bare code-rules gate, and exact required tests/)
103
+ assert.match(reviewProposalSource, /review_level: low \| medium \| xhigh/)
104
+ assert.match(reviewProposalSource, /severity: blocker \| high \| medium \| low \| nit/)
105
+ assert.match(reviewProposalSource, /verdict: CONFIRMED \| PLAUSIBLE/)
106
+ assert.match(reviewProposalSource, /outcome: fixed \| no_change_needed \| skipped/)
107
+ }
108
+
80
109
  test('audit modes retain their normal routing behavior', () => {
81
110
  assertTaskRoutingAndNormalMode()
82
111
  })
83
112
 
84
- test('one shared contract defines proposal evidence for both audit skills', () => {
113
+ test('one shared contract defines proposal evidence for audit and review skills', () => {
85
114
  for (const eachSkillContract of allSkillContracts) {
86
115
  assertProposalAdapter(eachSkillContract)
87
116
  }
117
+ assertReviewProposalAdapter()
88
118
  assertSharedProposalContract(canonicalContractSource)
89
119
  })
@@ -4,13 +4,23 @@ description: >-
4
4
  Max-recall code review at a selectable effort level (low, medium, xhigh), with
5
5
  optional auto-fix and an auto-execute loop for any level. Triggers:
6
6
  /e-code-review, /e-code-review low, /e-code-review medium, /e-code-review
7
- xhigh, /e-code-review <level> --fix, /e-code-review <level> loop.
7
+ xhigh, /e-code-review <level> --fix, /e-code-review <level> loop,
8
+ /e-code-review preflight-proposal.
8
9
  ---
9
10
 
10
11
  # e-code-review
11
12
 
12
13
  **Pick a level, run that review, optionally fix and loop.** Each level has its own procedure file. Fix application lives in `reference/fix.md`; repeat-until-clean lives in `reference/loop.md`.
13
14
 
15
+ ## Mode routing
16
+
17
+ Resolve the first matching invocation before the normal refusal and loop rules:
18
+
19
+ 1. `preflight-proposal` requires `<pr_number>`, `--level <low|medium|xhigh>`, `--base-sha <immutable SHA>`, `--head-sha <immutable SHA>`, and `--worktree <isolated path>`. The caller supplies `low` as the default level; `medium` and `xhigh` are valid selections. Route the selected mode through [reference/preflight-proposal.md](reference/preflight-proposal.md) to establish proposal context. The mode runs `<review_level> --fix loop` locally and returns selected-candidate-ready proposal evidence.
20
+ 2. Normal mode follows the current level, `--fix`, and `loop` behavior.
21
+
22
+ The proposal mode applies the canonical proposal contract at `@~/.claude/_shared/pr-loop/preflight-proposal.md`. The local extension selects the review level and records finding outcomes. The downstream owner records selected proposal IDs before reapplication; each new finding receives a new ID.
23
+
14
24
  ## Gotchas
15
25
 
16
26
  - **`low` stays single-pass.** No subagents, no full-file reads: one read pass per target item, one findings pass.
@@ -61,6 +71,7 @@ Detail: `reference/effort-evaluation.md`.
61
71
  | `reference/xhigh.md` | xhigh review procedure — 10 angles, 1-vote verify, gap sweep |
62
72
  | `reference/fix.md` | Fix application, code-rules gate, skip logging, outcome reporting |
63
73
  | `reference/loop.md` | Repeat review/fix rounds until clean |
74
+ | `reference/preflight-proposal.md` | Isolated local review, immutable SHAs, proposal evidence, and mutation boundary |
64
75
  | `reference/effort-evaluation.md` | Effort evaluation fixtures, evidence, and skill defaults |
65
76
  | `reference/runner-selection.md` | Runner selection map |
66
77
  | `scripts/finding_pipeline.py` | Collect every real finding; filter severity only later |
@@ -2,6 +2,10 @@
2
2
 
3
3
  When `--fix` is passed, apply the reviewed findings to the working tree.
4
4
 
5
+ ## Preflight-proposal mode
6
+
7
+ When the hub selects `preflight-proposal`, consume the established proposal context, selected review level, and current pull-request head.
8
+
5
9
  ## Resume the finding agent
6
10
 
7
11
  For each finding, resume the same Agent-tool agent instance that originally
@@ -73,4 +77,4 @@ the fix, or later work fixes it incidentally — call the structured
73
77
  findings-report call again with the same findings, each carrying an
74
78
  `outcome` (`fixed`, `no_change_needed`, or `skipped`). Do not repeat the
75
79
  findings as text. Make that call immediately after the fixes land, before any
76
- prose summary.
80
+ prose summary.
@@ -1,5 +1,9 @@
1
1
  # Loop until clean
2
2
 
3
+ ## Preflight-proposal mode
4
+
5
+ When the hub selects `preflight-proposal`, continue the review loop with the established proposal context. Proposal mode ends with proposal evidence. Normal mode keeps its GitHub disclosure and Ready actions.
6
+
3
7
  ## Act
4
8
 
5
9
  `loop` on the hub command authorizes the full cycle. After the effort level procedure returns findings, run the gate sequence below immediately.
@@ -0,0 +1,66 @@
1
+ import assert from 'node:assert/strict'
2
+ import { readFileSync } from 'node:fs'
3
+ import test from 'node:test'
4
+
5
+ const proposalSource = readFileSync(new URL('./preflight-proposal.md', import.meta.url), 'utf8')
6
+ const fixSource = readFileSync(new URL('./fix.md', import.meta.url), 'utf8')
7
+ const loopSource = readFileSync(new URL('./loop.md', import.meta.url), 'utf8')
8
+
9
+ function sourceBetweenHeadings(source, fromHeading, toHeading) {
10
+ const fromOffset = source.indexOf(fromHeading)
11
+ const toOffset = source.indexOf(toHeading, fromOffset)
12
+
13
+ assert.ok(fromOffset >= 0)
14
+ assert.ok(toOffset > fromOffset)
15
+
16
+ return source.slice(fromOffset, toOffset)
17
+ }
18
+
19
+ function assertReviewLevelExtension() {
20
+ assert.match(proposalSource, /<review_level> --fix loop/)
21
+ const allReviewLevelMappings = [
22
+ ['Omitted override', 'low'],
23
+ ['`low`', 'low'],
24
+ ['`medium`', 'medium'],
25
+ ['`xhigh`', 'xhigh'],
26
+ ]
27
+
28
+ for (const [eachCallerSelection, eachLevel] of allReviewLevelMappings) {
29
+ const expectedRow = `| ${eachCallerSelection} | \`${eachLevel}\` |`
30
+ assert.match(proposalSource, new RegExp(expectedRow.replaceAll('|', '\\|')))
31
+ }
32
+ assert.match(proposalSource, /`review_level` evidence mirrors the resolved `--level` value/)
33
+ }
34
+
35
+ function assertProposalHandoffs() {
36
+ assert.match(proposalSource, /canonical contract owns the immutable range/)
37
+ const fixModeSource = sourceBetweenHeadings(
38
+ fixSource,
39
+ '## Preflight-proposal mode',
40
+ '## Resume the finding agent',
41
+ )
42
+ assert.match(fixModeSource, /consume the established proposal context/)
43
+ assert.doesNotMatch(fixModeSource, /follow \[preflight-proposal.md\]/)
44
+ const proposalModeSource = sourceBetweenHeadings(
45
+ loopSource,
46
+ '## Preflight-proposal mode',
47
+ '## Act',
48
+ )
49
+ assert.match(proposalModeSource, /continue the review loop with the established proposal context/)
50
+ assert.doesNotMatch(proposalModeSource, /follow \[preflight-proposal.md\]/)
51
+ assert.match(proposalModeSource, /Proposal mode ends with proposal evidence/)
52
+ assert.doesNotMatch(proposalModeSource, /proof-of-work PR comment|gh pr ready/)
53
+ assert.doesNotMatch(proposalSource, /proof-of-work PR comment|gh pr ready/)
54
+ }
55
+
56
+ function assertNormalTerminationContract() {
57
+ const normalTerminalSource = loopSource.slice(loopSource.indexOf('## Terminal outcomes'))
58
+ assert.match(normalTerminalSource, /proof-of-work PR comment/)
59
+ assert.match(normalTerminalSource, /gh pr ready/)
60
+ }
61
+
62
+ test('preflight proposal mode isolates review loops and preserves normal termination behavior', () => {
63
+ assertReviewLevelExtension()
64
+ assertProposalHandoffs()
65
+ assertNormalTerminationContract()
66
+ })
@@ -0,0 +1,40 @@
1
+ # Review preflight proposal mode
2
+
3
+ Apply the canonical proposal contract at:
4
+
5
+ ```text
6
+ @~/.claude/_shared/pr-loop/preflight-proposal.md
7
+ ```
8
+
9
+ Use this review extension when the caller selects `preflight-proposal`:
10
+
11
+ ```text
12
+ /e-code-review preflight-proposal <pr_number> --level low --base-sha <base_sha> --head-sha <head_sha> --worktree <isolated_worktree>
13
+ ```
14
+
15
+ ## Review extension
16
+
17
+ The caller resolves the review level before invocation and always passes `--level`. Use this mapping:
18
+
19
+ | Caller selection | Resolved `--level` |
20
+ |---|---|
21
+ | Omitted override | `low` |
22
+ | `low` | `low` |
23
+ | `medium` | `medium` |
24
+ | `xhigh` | `xhigh` |
25
+
26
+ The `review_level` evidence mirrors the resolved `--level` value.
27
+
28
+ Run the selected level as `<review_level> --fix loop`. The selected level owns its normal finding and fix rules. Require `HEAD` to equal the supplied head SHA before each round.
29
+
30
+ Keep Gate 1, Gate 2, the bare code-rules gate, and exact required tests. Extend the canonical evidence record with:
31
+
32
+ ```yaml
33
+ review_level: low | medium | xhigh
34
+ findings:
35
+ - severity: blocker | high | medium | low | nit
36
+ verdict: CONFIRMED | PLAUSIBLE
37
+ outcome: fixed | no_change_needed | skipped
38
+ ```
39
+
40
+ The canonical contract owns the immutable range, worktree boundary, proposal identity, changed paths, exact tests and outcomes, mutation boundary, and downstream selection.
@@ -35,6 +35,7 @@ Open issue for implementation work should link this file and keep PRs small
35
35
  ## Related always-on docs
36
36
 
37
37
  - Skill hub: `../SKILL.md` (levels, fix, loop — not runner internals).
38
+ - Preflight proposal: `preflight-proposal.md` (immutable range, local runner, proposal evidence).
38
39
  - Medium procedure: `medium.md`.
39
40
  - Shared worker spawn (when applicable):
40
41
  `packages/claude-dev-env/_shared/pr-loop/worker-spawn.md` in the package tree.
@@ -1,24 +1,114 @@
1
1
  ---
2
2
  name: pr-cleanup
3
- description: Refine a pull request, then run the final simplify and code-review loop. Use when the user asks for /pr-cleanup or full PR cleanup.
3
+ description: >-
4
+ Refine pull requests through parallel placement and capability-name audits,
5
+ focused delivery sizing, and a final simplify and code-review loop. Triggers:
6
+ /pr-cleanup, run PR cleanup, full PR cleanup, extraction audit, capability
7
+ naming audit, simplify and review a PR, and split a cleaned PR.
4
8
  ---
5
9
 
6
- # PR Cleanup
10
+ # PR cleanup
7
11
 
8
- Run `pr-refinement`, then run `sr-loop` on its resulting pull request or stack.
12
+ ## Contents
9
13
 
10
- ## Workflow
14
+ - [Principle](#principle)
15
+ - [When this applies](#when-this-applies)
16
+ - [Composition](#composition)
17
+ - [Task seeding](#task-seeding)
18
+ - [Process](#process)
19
+ - [Promotion gates](#promotion-gates)
20
+ - [Finish report](#finish-report)
21
+ - [File index](#file-index)
11
22
 
12
- 1. Resolve the target pull request and use its head worktree.
13
- 2. Run `pr-refinement`. It owns extraction, capability naming, in-place updates, and a required replacement stack.
14
- 3. Run `sr-loop` on every resulting pull request. Apply findings, run scoped tests, commit, and push each validated change.
15
- 4. Keep every pull request draft. Keep merge authority with the user.
23
+ ## Principle
24
+
25
+ One coding agent owns the cleanup outcome. `pr-refinement` coordinates parallel
26
+ preflight audits and produces findings, tested proposals, a combined change map,
27
+ and a delivery decision for the cleanup owner. Parent-to-child promotion uses
28
+ exact commit ancestry and fresh child-head checks.
29
+
30
+ ## When this applies
31
+
32
+ Use this skill for a pull request that needs placement review, capability
33
+ naming, cleanup convergence, and a focused delivery boundary.
34
+
35
+ Required input: a pull request URL, number, or branch. If the target is missing,
36
+ respond exactly: `Give a GitHub PR number, URL, or branch for pr-cleanup.`
37
+
38
+ Use the repository that owns the target pull request. Keep every pull request in
39
+ draft state until its applicable Ready gate is complete. Keep merge authority
40
+ with the user.
41
+
42
+ ## Composition
43
+
44
+ | Skill | Role | Evidence |
45
+ |---|---|---|
46
+ | `pr-refinement` | Run the parallel audits, combine findings, and coordinate implementation shape | Change map and delivery decision |
47
+ | `pr-shared-extraction` | Find reusable behavior that belongs in `shared_utils` | Placement findings and tested proposal |
48
+ | `pr-name-by-capability` | Find driver or motive words on reusable capability surfaces | Naming findings and rename directions |
49
+ | `pr-small-cl` | Choose one coherent pull request or an ordered replacement stack | Focused boundary and dependencies |
50
+ | `source-command-sr-loop` | Run `e-simplify`, then `e-code-review low --fix` until clean | Review passes, fixes, and validation |
51
+
52
+ ## Task seeding
53
+
54
+ At skill start, register every item in `reference/task-seeds.md` as a session
55
+ task through `TaskCreate`, `TodoWrite`, or the host task equivalent. Work from
56
+ that task list. Mark each task complete with `PASS`, `FAIL` plus file and line
57
+ evidence, or `N/A` plus the reason.
58
+
59
+ ## Process
60
+
61
+ ### 1. Resolve the target
62
+
63
+ Resolve the pull request, repository, parent head SHA, and intended child
64
+ boundary. Record the immutable parent preflight SHA before creating worktrees.
65
+
66
+ ### 2. Run `pr-refinement`
67
+
68
+ Run [pr-refinement](../pr-refinement/SKILL.md). Record its combined change map,
69
+ audit findings, locations, priorities, destinations or rename directions,
70
+ validation evidence, and worker worktrees.
71
+
72
+ ### 3. Choose the delivery shape
73
+
74
+ Use [pr-small-cl](../pr-small-cl/SKILL.md) after the audit findings are
75
+ combined. Record the first pull request boundary, dependencies, tests, and
76
+ follow-up work.
77
+
78
+ ### 4. Implement the selected shape
79
+
80
+ Apply every actionable finding in dependency order. Keep preflight parent scope
81
+ read-only until the cleanup owner selects and reapplies tested proposals.
82
+ Validate each changed surface with its production-path tests. Commit each
83
+ validated concern and keep the resulting pull request in draft state.
84
+
85
+ ### 5. Run `source-command-sr-loop`
86
+
87
+ Run [source-command-sr-loop](../source-command-sr-loop/SKILL.md). Record the
88
+ review passes, fixes, skips, tests, and commit SHAs.
89
+
90
+ ### 6. Promote and report
91
+
92
+ After the applicable gate passes, complete the [Finish report](#finish-report).
16
93
 
17
94
  ## Promotion gates
18
95
 
19
- Run preflight work in isolated worktrees from the recorded parent SHA. Apply selected changes in the parent worktree.
96
+ Run preflight work in isolated worktrees from the recorded parent SHA. Apply
97
+ selected changes in the parent cleanup worktree after the owner selects the
98
+ tested proposals.
99
+
100
+ Promote the parent only after every actionable finding has an applied fix or an
101
+ exact disposition. Record the remote parent Ready state and exact
102
+ `parent_ready_sha`.
20
103
 
21
- Before promoting a child, merge the exact parent-ready SHA. Prove that SHA is an ancestor of the child head. Rerun the child tests, `e-simplify`, and `e-code-review` after the merge.
104
+ Create the child from its intended pre-parent base and merge the exact
105
+ `parent_ready_sha`. Prove that SHA is an ancestor of the child head with
106
+ `git merge-base --is-ancestor <parent_ready_sha> <child_head>` and record exit
107
+ code `0`.
108
+
109
+ Reapply every relevant fix to the child. Rerun child tests, `e-simplify`, and
110
+ `e-code-review` after the merge. Record the new child head and every validation
111
+ result before promoting the child to Ready.
22
112
 
23
113
  Use `reference/task-seeds.md` and `reference/process-inventory.md` to record promotion evidence.
24
114
 
@@ -26,6 +116,14 @@ Use `reference/task-seeds.md` and `reference/process-inventory.md` to record pro
26
116
 
27
117
  - Pull request or stack URLs.
28
118
  - `pr-refinement` outcome.
29
- - `sr-loop` passes, commits, and validation results.
119
+ - `source-command-sr-loop` passes, commits, and validation results.
30
120
  - Parent-ready and child-ready SHAs when a child is promoted.
31
121
  - Remaining hard block, or `null`.
122
+
123
+ ## File index
124
+
125
+ | Path | Purpose |
126
+ |---|---|
127
+ | `SKILL.md` | Hub for refinement, cleanup convergence, promotion gates, and reporting |
128
+ | `reference/task-seeds.md` | Ordered session tasks for audits, delivery, validation, and promotion |
129
+ | `reference/process-inventory.md` | Process classes, evidence homes, and paired task checks |
@@ -4,6 +4,7 @@
4
4
 
5
5
  default mode: git diff since merge-base, joined with untracked files
6
6
  --staged: validate the staged index; --paths: validate explicit files
7
+ --immediate: validate staged rules and terminology at commit time
7
8
  every mode ends by naming how many files it inspected
8
9
 
9
10
  This entry module wires the ``code_rules_gate_parts`` submodules into one CLI
@@ -43,6 +44,7 @@ try:
43
44
  ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS,
44
45
  EMPTY_FILE_SET_EXIT_CODE,
45
46
  EMPTY_FILE_SET_MESSAGE,
47
+ IMMEDIATE_SCOPE_ARGUMENT,
46
48
  INSPECTED_COUNT_MESSAGE,
47
49
  MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS,
48
50
  MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
@@ -263,31 +265,50 @@ def _run_explicit_paths_mode(
263
265
  )
264
266
 
265
267
 
266
- def _run_staged_mode(
268
+ def _run_staged_validation(
267
269
  validate_content: enforcer_loading.ValidateContentCallable,
268
270
  arguments: argparse.Namespace,
269
271
  repository_root: Path,
270
272
  ) -> int:
271
- """Validate the staged changes, run staged tests, and sweep terminology."""
272
- _report_terminology_findings(staged_terminology_findings(repository_root))
273
- staged_test_exit_code = _staged_pytest_exit_code_for_current_python(repository_root)
273
+ """Validate staged file content and report scoped rule findings."""
274
274
  staged_file_paths = filter_paths_under_prefixes(
275
275
  paths_from_git_staged(repository_root), repository_root, arguments.only_under
276
276
  )
277
277
  if not staged_file_paths:
278
278
  sys.stderr.write(INSPECTED_COUNT_MESSAGE.format(inspected_count=0) + "\n")
279
- return staged_test_exit_code
279
+ return 0
280
280
  staged_added_lines = added_lines_by_file_staged(repository_root, staged_file_paths)
281
- gate_exit_code = run_gate(
281
+ return run_gate(
282
282
  validate_content,
283
283
  staged_file_paths,
284
284
  repository_root,
285
285
  all_added_lines_by_path=staged_added_lines,
286
286
  should_read_staged_content=True,
287
287
  )
288
+
289
+
290
+ def _run_staged_mode(
291
+ validate_content: enforcer_loading.ValidateContentCallable,
292
+ arguments: argparse.Namespace,
293
+ repository_root: Path,
294
+ ) -> int:
295
+ """Validate staged rules, terminology, and staged-test behavior."""
296
+ _report_terminology_findings(staged_terminology_findings(repository_root))
297
+ staged_test_exit_code = _staged_pytest_exit_code_for_current_python(repository_root)
298
+ gate_exit_code = _run_staged_validation(validate_content, arguments, repository_root)
288
299
  return gate_exit_code or staged_test_exit_code
289
300
 
290
301
 
302
+ def _run_immediate_mode(
303
+ validate_content: enforcer_loading.ValidateContentCallable,
304
+ arguments: argparse.Namespace,
305
+ repository_root: Path,
306
+ ) -> int:
307
+ """Validate staged rules and terminology at the native commit boundary."""
308
+ _report_terminology_findings(staged_terminology_findings(repository_root))
309
+ return _run_staged_validation(validate_content, arguments, repository_root)
310
+
311
+
291
312
  def _run_diff_mode(
292
313
  validate_content: enforcer_loading.ValidateContentCallable,
293
314
  arguments: argparse.Namespace,
@@ -339,6 +360,8 @@ def main(all_arguments: list[str]) -> int:
339
360
  validate_content = load_validate_content()
340
361
  if arguments.paths:
341
362
  return _run_explicit_paths_mode(validate_content, arguments, repository_root)
363
+ if arguments.immediate:
364
+ return _run_immediate_mode(validate_content, arguments, repository_root)
342
365
  if arguments.staged:
343
366
  return _run_staged_mode(validate_content, arguments, repository_root)
344
367
  return _run_diff_mode(validate_content, arguments, repository_root)
@@ -3,9 +3,14 @@
3
3
  import argparse
4
4
  from pathlib import Path
5
5
 
6
+ from pr_loop_shared_constants.code_rules_gate_constants import (
7
+ IMMEDIATE_SCOPE_ARGUMENT,
8
+ IMMEDIATE_SCOPE_HELP,
9
+ )
10
+
6
11
 
7
12
  def _add_source_arguments(parser: argparse.ArgumentParser) -> None:
8
- """Add the repo-root, base-ref, and staged-mode arguments to *parser*."""
13
+ """Add the repo-root, base-ref, and staged-scope arguments to *parser*."""
9
14
  parser.add_argument(
10
15
  "--repo-root",
11
16
  type=Path,
@@ -17,12 +22,19 @@ def _add_source_arguments(parser: argparse.ArgumentParser) -> None:
17
22
  default="origin/main",
18
23
  help="Merge-base ref for git diff (default: origin/main).",
19
24
  )
20
- parser.add_argument(
25
+ staged_scope_group = parser.add_mutually_exclusive_group()
26
+ staged_scope_group.add_argument(
21
27
  "--staged",
22
28
  action="store_true",
23
29
  default=False,
24
30
  help="Scope to staged changes only (git diff --cached).",
25
31
  )
32
+ staged_scope_group.add_argument(
33
+ IMMEDIATE_SCOPE_ARGUMENT,
34
+ action="store_true",
35
+ default=False,
36
+ help=IMMEDIATE_SCOPE_HELP,
37
+ )
26
38
 
27
39
 
28
40
  def _add_filter_arguments(parser: argparse.ArgumentParser) -> None:
@@ -65,6 +77,6 @@ def parse_arguments(all_arguments: list[str]) -> argparse.Namespace:
65
77
 
66
78
  Returns:
67
79
  The parsed namespace with ``repo_root``, ``base``, ``staged``,
68
- ``only_under``, and ``paths`` attributes.
80
+ ``immediate``, ``only_under``, and ``paths`` attributes.
69
81
  """
70
82
  return _build_argument_parser().parse_args(all_arguments)
@@ -97,6 +97,10 @@ CODE_RULES_GATE_PYTHON_ENV_VAR: str = "CODE_RULES_GATE_PYTHON"
97
97
 
98
98
  CODE_RULES_GATE_PYTHONPATH_ENV_VAR: str = "CODE_RULES_GATE_PYTHONPATH"
99
99
 
100
+ IMMEDIATE_SCOPE_ARGUMENT: str = "--immediate"
101
+
102
+ IMMEDIATE_SCOPE_HELP: str = "Validate staged rules without running staged tests."
103
+
100
104
  PYTHONPATH_ENV_VAR: str = "PYTHONPATH"
101
105
 
102
106
  ALL_VENV_DIRECTORY_NAMES: tuple[str, ...] = (".venv", "venv")
@@ -300,6 +300,45 @@ def test_main_staged_mode_blocks_when_staged_lines_introduce_violations(
300
300
  assert exit_code == 1
301
301
 
302
302
 
303
+ def test_main_immediate_mode_blocks_staged_rule_violations(
304
+ temporary_git_repository: Path,
305
+ monkeypatch: pytest.MonkeyPatch,
306
+ ) -> None:
307
+ write_file(temporary_git_repository / "module.py", "first_count = 1\n")
308
+ commit_all_files(temporary_git_repository, "initial")
309
+ write_file(
310
+ temporary_git_repository / "module.py",
311
+ "first_count = 1\n"
312
+ "def compute_total(operand):\n"
313
+ " result = operand + 1\n"
314
+ " return result\n",
315
+ )
316
+ stage_file(temporary_git_repository, "module.py")
317
+
318
+ monkeypatch.chdir(temporary_git_repository)
319
+ exit_code = gate_module.main(["--immediate"])
320
+
321
+ assert exit_code == 1
322
+
323
+
324
+ def test_main_immediate_mode_skips_staged_test_execution(
325
+ temporary_git_repository: Path,
326
+ monkeypatch: pytest.MonkeyPatch,
327
+ ) -> None:
328
+ write_file(temporary_git_repository / "module.py", "first_count = 1\n")
329
+ commit_all_files(temporary_git_repository, "initial")
330
+ write_file(
331
+ temporary_git_repository / "test_staged_failure.py",
332
+ "def test_intentionally_fails() -> None:\n assert False\n",
333
+ )
334
+ stage_file(temporary_git_repository, "test_staged_failure.py")
335
+
336
+ monkeypatch.chdir(temporary_git_repository)
337
+ exit_code = gate_module.main(["--immediate"])
338
+
339
+ assert exit_code == 0
340
+
341
+
303
342
  def test_main_staged_mode_passes_when_no_staged_violations(
304
343
  temporary_git_repository: Path,
305
344
  monkeypatch: pytest.MonkeyPatch,
@@ -2336,10 +2375,18 @@ def test_parse_arguments_reads_staged_base_and_prefix_flags() -> None:
2336
2375
  assert parsed_arguments.paths == [Path("explicit_file.py")]
2337
2376
 
2338
2377
 
2378
+ def test_parse_arguments_reads_immediate_scope_flag() -> None:
2379
+ parsed_arguments = gate_module.parse_arguments(["--immediate"])
2380
+
2381
+ assert parsed_arguments.immediate is True
2382
+ assert parsed_arguments.staged is False
2383
+
2384
+
2339
2385
  def test_parse_arguments_applies_documented_defaults() -> None:
2340
2386
  parsed_arguments = gate_module.parse_arguments([])
2341
2387
 
2342
2388
  assert parsed_arguments.staged is False
2389
+ assert parsed_arguments.immediate is False
2343
2390
  assert parsed_arguments.base == "origin/main"
2344
2391
  assert parsed_arguments.repo_root is None
2345
2392
  assert parsed_arguments.only_under == []
@@ -94,6 +94,8 @@ Removed code is removed: no renamed re-export aliases, no `_old_*` aliases, no k
94
94
 
95
95
  Never swallow a failure into a default unless the caller explicitly opted in at the boundary. Name the specific exception (`except KeyError:`) and propagate the rest — collapsing every error class to `None` masks programming errors and makes debugging impossible.
96
96
 
97
+ **A per-member boundary records each member outcome.** In a batch loop, a `try`/`except` inside the loop body catches a declared `*ItemBlocked` type, records the failure with its reason, and continues to the next member — the failure reaches the run report by name. The boundary preserves the blast radius: escalations re-raise first so a `*RunFatal` passes through directly, while `except Exception` triggers the rule. Types, boundary shape, and the parked-member report: [`rules/failure-blast-radius.md`](../rules/failure-blast-radius.md).
98
+
97
99
  ## 9.8 REMOVE CODE YOU ORPHAN (Dead Code Elimination)
98
100
 
99
101
  An edit that deletes or rewrites code also removes everything it makes dead: unread variables, uncalled functions, unpassed parameters, dead branches, unused imports, helper files whose only consumer that edit deleted. Prove unreachability first: Serena `find_referencing_symbols` plus a text search for dynamic lookups (`getattr`, entry-point names). A symbol is live only when a reference chain reaches a live entry point (CLI command, route, public API, test); a self-referential dead cluster is removed together in the same commit. **When liveness is uncertain (public API, plugin hook, reflective dispatch), do NOT delete — surface the ambiguity via AskUserQuestion.** Source links: [`references/dead-code-elimination.md`](references/dead-code-elimination.md).
@@ -0,0 +1,10 @@
1
+ """Pytest registration for shared refactor guard test support."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ advisory_directory = str(Path(__file__).resolve().parent)
7
+ if advisory_directory not in sys.path:
8
+ sys.path.insert(0, advisory_directory)
9
+
10
+ from refactor_guard_test_support import git_repository # noqa: E402, F401