claude-dev-env 2.9.0 → 2.11.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 +2 -2
- package/_shared/advisor/CLAUDE.md +3 -2
- package/_shared/advisor/advisor-protocol.md +74 -108
- package/_shared/advisor/reference/advisor-block.md +37 -0
- package/_shared/advisor/reference/cli-chain.md +45 -0
- package/_shared/advisor/reference/consult-format.md +41 -0
- package/_shared/advisor/reference/lifecycle.md +21 -0
- package/_shared/advisor/reference/sol-rung.md +31 -0
- package/_shared/advisor/reference/spawn-walk-log.md +31 -0
- package/_shared/advisor/reference/third-party-bind.md +30 -0
- package/_shared/advisor/reference/warm-up.md +33 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +449 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +28 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +474 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
- package/_shared/advisor/scripts/tier_model_ids.py +24 -0
- package/_shared/pr-loop/CLAUDE.md +1 -1
- package/_shared/pr-loop/audit-contract.md +17 -6
- package/_shared/pr-loop/audit-reply-template.md +4 -4
- package/_shared/pr-loop/code-rules-gate.md +3 -5
- package/_shared/pr-loop/fix-protocol.md +2 -3
- package/_shared/pr-loop/gh-payloads.md +1 -1
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/README.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
- package/_shared/pr-loop/scripts/preflight.py +9 -4
- package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
- package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
- package/_shared/pr-loop/state-schema.md +5 -14
- package/agents/CLAUDE.md +2 -2
- package/agents/clean-coder.md +58 -548
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +32 -40
- package/audit-rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/audit-categories.json +704 -0
- package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
- package/bin/CLAUDE.md +16 -5
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install-plan.mjs +402 -0
- package/bin/install-transaction.mjs +455 -0
- package/bin/install.mjs +593 -147
- package/bin/install.plan.test.mjs +194 -0
- package/bin/install.profile-root.test.mjs +154 -0
- package/bin/install.profiles.test.mjs +253 -0
- package/bin/install.settings-defaults.test.mjs +200 -0
- package/bin/install.transaction.test.mjs +400 -0
- package/bin/install.uninstall-transaction.test.mjs +418 -0
- package/bin/merge_managed_permissions.mjs +130 -0
- package/bin/resolve-install-root.mjs +181 -0
- package/bin/select-install-targets.mjs +401 -0
- package/commands/CLAUDE.md +0 -2
- package/docs/references/CLAUDE.md +3 -1
- package/docs/references/advisor-tool.md +25 -7
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +3 -3
- package/docs/references/weak-executor-advisor.md +91 -0
- package/hooks/blocking/CLAUDE.md +6 -6
- package/hooks/blocking/_path_setup.py +9 -5
- package/hooks/blocking/code_rules_docstrings.py +124 -30
- package/hooks/blocking/code_rules_enforcer.py +161 -16
- package/hooks/blocking/code_rules_shared.py +40 -23
- package/hooks/blocking/config/CLAUDE.md +3 -5
- package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
- package/hooks/blocking/eli11_reply_enforcer.py +70 -113
- package/hooks/blocking/hedging_language_blocker.py +103 -20
- package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
- package/hooks/blocking/intent_only_ending_blocker.py +6 -0
- package/hooks/blocking/plain_language_blocker.py +139 -20
- package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
- package/hooks/blocking/state_description_blocker.py +7 -1
- package/hooks/blocking/tdd_enforcer.py +8 -0
- package/hooks/blocking/test__path_setup.py +28 -0
- package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
- package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
- package/hooks/blocking/test_fable_spawn_gate.py +18 -11
- package/hooks/blocking/test_hedging_language_blocker.py +120 -1
- package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
- package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
- package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
- package/hooks/blocking/test_plain_language_blocker.py +129 -19
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
- package/hooks/blocking/test_state_description_blocker.py +45 -2
- package/hooks/blocking/test_stop_dispatcher.py +11 -7
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
- package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
- package/hooks/hooks.json +15 -0
- package/hooks/hooks_constants/CLAUDE.md +14 -3
- package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
- package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
- package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
- package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/project_paths_reader.py +31 -4
- package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
- package/hooks/hooks_constants/pytest_invocation.py +354 -0
- package/hooks/hooks_constants/session_start_injector.py +163 -0
- package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
- package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
- package/hooks/hooks_constants/shell_command_segments.py +5 -0
- package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
- package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
- package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
- package/hooks/hooks_constants/test_session_start_injector.py +168 -0
- package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
- package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
- package/hooks/observability/CLAUDE.md +2 -0
- package/hooks/observability/prose_matcher_advisory.py +237 -0
- package/hooks/observability/test_prose_matcher_advisory.py +143 -0
- package/hooks/session/CLAUDE.md +9 -1
- package/hooks/session/_path_setup.py +13 -0
- package/hooks/session/issue_tracker_session_starter.py +135 -0
- package/hooks/session/orchestrator_auto_starter.py +100 -0
- package/hooks/session/test__path_setup.py +28 -0
- package/hooks/session/test_issue_tracker_session_starter.py +104 -0
- package/hooks/session/test_orchestrator_auto_starter.py +99 -0
- package/hooks/session/test_working_style_prompt.py +47 -0
- package/hooks/session/untracked_repo_detector.py +1 -24
- package/hooks/session/working_style_prompt.py +36 -0
- package/hooks/validators/_path_setup.py +19 -0
- package/hooks/validators/run_all_validators.py +8 -13
- package/installable-surfaces.manifest.json +21 -0
- package/output-styles/CLAUDE.md +1 -3
- package/package.json +4 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +6 -1
- package/rules/hedging-claims.md +4 -2
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/opus5-communication-contract.md +45 -0
- package/rules/plain-language.md +2 -2
- package/rules/research-mode.md +1 -1
- package/scripts/CLAUDE.md +11 -0
- package/scripts/Sync-RepoMain.ps1 +215 -0
- package/scripts/active_capability_references.py +218 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
- package/scripts/claude_chain_runner.py +394 -6
- package/scripts/claude_chain_usage.py +1 -1
- package/scripts/codex_compat_materializer.py +105 -85
- package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
- package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
- package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
- package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
- package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
- package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
- package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
- package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
- package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
- package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
- package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
- package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
- package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
- package/scripts/spawn_grok_batch.py +3 -0
- package/scripts/test_active_capability_references.py +108 -0
- package/scripts/test_claude_chain_runner.py +414 -82
- package/scripts/test_claude_chain_usage.py +12 -12
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_verify_installable_package.py +208 -0
- package/scripts/tests/test_codex_compat_materializer.py +33 -0
- package/scripts/verify_installable_package.py +612 -0
- package/settings.json +10 -0
- package/skills/CLAUDE.md +2 -0
- package/skills/_shared/advisor/CLAUDE.md +1 -1
- package/skills/_shared/advisor/scripts/README.md +2 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
- package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/stop-conditions.md +5 -3
- package/skills/beat-sheet/SKILL.md +54 -0
- package/skills/beat-sheet/reference/visual-beats.md +29 -0
- package/skills/bugteam/CONSTRAINTS.md +4 -4
- package/skills/bugteam/EXAMPLES.md +1 -1
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/e-code-review/SKILL.md +26 -5
- package/skills/e-code-review/reference/effort-evaluation.md +35 -0
- package/skills/e-code-review/reference/medium.md +15 -4
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
- package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
- package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
- package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
- package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
- package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
- package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
- package/skills/e-code-review/scripts/grok_code_review.py +16 -7
- package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
- package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
- package/skills/grokify/SKILL.md +1 -1
- package/skills/grokify/templates/handoff-template.md +2 -2
- package/skills/orchestrator/SKILL.md +5 -4
- package/skills/plan-to-pr/scripts/create_packet.py +4 -4
- package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
- package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
- package/skills/plan-to-pr/scripts/validate_run.py +4 -1
- package/skills/pr-converge/scripts/check_convergence.py +21 -19
- package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
- package/skills/pr-converge/scripts/conftest.py +35 -0
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/rebase/SKILL.md +15 -3
- package/skills/reviewer-gates/SKILL.md +2 -2
- package/skills/show/SKILL.md +51 -0
- package/skills/show/references/accessibility.md +7 -0
- package/skills/show/references/art.md +3 -0
- package/skills/show/references/charts.md +3 -0
- package/skills/show/references/core-design.md +14 -0
- package/skills/show/references/erds.md +3 -0
- package/skills/show/references/flowcharts.md +3 -0
- package/skills/show/references/host-and-html.md +3 -0
- package/skills/show/references/illustrative-diagrams.md +10 -0
- package/skills/show/references/interaction.md +3 -0
- package/skills/show/references/mockups.md +3 -0
- package/skills/show/references/quality-gates.md +7 -0
- package/skills/show/references/structural-diagrams.md +3 -0
- package/skills/show/references/subject-inventory.md +21 -0
- package/skills/show/references/svg-contract.md +22 -0
- package/skills/show/routing.yaml +30 -0
- package/skills/show/samples/pr1262-v2.svg +222 -0
- package/skills/show/scripts/README.md +6 -0
- package/skills/show/scripts/validate-artifact.py +91 -0
- package/skills/show/scripts/validate-package.py +18 -0
- package/skills/show/templates/html-widget.html +4 -0
- package/skills/show/templates/svg-base.svg +19 -0
- package/skills/show/tests/fixtures/css-var.svg +6 -0
- package/skills/show/tests/fixtures/dead-ref.svg +7 -0
- package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
- package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
- package/skills/show/tests/fixtures/invalid.svg +1 -0
- package/skills/show/tests/fixtures/large-canvas.svg +21 -0
- package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
- package/skills/show/tests/fixtures/valid.html +1 -0
- package/skills/show/tests/test_validate-artifact.py +74 -0
- package/skills/show/tests/test_validators.py +59 -0
- package/skills/show/workflows/create-visual.md +13 -0
- package/skills/show/workflows/review-visual.md +20 -0
- package/skills/split-pr/SKILL.md +85 -0
- package/skills/split-pr/reference/path-layers.md +16 -0
- package/skills/split-pr/reference/proposal-format.md +15 -0
- package/skills/split-pr/reference/split-further-loop.md +10 -0
- package/skills/split-pr/reference/splitting-principles.md +26 -0
- package/skills/split-pr/scripts/analyze_pr.py +279 -0
- package/skills/split-pr/scripts/categorize_files.py +106 -0
- package/skills/split-pr/scripts/config/__init__.py +1 -0
- package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
- package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
- package/skills/split-pr/scripts/config/packing_constants.py +61 -0
- package/skills/split-pr/scripts/config/plan_constants.py +49 -0
- package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
- package/skills/split-pr/scripts/execute_split_slices.py +82 -0
- package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
- package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
- package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
- package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
- package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
- package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
- package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
- package/skills/split-pr/scripts/split_pr_title.py +41 -0
- package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
- package/skills/split-pr/scripts/test_categorize_files.py +55 -0
- package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
- package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
- package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
- package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
- package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
- package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
- package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
- package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
- package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
- package/skills/split-pr/scripts/test_verify_plan.py +56 -0
- package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
- package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
- package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
- package/skills/split-pr/scripts/verify_plan.py +139 -0
- package/skills/team-advisor/SKILL.md +7 -4
- package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
- package/system-prompts/software-engineer.xml +11 -2
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/output-styles/caveman-agent.md +0 -37
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
"""Span-scope tests for docstring checks.
|
|
2
|
+
|
|
3
|
+
Covers two surfaces:
|
|
4
|
+
|
|
5
|
+
1. ``check_docstring_args_single_line_scope_vs_span`` (Category O6) — an Args
|
|
6
|
+
entry that claims single-line (block-anchor) scope while the body builds a
|
|
7
|
+
multi-line span and routes it through span intersection.
|
|
8
|
+
|
|
9
|
+
2. Changed-span grading for ``check_docstring_runon_sentence`` and
|
|
10
|
+
``check_docstring_prose_wall_without_illustration`` (issue #237) — far-away
|
|
11
|
+
no-op grandfathering, introduced-violation blocking, and
|
|
12
|
+
``defer_scope_to_caller`` pass-through.
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
15
|
from __future__ import annotations
|
|
@@ -201,3 +203,223 @@ def test_hook_infrastructure_is_not_exempt() -> None:
|
|
|
201
203
|
assert (
|
|
202
204
|
len(check_docstring_args_single_line_scope_vs_span(content, HOOK_INFRASTRUCTURE_PATH)) == 1
|
|
203
205
|
)
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Changed-span grading for the two whole-file docstring checks (issue #237 / P-41)
|
|
209
|
+
# check_docstring_runon_sentence and check_docstring_prose_wall_without_illustration
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def check_docstring_runon_sentence(
|
|
214
|
+
content: str,
|
|
215
|
+
file_path: str,
|
|
216
|
+
all_changed_lines: set[int] | None = None,
|
|
217
|
+
defer_scope_to_caller: bool = False,
|
|
218
|
+
) -> list[str]:
|
|
219
|
+
return code_rules_enforcer.check_docstring_runon_sentence(
|
|
220
|
+
content, file_path, all_changed_lines, defer_scope_to_caller
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def check_docstring_prose_wall_without_illustration(
|
|
225
|
+
content: str,
|
|
226
|
+
file_path: str,
|
|
227
|
+
all_changed_lines: set[int] | None = None,
|
|
228
|
+
defer_scope_to_caller: bool = False,
|
|
229
|
+
) -> list[str]:
|
|
230
|
+
return code_rules_enforcer.check_docstring_prose_wall_without_illustration(
|
|
231
|
+
content, file_path, all_changed_lines, defer_scope_to_caller
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
_CLEAN_HELPER_TAIL = (
|
|
236
|
+
"\n"
|
|
237
|
+
"def clean_helper() -> str:\n"
|
|
238
|
+
' """Return a short status token for the board.\n'
|
|
239
|
+
"\n"
|
|
240
|
+
" Each call names one vessel and its final port.\n"
|
|
241
|
+
' """\n'
|
|
242
|
+
' return "ok"\n'
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _module_with_far_away_runon_and_clean_helper() -> str:
|
|
247
|
+
return (
|
|
248
|
+
'"""Owns the SIGINT install/restore/installability check, the atexit terminal-record\n'
|
|
249
|
+
"registration, and the interrupted-run finalizer — the non-promoter-specific\n"
|
|
250
|
+
"machinery that brackets a run so the JSONL artifact always carries a terminal\n"
|
|
251
|
+
"record and an in-flight theme record on interrupt.\n"
|
|
252
|
+
'"""\n'
|
|
253
|
+
+ _CLEAN_HELPER_TAIL
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _module_with_far_away_prose_wall_and_clean_helper() -> str:
|
|
258
|
+
return (
|
|
259
|
+
'"""Assemble the nightly voyage tally from the harbor scans.\n'
|
|
260
|
+
"\n"
|
|
261
|
+
"A scan names one vessel and where it dropped anchor.\n"
|
|
262
|
+
"The tally walks the scans in arrival order and keeps that order.\n"
|
|
263
|
+
"A calm voyage ends well for every vessel it carried.\n"
|
|
264
|
+
"A halted voyage marks the vessel it was near when the storm arrived.\n"
|
|
265
|
+
"A wrecked voyage marks the vessel that sank and stops the walk there.\n"
|
|
266
|
+
"The tally groups the vessels by their final port for the harbor.\n"
|
|
267
|
+
"The harbor reads the tally and sees every arrival at a glance.\n"
|
|
268
|
+
'"""\n'
|
|
269
|
+
+ _CLEAN_HELPER_TAIL
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_runon_far_away_no_op_is_grandfathered() -> None:
|
|
274
|
+
content = _module_with_far_away_runon_and_clean_helper()
|
|
275
|
+
clean_helper_line = content.splitlines().index("def clean_helper() -> str:") + 1
|
|
276
|
+
all_changed_lines = set(range(clean_helper_line, clean_helper_line + 6))
|
|
277
|
+
unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
|
|
278
|
+
assert any("run-on" in each for each in unscoped), (
|
|
279
|
+
f"control: unscoped must still see the far-away run-on, got {unscoped!r}"
|
|
280
|
+
)
|
|
281
|
+
scoped = check_docstring_runon_sentence(
|
|
282
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines=all_changed_lines
|
|
283
|
+
)
|
|
284
|
+
assert scoped == [], (
|
|
285
|
+
f"far-away run-on must be grandfathered on a clean-helper edit, got {scoped!r}"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_runon_introduced_violation_blocks() -> None:
|
|
290
|
+
content = _module_with_far_away_runon_and_clean_helper()
|
|
291
|
+
unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
|
|
292
|
+
assert any("run-on" in each for each in unscoped)
|
|
293
|
+
scoped = check_docstring_runon_sentence(
|
|
294
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines={1}
|
|
295
|
+
)
|
|
296
|
+
assert any("run-on" in each for each in scoped), (
|
|
297
|
+
f"introduced/touched run-on must block, got {scoped!r}"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_prose_wall_far_away_no_op_is_grandfathered() -> None:
|
|
302
|
+
content = _module_with_far_away_prose_wall_and_clean_helper()
|
|
303
|
+
clean_helper_line = content.splitlines().index("def clean_helper() -> str:") + 1
|
|
304
|
+
all_changed_lines = set(range(clean_helper_line, clean_helper_line + 6))
|
|
305
|
+
unscoped = check_docstring_prose_wall_without_illustration(
|
|
306
|
+
content, PRODUCTION_FILE_PATH
|
|
307
|
+
)
|
|
308
|
+
assert any("worked example" in each for each in unscoped), (
|
|
309
|
+
f"control: unscoped must still see the far-away wall, got {unscoped!r}"
|
|
310
|
+
)
|
|
311
|
+
scoped = check_docstring_prose_wall_without_illustration(
|
|
312
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines=all_changed_lines
|
|
313
|
+
)
|
|
314
|
+
assert scoped == [], (
|
|
315
|
+
f"far-away wall must be grandfathered on a clean-helper edit, got {scoped!r}"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_prose_wall_introduced_violation_blocks() -> None:
|
|
320
|
+
content = _module_with_far_away_prose_wall_and_clean_helper()
|
|
321
|
+
unscoped = check_docstring_prose_wall_without_illustration(
|
|
322
|
+
content, PRODUCTION_FILE_PATH
|
|
323
|
+
)
|
|
324
|
+
assert any("worked example" in each for each in unscoped)
|
|
325
|
+
scoped = check_docstring_prose_wall_without_illustration(
|
|
326
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines={1}
|
|
327
|
+
)
|
|
328
|
+
assert any("worked example" in each for each in scoped), (
|
|
329
|
+
f"introduced/touched wall must block, got {scoped!r}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_runon_defer_scope_returns_all_violations() -> None:
|
|
334
|
+
content = _module_with_far_away_runon_and_clean_helper()
|
|
335
|
+
deferred = check_docstring_runon_sentence(
|
|
336
|
+
content,
|
|
337
|
+
PRODUCTION_FILE_PATH,
|
|
338
|
+
all_changed_lines={999},
|
|
339
|
+
defer_scope_to_caller=True,
|
|
340
|
+
)
|
|
341
|
+
assert any("run-on" in each for each in deferred)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _function_with_runon_docstring() -> str:
|
|
345
|
+
return (
|
|
346
|
+
"def clean_helper() -> str:\n"
|
|
347
|
+
' """Owns the SIGINT install/restore/installability check, the atexit terminal-record\n'
|
|
348
|
+
" registration, and the interrupted-run finalizer — the non-promoter-specific\n"
|
|
349
|
+
" machinery that brackets a run so the JSONL artifact always carries a terminal\n"
|
|
350
|
+
" record and an in-flight theme record on interrupt.\n"
|
|
351
|
+
' """\n'
|
|
352
|
+
' return "ok"\n'
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_runon_docstring_body_edit_blocks_without_def_line() -> None:
|
|
357
|
+
content = _function_with_runon_docstring()
|
|
358
|
+
unscoped = check_docstring_runon_sentence(content, PRODUCTION_FILE_PATH)
|
|
359
|
+
assert any("run-on" in each for each in unscoped), (
|
|
360
|
+
f"control: unscoped must see the function run-on, got {unscoped!r}"
|
|
361
|
+
)
|
|
362
|
+
docstring_body_lines = {2, 3, 4, 5}
|
|
363
|
+
scoped = check_docstring_runon_sentence(
|
|
364
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines=docstring_body_lines
|
|
365
|
+
)
|
|
366
|
+
assert any("run-on" in each for each in scoped), (
|
|
367
|
+
f"docstring-body edit must block even when the def line is untouched, got {scoped!r}"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_runon_post_docstring_body_edit_is_grandfathered() -> None:
|
|
372
|
+
content = _function_with_runon_docstring()
|
|
373
|
+
return_line = content.splitlines().index(' return "ok"') + 1
|
|
374
|
+
scoped = check_docstring_runon_sentence(
|
|
375
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines={return_line}
|
|
376
|
+
)
|
|
377
|
+
assert scoped == [], (
|
|
378
|
+
f"post-docstring body edit must grandfather the pre-existing run-on, got {scoped!r}"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _function_with_prose_wall_docstring() -> str:
|
|
383
|
+
return (
|
|
384
|
+
"def clean_helper() -> str:\n"
|
|
385
|
+
' """Assemble the nightly voyage tally from the harbor scans.\n'
|
|
386
|
+
"\n"
|
|
387
|
+
" A scan names one vessel and where it dropped anchor.\n"
|
|
388
|
+
" The tally walks the scans in arrival order and keeps that order.\n"
|
|
389
|
+
" A calm voyage ends well for every vessel it carried.\n"
|
|
390
|
+
" A halted voyage marks the vessel it was near when the storm arrived.\n"
|
|
391
|
+
" A wrecked voyage marks the vessel that sank and stops the walk there.\n"
|
|
392
|
+
" The tally groups the vessels by their final port for the harbor.\n"
|
|
393
|
+
" The harbor reads the tally and sees every arrival at a glance.\n"
|
|
394
|
+
' """\n'
|
|
395
|
+
' return "ok"\n'
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_prose_wall_docstring_body_edit_blocks_without_def_line() -> None:
|
|
400
|
+
content = _function_with_prose_wall_docstring()
|
|
401
|
+
unscoped = check_docstring_prose_wall_without_illustration(
|
|
402
|
+
content, PRODUCTION_FILE_PATH
|
|
403
|
+
)
|
|
404
|
+
assert any("worked example" in each for each in unscoped), (
|
|
405
|
+
f"control: unscoped must see the function wall, got {unscoped!r}"
|
|
406
|
+
)
|
|
407
|
+
docstring_body_lines = set(range(2, 12))
|
|
408
|
+
scoped = check_docstring_prose_wall_without_illustration(
|
|
409
|
+
content, PRODUCTION_FILE_PATH, all_changed_lines=docstring_body_lines
|
|
410
|
+
)
|
|
411
|
+
assert any("worked example" in each for each in scoped), (
|
|
412
|
+
f"docstring-body edit must block even when the def line is untouched, got {scoped!r}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_prose_wall_defer_scope_returns_all_violations() -> None:
|
|
417
|
+
content = _module_with_far_away_prose_wall_and_clean_helper()
|
|
418
|
+
deferred = check_docstring_prose_wall_without_illustration(
|
|
419
|
+
content,
|
|
420
|
+
PRODUCTION_FILE_PATH,
|
|
421
|
+
all_changed_lines={999},
|
|
422
|
+
defer_scope_to_caller=True,
|
|
423
|
+
)
|
|
424
|
+
assert any("worked example" in each for each in deferred)
|
|
425
|
+
|
|
@@ -186,7 +186,7 @@ def test_should_return_false_for_pytest_tmp_path_when_job_dir_elsewhere(
|
|
|
186
186
|
temp directory was an ephemeral source: pytest tmp_path fixtures live
|
|
187
187
|
under tempfile.gettempdir(), so the enforcer's own test targets must not
|
|
188
188
|
classify as ephemeral. CLAUDE_JOB_DIR points elsewhere (its scratch dir
|
|
189
|
-
lives under .claude-
|
|
189
|
+
lives under .claude-profile-c/jobs, not %TEMP%).
|
|
190
190
|
"""
|
|
191
191
|
monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
|
|
192
192
|
monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path / "elsewhere"))
|
|
@@ -65,3 +65,44 @@ def test_should_report_join_separator_line_number() -> None:
|
|
|
65
65
|
assert any("Line 2" in each_issue for each_issue in issues), (
|
|
66
66
|
f"Expected line 2 reported, got: {issues}"
|
|
67
67
|
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_should_grandfather_unchanged_join_separator_in_wide_edit() -> None:
|
|
71
|
+
prior_fragment = (
|
|
72
|
+
"def render(all_paths: list) -> str:\n"
|
|
73
|
+
" return ', '.join(str(each_path) for each_path in all_paths)\n"
|
|
74
|
+
)
|
|
75
|
+
proposed_fragment = (
|
|
76
|
+
"def render(all_paths: list) -> str:\n"
|
|
77
|
+
" # keep the join separator the same\n"
|
|
78
|
+
" return ', '.join(str(each_path) for each_path in all_paths)\n"
|
|
79
|
+
)
|
|
80
|
+
issues = code_rules_enforcer.validate_content(
|
|
81
|
+
proposed_fragment,
|
|
82
|
+
PRODUCTION_FILE_PATH,
|
|
83
|
+
prior_fragment,
|
|
84
|
+
)
|
|
85
|
+
join_issues = [each for each in issues if "join" in each]
|
|
86
|
+
assert join_issues == [], (
|
|
87
|
+
f"unchanged enclosed join separator must be grandfathered, got: {join_issues}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_should_block_new_join_separator_in_wide_edit() -> None:
|
|
92
|
+
prior_fragment = (
|
|
93
|
+
"def render(all_paths: list) -> str:\n"
|
|
94
|
+
" return JOIN_DELIMITER.join(all_paths)\n"
|
|
95
|
+
)
|
|
96
|
+
proposed_fragment = (
|
|
97
|
+
"def render(all_paths: list) -> str:\n"
|
|
98
|
+
" return ', '.join(all_paths)\n"
|
|
99
|
+
)
|
|
100
|
+
issues = code_rules_enforcer.validate_content(
|
|
101
|
+
proposed_fragment,
|
|
102
|
+
PRODUCTION_FILE_PATH,
|
|
103
|
+
prior_fragment,
|
|
104
|
+
)
|
|
105
|
+
join_issues = [each for each in issues if "join" in each]
|
|
106
|
+
assert any("Line 2" in each_issue for each_issue in join_issues), (
|
|
107
|
+
f"introduced join separator must block at its new location, got: {join_issues}"
|
|
108
|
+
)
|
|
@@ -232,3 +232,101 @@ def test_should_still_flag_literal_in_nested_function_body() -> None:
|
|
|
232
232
|
assert len(issues) == 1, (
|
|
233
233
|
f"Inner literal must be flagged exactly once (no duplicate from outer walk), got: {issues}"
|
|
234
234
|
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_should_grandfather_unchanged_string_magic_in_wide_edit() -> None:
|
|
238
|
+
prior_fragment = (
|
|
239
|
+
"import os\n"
|
|
240
|
+
"\n"
|
|
241
|
+
"def fetch_secret() -> str:\n"
|
|
242
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
243
|
+
)
|
|
244
|
+
proposed_fragment = (
|
|
245
|
+
"import os\n"
|
|
246
|
+
"\n"
|
|
247
|
+
"def fetch_secret() -> str:\n"
|
|
248
|
+
" # wide edit that leaves the magic string in place\n"
|
|
249
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
250
|
+
)
|
|
251
|
+
issues = code_rules_enforcer.validate_content(
|
|
252
|
+
proposed_fragment,
|
|
253
|
+
PRODUCTION_FILE_PATH,
|
|
254
|
+
prior_fragment,
|
|
255
|
+
)
|
|
256
|
+
magic_issues = [each for each in issues if "STRIPE_SECRET" in each]
|
|
257
|
+
assert magic_issues == [], (
|
|
258
|
+
f"unchanged enclosed string magic must be grandfathered, got: {magic_issues}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_should_block_new_string_magic_in_wide_edit() -> None:
|
|
263
|
+
prior_fragment = (
|
|
264
|
+
"import os\n"
|
|
265
|
+
"\n"
|
|
266
|
+
"def fetch_secret() -> str:\n"
|
|
267
|
+
" return os.environ.get('missing', '')\n"
|
|
268
|
+
)
|
|
269
|
+
proposed_fragment = (
|
|
270
|
+
"import os\n"
|
|
271
|
+
"\n"
|
|
272
|
+
"def fetch_secret() -> str:\n"
|
|
273
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
274
|
+
)
|
|
275
|
+
issues = code_rules_enforcer.validate_content(
|
|
276
|
+
proposed_fragment,
|
|
277
|
+
PRODUCTION_FILE_PATH,
|
|
278
|
+
prior_fragment,
|
|
279
|
+
)
|
|
280
|
+
magic_issues = [each for each in issues if "STRIPE_SECRET" in each]
|
|
281
|
+
assert any("Line " in each_issue for each_issue in magic_issues), (
|
|
282
|
+
f"introduced string magic must block with a line locator, got: {magic_issues}"
|
|
283
|
+
)
|
|
284
|
+
assert any("STRIPE_SECRET" in each_issue for each_issue in magic_issues)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_should_block_second_identical_string_magic_per_occurrence() -> None:
|
|
288
|
+
prior_fragment = (
|
|
289
|
+
"import os\n"
|
|
290
|
+
"\n"
|
|
291
|
+
"def fetch_secret() -> str:\n"
|
|
292
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
293
|
+
)
|
|
294
|
+
proposed_fragment = (
|
|
295
|
+
"import os\n"
|
|
296
|
+
"\n"
|
|
297
|
+
"def fetch_secret() -> str:\n"
|
|
298
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
299
|
+
"\n"
|
|
300
|
+
"def fetch_backup() -> str:\n"
|
|
301
|
+
" return os.environ['STRIPE_SECRET']\n"
|
|
302
|
+
)
|
|
303
|
+
issues = code_rules_enforcer.validate_content(
|
|
304
|
+
proposed_fragment,
|
|
305
|
+
PRODUCTION_FILE_PATH,
|
|
306
|
+
prior_fragment,
|
|
307
|
+
)
|
|
308
|
+
magic_issues = [each for each in issues if "STRIPE_SECRET" in each]
|
|
309
|
+
assert len(magic_issues) == 1, (
|
|
310
|
+
f"exactly one new identical magic must block (per-occurrence), got: {magic_issues}"
|
|
311
|
+
)
|
|
312
|
+
assert any("Line 7" in each_issue for each_issue in magic_issues), (
|
|
313
|
+
f"second occurrence must keep proposed-fragment line, got: {magic_issues}"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_should_surface_magic_when_gate_defers_scope_with_head_as_old() -> None:
|
|
318
|
+
source = (
|
|
319
|
+
"def compute() -> int:\n"
|
|
320
|
+
" old_number = 9999\n"
|
|
321
|
+
" return old_number\n"
|
|
322
|
+
)
|
|
323
|
+
issues = code_rules_enforcer.validate_content(
|
|
324
|
+
source,
|
|
325
|
+
PRODUCTION_FILE_PATH,
|
|
326
|
+
source,
|
|
327
|
+
defer_scope_to_caller=True,
|
|
328
|
+
)
|
|
329
|
+
assert any("9999" in each_issue for each_issue in issues), (
|
|
330
|
+
f"gate path must still surface whole-file magic when old_content equals content, "
|
|
331
|
+
f"got: {issues}"
|
|
332
|
+
)
|