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,362 @@
|
|
|
1
|
+
"""Machine-readable Opus effort evaluation for e-code-review.
|
|
2
|
+
|
|
3
|
+
Freezes easy / medium / demanding fixtures, validates evaluation rows, and
|
|
4
|
+
publishes a recommendation that cites completed rows. Live paid runs are
|
|
5
|
+
optional — offline tests feed synthetic rows through the same functions::
|
|
6
|
+
|
|
7
|
+
load fixtures → validate rows → recommend lowest effort that holds quality
|
|
8
|
+
|
|
9
|
+
Thinking stays enabled; effort is the only planned cost and latency lever.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Mapping, MutableMapping, Sequence
|
|
18
|
+
|
|
19
|
+
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
20
|
+
if str(_SCRIPTS_DIRECTORY / "config") not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(_SCRIPTS_DIRECTORY / "config"))
|
|
22
|
+
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
23
|
+
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
24
|
+
|
|
25
|
+
from e_code_review_effort_constants import (
|
|
26
|
+
ALL_EFFORT_LEVELS,
|
|
27
|
+
ALL_EFFORT_RANK_BY_NAME,
|
|
28
|
+
ALL_FIXTURE_BANDS,
|
|
29
|
+
ALL_REQUIRED_ROW_KEYS,
|
|
30
|
+
ALL_SCORE_ROW_KEYS,
|
|
31
|
+
ALL_SKILL_EFFORT_FOR_EVALUATION_EFFORT,
|
|
32
|
+
ALL_SKILL_EFFORT_LEVELS,
|
|
33
|
+
COST_LATENCY_LEVER,
|
|
34
|
+
EVALUATION_EVIDENCE_FILENAME,
|
|
35
|
+
EVALUATION_SCHEMA_VERSION,
|
|
36
|
+
FIXTURES_DIRECTORY_NAME,
|
|
37
|
+
JSON_SUFFIX,
|
|
38
|
+
LATENCY_MS_ROW_KEY,
|
|
39
|
+
MINIMUM_QUALITY_HOLD_SCORE,
|
|
40
|
+
THINKING_ENABLED_DEFAULT,
|
|
41
|
+
VISIBLE_TOKENS_ROW_KEY,
|
|
42
|
+
WORKFLOW_FAMILY_E_CODE_REVIEW,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def fixtures_directory() -> Path:
|
|
47
|
+
"""Return the frozen fixture directory next to this module.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Absolute path to the fixtures directory.
|
|
51
|
+
"""
|
|
52
|
+
return _SCRIPTS_DIRECTORY / FIXTURES_DIRECTORY_NAME
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_fixtures() -> list[dict[str, object]]:
|
|
56
|
+
"""Load every JSON fixture under the fixtures directory.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Fixture dicts sorted by band then fixture_id.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: When a fixture file is not a JSON object.
|
|
63
|
+
OSError: When a fixture file cannot be read.
|
|
64
|
+
json.JSONDecodeError: When a fixture file is not valid JSON.
|
|
65
|
+
"""
|
|
66
|
+
directory = fixtures_directory()
|
|
67
|
+
all_fixtures: list[dict[str, object]] = []
|
|
68
|
+
for each_path in sorted(directory.glob(f"*{JSON_SUFFIX}")):
|
|
69
|
+
loaded = json.loads(each_path.read_text(encoding="utf-8"))
|
|
70
|
+
if not isinstance(loaded, dict):
|
|
71
|
+
raise ValueError(f"Fixture must be an object: {each_path}")
|
|
72
|
+
all_fixtures.append(loaded)
|
|
73
|
+
all_fixtures.sort(
|
|
74
|
+
key=lambda each_fixture: (
|
|
75
|
+
str(each_fixture.get("band", "")),
|
|
76
|
+
str(each_fixture.get("fixture_id", "")),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
return all_fixtures
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_evaluation_row(all_row_fields: Mapping[str, object]) -> list[str]:
|
|
83
|
+
"""Return human-readable problems for one evaluation row.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
all_row_fields: Candidate evaluation row mapping.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Problem strings; empty when the row is valid.
|
|
90
|
+
"""
|
|
91
|
+
all_problems: list[str] = []
|
|
92
|
+
for each_key in ALL_REQUIRED_ROW_KEYS:
|
|
93
|
+
if each_key not in all_row_fields:
|
|
94
|
+
all_problems.append(f"missing key: {each_key}")
|
|
95
|
+
if all_problems:
|
|
96
|
+
return all_problems
|
|
97
|
+
effort = all_row_fields["effort"]
|
|
98
|
+
if effort not in ALL_EFFORT_LEVELS:
|
|
99
|
+
all_problems.append(f"unknown effort: {effort!r}")
|
|
100
|
+
fixture_band = all_row_fields["fixture_band"]
|
|
101
|
+
if fixture_band not in ALL_FIXTURE_BANDS:
|
|
102
|
+
all_problems.append(f"unknown fixture_band: {fixture_band!r}")
|
|
103
|
+
if all_row_fields["thinking_enabled"] is not True:
|
|
104
|
+
all_problems.append("thinking_enabled must be true on Opus paths")
|
|
105
|
+
for each_score_key in ALL_SCORE_ROW_KEYS:
|
|
106
|
+
score_amount = all_row_fields[each_score_key]
|
|
107
|
+
if isinstance(score_amount, bool) or not isinstance(score_amount, (int, float)):
|
|
108
|
+
all_problems.append(f"{each_score_key} must be numeric")
|
|
109
|
+
elif not 0.0 <= float(score_amount) <= 1.0:
|
|
110
|
+
all_problems.append(f"{each_score_key} out of range [0, 1]")
|
|
111
|
+
for each_count_key in (VISIBLE_TOKENS_ROW_KEY, LATENCY_MS_ROW_KEY):
|
|
112
|
+
count_amount = all_row_fields[each_count_key]
|
|
113
|
+
if (
|
|
114
|
+
isinstance(count_amount, bool)
|
|
115
|
+
or not isinstance(count_amount, (int, float))
|
|
116
|
+
or float(count_amount) < 0
|
|
117
|
+
):
|
|
118
|
+
all_problems.append(f"{each_count_key} must be a non-negative number")
|
|
119
|
+
return all_problems
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def quality_holds(
|
|
123
|
+
all_row_fields: Mapping[str, object],
|
|
124
|
+
minimum_quality: float = MINIMUM_QUALITY_HOLD_SCORE,
|
|
125
|
+
) -> bool:
|
|
126
|
+
"""Return True when quality, recall, and precision all meet the floor.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
all_row_fields: Evaluation row mapping.
|
|
130
|
+
minimum_quality: Floor for quality, recall, and precision.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Whether the row holds quality at the floor.
|
|
134
|
+
"""
|
|
135
|
+
return all(
|
|
136
|
+
float(all_row_fields[each_score_key]) >= minimum_quality
|
|
137
|
+
for each_score_key in ALL_SCORE_ROW_KEYS
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _lowest_holding_row(
|
|
142
|
+
all_band_rows: Sequence[Mapping[str, object]],
|
|
143
|
+
) -> Mapping[str, object]:
|
|
144
|
+
return min(
|
|
145
|
+
all_band_rows,
|
|
146
|
+
key=lambda each_row: ALL_EFFORT_RANK_BY_NAME[str(each_row["effort"])],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def recommend_effort_by_band(
|
|
151
|
+
all_rows: Sequence[Mapping[str, object]],
|
|
152
|
+
minimum_quality: float = MINIMUM_QUALITY_HOLD_SCORE,
|
|
153
|
+
) -> dict[str, object]:
|
|
154
|
+
"""Pick the lowest effort that holds quality for each fixture band.
|
|
155
|
+
|
|
156
|
+
Every recommendation entry cites the evaluation row that justified it.
|
|
157
|
+
Bands with no holding row stay null with an explicit blocker note.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
all_rows: Completed evaluation rows (already validated by the caller).
|
|
161
|
+
minimum_quality: Floor for quality, recall, and precision.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Machine-readable recommendation document.
|
|
165
|
+
"""
|
|
166
|
+
recommendation_by_band: MutableMapping[str, object] = {}
|
|
167
|
+
for each_band in ALL_FIXTURE_BANDS:
|
|
168
|
+
all_holding_rows = [
|
|
169
|
+
each_row
|
|
170
|
+
for each_row in all_rows
|
|
171
|
+
if each_row["fixture_band"] == each_band
|
|
172
|
+
and quality_holds(each_row, minimum_quality)
|
|
173
|
+
]
|
|
174
|
+
if not all_holding_rows:
|
|
175
|
+
recommendation_by_band[each_band] = {
|
|
176
|
+
"recommended_effort": None,
|
|
177
|
+
"cited_row": None,
|
|
178
|
+
"blocker": "no completed row meets the quality floor",
|
|
179
|
+
}
|
|
180
|
+
continue
|
|
181
|
+
chosen_row = _lowest_holding_row(all_holding_rows)
|
|
182
|
+
recommendation_by_band[each_band] = {
|
|
183
|
+
"recommended_effort": chosen_row["effort"],
|
|
184
|
+
"cited_row": dict(chosen_row),
|
|
185
|
+
"blocker": None,
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
"schema_version": EVALUATION_SCHEMA_VERSION,
|
|
189
|
+
"thinking_enabled": THINKING_ENABLED_DEFAULT,
|
|
190
|
+
"cost_latency_lever": COST_LATENCY_LEVER,
|
|
191
|
+
"defaults_unchanged": True,
|
|
192
|
+
"minimum_quality": minimum_quality,
|
|
193
|
+
"recommendation_by_band": dict(recommendation_by_band),
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_synthetic_row(
|
|
198
|
+
fixture_id: str,
|
|
199
|
+
fixture_band: str,
|
|
200
|
+
effort: str,
|
|
201
|
+
quality_score: float,
|
|
202
|
+
finding_recall: float,
|
|
203
|
+
finding_precision: float,
|
|
204
|
+
visible_tokens: int,
|
|
205
|
+
latency_ms: int,
|
|
206
|
+
) -> dict[str, object]:
|
|
207
|
+
"""Build one evaluation row with thinking enabled.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
fixture_id: Fixture identifier.
|
|
211
|
+
fixture_band: Band name (easy / medium / demanding).
|
|
212
|
+
effort: Effort level name.
|
|
213
|
+
quality_score: Overall quality in [0, 1].
|
|
214
|
+
finding_recall: Recall in [0, 1].
|
|
215
|
+
finding_precision: Precision in [0, 1].
|
|
216
|
+
visible_tokens: Visible token count.
|
|
217
|
+
latency_ms: Latency in milliseconds.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Complete evaluation row mapping.
|
|
221
|
+
"""
|
|
222
|
+
return {
|
|
223
|
+
"fixture_id": fixture_id,
|
|
224
|
+
"fixture_band": fixture_band,
|
|
225
|
+
"effort": effort,
|
|
226
|
+
"quality_score": quality_score,
|
|
227
|
+
"finding_recall": finding_recall,
|
|
228
|
+
"finding_precision": finding_precision,
|
|
229
|
+
VISIBLE_TOKENS_ROW_KEY: visible_tokens,
|
|
230
|
+
LATENCY_MS_ROW_KEY: latency_ms,
|
|
231
|
+
"thinking_enabled": THINKING_ENABLED_DEFAULT,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def evaluation_evidence_path() -> Path:
|
|
236
|
+
"""Return the committed evaluation evidence file path.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Path to ``effort_defaults_evidence.json`` beside this module.
|
|
240
|
+
"""
|
|
241
|
+
return _SCRIPTS_DIRECTORY / EVALUATION_EVIDENCE_FILENAME
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def load_evaluation_evidence() -> dict[str, object]:
|
|
245
|
+
"""Load the committed evaluation evidence document.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Parsed evidence mapping (rows, recommendation, skill defaults).
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
OSError: When the evidence file cannot be read.
|
|
252
|
+
json.JSONDecodeError: When the evidence file is not valid JSON.
|
|
253
|
+
"""
|
|
254
|
+
return json.loads(evaluation_evidence_path().read_text(encoding="utf-8"))
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def map_evaluation_effort_to_skill_level(evaluation_effort: str) -> str:
|
|
258
|
+
"""Map a full evaluation effort name to an e-code-review skill level.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
evaluation_effort: One of low / medium / high / xhigh / max.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
One of low / medium / xhigh.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
ValueError: When the evaluation effort is unknown.
|
|
268
|
+
"""
|
|
269
|
+
skill_effort = ALL_SKILL_EFFORT_FOR_EVALUATION_EFFORT.get(evaluation_effort)
|
|
270
|
+
if skill_effort is None:
|
|
271
|
+
raise ValueError(f"unknown evaluation effort: {evaluation_effort!r}")
|
|
272
|
+
return skill_effort
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def skill_defaults_from_recommendation(
|
|
276
|
+
all_recommendation_fields: Mapping[str, object],
|
|
277
|
+
) -> dict[str, object]:
|
|
278
|
+
"""Build e-code-review skill defaults that each cite a recommendation row.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
all_recommendation_fields: Output of ``recommend_effort_by_band``.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Skill-family defaults document with cited rows per band.
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
TypeError: When recommendation structure is not mapping-shaped.
|
|
288
|
+
ValueError: When a mapped skill effort leaves the skill surface.
|
|
289
|
+
"""
|
|
290
|
+
recommendation_by_band = all_recommendation_fields["recommendation_by_band"]
|
|
291
|
+
if not isinstance(recommendation_by_band, Mapping):
|
|
292
|
+
raise TypeError("recommendation_by_band must be a mapping")
|
|
293
|
+
default_by_band: MutableMapping[str, object] = {}
|
|
294
|
+
for each_band in ALL_FIXTURE_BANDS:
|
|
295
|
+
band_entry = recommendation_by_band[each_band]
|
|
296
|
+
if not isinstance(band_entry, Mapping):
|
|
297
|
+
raise TypeError(f"band entry for {each_band} must be a mapping")
|
|
298
|
+
evaluation_effort = band_entry.get("recommended_effort")
|
|
299
|
+
cited_row = band_entry.get("cited_row")
|
|
300
|
+
if evaluation_effort is None or cited_row is None:
|
|
301
|
+
default_by_band[each_band] = {
|
|
302
|
+
"skill_effort": None,
|
|
303
|
+
"cited_row": None,
|
|
304
|
+
"blocker": band_entry.get("blocker"),
|
|
305
|
+
}
|
|
306
|
+
continue
|
|
307
|
+
skill_effort = map_evaluation_effort_to_skill_level(str(evaluation_effort))
|
|
308
|
+
if skill_effort not in ALL_SKILL_EFFORT_LEVELS:
|
|
309
|
+
raise ValueError(f"skill effort out of surface: {skill_effort!r}")
|
|
310
|
+
default_by_band[each_band] = {
|
|
311
|
+
"skill_effort": skill_effort,
|
|
312
|
+
"evaluation_effort": evaluation_effort,
|
|
313
|
+
"cited_row": dict(cited_row) if isinstance(cited_row, Mapping) else cited_row,
|
|
314
|
+
"blocker": None,
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
"workflow_family": WORKFLOW_FAMILY_E_CODE_REVIEW,
|
|
318
|
+
"thinking_enabled": THINKING_ENABLED_DEFAULT,
|
|
319
|
+
"cost_latency_lever": COST_LATENCY_LEVER,
|
|
320
|
+
"skill_levels": list(ALL_SKILL_EFFORT_LEVELS),
|
|
321
|
+
"default_by_band": dict(default_by_band),
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def resolve_skill_effort_for_band(fixture_band: str) -> str:
|
|
326
|
+
"""Return the evaluation-backed skill effort for one fixture band.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
fixture_band: easy / medium / demanding.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Skill effort level (low / medium / xhigh).
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
ValueError: When the band is unknown or has no holding recommendation.
|
|
336
|
+
KeyError: When the evidence document lacks the band.
|
|
337
|
+
"""
|
|
338
|
+
if fixture_band not in ALL_FIXTURE_BANDS:
|
|
339
|
+
raise ValueError(f"unknown fixture band: {fixture_band!r}")
|
|
340
|
+
evidence = load_evaluation_evidence()
|
|
341
|
+
skill_defaults = evidence["skill_defaults"]
|
|
342
|
+
if not isinstance(skill_defaults, Mapping):
|
|
343
|
+
raise TypeError("skill_defaults must be a mapping")
|
|
344
|
+
default_by_band = skill_defaults["default_by_band"]
|
|
345
|
+
if not isinstance(default_by_band, Mapping):
|
|
346
|
+
raise TypeError("default_by_band must be a mapping")
|
|
347
|
+
band_default = default_by_band[fixture_band]
|
|
348
|
+
if not isinstance(band_default, Mapping):
|
|
349
|
+
raise TypeError(f"default for {fixture_band} must be a mapping")
|
|
350
|
+
skill_effort = band_default.get("skill_effort")
|
|
351
|
+
if skill_effort is None:
|
|
352
|
+
raise ValueError(
|
|
353
|
+
f"no evaluation-backed skill effort for band {fixture_band!r}: "
|
|
354
|
+
f"{band_default.get('blocker')}"
|
|
355
|
+
)
|
|
356
|
+
skill_effort_name = str(skill_effort)
|
|
357
|
+
if skill_effort_name not in ALL_SKILL_EFFORT_LEVELS:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
f"skill effort out of surface for band {fixture_band!r}: "
|
|
360
|
+
f"{skill_effort_name!r}"
|
|
361
|
+
)
|
|
362
|
+
return skill_effort_name
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Collect every real review finding; filter severity only later.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
collection = collect_findings(all_seeded)
|
|
6
|
+
ok: low and nit findings remain in the collection record
|
|
7
|
+
filtered = filter_findings_by_severity(collection, minimum_severity="medium")
|
|
8
|
+
ok: consumer stage drops lower severities; collection is unchanged
|
|
9
|
+
|
|
10
|
+
Collection never drops a real finding by severity. A later consumer stage
|
|
11
|
+
reads the complete collection and may filter for action or display.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
from e_code_review_scripts_constants.finding_pipeline_constants import (
|
|
19
|
+
ALL_COLLECTION_SEVERITIES,
|
|
20
|
+
COLLECTION_STAGE_NAME,
|
|
21
|
+
FILTER_STAGE_NAME,
|
|
22
|
+
FINDING_FIELD_CATEGORY,
|
|
23
|
+
FINDING_FIELD_EVIDENCE,
|
|
24
|
+
FINDING_FIELD_FILE,
|
|
25
|
+
FINDING_FIELD_LINE,
|
|
26
|
+
FINDING_FIELD_SEVERITY,
|
|
27
|
+
ALL_SEVERITY_RANK_BY_TOKEN,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class CollectedFinding:
|
|
33
|
+
"""One real finding retained during the collection stage."""
|
|
34
|
+
|
|
35
|
+
file_path: str
|
|
36
|
+
line_number: int
|
|
37
|
+
severity: str
|
|
38
|
+
category: str
|
|
39
|
+
evidence: str
|
|
40
|
+
|
|
41
|
+
def as_mapping(self) -> dict[str, str | int]:
|
|
42
|
+
"""Return the stable field map every retained finding keeps.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Mapping with file, line, severity, category, and evidence.
|
|
46
|
+
"""
|
|
47
|
+
return {
|
|
48
|
+
FINDING_FIELD_FILE: self.file_path,
|
|
49
|
+
FINDING_FIELD_LINE: self.line_number,
|
|
50
|
+
FINDING_FIELD_SEVERITY: self.severity,
|
|
51
|
+
FINDING_FIELD_CATEGORY: self.category,
|
|
52
|
+
FINDING_FIELD_EVIDENCE: self.evidence,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class FindingCollection:
|
|
58
|
+
"""Unfiltered collection record of every real finding."""
|
|
59
|
+
|
|
60
|
+
stage_name: str
|
|
61
|
+
all_findings: tuple[CollectedFinding, ...]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class SeverityFilterView:
|
|
66
|
+
"""Severity-filtered view produced by a separate consumer stage."""
|
|
67
|
+
|
|
68
|
+
stage_name: str
|
|
69
|
+
minimum_severity: str
|
|
70
|
+
all_findings: tuple[CollectedFinding, ...]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def collect_findings(
|
|
74
|
+
all_findings: list[CollectedFinding] | tuple[CollectedFinding, ...],
|
|
75
|
+
) -> FindingCollection:
|
|
76
|
+
"""Retain every real finding with its file, line, evidence, and category.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
all_findings: Seeded real findings of any legal severity.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Collection stage record holding every input finding.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: When a finding lacks a required field or legal severity.
|
|
86
|
+
"""
|
|
87
|
+
all_retained: list[CollectedFinding] = []
|
|
88
|
+
for each_finding in all_findings:
|
|
89
|
+
if not each_finding.file_path:
|
|
90
|
+
raise ValueError("finding requires a non-empty file path")
|
|
91
|
+
if each_finding.line_number < 1:
|
|
92
|
+
raise ValueError("finding requires a positive line number")
|
|
93
|
+
if not each_finding.category:
|
|
94
|
+
raise ValueError("finding requires a non-empty category")
|
|
95
|
+
if not each_finding.evidence:
|
|
96
|
+
raise ValueError("finding requires non-empty evidence")
|
|
97
|
+
if each_finding.severity not in ALL_COLLECTION_SEVERITIES:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"unknown collection severity: {each_finding.severity!r}"
|
|
100
|
+
)
|
|
101
|
+
all_retained.append(each_finding)
|
|
102
|
+
return FindingCollection(
|
|
103
|
+
stage_name=COLLECTION_STAGE_NAME,
|
|
104
|
+
all_findings=tuple(all_retained),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def filter_findings_by_severity(
|
|
109
|
+
collection: FindingCollection,
|
|
110
|
+
*,
|
|
111
|
+
minimum_severity: str,
|
|
112
|
+
) -> SeverityFilterView:
|
|
113
|
+
"""Filter a complete collection by severity in a separate consumer stage.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
collection: Unfiltered collection record from ``collect_findings``.
|
|
117
|
+
minimum_severity: Lowest severity to keep (inclusive).
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Filter-stage view. The input collection record is not mutated.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ValueError: When ``minimum_severity`` or a finding severity is unknown.
|
|
124
|
+
"""
|
|
125
|
+
if minimum_severity not in ALL_SEVERITY_RANK_BY_TOKEN:
|
|
126
|
+
raise ValueError(f"unknown minimum severity: {minimum_severity!r}")
|
|
127
|
+
minimum_rank = ALL_SEVERITY_RANK_BY_TOKEN[minimum_severity]
|
|
128
|
+
all_kept: list[CollectedFinding] = []
|
|
129
|
+
for each_finding in collection.all_findings:
|
|
130
|
+
if each_finding.severity not in ALL_SEVERITY_RANK_BY_TOKEN:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"unknown collection severity: {each_finding.severity!r}"
|
|
133
|
+
)
|
|
134
|
+
if ALL_SEVERITY_RANK_BY_TOKEN[each_finding.severity] >= minimum_rank:
|
|
135
|
+
all_kept.append(each_finding)
|
|
136
|
+
return SeverityFilterView(
|
|
137
|
+
stage_name=FILTER_STAGE_NAME,
|
|
138
|
+
minimum_severity=minimum_severity,
|
|
139
|
+
all_findings=tuple(all_kept),
|
|
140
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture_id": "demanding-multi-surface",
|
|
3
|
+
"band": "demanding",
|
|
4
|
+
"description": "Cross-file review with three seeded findings across hooks and tests.",
|
|
5
|
+
"target_summary": "Hook change drifts docstring, drops a paired test path, and uses a banned identifier.",
|
|
6
|
+
"seeded_findings": [
|
|
7
|
+
{
|
|
8
|
+
"file": "hooks/blocking/sample_gate.py",
|
|
9
|
+
"line": 88,
|
|
10
|
+
"category": "docstring-vs-impl",
|
|
11
|
+
"severity": "high"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"file": "hooks/blocking/test_sample_gate.py",
|
|
15
|
+
"line": 1,
|
|
16
|
+
"category": "paired-test-coverage",
|
|
17
|
+
"severity": "blocker"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"file": "hooks/blocking/sample_gate.py",
|
|
21
|
+
"line": 15,
|
|
22
|
+
"category": "banned-identifier",
|
|
23
|
+
"severity": "medium"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture_id": "easy-comment-preservation",
|
|
3
|
+
"band": "easy",
|
|
4
|
+
"description": "Single-file comment-preservation miss with one seeded finding.",
|
|
5
|
+
"target_summary": "Production helper drops an existing inline comment on an unchanged line.",
|
|
6
|
+
"seeded_findings": [
|
|
7
|
+
{
|
|
8
|
+
"file": "sample_helper.py",
|
|
9
|
+
"line": 12,
|
|
10
|
+
"category": "comment-preservation",
|
|
11
|
+
"severity": "medium"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture_id": "medium-constants-and-types",
|
|
3
|
+
"band": "medium",
|
|
4
|
+
"description": "Two seeded findings: bare type-ignore and a magic timeout literal.",
|
|
5
|
+
"target_summary": "Service module adds a bare type ignore and a hard-coded timeout.",
|
|
6
|
+
"seeded_findings": [
|
|
7
|
+
{
|
|
8
|
+
"file": "order_service.py",
|
|
9
|
+
"line": 24,
|
|
10
|
+
"category": "type-ignore-justification",
|
|
11
|
+
"severity": "high"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"file": "order_service.py",
|
|
15
|
+
"line": 41,
|
|
16
|
+
"category": "magic-value",
|
|
17
|
+
"severity": "medium"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -197,21 +197,30 @@ def run_medium_review(
|
|
|
197
197
|
batch.is_rejected = True
|
|
198
198
|
batch.rejection_reason = "finder_head_mismatch"
|
|
199
199
|
return batch
|
|
200
|
-
all_angles =
|
|
200
|
+
all_angles = sorted({each.angle for each in all_finder_candidates})
|
|
201
|
+
finder_count = len(all_angles)
|
|
202
|
+
all_identities = {
|
|
203
|
+
(
|
|
204
|
+
each.angle,
|
|
205
|
+
each.leader_socket,
|
|
206
|
+
each.advisor_session_id,
|
|
207
|
+
each.worktree_path,
|
|
208
|
+
)
|
|
209
|
+
for each in all_finder_candidates
|
|
210
|
+
}
|
|
201
211
|
all_sockets = {each.leader_socket for each in all_finder_candidates}
|
|
202
212
|
all_sessions = {each.advisor_session_id for each in all_finder_candidates}
|
|
203
213
|
all_worktrees = {each.worktree_path for each in all_finder_candidates}
|
|
204
214
|
if (
|
|
205
|
-
len(
|
|
206
|
-
or len(
|
|
207
|
-
or len(
|
|
215
|
+
len(all_identities) != finder_count
|
|
216
|
+
or len(all_sockets) != finder_count
|
|
217
|
+
or len(all_sessions) != finder_count
|
|
218
|
+
or len(all_worktrees) != finder_count
|
|
208
219
|
):
|
|
209
220
|
batch.is_rejected = True
|
|
210
221
|
batch.rejection_reason = "non_unique_finder_identity"
|
|
211
222
|
return batch
|
|
212
|
-
require_exact_finder_set(tuple(
|
|
213
|
-
if len(set(all_angles)) != MEDIUM_REVIEW_FINDER_COUNT:
|
|
214
|
-
raise ValueError("each finder angle must run exactly once")
|
|
223
|
+
require_exact_finder_set(tuple(all_angles))
|
|
215
224
|
deduped = deduplicate_candidates(all_finder_candidates)
|
|
216
225
|
batch.all_retained_findings = retain_verified_findings(
|
|
217
226
|
all_candidates=deduped,
|