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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Pure AskUserQuestion payload-shape analysis.
|
|
2
|
+
|
|
3
|
+
Maps an AskUserQuestion tool input to lean-block violations without hook I/O,
|
|
4
|
+
deny payloads, or environment state::
|
|
5
|
+
|
|
6
|
+
analyze_ask_user_question_shape({"questions": [...]})
|
|
7
|
+
ok: a lean question and short options -> empty violations
|
|
8
|
+
flag: a bullet list under the question -> named violation texts
|
|
9
|
+
|
|
10
|
+
Callers that need a deny reason compose with the shared lean-block message
|
|
11
|
+
constants; this module only grades shape.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
from hooks_constants.plain_language_blocker_constants import (
|
|
20
|
+
ALL_CHAT_DETAIL_MARKERS,
|
|
21
|
+
ALL_LINE_ENDING_REPLACEMENTS,
|
|
22
|
+
COUNTABLE_WORD_PATTERN,
|
|
23
|
+
INLINE_CODE_PLACEHOLDER,
|
|
24
|
+
INLINE_CODE_SPAN_PATTERN,
|
|
25
|
+
MAXIMUM_OPTION_DESCRIPTION_SENTENCE_COUNT,
|
|
26
|
+
MAXIMUM_OPTION_DESCRIPTION_WORD_COUNT,
|
|
27
|
+
MAXIMUM_QUESTION_SENTENCE_COUNT,
|
|
28
|
+
MAXIMUM_QUESTION_WORD_COUNT,
|
|
29
|
+
OPTION_DESCRIPTION_SURFACE_NAME,
|
|
30
|
+
QUESTION_SURFACE_NAME,
|
|
31
|
+
SENTENCE_BOUNDARY_PATTERN,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AskUserQuestionShapeResult",
|
|
36
|
+
"analyze_ask_user_question_shape",
|
|
37
|
+
"count_prose_sentences",
|
|
38
|
+
"count_prose_words",
|
|
39
|
+
"find_chat_detail_markers",
|
|
40
|
+
"find_lean_block_violations",
|
|
41
|
+
"find_question_block_violations",
|
|
42
|
+
"mask_inline_code",
|
|
43
|
+
"normalize_line_endings",
|
|
44
|
+
"violations_for_one_question",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class AskUserQuestionShapeResult:
|
|
50
|
+
"""Shape-grade for one AskUserQuestion tool input.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
all_violations: One violation text per broken lean-block rule, in
|
|
54
|
+
first-seen order, empty when the whole question block is lean.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
all_violations: tuple[str, ...]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def is_lean(self) -> bool:
|
|
61
|
+
"""True when the payload carries no lean-block violations."""
|
|
62
|
+
return not self.all_violations
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def normalize_line_endings(prose_text: str) -> str:
|
|
66
|
+
"""Return the prose with every line ending spelled as a bare line feed.
|
|
67
|
+
|
|
68
|
+
::
|
|
69
|
+
|
|
70
|
+
in: "Which gate?\\r\\n\\r\\nThe write gate reads it."
|
|
71
|
+
out: "Which gate?\\n\\nThe write gate reads it."
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
prose_text: One question text or one option description.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The prose with carriage returns folded into line feeds.
|
|
78
|
+
"""
|
|
79
|
+
normalized_text = prose_text
|
|
80
|
+
for each_line_ending, each_replacement in ALL_LINE_ENDING_REPLACEMENTS:
|
|
81
|
+
normalized_text = normalized_text.replace(each_line_ending, each_replacement)
|
|
82
|
+
return normalized_text
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def mask_inline_code(prose_text: str) -> str:
|
|
86
|
+
"""Return the prose with each inline code span collapsed to a single word.
|
|
87
|
+
|
|
88
|
+
::
|
|
89
|
+
|
|
90
|
+
in: "Does `git diff --cached` list it?"
|
|
91
|
+
out: "Does code list it?"
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
prose_text: One question text or one option description.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The prose with every single-line inline code span replaced.
|
|
98
|
+
"""
|
|
99
|
+
return INLINE_CODE_SPAN_PATTERN.sub(INLINE_CODE_PLACEHOLDER, prose_text)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def count_prose_sentences(prose_text: str) -> int:
|
|
103
|
+
"""Return how many sentences one piece of question-block prose carries.
|
|
104
|
+
|
|
105
|
+
::
|
|
106
|
+
|
|
107
|
+
in: "Which gate runs first? Both read the same lines."
|
|
108
|
+
out: 2
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
prose_text: One question text or one option description.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The count of sentence closings the prose carries.
|
|
115
|
+
"""
|
|
116
|
+
return len(SENTENCE_BOUNDARY_PATTERN.findall(prose_text))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def count_prose_words(prose_text: str) -> int:
|
|
120
|
+
"""Return how many reader-visible words one piece of prose carries.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
prose_text: One question text or one option description.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The count of whitespace-separated tokens carrying a letter or digit.
|
|
127
|
+
"""
|
|
128
|
+
return len(COUNTABLE_WORD_PATTERN.findall(prose_text))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def find_chat_detail_markers(prose_text: str) -> list[str]:
|
|
132
|
+
"""Return the name of each chat-detail marker the prose carries.
|
|
133
|
+
|
|
134
|
+
::
|
|
135
|
+
|
|
136
|
+
ok: "Which gate should run first?" -> []
|
|
137
|
+
flag: "Which gate?\\n- write\\n- commit" -> ["a bullet or numbered
|
|
138
|
+
list marker"]
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
prose_text: One question text or one option description.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
One marker name per marker found, in the order the markers are tried.
|
|
145
|
+
"""
|
|
146
|
+
return [
|
|
147
|
+
each_marker_name
|
|
148
|
+
for each_pattern, each_marker_name in ALL_CHAT_DETAIL_MARKERS
|
|
149
|
+
if each_pattern.search(prose_text)
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def find_lean_block_violations(
|
|
154
|
+
prose_text: str,
|
|
155
|
+
surface_name: str,
|
|
156
|
+
maximum_sentence_count: int,
|
|
157
|
+
maximum_word_count: int,
|
|
158
|
+
) -> list[str]:
|
|
159
|
+
"""Return every lean-block rule one piece of question-block prose breaks.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
prose_text: One question text or one option description.
|
|
163
|
+
surface_name: How the violation text names this piece of prose.
|
|
164
|
+
maximum_sentence_count: The sentence cap this piece answers to.
|
|
165
|
+
maximum_word_count: The word cap this piece answers to.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
One violation text per broken rule, empty when the prose is lean.
|
|
169
|
+
"""
|
|
170
|
+
normalized_text = normalize_line_endings(prose_text)
|
|
171
|
+
masked_text = mask_inline_code(normalized_text)
|
|
172
|
+
all_violations = [
|
|
173
|
+
f"{surface_name} carries {each_marker_name}"
|
|
174
|
+
for each_marker_name in find_chat_detail_markers(normalized_text)
|
|
175
|
+
]
|
|
176
|
+
sentence_count = count_prose_sentences(masked_text)
|
|
177
|
+
if sentence_count > maximum_sentence_count:
|
|
178
|
+
all_violations.append(
|
|
179
|
+
f"{surface_name} runs {sentence_count} sentences, over the "
|
|
180
|
+
f"{maximum_sentence_count}-sentence cap"
|
|
181
|
+
)
|
|
182
|
+
word_count = count_prose_words(masked_text)
|
|
183
|
+
if word_count > maximum_word_count:
|
|
184
|
+
all_violations.append(
|
|
185
|
+
f"{surface_name} runs {word_count} words, over the "
|
|
186
|
+
f"{maximum_word_count}-word cap"
|
|
187
|
+
)
|
|
188
|
+
return all_violations
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def violations_for_one_question(question_by_key: Mapping[str, object]) -> list[str]:
|
|
192
|
+
"""Return the lean-block violations one question entry carries.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
question_by_key: One entry of the AskUserQuestion questions list.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
One violation text per broken rule across the question text and every
|
|
199
|
+
option description the entry carries.
|
|
200
|
+
"""
|
|
201
|
+
all_violations: list[str] = []
|
|
202
|
+
question_text = question_by_key.get("question", "")
|
|
203
|
+
if isinstance(question_text, str):
|
|
204
|
+
all_violations.extend(
|
|
205
|
+
find_lean_block_violations(
|
|
206
|
+
question_text,
|
|
207
|
+
QUESTION_SURFACE_NAME,
|
|
208
|
+
MAXIMUM_QUESTION_SENTENCE_COUNT,
|
|
209
|
+
MAXIMUM_QUESTION_WORD_COUNT,
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
all_options = question_by_key.get("options", [])
|
|
213
|
+
if not isinstance(all_options, Sequence) or isinstance(all_options, (str, bytes)):
|
|
214
|
+
return all_violations
|
|
215
|
+
for each_option in all_options:
|
|
216
|
+
if not isinstance(each_option, Mapping):
|
|
217
|
+
continue
|
|
218
|
+
option_description = each_option.get("description", "")
|
|
219
|
+
if not isinstance(option_description, str):
|
|
220
|
+
continue
|
|
221
|
+
all_violations.extend(
|
|
222
|
+
find_lean_block_violations(
|
|
223
|
+
option_description,
|
|
224
|
+
OPTION_DESCRIPTION_SURFACE_NAME,
|
|
225
|
+
MAXIMUM_OPTION_DESCRIPTION_SENTENCE_COUNT,
|
|
226
|
+
MAXIMUM_OPTION_DESCRIPTION_WORD_COUNT,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
return all_violations
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def find_question_block_violations(payload_by_key: Mapping[str, object]) -> list[str]:
|
|
233
|
+
"""Return every lean-block violation an AskUserQuestion payload carries.
|
|
234
|
+
|
|
235
|
+
::
|
|
236
|
+
|
|
237
|
+
ok: "Which gate should run first?" + "Runs on every write." -> []
|
|
238
|
+
flag: a question with a three-bullet plan under it
|
|
239
|
+
-> ["the question carries a bullet or numbered list marker"]
|
|
240
|
+
|
|
241
|
+
Each violation text appears once, so two options breaking the same cap read
|
|
242
|
+
as one line.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
payload_by_key: The AskUserQuestion tool input carrying the questions list.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
One violation text per broken rule, in first-seen order, empty when the
|
|
249
|
+
whole question block is lean.
|
|
250
|
+
"""
|
|
251
|
+
all_questions = payload_by_key.get("questions", [])
|
|
252
|
+
if not isinstance(all_questions, Sequence) or isinstance(all_questions, (str, bytes)):
|
|
253
|
+
return []
|
|
254
|
+
all_violations: list[str] = []
|
|
255
|
+
for each_question in all_questions:
|
|
256
|
+
if isinstance(each_question, Mapping):
|
|
257
|
+
all_violations.extend(violations_for_one_question(each_question))
|
|
258
|
+
return list(dict.fromkeys(all_violations))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def analyze_ask_user_question_shape(
|
|
262
|
+
payload_by_key: Mapping[str, object],
|
|
263
|
+
) -> AskUserQuestionShapeResult:
|
|
264
|
+
"""Grade an AskUserQuestion tool input for lean-block shape only.
|
|
265
|
+
|
|
266
|
+
::
|
|
267
|
+
|
|
268
|
+
analyze_ask_user_question_shape({"questions": [
|
|
269
|
+
{"question": "Which gate?", "options": [{"description": "Runs on write."}]}
|
|
270
|
+
]}).is_lean
|
|
271
|
+
True
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
payload_by_key: The AskUserQuestion tool input carrying the questions list.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
A frozen result holding the ordered, de-duplicated violation texts.
|
|
278
|
+
"""
|
|
279
|
+
return AskUserQuestionShapeResult(
|
|
280
|
+
all_violations=tuple(find_question_block_violations(payload_by_key))
|
|
281
|
+
)
|
|
@@ -27,7 +27,8 @@ CLAUDE_JOB_DIR_SCRATCH_SUBDIRECTORY: str = "tmp"
|
|
|
27
27
|
EPHEMERAL_EXEMPT_DISABLE_ENVIRONMENT_VARIABLE_NAME: str = "CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT"
|
|
28
28
|
ALL_EPHEMERAL_EXEMPT_DISABLE_TRUTHY_VALUES: frozenset[str] = frozenset({"1", "true", "yes", "on"})
|
|
29
29
|
LEADING_DRIVE_LETTER_PATTERN: re.Pattern[str] = re.compile(r"^[a-z]:")
|
|
30
|
-
|
|
30
|
+
ALL_AGENT_HOME_TOOLING_PATTERNS = {"/.grok/", "\\.grok\\", "\\.grok/"}
|
|
31
|
+
ALL_HOOK_INFRASTRUCTURE_PATTERNS = {"/.claude/hooks/", "\\.claude\\hooks\\", "\\.claude/hooks/", "/packages/claude-dev-env/hooks/", "\\packages\\claude-dev-env\\hooks\\"} | ALL_AGENT_HOME_TOOLING_PATTERNS
|
|
31
32
|
ALL_WORKFLOW_REGISTRY_PATTERNS = {"/workflow/", "\\workflow\\", "_tab.py", "/states.py", "\\states.py", "/modules.py", "\\modules.py"}
|
|
32
33
|
ALL_MIGRATION_PATH_PATTERNS = {"/migrations/", "\\migrations\\"}
|
|
33
34
|
|
|
@@ -24,13 +24,10 @@ __all__ = [
|
|
|
24
24
|
"IMPERATIVE_OBJECT_TOKEN_PATTERN",
|
|
25
25
|
"LINK_TARGET_PATTERN",
|
|
26
26
|
"LIST_MARKER_PREFIX_PATTERN",
|
|
27
|
-
"LONG_FORM_ESCAPE_PREFIX",
|
|
28
27
|
"MARKDOWN_LEAD_MARKER_PATTERN",
|
|
29
28
|
"MAXIMUM_BULLET_LINE_COUNT",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"MAXIMUM_WORDS_PER_LINE",
|
|
33
|
-
"MINIMUM_ENFORCED_WORD_COUNT",
|
|
29
|
+
"MAXIMUM_OVERPACKED_LIST_LINE_COUNT",
|
|
30
|
+
"MAXIMUM_WORDS_PER_LIST_LINE",
|
|
34
31
|
"NUMBERED_STEP_PATTERN",
|
|
35
32
|
"TABLE_ROW_PATTERN",
|
|
36
33
|
"TARGET_BULLET_LINE_COUNT",
|
|
@@ -38,18 +35,14 @@ __all__ = [
|
|
|
38
35
|
"VIOLATION_SEPARATOR",
|
|
39
36
|
]
|
|
40
37
|
|
|
41
|
-
MAXIMUM_REPLY_WORD_COUNT = 120
|
|
42
|
-
MINIMUM_ENFORCED_WORD_COUNT = 60
|
|
43
38
|
MAXIMUM_BULLET_LINE_COUNT = 6
|
|
44
39
|
TARGET_BULLET_LINE_COUNT = 3
|
|
45
40
|
ACTION_FIRST_LEAD_LINE_COUNT = 6
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
MAXIMUM_WORDS_PER_LIST_LINE = 20
|
|
42
|
+
MAXIMUM_OVERPACKED_LIST_LINE_COUNT = 2
|
|
48
43
|
|
|
49
|
-
LONG_FORM_ESCAPE_PREFIX = "long form:"
|
|
50
44
|
VIOLATION_SEPARATOR = "; "
|
|
51
|
-
USER_FACING_ELI11_NOTICE = "
|
|
52
|
-
|
|
45
|
+
USER_FACING_ELI11_NOTICE = "Rewrite the reply into a short action-first shape..."
|
|
53
46
|
TABLE_ROW_PATTERN = re.compile(r"^[ \t]*\|.*\|[ \t]*$", re.MULTILINE)
|
|
54
47
|
LINK_TARGET_PATTERN = re.compile(r"(?:https?://|www\.)\S+", re.IGNORECASE)
|
|
55
48
|
COUNTABLE_WORD_PATTERN = re.compile(r"\S*[A-Za-z0-9]\S*")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Explicit uncertainty labels that co-occur with hedge words in one sentence.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
ok: This claim is unverified; the port is probably down.
|
|
6
|
+
flag: The port is probably down. (no label in that sentence)
|
|
7
|
+
|
|
8
|
+
A response-wide "unverified" label does not exempt a bare hedge in another
|
|
9
|
+
sentence. Classification of matcher precision (OP-07B) uses the same blocking
|
|
10
|
+
surface: only hedges that survive this filter count toward hard-block evidence.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
# Sentence ends at . ! ? when followed by space or end; keep the terminator
|
|
18
|
+
# attached to the sentence so labels next to periods still match.
|
|
19
|
+
SENTENCE_BOUNDARY_PATTERN = re.compile(r"(?<=[.!?])\s+")
|
|
20
|
+
|
|
21
|
+
ALL_EXPLICIT_UNCERTAINTY_LABEL_PATTERNS = (
|
|
22
|
+
re.compile(r"\bunverified\b", re.IGNORECASE),
|
|
23
|
+
re.compile(r"\bi don't know\b", re.IGNORECASE),
|
|
24
|
+
re.compile(r"\bi do not know\b", re.IGNORECASE),
|
|
25
|
+
re.compile(r"\bno source for this claim\b", re.IGNORECASE),
|
|
26
|
+
re.compile(r"\bwithout a (credible )?source\b", re.IGNORECASE),
|
|
27
|
+
re.compile(r"\bnamed (as )?unverified\b", re.IGNORECASE),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
HEDGING_TERM_LIST_SEPARATOR = ", "
|
|
31
|
+
|
|
32
|
+
POSITIVE_CORRECTIVE_GUIDANCE = (
|
|
33
|
+
"State the claim with evidence that backs it, or label that claim unverified "
|
|
34
|
+
"in the same sentence as the uncertainty word. "
|
|
35
|
+
"Gather a source, run a live probe, or ask the user through AskUserQuestion — "
|
|
36
|
+
"then re-output the complete revised response."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
BLOCK_REASON_PREFIX = (
|
|
40
|
+
"ANTI-HALLUCINATION GUARDRAIL: Your response contains hedging language without "
|
|
41
|
+
"an explicit uncertainty label in the same sentence: "
|
|
42
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Constants for the repository-gated issue-tracker SessionStart starter.
|
|
2
|
+
|
|
3
|
+
Default off. Set CLAUDE_ISSUE_TRACKER_SESSION_STARTER_ENABLED to 1/true/yes/on.
|
|
4
|
+
Even when enabled, injects only when the session cwd's git root is registered
|
|
5
|
+
in ~/.claude/project-paths.json. Missing registry fails closed (no inject).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR: str = (
|
|
11
|
+
"CLAUDE_ISSUE_TRACKER_SESSION_STARTER_ENABLED"
|
|
12
|
+
)
|
|
13
|
+
ALL_ISSUE_TRACKER_STARTER_ENABLED_ENV_VALUES: frozenset[str] = frozenset(
|
|
14
|
+
{"1", "true", "yes", "on"}
|
|
15
|
+
)
|
|
16
|
+
ISSUE_TRACKER_STARTER_TIMEOUT_MILLISECONDS: int = 50
|
|
17
|
+
|
|
18
|
+
ISSUE_TRACKER_SESSION_START_DIRECTIVE: str = (
|
|
19
|
+
"SessionStart issue-tracker opt-in is active for this registered repository. "
|
|
20
|
+
"Load the issue-tracker skill when the session needs one GitHub issue action "
|
|
21
|
+
"(open epic, file sub-issue, update status, refresh checklist, close sub-issue). "
|
|
22
|
+
"Manual issue-tracker agent spawn remains available unchanged."
|
|
23
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Constants for the opt-in orchestrator SessionStart auto-starter.
|
|
2
|
+
|
|
3
|
+
Default off. Set CLAUDE_ORCHESTRATOR_AUTO_STARTER_ENABLED to 1/true/yes/on to
|
|
4
|
+
emit an additionalContext directive that points the session at the orchestrator
|
|
5
|
+
skill. Manual /orchestrator invocation is unchanged.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR: str = (
|
|
11
|
+
"CLAUDE_ORCHESTRATOR_AUTO_STARTER_ENABLED"
|
|
12
|
+
)
|
|
13
|
+
ALL_ORCHESTRATOR_STARTER_ENABLED_ENV_VALUES: frozenset[str] = frozenset(
|
|
14
|
+
{"1", "true", "yes", "on"}
|
|
15
|
+
)
|
|
16
|
+
ORCHESTRATOR_STARTER_TIMEOUT_MILLISECONDS: int = 50
|
|
17
|
+
|
|
18
|
+
ORCHESTRATOR_SESSION_START_DIRECTIVE: str = (
|
|
19
|
+
"SessionStart orchestrator opt-in is active. Load the orchestrator skill "
|
|
20
|
+
"and run in executor-advisor mode for this session's multi-step work. "
|
|
21
|
+
"Do not invent a second orchestrator path; follow the skill. Manual "
|
|
22
|
+
"/orchestrator remains available unchanged."
|
|
23
|
+
)
|
|
@@ -13,7 +13,10 @@ flags from its first operand, the wrapper commands whose run passes through to
|
|
|
13
13
|
the program behind them, the separator that rejoins a wrapper's argument
|
|
14
14
|
tokens, the empty commenter set that leaves ``#`` to the comment-start pattern,
|
|
15
15
|
the quote characters stripped before a basename read, and the deny message.
|
|
16
|
-
Segment helpers come from ``shell_command_segments.py``.
|
|
16
|
+
Segment helpers come from ``shell_command_segments.py``. Quote-aware pipeline
|
|
17
|
+
parsing lives in ``shell_command_pipeline``. Pytest invocation classification
|
|
18
|
+
that consumes these program, wrapper, and option constants lives in
|
|
19
|
+
``pytest_invocation``.
|
|
17
20
|
|
|
18
21
|
Two wrapper shapes reach the program behind them differently. ``sudo`` and
|
|
19
22
|
``uvx`` take their own option flags and then the command, so the step-over drops
|
|
@@ -18,7 +18,9 @@ strippers the word scan runs first, and the lean-question-block tunables: the
|
|
|
18
18
|
block-level chat-detail markers that belong in chat text, the line-ending
|
|
19
19
|
spellings normalized before those markers run, the inline-code span the length
|
|
20
20
|
counts collapse to one word, and the sentence and word caps a question and an
|
|
21
|
-
option description each answer to.
|
|
21
|
+
option description each answer to. The pure analyzer in
|
|
22
|
+
``ask_user_question_shape`` reads the same lean-block tunables so shape grading
|
|
23
|
+
and the runtime hook share one contract.
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
26
|
from __future__ import annotations
|
|
@@ -352,6 +354,7 @@ OPTION_DESCRIPTION_SURFACE_NAME: str = "an option description"
|
|
|
352
354
|
|
|
353
355
|
LEAN_QUESTION_BLOCK_PREFIX: str = "BLOCKED: [LEAN_QUESTION] Question block carries chat detail -- "
|
|
354
356
|
LEAN_QUESTION_VIOLATION_SEPARATOR: str = "; "
|
|
357
|
+
HEAVY_WORD_SWAP_LIST_SEPARATOR: str = ", "
|
|
355
358
|
LEAN_QUESTION_BLOCK_GUIDANCE: str = (
|
|
356
359
|
"AskUserQuestion renders as one plain text block. Print the plan, the counts, "
|
|
357
360
|
"and the tradeoffs in chat text before the call, keep the question block to a "
|
|
@@ -16,6 +16,8 @@ __all__ = [
|
|
|
16
16
|
"BLOCKING_CRASH_EXIT_CODE",
|
|
17
17
|
"EXIT_CODE_TWO_DENY_REASON",
|
|
18
18
|
"BLOCKING_CRASH_DENY_REASON",
|
|
19
|
+
"REASON_JOIN_SEPARATOR",
|
|
20
|
+
"CONTEXT_JOIN_SEPARATOR",
|
|
19
21
|
"WRITE_TOOL_NAME",
|
|
20
22
|
"EDIT_TOOL_NAME",
|
|
21
23
|
"MULTI_EDIT_TOOL_NAME",
|
|
@@ -23,6 +25,7 @@ __all__ = [
|
|
|
23
25
|
"ALL_WRITE_EDIT_MULTI_EDIT_TOOL_NAMES",
|
|
24
26
|
"STATE_DESCRIPTION_BLOCKER_MODULE_NAME",
|
|
25
27
|
"PLAIN_LANGUAGE_BLOCKER_MODULE_NAME",
|
|
28
|
+
"SYSTEM_MESSAGE_JOIN_SEPARATOR",
|
|
26
29
|
"HostedHookEntry",
|
|
27
30
|
"ALL_HOSTED_HOOK_ENTRIES",
|
|
28
31
|
]
|
|
@@ -30,9 +33,12 @@ __all__ = [
|
|
|
30
33
|
DENY_DECISION = "deny"
|
|
31
34
|
ALLOW_DECISION = "allow"
|
|
32
35
|
HOOK_EVENT_NAME = "PreToolUse"
|
|
36
|
+
SYSTEM_MESSAGE_JOIN_SEPARATOR = "\n"
|
|
33
37
|
BLOCKING_CRASH_EXIT_CODE = 2
|
|
34
38
|
EXIT_CODE_TWO_DENY_REASON = "[dispatcher] hook denied via exit code 2 — write blocked"
|
|
35
39
|
BLOCKING_CRASH_DENY_REASON = "[dispatcher] hook crash in blocking hook — write blocked for safety"
|
|
40
|
+
REASON_JOIN_SEPARATOR = " | "
|
|
41
|
+
CONTEXT_JOIN_SEPARATOR = "\n"
|
|
36
42
|
|
|
37
43
|
WRITE_TOOL_NAME = "Write"
|
|
38
44
|
EDIT_TOOL_NAME = "Edit"
|
|
@@ -8,8 +8,11 @@ import os
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from hooks_constants.dynamic_stderr_handler import DynamicStderrHandler
|
|
11
|
-
from hooks_constants.setup_project_paths_constants import
|
|
12
|
-
|
|
11
|
+
from hooks_constants.setup_project_paths_constants import (
|
|
12
|
+
GIT_DIRECTORY_SEGMENT_NAME,
|
|
13
|
+
META_KEY,
|
|
14
|
+
UTF8_ENCODING,
|
|
15
|
+
)
|
|
13
16
|
|
|
14
17
|
_logger = logging.getLogger("project_paths_reader")
|
|
15
18
|
if not _logger.handlers:
|
|
@@ -34,6 +37,30 @@ def _normalize_path_separators(raw_path: str) -> str:
|
|
|
34
37
|
return os.path.normcase(os.path.normpath(forward_slash_form)).replace("\\", "/")
|
|
35
38
|
|
|
36
39
|
|
|
40
|
+
def find_git_root(start_path: str) -> str | None:
|
|
41
|
+
"""Walk upward for .git; stop at home or filesystem root.
|
|
42
|
+
|
|
43
|
+
Home bound prevents a parent .git above the user home from becoming the
|
|
44
|
+
session repo root (same contract as the untracked-repo SessionStart hook).
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
start_path: Directory to begin the upward walk.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Absolute path of the git root, or None when none is found in bounds.
|
|
51
|
+
"""
|
|
52
|
+
home_directory = Path.home().resolve()
|
|
53
|
+
candidate = Path(start_path).resolve()
|
|
54
|
+
while True:
|
|
55
|
+
if (candidate / GIT_DIRECTORY_SEGMENT_NAME).exists():
|
|
56
|
+
return str(candidate)
|
|
57
|
+
if candidate == home_directory:
|
|
58
|
+
return None
|
|
59
|
+
parent = candidate.parent
|
|
60
|
+
if parent == candidate:
|
|
61
|
+
return None
|
|
62
|
+
candidate = parent
|
|
63
|
+
|
|
37
64
|
def load_registry(config_path: Path | None = None) -> dict[str, str]:
|
|
38
65
|
"""Return the name-to-absolute-path mapping with the _meta key stripped.
|
|
39
66
|
|
|
@@ -65,14 +92,14 @@ def load_registry(config_path: Path | None = None) -> dict[str, str]:
|
|
|
65
92
|
}
|
|
66
93
|
|
|
67
94
|
|
|
68
|
-
def registry_contains_path(
|
|
95
|
+
def registry_contains_path(all_known_registry: dict[str, str], path_to_find: str) -> bool:
|
|
69
96
|
"""Return True when the given path appears as any registry value.
|
|
70
97
|
|
|
71
98
|
Normalizes both sides before comparing so Windows and POSIX separator
|
|
72
99
|
forms of the same path compare equal.
|
|
73
100
|
"""
|
|
74
101
|
normalized_target = _normalize_path_separators(path_to_find)
|
|
75
|
-
for each_registered_path in
|
|
102
|
+
for each_registered_path in all_known_registry.values():
|
|
76
103
|
if _normalize_path_separators(each_registered_path) == normalized_target:
|
|
77
104
|
return True
|
|
78
105
|
return False
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Thresholds and paths for prose-matcher precision measurement (OP-07B).
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
labeled count < SAMPLE_FLOOR_PER_MATCHER -> remain advisory
|
|
6
|
+
precision >= KEEP_PRECISION_FLOOR -> keep
|
|
7
|
+
precision >= NARROW_PRECISION_FLOOR -> narrow
|
|
8
|
+
otherwise -> drop
|
|
9
|
+
|
|
10
|
+
No matcher becomes hard-blocking from historical labels alone; classification
|
|
11
|
+
only records keep/narrow/drop/advisory for the observation window.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
ADVISORY_LOG_RELATIVE_PATH = ".claude/logs/prose-matcher-advisory.jsonl"
|
|
17
|
+
SAMPLE_FLOOR_PER_MATCHER = 30
|
|
18
|
+
KEEP_PRECISION_FLOOR = 0.7
|
|
19
|
+
NARROW_PRECISION_FLOOR = 0.4
|
|
20
|
+
MATCHER_ID_PLAIN_LANGUAGE_HEAVY_WORD = "plain_language_heavy_word"
|
|
21
|
+
MATCHER_ID_HEDGING_WORD = "hedging_word"
|
|
22
|
+
ALL_KNOWN_MATCHER_IDS = (
|
|
23
|
+
MATCHER_ID_PLAIN_LANGUAGE_HEAVY_WORD,
|
|
24
|
+
MATCHER_ID_HEDGING_WORD,
|
|
25
|
+
)
|
|
26
|
+
DECISION_TRUE_POSITIVE = "true_positive"
|
|
27
|
+
DECISION_FALSE_POSITIVE = "false_positive"
|
|
28
|
+
DECISION_UNLABELED = "unlabeled"
|
|
29
|
+
ALL_LABEL_DECISIONS = (
|
|
30
|
+
DECISION_TRUE_POSITIVE,
|
|
31
|
+
DECISION_FALSE_POSITIVE,
|
|
32
|
+
DECISION_UNLABELED,
|
|
33
|
+
)
|
|
34
|
+
CLASSIFICATION_KEEP = "keep"
|
|
35
|
+
CLASSIFICATION_NARROW = "narrow"
|
|
36
|
+
CLASSIFICATION_DROP = "drop"
|
|
37
|
+
CLASSIFICATION_ADVISORY = "advisory"
|
|
38
|
+
CONTEXT_FINGERPRINT_HEX_LENGTH = 16
|
|
39
|
+
MAXIMUM_ADVISORY_EMITS_PER_CALL = 5
|
|
40
|
+
ADVISORY_CONTEXT_SNIPPET_MAX_CHARS = 120
|