claude-dev-env 8.72.2 → 8.73.1

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 (50) hide show
  1. package/.agents/skills/build-eval/SKILL.md +4 -8
  2. package/.agents/skills/build-eval/reference/context-budget.md +49 -0
  3. package/.agents/skills/orchestrator/reference/oversee-delegated-work.md +1 -0
  4. package/.agents/skills/pr-lifecycle/SKILL.md +3 -10
  5. package/bin/codex-trimmed-sol.mjs +109 -0
  6. package/bin/codex-trimmed-sol.test.mjs +226 -0
  7. package/bin/install.mjs +18 -0
  8. package/bin/install.question-presentation.test.mjs +1 -1
  9. package/docs/codex-accounts.md +26 -0
  10. package/hooks/blocking/context_budget_blocker.py +177 -0
  11. package/hooks/blocking/test_context_budget_blocker.py +189 -0
  12. package/hooks/blocking/test_pre_tool_use_dispatcher.py +3 -1
  13. package/hooks/context_budget/__init__.py +1 -0
  14. package/hooks/context_budget/baseline.py +78 -0
  15. package/hooks/context_budget/findings.py +251 -0
  16. package/hooks/context_budget/hook_measure.py +178 -0
  17. package/hooks/context_budget/model.py +134 -0
  18. package/hooks/context_budget/policy_changes.py +214 -0
  19. package/hooks/context_budget/policy_file.py +222 -0
  20. package/hooks/context_budget/sections.py +213 -0
  21. package/hooks/context_budget/tests/budget_support.py +71 -0
  22. package/hooks/context_budget/tests/test_baseline.py +43 -0
  23. package/hooks/context_budget/tests/test_findings.py +120 -0
  24. package/hooks/context_budget/tests/test_hook_measure.py +57 -0
  25. package/hooks/context_budget/tests/test_policy_changes.py +175 -0
  26. package/hooks/context_budget/tests/test_policy_file.py +62 -0
  27. package/hooks/context_budget/tests/test_sections.py +112 -0
  28. package/hooks/features/write-edit-blocking.md +5 -1
  29. package/hooks/hooks_constants/context_budget_constants.py +154 -0
  30. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -1
  31. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -2
  32. package/hooks/test_context_budget_hooks.py +79 -0
  33. package/hooks/test_files/context_budget/advisor_config/settings.json +3 -0
  34. package/hooks/test_files/context_budget/empty_transcript.jsonl +0 -0
  35. package/hooks/test_files/context_budget/orchestrator_skill_3f2c3ef.txt +170 -0
  36. package/hooks/test_files/context_budget/orchestrator_skill_98f45b1.txt +158 -0
  37. package/package.json +1 -1
  38. package/scripts/_code_review_test_support.py +7 -12
  39. package/scripts/context_budget_check.py +243 -0
  40. package/scripts/dev_env_scripts_constants/context_budget_check_constants.py +26 -0
  41. package/scripts/policy_lint/adapter_context_budget.py +158 -0
  42. package/scripts/policy_lint/adapters.py +73 -0
  43. package/scripts/policy_lint/config/constants.py +17 -1
  44. package/scripts/policy_lint/registry.py +17 -0
  45. package/scripts/test__code_review_test_support.py +40 -0
  46. package/scripts/test_codec_forwarding_test_support.py +28 -0
  47. package/scripts/tests/test_context_budget_check.py +270 -0
  48. package/scripts/tests/test_policy_lint_rules_context_budget.py +218 -0
  49. package/scripts/tests/test_policy_lint_rules_pairing.py +26 -0
  50. package/system-prompts/codex-sol.md +170 -0
