claude-dev-env 1.78.0 → 1.80.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 (63) hide show
  1. package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  2. package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
  5. package/_shared/pr-loop/scripts/tests/CLAUDE.md +7 -0
  6. package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
  7. package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +242 -0
  8. package/_shared/pr-loop/scripts/tests/test_copilot_quota_constants.py +63 -0
  9. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
  10. package/bin/install.mjs +1 -0
  11. package/bin/install.test.mjs +3 -2
  12. package/hooks/blocking/CLAUDE.md +3 -2
  13. package/hooks/blocking/code_rules_docstrings.py +609 -16
  14. package/hooks/blocking/code_rules_enforcer.py +41 -0
  15. package/hooks/blocking/code_rules_imports_logging.py +136 -1
  16. package/hooks/blocking/code_rules_naming_collection.py +76 -1
  17. package/hooks/blocking/code_rules_paired_test.py +240 -22
  18. package/hooks/blocking/code_rules_test_assertions.py +159 -1
  19. package/hooks/blocking/code_rules_unused_imports.py +7 -63
  20. package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
  21. package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
  22. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
  23. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
  24. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
  25. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
  26. package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
  27. package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
  28. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +190 -0
  29. package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
  30. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +96 -15
  31. package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
  32. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
  33. package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
  34. package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
  35. package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
  36. package/hooks/hooks_constants/CLAUDE.md +1 -0
  37. package/hooks/hooks_constants/blocking_check_limits.py +79 -0
  38. package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
  39. package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
  40. package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
  41. package/hooks/hooks_constants/paired_test_coverage_constants.py +11 -3
  42. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  43. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
  44. package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
  45. package/package.json +1 -1
  46. package/rules/CLAUDE.md +1 -0
  47. package/rules/docstring-prose-matches-implementation.md +57 -54
  48. package/rules/env-var-table-code-drift.md +24 -0
  49. package/rules/package-inventory-stale-entry.md +4 -4
  50. package/rules/paired-test-coverage.md +12 -5
  51. package/skills/autoconverge/SKILL.md +26 -5
  52. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  53. package/skills/autoconverge/workflow/converge.contract.test.mjs +56 -120
  54. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +45 -5
  55. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +50 -46
  56. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +6 -6
  57. package/skills/autoconverge/workflow/converge.mjs +110 -228
  58. package/skills/autoconverge/workflow/converge.run-input.test.mjs +11 -0
  59. package/skills/autoconverge/workflow/converge_multi.mjs +23 -7
  60. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +28 -2
  61. package/skills/pr-converge/SKILL.md +28 -2
  62. package/skills/pr-converge/reference/convergence-gates.md +13 -1
  63. package/skills/pr-converge/reference/state-schema.md +11 -0
