claude-dev-env 2.8.0 → 2.10.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 +8 -2
- 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 +62 -562
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +58 -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/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +3 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +14 -8
- 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_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/code_rules_path_utils_constants.py +1 -0
- 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 +15 -0
- package/package.json +5 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/code-standards.md +33 -7
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +7 -2
- 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 +13 -2
- 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 +3 -1
- 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/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -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 +556 -9
- 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_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_spawn_grok_batch.py +295 -0
- 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 +6 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -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/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -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 +29 -3
- 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/reference/runner-selection.md +40 -0
- 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/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -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 +230 -0
- 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 +289 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- 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/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +17 -5
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -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
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Stop hook that blocks
|
|
4
|
-
|
|
5
|
-
The `eli11-replies`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
A reply opening with "Long form:" opts out, so a user-requested full report
|
|
12
|
-
still goes through.
|
|
3
|
+
Stop hook that blocks egregious reply-shape failures (action-first, bullets).
|
|
4
|
+
|
|
5
|
+
The `eli11-replies` and `opus5-communication-contract` rules ask every chat
|
|
6
|
+
reply to lead with the action when the user must act, keep findings to a few
|
|
7
|
+
bullets, and stay scannable. This hook reads the final assistant message,
|
|
8
|
+
strips code fences, inline code, blockquotes, table rows, and link targets,
|
|
9
|
+
then judges shape only: multi-line instructions need numbered steps first,
|
|
10
|
+
bullet count stays bounded, and list lines stay one-idea short.
|
|
13
11
|
"""
|
|
14
12
|
|
|
15
13
|
import json
|
|
@@ -30,13 +28,10 @@ from hooks_constants.eli11_reply_enforcer_constants import ( # noqa: E402
|
|
|
30
28
|
IMPERATIVE_OBJECT_TOKEN_PATTERN,
|
|
31
29
|
LINK_TARGET_PATTERN,
|
|
32
30
|
LIST_MARKER_PREFIX_PATTERN,
|
|
33
|
-
LONG_FORM_ESCAPE_PREFIX,
|
|
34
31
|
MARKDOWN_LEAD_MARKER_PATTERN,
|
|
35
32
|
MAXIMUM_BULLET_LINE_COUNT,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
MAXIMUM_WORDS_PER_LINE,
|
|
39
|
-
MINIMUM_ENFORCED_WORD_COUNT,
|
|
33
|
+
MAXIMUM_OVERPACKED_LIST_LINE_COUNT,
|
|
34
|
+
MAXIMUM_WORDS_PER_LIST_LINE,
|
|
40
35
|
NUMBERED_STEP_PATTERN,
|
|
41
36
|
TABLE_ROW_PATTERN,
|
|
42
37
|
TARGET_BULLET_LINE_COUNT,
|
|
@@ -69,18 +64,6 @@ def extract_reply_prose(assistant_message: str) -> str:
|
|
|
69
64
|
return LINK_TARGET_PATTERN.sub("", prose_text)
|
|
70
65
|
|
|
71
66
|
|
|
72
|
-
def count_reply_words(prose_text: str) -> int:
|
|
73
|
-
"""Return the number of reader-visible words in the stripped prose.
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
prose_text: Prose already cleaned of code, quotes, tables, and links.
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
The count of whitespace-separated tokens carrying a letter or digit.
|
|
80
|
-
"""
|
|
81
|
-
return len(COUNTABLE_WORD_PATTERN.findall(prose_text))
|
|
82
|
-
|
|
83
|
-
|
|
84
67
|
def collect_non_empty_lines(prose_text: str) -> list[str]:
|
|
85
68
|
"""Return every line of the stripped prose that carries visible text.
|
|
86
69
|
|
|
@@ -249,29 +232,13 @@ def count_words_in_line(prose_line: str) -> int:
|
|
|
249
232
|
return len(COUNTABLE_WORD_PATTERN.findall(unmarked_line))
|
|
250
233
|
|
|
251
234
|
|
|
252
|
-
def count_overpacked_lines(all_prose_lines: list[str]) -> int:
|
|
253
|
-
"""Return how many lines carry more words than one idea needs.
|
|
254
|
-
|
|
255
|
-
Args:
|
|
256
|
-
all_prose_lines: The non-empty lines of reply prose.
|
|
257
|
-
|
|
258
|
-
Returns:
|
|
259
|
-
The count of lines over the per-line word cap.
|
|
260
|
-
"""
|
|
261
|
-
return sum(
|
|
262
|
-
1
|
|
263
|
-
for each_line in all_prose_lines
|
|
264
|
-
if count_words_in_line(each_line) > MAXIMUM_WORDS_PER_LINE
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
|
|
268
235
|
def strip_markdown_lead_markers(prose_line: str) -> str:
|
|
269
236
|
"""Return a line with its opening blockquote, heading, and bold markers off.
|
|
270
237
|
|
|
271
238
|
::
|
|
272
239
|
|
|
273
|
-
in: "> **
|
|
274
|
-
out: "
|
|
240
|
+
in: "> **Bold lead:** the report follows"
|
|
241
|
+
out: "Bold lead:** the report follows"
|
|
275
242
|
|
|
276
243
|
Args:
|
|
277
244
|
prose_line: One line of raw reply text.
|
|
@@ -282,34 +249,37 @@ def strip_markdown_lead_markers(prose_line: str) -> str:
|
|
|
282
249
|
return MARKDOWN_LEAD_MARKER_PATTERN.sub("", prose_line.strip(), count=1)
|
|
283
250
|
|
|
284
251
|
|
|
285
|
-
def
|
|
286
|
-
"""Return True when
|
|
252
|
+
def is_list_item_line(prose_line: str) -> bool:
|
|
253
|
+
"""Return True when a line is a bullet or numbered list item.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
prose_line: One non-empty line of reply prose.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
True when the line opens with a bullet or numbered-step marker.
|
|
260
|
+
"""
|
|
261
|
+
if BULLET_LINE_PATTERN.match(prose_line):
|
|
262
|
+
return True
|
|
263
|
+
return NUMBERED_STEP_PATTERN.match(prose_line) is not None
|
|
287
264
|
|
|
288
|
-
::
|
|
289
265
|
|
|
290
|
-
|
|
291
|
-
|
|
266
|
+
def count_overpacked_list_lines(all_prose_lines: list[str]) -> int:
|
|
267
|
+
"""Return how many list lines carry more words than one idea needs.
|
|
292
268
|
|
|
293
|
-
|
|
269
|
+
Plain paragraphs are not counted so a requested full report can use long
|
|
270
|
+
prose lines without a word ceiling.
|
|
294
271
|
|
|
295
272
|
Args:
|
|
296
|
-
|
|
273
|
+
all_prose_lines: The non-empty lines of reply prose.
|
|
297
274
|
|
|
298
275
|
Returns:
|
|
299
|
-
|
|
276
|
+
The count of bullet/numbered lines over the per-list-line word cap.
|
|
300
277
|
"""
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return False
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def describe_word_cap_violation(reply_word_count: int) -> str:
|
|
310
|
-
"""Return the violation text naming the reply length and the cap."""
|
|
311
|
-
return (
|
|
312
|
-
f"{reply_word_count} words, over the {MAXIMUM_REPLY_WORD_COUNT}-word cap"
|
|
278
|
+
return sum(
|
|
279
|
+
1
|
|
280
|
+
for each_line in all_prose_lines
|
|
281
|
+
if is_list_item_line(each_line)
|
|
282
|
+
and count_words_in_line(each_line) > MAXIMUM_WORDS_PER_LIST_LINE
|
|
313
283
|
)
|
|
314
284
|
|
|
315
285
|
|
|
@@ -317,70 +287,68 @@ def describe_bullet_cap_violation(bullet_line_count: int) -> str:
|
|
|
317
287
|
"""Return the violation text naming the bullet count and the target."""
|
|
318
288
|
return (
|
|
319
289
|
f"{bullet_line_count} bullets, over the {MAXIMUM_BULLET_LINE_COUNT}-bullet "
|
|
320
|
-
f"cap -
|
|
290
|
+
f"cap - put findings in at most {TARGET_BULLET_LINE_COUNT} bullets"
|
|
321
291
|
)
|
|
322
292
|
|
|
323
293
|
|
|
324
|
-
def
|
|
325
|
-
"""Return the violation text
|
|
294
|
+
def describe_overpacked_list_line_violation(overpacked_line_count: int) -> str:
|
|
295
|
+
"""Return the violation text for overpacked list lines."""
|
|
326
296
|
return (
|
|
327
|
-
f"{overpacked_line_count} lines carry too many words - one idea per
|
|
328
|
-
f"at most {
|
|
297
|
+
f"{overpacked_line_count} list lines carry too many words - one idea per "
|
|
298
|
+
f"bullet or step, at most {MAXIMUM_WORDS_PER_LIST_LINE} words each"
|
|
329
299
|
)
|
|
330
300
|
|
|
331
301
|
|
|
332
302
|
def describe_action_first_violation() -> str:
|
|
333
303
|
"""Return the violation text for instructions buried under prose."""
|
|
334
304
|
return (
|
|
335
|
-
"the reply tells the user to act but leads with prose -
|
|
336
|
-
"
|
|
305
|
+
"the reply tells the user to act but leads with prose - open with "
|
|
306
|
+
"numbered steps, one short line each"
|
|
337
307
|
)
|
|
338
308
|
|
|
339
309
|
|
|
340
|
-
def is_action_first_violation(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
310
|
+
def is_action_first_violation(all_prose_lines: list[str]) -> bool:
|
|
311
|
+
"""Return True when multi-line instructions lack leading numbered steps.
|
|
312
|
+
|
|
313
|
+
::
|
|
314
|
+
|
|
315
|
+
ok: single-line "Run the migration." -> False
|
|
316
|
+
ok: numbered steps first, then prose -> False
|
|
317
|
+
flag: prose then "Install the package." -> True
|
|
344
318
|
|
|
345
319
|
Args:
|
|
346
|
-
reply_word_count: The reader-visible word count of the reply.
|
|
347
320
|
all_prose_lines: The non-empty lines of reply prose.
|
|
348
321
|
|
|
349
322
|
Returns:
|
|
350
|
-
True when the reply
|
|
351
|
-
numbered step among its lead lines.
|
|
323
|
+
True when the reply has more than one line, carries instruction
|
|
324
|
+
lines, and shows no numbered step among its lead lines.
|
|
352
325
|
"""
|
|
353
|
-
if
|
|
326
|
+
if len(all_prose_lines) <= 1:
|
|
354
327
|
return False
|
|
355
328
|
if not has_imperative_instruction_line(all_prose_lines):
|
|
356
329
|
return False
|
|
357
330
|
return not has_leading_numbered_step(all_prose_lines)
|
|
358
331
|
|
|
359
332
|
|
|
360
|
-
def collect_shape_violations(
|
|
361
|
-
reply_word_count: int, all_prose_lines: list[str]
|
|
362
|
-
) -> list[str]:
|
|
333
|
+
def collect_shape_violations(all_prose_lines: list[str]) -> list[str]:
|
|
363
334
|
"""Return the violation texts for every reply-shape rule the reply breaks.
|
|
364
335
|
|
|
365
336
|
Args:
|
|
366
|
-
reply_word_count: The reader-visible word count of the reply.
|
|
367
337
|
all_prose_lines: The non-empty lines of reply prose.
|
|
368
338
|
|
|
369
339
|
Returns:
|
|
370
340
|
One violation text per broken rule, empty when the reply is in shape.
|
|
371
341
|
"""
|
|
372
342
|
all_violations: list[str] = []
|
|
373
|
-
if
|
|
374
|
-
all_violations.append(describe_word_cap_violation(reply_word_count))
|
|
375
|
-
if is_action_first_violation(reply_word_count, all_prose_lines):
|
|
343
|
+
if is_action_first_violation(all_prose_lines):
|
|
376
344
|
all_violations.append(describe_action_first_violation())
|
|
377
345
|
bullet_line_count = count_bullet_lines(all_prose_lines)
|
|
378
346
|
if bullet_line_count > MAXIMUM_BULLET_LINE_COUNT:
|
|
379
347
|
all_violations.append(describe_bullet_cap_violation(bullet_line_count))
|
|
380
|
-
|
|
381
|
-
if
|
|
348
|
+
overpacked_list_line_count = count_overpacked_list_lines(all_prose_lines)
|
|
349
|
+
if overpacked_list_line_count > MAXIMUM_OVERPACKED_LIST_LINE_COUNT:
|
|
382
350
|
all_violations.append(
|
|
383
|
-
|
|
351
|
+
describe_overpacked_list_line_violation(overpacked_list_line_count)
|
|
384
352
|
)
|
|
385
353
|
return all_violations
|
|
386
354
|
|
|
@@ -390,11 +358,9 @@ def find_reply_shape_violations(assistant_message: str) -> list[str]:
|
|
|
390
358
|
|
|
391
359
|
::
|
|
392
360
|
|
|
393
|
-
ok: a
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
A reply opening with the escape prefix, and a reply under the enforced word
|
|
397
|
-
floor, are both returned as in shape.
|
|
361
|
+
ok: a short outcome sentence -> []
|
|
362
|
+
ok: a long requested audit with few bullets -> []
|
|
363
|
+
flag: prose then buried "Install the package." -> [action-first]
|
|
398
364
|
|
|
399
365
|
Args:
|
|
400
366
|
assistant_message: The raw final assistant message.
|
|
@@ -402,19 +368,12 @@ def find_reply_shape_violations(assistant_message: str) -> list[str]:
|
|
|
402
368
|
Returns:
|
|
403
369
|
One violation text per broken rule, empty when the reply is in shape.
|
|
404
370
|
"""
|
|
405
|
-
if opens_with_long_form_escape(assistant_message):
|
|
406
|
-
return []
|
|
407
371
|
prose_text = extract_reply_prose(assistant_message)
|
|
408
|
-
|
|
409
|
-
if reply_word_count < MINIMUM_ENFORCED_WORD_COUNT:
|
|
410
|
-
return []
|
|
411
|
-
return collect_shape_violations(
|
|
412
|
-
reply_word_count, collect_non_empty_lines(prose_text)
|
|
413
|
-
)
|
|
372
|
+
return collect_shape_violations(collect_non_empty_lines(prose_text))
|
|
414
373
|
|
|
415
374
|
|
|
416
375
|
def build_block_reason(all_violations: list[str]) -> str:
|
|
417
|
-
"""Return the corrective message naming each violation and the
|
|
376
|
+
"""Return the corrective message naming each violation and the rewrite path.
|
|
418
377
|
|
|
419
378
|
Args:
|
|
420
379
|
all_violations: The violation texts the reply earned.
|
|
@@ -424,16 +383,14 @@ def build_block_reason(all_violations: list[str]) -> str:
|
|
|
424
383
|
"""
|
|
425
384
|
formatted_violation_list = VIOLATION_SEPARATOR.join(all_violations)
|
|
426
385
|
return (
|
|
427
|
-
f"ELI11 REPLY SHAPE:
|
|
386
|
+
f"ELI11 REPLY SHAPE: Rewrite the reply into a short action-first shape "
|
|
428
387
|
f"({formatted_violation_list}).\n\n"
|
|
429
|
-
f"
|
|
430
|
-
f"
|
|
431
|
-
f"
|
|
432
|
-
f"
|
|
433
|
-
f"
|
|
434
|
-
f"links are already exempt from
|
|
435
|
-
f"When the user asked for a full report, start the reply with "
|
|
436
|
-
f'"Long form:" to opt out of this check.\n\n'
|
|
388
|
+
f"When the user must act, open with numbered click-by-click steps, one "
|
|
389
|
+
f"short line each. When nothing is needed, open with the outcome in one "
|
|
390
|
+
f"sentence. Keep findings to at most {TARGET_BULLET_LINE_COUNT} short "
|
|
391
|
+
f"bullets. A requested full report may run long; keep list lines short "
|
|
392
|
+
f"and lead with the outcome. Code fences, blockquotes, tables, and "
|
|
393
|
+
f"links are already exempt from shape counting.\n\n"
|
|
437
394
|
f"You MUST re-output the complete, revised response with the correction "
|
|
438
395
|
f"applied."
|
|
439
396
|
)
|