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
|
@@ -18,6 +18,7 @@ concern focused. The separate ``tdd_enforcer.py`` hook accepts any
|
|
|
18
18
|
import json
|
|
19
19
|
import sys
|
|
20
20
|
from collections import Counter
|
|
21
|
+
from collections.abc import Callable
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from typing import TextIO
|
|
23
24
|
|
|
@@ -285,7 +286,15 @@ def validate_content(
|
|
|
285
286
|
all_issues.extend(check_logging_adjacent_string_literals(content, file_path))
|
|
286
287
|
all_issues.extend(check_windows_api_none(content))
|
|
287
288
|
all_issues.extend(check_naive_datetime_construction(content, file_path))
|
|
288
|
-
all_issues.extend(
|
|
289
|
+
all_issues.extend(
|
|
290
|
+
_fragment_or_deferred_check(
|
|
291
|
+
check_magic_values,
|
|
292
|
+
old_content,
|
|
293
|
+
content,
|
|
294
|
+
file_path,
|
|
295
|
+
defer_scope_to_caller,
|
|
296
|
+
)
|
|
297
|
+
)
|
|
289
298
|
all_issues.extend(check_fstring_structural_literals(content, file_path))
|
|
290
299
|
all_issues.extend(check_constants_outside_config(content, file_path))
|
|
291
300
|
all_issues.extend(check_config_duplicate_path_anchor(content, file_path))
|
|
@@ -358,9 +367,21 @@ def validate_content(
|
|
|
358
367
|
all_issues.extend(
|
|
359
368
|
check_class_docstring_names_public_methods(effective_content, file_path)
|
|
360
369
|
)
|
|
361
|
-
all_issues.extend(check_docstring_runon_sentence(effective_content, file_path))
|
|
362
370
|
all_issues.extend(
|
|
363
|
-
|
|
371
|
+
check_docstring_runon_sentence(
|
|
372
|
+
effective_content,
|
|
373
|
+
file_path,
|
|
374
|
+
all_changed_lines,
|
|
375
|
+
defer_scope_to_caller,
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
all_issues.extend(
|
|
379
|
+
check_docstring_prose_wall_without_illustration(
|
|
380
|
+
effective_content,
|
|
381
|
+
file_path,
|
|
382
|
+
all_changed_lines,
|
|
383
|
+
defer_scope_to_caller,
|
|
384
|
+
)
|
|
364
385
|
)
|
|
365
386
|
all_issues.extend(
|
|
366
387
|
check_module_docstring_names_public_checks(effective_content, file_path)
|
|
@@ -508,8 +529,24 @@ def validate_content(
|
|
|
508
529
|
all_issues.extend(check_polarity_name_contradiction(content, file_path))
|
|
509
530
|
all_issues.extend(check_inline_literal_collections(content, file_path))
|
|
510
531
|
all_issues.extend(check_inline_tuple_string_magic(content, file_path))
|
|
511
|
-
all_issues.extend(
|
|
512
|
-
|
|
532
|
+
all_issues.extend(
|
|
533
|
+
_fragment_or_deferred_check(
|
|
534
|
+
check_join_separator_string_magic,
|
|
535
|
+
old_content,
|
|
536
|
+
content,
|
|
537
|
+
file_path,
|
|
538
|
+
defer_scope_to_caller,
|
|
539
|
+
)
|
|
540
|
+
)
|
|
541
|
+
all_issues.extend(
|
|
542
|
+
_fragment_or_deferred_check(
|
|
543
|
+
check_string_literal_magic,
|
|
544
|
+
old_content,
|
|
545
|
+
content,
|
|
546
|
+
file_path,
|
|
547
|
+
defer_scope_to_caller,
|
|
548
|
+
)
|
|
549
|
+
)
|
|
513
550
|
all_issues.extend(check_whitespace_indentation_magic(content, file_path))
|
|
514
551
|
all_issues.extend(check_orphan_css_classes(effective_content, file_path))
|
|
515
552
|
check_incomplete_mocks(content, file_path)
|
|
@@ -723,6 +760,124 @@ def _without_line_prefix(violation_text: str) -> str:
|
|
|
723
760
|
return violation_text
|
|
724
761
|
|
|
725
762
|
|
|
763
|
+
def _issues_absent_from_prior_bodies(
|
|
764
|
+
all_candidate_issues: list[str],
|
|
765
|
+
all_prior_issues: list[str],
|
|
766
|
+
) -> list[str]:
|
|
767
|
+
"""Return candidates whose message bodies are not covered by prior issues.
|
|
768
|
+
|
|
769
|
+
Matching is line-number-agnostic with per-occurrence accounting: each prior
|
|
770
|
+
entry consumes exactly one candidate carrying the same body.
|
|
771
|
+
|
|
772
|
+
::
|
|
773
|
+
|
|
774
|
+
prior: ["Line 1: magic 'X'"]
|
|
775
|
+
candidates: ["Line 4: magic 'X'", "Line 9: magic 'Y'"]
|
|
776
|
+
-> ["Line 9: magic 'Y'"]
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
all_candidate_issues: Findings from the scan under review.
|
|
780
|
+
all_prior_issues: Findings that already account for a body.
|
|
781
|
+
|
|
782
|
+
Returns:
|
|
783
|
+
Candidates not consumed by a matching prior body.
|
|
784
|
+
"""
|
|
785
|
+
remaining_prior_counts = Counter(
|
|
786
|
+
_without_line_prefix(each_issue) for each_issue in all_prior_issues
|
|
787
|
+
)
|
|
788
|
+
all_uncovered_issues: list[str] = []
|
|
789
|
+
for each_issue in all_candidate_issues:
|
|
790
|
+
message_body = _without_line_prefix(each_issue)
|
|
791
|
+
if remaining_prior_counts[message_body] > 0:
|
|
792
|
+
remaining_prior_counts[message_body] -= 1
|
|
793
|
+
continue
|
|
794
|
+
all_uncovered_issues.append(each_issue)
|
|
795
|
+
return all_uncovered_issues
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _fragment_or_deferred_check(
|
|
799
|
+
check_function: Callable[[str, str], list[str]],
|
|
800
|
+
old_content: str,
|
|
801
|
+
new_content: str,
|
|
802
|
+
file_path: str,
|
|
803
|
+
defer_scope_to_caller: bool,
|
|
804
|
+
) -> list[str]:
|
|
805
|
+
"""Run a check with fragment baselining, or full-file when the gate owns scope.
|
|
806
|
+
|
|
807
|
+
The commit/push gate sets ``defer_scope_to_caller`` and passes HEAD as
|
|
808
|
+
``old_content`` while scanning the current file (often the same blob on a
|
|
809
|
+
clean worktree). Fragment baselining would grandfather every finding there,
|
|
810
|
+
so the gate path runs the check on ``new_content`` alone and classifies by
|
|
811
|
+
added line afterward. PreToolUse Edit keeps baselining against the prior
|
|
812
|
+
fragment.
|
|
813
|
+
|
|
814
|
+
::
|
|
815
|
+
|
|
816
|
+
defer=True, old==new with magic 9999
|
|
817
|
+
-> [Line 2: Magic value 9999 ...] gate still sees it
|
|
818
|
+
defer=False, old and new both carry the same magic
|
|
819
|
+
-> [] grandfathered for wide Edit
|
|
820
|
+
|
|
821
|
+
Args:
|
|
822
|
+
check_function: A ``(content, file_path) -> list[str]`` check.
|
|
823
|
+
old_content: Prior fragment or gate HEAD blob.
|
|
824
|
+
new_content: Proposed fragment or current full file.
|
|
825
|
+
file_path: Destination path used for path-based exemptions.
|
|
826
|
+
defer_scope_to_caller: True when the gate will scope by added line.
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
Findings from the check, baselined against ``old_content`` only when
|
|
830
|
+
``defer_scope_to_caller`` is False.
|
|
831
|
+
"""
|
|
832
|
+
if defer_scope_to_caller:
|
|
833
|
+
return check_function(new_content, file_path)
|
|
834
|
+
return _issues_introduced_in_fragment(
|
|
835
|
+
check_function, old_content, new_content, file_path
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def _issues_introduced_in_fragment(
|
|
840
|
+
check_function: Callable[[str, str], list[str]],
|
|
841
|
+
old_content: str,
|
|
842
|
+
new_content: str,
|
|
843
|
+
file_path: str,
|
|
844
|
+
) -> list[str]:
|
|
845
|
+
"""Return fragment findings that are new relative to the pre-edit fragment.
|
|
846
|
+
|
|
847
|
+
Grades both the prior and proposed Edit fragments with the same check, then
|
|
848
|
+
subtracts findings whose message body already appears in the prior scan.
|
|
849
|
+
Per-occurrence accounting keeps a second identical new finding when only
|
|
850
|
+
one matching body existed before. Line numbers on kept findings come from
|
|
851
|
+
the proposed fragment so diagnostics point at the introduced location.
|
|
852
|
+
|
|
853
|
+
::
|
|
854
|
+
|
|
855
|
+
old: def f(): return os.environ['STRIPE_SECRET']
|
|
856
|
+
new: def f(): return os.environ['STRIPE_SECRET'] # same body
|
|
857
|
+
-> [] grandfathered
|
|
858
|
+
old: def f(): return 0
|
|
859
|
+
new: def f(): return os.environ['STRIPE_SECRET']
|
|
860
|
+
-> [Line ...: string magic value 'STRIPE_SECRET' ...]
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
check_function: A ``(content, file_path) -> list[str]`` check.
|
|
864
|
+
old_content: The Edit's prior ``old_string`` fragment, or empty when
|
|
865
|
+
there is no prior region to baseline against.
|
|
866
|
+
new_content: The Edit's ``new_string`` fragment (or Write body).
|
|
867
|
+
file_path: Destination path used for path-based exemptions.
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
Findings present in ``new_content`` that are absent from ``old_content``
|
|
871
|
+
under line-number-agnostic body matching. When ``old_content`` is empty,
|
|
872
|
+
every finding from ``new_content`` is returned.
|
|
873
|
+
"""
|
|
874
|
+
all_new_issues = check_function(new_content, file_path)
|
|
875
|
+
if not old_content:
|
|
876
|
+
return all_new_issues
|
|
877
|
+
all_old_issues = check_function(old_content, file_path)
|
|
878
|
+
return _issues_absent_from_prior_bodies(all_new_issues, all_old_issues)
|
|
879
|
+
|
|
880
|
+
|
|
726
881
|
def _forecast_full_file_violations(
|
|
727
882
|
full_file_content_after_edit: str,
|
|
728
883
|
file_path: str,
|
|
@@ -763,17 +918,7 @@ def _forecast_full_file_violations(
|
|
|
763
918
|
all_full_file_issues = validate_content(
|
|
764
919
|
full_file_content_after_edit, file_path, prior_full_file_content
|
|
765
920
|
)
|
|
766
|
-
|
|
767
|
-
_without_line_prefix(each_issue) for each_issue in all_blocking_issues
|
|
768
|
-
)
|
|
769
|
-
forecast_issues: list[str] = []
|
|
770
|
-
for each_issue in all_full_file_issues:
|
|
771
|
-
message_body = _without_line_prefix(each_issue)
|
|
772
|
-
if remaining_blocking_counts[message_body] > 0:
|
|
773
|
-
remaining_blocking_counts[message_body] -= 1
|
|
774
|
-
continue
|
|
775
|
-
forecast_issues.append(each_issue)
|
|
776
|
-
return forecast_issues
|
|
921
|
+
return _issues_absent_from_prior_bodies(all_full_file_issues, all_blocking_issues)
|
|
777
922
|
|
|
778
923
|
|
|
779
924
|
def _precheck_hint() -> str:
|
|
@@ -3,19 +3,12 @@
|
|
|
3
3
|
import ast
|
|
4
4
|
import difflib
|
|
5
5
|
import os
|
|
6
|
-
import sys
|
|
7
6
|
import tempfile
|
|
8
7
|
from collections.abc import Collection, Iterator
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if _blocking_directory not in sys.path:
|
|
14
|
-
sys.path.insert(0, _blocking_directory)
|
|
15
|
-
if _hooks_directory not in sys.path:
|
|
16
|
-
sys.path.insert(0, _hooks_directory)
|
|
17
|
-
|
|
18
|
-
from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
|
|
10
|
+
from hooks_constants.code_rules_enforcer_constants import (
|
|
11
|
+
ALL_AGENT_HOME_TOOLING_PATTERNS,
|
|
19
12
|
ALL_DIFF_CHANGED_OPCODE_TAGS,
|
|
20
13
|
ALL_EPHEMERAL_EXEMPT_DISABLE_TRUTHY_VALUES,
|
|
21
14
|
ALL_HOOK_INFRASTRUCTURE_PATTERNS,
|
|
@@ -30,14 +23,14 @@ from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
|
|
|
30
23
|
LEADING_DRIVE_LETTER_PATTERN,
|
|
31
24
|
STRICT_TEST_FILE_BASENAME_PATTERN,
|
|
32
25
|
)
|
|
33
|
-
from hooks_constants.harness_scratchpad_constants import (
|
|
26
|
+
from hooks_constants.harness_scratchpad_constants import (
|
|
34
27
|
CLAUDE_SESSION_ID_ENVIRONMENT_VARIABLE_NAME,
|
|
35
28
|
HARNESS_SCRATCHPAD_LEAF_DIRECTORY_NAME,
|
|
36
29
|
HARNESS_SCRATCHPAD_USER_DIRECTORY_NAME,
|
|
37
30
|
HARNESS_SCRATCHPAD_USER_DIRECTORY_PREFIX,
|
|
38
31
|
HOOK_PAYLOAD_SESSION_ID_KEY,
|
|
39
32
|
)
|
|
40
|
-
from hooks_constants.unused_module_import_constants import (
|
|
33
|
+
from hooks_constants.unused_module_import_constants import (
|
|
41
34
|
TYPE_CHECKING_IDENTIFIER,
|
|
42
35
|
)
|
|
43
36
|
|
|
@@ -56,6 +49,27 @@ def is_hook_infrastructure(file_path: str) -> bool:
|
|
|
56
49
|
return any(pattern.replace("\\", "/") in path_lower for pattern in ALL_HOOK_INFRASTRUCTURE_PATTERNS)
|
|
57
50
|
|
|
58
51
|
|
|
52
|
+
def is_agent_home_tooling(file_path: str) -> bool:
|
|
53
|
+
"""Check whether a path sits under a coding agent's home-directory tooling.
|
|
54
|
+
|
|
55
|
+
A coding agent installs helper scripts under a dot-directory in the home
|
|
56
|
+
tree, such as ``~/.grok/runs/``. That tree is vendored tooling rather than
|
|
57
|
+
project code, so the repository code rules do not govern it. The leading dot
|
|
58
|
+
is load-bearing: a project directory named ``grok/`` stays governed.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
file_path: The candidate path to classify.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True when the path sits under a recognized agent home directory.
|
|
65
|
+
"""
|
|
66
|
+
path_lower = "/" + file_path.lower().replace("\\", "/").lstrip("/")
|
|
67
|
+
return any(
|
|
68
|
+
each_pattern.replace("\\", "/") in path_lower
|
|
69
|
+
for each_pattern in ALL_AGENT_HOME_TOOLING_PATTERNS
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
59
73
|
def is_test_file(file_path: str) -> bool:
|
|
60
74
|
"""Check if file is a test file."""
|
|
61
75
|
path_lower = file_path.lower()
|
|
@@ -405,17 +419,18 @@ def is_under_session_scratchpad(file_path: str, hook_payload: dict) -> bool:
|
|
|
405
419
|
|
|
406
420
|
|
|
407
421
|
def is_ephemeral_path(file_path: str, hook_payload: dict | None = None) -> bool:
|
|
408
|
-
"""Return True when file_path is
|
|
409
|
-
|
|
410
|
-
Combines the
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
422
|
+
"""Return True when file_path is exempt from the repository gates.
|
|
423
|
+
|
|
424
|
+
Combines the path families a repo gate skips: the root-anchored ephemeral
|
|
425
|
+
scratch directories (``/tmp`` and ``$CLAUDE_JOB_DIR/tmp``), the harness
|
|
426
|
+
session scratchpad, and a coding agent's own home-directory tooling such as
|
|
427
|
+
``~/.grok/runs/``. The session scratchpad match reads the session id from the
|
|
428
|
+
payload when one is supplied, and from the harness environment variable
|
|
429
|
+
otherwise, so a caller that holds no payload still gets the match. The
|
|
430
|
+
run_all_validators PreToolUse gate calls this predicate to skip an exempt
|
|
431
|
+
target before it validates. The code-rules and TDD gates call the underlying
|
|
432
|
+
predicates ``is_ephemeral_script_path``, ``is_agent_home_tooling``, and
|
|
433
|
+
``is_under_session_scratchpad`` directly.
|
|
419
434
|
|
|
420
435
|
Args:
|
|
421
436
|
file_path: The candidate path to classify.
|
|
@@ -423,10 +438,12 @@ def is_ephemeral_path(file_path: str, hook_payload: dict | None = None) -> bool:
|
|
|
423
438
|
read the session id from the environment alone.
|
|
424
439
|
|
|
425
440
|
Returns:
|
|
426
|
-
True when the path is
|
|
441
|
+
True when the path is scratch, agent tooling, or the session scratchpad.
|
|
427
442
|
"""
|
|
428
443
|
if is_ephemeral_script_path(file_path):
|
|
429
444
|
return True
|
|
445
|
+
if is_agent_home_tooling(file_path):
|
|
446
|
+
return True
|
|
430
447
|
return is_under_session_scratchpad(file_path, hook_payload or {})
|
|
431
448
|
|
|
432
449
|
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# hooks/blocking/config
|
|
2
2
|
|
|
3
|
-
A Python package that holds shared constants for
|
|
4
|
-
|
|
5
|
-
- `verification_verdict_store.py`
|
|
6
|
-
- `verified_commit_gate.py`
|
|
7
|
-
- `verifier_verdict_minter.py`
|
|
3
|
+
A Python package that holds shared constants for blocking hooks. Verified-commit gate modules and opinionated prose-style gates import from here.
|
|
8
4
|
|
|
9
5
|
## Key files
|
|
10
6
|
|
|
11
7
|
| File | Contents |
|
|
12
8
|
|---|---|
|
|
13
9
|
| `__init__.py` | Declares this as a regular package (not a namespace package) so it resolves first on `sys.path` |
|
|
10
|
+
| `code_review_enforcement_constants.py` | Stamp and effort tunables for the code-review enforcement gates |
|
|
11
|
+
| `prose_style_enforcement_constants.py` | `CLAUDE_PROSE_STYLE_ENFORCEMENT` opt-in (default off) for opinionated prose gates |
|
|
14
12
|
| `verified_commit_constants.py` | All tunables for the gate: directory names, regex patterns for detecting verdict paths and obfuscation attempts, timeout values, git subcommand sets, bypass marker, and corrective messages |
|
|
15
13
|
| `verified_commit_context_constants.py` | `VERIFY_SKIP_ADDITIONAL_CONTEXT`, the ``# verify-skip`` usage rule the gate attaches to a deny payload's `additionalContext` |
|
|
16
14
|
| `verified_commit_gate_output_constants.py` | `PRE_TOOL_USE_HOOK_EVENT_NAME` and `DENY_PERMISSION_DECISION` (the deny payload's event name and decision string), `GATE_HOOK_MODULE_NAME` (the gate's own module name for block logging), and `REGEX_ALTERNATION_SEPARATOR` (the `\|` join for the directory-change verb alternation in `gated_invocations.py`) |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Single source of truth for opinionated prose-style gate opt-in.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
CLAUDE_PROSE_STYLE_ENFORCEMENT=1 -> PROSE_STYLE_ENFORCEMENT_ENABLED True
|
|
6
|
+
(unset) -> PROSE_STYLE_ENFORCEMENT_ENABLED False
|
|
7
|
+
|
|
8
|
+
Structural AskUserQuestion lean-block validation stays always on. The flag
|
|
9
|
+
arms only opinionated prose gates (heavy-word swaps, hedging, historical
|
|
10
|
+
state phrasing, intent-only endings, hook prose-detector consistency).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
PROSE_STYLE_ENFORCEMENT_ENV_VAR = "CLAUDE_PROSE_STYLE_ENFORCEMENT"
|
|
18
|
+
ALL_ENFORCEMENT_ENABLED_ENV_VALUES = frozenset({"1", "true", "yes", "on"})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prose_style_enforcement_enabled_in_environment() -> bool:
|
|
22
|
+
"""Read whether this machine turns opinionated prose gates on.
|
|
23
|
+
|
|
24
|
+
::
|
|
25
|
+
|
|
26
|
+
CLAUDE_PROSE_STYLE_ENFORCEMENT=1 -> True
|
|
27
|
+
CLAUDE_PROSE_STYLE_ENFORCEMENT=" On " -> True
|
|
28
|
+
CLAUDE_PROSE_STYLE_ENFORCEMENT=0 -> False
|
|
29
|
+
(variable unset) -> False
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True when the variable holds an enabled value, False otherwise.
|
|
33
|
+
"""
|
|
34
|
+
raw_environment_setting = os.environ.get(PROSE_STYLE_ENFORCEMENT_ENV_VAR, "")
|
|
35
|
+
return raw_environment_setting.strip().lower() in ALL_ENFORCEMENT_ENABLED_ENV_VALUES
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
PROSE_STYLE_ENFORCEMENT_ENABLED = prose_style_enforcement_enabled_in_environment()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Tests for the prose-style enforcement opt-in flag (default off)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_CONSTANTS_PATH = Path(__file__).resolve().parent / "prose_style_enforcement_constants.py"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load_constants_module(monkeypatch: pytest.MonkeyPatch, env_value: str | None):
|
|
14
|
+
module_name = "prose_style_enforcement_constants_under_test"
|
|
15
|
+
if env_value is None:
|
|
16
|
+
monkeypatch.delenv("CLAUDE_PROSE_STYLE_ENFORCEMENT", raising=False)
|
|
17
|
+
else:
|
|
18
|
+
monkeypatch.setenv("CLAUDE_PROSE_STYLE_ENFORCEMENT", env_value)
|
|
19
|
+
module_spec = importlib.util.spec_from_file_location(module_name, _CONSTANTS_PATH)
|
|
20
|
+
assert module_spec is not None and module_spec.loader is not None
|
|
21
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
22
|
+
module_spec.loader.exec_module(module)
|
|
23
|
+
return module
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_absent_env_is_default_off(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
27
|
+
module = _load_constants_module(monkeypatch, None)
|
|
28
|
+
assert module.PROSE_STYLE_ENFORCEMENT_ENABLED is False
|
|
29
|
+
assert module.prose_style_enforcement_enabled_in_environment() is False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize("enabled_value", ["1", "true", "yes", "on", " True "])
|
|
33
|
+
def test_truthy_env_enables(
|
|
34
|
+
monkeypatch: pytest.MonkeyPatch, enabled_value: str
|
|
35
|
+
) -> None:
|
|
36
|
+
module = _load_constants_module(monkeypatch, enabled_value)
|
|
37
|
+
assert module.PROSE_STYLE_ENFORCEMENT_ENABLED is True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize("disabled_value", ["0", "false", "no", "off", ""])
|
|
41
|
+
def test_falsy_env_stays_off(
|
|
42
|
+
monkeypatch: pytest.MonkeyPatch, disabled_value: str
|
|
43
|
+
) -> None:
|
|
44
|
+
module = _load_constants_module(monkeypatch, disabled_value)
|
|
45
|
+
assert module.PROSE_STYLE_ENFORCEMENT_ENABLED is False
|