@@ -0,0 +1,242 @@
1
+ """Direct unit tests for the copilot_quota pre-check.
2
+
3
+ Every case drives the production ``main`` / ``evaluate_copilot_quota`` path with
4
+ only the ``gh`` subprocess boundary (``_run_gh``) stubbed, fed a captured
5
+ ``copilot_internal/user`` JSON fixture in the jonecho shape.
6
+ """
7
+
8
+ import importlib.util
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+ from types import ModuleType
13
+
14
+ import pytest
15
+
16
+
17
+ def _load_copilot_quota_module() -> ModuleType:
18
+ scripts_directory = Path(__file__).parent.parent
19
+ if str(scripts_directory) not in sys.path:
20
+ sys.path.insert(0, str(scripts_directory))
21
+ module_path = scripts_directory / "copilot_quota.py"
22
+ specification = importlib.util.spec_from_file_location("copilot_quota", module_path)
23
+ assert specification is not None
24
+ assert specification.loader is not None
25
+ module = importlib.util.module_from_spec(specification)
26
+ sys.modules[specification.name] = module
27
+ specification.loader.exec_module(module)
28
+ return module
29
+
30
+
31
+ copilot_quota = _load_copilot_quota_module()
32
+
33
+ FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_jonecho.json"
34
+ AVAILABLE_USER_JSON = FIXTURE_PATH.read_text(encoding="utf-8")
35
+ FAKE_TOKEN_RESULT = (0, "ghp_faketoken_value\n")
36
+
37
+
38
+ def _exhausted_user_json() -> str:
39
+ user = json.loads(AVAILABLE_USER_JSON)
40
+ premium = user["quota_snapshots"]["premium_interactions"]
41
+ premium["remaining"] = 0
42
+ premium["quota_remaining"] = 0.0
43
+ premium["percent_remaining"] = 0.0
44
+ return json.dumps(user)
45
+
46
+
47
+ def _user_json_without_premium_snapshot() -> str:
48
+ user = json.loads(AVAILABLE_USER_JSON)
49
+ del user["quota_snapshots"]
50
+ return json.dumps(user)
51
+
52
+
53
+ def _gh_stub(token_result: tuple[int, str], api_result: tuple[int, str]):
54
+ def _fake_run_gh(
55
+ command_arguments: list[str],
56
+ extra_environment: dict[str, str] | None = None,
57
+ ) -> tuple[int, str]:
58
+ if command_arguments and command_arguments[0] == "auth":
59
+ return token_result
60
+ if command_arguments and command_arguments[0] == "api":
61
+ return api_result
62
+ raise AssertionError(f"unexpected gh command {command_arguments}")
63
+
64
+ return _fake_run_gh
65
+
66
+
67
+ @pytest.fixture(autouse=True)
68
+ def _isolate_account_sources(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
69
+ monkeypatch.delenv("COPILOT_QUOTA_ACCOUNT", raising=False)
70
+ monkeypatch.setattr(
71
+ copilot_quota, "COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH", tmp_path / ".env"
72
+ )
73
+
74
+
75
+ def test_main_runs_copilot_when_premium_quota_available(
76
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
77
+ ) -> None:
78
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
79
+ monkeypatch.setattr(
80
+ copilot_quota,
81
+ "_run_gh",
82
+ _gh_stub(FAKE_TOKEN_RESULT, (0, AVAILABLE_USER_JSON)),
83
+ )
84
+ exit_code = copilot_quota.main([])
85
+ captured = capsys.readouterr()
86
+ assert exit_code == 0
87
+ assert "running Copilot" in captured.out
88
+ assert "6817" in captured.out
89
+
90
+
91
+ def test_main_exits_out_of_quota_when_premium_exhausted(
92
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
93
+ ) -> None:
94
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
95
+ monkeypatch.setattr(
96
+ copilot_quota,
97
+ "_run_gh",
98
+ _gh_stub(FAKE_TOKEN_RESULT, (0, _exhausted_user_json())),
99
+ )
100
+ exit_code = copilot_quota.main([])
101
+ captured = capsys.readouterr()
102
+ assert exit_code == 1
103
+ assert "scenario A" in captured.err
104
+
105
+
106
+ def test_main_exits_api_down_on_non_json_response(
107
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
108
+ ) -> None:
109
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
110
+ monkeypatch.setattr(
111
+ copilot_quota,
112
+ "_run_gh",
113
+ _gh_stub(FAKE_TOKEN_RESULT, (0, "not json at all")),
114
+ )
115
+ exit_code = copilot_quota.main([])
116
+ captured = capsys.readouterr()
117
+ assert exit_code == 2
118
+ assert "scenario B" in captured.err
119
+
120
+
121
+ def test_main_exits_api_down_on_missing_premium_snapshot(
122
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
123
+ ) -> None:
124
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
125
+ monkeypatch.setattr(
126
+ copilot_quota,
127
+ "_run_gh",
128
+ _gh_stub(FAKE_TOKEN_RESULT, (0, _user_json_without_premium_snapshot())),
129
+ )
130
+ exit_code = copilot_quota.main([])
131
+ captured = capsys.readouterr()
132
+ assert exit_code == 2
133
+ assert "scenario B" in captured.err
134
+
135
+
136
+ def test_main_exits_api_down_when_gh_token_unresolved(
137
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
138
+ ) -> None:
139
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
140
+ monkeypatch.setattr(
141
+ copilot_quota,
142
+ "_run_gh",
143
+ _gh_stub((1, ""), (0, AVAILABLE_USER_JSON)),
144
+ )
145
+ exit_code = copilot_quota.main([])
146
+ captured = capsys.readouterr()
147
+ assert exit_code == 2
148
+ assert "scenario B" in captured.err
149
+
150
+
151
+ def test_main_exits_no_config_and_names_env_path_and_key(
152
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
153
+ ) -> None:
154
+ def _fail_if_called(
155
+ command_arguments: list[str],
156
+ extra_environment: dict[str, str] | None = None,
157
+ ) -> tuple[int, str]:
158
+ raise AssertionError("gh must not run when no account is configured")
159
+
160
+ monkeypatch.setattr(copilot_quota, "_run_gh", _fail_if_called)
161
+ exit_code = copilot_quota.main([])
162
+ captured = capsys.readouterr()
163
+ assert exit_code == 3
164
+ assert "scenario C" in captured.err
165
+ assert "COPILOT_QUOTA_ACCOUNT" in captured.err
166
+ assert ".env" in captured.err
167
+
168
+
169
+ @pytest.mark.parametrize(
170
+ ("api_result", "expected_exit_code", "expected_scenario"),
171
+ [
172
+ ((0, _exhausted_user_json()), 1, "scenario A"),
173
+ ((0, "not json at all"), 2, "scenario B"),
174
+ ],
175
+ )
176
+ def test_every_gh_backed_skip_writes_a_log_line_naming_the_scenario(
177
+ monkeypatch: pytest.MonkeyPatch,
178
+ capsys: pytest.CaptureFixture[str],
179
+ api_result: tuple[int, str],
180
+ expected_exit_code: int,
181
+ expected_scenario: str,
182
+ ) -> None:
183
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "jonecho")
184
+ monkeypatch.setattr(
185
+ copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, api_result)
186
+ )
187
+ exit_code = copilot_quota.main([])
188
+ captured = capsys.readouterr()
189
+ assert exit_code == expected_exit_code
190
+ assert captured.out == ""
191
+ assert expected_scenario in captured.err
192
+ assert captured.err.strip() != ""
193
+
194
+
195
+ def test_cli_account_takes_precedence_over_env_var(
196
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
197
+ ) -> None:
198
+ monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "env-user")
199
+ accounts_seen: list[str] = []
200
+
201
+ def _fake_run_gh(
202
+ command_arguments: list[str],
203
+ extra_environment: dict[str, str] | None = None,
204
+ ) -> tuple[int, str]:
205
+ if command_arguments[0] == "auth":
206
+ accounts_seen.append(command_arguments[-1])
207
+ return FAKE_TOKEN_RESULT
208
+ return (0, AVAILABLE_USER_JSON)
209
+
210
+ monkeypatch.setattr(copilot_quota, "_run_gh", _fake_run_gh)
211
+ decision = copilot_quota.evaluate_copilot_quota(
212
+ cli_account="cli-user", env_file_path=tmp_path / ".env"
213
+ )
214
+ assert decision.exit_code == 0
215
+ assert accounts_seen == ["cli-user"]
216
+
217
+
218
+ def test_account_resolves_from_env_file_when_flag_and_env_absent(
219
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
220
+ ) -> None:
221
+ env_file = tmp_path / ".env"
222
+ env_file.write_text(
223
+ "# local copilot quota account\nCOPILOT_QUOTA_ACCOUNT=file-user\n",
224
+ encoding="utf-8",
225
+ )
226
+ accounts_seen: list[str] = []
227
+
228
+ def _fake_run_gh(
229
+ command_arguments: list[str],
230
+ extra_environment: dict[str, str] | None = None,
231
+ ) -> tuple[int, str]:
232
+ if command_arguments[0] == "auth":
233
+ accounts_seen.append(command_arguments[-1])
234
+ return FAKE_TOKEN_RESULT
235
+ return (0, AVAILABLE_USER_JSON)
236
+
237
+ monkeypatch.setattr(copilot_quota, "_run_gh", _fake_run_gh)
238
+ decision = copilot_quota.evaluate_copilot_quota(
239
+ cli_account=None, env_file_path=env_file
240
+ )
241
+ assert decision.exit_code == 0
242
+ assert accounts_seen == ["file-user"]
@@ -0,0 +1,63 @@
1
+ """Tests for copilot_quota_constants.py extracted constant set."""
2
+
3
+ import importlib.util
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _load_constants_module() -> ModuleType:
9
+ module_path = (
10
+ Path(__file__).parent.parent
11
+ / "pr_loop_shared_constants"
12
+ / "copilot_quota_constants.py"
13
+ )
14
+ specification = importlib.util.spec_from_file_location(
15
+ "pr_loop_shared_constants.copilot_quota_constants", module_path
16
+ )
17
+ assert specification is not None
18
+ assert specification.loader is not None
19
+ module = importlib.util.module_from_spec(specification)
20
+ specification.loader.exec_module(module)
21
+ return module
22
+
23
+
24
+ constants_module = _load_constants_module()
25
+
26
+
27
+ def test_copilot_quota_account_env_var_name() -> None:
28
+ assert (
29
+ constants_module.COPILOT_QUOTA_ACCOUNT_ENV_VAR_NAME == "COPILOT_QUOTA_ACCOUNT"
30
+ )
31
+
32
+
33
+ def test_gh_token_env_var_name() -> None:
34
+ assert constants_module.GH_TOKEN_ENV_VAR_NAME == "GH_TOKEN"
35
+
36
+
37
+ def test_copilot_internal_user_api_path() -> None:
38
+ assert constants_module.COPILOT_INTERNAL_USER_API_PATH == "copilot_internal/user"
39
+
40
+
41
+ def test_premium_interactions_gating_field_names() -> None:
42
+ assert constants_module.QUOTA_SNAPSHOTS_FIELD_NAME == "quota_snapshots"
43
+ assert constants_module.PREMIUM_INTERACTIONS_FIELD_NAME == "premium_interactions"
44
+ assert constants_module.PREMIUM_UNLIMITED_FIELD_NAME == "unlimited"
45
+ assert constants_module.PREMIUM_REMAINING_FIELD_NAME == "remaining"
46
+ assert constants_module.PREMIUM_OVERAGE_PERMITTED_FIELD_NAME == "overage_permitted"
47
+
48
+
49
+ def test_exit_codes_are_the_four_distinct_scenarios() -> None:
50
+ all_exit_codes = (
51
+ constants_module.EXIT_CODE_QUOTA_AVAILABLE,
52
+ constants_module.EXIT_CODE_OUT_OF_QUOTA,
53
+ constants_module.EXIT_CODE_QUOTA_API_DOWN,
54
+ constants_module.EXIT_CODE_NO_ACCOUNT_CONFIGURED,
55
+ )
56
+ assert all_exit_codes == (0, 1, 2, 3)
57
+ assert len(set(all_exit_codes)) == len(all_exit_codes)
58
+
59
+
60
+ def test_default_env_file_path_sits_at_the_package_root() -> None:
61
+ default_env_file_path = constants_module.COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH
62
+ assert default_env_file_path.name == ".env"
63
+ assert default_env_file_path.parent.name == "claude-dev-env"
@@ -22,6 +22,7 @@
22
22
  - A module's existing `_resolve_base_ref` guards a missing remote with `getattr(remote, "name", "") or DEFAULT_REMOTE`; the diff adds `_resolve_head_ref` beside it that dereferences `remote.name` bare, crashing on the detached-HEAD case its sibling survives.
