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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Red fixtures: low-severity findings survive collection; filter is separate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
11
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
13
|
+
|
|
14
|
+
from e_code_review_scripts_constants.finding_pipeline_constants import (
|
|
15
|
+
ALL_COLLECTION_SEVERITIES,
|
|
16
|
+
COLLECTION_STAGE_NAME,
|
|
17
|
+
FILTER_STAGE_NAME,
|
|
18
|
+
FINDING_FIELD_CATEGORY,
|
|
19
|
+
FINDING_FIELD_EVIDENCE,
|
|
20
|
+
FINDING_FIELD_FILE,
|
|
21
|
+
FINDING_FIELD_LINE,
|
|
22
|
+
FINDING_FIELD_SEVERITY,
|
|
23
|
+
REPORT_EVERY_FINDING_INSTRUCTION,
|
|
24
|
+
SEVERITY_BLOCKER,
|
|
25
|
+
SEVERITY_HIGH,
|
|
26
|
+
SEVERITY_LOW,
|
|
27
|
+
SEVERITY_MEDIUM,
|
|
28
|
+
SEVERITY_NIT,
|
|
29
|
+
)
|
|
30
|
+
from finding_pipeline import (
|
|
31
|
+
CollectedFinding,
|
|
32
|
+
FindingCollection,
|
|
33
|
+
collect_findings,
|
|
34
|
+
filter_findings_by_severity,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _seeded_findings() -> list[CollectedFinding]:
|
|
39
|
+
return [
|
|
40
|
+
CollectedFinding(
|
|
41
|
+
file_path="pkg/a.py",
|
|
42
|
+
line_number=10,
|
|
43
|
+
severity=SEVERITY_BLOCKER,
|
|
44
|
+
category="correctness",
|
|
45
|
+
evidence="raises on empty input",
|
|
46
|
+
),
|
|
47
|
+
CollectedFinding(
|
|
48
|
+
file_path="pkg/b.py",
|
|
49
|
+
line_number=20,
|
|
50
|
+
severity=SEVERITY_HIGH,
|
|
51
|
+
category="security",
|
|
52
|
+
evidence="shell=True with user text",
|
|
53
|
+
),
|
|
54
|
+
CollectedFinding(
|
|
55
|
+
file_path="pkg/c.py",
|
|
56
|
+
line_number=30,
|
|
57
|
+
severity=SEVERITY_MEDIUM,
|
|
58
|
+
category="api-contracts",
|
|
59
|
+
evidence="return type narrowed",
|
|
60
|
+
),
|
|
61
|
+
CollectedFinding(
|
|
62
|
+
file_path="pkg/d.py",
|
|
63
|
+
line_number=40,
|
|
64
|
+
severity=SEVERITY_LOW,
|
|
65
|
+
category="simplification",
|
|
66
|
+
evidence="duplicated helper body",
|
|
67
|
+
),
|
|
68
|
+
CollectedFinding(
|
|
69
|
+
file_path="pkg/e.py",
|
|
70
|
+
line_number=50,
|
|
71
|
+
severity=SEVERITY_NIT,
|
|
72
|
+
category="conventions",
|
|
73
|
+
evidence="typo in comment",
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_collection_retains_every_seeded_severity_including_low_and_nit() -> None:
|
|
79
|
+
all_seeded = _seeded_findings()
|
|
80
|
+
collection = collect_findings(all_seeded)
|
|
81
|
+
|
|
82
|
+
assert collection.stage_name == COLLECTION_STAGE_NAME
|
|
83
|
+
all_severities = {
|
|
84
|
+
each.severity for each in collection.all_findings
|
|
85
|
+
}
|
|
86
|
+
assert all_severities == set(ALL_COLLECTION_SEVERITIES)
|
|
87
|
+
assert len(collection.all_findings) == len(all_seeded)
|
|
88
|
+
low_finding = next(
|
|
89
|
+
each for each in collection.all_findings if each.severity == SEVERITY_LOW
|
|
90
|
+
)
|
|
91
|
+
assert low_finding.file_path == "pkg/d.py"
|
|
92
|
+
assert low_finding.line_number == 40
|
|
93
|
+
assert low_finding.evidence == "duplicated helper body"
|
|
94
|
+
assert low_finding.category == "simplification"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_each_collected_finding_keeps_file_line_evidence_and_category() -> None:
|
|
98
|
+
collection = collect_findings(_seeded_findings())
|
|
99
|
+
for each_finding in collection.all_findings:
|
|
100
|
+
as_mapping = each_finding.as_mapping()
|
|
101
|
+
assert as_mapping[FINDING_FIELD_FILE]
|
|
102
|
+
assert as_mapping[FINDING_FIELD_LINE] > 0
|
|
103
|
+
assert as_mapping[FINDING_FIELD_EVIDENCE]
|
|
104
|
+
assert as_mapping[FINDING_FIELD_CATEGORY]
|
|
105
|
+
assert as_mapping[FINDING_FIELD_SEVERITY] in ALL_COLLECTION_SEVERITIES
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_filter_is_explicit_later_stage_and_does_not_mutate_collection() -> None:
|
|
109
|
+
collection = collect_findings(_seeded_findings())
|
|
110
|
+
collected_snapshot = [
|
|
111
|
+
(
|
|
112
|
+
each.file_path,
|
|
113
|
+
each.line_number,
|
|
114
|
+
each.severity,
|
|
115
|
+
each.category,
|
|
116
|
+
each.evidence,
|
|
117
|
+
)
|
|
118
|
+
for each in collection.all_findings
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
filtered = filter_findings_by_severity(
|
|
122
|
+
collection,
|
|
123
|
+
minimum_severity=SEVERITY_MEDIUM,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
assert filtered.stage_name == FILTER_STAGE_NAME
|
|
127
|
+
all_filtered_severities = {each.severity for each in filtered.all_findings}
|
|
128
|
+
assert SEVERITY_LOW not in all_filtered_severities
|
|
129
|
+
assert SEVERITY_NIT not in all_filtered_severities
|
|
130
|
+
assert SEVERITY_MEDIUM in all_filtered_severities
|
|
131
|
+
assert SEVERITY_BLOCKER in all_filtered_severities
|
|
132
|
+
|
|
133
|
+
after_filter_snapshot = [
|
|
134
|
+
(
|
|
135
|
+
each.file_path,
|
|
136
|
+
each.line_number,
|
|
137
|
+
each.severity,
|
|
138
|
+
each.category,
|
|
139
|
+
each.evidence,
|
|
140
|
+
)
|
|
141
|
+
for each in collection.all_findings
|
|
142
|
+
]
|
|
143
|
+
assert after_filter_snapshot == collected_snapshot
|
|
144
|
+
assert len(collection.all_findings) == 5
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_filter_rejects_unknown_minimum_severity() -> None:
|
|
148
|
+
collection = collect_findings(_seeded_findings())
|
|
149
|
+
with pytest.raises(ValueError, match="unknown minimum severity"):
|
|
150
|
+
filter_findings_by_severity(collection, minimum_severity="P1")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_filter_rejects_unknown_finding_severity_with_value_error() -> None:
|
|
154
|
+
collection = FindingCollection(
|
|
155
|
+
stage_name=COLLECTION_STAGE_NAME,
|
|
156
|
+
all_findings=(
|
|
157
|
+
CollectedFinding(
|
|
158
|
+
file_path="pkg/x.py",
|
|
159
|
+
line_number=1,
|
|
160
|
+
severity="P0",
|
|
161
|
+
category="correctness",
|
|
162
|
+
evidence="bypass collect",
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
with pytest.raises(ValueError, match="unknown collection severity"):
|
|
167
|
+
filter_findings_by_severity(collection, minimum_severity=SEVERITY_LOW)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_collect_rejects_finding_missing_required_fields() -> None:
|
|
171
|
+
with pytest.raises(ValueError, match="file"):
|
|
172
|
+
collect_findings(
|
|
173
|
+
[
|
|
174
|
+
CollectedFinding(
|
|
175
|
+
file_path="",
|
|
176
|
+
line_number=1,
|
|
177
|
+
severity=SEVERITY_LOW,
|
|
178
|
+
category="correctness",
|
|
179
|
+
evidence="x",
|
|
180
|
+
)
|
|
181
|
+
]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_report_every_finding_instruction_names_all_severities() -> None:
|
|
186
|
+
for each_severity in ALL_COLLECTION_SEVERITIES:
|
|
187
|
+
assert each_severity in REPORT_EVERY_FINDING_INSTRUCTION
|
|
188
|
+
assert "consumer stage" in REPORT_EVERY_FINDING_INSTRUCTION
|
|
189
|
+
assert "Do not drop findings by severity during collection" in (
|
|
190
|
+
REPORT_EVERY_FINDING_INSTRUCTION
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_collection_record_type_is_immutable() -> None:
|
|
195
|
+
collection = collect_findings(_seeded_findings())
|
|
196
|
+
assert isinstance(collection, FindingCollection)
|
|
197
|
+
assert collection.stage_name == COLLECTION_STAGE_NAME
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Behavioral tests for Grok medium-review discovery and verification."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_SCRIPTS = Path(__file__).resolve().parent
|
|
11
|
+
if str(_SCRIPTS) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_SCRIPTS))
|
|
13
|
+
|
|
14
|
+
from e_code_review_scripts_constants.grok_code_review_constants import ( # noqa: E402
|
|
15
|
+
ALL_MEDIUM_FINDER_ANGLES,
|
|
16
|
+
MEDIUM_REVIEW_FINDER_COUNT,
|
|
17
|
+
VERDICT_CONFIRMED,
|
|
18
|
+
VERDICT_PLAUSIBLE,
|
|
19
|
+
VERDICT_REFUTED,
|
|
20
|
+
)
|
|
21
|
+
from grok_code_review import ( # noqa: E402
|
|
22
|
+
FinderCandidate,
|
|
23
|
+
deduplicate_candidates,
|
|
24
|
+
require_exact_finder_set,
|
|
25
|
+
retain_verified_findings,
|
|
26
|
+
run_medium_review,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _candidate(
|
|
31
|
+
angle: str,
|
|
32
|
+
*,
|
|
33
|
+
file_path: str = "a.py",
|
|
34
|
+
line_number: int = 1,
|
|
35
|
+
mechanism: str = "m",
|
|
36
|
+
scenario: str = "short",
|
|
37
|
+
suffix: str = "",
|
|
38
|
+
head: str = "head1",
|
|
39
|
+
) -> FinderCandidate:
|
|
40
|
+
token = suffix or angle
|
|
41
|
+
return FinderCandidate(
|
|
42
|
+
angle=angle,
|
|
43
|
+
file_path=file_path,
|
|
44
|
+
line_number=line_number,
|
|
45
|
+
mechanism=mechanism,
|
|
46
|
+
scenario=scenario,
|
|
47
|
+
worktree_path=f"/wt/{token}",
|
|
48
|
+
leader_socket=f"sock-{token}",
|
|
49
|
+
advisor_session_id=f"adv-{token}",
|
|
50
|
+
reviewed_head=head,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_require_exact_finder_set_accepts_all_angles() -> None:
|
|
55
|
+
require_exact_finder_set(ALL_MEDIUM_FINDER_ANGLES)
|
|
56
|
+
with pytest.raises(ValueError):
|
|
57
|
+
require_exact_finder_set(ALL_MEDIUM_FINDER_ANGLES[:-1])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_deduplicate_keeps_most_concrete_scenario() -> None:
|
|
61
|
+
short = _candidate("correctness", scenario="short")
|
|
62
|
+
long = _candidate("security", scenario="much more concrete failure path")
|
|
63
|
+
# same key different angles - force same file/line/mech
|
|
64
|
+
short = FinderCandidate(
|
|
65
|
+
angle="correctness",
|
|
66
|
+
file_path="a.py",
|
|
67
|
+
line_number=3,
|
|
68
|
+
mechanism="null",
|
|
69
|
+
scenario="x",
|
|
70
|
+
worktree_path="/wt/a",
|
|
71
|
+
leader_socket="s1",
|
|
72
|
+
advisor_session_id="a1",
|
|
73
|
+
reviewed_head="h",
|
|
74
|
+
)
|
|
75
|
+
long = FinderCandidate(
|
|
76
|
+
angle="security",
|
|
77
|
+
file_path="a.py",
|
|
78
|
+
line_number=3,
|
|
79
|
+
mechanism="null",
|
|
80
|
+
scenario="concrete long scenario",
|
|
81
|
+
worktree_path="/wt/b",
|
|
82
|
+
leader_socket="s2",
|
|
83
|
+
advisor_session_id="a2",
|
|
84
|
+
reviewed_head="h",
|
|
85
|
+
)
|
|
86
|
+
kept = deduplicate_candidates([short, long])
|
|
87
|
+
assert len(kept) == 1
|
|
88
|
+
assert kept[0].scenario == "concrete long scenario"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_run_medium_review_happy_path() -> None:
|
|
92
|
+
head = "abc123"
|
|
93
|
+
all_finders = [
|
|
94
|
+
_candidate(each_angle, head=head, suffix=each_angle)
|
|
95
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
96
|
+
]
|
|
97
|
+
# make unique keys per angle
|
|
98
|
+
all_finders = [
|
|
99
|
+
FinderCandidate(
|
|
100
|
+
angle=each_angle,
|
|
101
|
+
file_path=f"{each_angle}.py",
|
|
102
|
+
line_number=1,
|
|
103
|
+
mechanism="m",
|
|
104
|
+
scenario=f"scenario {each_angle}",
|
|
105
|
+
worktree_path=f"/wt/{each_angle}",
|
|
106
|
+
leader_socket=f"sock-{each_angle}",
|
|
107
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
108
|
+
reviewed_head=head,
|
|
109
|
+
)
|
|
110
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
111
|
+
]
|
|
112
|
+
verdicts = {
|
|
113
|
+
(f"{each}.py", 1, "m"): VERDICT_CONFIRMED
|
|
114
|
+
for each in ALL_MEDIUM_FINDER_ANGLES
|
|
115
|
+
}
|
|
116
|
+
severities = {
|
|
117
|
+
(f"{each}.py", 1, "m"): "medium" for each in ALL_MEDIUM_FINDER_ANGLES
|
|
118
|
+
}
|
|
119
|
+
# refute one
|
|
120
|
+
first = ALL_MEDIUM_FINDER_ANGLES[0]
|
|
121
|
+
verdicts[(f"{first}.py", 1, "m")] = VERDICT_REFUTED
|
|
122
|
+
batch = run_medium_review(
|
|
123
|
+
target_head=head,
|
|
124
|
+
diff_base="base",
|
|
125
|
+
all_finder_candidates=all_finders,
|
|
126
|
+
verdict_by_key=verdicts,
|
|
127
|
+
severity_by_key=severities,
|
|
128
|
+
live_head=head,
|
|
129
|
+
)
|
|
130
|
+
assert batch.is_rejected is False
|
|
131
|
+
assert len(batch.all_finder_angles) == MEDIUM_REVIEW_FINDER_COUNT
|
|
132
|
+
assert all(each.reviewed_head == head for each in batch.all_retained_findings)
|
|
133
|
+
assert all(each.verdict in {VERDICT_CONFIRMED, VERDICT_PLAUSIBLE} for each in batch.all_retained_findings)
|
|
134
|
+
assert len(batch.all_retained_findings) == MEDIUM_REVIEW_FINDER_COUNT - 1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _one_candidate_per_angle(head: str) -> list[FinderCandidate]:
|
|
138
|
+
return [
|
|
139
|
+
FinderCandidate(
|
|
140
|
+
angle=each_angle,
|
|
141
|
+
file_path=f"{each_angle}.py",
|
|
142
|
+
line_number=1,
|
|
143
|
+
mechanism="m",
|
|
144
|
+
scenario=f"scenario {each_angle}",
|
|
145
|
+
worktree_path=f"/wt/{each_angle}",
|
|
146
|
+
leader_socket=f"sock-{each_angle}",
|
|
147
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
148
|
+
reviewed_head=head,
|
|
149
|
+
)
|
|
150
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_a_finder_reporting_several_candidates_is_not_a_identity_clash() -> None:
|
|
155
|
+
head = "abc123"
|
|
156
|
+
all_finders = _one_candidate_per_angle(head)
|
|
157
|
+
first = all_finders[0]
|
|
158
|
+
second_from_first_finder = FinderCandidate(
|
|
159
|
+
angle=first.angle,
|
|
160
|
+
file_path="second.py",
|
|
161
|
+
line_number=9,
|
|
162
|
+
mechanism="m",
|
|
163
|
+
scenario="a second finding from the same finder",
|
|
164
|
+
worktree_path=first.worktree_path,
|
|
165
|
+
leader_socket=first.leader_socket,
|
|
166
|
+
advisor_session_id=first.advisor_session_id,
|
|
167
|
+
reviewed_head=head,
|
|
168
|
+
)
|
|
169
|
+
all_finders.append(second_from_first_finder)
|
|
170
|
+
all_keys = [
|
|
171
|
+
(each.file_path, each.line_number, each.mechanism) for each in all_finders
|
|
172
|
+
]
|
|
173
|
+
batch = run_medium_review(
|
|
174
|
+
target_head=head,
|
|
175
|
+
diff_base="base",
|
|
176
|
+
all_finder_candidates=all_finders,
|
|
177
|
+
verdict_by_key={each_key: VERDICT_CONFIRMED for each_key in all_keys},
|
|
178
|
+
severity_by_key={each_key: "medium" for each_key in all_keys},
|
|
179
|
+
live_head=head,
|
|
180
|
+
)
|
|
181
|
+
assert batch.is_rejected is False
|
|
182
|
+
assert batch.rejection_reason is None
|
|
183
|
+
assert len(batch.all_retained_findings) == len(all_finders)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_two_finders_sharing_one_socket_reject_the_batch() -> None:
|
|
187
|
+
head = "abc123"
|
|
188
|
+
all_finders = _one_candidate_per_angle(head)
|
|
189
|
+
first = all_finders[0]
|
|
190
|
+
second = all_finders[1]
|
|
191
|
+
all_finders[1] = FinderCandidate(
|
|
192
|
+
angle=second.angle,
|
|
193
|
+
file_path=second.file_path,
|
|
194
|
+
line_number=second.line_number,
|
|
195
|
+
mechanism=second.mechanism,
|
|
196
|
+
scenario=second.scenario,
|
|
197
|
+
worktree_path=second.worktree_path,
|
|
198
|
+
leader_socket=first.leader_socket,
|
|
199
|
+
advisor_session_id=second.advisor_session_id,
|
|
200
|
+
reviewed_head=head,
|
|
201
|
+
)
|
|
202
|
+
batch = run_medium_review(
|
|
203
|
+
target_head=head,
|
|
204
|
+
diff_base="base",
|
|
205
|
+
all_finder_candidates=all_finders,
|
|
206
|
+
verdict_by_key={},
|
|
207
|
+
severity_by_key={},
|
|
208
|
+
live_head=head,
|
|
209
|
+
)
|
|
210
|
+
assert batch.is_rejected is True
|
|
211
|
+
assert batch.rejection_reason == "non_unique_finder_identity"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_head_drift_rejects_batch() -> None:
|
|
215
|
+
head = "h1"
|
|
216
|
+
all_finders = [
|
|
217
|
+
FinderCandidate(
|
|
218
|
+
angle=each_angle,
|
|
219
|
+
file_path=f"{each_angle}.py",
|
|
220
|
+
line_number=1,
|
|
221
|
+
mechanism="m",
|
|
222
|
+
scenario="s",
|
|
223
|
+
worktree_path=f"/wt/{each_angle}",
|
|
224
|
+
leader_socket=f"sock-{each_angle}",
|
|
225
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
226
|
+
reviewed_head=head,
|
|
227
|
+
)
|
|
228
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
229
|
+
]
|
|
230
|
+
batch = run_medium_review(
|
|
231
|
+
target_head=head,
|
|
232
|
+
diff_base="b",
|
|
233
|
+
all_finder_candidates=all_finders,
|
|
234
|
+
verdict_by_key={},
|
|
235
|
+
severity_by_key={},
|
|
236
|
+
live_head="other",
|
|
237
|
+
)
|
|
238
|
+
assert batch.is_rejected is True
|
|
239
|
+
assert batch.rejection_reason == "head_drift"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_advisor_blocked_rejects_batch() -> None:
|
|
243
|
+
head = "h1"
|
|
244
|
+
all_finders = [
|
|
245
|
+
FinderCandidate(
|
|
246
|
+
angle=each_angle,
|
|
247
|
+
file_path=f"{each_angle}.py",
|
|
248
|
+
line_number=1,
|
|
249
|
+
mechanism="m",
|
|
250
|
+
scenario="s",
|
|
251
|
+
worktree_path=f"/wt/{each_angle}",
|
|
252
|
+
leader_socket=f"sock-{each_angle}",
|
|
253
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
254
|
+
reviewed_head=head,
|
|
255
|
+
)
|
|
256
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
257
|
+
]
|
|
258
|
+
batch = run_medium_review(
|
|
259
|
+
target_head=head,
|
|
260
|
+
diff_base="b",
|
|
261
|
+
all_finder_candidates=all_finders,
|
|
262
|
+
verdict_by_key={},
|
|
263
|
+
severity_by_key={},
|
|
264
|
+
live_head=head,
|
|
265
|
+
is_any_advisor_blocked=True,
|
|
266
|
+
)
|
|
267
|
+
assert batch.is_rejected is True
|
|
268
|
+
assert batch.rejection_reason == "advisor_blocked"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_retain_verified_findings_keeps_confirmed() -> None:
|
|
272
|
+
candidate = FinderCandidate(
|
|
273
|
+
angle="correctness",
|
|
274
|
+
file_path="a.py",
|
|
275
|
+
line_number=2,
|
|
276
|
+
mechanism="null",
|
|
277
|
+
scenario="boom",
|
|
278
|
+
worktree_path="/wt/x",
|
|
279
|
+
leader_socket="s",
|
|
280
|
+
advisor_session_id="a",
|
|
281
|
+
reviewed_head="h",
|
|
282
|
+
)
|
|
283
|
+
kept = retain_verified_findings(
|
|
284
|
+
all_candidates=[candidate],
|
|
285
|
+
verdict_by_key={("a.py", 2, "null"): VERDICT_CONFIRMED},
|
|
286
|
+
severity_by_key={("a.py", 2, "null"): "high"},
|
|
287
|
+
)
|
|
288
|
+
assert len(kept) == 1
|
|
289
|
+
assert kept[0].verdict == VERDICT_CONFIRMED
|
|
@@ -231,3 +231,8 @@ Sibling skill: `/grokify` for a single paste-ready interactive Grok Build handof
|
|
|
231
231
|
|
|
232
232
|
- `SKILL.md` — hub and process.
|
|
233
233
|
- `reference/` — brief templates and flag profiles.
|
|
234
|
+
|
|
235
|
+
## Worker advisors
|
|
236
|
+
|
|
237
|
+
Each grok worker binds a unique Opus-high dvisor_session_id through the lead-supplied dvisor.launcher in the batch spec (committed default is a placeholder). Reports require the same handle\'s ENDORSE (or bounded CORRECTION/PLAN then ENDORSE); STOP or malformed signals end as advisor_blocked.
|
|
238
|
+
|
|
@@ -358,3 +358,8 @@ no open work remains, run `set --status done` before any re-arm attempt.
|
|
|
358
358
|
- `scripts/` — deterministic status_gate.
|
|
359
359
|
- Advisor policy:
|
|
360
360
|
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md).
|
|
361
|
+
|
|
362
|
+
## File-backed run ledger
|
|
363
|
+
|
|
364
|
+
When host task tools are absent, reconcile delegated work through `scripts/grok_run_ledger.py` under the run-state directory (stable task ids, one live owner, unique advisor sessions, dependency blocking, snapshot-drift reopening).
|
|
365
|
+
|
|
@@ -8,10 +8,10 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from
|
|
11
|
+
_scripts_directory = str(Path(__file__).resolve().parent)
|
|
12
|
+
if _scripts_directory not in sys.path:
|
|
13
|
+
sys.path.insert(0, _scripts_directory)
|
|
14
|
+
from load_skill_constants import (
|
|
15
15
|
PACKET_CREATION_ERROR_EXIT_CODE,
|
|
16
16
|
PACKET_JSON_INDENT_LEVEL,
|
|
17
17
|
SLUG_PATTERN,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Load plan-to-pr skill constants without colliding with repo-root config.
|
|
2
|
+
|
|
3
|
+
Repo pytest sets ``pythonpath = .``, so a bare ``from config.constants import …``
|
|
4
|
+
can bind the monorepo root ``config`` package instead of this skill's
|
|
5
|
+
``scripts/config/constants.py``. Load the skill file under a unique module name
|
|
6
|
+
and re-export its public names for script imports.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib.util
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_skill_constants_module() -> ModuleType:
|
|
18
|
+
"""Return the skill-local constants module, loading it once per process."""
|
|
19
|
+
skill_constants_module_name = "plan_to_pr_scripts_config_constants"
|
|
20
|
+
skill_constants_path = Path(__file__).resolve().parent / "config" / "constants.py"
|
|
21
|
+
existing_module = sys.modules.get(skill_constants_module_name)
|
|
22
|
+
if existing_module is not None:
|
|
23
|
+
return existing_module
|
|
24
|
+
module_spec = importlib.util.spec_from_file_location(
|
|
25
|
+
skill_constants_module_name, skill_constants_path
|
|
26
|
+
)
|
|
27
|
+
if module_spec is None or module_spec.loader is None:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
f"unable to load plan-to-pr skill constants from {skill_constants_path}"
|
|
30
|
+
)
|
|
31
|
+
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
32
|
+
sys.modules[skill_constants_module_name] = loaded_module
|
|
33
|
+
module_spec.loader.exec_module(loaded_module)
|
|
34
|
+
return loaded_module
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_skill_constants = _load_skill_constants_module()
|
|
38
|
+
for each_constant_name, each_constant_binding in _skill_constants.__dict__.items():
|
|
39
|
+
if each_constant_name.startswith("_"):
|
|
40
|
+
continue
|
|
41
|
+
globals()[each_constant_name] = each_constant_binding
|
|
@@ -9,10 +9,10 @@ import shlex
|
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from
|
|
12
|
+
_scripts_directory = str(Path(__file__).resolve().parent)
|
|
13
|
+
if _scripts_directory not in sys.path:
|
|
14
|
+
sys.path.insert(0, _scripts_directory)
|
|
15
|
+
from load_skill_constants import (
|
|
16
16
|
COMMAND_SEPARATOR,
|
|
17
17
|
HANDOFF_ACCEPTANCE_PREFIX,
|
|
18
18
|
HANDOFF_ALLOWED_FILES_PREFIX,
|
|
@@ -10,7 +10,10 @@ import sys
|
|
|
10
10
|
from collections.abc import Mapping, Sequence
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
_scripts_directory = str(Path(__file__).resolve().parent)
|
|
14
|
+
if _scripts_directory not in sys.path:
|
|
15
|
+
sys.path.insert(0, _scripts_directory)
|
|
16
|
+
from load_skill_constants import (
|
|
14
17
|
ALL_ALLOWED_RECORD_FIELDS,
|
|
15
18
|
ALL_RECORD_FIELDS,
|
|
16
19
|
ALL_REPAIR_FIELDS,
|
|
@@ -8,7 +8,10 @@ import sys
|
|
|
8
8
|
from collections.abc import Mapping, Sequence
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
_scripts_directory = str(Path(__file__).resolve().parent)
|
|
12
|
+
if _scripts_directory not in sys.path:
|
|
13
|
+
sys.path.insert(0, _scripts_directory)
|
|
14
|
+
from load_skill_constants import (
|
|
12
15
|
COMMIT_RANGE_ARGUMENT_INDEX,
|
|
13
16
|
COMMIT_RANGE_SEPARATOR,
|
|
14
17
|
EXIT_CODE_INVALID_SET,
|
|
@@ -16,17 +16,16 @@ Each bypass flag closes one reviewer gate when that reviewer is unavailable.
|
|
|
16
16
|
the Copilot review and pending-review gates. ``--bugteam-post-blocked`` skips
|
|
17
17
|
the bugteam CLEAN-review gate. ``--codex-down`` skips the Codex review gate.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
Bugbot, bugteam, and codex are off by default. Each gate runs only when
|
|
20
|
+
``CLAUDE_REVIEWS_ENABLED`` lists its token, and a token in
|
|
21
|
+
``CLAUDE_REVIEWS_DISABLED`` forces one off even when the opt-in lists it.
|
|
22
|
+
Copilot runs by default and stops only when the disabled list names it. For
|
|
23
|
+
Codex, sticky job-dir state ``codex_down: true`` is a further bypass source
|
|
24
|
+
when the original run could not re-pass ``--codex-down``.
|
|
25
|
+
|
|
26
|
+
Every reviewer waiver is disk-authoritative.
|
|
27
27
|
When ``~/.claude/settings.json`` is readable, its env block is the single source.
|
|
28
28
|
The frozen process env is only a fallback when that disk read fails (logged once).
|
|
29
|
-
Bugteam's opt-out is env-only via ``_resolve_bugteam_post_blocked``.
|
|
30
29
|
A probe error does not waive the gate; the live GitHub checks still run.
|
|
31
30
|
|
|
32
31
|
The Codex gate is conditional-required: it demands
|
|
@@ -105,11 +104,11 @@ from pr_converge_skill_constants.constants import (
|
|
|
105
104
|
)
|
|
106
105
|
from reviews_disabled import (
|
|
107
106
|
is_bugbot_disabled_via_env,
|
|
108
|
-
is_bugteam_disabled_via_env,
|
|
109
|
-
is_codex_disabled_via_env,
|
|
110
107
|
is_copilot_disabled_via_env,
|
|
111
108
|
)
|
|
112
109
|
from check_convergence_availability import (
|
|
110
|
+
_is_bugteam_disabled_via_resolved_settings,
|
|
111
|
+
_is_codex_disabled_via_resolved_settings,
|
|
113
112
|
_resolve_bugbot_waiver,
|
|
114
113
|
_resolve_copilot_waiver,
|
|
115
114
|
_waiver_from_cli_flag,
|
|
@@ -815,18 +814,21 @@ def _resolve_copilot_down(is_copilot_down_flag: bool) -> bool:
|
|
|
815
814
|
|
|
816
815
|
|
|
817
816
|
def _resolve_bugteam_post_blocked(is_bugteam_post_blocked_flag: bool) -> bool:
|
|
818
|
-
"""Combine the --bugteam-post-blocked flag with the bugteam
|
|
817
|
+
"""Combine the --bugteam-post-blocked flag with the resolved bugteam opt-in.
|
|
819
818
|
|
|
820
819
|
::
|
|
821
820
|
|
|
822
|
-
flag True
|
|
823
|
-
flag False, reviews-
|
|
824
|
-
flag False,
|
|
821
|
+
flag True -> True (caller passed the flag)
|
|
822
|
+
flag False, reviews-enabled lists bugteam -> False (gate runs)
|
|
823
|
+
flag False, neither list names bugteam -> True (off by default)
|
|
824
|
+
flag False, reviews-disabled lists bugteam -> True (opt-out wins)
|
|
825
825
|
|
|
826
|
-
|
|
827
|
-
|
|
826
|
+
Bugteam is off by default and runs only on an explicit opt-in. Settings on
|
|
827
|
+
disk are the source when readable, so a settings.json opt-in reaches the
|
|
828
|
+
mark-ready blocker hook, which re-runs this script with no flags. The
|
|
829
|
+
process env is the fallback when that disk read fails.
|
|
828
830
|
"""
|
|
829
|
-
return is_bugteam_post_blocked_flag or
|
|
831
|
+
return is_bugteam_post_blocked_flag or _is_bugteam_disabled_via_resolved_settings()
|
|
830
832
|
|
|
831
833
|
|
|
832
834
|
def _resolve_codex_down(
|
|
@@ -854,7 +856,7 @@ def _resolve_codex_down(
|
|
|
854
856
|
"""
|
|
855
857
|
return (
|
|
856
858
|
is_codex_down_flag
|
|
857
|
-
or
|
|
859
|
+
or _is_codex_disabled_via_resolved_settings()
|
|
858
860
|
or _read_codex_down_from_job_state(all_job_state_fields)
|
|
859
861
|
)
|
|
860
862
|
|