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,180 @@
|
|
|
1
|
+
"""Behavior tests for the effort evaluation harness."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
9
|
+
_CONSTANTS_PARENT = _SCRIPTS_DIRECTORY / "config"
|
|
10
|
+
for each_path in (_SCRIPTS_DIRECTORY, _CONSTANTS_PARENT):
|
|
11
|
+
path_text = str(each_path)
|
|
12
|
+
if path_text not in sys.path:
|
|
13
|
+
sys.path.insert(0, path_text)
|
|
14
|
+
|
|
15
|
+
from e_code_review_effort_constants import ( # noqa: E402
|
|
16
|
+
ALL_EFFORT_LEVELS,
|
|
17
|
+
ALL_FIXTURE_BANDS,
|
|
18
|
+
ALL_SKILL_EFFORT_LEVELS,
|
|
19
|
+
MINIMUM_QUALITY_HOLD_SCORE,
|
|
20
|
+
THINKING_ENABLED_DEFAULT,
|
|
21
|
+
WORKFLOW_FAMILY_E_CODE_REVIEW,
|
|
22
|
+
)
|
|
23
|
+
from effort_evaluation import ( # noqa: E402
|
|
24
|
+
build_synthetic_row,
|
|
25
|
+
evaluation_evidence_path,
|
|
26
|
+
fixtures_directory,
|
|
27
|
+
load_evaluation_evidence,
|
|
28
|
+
load_fixtures,
|
|
29
|
+
map_evaluation_effort_to_skill_level,
|
|
30
|
+
quality_holds,
|
|
31
|
+
recommend_effort_by_band,
|
|
32
|
+
resolve_skill_effort_for_band,
|
|
33
|
+
skill_defaults_from_recommendation,
|
|
34
|
+
validate_evaluation_row,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_fixtures_directory_points_at_frozen_json() -> None:
|
|
39
|
+
directory = fixtures_directory()
|
|
40
|
+
assert directory.is_dir()
|
|
41
|
+
assert directory.name == "fixtures"
|
|
42
|
+
assert (directory / "easy.json").is_file()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_loads_three_frozen_fixture_bands() -> None:
|
|
46
|
+
all_fixtures = load_fixtures()
|
|
47
|
+
assert len(all_fixtures) == len(ALL_FIXTURE_BANDS)
|
|
48
|
+
bands = {str(each_fixture["band"]) for each_fixture in all_fixtures}
|
|
49
|
+
assert bands == set(ALL_FIXTURE_BANDS)
|
|
50
|
+
for each_fixture in all_fixtures:
|
|
51
|
+
assert each_fixture["fixture_id"]
|
|
52
|
+
assert isinstance(each_fixture["seeded_findings"], list)
|
|
53
|
+
assert len(each_fixture["seeded_findings"]) >= 1
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_validate_row_accepts_complete_row_and_rejects_thinking_off() -> None:
|
|
57
|
+
good_row = build_synthetic_row(
|
|
58
|
+
fixture_id="easy-comment-preservation",
|
|
59
|
+
fixture_band="easy",
|
|
60
|
+
effort="low",
|
|
61
|
+
quality_score=0.9,
|
|
62
|
+
finding_recall=1.0,
|
|
63
|
+
finding_precision=1.0,
|
|
64
|
+
visible_tokens=1200,
|
|
65
|
+
latency_ms=4000,
|
|
66
|
+
)
|
|
67
|
+
assert validate_evaluation_row(good_row) == []
|
|
68
|
+
assert good_row["thinking_enabled"] is THINKING_ENABLED_DEFAULT
|
|
69
|
+
bad_row = dict(good_row)
|
|
70
|
+
bad_row["thinking_enabled"] = False
|
|
71
|
+
problems = validate_evaluation_row(bad_row)
|
|
72
|
+
assert any("thinking_enabled" in each_problem for each_problem in problems)
|
|
73
|
+
bool_score_row = dict(good_row)
|
|
74
|
+
bool_score_row["quality_score"] = True
|
|
75
|
+
bool_problems = validate_evaluation_row(bool_score_row)
|
|
76
|
+
assert any("quality_score" in each_problem for each_problem in bool_problems)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_recommend_picks_lowest_effort_that_holds_quality() -> None:
|
|
80
|
+
all_rows = [
|
|
81
|
+
build_synthetic_row("easy-a", "easy", "low", 0.5, 0.5, 0.5, 100, 1000),
|
|
82
|
+
build_synthetic_row("easy-a", "easy", "medium", 0.95, 0.95, 0.95, 200, 2000),
|
|
83
|
+
build_synthetic_row("easy-a", "easy", "xhigh", 0.99, 0.99, 0.99, 400, 5000),
|
|
84
|
+
build_synthetic_row("medium-a", "medium", "high", 0.9, 0.9, 0.9, 300, 3000),
|
|
85
|
+
build_synthetic_row(
|
|
86
|
+
"demanding-a", "demanding", "max", 0.85, 0.85, 0.85, 800, 9000
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
for each_row in all_rows:
|
|
90
|
+
assert validate_evaluation_row(each_row) == []
|
|
91
|
+
recommendation = recommend_effort_by_band(all_rows)
|
|
92
|
+
by_band = recommendation["recommendation_by_band"]
|
|
93
|
+
assert isinstance(by_band, dict)
|
|
94
|
+
assert by_band["easy"]["recommended_effort"] == "medium"
|
|
95
|
+
assert by_band["easy"]["cited_row"]["effort"] == "medium"
|
|
96
|
+
assert by_band["medium"]["recommended_effort"] == "high"
|
|
97
|
+
assert by_band["demanding"]["recommended_effort"] == "max"
|
|
98
|
+
assert recommendation["thinking_enabled"] is True
|
|
99
|
+
assert recommendation["cost_latency_lever"] == "effort"
|
|
100
|
+
assert recommendation["defaults_unchanged"] is True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_recommend_blocks_band_without_holding_row() -> None:
|
|
104
|
+
all_rows = [
|
|
105
|
+
build_synthetic_row("easy-a", "easy", "low", 0.1, 0.1, 0.1, 50, 500),
|
|
106
|
+
]
|
|
107
|
+
recommendation = recommend_effort_by_band(all_rows)
|
|
108
|
+
easy = recommendation["recommendation_by_band"]["easy"]
|
|
109
|
+
assert easy["recommended_effort"] is None
|
|
110
|
+
assert easy["cited_row"] is None
|
|
111
|
+
assert "quality floor" in str(easy["blocker"])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_quality_holds_uses_named_floor() -> None:
|
|
115
|
+
holding = build_synthetic_row(
|
|
116
|
+
"x", "easy", "low", MINIMUM_QUALITY_HOLD_SCORE, 1.0, 1.0, 1, 1
|
|
117
|
+
)
|
|
118
|
+
failing = build_synthetic_row(
|
|
119
|
+
"x", "easy", "low", MINIMUM_QUALITY_HOLD_SCORE - 0.01, 1.0, 1.0, 1, 1
|
|
120
|
+
)
|
|
121
|
+
assert quality_holds(holding) is True
|
|
122
|
+
assert quality_holds(failing) is False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_effort_levels_cover_cli_set() -> None:
|
|
126
|
+
assert "low" in ALL_EFFORT_LEVELS
|
|
127
|
+
assert "max" in ALL_EFFORT_LEVELS
|
|
128
|
+
assert len(ALL_EFFORT_LEVELS) == 5
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_evidence_rows_validate_and_skill_defaults_cite_rows() -> None:
|
|
132
|
+
evidence_path = evaluation_evidence_path()
|
|
133
|
+
assert evidence_path.is_file()
|
|
134
|
+
assert evidence_path.name == "effort_defaults_evidence.json"
|
|
135
|
+
evidence = load_evaluation_evidence()
|
|
136
|
+
assert evidence["workflow_family"] == WORKFLOW_FAMILY_E_CODE_REVIEW
|
|
137
|
+
assert evidence["thinking_enabled"] is True
|
|
138
|
+
all_rows = evidence["all_rows"]
|
|
139
|
+
assert isinstance(all_rows, list)
|
|
140
|
+
for each_row in all_rows:
|
|
141
|
+
assert isinstance(each_row, dict)
|
|
142
|
+
assert validate_evaluation_row(each_row) == []
|
|
143
|
+
recommendation = recommend_effort_by_band(all_rows)
|
|
144
|
+
skill_defaults = skill_defaults_from_recommendation(recommendation)
|
|
145
|
+
assert skill_defaults["workflow_family"] == WORKFLOW_FAMILY_E_CODE_REVIEW
|
|
146
|
+
default_by_band = skill_defaults["default_by_band"]
|
|
147
|
+
assert isinstance(default_by_band, dict)
|
|
148
|
+
for each_band in ALL_FIXTURE_BANDS:
|
|
149
|
+
band_default = default_by_band[each_band]
|
|
150
|
+
assert band_default["skill_effort"] in ALL_SKILL_EFFORT_LEVELS
|
|
151
|
+
assert band_default["cited_row"] is not None
|
|
152
|
+
assert band_default["cited_row"]["thinking_enabled"] is True
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_committed_skill_defaults_match_resolver() -> None:
|
|
156
|
+
assert resolve_skill_effort_for_band("easy") == "medium"
|
|
157
|
+
assert resolve_skill_effort_for_band("medium") == "xhigh"
|
|
158
|
+
assert resolve_skill_effort_for_band("demanding") == "xhigh"
|
|
159
|
+
evidence = load_evaluation_evidence()
|
|
160
|
+
committed = evidence["skill_defaults"]["default_by_band"]
|
|
161
|
+
for each_band in ALL_FIXTURE_BANDS:
|
|
162
|
+
assert committed[each_band]["skill_effort"] == resolve_skill_effort_for_band(
|
|
163
|
+
each_band
|
|
164
|
+
)
|
|
165
|
+
assert committed[each_band]["cited_row"]["effort"] is not None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_map_high_and_max_to_skill_xhigh() -> None:
|
|
169
|
+
assert map_evaluation_effort_to_skill_level("high") == "xhigh"
|
|
170
|
+
assert map_evaluation_effort_to_skill_level("max") == "xhigh"
|
|
171
|
+
assert map_evaluation_effort_to_skill_level("medium") == "medium"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_resolve_rejects_unknown_band() -> None:
|
|
175
|
+
try:
|
|
176
|
+
resolve_skill_effort_for_band("not-a-band")
|
|
177
|
+
except ValueError as error:
|
|
178
|
+
assert "unknown fixture band" in str(error)
|
|
179
|
+
else:
|
|
180
|
+
raise AssertionError("expected ValueError for unknown band")
|
|
@@ -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
|
|
@@ -134,6 +134,83 @@ def test_run_medium_review_happy_path() -> None:
|
|
|
134
134
|
assert len(batch.all_retained_findings) == MEDIUM_REVIEW_FINDER_COUNT - 1
|
|
135
135
|
|
|
136
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
|
+
|
|
137
214
|
def test_head_drift_rejects_batch() -> None:
|
|
138
215
|
head = "h1"
|
|
139
216
|
all_finders = [
|
package/skills/grokify/SKILL.md
CHANGED
|
@@ -43,7 +43,7 @@ The user types `/grokify`, alone or with guidance.
|
|
|
43
43
|
|
|
44
44
|
- **Bind once, first:** charter + findings + constraints + plan piped from a temp file into `claude -p --model fable --effort high --output-format json`; parse and save `session_id` from the JSON reply.
|
|
45
45
|
- **Consult:** brief piped into `claude -p --resume <session_id> --model fable --effort high --output-format json`.
|
|
46
|
-
- **ConsultB** If fable is unavailable, use opus with
|
|
46
|
+
- **ConsultB** If fable is unavailable, use opus with xhigh effort: `claude -p --resume <session_id> --model opus --effort xhigh --output-format json`.
|
|
47
47
|
- **Signals:** every advisor reply opens with exactly one of ENDORSE, CORRECTION, PLAN, or STOP. CORRECTION and PLAN are actions to take, with a report-back in the next consult on that topic. STOP halts that line of work and surfaces it to the user. When the CLI is unreachable, Grok stops and says so — it never self-endorses in the advisor's place.
|
|
48
48
|
- **Cadence, mandatory:** after planning and before any edit; per phase before implementation (TDD red + approach) and after (diff, tests, acceptance evidence); before every `git commit` and `git push`; on every user-facing fork before asking; on any twice-repeated failure or stall.
|
|
49
49
|
|
|
@@ -43,7 +43,7 @@ CONFIRM THESE WITH THE USER FIRST, VIA ASKUSERQUESTION, BEFORE EMBEDDING]
|
|
|
43
43
|
append the full Established findings, Hard constraints, and Plan sections of this
|
|
44
44
|
document to the same file.
|
|
45
45
|
2. Bind: `claude -p --model fable --effort high --output-format json < <charter-file>`
|
|
46
|
-
2a. If 2 fails, replace fable with opus, effort high to
|
|
46
|
+
2a. If 2 fails, replace fable with opus, effort high to xhigh.
|
|
47
47
|
3. The JSON output is an array of events, not one object. Take `session_id` from any
|
|
48
48
|
event; the reply text is the `type == "result"` event's `.result` field. Persist
|
|
49
49
|
`session_id`, the repo root, and the cwd to a state file at once.
|
|
@@ -56,7 +56,7 @@ CONFIRM THESE WITH THE USER FIRST, VIA ASKUSERQUESTION, BEFORE EMBEDDING]
|
|
|
56
56
|
session, not a model failure.
|
|
57
57
|
|
|
58
58
|
**Consult (every time):** write the brief to a temp file, then
|
|
59
|
-
`claude -p --resume <session_id> --model fable (or opus) --effort high (
|
|
59
|
+
`claude -p --resume <session_id> --model fable (or opus) --effort high (xhigh for opus) --output-format json < <brief-file>`.
|
|
60
60
|
Act on the reply's opening signal: ENDORSE — proceed. CORRECTION — apply it first;
|
|
61
61
|
your next consult on that topic opens with what happened. PLAN — adopt it; same
|
|
62
62
|
report-back rule. STOP — halt that line of work and surface it to the user. Never
|
|
@@ -193,7 +193,7 @@ Touch only: <files or globs>
|
|
|
193
193
|
Done when: <one mechanical check — a command, a test, a diff scope>
|
|
194
194
|
Return: status, artifact paths, blockers — nothing else.
|
|
195
195
|
|
|
196
|
-
<
|
|
196
|
+
<Advisor block assembled per _shared/advisor/reference/advisor-block.md — advisor name filled in>
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
- **Size the task by its done-check.** The right task is the largest
|
|
@@ -218,9 +218,10 @@ Return: status, artifact paths, blockers — nothing else.
|
|
|
218
218
|
table picks the definition, and `clean-coder` already holds the code
|
|
219
219
|
discipline. The ticket adds the task, the pointers, and the Advisor
|
|
220
220
|
block only.
|
|
221
|
-
- **The Advisor block is
|
|
222
|
-
|
|
223
|
-
block
|
|
221
|
+
- **The Advisor block is pasted, assembled text.** Assemble it at bind
|
|
222
|
+
time from the parts in
|
|
223
|
+
[`_shared/advisor/reference/advisor-block.md`](../../_shared/advisor/reference/advisor-block.md)
|
|
224
|
+
and paste the assembled text itself into the ticket.
|
|
224
225
|
|
|
225
226
|
## Workflow Agent Routing
|
|
226
227
|
|
|
@@ -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,
|