23
23
  - A rules reference whose enforcement table marks letter J with ⚡ (blocking hook) while its audit-surface section three paragraphs later lists J under "non-blocking, multi-file reasoning" — one letter, two contradictory enforcement claims in one document.
24
24
  - A hooks.json with the same hook registered in two parallel matcher blocks (Write|Edit + MultiEdit) when an existing Write|Edit|MultiEdit block already handles the same surface.
25
+ - A new function re-implements an in-place atomic file rewrite — stream to a temp sibling, `os.replace` over the original, best-effort temp cleanup on failure — that a shared helper already centralizes (`rewrite_stp_member_atomically` and the like); the two copies drift the moment one gets a temp-naming or concurrency fix the other misses (CODE_RULES §3, reuse before create). The audit catches it by matching the diff's temp-sibling-plus-`os.replace` orchestration against the shared module the same file already imports from.
25
26
 
26
27
  **Companion reference:** see `../source-material-section-types.md`.
27
28
 
package/bin/install.mjs CHANGED
@@ -312,6 +312,7 @@ export const FOLDED_HOOK_RELATIVE_PATHS = new Set([
312
312
  'blocking/verified_commit_message_accuracy_blocker.py',
313
313
  'blocking/workflow_substitution_slot_blocker.py',
314
314
  'blocking/claude_md_orphan_file_blocker.py',
315
+ 'blocking/env_var_table_code_drift_blocker.py',
315
316
  'blocking/pytest_testpaths_orphan_blocker.py',
316
317
  'blocking/open_questions_in_plans_blocker.py',
317
318
  'blocking/plain_language_blocker.py',
@@ -718,8 +718,8 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
718
718
  };
719
719
 
720
720
 
721
- test('FOLDED_HOOK_RELATIVE_PATHS contains all 15 hooks removed from hooks.json', () => {
722
- assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 15);
721
+ test('FOLDED_HOOK_RELATIVE_PATHS contains all 16 hooks removed from hooks.json', () => {
722
+ assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 16);
723
723
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/write_existing_file_blocker.py'));
724
724
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/plain_language_blocker.py'));
725
725
  assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/code_rules_enforcer.py'));
@@ -741,6 +741,7 @@ test('FOLDED_HOOK_RELATIVE_PATHS lists every hook the PreToolUse dispatcher host
741
741
  'blocking/verified_commit_message_accuracy_blocker.py',
742
742
  'blocking/workflow_substitution_slot_blocker.py',
743
743
  'blocking/claude_md_orphan_file_blocker.py',
744
+ 'blocking/env_var_table_code_drift_blocker.py',
744
745
  'blocking/pytest_testpaths_orphan_blocker.py',
745
746
  'blocking/open_questions_in_plans_blocker.py',
746
747
  'blocking/plain_language_blocker.py',
@@ -38,7 +38,7 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
38
38
  | `code_rules_naming_collection.py` | Collection names must use `all_*` prefix |
39
39
  | `code_rules_optional_params.py` | No optional parameters where a required one would do |
40
40
  | `code_rules_orphan_css_class.py` | CSS class attributes in Python markup with no matching `.<class>` selector |
41
- | `code_rules_paired_test.py` | A public function omitted by a module's established paired test suite must get a behavioral test |
41
+ | `code_rules_paired_test.py` | A public function omitted by a module's established paired test suite must get a behavioral test — checked on both the production-module write and the stem-matched test-file write |
42
42
  | `code_rules_path_utils.py` | Path utility helpers shared across check modules |
43
43
  | `code_rules_paths_syspath.py` | `sys.path.insert` must be guarded |
44
44
  | `code_rules_probe_chains.py` | Probe-chain detection logic |
@@ -66,6 +66,7 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
66
66
  | `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
67
67
  | `docstring_rule_gate_count_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A stale spelled-out gate-validator count in `docstring-prose-matches-implementation.md` — the "N more gate validators" / "M gated slices" count drifting from the `check_docstring_*` validators the prose names |
68
68
  | `duplicate_rmtree_helper_blocker.py` | PreToolUse (Write/Edit) | A local re-definition of the Windows-safe rmtree helper trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`) in place of importing a shared helper |
69
+ | `env_var_table_code_drift_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A markdown env-var summary table row attributing an environment variable to a code file whose source never references that variable name |
69
70
  | `es_exe_path_rewriter.py` | PreToolUse | Rewrites paths referencing `.exe` under the Everything search path |
70
71
  | `gh_body_arg_blocker.py` | PreToolUse (Bash) | `gh` commands passing `--body`/`-b` directly (requires `--body-file` instead) |
71
72
  | `gh_pr_author_enforcer.py` | PreToolUse | Enforces PR author identity rules |
@@ -75,7 +76,7 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
75
76
  | `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work |
76
77
  | `md_to_html_blocker.py` | PreToolUse (Write/Edit) | Writing `.md` files when an `.html` companion is required |
77
78
  | `open_questions_in_plans_blocker.py` | PreToolUse (Write/Edit) | Plan documents with unresolved open questions |
78
- | `package_inventory_stale_blocker.py` | PreToolUse (Write) | A new production code file created in a directory whose `README.md`/`CLAUDE.md` inventory names two or more sibling files but no entry for the new file |
79
+ | `package_inventory_stale_blocker.py` | PreToolUse (Write) | A new production code file created in a directory whose `README.md`/`CLAUDE.md` inventory (or a parent skill's `SKILL.md` Layout table mapping the `scripts/` subdirectory) names two or more sibling files but no entry for the new file |
79
80
  | `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose |
80
81
  | `pr_converge_bugteam_enforcer.py` | PreToolUse | Enforces that bugteam runs in parallel with bugbot in pr-converge loops |
81
82
  | `pr_description_enforcer.py` | PreToolUse (Bash) | `gh pr create`/`edit` without a PR-description-writer-authored body |