claude-dev-env 1.82.0 → 1.84.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.
- package/CLAUDE.md +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
package/bin/install.test.mjs
CHANGED
|
@@ -718,12 +718,13 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
|
|
|
718
718
|
};
|
|
719
719
|
|
|
720
720
|
|
|
721
|
-
test('FOLDED_HOOK_RELATIVE_PATHS contains all 16 hooks removed from hooks.json', () => {
|
|
722
|
-
assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
721
|
+
test('FOLDED_HOOK_RELATIVE_PATHS contains all 16 hooks removed from hooks.json plus the retired md_to_html_blocker', () => {
|
|
722
|
+
assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 17);
|
|
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'));
|
|
726
726
|
assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/pytest_testpaths_orphan_blocker.py'));
|
|
727
|
+
assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/md_to_html_blocker.py'));
|
|
727
728
|
});
|
|
728
729
|
|
|
729
730
|
|
|
@@ -746,16 +747,25 @@ test('FOLDED_HOOK_RELATIVE_PATHS lists every hook the PreToolUse dispatcher host
|
|
|
746
747
|
'blocking/open_questions_in_plans_blocker.py',
|
|
747
748
|
'blocking/plain_language_blocker.py',
|
|
748
749
|
];
|
|
750
|
+
const retiredHooks = [
|
|
751
|
+
'blocking/md_to_html_blocker.py',
|
|
752
|
+
];
|
|
749
753
|
for (const hostedPath of dispatcherHostedHooks) {
|
|
750
754
|
assert.ok(
|
|
751
755
|
FOLDED_HOOK_RELATIVE_PATHS.has(hostedPath),
|
|
752
756
|
`dispatcher-hosted hook ${hostedPath} must be in FOLDED_HOOK_RELATIVE_PATHS so a reinstall prunes its stale standalone entry and it does not double-run`
|
|
753
757
|
);
|
|
754
758
|
}
|
|
759
|
+
for (const retiredPath of retiredHooks) {
|
|
760
|
+
assert.ok(
|
|
761
|
+
FOLDED_HOOK_RELATIVE_PATHS.has(retiredPath),
|
|
762
|
+
`retired hook ${retiredPath} must be in FOLDED_HOOK_RELATIVE_PATHS so a reinstall prunes its stale standalone entry pointing at a script no longer on disk`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
755
765
|
assert.equal(
|
|
756
766
|
FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
757
|
-
dispatcherHostedHooks.length,
|
|
758
|
-
'FOLDED_HOOK_RELATIVE_PATHS must hold exactly the dispatcher-hosted hooks, no more, no fewer'
|
|
767
|
+
dispatcherHostedHooks.length + retiredHooks.length,
|
|
768
|
+
'FOLDED_HOOK_RELATIVE_PATHS must hold exactly the dispatcher-hosted hooks plus the retired hooks, no more, no fewer'
|
|
759
769
|
);
|
|
760
770
|
});
|
|
761
771
|
|
|
@@ -887,11 +897,12 @@ const OLD_POST_FOLDED_HOOKS_SETTINGS = {
|
|
|
887
897
|
};
|
|
888
898
|
|
|
889
899
|
|
|
890
|
-
test('POST_FOLDED_HOOK_RELATIVE_PATHS contains the
|
|
891
|
-
assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
900
|
+
test('POST_FOLDED_HOOK_RELATIVE_PATHS contains the after-write hooks and the retired md_to_html_companion', () => {
|
|
901
|
+
assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size, 4);
|
|
892
902
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('validation/mypy_validator.py'));
|
|
893
903
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/auto_formatter.py'));
|
|
894
904
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/doc_gist_auto_publish.py'));
|
|
905
|
+
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/md_to_html_companion.py'));
|
|
895
906
|
});
|
|
896
907
|
|
|
897
908
|
|
package/commands/CLAUDE.md
CHANGED
|
@@ -7,7 +7,6 @@ Slash-command definitions installed into `~/.claude/commands/` by `bin/install.m
|
|
|
7
7
|
| File | Command | What it does |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| `commit.md` | `/commit` | Commits and pushes changes to GitHub |
|
|
10
|
-
| `doc-gist.md` | `/doc-gist` | Uploads an HTML file as a secret gist and opens the htmlpreview URL |
|
|
11
10
|
| `docupdate.md` | `/docupdate` | Updates documentation to match current code state |
|
|
12
11
|
| `hook-log-extract.md` | `/hook-log-extract` | Extracts and formats hook log entries for a session |
|
|
13
12
|
| `hook-log-init.md` | `/hook-log-init` | Initializes the Neon Postgres schema that backs the hook-log extractor (one-time per machine) |
|
package/docs/CODE_RULES.md
CHANGED
|
@@ -94,4 +94,4 @@ If you already have the data, don't fetch it again.
|
|
|
94
94
|
|
|
95
95
|
## 11. ENFORCEMENT SURFACES
|
|
96
96
|
|
|
97
|
-
⚡ **Hooks** block pattern-matchable violations at Write/Edit time. 🤖 **Prompt context** carries judgment principles (SRP, Right-Sized Engineering, conservative-action, BDD discovery, docstring-prose-matches-implementation; the `/code` skill prepends strict mode for a session: no `Any`/`cast()`, immutable TypedDicts with `_encode_*`/`_decode_*` + `require_*` validation, per-module `_test_hooks.py` DI, 100% statement + branch coverage, zero mocks). 👥 **Audit rubrics** (`/check`, `packages/claude-dev-env/audit-rubrics/` categories A–Q) cover cross-file architectural concerns. Rules with documented-but-pending hook coverage live in `~/.claude/rules/*.md` and `skills/code/SKILL.md`; each names its own promotion path. The docstring-prose standard (free-form enumerations match the body) lives in `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md`, enforced via Category O6 audit.
|
|
97
|
+
⚡ **Hooks** block pattern-matchable violations at Write/Edit time. 🤖 **Prompt context** carries judgment principles (SRP, Right-Sized Engineering, conservative-action, BDD discovery, docstring-prose-matches-implementation; the `/code` skill prepends strict mode for a session: no `Any`/`cast()`, immutable TypedDicts with `_encode_*`/`_decode_*` + `require_*` validation, per-module `_test_hooks.py` DI, 100% statement + branch coverage, zero mocks). 👥 **Audit rubrics** (`/check`, `packages/claude-dev-env/audit-rubrics/` categories A–Q) cover cross-file architectural concerns. Rules with documented-but-pending hook coverage live in `~/.claude/rules/*.md` and `skills/code/SKILL.md`; each names its own promotion path. The docstring-prose standard (free-form enumerations match the body) lives in `packages/claude-dev-env/rules/docstring-prose-matches-implementation.md`, enforced via Category O6 audit. The diagram-first docstring standard (a summary line, then a `::` example or doctest, then a couple of short prose lines) lives in `packages/claude-dev-env/rules/plain-illustrative-docstrings.md`, enforced by the `check_docstring_runon_sentence` and `check_docstring_prose_wall_without_illustration` backstop hooks and Category O9 audit.
|
package/hooks/blocking/CLAUDE.md
CHANGED
|
@@ -77,7 +77,6 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
77
77
|
| `hedging_language_blocker.py` | Stop | Responses with hedging words (`likely`, `probably`, `appears to`) |
|
|
78
78
|
| `hook_prose_detector_consistency.py` | PreToolUse (Write/Edit) | Hook docstrings/messages that claim a trigger the detector cannot fire on |
|
|
79
79
|
| `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work |
|
|
80
|
-
| `md_to_html_blocker.py` | PreToolUse (Write/Edit) | Writing `.md` files when an `.html` companion is required |
|
|
81
80
|
| `open_questions_in_plans_blocker.py` | PreToolUse (Write/Edit) | Plan documents with unresolved open questions |
|
|
82
81
|
| `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 |
|
|
83
82
|
| `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose |
|
|
@@ -86,9 +85,11 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
86
85
|
| `precommit_code_rules_gate.py` | PreToolUse (Bash) | Staged changes that fail the CODE_RULES gate at commit time |
|
|
87
86
|
| `pytest_testpaths_orphan_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | New `test_*.py` files created under a directory absent from a package's explicit pytest `testpaths` allowlist |
|
|
88
87
|
| `question_to_user_enforcer.py` | Stop | User-directed questions not routed through `AskUserQuestion` |
|
|
88
|
+
| `reviewer_spawn_gate.py` | PreToolUse (Bash) | A sentinel-marked autoconverge reviewer-spawn command (Copilot review request, Bugbot rerun comment) run while `reviewer_availability.py` reports that reviewer down or out of quota |
|
|
89
89
|
| `send_user_file_open_locally_blocker.py` | PreToolUse (SendUserFile) | A desk-side file attach (`SendUserFile` with `status` not `proactive`); points to opening the file locally via `Show-Asset.ps1` |
|
|
90
90
|
| `sensitive_file_protector.py` | PreToolUse (Write/Edit) | Writes to sensitive credential or config files |
|
|
91
91
|
| `session_handoff_blocker.py` | Stop | Responses suggesting a new session mid-task |
|
|
92
|
+
| `stale_comment_reference_blocker.py` | PreToolUse (Edit) | An Edit that rewrites a Python code line while keeping the standalone comment directly above it, when that comment names an identifier the rewrite removes from the line |
|
|
92
93
|
| `state_description_blocker.py` | PreToolUse (Write/Edit) | Historical/comparative language in documentation |
|
|
93
94
|
| `subprocess_budget_completeness.py` | PreToolUse | Subprocess calls missing required budget arguments |
|
|
94
95
|
| `tdd_enforcer.py` | PreToolUse (Write/Edit) | Production code written without a matching failing test |
|
|
@@ -105,8 +106,6 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
105
106
|
| File | Role |
|
|
106
107
|
|---|---|
|
|
107
108
|
| `_gh_body_arg_utils.py` | Parsing helpers for `gh_body_arg_blocker.py` |
|
|
108
|
-
| `_md_to_html_blocker_test_support.py` | Test fixtures shared across `md_to_html_blocker` tests |
|
|
109
|
-
| `md_path_exemptions.py` | Path exemption logic for `md_to_html_blocker.py` |
|
|
110
109
|
| `pr_description_body_audit.py` | Body audit logic for `pr_description_enforcer.py` |
|
|
111
110
|
| `pr_description_command_parser.py` | `gh` command parsing for `pr_description_enforcer.py` |
|
|
112
111
|
| `pr_description_pr_number.py` | PR number extraction logic |
|
|
@@ -117,4 +116,3 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
|
|
|
117
116
|
|
|
118
117
|
- Tests live beside each hook as `test_<hookname>.py` or `test_<hookname>_<suffix>.py`. Run with `python -m pytest <test_file>`.
|
|
119
118
|
- Tunable constants live in `hooks_constants/<hook_name>_constants.py`; the verified-commit family uses `blocking/config/verified_commit_constants.py`.
|
|
120
|
-
- `conftest.py` gives shared pytest fixtures for the blocking test suite.
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
"""Constants-outside-config checks and the file-global constant use-count check.
|
|
1
|
+
"""Constants-outside-config checks and the file-global constant use-count check.
|
|
2
|
+
|
|
3
|
+
Also carries check_config_duplicate_path_anchor, which flags a config module
|
|
4
|
+
that rebuilds a directory a sibling module in the same package already
|
|
5
|
+
anchors from its own location.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
import ast
|
|
4
9
|
import re
|
|
@@ -18,12 +23,16 @@ from code_rules_path_utils import ( # noqa: E402
|
|
|
18
23
|
)
|
|
19
24
|
from code_rules_shared import ( # noqa: E402
|
|
20
25
|
_build_parent_map,
|
|
26
|
+
docstring_line_numbers,
|
|
21
27
|
get_file_extension,
|
|
22
28
|
is_migration_file,
|
|
23
29
|
is_test_file,
|
|
24
30
|
is_workflow_registry_file,
|
|
25
31
|
)
|
|
26
32
|
|
|
33
|
+
from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
34
|
+
MAX_CONFIG_DUPLICATE_PATH_ANCHOR_ISSUES,
|
|
35
|
+
)
|
|
27
36
|
from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
|
|
28
37
|
ALL_PYTHON_EXTENSIONS,
|
|
29
38
|
FILE_GLOBAL_UPPER_SNAKE_PATTERN,
|
|
@@ -102,6 +111,7 @@ def _scan_function_body_constants(content: str) -> list[str]:
|
|
|
102
111
|
"""
|
|
103
112
|
advisory_issues: list[str] = []
|
|
104
113
|
lines = content.split("\n")
|
|
114
|
+
all_docstring_line_numbers = docstring_line_numbers(content)
|
|
105
115
|
function_indent_stack: list[int] = []
|
|
106
116
|
constant_pattern = re.compile(r"^([A-Z][A-Z0-9_]{2,})(?:\s*:\s*[^=]+)?\s*=\s*[^=]")
|
|
107
117
|
|
|
@@ -111,6 +121,9 @@ def _scan_function_body_constants(content: str) -> list[str]:
|
|
|
111
121
|
if not stripped:
|
|
112
122
|
continue
|
|
113
123
|
|
|
124
|
+
if each_line_number in all_docstring_line_numbers:
|
|
125
|
+
continue
|
|
126
|
+
|
|
114
127
|
indent = len(each_line) - len(each_line.lstrip())
|
|
115
128
|
|
|
116
129
|
while function_indent_stack and indent <= function_indent_stack[-1] and not stripped.startswith(("#", "@", ")")):
|
|
@@ -253,3 +266,153 @@ def check_file_global_constants_use_count(content: str, file_path: str) -> list[
|
|
|
253
266
|
)
|
|
254
267
|
|
|
255
268
|
return issues
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _references_dunder_file(node: ast.AST) -> bool:
|
|
272
|
+
"""Return True when the expression tree reads ``__file__``."""
|
|
273
|
+
return any(
|
|
274
|
+
isinstance(each_node, ast.Name) and each_node.id == "__file__"
|
|
275
|
+
for each_node in ast.walk(node)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _file_anchor_up_count(node: ast.AST) -> Optional[int]:
|
|
280
|
+
"""Return how many directory levels a ``__file__`` anchor climbs, else None.
|
|
281
|
+
|
|
282
|
+
A ``parents[N]`` subscript on a ``__file__``-rooted expression climbs
|
|
283
|
+
``N + 1`` levels; a chain of ``.parent`` attributes climbs one level per
|
|
284
|
+
link. Any other expression returns None.
|
|
285
|
+
"""
|
|
286
|
+
if isinstance(node, ast.Subscript):
|
|
287
|
+
subscripted = node.value
|
|
288
|
+
if (
|
|
289
|
+
isinstance(subscripted, ast.Attribute)
|
|
290
|
+
and subscripted.attr == "parents"
|
|
291
|
+
and isinstance(node.slice, ast.Constant)
|
|
292
|
+
and isinstance(node.slice.value, int)
|
|
293
|
+
and _references_dunder_file(subscripted.value)
|
|
294
|
+
):
|
|
295
|
+
return node.slice.value + 1
|
|
296
|
+
parent_step_count = 0
|
|
297
|
+
current_node = node
|
|
298
|
+
while isinstance(current_node, ast.Attribute) and current_node.attr == "parent":
|
|
299
|
+
parent_step_count += 1
|
|
300
|
+
current_node = current_node.value
|
|
301
|
+
if parent_step_count and _references_dunder_file(current_node):
|
|
302
|
+
return parent_step_count
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _anchored_join_signature(node: ast.AST) -> Optional[tuple[int, str]]:
|
|
307
|
+
"""Return (up_count, first literal segment) for an anchored ``/`` join, else None.
|
|
308
|
+
|
|
309
|
+
Flattens a left-leaning ``/`` chain, requires the left-most operand to be
|
|
310
|
+
a ``__file__`` anchor, and requires the first joined segment to be a
|
|
311
|
+
string literal.
|
|
312
|
+
"""
|
|
313
|
+
if not isinstance(node, ast.BinOp) or not isinstance(node.op, ast.Div):
|
|
314
|
+
return None
|
|
315
|
+
all_segments: list[str] = []
|
|
316
|
+
current_node: ast.expr = node
|
|
317
|
+
while isinstance(current_node, ast.BinOp) and isinstance(current_node.op, ast.Div):
|
|
318
|
+
right_operand = current_node.right
|
|
319
|
+
if isinstance(right_operand, ast.Constant) and isinstance(right_operand.value, str):
|
|
320
|
+
all_segments.insert(0, right_operand.value)
|
|
321
|
+
else:
|
|
322
|
+
all_segments.insert(0, "")
|
|
323
|
+
current_node = current_node.left
|
|
324
|
+
anchor_up_count = _file_anchor_up_count(current_node)
|
|
325
|
+
if anchor_up_count is None or not all_segments or not all_segments[0]:
|
|
326
|
+
return None
|
|
327
|
+
return anchor_up_count, all_segments[0]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _module_anchor_signatures(module_tree: ast.Module) -> dict[tuple[int, str], int]:
|
|
331
|
+
"""Return line numbers keyed by anchored-join signature for top-level assignments."""
|
|
332
|
+
signatures_by_key: dict[tuple[int, str], int] = {}
|
|
333
|
+
for each_statement in module_tree.body:
|
|
334
|
+
if isinstance(each_statement, ast.Assign):
|
|
335
|
+
assigned_expression = each_statement.value
|
|
336
|
+
elif isinstance(each_statement, ast.AnnAssign) and each_statement.value is not None:
|
|
337
|
+
assigned_expression = each_statement.value
|
|
338
|
+
else:
|
|
339
|
+
continue
|
|
340
|
+
for each_node in ast.walk(assigned_expression):
|
|
341
|
+
signature = _anchored_join_signature(each_node)
|
|
342
|
+
if signature is not None:
|
|
343
|
+
signatures_by_key.setdefault(signature, each_statement.lineno)
|
|
344
|
+
return signatures_by_key
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _config_anchor_check_applies(file_path: str) -> bool:
|
|
348
|
+
"""Return whether the duplicate-path-anchor check should inspect this file."""
|
|
349
|
+
return (
|
|
350
|
+
is_config_file(file_path)
|
|
351
|
+
and not is_test_file(file_path)
|
|
352
|
+
and get_file_extension(file_path) in ALL_PYTHON_EXTENSIONS
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _sibling_anchor_owner_names(config_directory: Path, written_name: str) -> dict[tuple[int, str], str]:
|
|
357
|
+
"""Return the sibling config module name that first builds each anchor signature."""
|
|
358
|
+
owner_name_by_signature: dict[tuple[int, str], str] = {}
|
|
359
|
+
for each_sibling_path in sorted(config_directory.glob("*.py")):
|
|
360
|
+
if each_sibling_path.name == written_name or is_test_file(str(each_sibling_path)):
|
|
361
|
+
continue
|
|
362
|
+
try:
|
|
363
|
+
sibling_tree = ast.parse(each_sibling_path.read_text(encoding="utf-8"))
|
|
364
|
+
except (OSError, SyntaxError, UnicodeDecodeError):
|
|
365
|
+
continue
|
|
366
|
+
for each_signature in _module_anchor_signatures(sibling_tree):
|
|
367
|
+
owner_name_by_signature.setdefault(each_signature, each_sibling_path.name)
|
|
368
|
+
return owner_name_by_signature
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def check_config_duplicate_path_anchor(content: str, file_path: str) -> list[str]:
|
|
372
|
+
"""Flag a config module re-anchoring a path a sibling config module already builds.
|
|
373
|
+
|
|
374
|
+
Two config modules in the same ``config/`` directory that each anchor
|
|
375
|
+
``Path(__file__)`` the same number of levels up and join the same first
|
|
376
|
+
literal segment are two sources of truth for one directory: a rename in
|
|
377
|
+
one module silently leaves the other pointing at the old folder. The
|
|
378
|
+
check fires on a config-module write whose module-level assignment joins
|
|
379
|
+
a ``__file__`` anchor (``parents[N]`` or a ``.parent`` chain) with a
|
|
380
|
+
literal segment that a sibling module in the same directory also joins at
|
|
381
|
+
the same depth. Compose one shared base constant and build the other path
|
|
382
|
+
from it. Test files, non-Python files, and modules outside a config
|
|
383
|
+
directory are exempt; an unreadable or unparseable sibling is skipped.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
content: The Python source under validation.
|
|
387
|
+
file_path: The destination path, used to locate sibling config modules.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
One issue line per duplicated anchor, capped at the configured maximum.
|
|
391
|
+
"""
|
|
392
|
+
if not _config_anchor_check_applies(file_path):
|
|
393
|
+
return []
|
|
394
|
+
try:
|
|
395
|
+
module_tree = ast.parse(content)
|
|
396
|
+
except SyntaxError:
|
|
397
|
+
return []
|
|
398
|
+
written_signatures = _module_anchor_signatures(module_tree)
|
|
399
|
+
if not written_signatures:
|
|
400
|
+
return []
|
|
401
|
+
config_directory = Path(file_path).parent
|
|
402
|
+
if not config_directory.is_dir():
|
|
403
|
+
return []
|
|
404
|
+
owner_name_by_signature = _sibling_anchor_owner_names(config_directory, Path(file_path).name)
|
|
405
|
+
issues: list[str] = []
|
|
406
|
+
for each_signature, each_line_number in sorted(written_signatures.items(), key=lambda pair: pair[1]):
|
|
407
|
+
owner_name = owner_name_by_signature.get(each_signature)
|
|
408
|
+
if owner_name is None:
|
|
409
|
+
continue
|
|
410
|
+
anchor_up_count, first_segment = each_signature
|
|
411
|
+
issues.append(
|
|
412
|
+
f"Line {each_line_number}: joins {first_segment} onto the same base, "
|
|
413
|
+
f"{anchor_up_count} levels above this file, that {owner_name} "
|
|
414
|
+
"already builds - define the base once and compose both paths from it"
|
|
415
|
+
)
|
|
416
|
+
if len(issues) >= MAX_CONFIG_DUPLICATE_PATH_ANCHOR_ISSUES:
|
|
417
|
+
break
|
|
418
|
+
return issues
|