@@ -0,0 +1,270 @@
1
+ """The context budget command checks explicit files against HEAD and keeps the list generated."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import context_budget_check
12
+ import pytest
13
+
14
+ REPOSITORY_ROOT = Path(__file__).resolve().parents[4]
15
+ SKILL_RELATIVE_PATH = "skills/example/SKILL.md"
16
+
17
+
18
+ def _git(repository_root: Path, *all_arguments: str) -> None:
19
+ subprocess.run(
20
+ [
21
+ "git",
22
+ "-c",
23
+ "user.name=Test",
24
+ "-c",
25
+ "user.email=test@example.com",
26
+ *all_arguments,
27
+ ],
28
+ cwd=repository_root,
29
+ check=True,
30
+ capture_output=True,
31
+ )
32
+
33
+
34
+ def _section(heading: str, detail_count: int) -> str:
35
+ return f"## {heading}\n" + "".join(
36
+ f"Detail line {each}.\n" for each in range(detail_count)
37
+ )
38
+
39
+
40
+ @pytest.fixture
41
+ def repository_root(tmp_path: Path) -> Path:
42
+ policy = {
43
+ "section_detail_line_limit": 6,
44
+ "kinds": [
45
+ {
46
+ "name": "skill entry",
47
+ "patterns": ["**/SKILL.md"],
48
+ "line_limit": 10,
49
+ "section_rule": True,
50
+ }
51
+ ],
52
+ "hooks": [],
53
+ "baseline": {
54
+ "files": {SKILL_RELATIVE_PATH: {"lines": 20, "sections": ["Plan"]}},
55
+ "hooks": {},
56
+ },
57
+ }
58
+ (tmp_path / ".claude").mkdir()
59
+ (tmp_path / ".claude" / "context-budget.json").write_text(
60
+ json.dumps(policy), encoding="utf-8"
61
+ )
62
+ skill_path = tmp_path / SKILL_RELATIVE_PATH
63
+ skill_path.parent.mkdir(parents=True)
64
+ skill_path.write_text(_section("Plan", 7) + "\n" * 12, encoding="utf-8")
65
+ _git(tmp_path, "init", "-q")
66
+ _git(tmp_path, "add", ".")
67
+ _git(tmp_path, "commit", "-q", "-m", "seed")
68
+ return tmp_path
69
+
70
+
71
+ def _run(repository_root: Path, *all_arguments: str) -> tuple[int, str]:
72
+ stdout = io.StringIO()
73
+ exit_code = context_budget_check.main(list(all_arguments), repository_root, stdout)
74
+ return exit_code, stdout.getvalue()
75
+
76
+
77
+ def test_an_unchanged_listed_file_is_ok(repository_root: Path) -> None:
78
+ exit_code, report_text = _run(
79
+ repository_root, str(repository_root / SKILL_RELATIVE_PATH)
80
+ )
81
+
82
+ assert exit_code == 0
83
+ assert "context-budget: OK (1 checked)" in report_text
84
+
85
+
86
+ def test_head_is_the_prior_state_so_a_shrunk_listed_file_must_lower_its_entry(
87
+ repository_root: Path,
88
+ ) -> None:
89
+ (repository_root / SKILL_RELATIVE_PATH).write_text(
90
+ _section("Plan", 7) + "\n" * 4, encoding="utf-8"
91
+ )
92
+
93
+ exit_code, report_text = _run(
94
+ repository_root, str(repository_root / SKILL_RELATIVE_PATH)
95
+ )
96
+
97
+ assert exit_code == 1
98
+ assert '{"lines": 12, "sections": ["Plan"]}' in report_text
99
+
100
+
101
+ def test_a_new_unpointed_section_is_reported(repository_root: Path) -> None:
102
+ skill_path = repository_root / SKILL_RELATIVE_PATH
103
+ skill_path.write_text(_section("Plan", 7) + _section("Steps", 7), encoding="utf-8")
104
+
105
+ exit_code, report_text = _run(repository_root, str(skill_path))
106
+
107
+ assert exit_code == 1
108
+ assert 'section "Steps" has 7 detail lines' in report_text
109
+
110
+
111
+ def test_write_baseline_records_the_tree(repository_root: Path) -> None:
112
+ (repository_root / SKILL_RELATIVE_PATH).write_text(
113
+ _section("Plan", 7) + "\n" * 4, encoding="utf-8"
114
+ )
115
+
116
+ exit_code, report_text = _run(repository_root, "--write-baseline")
117
+
118
+ written = json.loads(
119
+ (repository_root / ".claude" / "context-budget.json").read_text(
120
+ encoding="utf-8"
121
+ )
122
+ )
123
+ assert exit_code == 0 and "1 files" in report_text
124
+ assert written["baseline"]["files"] == {
125
+ SKILL_RELATIVE_PATH: {"lines": 12, "sections": ["Plan"]}
126
+ }
127
+
128
+
129
+ def test_the_committed_baseline_matches_what_the_generator_builds_today() -> None:
130
+ policy = context_budget_check.read_policy_file(REPOSITORY_ROOT)
131
+ assert policy is not None
132
+ committed = json.loads(
133
+ (REPOSITORY_ROOT / ".claude" / "context-budget.json").read_text(
134
+ encoding="utf-8"
135
+ )
136
+ )
137
+
138
+ regenerated = context_budget_check.current_baseline(policy, REPOSITORY_ROOT)
139
+
140
+ assert regenerated == committed["baseline"]
141
+
142
+
143
+ def _hook_policy(repository_root: Path, all_hook_specs: list[tuple[str, str]]) -> Path:
144
+ all_hooks = []
145
+ for index, (name, source_text) in enumerate(all_hook_specs):
146
+ hook_path = repository_root / f"fixture_hook_{index}.py"
147
+ hook_path.write_text(source_text, encoding="utf-8")
148
+ all_hooks.append(
149
+ {
150
+ "name": name,
151
+ "command": [sys.executable, str(hook_path)],
152
+ "stdin": {},
153
+ "char_limit": 1500,
154
+ }
155
+ )
156
+ policy_path = repository_root / ".claude" / "context-budget.json"
157
+ policy_path.write_text(
158
+ json.dumps(
159
+ {
160
+ "kinds": [],
161
+ "hooks": all_hooks,
162
+ "baseline": {"files": {}, "hooks": {"sample": 2200}},
163
+ }
164
+ ),
165
+ encoding="utf-8",
166
+ )
167
+ return policy_path
168
+
169
+
170
+ def _hook_cli(repository_root: Path, action: str) -> subprocess.CompletedProcess[str]:
171
+ return subprocess.run(
172
+ [sys.executable, context_budget_check.__file__, action],
173
+ cwd=repository_root,
174
+ capture_output=True,
175
+ text=True,
176
+ check=False,
177
+ )
178
+
179
+
180
+ @pytest.mark.parametrize("action", ["--hooks", "--write-baseline"])
181
+ def test_duplicate_hook_names_fail_without_replacing_the_policy(
182
+ repository_root: Path,
183
+ action: str,
184
+ ) -> None:
185
+ policy_path = _hook_policy(
186
+ repository_root,
187
+ [
188
+ ("sample", "print('x' * 2100)\n"),
189
+ ("sample", "print('x')\n"),
190
+ ],
191
+ )
192
+ prior_text = policy_path.read_bytes()
193
+
194
+ completed = _hook_cli(repository_root, action)
195
+
196
+ assert completed.returncode == 1
197
+ assert "hook names must be unique" in completed.stdout
198
+ assert policy_path.read_bytes() == prior_text
199
+
200
+
201
+ @pytest.mark.parametrize("action", ["--hooks", "--write-baseline"])
202
+ @pytest.mark.parametrize(
203
+ "source_text",
204
+ [
205
+ "raise RuntimeError('fixture failure')\n",
206
+ "print('partial output')\nraise RuntimeError('fixture failure')\n",
207
+ ],
208
+ )
209
+ def test_failed_hook_measurement_fails_without_replacing_the_policy(
210
+ repository_root: Path,
211
+ action: str,
212
+ source_text: str,
213
+ ) -> None:
214
+ policy_path = _hook_policy(repository_root, [("sample", source_text)])
215
+ prior_text = policy_path.read_bytes()
216
+
217
+ completed = _hook_cli(repository_root, action)
218
+
219
+ assert completed.returncode == 1
220
+ assert 'hook "sample" exited with status 1' in completed.stdout
221
+ assert "fixture failure" in completed.stdout
222
+ assert policy_path.read_bytes() == prior_text
223
+
224
+
225
+ def test_unique_hook_measurements_keep_each_count(repository_root: Path) -> None:
226
+ _hook_policy(
227
+ repository_root, [("sample", "print('x' * 2100)\n"), ("short", "print('x')\n")]
228
+ )
229
+
230
+ completed = _hook_cli(repository_root, "--hooks")
231
+
232
+ assert completed.returncode == 0
233
+ assert "sample: 2101 characters" in completed.stdout
234
+ assert "short: 2 characters" in completed.stdout
235
+
236
+
237
+ def test_successful_empty_hook_can_remove_its_baseline(repository_root: Path) -> None:
238
+ policy_path = _hook_policy(repository_root, [("sample", "")])
239
+
240
+ completed = _hook_cli(repository_root, "--write-baseline")
241
+
242
+ assert completed.returncode == 0
243
+ assert (
244
+ json.loads(policy_path.read_text(encoding="utf-8"))["baseline"]["hooks"] == {}
245
+ )
246
+
247
+
248
+ def test_successful_hook_baseline_preserves_its_count(repository_root: Path) -> None:
249
+ policy_path = _hook_policy(repository_root, [("sample", "print('x' * 2100)\n")])
250
+
251
+ completed = _hook_cli(repository_root, "--write-baseline")
252
+
253
+ assert completed.returncode == 0
254
+ assert json.loads(policy_path.read_text(encoding="utf-8"))["baseline"]["hooks"] == {
255
+ "sample": 2101
256
+ }
257
+
258
+
259
+ def test_a_unique_unlisted_long_hook_still_exceeds_its_limit(
260
+ repository_root: Path,
261
+ ) -> None:
262
+ policy_path = _hook_policy(repository_root, [("sample", "print('x' * 2100)\n")])
263
+ raw_policy = json.loads(policy_path.read_text(encoding="utf-8"))
264
+ raw_policy["baseline"]["hooks"] = {}
265
+ policy_path.write_text(json.dumps(raw_policy), encoding="utf-8")
266
+
267
+ completed = _hook_cli(repository_root, "--hooks")
268
+
269
+ assert completed.returncode == 1
270
+ assert "injects 2101 characters (limit 1500)" in completed.stdout
@@ -0,0 +1,218 @@
1
+ """The context-budget rules reach context Markdown and the policy file through the registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ from pathlib import Path, PurePosixPath
9
+
10
+ import pytest
11
+
12
+ from policy_lint import adapters, registry
13
+ from policy_lint.engine import lint
14
+ from policy_lint.model import ContentOrigin, Document, DocumentRule, LintRequest
15
+
16
+ _SKILL_PATH = "tools/example/SKILL.md"
17
+ _POLICY_PATH = ".claude/context-budget.json"
18
+
19
+
20
+ def _policy(baseline_files: dict | None = None, line_limit: int = 200) -> dict:
21
+ return {
22
+ "section_detail_line_limit": 6,
23
+ "kinds": [
24
+ {
25
+ "name": "skill entry",
26
+ "patterns": ["**/SKILL.md"],
27
+ "line_limit": line_limit,
28
+ "section_rule": True,
29
+ }
30
+ ],
31
+ "hooks": [],
32
+ "baseline": {"files": baseline_files or {}, "hooks": {}},
33
+ }
34
+
35
+
36
+ def _write_policy(repository_root: Path, raw_policy: dict) -> None:
37
+ (repository_root / ".claude").mkdir(exist_ok=True)
38
+ (repository_root / _POLICY_PATH).write_text(
39
+ json.dumps(raw_policy), encoding="utf-8"
40
+ )
41
+
42
+
43
+ def _document(path: str, text: str, prior_text: str | None = None) -> Document:
44
+ return Document(PurePosixPath(path), text, prior_text, None, ContentOrigin.WORKTREE)
45
+
46
+
47
+ def _section(heading: str, detail_count: int) -> str:
48
+ all_lines = [
49
+ f"## {heading}",
50
+ *(f"Detail line {each}." for each in range(detail_count)),
51
+ ]
52
+ return "\n".join(all_lines) + "\n"
53
+
54
+
55
+ def _registered_rule(rule_id: str) -> DocumentRule:
56
+ for each_rule in registry.default_registry():
57
+ if each_rule.rule_id == rule_id and isinstance(each_rule, DocumentRule):
58
+ return each_rule
59
+ raise AssertionError(rule_id)
60
+
61
+
62
+ def test_both_rules_are_registered_for_changed_and_repository_selections() -> None:
63
+ for each_rule_id in ("context-budget", "context-budget-policy"):
64
+ assert _registered_rule(each_rule_id).rule_sets == frozenset(
65
+ {"changed", "repository"}
66
+ )
67
+ assert _registered_rule("context-budget").accepts(_document(_SKILL_PATH, ""))
68
+ assert _registered_rule("context-budget-policy").accepts(
69
+ _document(_POLICY_PATH, "{}")
70
+ )
71
+ assert not _registered_rule("context-budget-policy").accepts(
72
+ _document("a/b.json", "{}")
73
+ )
74
+
75
+
76
+ def test_an_unpointed_long_section_reports_with_its_fix(tmp_path: Path) -> None:
77
+ _write_policy(tmp_path, _policy())
78
+
79
+ all_diagnostics = adapters.context_budget_diagnostics(
80
+ _document(_SKILL_PATH, _section("Plan", 7)), tmp_path
81
+ )
82
+
83
+ assert [each.rule_id for each in all_diagnostics] == ["context-budget"]
84
+ assert (
85
+ 'section "Plan" has 7 detail lines and no pointer (limit 6)'
86
+ in all_diagnostics[0].message
87
+ )
88
+ assert "reference/plan.md" in all_diagnostics[0].message
89
+
90
+
91
+ def test_a_repository_without_a_policy_file_is_not_checked(tmp_path: Path) -> None:
92
+ assert (
93
+ adapters.context_budget_diagnostics(
94
+ _document(_SKILL_PATH, _section("Plan", 9)), tmp_path
95
+ )
96
+ == ()
97
+ )
98
+
99
+
100
+ def test_a_changed_listed_file_below_its_record_must_lower_its_entry(
101
+ tmp_path: Path,
102
+ ) -> None:
103
+ prior_text = _section("Plan", 7) + "\n" * 10
104
+ _write_policy(
105
+ tmp_path, _policy({_SKILL_PATH: {"lines": 18, "sections": ["Plan"]}}, 10)
106
+ )
107
+
108
+ all_diagnostics = adapters.context_budget_diagnostics(
109
+ _document(_SKILL_PATH, _section("Plan", 7) + "\n" * 5, prior_text), tmp_path
110
+ )
111
+
112
+ assert len(all_diagnostics) == 1
113
+ assert '{"lines": 13, "sections": ["Plan"]}' in all_diagnostics[0].message
114
+
115
+
116
+ def test_raising_a_baseline_entry_is_a_shrink_only_finding(tmp_path: Path) -> None:
117
+ prior_text = json.dumps(_policy({_SKILL_PATH: {"lines": 300, "sections": []}}))
118
+ current_text = json.dumps(_policy({_SKILL_PATH: {"lines": 310, "sections": []}}))
119
+
120
+ all_diagnostics = adapters.context_budget_policy_diagnostics(
121
+ _document(_POLICY_PATH, current_text, prior_text), tmp_path
122
+ )
123
+
124
+ assert [each.rule_id for each in all_diagnostics] == ["context-budget-policy"]
125
+ assert "the over-budget list may only shrink" in all_diagnostics[0].message
126
+
127
+
128
+ def test_a_new_or_shrunk_policy_file_passes_and_a_malformed_one_reports(
129
+ tmp_path: Path,
130
+ ) -> None:
131
+ prior_text = json.dumps(_policy({_SKILL_PATH: {"lines": 300, "sections": []}}))
132
+ shrunk_text = json.dumps(_policy())
133
+
134
+ assert (
135
+ adapters.context_budget_policy_diagnostics(
136
+ _document(_POLICY_PATH, shrunk_text, prior_text), tmp_path
137
+ )
138
+ == ()
139
+ )
140
+ assert (
141
+ adapters.context_budget_policy_diagnostics(
142
+ _document(_POLICY_PATH, shrunk_text), tmp_path
143
+ )
144
+ == ()
145
+ )
146
+ malformed = adapters.context_budget_policy_diagnostics(
147
+ _document(_POLICY_PATH, "{broken"), tmp_path
148
+ )
149
+ assert "does not parse" in malformed[0].message
150
+
151
+
152
+ def _git(repository_root: Path, *arguments: str) -> None:
153
+ environment = {
154
+ variable_name: variable_text
155
+ for variable_name, variable_text in os.environ.items()
156
+ if not variable_name.upper().startswith("GIT_")
157
+ }
158
+ subprocess.run(
159
+ ["git", "-c", "core.hooksPath=.git/fixture-hooks", *arguments],
160
+ cwd=repository_root, env=environment,
161
+ check=True, capture_output=True, text=True,
162
+ )
163
+
164
+
165
+ def test_a_section_added_below_line_one_reports_in_a_base_selection(
166
+ tmp_path: Path,
167
+ ) -> None:
168
+ """A base selection keeps only diagnostics on changed lines, so a finding
169
+ pinned to an unchanged line 1 vanished from the pull request check."""
170
+ _git(tmp_path, "init", "--quiet", "-b", "main")
171
+ _git(tmp_path, "config", "user.name", "Budget Tests")
172
+ _git(tmp_path, "config", "user.email", "budget@example.invalid")
173
+ _git(tmp_path, "config", "commit.gpgsign", "false")
174
+ _write_policy(tmp_path, _policy())
175
+ skill_path = tmp_path / _SKILL_PATH
176
+ skill_path.parent.mkdir(parents=True)
177
+ skill_path.write_text("# Example\n\nShort map.\n", encoding="utf-8")
178
+ _git(tmp_path, "add", ".")
179
+ _git(tmp_path, "commit", "--quiet", "-m", "fixture")
180
+ with skill_path.open("a", encoding="utf-8") as skill_file:
181
+ skill_file.write("\n" + _section("Oversee", 9))
182
+ _git(tmp_path, "commit", "--quiet", "-am", "inline a long section")
183
+
184
+ report = lint(LintRequest.base(tmp_path, "HEAD~1"))
185
+
186
+ assert [
187
+ each.rule_id for each in report.diagnostics if each.rule_id == "context-budget"
188
+ ] == ["context-budget"]
189
+
190
+
191
+ @pytest.mark.parametrize("selection", ["staged", "base"])
192
+ @pytest.mark.parametrize("operation", ["delete", "rename"])
193
+ def test_removing_the_policy_is_rejected_by_git_selection(
194
+ tmp_path: Path, selection: str, operation: str
195
+ ) -> None:
196
+ _git(tmp_path, "init", "--quiet", "-b", "main")
197
+ _git(tmp_path, "config", "user.name", "Budget Tests")
198
+ _git(tmp_path, "config", "user.email", "budget@example.invalid")
199
+ _git(tmp_path, "config", "commit.gpgsign", "false")
200
+ _write_policy(tmp_path, _policy())
201
+ _git(tmp_path, "add", ".")
202
+ _git(tmp_path, "commit", "--quiet", "-m", "fixture")
203
+ if operation == "delete":
204
+ _git(tmp_path, "rm", _POLICY_PATH)
205
+ else:
206
+ _git(tmp_path, "mv", _POLICY_PATH, ".claude/renamed-budget.json")
207
+ request = (
208
+ LintRequest.staged(tmp_path)
209
+ if selection == "staged"
210
+ else LintRequest.base(tmp_path, "HEAD")
211
+ )
212
+
213
+ report = lint(request)
214
+
215
+ findings = [each for each in report.diagnostics if each.rule_id == "context-budget-policy"]
216
+ assert len(findings) == 1
217
+ assert "policy file is removed" in findings[0].message
218
+ assert not report.failed_rules
@@ -594,3 +594,29 @@ def test_pairing_rejects_comment_rules_module_without_any_approved_suite(
594
594
  ) -> None:
