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
|
@@ -25,6 +25,7 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
25
25
|
ALL_ABSENT_TYPE_CHECKING_GATE_DOCSTRING_PHRASES,
|
|
26
26
|
ALL_DATA_SCHEMA_CONSTANT_NAME_MARKERS,
|
|
27
27
|
ALL_DATA_SCHEMA_DOCSTRING_ACKNOWLEDGEMENT_PHRASES,
|
|
28
|
+
ALL_DELEGATION_ENUMERATION_STOP_TOKENS,
|
|
28
29
|
ALL_DOCSTRING_EXCLUSIVE_SCOPE_PHRASES,
|
|
29
30
|
ALL_DOCSTRING_EXEMPT_DECORATOR_NAMES,
|
|
30
31
|
ALL_DOCSTRING_FILE_REFERENCE_SUFFIXES,
|
|
@@ -52,6 +53,8 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
52
53
|
ALL_ZIPFILE_WRITE_MODE_VALUES,
|
|
53
54
|
DOCSTRING_FALLBACK_BRANCH_MINIMUM_ROUTE_COUNT,
|
|
54
55
|
DOCSTRING_LARGE_ZIP_FILE_EXCEPTION_NAME,
|
|
56
|
+
DOCSTRING_NARRATIVE_LINE_JOIN_SEPARATOR,
|
|
57
|
+
DOCSTRING_NARRATIVE_PROSE_LINE_LIMIT,
|
|
55
58
|
DOCSTRING_REFERENCE_MARKER_WINDOW,
|
|
56
59
|
DOCSTRING_RUNON_SENTENCE_BOUNDARY_PATTERN,
|
|
57
60
|
DOCSTRING_RUNON_SENTENCE_WORD_LIMIT,
|
|
@@ -63,6 +66,7 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
63
66
|
MAX_DOCSTRING_ARGS_SIGNATURE_ISSUES,
|
|
64
67
|
MAX_DOCSTRING_ARGS_SPAN_SCOPE_ISSUES,
|
|
65
68
|
MAX_DOCSTRING_CARDINAL_FAMILY_ISSUES,
|
|
69
|
+
MAX_DOCSTRING_DELEGATION_ENUMERATION_ISSUES,
|
|
66
70
|
MAX_DOCSTRING_FALLBACK_BRANCH_ISSUES,
|
|
67
71
|
MAX_DOCSTRING_FIELD_RUNMODE_OUTCOME_ISSUES,
|
|
68
72
|
MAX_DOCSTRING_FORMAT_ISSUES,
|
|
@@ -70,6 +74,7 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
70
74
|
MAX_DOCSTRING_MARK_GLYPH_ENUMERATION_ISSUES,
|
|
71
75
|
MAX_DOCSTRING_NO_CONSUMER_CLAIM_ISSUES,
|
|
72
76
|
MAX_DOCSTRING_NO_NETWORK_CLAIM_ISSUES,
|
|
77
|
+
MAX_DOCSTRING_PROSE_WALL_ISSUES,
|
|
73
78
|
MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES,
|
|
74
79
|
MAX_DOCSTRING_RETURNS_PLURAL_CARDINALITY_ISSUES,
|
|
75
80
|
MAX_DOCSTRING_RUNON_SENTENCE_ISSUES,
|
|
@@ -82,16 +87,21 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
82
87
|
MAX_MODULE_DOCSTRING_CHECK_ROSTER_ISSUES,
|
|
83
88
|
MAX_MODULE_DOCSTRING_DATA_SCHEMA_SCOPE_ISSUES,
|
|
84
89
|
MINIMUM_CONSTANT_FAMILY_MEMBERS_FOR_CARDINAL_CHECK,
|
|
90
|
+
MINIMUM_DELEGATION_ENUMERATION_ITEMS,
|
|
85
91
|
MINIMUM_DOCSTRING_FAMILY_OVERLAP_FOR_CARDINAL_CHECK,
|
|
86
92
|
MINIMUM_NAMED_LINEAR_STEPS_FOR_DISPATCH_CHECK,
|
|
87
93
|
MINIMUM_NAMED_MARKS_FOR_PROSE_ENUMERATION,
|
|
88
94
|
MINIMUM_PUBLIC_CHECKS_FOR_MODULE_DOCSTRING_ROSTER,
|
|
89
95
|
MINIMUM_PUBLIC_METHODS_FOR_CLASS_DOCSTRING_BREADTH,
|
|
96
|
+
MINIMUM_SIBLING_OCCURRENCES_FOR_SHARED_TOKEN,
|
|
90
97
|
MINIMUM_TOKENS_FOR_DISPATCH_CALLEE,
|
|
91
98
|
MINIMUM_TUPLE_MEMBERS_FOR_DOCSTRING_ENUMERATION,
|
|
92
99
|
MODULE_DOCSTRING_DATA_SCHEMA_CONSTANT_SAMPLE_LIMIT,
|
|
100
|
+
NEIGHBOR_SCAN_FILE_LIMIT,
|
|
101
|
+
POINTER_TO_DELEGATE_PATTERN,
|
|
93
102
|
PYTHON_MODULE_FILE_SUFFIX,
|
|
94
103
|
SINGLE_DICT_KEY_COUNT_FOR_PLURAL_CARDINALITY_DRIFT,
|
|
104
|
+
TOKEN_WORD_PATTERN,
|
|
95
105
|
TYPE_CHECKING_IDENTIFIER_MARKER,
|
|
96
106
|
WORD_BOUNDARY_REGEX,
|
|
97
107
|
ZIPFILE_ALLOW_ZIP64_KEYWORD,
|
|
@@ -1128,14 +1138,48 @@ def _distinctive_name_tokens(check_name: str) -> list[str]:
|
|
|
1128
1138
|
]
|
|
1129
1139
|
|
|
1130
1140
|
|
|
1131
|
-
def
|
|
1141
|
+
def _shared_sibling_name_tokens(all_check_names: list[str]) -> frozenset[str]:
|
|
1142
|
+
token_occurrence_count: dict[str, int] = {}
|
|
1143
|
+
for each_check_name in all_check_names:
|
|
1144
|
+
for each_token in set(each_token.lower() for each_token in _name_tokens(each_check_name)):
|
|
1145
|
+
token_occurrence_count[each_token] = token_occurrence_count.get(each_token, 0) + 1
|
|
1146
|
+
return frozenset(
|
|
1147
|
+
each_token
|
|
1148
|
+
for each_token, each_count in token_occurrence_count.items()
|
|
1149
|
+
if each_count >= MINIMUM_SIBLING_OCCURRENCES_FOR_SHARED_TOKEN
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def _module_distinctive_name_tokens(
|
|
1154
|
+
check_name: str, all_shared_sibling_tokens: frozenset[str]
|
|
1155
|
+
) -> list[str]:
|
|
1156
|
+
return [
|
|
1157
|
+
each_token
|
|
1158
|
+
for each_token in _distinctive_name_tokens(check_name)
|
|
1159
|
+
if each_token.lower() not in all_shared_sibling_tokens
|
|
1160
|
+
]
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def _token_present_in_text(token: str, lowered_text: str) -> bool:
|
|
1164
|
+
lowered_token = token.lower()
|
|
1165
|
+
if lowered_token in lowered_text:
|
|
1166
|
+
return True
|
|
1167
|
+
return lowered_token.endswith("s") and lowered_token[:-1] in lowered_text
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def _docstring_mentions_check(
|
|
1171
|
+
docstring_text: str, check_name: str, all_shared_sibling_tokens: frozenset[str]
|
|
1172
|
+
) -> bool:
|
|
1132
1173
|
lowered_docstring = docstring_text.lower()
|
|
1133
1174
|
if check_name.lower() in lowered_docstring:
|
|
1134
1175
|
return True
|
|
1135
|
-
|
|
1136
|
-
if not
|
|
1176
|
+
distinguishing_tokens = _module_distinctive_name_tokens(check_name, all_shared_sibling_tokens)
|
|
1177
|
+
if not distinguishing_tokens:
|
|
1137
1178
|
return True
|
|
1138
|
-
return any(
|
|
1179
|
+
return any(
|
|
1180
|
+
_token_present_in_text(each_token, lowered_docstring)
|
|
1181
|
+
for each_token in distinguishing_tokens
|
|
1182
|
+
)
|
|
1139
1183
|
|
|
1140
1184
|
|
|
1141
1185
|
def check_module_docstring_names_public_checks(content: str, file_path: str) -> list[str]:
|
|
@@ -1146,10 +1190,17 @@ def check_module_docstring_names_public_checks(content: str, file_path: str) ->
|
|
|
1146
1190
|
roster. When the module grows a public ``check_*`` entry point the summary
|
|
1147
1191
|
never names, the enumeration under-describes the module — the
|
|
1148
1192
|
docstring-prose-vs-implementation drift the repo flags as Category O6/O8.
|
|
1149
|
-
A check counts as named when the full ``check_*`` name, or
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1193
|
+
A check counts as named when the full ``check_*`` name, or a
|
|
1194
|
+
module-distinctive underscore-separated token of it, appears in the summary
|
|
1195
|
+
(matched allowing a trailing-``s`` plural). A token is module-distinctive
|
|
1196
|
+
when it is non-generic (generic tokens ``check``, ``test``, ``tests`` never
|
|
1197
|
+
count) and it is not shared by two or more of the module's check names — a
|
|
1198
|
+
token such as ``string`` or ``magic`` that recurs across sibling checks is
|
|
1199
|
+
no evidence that any one check is named, so a check whose only
|
|
1200
|
+
summary-present tokens are shared ones is reported. A check with no
|
|
1201
|
+
module-distinctive token is treated as named, since the summary cannot single
|
|
1202
|
+
it out. A module with two or more public checks and any check the summary
|
|
1203
|
+
never names is reported
|
|
1153
1204
|
so the summary names the full roster. Modules with a multi-paragraph
|
|
1154
1205
|
docstring body are left to the audit lane, since their prose can carry the
|
|
1155
1206
|
roster without naming each check by name. This check covers hook
|
|
@@ -1175,9 +1226,10 @@ def check_module_docstring_names_public_checks(content: str, file_path: str) ->
|
|
|
1175
1226
|
public_check_names = _module_public_check_names(parsed_tree)
|
|
1176
1227
|
if len(public_check_names) < MINIMUM_PUBLIC_CHECKS_FOR_MODULE_DOCSTRING_ROSTER:
|
|
1177
1228
|
return []
|
|
1229
|
+
all_shared_sibling_tokens = _shared_sibling_name_tokens(public_check_names)
|
|
1178
1230
|
issues: list[str] = []
|
|
1179
1231
|
for each_name in public_check_names:
|
|
1180
|
-
if _docstring_mentions_check(module_docstring, each_name):
|
|
1232
|
+
if _docstring_mentions_check(module_docstring, each_name, all_shared_sibling_tokens):
|
|
1181
1233
|
continue
|
|
1182
1234
|
issues.append(
|
|
1183
1235
|
f"Line 1: module docstring omits public check {each_name}() — name every "
|
|
@@ -2513,14 +2565,71 @@ def _is_narrative_cut_header(stripped_line: str) -> bool:
|
|
|
2513
2565
|
)
|
|
2514
2566
|
|
|
2515
2567
|
|
|
2516
|
-
def
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2568
|
+
def _docstring_narrative_partition(docstring_text: str) -> tuple[list[str], bool]:
|
|
2569
|
+
"""Split a docstring narrative into its prose lines and whether it shows an example.
|
|
2570
|
+
|
|
2571
|
+
Walk the narrative up to the first ``Args:``/``Returns:`` cut header and sort
|
|
2572
|
+
every line into one of two buckets::
|
|
2573
|
+
|
|
2574
|
+
Render the report so the reader sees how it ended. <- prose line
|
|
2575
|
+
A finished run versus an interrupted one:: <- prose line (opener)
|
|
2576
|
+
theme_42 -- interrupted -> marked in-flight -- indented example body
|
|
2577
|
+
OK: a clean run reads differently in the log -- indented example body
|
|
2578
|
+
The reader reads the final outcome at a glance. <- prose line
|
|
2579
|
+
|
|
2580
|
+
A ``>>>`` doctest region and a ``::`` block with an indented body both count as
|
|
2581
|
+
an illustration, so their lines never join the prose bucket. The caller reads
|
|
2582
|
+
the prose count to size the wall and the flag to know whether an example is present.
|
|
2583
|
+
"""
|
|
2584
|
+
prose_lines: list[str] = []
|
|
2585
|
+
has_illustration = False
|
|
2586
|
+
all_lines = docstring_text.splitlines()
|
|
2587
|
+
total_lines = len(all_lines)
|
|
2588
|
+
line_index = 0
|
|
2589
|
+
while line_index < total_lines:
|
|
2590
|
+
raw_line = all_lines[line_index]
|
|
2591
|
+
stripped_line = raw_line.strip()
|
|
2520
2592
|
if _is_narrative_cut_header(stripped_line):
|
|
2521
2593
|
break
|
|
2522
|
-
|
|
2523
|
-
|
|
2594
|
+
if stripped_line.startswith(">>>"):
|
|
2595
|
+
has_illustration = True
|
|
2596
|
+
line_index += 1
|
|
2597
|
+
while line_index < total_lines:
|
|
2598
|
+
inner_stripped = all_lines[line_index].strip()
|
|
2599
|
+
if not inner_stripped or _is_narrative_cut_header(inner_stripped):
|
|
2600
|
+
break
|
|
2601
|
+
line_index += 1
|
|
2602
|
+
continue
|
|
2603
|
+
if stripped_line.endswith("::"):
|
|
2604
|
+
opener_indent = len(raw_line) - len(raw_line.lstrip())
|
|
2605
|
+
probe_index = line_index + 1
|
|
2606
|
+
has_indented_body = False
|
|
2607
|
+
while probe_index < total_lines:
|
|
2608
|
+
probe_raw = all_lines[probe_index]
|
|
2609
|
+
probe_stripped = probe_raw.strip()
|
|
2610
|
+
if not probe_stripped:
|
|
2611
|
+
probe_index += 1
|
|
2612
|
+
continue
|
|
2613
|
+
probe_indent = len(probe_raw) - len(probe_raw.lstrip())
|
|
2614
|
+
if probe_indent > opener_indent:
|
|
2615
|
+
has_indented_body = True
|
|
2616
|
+
probe_index += 1
|
|
2617
|
+
continue
|
|
2618
|
+
break
|
|
2619
|
+
if has_indented_body:
|
|
2620
|
+
has_illustration = True
|
|
2621
|
+
prose_lines.append(stripped_line)
|
|
2622
|
+
line_index = probe_index
|
|
2623
|
+
continue
|
|
2624
|
+
if stripped_line:
|
|
2625
|
+
prose_lines.append(stripped_line)
|
|
2626
|
+
line_index += 1
|
|
2627
|
+
return prose_lines, has_illustration
|
|
2628
|
+
|
|
2629
|
+
|
|
2630
|
+
def _docstring_narrative_text(docstring_text: str) -> str:
|
|
2631
|
+
prose_lines, _has_illustration = _docstring_narrative_partition(docstring_text)
|
|
2632
|
+
return DOCSTRING_NARRATIVE_LINE_JOIN_SEPARATOR.join(prose_lines)
|
|
2524
2633
|
|
|
2525
2634
|
|
|
2526
2635
|
def _sentence_word_count(sentence_text: str) -> int:
|
|
@@ -2626,6 +2735,56 @@ def check_docstring_runon_sentence(content: str, file_path: str) -> list[str]:
|
|
|
2626
2735
|
return issues[:MAX_DOCSTRING_RUNON_SENTENCE_ISSUES]
|
|
2627
2736
|
|
|
2628
2737
|
|
|
2738
|
+
def check_docstring_prose_wall_without_illustration(content: str, file_path: str) -> list[str]:
|
|
2739
|
+
"""Flag a summary that tells for many sentences and shows nothing.
|
|
2740
|
+
|
|
2741
|
+
A reader trusts the opening to paint a scene. A run of short sentences with
|
|
2742
|
+
no worked example leaves the reader piecing that scene together alone::
|
|
2743
|
+
|
|
2744
|
+
A calm voyage ends well for every vessel. <- one more line
|
|
2745
|
+
A halted voyage marks the vessel it neared. <- and another
|
|
2746
|
+
... more like these, no worked example ... flag: a wall, no scene
|
|
2747
|
+
A calm voyage versus a halted one:: ok: a worked example
|
|
2748
|
+
a lone vessel -- halted -> marked mid-voyage
|
|
2749
|
+
|
|
2750
|
+
Past the prose-line limit with no ``::`` listing and no ``>>>`` doctest, this
|
|
2751
|
+
fires. A narrative that shows a worked example, or one at the limit, passes.
|
|
2752
|
+
|
|
2753
|
+
Args:
|
|
2754
|
+
content: The source text to inspect.
|
|
2755
|
+
file_path: The path the source will be written to, used for exemptions.
|
|
2756
|
+
|
|
2757
|
+
Returns:
|
|
2758
|
+
One issue per summary that runs a wall of sentences with no worked
|
|
2759
|
+
example, capped at the issue limit for the rule.
|
|
2760
|
+
"""
|
|
2761
|
+
if is_test_file(file_path) or is_hook_infrastructure(file_path):
|
|
2762
|
+
return []
|
|
2763
|
+
try:
|
|
2764
|
+
parsed_tree = ast.parse(content)
|
|
2765
|
+
except SyntaxError:
|
|
2766
|
+
return []
|
|
2767
|
+
issues: list[str] = []
|
|
2768
|
+
for each_line_number, each_label, each_docstring in _documentable_docstring_targets(
|
|
2769
|
+
parsed_tree
|
|
2770
|
+
):
|
|
2771
|
+
prose_lines, has_illustration = _docstring_narrative_partition(each_docstring)
|
|
2772
|
+
if has_illustration:
|
|
2773
|
+
continue
|
|
2774
|
+
prose_line_count = len(prose_lines)
|
|
2775
|
+
if prose_line_count <= DOCSTRING_NARRATIVE_PROSE_LINE_LIMIT:
|
|
2776
|
+
continue
|
|
2777
|
+
issues.append(
|
|
2778
|
+
f"Line {each_line_number}: {each_label} summary runs {prose_line_count} "
|
|
2779
|
+
"narrative lines with no worked example - show, don't tell: swap the wall for a "
|
|
2780
|
+
"'::' listing (a sample input, an annotated outcome, ok/flag contrast rows) and "
|
|
2781
|
+
"keep the narrative to a few short lines (plain-illustrative-docstrings)"
|
|
2782
|
+
)
|
|
2783
|
+
if len(issues) >= MAX_DOCSTRING_PROSE_WALL_ISSUES:
|
|
2784
|
+
break
|
|
2785
|
+
return issues[:MAX_DOCSTRING_PROSE_WALL_ISSUES]
|
|
2786
|
+
|
|
2787
|
+
|
|
2629
2788
|
def _raises_section_text(docstring_text: str) -> str:
|
|
2630
2789
|
docstring_lines = docstring_text.splitlines()
|
|
2631
2790
|
raises_section_lines: list[str] = []
|
|
@@ -2874,3 +3033,257 @@ def check_docstring_raises_unraisable_largezipfile(
|
|
|
2874
3033
|
if len(issues) >= MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES:
|
|
2875
3034
|
break
|
|
2876
3035
|
return issues[:MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES]
|
|
3036
|
+
|
|
3037
|
+
|
|
3038
|
+
def _leading_summary_line(raw_docstring: str) -> str:
|
|
3039
|
+
summary_paragraph = raw_docstring.strip().split("\n\n", maxsplit=1)[0]
|
|
3040
|
+
return " ".join(summary_paragraph.split())
|
|
3041
|
+
|
|
3042
|
+
|
|
3043
|
+
def _pointer_target_stem(raw_docstring: str) -> str:
|
|
3044
|
+
marker_match = POINTER_TO_DELEGATE_PATTERN.search(raw_docstring)
|
|
3045
|
+
return marker_match.group(1) if marker_match is not None else ""
|
|
3046
|
+
|
|
3047
|
+
|
|
3048
|
+
def _listed_entries(wrapper_summary: str) -> list[str]:
|
|
3049
|
+
marker_match = POINTER_TO_DELEGATE_PATTERN.search(wrapper_summary)
|
|
3050
|
+
if marker_match is None:
|
|
3051
|
+
return []
|
|
3052
|
+
listed_text = wrapper_summary[: marker_match.start()].strip().rstrip(";,").strip()
|
|
3053
|
+
normalized_text = re.sub(r"\s+and\s+", ", ", listed_text, flags=re.IGNORECASE)
|
|
3054
|
+
return [
|
|
3055
|
+
each_entry.strip()
|
|
3056
|
+
for each_entry in normalized_text.split(",")
|
|
3057
|
+
if each_entry.strip()
|
|
3058
|
+
]
|
|
3059
|
+
|
|
3060
|
+
|
|
3061
|
+
def _words_of_entry(raw_entry: str) -> list[str]:
|
|
3062
|
+
return [
|
|
3063
|
+
each_token.lower()
|
|
3064
|
+
for each_token in TOKEN_WORD_PATTERN.findall(raw_entry)
|
|
3065
|
+
if each_token.lower() not in ALL_DELEGATION_ENUMERATION_STOP_TOKENS
|
|
3066
|
+
]
|
|
3067
|
+
|
|
3068
|
+
|
|
3069
|
+
def _matches_any_word(delegate_summary: str, all_distinct_words: list[str]) -> bool:
|
|
3070
|
+
lowered_summary = delegate_summary.lower()
|
|
3071
|
+
return any(
|
|
3072
|
+
re.search(
|
|
3073
|
+
WORD_BOUNDARY_REGEX + re.escape(each_token) + WORD_BOUNDARY_REGEX,
|
|
3074
|
+
lowered_summary,
|
|
3075
|
+
)
|
|
3076
|
+
is not None
|
|
3077
|
+
for each_token in all_distinct_words
|
|
3078
|
+
)
|
|
3079
|
+
|
|
3080
|
+
|
|
3081
|
+
def _absent_entries(wrapper_summary: str, delegate_summary: str) -> list[str]:
|
|
3082
|
+
all_entries = _listed_entries(wrapper_summary)
|
|
3083
|
+
if len(all_entries) < MINIMUM_DELEGATION_ENUMERATION_ITEMS:
|
|
3084
|
+
return []
|
|
3085
|
+
if "," not in delegate_summary:
|
|
3086
|
+
return []
|
|
3087
|
+
absent_entry_texts: list[str] = []
|
|
3088
|
+
for each_entry in all_entries:
|
|
3089
|
+
distinct_words = _words_of_entry(each_entry)
|
|
3090
|
+
if not distinct_words:
|
|
3091
|
+
continue
|
|
3092
|
+
if _matches_any_word(delegate_summary, distinct_words):
|
|
3093
|
+
continue
|
|
3094
|
+
absent_entry_texts.append(each_entry)
|
|
3095
|
+
return absent_entry_texts
|
|
3096
|
+
|
|
3097
|
+
|
|
3098
|
+
def _top_level_definitions_and_methods(
|
|
3099
|
+
parsed_tree: ast.Module,
|
|
3100
|
+
) -> list[ast.FunctionDef | ast.AsyncFunctionDef]:
|
|
3101
|
+
all_definitions_and_methods: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
|
|
3102
|
+
for each_node in parsed_tree.body:
|
|
3103
|
+
if isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
3104
|
+
all_definitions_and_methods.append(each_node)
|
|
3105
|
+
elif isinstance(each_node, ast.ClassDef):
|
|
3106
|
+
all_definitions_and_methods.extend(
|
|
3107
|
+
each_member
|
|
3108
|
+
for each_member in each_node.body
|
|
3109
|
+
if isinstance(each_member, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
3110
|
+
)
|
|
3111
|
+
return all_definitions_and_methods
|
|
3112
|
+
|
|
3113
|
+
|
|
3114
|
+
def _entries_by_function_name(parsed_tree: ast.Module) -> dict[str, tuple[int, str]]:
|
|
3115
|
+
entries_by_name: dict[str, tuple[int, str]] = {}
|
|
3116
|
+
ambiguous_names: set[str] = set()
|
|
3117
|
+
for each_definition in _top_level_definitions_and_methods(parsed_tree):
|
|
3118
|
+
if each_definition.name in entries_by_name:
|
|
3119
|
+
ambiguous_names.add(each_definition.name)
|
|
3120
|
+
continue
|
|
3121
|
+
entries_by_name[each_definition.name] = (
|
|
3122
|
+
each_definition.lineno,
|
|
3123
|
+
ast.get_docstring(each_definition) or "",
|
|
3124
|
+
)
|
|
3125
|
+
for each_ambiguous_name in ambiguous_names:
|
|
3126
|
+
del entries_by_name[each_ambiguous_name]
|
|
3127
|
+
return entries_by_name
|
|
3128
|
+
|
|
3129
|
+
|
|
3130
|
+
def _source_text_or_empty(source_path: Path) -> str:
|
|
3131
|
+
try:
|
|
3132
|
+
return source_path.read_text(encoding="utf-8")
|
|
3133
|
+
except (OSError, UnicodeDecodeError):
|
|
3134
|
+
return ""
|
|
3135
|
+
|
|
3136
|
+
|
|
3137
|
+
def _parsed_module_or_none(source_text: str) -> ast.Module | None:
|
|
3138
|
+
if not source_text:
|
|
3139
|
+
return None
|
|
3140
|
+
try:
|
|
3141
|
+
return ast.parse(source_text)
|
|
3142
|
+
except SyntaxError:
|
|
3143
|
+
return None
|
|
3144
|
+
|
|
3145
|
+
|
|
3146
|
+
def _outbound_pointer_issues(parsed_tree: ast.Module, file_path: str) -> list[str]:
|
|
3147
|
+
parent_directory = Path(file_path).parent
|
|
3148
|
+
issues: list[str] = []
|
|
3149
|
+
delegate_entries_by_stem: dict[str, dict[str, tuple[int, str]]] = {}
|
|
3150
|
+
for each_node in _top_level_definitions_and_methods(parsed_tree):
|
|
3151
|
+
wrapper_docstring = ast.get_docstring(each_node) or ""
|
|
3152
|
+
wrapper_summary = _leading_summary_line(wrapper_docstring)
|
|
3153
|
+
target_stem = _pointer_target_stem(wrapper_summary)
|
|
3154
|
+
if not target_stem:
|
|
3155
|
+
continue
|
|
3156
|
+
if target_stem not in delegate_entries_by_stem:
|
|
3157
|
+
delegate_path = parent_directory / (target_stem + PYTHON_MODULE_FILE_SUFFIX)
|
|
3158
|
+
delegate_tree = _parsed_module_or_none(_source_text_or_empty(delegate_path))
|
|
3159
|
+
delegate_entries_by_stem[target_stem] = (
|
|
3160
|
+
_entries_by_function_name(delegate_tree)
|
|
3161
|
+
if delegate_tree is not None
|
|
3162
|
+
else {}
|
|
3163
|
+
)
|
|
3164
|
+
delegate_entry = delegate_entries_by_stem[target_stem].get(each_node.name)
|
|
3165
|
+
if delegate_entry is None:
|
|
3166
|
+
continue
|
|
3167
|
+
delegate_summary = _leading_summary_line(delegate_entry[1])
|
|
3168
|
+
for each_entry in _absent_entries(wrapper_summary, delegate_summary):
|
|
3169
|
+
issues.append(
|
|
3170
|
+
f"Line {each_node.lineno}: {each_node.name}() names "
|
|
3171
|
+
f"'{each_entry}' in its summary, but the summary of the "
|
|
3172
|
+
f"same-named function in {target_stem} omits it - align the "
|
|
3173
|
+
"two summaries (Category O6 docstring-vs-implementation drift)"
|
|
3174
|
+
)
|
|
3175
|
+
return issues
|
|
3176
|
+
|
|
3177
|
+
|
|
3178
|
+
def _scannable_neighbor_paths(written_path: Path) -> list[Path]:
|
|
3179
|
+
try:
|
|
3180
|
+
all_neighbor_paths = sorted(
|
|
3181
|
+
written_path.parent.glob("*" + PYTHON_MODULE_FILE_SUFFIX)
|
|
3182
|
+
)
|
|
3183
|
+
except OSError:
|
|
3184
|
+
return []
|
|
3185
|
+
scannable_paths: list[Path] = []
|
|
3186
|
+
for each_path in all_neighbor_paths:
|
|
3187
|
+
if each_path.name == written_path.name:
|
|
3188
|
+
continue
|
|
3189
|
+
if is_strict_test_file(str(each_path)):
|
|
3190
|
+
continue
|
|
3191
|
+
scannable_paths.append(each_path)
|
|
3192
|
+
if len(scannable_paths) >= NEIGHBOR_SCAN_FILE_LIMIT:
|
|
3193
|
+
break
|
|
3194
|
+
return scannable_paths
|
|
3195
|
+
|
|
3196
|
+
|
|
3197
|
+
def _inbound_pointer_issues(
|
|
3198
|
+
neighbor_tree: ast.Module,
|
|
3199
|
+
neighbor_name: str,
|
|
3200
|
+
delegate_stem: str,
|
|
3201
|
+
all_entries_by_name: dict[str, tuple[int, str]],
|
|
3202
|
+
) -> list[str]:
|
|
3203
|
+
issues: list[str] = []
|
|
3204
|
+
for each_node in _top_level_definitions_and_methods(neighbor_tree):
|
|
3205
|
+
wrapper_docstring = ast.get_docstring(each_node) or ""
|
|
3206
|
+
wrapper_summary = _leading_summary_line(wrapper_docstring)
|
|
3207
|
+
if _pointer_target_stem(wrapper_summary) != delegate_stem:
|
|
3208
|
+
continue
|
|
3209
|
+
delegate_entry = all_entries_by_name.get(each_node.name)
|
|
3210
|
+
if delegate_entry is None:
|
|
3211
|
+
continue
|
|
3212
|
+
delegate_summary = _leading_summary_line(delegate_entry[1])
|
|
3213
|
+
for each_entry in _absent_entries(wrapper_summary, delegate_summary):
|
|
3214
|
+
issues.append(
|
|
3215
|
+
f"Line {delegate_entry[0]}: {each_node.name}() summary omits "
|
|
3216
|
+
f"'{each_entry}', named by the pointing wrapper docstring in "
|
|
3217
|
+
f"{neighbor_name} - reword that wrapper docstring in the same "
|
|
3218
|
+
"change (Category O6 docstring-vs-implementation drift)"
|
|
3219
|
+
)
|
|
3220
|
+
return issues
|
|
3221
|
+
|
|
3222
|
+
|
|
3223
|
+
def _neighbor_scan_issues(parsed_tree: ast.Module, file_path: str) -> list[str]:
|
|
3224
|
+
written_path = Path(file_path)
|
|
3225
|
+
all_entries_by_name = _entries_by_function_name(parsed_tree)
|
|
3226
|
+
if not all_entries_by_name:
|
|
3227
|
+
return []
|
|
3228
|
+
issues: list[str] = []
|
|
3229
|
+
for each_neighbor in _scannable_neighbor_paths(written_path):
|
|
3230
|
+
neighbor_source = _source_text_or_empty(each_neighbor)
|
|
3231
|
+
if POINTER_TO_DELEGATE_PATTERN.search(neighbor_source) is None:
|
|
3232
|
+
continue
|
|
3233
|
+
neighbor_tree = _parsed_module_or_none(neighbor_source)
|
|
3234
|
+
if neighbor_tree is None:
|
|
3235
|
+
continue
|
|
3236
|
+
issues.extend(
|
|
3237
|
+
_inbound_pointer_issues(
|
|
3238
|
+
neighbor_tree,
|
|
3239
|
+
each_neighbor.name,
|
|
3240
|
+
written_path.stem,
|
|
3241
|
+
all_entries_by_name,
|
|
3242
|
+
)
|
|
3243
|
+
)
|
|
3244
|
+
return issues
|
|
3245
|
+
|
|
3246
|
+
|
|
3247
|
+
def check_docstring_delegation_summary_enumeration_drift(
|
|
3248
|
+
content: str, file_path: str
|
|
3249
|
+
) -> list[str]:
|
|
3250
|
+
"""Flag a delegating wrapper summary that its delegation target contradicts.
|
|
3251
|
+
|
|
3252
|
+
The drift this catches: a thin wrapper method whose docstring summary
|
|
3253
|
+
enumerates the actions it stands for and points at the home of the real
|
|
3254
|
+
body ("Apply App Info, Russia, review note, publication edits; full doc
|
|
3255
|
+
on `listing_edit_flow`"), while the same-named function in that named
|
|
3256
|
+
sibling file carries a summary enumeration that omits one of those
|
|
3257
|
+
actions. An edit that moves one action out of the delegated body and
|
|
3258
|
+
rewords only the delegated docstring leaves the wrapper claiming work the
|
|
3259
|
+
body skips. The gate fires from both sides. On a wrapper save, it resolves
|
|
3260
|
+
the named sibling file beside the saved one, finds the same-named
|
|
3261
|
+
function, and compares the two summary enumerations. On a delegated-body
|
|
3262
|
+
save, it scans neighboring .py files for a wrapper docstring pointing at
|
|
3263
|
+
the saved stem and runs the same comparison, so the save that removes the
|
|
3264
|
+
action hears about the stranded wrapper docstring. A listed action counts
|
|
3265
|
+
as named when any of its distinctive words appears at a word boundary in
|
|
3266
|
+
the delegated summary. The comparison binds when the wrapper enumerates
|
|
3267
|
+
two or more actions and the delegated summary itself holds a comma, so a
|
|
3268
|
+
plain one-purpose delegated summary never gets compared. This covers the
|
|
3269
|
+
deterministic delegating-wrapper slice of Category O6
|
|
3270
|
+
docstring-prose-vs-implementation drift.
|
|
3271
|
+
|
|
3272
|
+
Args:
|
|
3273
|
+
content: The source text to inspect.
|
|
3274
|
+
file_path: The path the source will be written to, used for exemptions
|
|
3275
|
+
and to resolve the delegated or wrapping file beside it.
|
|
3276
|
+
|
|
3277
|
+
Returns:
|
|
3278
|
+
One issue per listed wrapper action the paired delegated summary
|
|
3279
|
+
omits, capped at the module limit.
|
|
3280
|
+
"""
|
|
3281
|
+
if is_strict_test_file(file_path) or is_hook_infrastructure(file_path):
|
|
3282
|
+
return []
|
|
3283
|
+
try:
|
|
3284
|
+
parsed_tree = ast.parse(content)
|
|
3285
|
+
except SyntaxError:
|
|
3286
|
+
return []
|
|
3287
|
+
issues = _outbound_pointer_issues(parsed_tree, file_path)
|
|
3288
|
+
issues.extend(_neighbor_scan_issues(parsed_tree, file_path))
|
|
3289
|
+
return issues[:MAX_DOCSTRING_DELEGATION_ENUMERATION_ISSUES]
|
|
@@ -51,6 +51,7 @@ from code_rules_comments import ( # noqa: E402
|
|
|
51
51
|
check_comment_changes,
|
|
52
52
|
)
|
|
53
53
|
from code_rules_constants_config import ( # noqa: E402
|
|
54
|
+
check_config_duplicate_path_anchor,
|
|
54
55
|
check_constants_outside_config,
|
|
55
56
|
check_constants_outside_config_advisory,
|
|
56
57
|
check_file_global_constants_use_count,
|
|
@@ -75,6 +76,7 @@ from code_rules_docstrings import ( # noqa: E402
|
|
|
75
76
|
check_docstring_args_match_signature,
|
|
76
77
|
check_docstring_args_single_line_scope_vs_span,
|
|
77
78
|
check_docstring_cardinal_count_matches_constant_family,
|
|
79
|
+
check_docstring_delegation_summary_enumeration_drift,
|
|
78
80
|
check_docstring_documents_unreferenced_parameter,
|
|
79
81
|
check_docstring_fallback_branch_coverage,
|
|
80
82
|
check_docstring_field_runmode_outcome,
|
|
@@ -85,6 +87,7 @@ from code_rules_docstrings import ( # noqa: E402
|
|
|
85
87
|
check_docstring_no_consumer_claim,
|
|
86
88
|
check_docstring_no_inline_literal_claim,
|
|
87
89
|
check_docstring_no_network_claim_with_metadata_access,
|
|
90
|
+
check_docstring_prose_wall_without_illustration,
|
|
88
91
|
check_docstring_punctuation_mark_enumeration_coverage,
|
|
89
92
|
check_docstring_raises_unraisable_largezipfile,
|
|
90
93
|
check_docstring_returns_plural_cardinality,
|
|
@@ -105,11 +108,15 @@ from code_rules_imports_logging import ( # noqa: E402
|
|
|
105
108
|
check_e2e_test_naming,
|
|
106
109
|
check_import_block_sorted,
|
|
107
110
|
check_imports_at_top,
|
|
111
|
+
check_js_bare_flag_return_directive,
|
|
108
112
|
check_js_resume_task_enumeration_coverage,
|
|
109
113
|
check_js_returns_object_schemaless_branch,
|
|
114
|
+
check_js_sibling_return_object_key_drift,
|
|
110
115
|
check_library_print,
|
|
116
|
+
check_logging_adjacent_string_literals,
|
|
111
117
|
check_logging_fstrings,
|
|
112
118
|
check_logging_printf_tokens,
|
|
119
|
+
check_naive_datetime_construction,
|
|
113
120
|
check_windows_api_none,
|
|
114
121
|
)
|
|
115
122
|
from code_rules_js_conventions import ( # noqa: E402
|
|
@@ -127,6 +134,7 @@ from code_rules_naming_collection import ( # noqa: E402
|
|
|
127
134
|
check_collection_prefix,
|
|
128
135
|
check_loop_variable_naming,
|
|
129
136
|
check_polarity_name_contradiction,
|
|
137
|
+
check_referenced_underscore_loop_variable,
|
|
130
138
|
check_stuttering_collection_prefix,
|
|
131
139
|
)
|
|
132
140
|
from code_rules_optional_params import ( # noqa: E402
|
|
@@ -154,6 +162,7 @@ from code_rules_shared import ( # noqa: E402
|
|
|
154
162
|
from code_rules_string_magic import ( # noqa: E402
|
|
155
163
|
check_inline_literal_collections,
|
|
156
164
|
check_inline_tuple_string_magic,
|
|
165
|
+
check_join_separator_string_magic,
|
|
157
166
|
check_string_literal_magic,
|
|
158
167
|
check_whitespace_indentation_magic,
|
|
159
168
|
)
|
|
@@ -268,10 +277,13 @@ def validate_content(
|
|
|
268
277
|
)
|
|
269
278
|
all_issues.extend(check_logging_fstrings(content))
|
|
270
279
|
all_issues.extend(check_logging_printf_tokens(content, file_path))
|
|
280
|
+
all_issues.extend(check_logging_adjacent_string_literals(content, file_path))
|
|
271
281
|
all_issues.extend(check_windows_api_none(content))
|
|
282
|
+
all_issues.extend(check_naive_datetime_construction(content, file_path))
|
|
272
283
|
all_issues.extend(check_magic_values(content, file_path))
|
|
273
284
|
all_issues.extend(check_fstring_structural_literals(content, file_path))
|
|
274
285
|
all_issues.extend(check_constants_outside_config(content, file_path))
|
|
286
|
+
all_issues.extend(check_config_duplicate_path_anchor(content, file_path))
|
|
275
287
|
all_issues.extend(check_constants_outside_config_advisory(content, file_path))
|
|
276
288
|
all_issues.extend(check_file_global_constants_use_count(content, file_path))
|
|
277
289
|
all_issues.extend(
|
|
@@ -340,6 +352,9 @@ def validate_content(
|
|
|
340
352
|
check_class_docstring_names_public_methods(effective_content, file_path)
|
|
341
353
|
)
|
|
342
354
|
all_issues.extend(check_docstring_runon_sentence(effective_content, file_path))
|
|
355
|
+
all_issues.extend(
|
|
356
|
+
check_docstring_prose_wall_without_illustration(effective_content, file_path)
|
|
357
|
+
)
|
|
343
358
|
all_issues.extend(
|
|
344
359
|
check_module_docstring_names_public_checks(effective_content, file_path)
|
|
345
360
|
)
|
|
@@ -389,6 +404,11 @@ def validate_content(
|
|
|
389
404
|
all_issues.extend(
|
|
390
405
|
check_docstring_args_single_line_scope_vs_span(effective_content, file_path)
|
|
391
406
|
)
|
|
407
|
+
all_issues.extend(
|
|
408
|
+
check_docstring_delegation_summary_enumeration_drift(
|
|
409
|
+
effective_content, file_path
|
|
410
|
+
)
|
|
411
|
+
)
|
|
392
412
|
all_issues.extend(
|
|
393
413
|
check_boolean_naming(
|
|
394
414
|
effective_content,
|
|
@@ -477,9 +497,11 @@ def validate_content(
|
|
|
477
497
|
)
|
|
478
498
|
)
|
|
479
499
|
all_issues.extend(check_loop_variable_naming(content, file_path))
|
|
500
|
+
all_issues.extend(check_referenced_underscore_loop_variable(content, file_path))
|
|
480
501
|
all_issues.extend(check_polarity_name_contradiction(content, file_path))
|
|
481
502
|
all_issues.extend(check_inline_literal_collections(content, file_path))
|
|
482
503
|
all_issues.extend(check_inline_tuple_string_magic(content, file_path))
|
|
504
|
+
all_issues.extend(check_join_separator_string_magic(content, file_path))
|
|
483
505
|
all_issues.extend(check_string_literal_magic(content, file_path))
|
|
484
506
|
all_issues.extend(check_whitespace_indentation_magic(content, file_path))
|
|
485
507
|
all_issues.extend(check_orphan_css_classes(effective_content, file_path))
|
|
@@ -513,6 +535,17 @@ def validate_content(
|
|
|
513
535
|
all_issues.extend(
|
|
514
536
|
check_js_returns_object_schemaless_branch(content, file_path)
|
|
515
537
|
)
|
|
538
|
+
all_issues.extend(
|
|
539
|
+
check_js_sibling_return_object_key_drift(content, file_path)
|
|
540
|
+
)
|
|
541
|
+
all_issues.extend(
|
|
542
|
+
check_js_bare_flag_return_directive(
|
|
543
|
+
effective_content,
|
|
544
|
+
file_path,
|
|
545
|
+
all_changed_lines,
|
|
546
|
+
defer_scope_to_caller,
|
|
547
|
+
)
|
|
548
|
+
)
|
|
516
549
|
|
|
517
550
|
if extension in ALL_CODE_EXTENSIONS:
|
|
518
551
|
advise_file_line_count(content, file_path)
|