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
|
@@ -84,7 +84,7 @@ def test_remaining_percent_is_full_scale_minus_utilization(
|
|
|
84
84
|
tmp_path,
|
|
85
85
|
[
|
|
86
86
|
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
87
|
-
_entry("claude-
|
|
87
|
+
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
88
88
|
],
|
|
89
89
|
)
|
|
90
90
|
monkeypatch.setattr(
|
|
@@ -100,7 +100,7 @@ def test_remaining_percent_is_full_scale_minus_utilization(
|
|
|
100
100
|
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
101
101
|
assert [each_report.command for each_report in all_reports] == [
|
|
102
102
|
"claude",
|
|
103
|
-
"claude-
|
|
103
|
+
"claude-profile-c",
|
|
104
104
|
]
|
|
105
105
|
assert all_reports[0].weekly_remaining_percent == pytest.approx(
|
|
106
106
|
FULL_WEEKLY_PERCENT - 42.0
|
|
@@ -143,7 +143,7 @@ def test_probe_failure_yields_null_remaining_and_error_string(
|
|
|
143
143
|
tmp_path,
|
|
144
144
|
[
|
|
145
145
|
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
146
|
-
_entry("claude-
|
|
146
|
+
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
147
147
|
],
|
|
148
148
|
)
|
|
149
149
|
monkeypatch.setattr(
|
|
@@ -217,7 +217,7 @@ def test_entry_credentials_path_is_passed_to_probe(
|
|
|
217
217
|
) -> None:
|
|
218
218
|
config_file = _write_chain_config(
|
|
219
219
|
tmp_path,
|
|
220
|
-
[_entry("claude-
|
|
220
|
+
[_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_TERTIARY)],
|
|
221
221
|
)
|
|
222
222
|
probed_paths: list[Path] = []
|
|
223
223
|
|
|
@@ -236,7 +236,7 @@ def test_entry_credentials_path_expands_user_home(
|
|
|
236
236
|
tilde_credentials_path = "~/.claude-accounts/secondary/.credentials.json"
|
|
237
237
|
config_file = _write_chain_config(
|
|
238
238
|
tmp_path,
|
|
239
|
-
[_entry("claude-
|
|
239
|
+
[_entry("claude-profile-c", credentials_path=tilde_credentials_path)],
|
|
240
240
|
)
|
|
241
241
|
probed_paths: list[Path] = []
|
|
242
242
|
|
|
@@ -254,7 +254,7 @@ def test_load_chain_carries_optional_credentials_path(tmp_path: Path) -> None:
|
|
|
254
254
|
tmp_path,
|
|
255
255
|
[
|
|
256
256
|
_entry("claude"),
|
|
257
|
-
_entry("claude-
|
|
257
|
+
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
258
258
|
],
|
|
259
259
|
)
|
|
260
260
|
all_entries = chain_runner.load_chain(config_file)
|
|
@@ -286,7 +286,7 @@ def test_reports_to_json_payload_matches_cli_contract() -> None:
|
|
|
286
286
|
all_reports = [
|
|
287
287
|
usage.AccountUsageReport(command="claude", weekly_remaining_percent=58.0),
|
|
288
288
|
usage.AccountUsageReport(
|
|
289
|
-
command="claude-
|
|
289
|
+
command="claude-profile-c",
|
|
290
290
|
weekly_remaining_percent=None,
|
|
291
291
|
error="probe failed",
|
|
292
292
|
),
|
|
@@ -299,7 +299,7 @@ def test_reports_to_json_payload_matches_cli_contract() -> None:
|
|
|
299
299
|
JSON_WEEKLY_REMAINING_PERCENT_KEY: 58.0,
|
|
300
300
|
},
|
|
301
301
|
{
|
|
302
|
-
JSON_COMMAND_KEY: "claude-
|
|
302
|
+
JSON_COMMAND_KEY: "claude-profile-c",
|
|
303
303
|
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
304
304
|
JSON_ERROR_KEY: "probe failed",
|
|
305
305
|
},
|
|
@@ -314,7 +314,7 @@ def test_cli_writes_json_accounts_report(
|
|
|
314
314
|
tmp_path,
|
|
315
315
|
[
|
|
316
316
|
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
317
|
-
_entry("claude-
|
|
317
|
+
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
318
318
|
],
|
|
319
319
|
)
|
|
320
320
|
monkeypatch.setattr(
|
|
@@ -341,7 +341,7 @@ def test_cli_writes_json_accounts_report(
|
|
|
341
341
|
JSON_WEEKLY_REMAINING_PERCENT_KEY: FULL_WEEKLY_PERCENT - 42.0,
|
|
342
342
|
},
|
|
343
343
|
{
|
|
344
|
-
JSON_COMMAND_KEY: "claude-
|
|
344
|
+
JSON_COMMAND_KEY: "claude-profile-c",
|
|
345
345
|
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
346
346
|
JSON_ERROR_KEY: "no token",
|
|
347
347
|
},
|
|
@@ -428,7 +428,7 @@ def test_probe_ignores_ingress_when_credential_token_missing(
|
|
|
428
428
|
) -> None:
|
|
429
429
|
config_file = _write_chain_config(
|
|
430
430
|
tmp_path,
|
|
431
|
-
[_entry("claude-
|
|
431
|
+
[_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY)],
|
|
432
432
|
)
|
|
433
433
|
|
|
434
434
|
class _FakeResolver:
|
|
@@ -462,7 +462,7 @@ def test_load_failure_yields_per_account_error_and_cli_exits_zero(
|
|
|
462
462
|
tmp_path,
|
|
463
463
|
[
|
|
464
464
|
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
465
|
-
_entry("claude-
|
|
465
|
+
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
466
466
|
],
|
|
467
467
|
)
|
|
468
468
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Behavioral tests for patch artifact extraction and manifests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
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 grok_patch_artifacts import ( # noqa: E402
|
|
15
|
+
compute_sha256_hex,
|
|
16
|
+
extract_worktree_diff,
|
|
17
|
+
write_patch_manifest,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _init_repo(path: Path) -> str:
|
|
22
|
+
subprocess.check_call(["git", "init"], cwd=path, stdout=subprocess.DEVNULL)
|
|
23
|
+
subprocess.check_call(
|
|
24
|
+
["git", "config", "user.email", "test@example.com"], cwd=path, stdout=subprocess.DEVNULL
|
|
25
|
+
)
|
|
26
|
+
subprocess.check_call(
|
|
27
|
+
["git", "config", "user.name", "test"], cwd=path, stdout=subprocess.DEVNULL
|
|
28
|
+
)
|
|
29
|
+
sample = path / "sample.txt"
|
|
30
|
+
sample.write_text("one\n", encoding="utf-8")
|
|
31
|
+
subprocess.check_call(["git", "add", "sample.txt"], cwd=path, stdout=subprocess.DEVNULL)
|
|
32
|
+
subprocess.check_call(
|
|
33
|
+
["git", "commit", "-m", "init"], cwd=path, stdout=subprocess.DEVNULL
|
|
34
|
+
)
|
|
35
|
+
base = subprocess.check_output(
|
|
36
|
+
["git", "rev-parse", "HEAD"], cwd=path, text=True
|
|
37
|
+
).strip()
|
|
38
|
+
sample.write_text("one\ntwo\n", encoding="utf-8")
|
|
39
|
+
return base
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_write_patch_manifest_binds_hash_paths_and_report(tmp_path: Path) -> None:
|
|
43
|
+
worktree = tmp_path / "wt"
|
|
44
|
+
worktree.mkdir()
|
|
45
|
+
base_sha = _init_repo(worktree)
|
|
46
|
+
run_dir = tmp_path / "run"
|
|
47
|
+
report_text = '{"status":"ok"}'
|
|
48
|
+
manifest = write_patch_manifest(
|
|
49
|
+
run_state_directory=run_dir,
|
|
50
|
+
task_id="O-04",
|
|
51
|
+
base_sha=base_sha,
|
|
52
|
+
worktree_path=worktree,
|
|
53
|
+
worker_report_text=report_text,
|
|
54
|
+
)
|
|
55
|
+
assert manifest["task_id"] == "O-04"
|
|
56
|
+
assert manifest["base_sha"] == base_sha
|
|
57
|
+
assert "sample.txt" in manifest["changed_paths"]
|
|
58
|
+
patch_path = Path(str(manifest["patch_path"]))
|
|
59
|
+
assert patch_path.is_file()
|
|
60
|
+
assert manifest["content_sha256"] == compute_sha256_hex(patch_path.read_bytes())
|
|
61
|
+
assert manifest["worker_report_sha256"] == compute_sha256_hex(
|
|
62
|
+
report_text.encode("utf-8")
|
|
63
|
+
)
|
|
64
|
+
on_disk = json.loads((run_dir / "patch-manifest.json").read_text(encoding="utf-8"))
|
|
65
|
+
assert on_disk["content_sha256"] == manifest["content_sha256"]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_compute_sha256_hex_is_stable() -> None:
|
|
69
|
+
digest = compute_sha256_hex(b"abc")
|
|
70
|
+
assert digest == compute_sha256_hex(b"abc")
|
|
71
|
+
assert digest != compute_sha256_hex(b"abd")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_extract_worktree_diff_returns_paths(tmp_path: Path) -> None:
|
|
75
|
+
worktree = tmp_path / "wt"
|
|
76
|
+
worktree.mkdir()
|
|
77
|
+
base_sha = _init_repo(worktree)
|
|
78
|
+
diff_text, all_changed_paths = extract_worktree_diff(
|
|
79
|
+
worktree_path=worktree, base_sha=base_sha
|
|
80
|
+
)
|
|
81
|
+
assert "sample.txt" in all_changed_paths
|
|
82
|
+
assert "two" in diff_text or "sample" in diff_text
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Behavioral tests for the host-neutral Grok run ledger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
12
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
14
|
+
|
|
15
|
+
from dev_env_scripts_constants.grok_run_ledger_constants import ( # noqa: E402
|
|
16
|
+
TASK_STATUS_ADVISOR_BLOCKED,
|
|
17
|
+
TASK_STATUS_COMPLETED,
|
|
18
|
+
TASK_STATUS_PENDING,
|
|
19
|
+
TASK_STATUS_PENDING_REVIEW,
|
|
20
|
+
)
|
|
21
|
+
from grok_run_ledger import GrokRunLedger, is_legal_status # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_register_task_persists_atomically(tmp_path: Path) -> None:
|
|
25
|
+
ledger = GrokRunLedger(tmp_path)
|
|
26
|
+
ledger.register_task(task_id="O-04", all_dependencies=())
|
|
27
|
+
reloaded = GrokRunLedger(tmp_path)
|
|
28
|
+
record = reloaded.get_task("O-04")
|
|
29
|
+
assert record.status == TASK_STATUS_PENDING
|
|
30
|
+
assert is_legal_status(record.status)
|
|
31
|
+
payload = json.loads((tmp_path / "grok-run-ledger.json").read_text(encoding="utf-8"))
|
|
32
|
+
assert payload["tasks"][0]["task_id"] == "O-04"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_dependencies_block_dispatch(tmp_path: Path) -> None:
|
|
36
|
+
ledger = GrokRunLedger(tmp_path)
|
|
37
|
+
ledger.register_task(task_id="dep", all_dependencies=())
|
|
38
|
+
ledger.register_task(task_id="child", all_dependencies=("dep",))
|
|
39
|
+
assert ledger.can_dispatch("child") is False
|
|
40
|
+
with pytest.raises(ValueError, match="dependencies"):
|
|
41
|
+
ledger.mark_in_progress(
|
|
42
|
+
task_id="child",
|
|
43
|
+
owner_id="w1",
|
|
44
|
+
advisor_session_id="s1",
|
|
45
|
+
base_sha="aaa",
|
|
46
|
+
)
|
|
47
|
+
ledger.mark_in_progress(
|
|
48
|
+
task_id="dep", owner_id="w0", advisor_session_id="s0", base_sha="aaa"
|
|
49
|
+
)
|
|
50
|
+
ledger.mark_completed(
|
|
51
|
+
task_id="dep",
|
|
52
|
+
reviewed_head="bbb",
|
|
53
|
+
all_changed_paths=(),
|
|
54
|
+
advisor_verdict="ENDORSE",
|
|
55
|
+
all_acceptance_mapping={},
|
|
56
|
+
all_test_evidence=["ok"],
|
|
57
|
+
)
|
|
58
|
+
assert ledger.can_dispatch("child") is True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_one_live_owner_and_unique_advisor_session(tmp_path: Path) -> None:
|
|
62
|
+
ledger = GrokRunLedger(tmp_path)
|
|
63
|
+
ledger.register_task(task_id="a", all_dependencies=())
|
|
64
|
+
ledger.register_task(task_id="b", all_dependencies=())
|
|
65
|
+
ledger.mark_in_progress(
|
|
66
|
+
task_id="a", owner_id="owner", advisor_session_id="sess-a", base_sha="1"
|
|
67
|
+
)
|
|
68
|
+
with pytest.raises(ValueError, match="owner already live"):
|
|
69
|
+
ledger.mark_in_progress(
|
|
70
|
+
task_id="b", owner_id="owner", advisor_session_id="sess-b", base_sha="1"
|
|
71
|
+
)
|
|
72
|
+
with pytest.raises(ValueError, match="advisor session already bound"):
|
|
73
|
+
ledger.mark_in_progress(
|
|
74
|
+
task_id="b", owner_id="other", advisor_session_id="sess-a", base_sha="1"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_snapshot_drift_moves_to_pending_review(tmp_path: Path) -> None:
|
|
79
|
+
ledger = GrokRunLedger(tmp_path)
|
|
80
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
81
|
+
ledger.mark_in_progress(
|
|
82
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
83
|
+
)
|
|
84
|
+
record = ledger.invalidate_on_snapshot_drift(task_id="t", current_sha="drifted")
|
|
85
|
+
assert record.status == TASK_STATUS_PENDING_REVIEW
|
|
86
|
+
assert record.owner_id is None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_advisor_blocked_terminal(tmp_path: Path) -> None:
|
|
90
|
+
ledger = GrokRunLedger(tmp_path)
|
|
91
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
92
|
+
ledger.mark_in_progress(
|
|
93
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
94
|
+
)
|
|
95
|
+
record = ledger.mark_advisor_blocked(task_id="t", reason="bind failed")
|
|
96
|
+
assert record.status == TASK_STATUS_ADVISOR_BLOCKED
|
|
97
|
+
assert "bind failed" in record.test_evidence[0]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_completed_records_acceptance_and_head(tmp_path: Path) -> None:
|
|
101
|
+
ledger = GrokRunLedger(tmp_path)
|
|
102
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
103
|
+
ledger.mark_in_progress(
|
|
104
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
105
|
+
)
|
|
106
|
+
record = ledger.mark_completed(
|
|
107
|
+
task_id="t",
|
|
108
|
+
reviewed_head="head",
|
|
109
|
+
all_changed_paths=("a.py",),
|
|
110
|
+
advisor_verdict="ENDORSE",
|
|
111
|
+
all_acceptance_mapping={"criterion": "evidence"},
|
|
112
|
+
all_test_evidence=["pytest -q"],
|
|
113
|
+
)
|
|
114
|
+
assert record.status == TASK_STATUS_COMPLETED
|
|
115
|
+
assert record.reviewed_head == "head"
|
|
116
|
+
assert record.changed_paths == ("a.py",)
|
|
@@ -1141,7 +1141,7 @@ def test_usage_limit_fallover_delivers_full_prompt_to_each_binary(
|
|
|
1141
1141
|
"load_chain",
|
|
1142
1142
|
lambda _config_path: [
|
|
1143
1143
|
chain_runner.ChainEntry(command="claude", extra_args=()),
|
|
1144
|
-
chain_runner.ChainEntry(command="claude-
|
|
1144
|
+
chain_runner.ChainEntry(command="claude-profile-c", extra_args=()),
|
|
1145
1145
|
],
|
|
1146
1146
|
)
|
|
1147
1147
|
prompt_text_by_command: dict[str, str] = {}
|
|
@@ -1179,6 +1179,6 @@ def test_usage_limit_fallover_delivers_full_prompt_to_each_binary(
|
|
|
1179
1179
|
)
|
|
1180
1180
|
|
|
1181
1181
|
assert prompt_text_by_command["claude"] == FIXTURE_PROMPT_TEXT
|
|
1182
|
-
assert prompt_text_by_command["claude-
|
|
1182
|
+
assert prompt_text_by_command["claude-profile-c"] == FIXTURE_PROMPT_TEXT
|
|
1183
1183
|
assert outcome.tier_used == TIER_CLAUDE_HEADLESS
|
|
1184
1184
|
assert outcome.is_ok is True
|
|
@@ -1319,3 +1319,298 @@ def test_load_batch_spec_rejects_empty_agent_name(tmp_path: Path) -> None:
|
|
|
1319
1319
|
|
|
1320
1320
|
with pytest.raises(ValueError, match=WORKER_SPEC_AGENT_NAME_KEY):
|
|
1321
1321
|
batch.load_batch_spec(specification_path)
|
|
1322
|
+
|
|
1323
|
+
# --- O-02 worker advisor contract ---
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
def test_extract_advisor_signal_accepts_four_tokens_only() -> None:
|
|
1327
|
+
assert batch.extract_advisor_signal("ENDORSE\nok") == "ENDORSE"
|
|
1328
|
+
assert batch.extract_advisor_signal("CORRECTION fix path") == "CORRECTION"
|
|
1329
|
+
assert batch.extract_advisor_signal("PLAN later") == "PLAN"
|
|
1330
|
+
assert batch.extract_advisor_signal("STOP") == "STOP"
|
|
1331
|
+
assert batch.extract_advisor_signal("hello ENDORSE") is None
|
|
1332
|
+
assert batch.extract_advisor_signal("") is None
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
def test_load_batch_spec_parses_advisor_block(tmp_path: Path) -> None:
|
|
1336
|
+
header_part, body_part = _write_prompt_parts(tmp_path)
|
|
1337
|
+
payload = _worker_payload(
|
|
1338
|
+
role_name="lens",
|
|
1339
|
+
all_prompt_parts=[str(header_part), str(body_part)],
|
|
1340
|
+
working_directory=tmp_path,
|
|
1341
|
+
tool_profile=TOOL_PROFILE_READONLY,
|
|
1342
|
+
)
|
|
1343
|
+
specification_path = tmp_path / "batch-spec.json"
|
|
1344
|
+
specification_path.write_text(
|
|
1345
|
+
json.dumps(
|
|
1346
|
+
{
|
|
1347
|
+
"role": DEFAULT_ROLE,
|
|
1348
|
+
"should_ping": False,
|
|
1349
|
+
"workers": [payload],
|
|
1350
|
+
"advisor": {
|
|
1351
|
+
"launcher": "fixture-advisor-launcher",
|
|
1352
|
+
"model": "opus",
|
|
1353
|
+
"effort": "high",
|
|
1354
|
+
},
|
|
1355
|
+
}
|
|
1356
|
+
),
|
|
1357
|
+
encoding=UTF8_ENCODING,
|
|
1358
|
+
)
|
|
1359
|
+
loaded = batch.load_batch_spec(specification_path)
|
|
1360
|
+
assert loaded.advisor is not None
|
|
1361
|
+
assert loaded.advisor.launcher == "fixture-advisor-launcher"
|
|
1362
|
+
assert loaded.advisor.model == "opus"
|
|
1363
|
+
assert loaded.advisor.effort == "high"
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def test_unique_advisor_sessions_and_completion_verdict(
|
|
1367
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1368
|
+
) -> None:
|
|
1369
|
+
header_a, body_a = _write_prompt_parts(tmp_path, role_marker="alpha")
|
|
1370
|
+
header_b, body_b = _write_prompt_parts(tmp_path, role_marker="beta")
|
|
1371
|
+
workers = [
|
|
1372
|
+
_worker_payload(
|
|
1373
|
+
role_name="alpha",
|
|
1374
|
+
all_prompt_parts=[str(header_a), str(body_a)],
|
|
1375
|
+
working_directory=tmp_path,
|
|
1376
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
1377
|
+
),
|
|
1378
|
+
_worker_payload(
|
|
1379
|
+
role_name="beta",
|
|
1380
|
+
all_prompt_parts=[str(header_b), str(body_b)],
|
|
1381
|
+
working_directory=tmp_path,
|
|
1382
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
1383
|
+
),
|
|
1384
|
+
]
|
|
1385
|
+
specification_path = tmp_path / "batch-spec.json"
|
|
1386
|
+
specification_path.write_text(
|
|
1387
|
+
json.dumps(
|
|
1388
|
+
{
|
|
1389
|
+
"role": DEFAULT_ROLE,
|
|
1390
|
+
"should_ping": False,
|
|
1391
|
+
"workers": workers,
|
|
1392
|
+
"advisor": {
|
|
1393
|
+
"launcher": "fixture-advisor-launcher",
|
|
1394
|
+
"model": "opus",
|
|
1395
|
+
"effort": "high",
|
|
1396
|
+
},
|
|
1397
|
+
}
|
|
1398
|
+
),
|
|
1399
|
+
encoding=UTF8_ENCODING,
|
|
1400
|
+
)
|
|
1401
|
+
bind_count = {"n": 0}
|
|
1402
|
+
sessions_issued: list[str] = []
|
|
1403
|
+
|
|
1404
|
+
def fake_advisor(
|
|
1405
|
+
*,
|
|
1406
|
+
launcher: str,
|
|
1407
|
+
model: str,
|
|
1408
|
+
effort: str,
|
|
1409
|
+
prompt_text: str,
|
|
1410
|
+
session_id: str | None = None,
|
|
1411
|
+
) -> tuple[str | None, str, int]:
|
|
1412
|
+
assert launcher == "fixture-advisor-launcher"
|
|
1413
|
+
assert model == "opus"
|
|
1414
|
+
assert effort == "high"
|
|
1415
|
+
if session_id is None:
|
|
1416
|
+
bind_count["n"] += 1
|
|
1417
|
+
session = f"session-{bind_count['n']}"
|
|
1418
|
+
sessions_issued.append(session)
|
|
1419
|
+
return session, "ENDORSE\npre-dispatch ok", 0
|
|
1420
|
+
return session_id, "ENDORSE\npost-report ok", 0
|
|
1421
|
+
|
|
1422
|
+
monkeypatch.setattr(batch, "batch_invoke_advisor", fake_advisor)
|
|
1423
|
+
monkeypatch.setattr(
|
|
1424
|
+
batch,
|
|
1425
|
+
"batch_preflight",
|
|
1426
|
+
lambda **kwargs: PreflightOutcome(is_usable=True, reason=None),
|
|
1427
|
+
)
|
|
1428
|
+
monkeypatch.setattr(batch, "batch_sleep", lambda seconds: None)
|
|
1429
|
+
recorder = _RunnerRecorder(
|
|
1430
|
+
{
|
|
1431
|
+
"alpha": _ok_outcome(),
|
|
1432
|
+
"beta": _ok_outcome(),
|
|
1433
|
+
}
|
|
1434
|
+
)
|
|
1435
|
+
monkeypatch.setattr(batch, "batch_headless_runner", recorder)
|
|
1436
|
+
loaded = batch.load_batch_spec(specification_path)
|
|
1437
|
+
summary = batch.run_grok_batch(
|
|
1438
|
+
batch_spec=loaded, run_state_directory=tmp_path / "run"
|
|
1439
|
+
)
|
|
1440
|
+
assert summary.is_preflight_usable
|
|
1441
|
+
assert len(summary.all_worker_reports) == 2
|
|
1442
|
+
all_session_ids = {
|
|
1443
|
+
each.advisor_session_id for each in summary.all_worker_reports
|
|
1444
|
+
}
|
|
1445
|
+
assert all_session_ids == {"session-1", "session-2"}
|
|
1446
|
+
assert all(
|
|
1447
|
+
each.advisor_completion_signal == "ENDORSE"
|
|
1448
|
+
for each in summary.all_worker_reports
|
|
1449
|
+
)
|
|
1450
|
+
assert all(
|
|
1451
|
+
each.classification != "advisor_blocked"
|
|
1452
|
+
for each in summary.all_worker_reports
|
|
1453
|
+
)
|
|
1454
|
+
for each_report in summary.all_worker_reports:
|
|
1455
|
+
prompt_text = Path(each_report.prompt_path).read_text(encoding=UTF8_ENCODING)
|
|
1456
|
+
assert each_report.advisor_session_id in prompt_text
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
def test_advisor_failure_classifies_advisor_blocked(
|
|
1460
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1461
|
+
) -> None:
|
|
1462
|
+
header_part, body_part = _write_prompt_parts(tmp_path, role_marker="solo")
|
|
1463
|
+
payload = _worker_payload(
|
|
1464
|
+
role_name="solo",
|
|
1465
|
+
all_prompt_parts=[str(header_part), str(body_part)],
|
|
1466
|
+
working_directory=tmp_path,
|
|
1467
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
1468
|
+
)
|
|
1469
|
+
specification_path = tmp_path / "batch-spec.json"
|
|
1470
|
+
specification_path.write_text(
|
|
1471
|
+
json.dumps(
|
|
1472
|
+
{
|
|
1473
|
+
"role": DEFAULT_ROLE,
|
|
1474
|
+
"should_ping": False,
|
|
1475
|
+
"workers": [payload],
|
|
1476
|
+
"advisor": {"launcher": "fixture-advisor-launcher"},
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
encoding=UTF8_ENCODING,
|
|
1480
|
+
)
|
|
1481
|
+
|
|
1482
|
+
def failing_advisor(**kwargs: object) -> tuple[str | None, str, int]:
|
|
1483
|
+
return None, "", 1
|
|
1484
|
+
|
|
1485
|
+
monkeypatch.setattr(batch, "batch_invoke_advisor", failing_advisor)
|
|
1486
|
+
monkeypatch.setattr(
|
|
1487
|
+
batch,
|
|
1488
|
+
"batch_preflight",
|
|
1489
|
+
lambda **kwargs: PreflightOutcome(is_usable=True, reason=None),
|
|
1490
|
+
)
|
|
1491
|
+
monkeypatch.setattr(batch, "batch_sleep", lambda seconds: None)
|
|
1492
|
+
monkeypatch.setattr(
|
|
1493
|
+
batch,
|
|
1494
|
+
"batch_headless_runner",
|
|
1495
|
+
_RunnerRecorder({"solo": _ok_outcome()}),
|
|
1496
|
+
)
|
|
1497
|
+
loaded = batch.load_batch_spec(specification_path)
|
|
1498
|
+
summary = batch.run_grok_batch(
|
|
1499
|
+
batch_spec=loaded, run_state_directory=tmp_path / "run"
|
|
1500
|
+
)
|
|
1501
|
+
assert len(summary.all_worker_reports) == 1
|
|
1502
|
+
report = summary.all_worker_reports[0]
|
|
1503
|
+
assert report.classification == "advisor_blocked"
|
|
1504
|
+
assert report.is_ok is False
|
|
1505
|
+
|
|
1506
|
+
def test_bind_unique_worker_advisor_rejects_placeholder(
|
|
1507
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1508
|
+
) -> None:
|
|
1509
|
+
def boom(**kwargs: object) -> tuple[str | None, str, int]:
|
|
1510
|
+
raise AssertionError("should not call launcher for placeholder")
|
|
1511
|
+
|
|
1512
|
+
monkeypatch.setattr(batch, "batch_invoke_advisor", boom)
|
|
1513
|
+
with pytest.raises(ValueError, match="placeholder"):
|
|
1514
|
+
batch.bind_unique_worker_advisor(
|
|
1515
|
+
advisor_spec=batch.AdvisorSpec(launcher=batch.DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER),
|
|
1516
|
+
role_name="lens",
|
|
1517
|
+
all_used_session_ids=set(),
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
def test_bind_unique_worker_advisor_returns_session(
|
|
1522
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1523
|
+
) -> None:
|
|
1524
|
+
def fake(**kwargs: object) -> tuple[str | None, str, int]:
|
|
1525
|
+
return "sess-unique-1", "ENDORSE\nok", 0
|
|
1526
|
+
|
|
1527
|
+
monkeypatch.setattr(batch, "batch_invoke_advisor", fake)
|
|
1528
|
+
session_id, signal = batch.bind_unique_worker_advisor(
|
|
1529
|
+
advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
|
|
1530
|
+
role_name="lens",
|
|
1531
|
+
all_used_session_ids=set(),
|
|
1532
|
+
)
|
|
1533
|
+
assert session_id == "sess-unique-1"
|
|
1534
|
+
assert signal == "ENDORSE"
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
def test_obtain_advisor_completion_verdict_endorses(
|
|
1538
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1539
|
+
) -> None:
|
|
1540
|
+
def fake(**kwargs: object) -> tuple[str | None, str, int]:
|
|
1541
|
+
return kwargs.get("session_id"), "ENDORSE\nok", 0
|
|
1542
|
+
|
|
1543
|
+
monkeypatch.setattr(batch, "batch_invoke_advisor", fake)
|
|
1544
|
+
signal = batch.obtain_advisor_completion_verdict(
|
|
1545
|
+
advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
|
|
1546
|
+
role_name="lens",
|
|
1547
|
+
session_id="sess-1",
|
|
1548
|
+
report_text="done",
|
|
1549
|
+
)
|
|
1550
|
+
assert signal == "ENDORSE"
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
def test_invoke_advisor_launcher_builds_command(
|
|
1554
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1555
|
+
) -> None:
|
|
1556
|
+
captured: dict[str, object] = {}
|
|
1557
|
+
|
|
1558
|
+
class _Completed:
|
|
1559
|
+
returncode = 0
|
|
1560
|
+
stdout = '{"session_id":"s1","result":"ENDORSE\\nok"}'
|
|
1561
|
+
stderr = ""
|
|
1562
|
+
|
|
1563
|
+
def fake_run(args, **kwargs): # type: ignore[no-untyped-def] # subprocess.run stub for argv capture
|
|
1564
|
+
captured["args"] = list(args)
|
|
1565
|
+
captured["input"] = kwargs.get("input")
|
|
1566
|
+
return _Completed()
|
|
1567
|
+
|
|
1568
|
+
monkeypatch.setattr(batch.subprocess, "run", fake_run)
|
|
1569
|
+
session_id, body, code = batch.invoke_advisor_launcher(
|
|
1570
|
+
launcher="fixture-advisor-launcher",
|
|
1571
|
+
model="opus",
|
|
1572
|
+
effort="high",
|
|
1573
|
+
prompt_text="hello",
|
|
1574
|
+
)
|
|
1575
|
+
assert code == 0
|
|
1576
|
+
assert session_id == "s1"
|
|
1577
|
+
assert "ENDORSE" in body
|
|
1578
|
+
assert captured["args"][0] == "fixture-advisor-launcher"
|
|
1579
|
+
assert "--model" in captured["args"]
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
def test_invoke_advisor_launcher_missing_binary_raises_advisor_failure() -> None:
|
|
1583
|
+
try:
|
|
1584
|
+
batch.invoke_advisor_launcher(
|
|
1585
|
+
launcher="__no_such_advisor_launcher_xyz__",
|
|
1586
|
+
model="opus",
|
|
1587
|
+
effort="high",
|
|
1588
|
+
prompt_text="ping",
|
|
1589
|
+
)
|
|
1590
|
+
raise AssertionError("expected AdvisorFailureError")
|
|
1591
|
+
except batch.AdvisorFailureError as raised:
|
|
1592
|
+
assert "not found" in str(raised).lower() or "launcher" in str(raised).lower()
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
def test_invoke_advisor_launcher_passes_timeout(
|
|
1596
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1597
|
+
) -> None:
|
|
1598
|
+
captured: dict[str, object] = {}
|
|
1599
|
+
|
|
1600
|
+
class _Completed:
|
|
1601
|
+
returncode = 0
|
|
1602
|
+
stdout = '{"session_id":"s-timeout","result":"ENDORSE\\nok"}'
|
|
1603
|
+
stderr = ""
|
|
1604
|
+
|
|
1605
|
+
def fake_run(args, **kwargs): # type: ignore[no-untyped-def] # subprocess.run stub
|
|
1606
|
+
captured["timeout"] = kwargs.get("timeout")
|
|
1607
|
+
return _Completed()
|
|
1608
|
+
|
|
1609
|
+
monkeypatch.setattr(batch.subprocess, "run", fake_run)
|
|
1610
|
+
batch.invoke_advisor_launcher(
|
|
1611
|
+
launcher="fixture-advisor-launcher",
|
|
1612
|
+
model="opus",
|
|
1613
|
+
effort="high",
|
|
1614
|
+
prompt_text="hello",
|
|
1615
|
+
)
|
|
1616
|
+
assert captured["timeout"] == batch.MAXIMUM_ADVISOR_TIMEOUT_SECONDS
|