595
595
  all_paths = _diagnostic_paths(tmp_path, _body_change_at(_COMMENT_RULES_PRODUCTION_PATH))
596
596
  assert all_paths == (_COMMENT_RULES_PRODUCTION_PATH,)
597
+
598
+
599
+ def test_pairing_treats_test_support_modules_as_test_files(tmp_path: Path) -> None:
600
+ all_test_support_paths = (
601
+ PurePosixPath("packages/claude-dev-env/scripts/_code_review_test_support.py"),
602
+ PurePosixPath("packages/claude-dev-env/scripts/codec_forwarding_test_support.py"),
603
+ )
604
+ all_paths = _diagnostic_paths(
605
+ tmp_path,
606
+ *(_body_change_at(each_path) for each_path in all_test_support_paths),
607
+ )
608
+ assert all_paths == ()
609
+
610
+
611
+ def test_pairing_rejects_a_module_beside_only_its_changed_test_support(
612
+ tmp_path: Path,
613
+ ) -> None:
614
+ production_path = PurePosixPath("packages/claude-dev-env/scripts/codec_forwarding.py")
615
+ all_paths = _diagnostic_paths(
616
+ tmp_path,
617
+ _body_change_at(production_path),
618
+ _body_change_at(
619
+ PurePosixPath("packages/claude-dev-env/scripts/codec_forwarding_test_support.py")
620
+ ),
621
+ )
622
+ assert all_paths == (production_path,)