claude-dev-env 2.3.0 → 2.5.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 +53 -48
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +3 -1
- package/agents/caveman.md +0 -1
- package/agents/clasp-deployment-orchestrator.md +0 -1
- package/agents/clean-coder.md +0 -1
- package/agents/code-advisor.md +0 -1
- package/agents/code-quality-agent.md +1 -2
- package/agents/code-verifier.md +3 -4
- package/agents/deep-research.md +0 -1
- package/agents/docs-agent.md +0 -1
- package/agents/git-commit-crafter.md +0 -1
- package/agents/issue-tracker.md +42 -0
- package/agents/plan-packet-validator.md +0 -1
- package/agents/pr-description-writer.md +0 -1
- package/agents/skill-writer-agent.md +84 -0
- package/agents/test_agent_frontmatter.py +67 -18
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
- package/bin/CLAUDE.md +68 -5
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +88 -0
- package/bin/install.mjs +1138 -114
- package/bin/install.prune.test.mjs +869 -19
- package/bin/install.test.mjs +906 -2
- package/commands/implement.md +1 -1
- package/commands/right-size.md +1 -1
- package/docs/CLAUDE.md +2 -0
- package/docs/host-pool-health-monitor.md +102 -0
- package/docs/references/CLAUDE.md +5 -2
- package/docs/references/advisor-tool.md +13 -0
- package/docs/references/code-review-enforcement.md +107 -0
- package/docs/references/team-advisor-skill.md +14 -0
- package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
- package/hooks/blocking/CLAUDE.md +9 -1
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
- package/hooks/blocking/code_review_gate_deny.py +74 -0
- package/hooks/blocking/code_review_pr_create_gate.py +198 -0
- package/hooks/blocking/code_review_push_gate.py +145 -0
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
- package/hooks/blocking/code_review_stamp_store.py +233 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
- package/hooks/blocking/config/__init__.py +5 -5
- package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
- package/hooks/blocking/config/verified_commit_constants.py +160 -155
- package/hooks/blocking/conftest.py +2 -0
- package/hooks/blocking/convergence_gate_blocker.py +112 -23
- package/hooks/blocking/destructive_command_blocker.py +19 -6
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
- package/hooks/blocking/pr_description_proof_of_work.py +52 -34
- package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
- package/hooks/blocking/test_code_review_gate_deny.py +54 -0
- package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
- package/hooks/blocking/test_code_review_push_gate.py +205 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
- package/hooks/blocking/test_code_review_stamp_store.py +205 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
- package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
- package/hooks/blocking/test_destructive_command_blocker.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
- package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
- package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
- package/hooks/blocking/test_verification_verdict_store.py +974 -903
- package/hooks/blocking/test_verified_commit_gate.py +581 -581
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
- package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
- package/hooks/blocking/verification_verdict_store.py +1039 -1014
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
- package/hooks/blocking/verifier_verdict_minter.py +280 -280
- package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
- package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
- package/hooks/git-hooks/pre_push.py +89 -2
- package/hooks/git-hooks/test_pre_push.py +128 -0
- package/hooks/hooks.json +26 -1
- package/hooks/hooks_constants/CLAUDE.md +3 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
- package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
- package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
- package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
- package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
- package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
- package/hooks/pyproject.toml +1 -0
- package/hooks/validators/CLAUDE.md +2 -0
- package/hooks/validators/config/__init__.py +0 -0
- package/hooks/validators/config/directory_exemption_constants.py +183 -0
- package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
- package/hooks/validators/conftest.py +4 -0
- package/hooks/validators/mypy_integration.py +63 -50
- package/hooks/validators/pyproject_config_discovery.py +101 -0
- package/hooks/validators/ruff_integration.py +257 -25
- package/hooks/validators/run_all_validators.py +223 -19
- package/hooks/validators/test_directory_exemption_constants.py +185 -0
- package/hooks/validators/test_mypy_integration.py +32 -0
- package/hooks/validators/test_pyproject_config_discovery.py +94 -0
- package/hooks/validators/test_python_antipattern_checks.py +110 -5
- package/hooks/validators/test_ruff_integration.py +160 -2
- package/hooks/validators/test_run_all_validators.py +140 -68
- package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
- package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
- package/package.json +10 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +45 -67
- package/rules/durable-post-artifacts.md +7 -0
- package/rules/state-what-is.md +25 -0
- package/rules/verified-commit-gate-skip.md +1 -1
- package/scripts/CLAUDE.md +1 -0
- package/scripts/Capture-PoolHealth.ps1 +410 -0
- package/scripts/_code_review_test_support.py +404 -0
- package/scripts/claude_chain_runner.py +141 -1
- package/scripts/codec_forwarding_test_support.py +83 -0
- package/scripts/conftest.py +23 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
- package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
- package/scripts/invoke_code_review.py +550 -38
- package/scripts/resolve_worker_spawn.py +626 -619
- package/scripts/spawn_grok_batch.py +672 -672
- package/scripts/test_claude_chain_runner.py +131 -0
- package/scripts/test_invoke_code_review_chain.py +70 -0
- package/scripts/test_invoke_code_review_cli.py +192 -0
- package/scripts/test_invoke_code_review_codec.py +77 -0
- package/scripts/test_invoke_code_review_contract.py +256 -0
- package/scripts/test_invoke_code_review_git.py +123 -0
- package/scripts/test_invoke_code_review_mode.py +99 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -1014
- package/scripts/test_resolve_worker_spawn_codec.py +101 -0
- package/skills/CLAUDE.md +2 -0
- package/skills/auditing-claude-config/SKILL.md +114 -114
- package/skills/autoconverge/SKILL.md +427 -421
- package/skills/autoconverge/reference/convergence.md +26 -4
- package/skills/autoconverge/reference/multi-pr.md +6 -1
- package/skills/autoconverge/reference/stop-conditions.md +16 -10
- package/skills/autoconverge/workflow/CLAUDE.md +1 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
- package/skills/autoconverge/workflow/converge.mjs +191 -8
- package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
- package/skills/closeout/SKILL.md +33 -50
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
- package/skills/codex-review/scripts/run_codex_review.py +233 -1
- package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
- package/skills/condensing-instructions/SKILL.md +81 -0
- package/skills/copilot-review/SKILL.md +119 -119
- package/skills/e-code-review/SKILL.md +52 -0
- package/skills/e-code-review/reference/fix.md +54 -0
- package/skills/e-code-review/reference/loop.md +43 -0
- package/skills/e-code-review/reference/low.md +57 -0
- package/skills/e-code-review/reference/medium.md +153 -0
- package/skills/e-code-review/reference/xhigh.md +182 -0
- package/skills/e-simplify/SKILL.md +97 -0
- package/skills/fresh-branch/CLAUDE.md +2 -0
- package/skills/fresh-branch/SKILL.md +2 -0
- package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
- package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
- package/skills/fresh-branch/scripts/pytest.ini +4 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
- package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
- package/skills/issue-tracker/SKILL.md +92 -0
- package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
- package/skills/issue-tracker/reference/handoff-schema.md +64 -0
- package/skills/issue-tracker/reference/operation-matrix.md +41 -0
- package/skills/orchestrator/SKILL.md +162 -21
- package/skills/orchestrator/scripts/status_gate.py +625 -0
- package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
- package/skills/orchestrator/scripts/test_status_gate.py +439 -0
- package/skills/orchestrator-refresh/SKILL.md +110 -35
- package/skills/plan-to-pr/SKILL.md +155 -0
- package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
- package/skills/plan-to-pr/reference/model-routing.md +36 -0
- package/skills/plan-to-pr/reference/packet-contract.md +43 -0
- package/skills/plan-to-pr/reference/packet-schema.json +57 -0
- package/skills/plan-to-pr/reference/process-inventory.md +22 -0
- package/skills/plan-to-pr/reference/review-loop.md +33 -0
- package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
- package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
- package/skills/plan-to-pr/reference/task-seeds.md +14 -0
- package/skills/plan-to-pr/reference/task-ticket.md +38 -0
- package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
- package/skills/plan-to-pr/scripts/config/constants.py +193 -0
- package/skills/plan-to-pr/scripts/create_packet.py +173 -0
- package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
- package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
- package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
- package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
- package/skills/plan-to-pr/scripts/validate_run.py +173 -0
- package/skills/plan-to-pr/test_skill_contract.py +207 -0
- package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
- package/skills/pr-converge/SKILL.md +472 -469
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +7 -4
- package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/pr-fix-protocol/SKILL.md +1 -1
- package/skills/privacy-hygiene/SKILL.md +68 -68
- package/skills/prototype/SKILL.md +86 -0
- package/skills/prototype/reference/honest-limitations.md +23 -0
- package/skills/prototype/reference/promotion-tasks.md +23 -0
- package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
- package/skills/prototype/scripts/conftest.py +15 -0
- package/skills/prototype/scripts/launch_sandbox.py +205 -0
- package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
- package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
- package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
- package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
- package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
- package/skills/prototype/workflows/promotion.md +27 -0
- package/skills/prototype/workflows/sandbox.md +35 -0
- package/skills/release-notes-html/SKILL.md +164 -0
- package/skills/skill-builder/CLAUDE.md +3 -3
- package/skills/skill-builder/SKILL.md +5 -5
- package/skills/skill-builder/references/CLAUDE.md +1 -1
- package/skills/skill-builder/references/delegation-map.md +3 -3
- package/skills/skill-builder/references/description-field.md +1 -1
- package/skills/skill-builder/references/skill-modularity.md +2 -3
- package/skills/skill-builder/workflows/CLAUDE.md +1 -1
- package/skills/skill-builder/workflows/improve-skill.md +1 -1
- package/skills/skill-builder/workflows/new-skill.md +2 -2
- package/skills/task-build/CLAUDE.md +8 -7
- package/skills/task-build/SKILL.md +16 -8
- package/skills/task-build/reference/tool-routing.md +19 -0
- package/skills/team-advisor/SKILL.md +2 -2
- package/scripts/test_invoke_code_review.py +0 -672
- package/skills/closeout/reference/issue-body-templates.md +0 -108
|
@@ -18,17 +18,28 @@ export const meta = {
|
|
|
18
18
|
{ title: 'Converge', detail: 'A deterministic static sweep runs first each round; then code-review, bug-audit, and self-review run in parallel on the same HEAD; one clean-coder applies all fixes; the loop repeats until every lens is clean on a stable HEAD' },
|
|
19
19
|
{ title: 'Bugbot gate', detail: 'A terminal confirmation gate that runs once the internal lenses are clean; when Bugbot is disabled or unavailable it spawns no agent and passes, otherwise it fetches Bugbot\'s verdict and routes any finding back into Converge' },
|
|
20
20
|
{ title: 'Copilot gate', detail: 'When the quota pre-check reports Copilot out of premium-request quota or unavailable, skip the gate with no agent spawned; otherwise request a Copilot review and poll up to the configured cap, then route findings by tier — self-healing findings flow back into Converge, and each code-concern finding goes to its own verifier agent that must execute a check against HEAD: a confirmed finding (defect reproduced by a run command) joins the fix round carrying its repro, a refuted finding (correct behavior demonstrated by a run command) is replied-to and resolved with the check evidence, and only inconclusive findings return blocker user-review for the orchestrator to gate on rather than auto-fixing or marking the PR ready; when Copilot is down or out of quota log a notice and proceed with the gate bypassed' },
|
|
21
|
-
{ title: 'Codex gate', detail: 'A conditional-required terminal gate that runs after Bugbot and Copilot: when CLAUDE_REVIEWS_DISABLED lists codex, when weekly usage is at or below the shared probe threshold (or null), or when the wrapper classifies codex_down, the gate skips without blocking; when usage is above the threshold it runs the codex-review wrapper against the PR base branch, routes findings into the fix path, and on a clean pass records the codex-clean HEAD for the convergence check' },
|
|
21
|
+
{ title: 'Codex gate', detail: 'A conditional-required terminal gate that runs after Bugbot and Copilot: when CLAUDE_REVIEWS_DISABLED lists codex, when weekly usage is at or below the shared probe threshold (or null), or when the wrapper classifies codex_down, the gate skips without blocking (a codex_down skip is logged as a note so the bypass stays visible); when usage is above the threshold it runs the codex-review wrapper against the PR base branch, routes findings into the fix path, and on a clean pass records the codex-clean HEAD for the convergence check' },
|
|
22
22
|
{ title: 'Finalize', detail: 'Run check_convergence.py; mark draft=false on a full pass' },
|
|
23
23
|
],
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const homeDirectoryPayload = normalizeRunInput(args)
|
|
27
|
+
const homeDirectoryFromEnv = typeof process !== 'undefined' && process.env ? process.env.HOME || process.env.USERPROFILE || '' : ''
|
|
28
|
+
const homeDirectory = (
|
|
29
|
+
(homeDirectoryPayload && typeof homeDirectoryPayload.homeDirectory === 'string'
|
|
30
|
+
? homeDirectoryPayload.homeDirectory
|
|
31
|
+
: '') ||
|
|
32
|
+
homeDirectoryFromEnv ||
|
|
33
|
+
''
|
|
34
|
+
).replace(/\\/g, '/')
|
|
27
35
|
|
|
28
36
|
const CONFIG = {
|
|
29
37
|
maxIterations: 20,
|
|
30
38
|
maxConsecutiveNoLensRounds: 3,
|
|
31
39
|
copilotMaxPolls: 8,
|
|
40
|
+
codexReviewTimeoutSeconds: 2400,
|
|
41
|
+
codexReviewPollIntervalSeconds: 120,
|
|
42
|
+
codexReviewMaxPolls: 21,
|
|
32
43
|
sharedScripts: '$HOME/.claude/skills/pr-converge/scripts',
|
|
33
44
|
prLoopScripts: '$HOME/.claude/_shared/pr-loop/scripts',
|
|
34
45
|
codexScripts: `${homeDirectory}/.claude/skills/codex-review/scripts`,
|
|
@@ -49,7 +60,7 @@ const HEADLESS_EDIT_PREAMBLE =
|
|
|
49
60
|
'- Never place a destructive-command literal inside a Bash command — not in echo, not in a heredoc, and not as an argument to python -c, node -e, or awk. To exercise or verify destructive_command_blocker (or any hook) behavior, run the committed test suite, e.g. python -m pytest <test_file>, which passes the command strings as in-language data rather than as a shell command.\n' +
|
|
50
61
|
'- When a commit message, or a PR / issue / review-comment body, must describe destructive-command behavior, write that text to a file and pass it by path (git commit -F <file>, gh ... --body-file <file>); never inline it with git commit -m or gh ... -b, where the literal lands in the Bash command and stalls you.\n' +
|
|
51
62
|
'- Keep scratch files and cleanup inside the OS temp dir; never target a repository or worktree path.\n' +
|
|
52
|
-
'- rm shape rules — the hook grants several rm auto-allow paths. The simplest one accepts a standalone Bash call whose target resolves inside the ephemeral namespace (/tmp, /temp, the OS temp root, or the run worktree); a compound path accepts an rm joined with benign reporting segments when every rm target is an absolute ephemeral path. Both of those paths fail closed on $(...) command substitution and on backtick subshells. The compound path additionally fails closed on any $ in the target — including $CLAUDE_JOB_DIR. The standalone path declines a $-bearing target only when the literal path is not already under an ephemeral root, so it does not by itself stop a $VAR that expands inside an ephemeral root. A third, broad path matches only when the command itself declares an ephemeral working directory (it cds into one, or runs under one): that cwd-scoped path resolves the target against the declared cwd, fails closed on $(...) , backticks, and unknown variables, and resolves the known temporary variables TEMP, TMP, TMPDIR, and CLAUDE_JOB_DIR to the OS temp root, so under that declared ephemeral cwd a bare $CLAUDE_JOB_DIR/tmp/<name> target and a relative target after a cd are auto-allowed.
|
|
63
|
+
'- rm shape rules — the hook grants several rm auto-allow paths. The simplest one accepts a standalone Bash call whose target resolves inside the ephemeral namespace (/tmp, /temp, the OS temp root, or the run worktree); a compound path accepts an rm joined with benign reporting segments when every rm target is an absolute ephemeral path. Both of those paths fail closed on $(...) command substitution and on backtick subshells. The compound path additionally fails closed on any $ in the target — including $CLAUDE_JOB_DIR. The standalone path declines a $-bearing target only when the literal path is not already under an ephemeral root, so it does not by itself stop a $VAR that expands inside an ephemeral root. A third, broad path matches only when the command itself declares an ephemeral working directory (it cds into one, or runs under one): that cwd-scoped path resolves the target against the declared cwd, fails closed on $(...) , backticks, and unknown variables, and resolves the known temporary variables TEMP, TMP, TMPDIR, and CLAUDE_JOB_DIR to the OS temp root, so under that declared ephemeral cwd a bare $CLAUDE_JOB_DIR/tmp/<name> target and a relative target after a cd are auto-allowed. Outside those ephemeral paths, run no bash rm at all: an rm on a repo or worktree path — most often a build cache like .mypy_cache, .pytest_cache, or __pycache__ left by ruff, mypy, or pytest — is not auto-allowed and stalls you on a prompt no human can answer. A gitignored build cache is harmless, so never delete it — leave it in place. For any genuine delete outside the ephemeral namespace, use the PowerShell tool (Remove-Item -Recurse -Force -Confirm:$false <absolute path>), which the blocker never sees because it watches only the Bash tool. For any cleanup whose path is variable-built or whose setup/teardown spans multiple steps, author a Python helper and run it as python <file>.py, which keeps every destructive literal out of a Bash command string entirely.\n' +
|
|
53
64
|
'- If a step appears to require a real destructive command, use a non-destructive equivalent or report it as a blocker instead of running it.\n\n' +
|
|
54
65
|
'WAITS AND POLLS — foreground sleep is blocked in this headless harness: a bare Bash "sleep N" or a PowerShell "Start-Sleep" is denied, and a wait you move to a background process — then end your turn to await it — never resumes, because a schema-bearing agent runs for a single turn. Therefore:\n' +
|
|
55
66
|
'- Perform every required delay or poll-interval wait inside this same turn by pairing the Monitor tool with a bounded until-loop: the Monitor tool streams its events to you while you keep working, and the until-loop re-runs the step condition on the interval the step names, up to the attempt budget the step names, exiting the moment the condition holds or the budget is spent — consume the Monitor notifications as they arrive rather than ending your turn to await them. Never end your turn to wait for something to finish.\n' +
|
|
@@ -561,7 +572,7 @@ function runConvergenceCheck(context) {
|
|
|
561
572
|
if (context.copilotDown) reviewerOptOutTokens.push('copilot')
|
|
562
573
|
if (context.codexDown) reviewerOptOutTokens.push('codex')
|
|
563
574
|
if (context.bugteamPostBlocked) reviewerOptOutTokens.push('bugteam')
|
|
564
|
-
const jobDirName = process.env.CLAUDE_JOB_DIR
|
|
575
|
+
const jobDirName = typeof process !== 'undefined' && process.env ? process.env.CLAUDE_JOB_DIR : undefined
|
|
565
576
|
const needsCodexCleanExportFallback = Boolean(context.codexCleanAt) && !jobDirName
|
|
566
577
|
if (needsCodexCleanExportFallback && !reviewerOptOutTokens.includes('codex')) {
|
|
567
578
|
reviewerOptOutTokens.push('codex')
|
|
@@ -692,7 +703,7 @@ const CODEX_SCHEMA = {
|
|
|
692
703
|
properties: {
|
|
693
704
|
sha: { type: 'string', description: 'PR HEAD SHA this gate evaluated' },
|
|
694
705
|
clean: { type: 'boolean', description: 'true when Codex reported no findings on sha, or when the gate skipped without requiring a review' },
|
|
695
|
-
down: { type: 'boolean', description: 'true when
|
|
706
|
+
down: { type: 'boolean', description: 'true when the wrapper classifies codex_down (the gate is skipped and bypassed, recorded as a note) or when Codex is opted out via the codex token — the gate is bypassed' },
|
|
696
707
|
skipped: { type: 'boolean', description: 'true when the gate did not run the review wrapper (opt-out token or usage at/below the shared threshold)' },
|
|
697
708
|
skipReason: {
|
|
698
709
|
type: 'string',
|
|
@@ -2072,9 +2083,11 @@ function runCodexGate(head) {
|
|
|
2072
2083
|
`3. Resolve the PR base branch (the review target is HEAD vs base, never an invented commit range). Run exactly:\n` +
|
|
2073
2084
|
` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .base.ref\n` +
|
|
2074
2085
|
` Capture the printed base branch name.\n\n` +
|
|
2075
|
-
`4. Run the codex-review wrapper against that base. From the PR worktree root,
|
|
2086
|
+
`4. Run the codex-review wrapper against that base. A real Codex review routinely runs about 11-12 minutes — longer than the default 2-minute tool timeout and longer than the 10-minute foreground tool ceiling — so it MUST run in the background, or the tool timeout kills it mid-review and it is misreported as codex_down. From the PR worktree root, write a short Python driver to a temp file that imports the skill scripts (add "${CONFIG.codexScripts}" to sys.path) and:\n` +
|
|
2076
2087
|
` - creates a temp run-state directory under the OS temp dir\n` +
|
|
2077
|
-
` - calls run_codex_review.run_codex_review(repository_directory=<repo root Path>, run_state_directory=<temp Path>, base_branch=<base ref from step 3
|
|
2088
|
+
` - calls run_codex_review.run_codex_review(repository_directory=<repo root Path>, run_state_directory=<temp Path>, base_branch=<base ref from step 3>, timeout_seconds=${CONFIG.codexReviewTimeoutSeconds})\n` +
|
|
2089
|
+
` - writes the outcome (outcome_class, exit_code, and the agent_message when completed) as JSON to a result file under the OS temp dir, then exits\n` +
|
|
2090
|
+
` Run that driver with the Bash tool in the background (run_in_background: true), never in the foreground — the foreground tool timeout caps below the review runtime and would kill a healthy review. Do NOT end this turn to await the background run — a schema-bearing turn that ends never resumes, so the result would never be collected. Wait for the background run to finish inside this same turn per the WAITS AND POLLS rule: pair the Monitor tool with a bounded until-loop that re-checks whether the result file has been written, on a ${CONFIG.codexReviewPollIntervalSeconds}-second interval up to ${CONFIG.codexReviewMaxPolls} attempts (interval x attempts covers the ${CONFIG.codexReviewTimeoutSeconds}-second review budget), consuming the Monitor notifications as they arrive; arm the Monitor with persistent:true (or a timeout_ms of at least interval x attempts) so it is not timed out before the review lands. If the full poll budget is spent with no result file written, return {sha:${'`'}${head}${'`'}, clean:false, down:true, skipped:false, skipReason:'', findings:[]} and stop. When the background run finishes, read the result file and map its outcome:\n` +
|
|
2078
2091
|
` - when outcome_class is codex_down (or classify_codex_run on a non-zero exit reports codex_down) -> return {sha:${'`'}${head}${'`'}, clean:false, down:true, skipped:false, skipReason:'', findings:[]} and stop\n` +
|
|
2079
2092
|
` - when outcome_class is completed: parse findings with parse_codex_findings.parse_codex_findings(agent_message)\n` +
|
|
2080
2093
|
` - empty findings -> return {sha:${'`'}${head}${'`'}, clean:true, down:false, skipped:false, skipReason:'', findings:[]}\n` +
|
|
@@ -2263,6 +2276,101 @@ function isStandardsOnlyRound(findings) {
|
|
|
2263
2276
|
return findings.length > 0 && findings.every((each) => each.category === 'code-standard')
|
|
2264
2277
|
}
|
|
2265
2278
|
|
|
2279
|
+
/**
|
|
2280
|
+
* Decide whether a review phase surfaced ONLY P2 findings — pure low-severity
|
|
2281
|
+
* items with no P0/P1 mixed in. Such a phase still applies the fixes once, then
|
|
2282
|
+
* treats the fixed HEAD as clean for that phase and advances forward rather than
|
|
2283
|
+
* re-converging the same phase.
|
|
2284
|
+
* @param {Array<object>} findings already-deduped findings for the phase
|
|
2285
|
+
* @returns {boolean} true when every finding is severity P2
|
|
2286
|
+
*/
|
|
2287
|
+
function isP2OnlyFindings(findings) {
|
|
2288
|
+
return findings.length > 0 && findings.every((each) => each.severity === 'P2')
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* Map the phase that just finished a P2-only fix onto the next phase to enter
|
|
2293
|
+
* on the fixed HEAD. Pure P2 never returns to CONVERGE; each gate advances
|
|
2294
|
+
* forward only.
|
|
2295
|
+
*
|
|
2296
|
+
* ::
|
|
2297
|
+
*
|
|
2298
|
+
* nextPhaseAfterP2OnlyFix('CONVERGE') -> 'BUGBOT'
|
|
2299
|
+
* nextPhaseAfterP2OnlyFix('BUGBOT') -> 'COPILOT'
|
|
2300
|
+
* nextPhaseAfterP2OnlyFix('COPILOT') -> 'CODEX'
|
|
2301
|
+
* nextPhaseAfterP2OnlyFix('CODEX') -> 'FINALIZE'
|
|
2302
|
+
*
|
|
2303
|
+
* @param {string} currentPhase the phase that just applied the P2-only fixes
|
|
2304
|
+
* @returns {string} the next phase name
|
|
2305
|
+
*/
|
|
2306
|
+
function nextPhaseAfterP2OnlyFix(currentPhase) {
|
|
2307
|
+
const nextPhaseByCurrent = {
|
|
2308
|
+
CONVERGE: 'BUGBOT',
|
|
2309
|
+
BUGBOT: 'COPILOT',
|
|
2310
|
+
COPILOT: 'CODEX',
|
|
2311
|
+
}
|
|
2312
|
+
return nextPhaseByCurrent[currentPhase] || 'FINALIZE'
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
/**
|
|
2316
|
+
* Apply P2-only findings once and report whether the fixed HEAD advanced.
|
|
2317
|
+
* Shared by the four phase branches that fix pure-P2 findings then advance
|
|
2318
|
+
* forward without re-converging.
|
|
2319
|
+
*
|
|
2320
|
+
* ::
|
|
2321
|
+
*
|
|
2322
|
+
* await applyP2OnlyFix(head, findings, 'bugbot', 'bugbot gate', 'bugbot fix lens')
|
|
2323
|
+
* -> { progressed: true, newSha: '<fixed>', blocker: null }
|
|
2324
|
+
*
|
|
2325
|
+
* @param {string} head PR HEAD SHA the findings were raised against
|
|
2326
|
+
* @param {Array<object>} findings P2-only findings for this phase
|
|
2327
|
+
* @param {string} sourceLabel short label passed to applyFixes
|
|
2328
|
+
* @param {string} stallSubject noun phrase for the resolvedWithoutCommit stall message
|
|
2329
|
+
* @param {string} noPushSubject subject for the no-push stall message
|
|
2330
|
+
* @returns {Promise<{progressed: boolean, newSha: string, blocker: string|null}>} fix outcome
|
|
2331
|
+
*/
|
|
2332
|
+
async function applyP2OnlyFix(head, findings, sourceLabel, stallSubject, noPushSubject) {
|
|
2333
|
+
const fixResult = await applyFixes(head, findings, sourceLabel)
|
|
2334
|
+
const hadThreadBearingFinding = findings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
2335
|
+
const fixProgress = detectFixProgress(fixResult, head, hadThreadBearingFinding)
|
|
2336
|
+
if (fixProgress.progressed) {
|
|
2337
|
+
return { progressed: true, newSha: fixProgress.newSha, blocker: null }
|
|
2338
|
+
}
|
|
2339
|
+
const blocker =
|
|
2340
|
+
fixResult?.resolvedWithoutCommit === true && !hadThreadBearingFinding
|
|
2341
|
+
? `fix stalled: ${stallSubject} raised ${findings.length} in-memory finding(s) with no GitHub thread, the fix judged them all stale (resolvedWithoutCommit) and moved no code on HEAD ${head} — re-raising would loop to the iteration cap`
|
|
2342
|
+
: `${noPushSubject} landed no push for ${findings.length} finding(s) on HEAD ${head}`
|
|
2343
|
+
return { progressed: false, newSha: head, blocker }
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/**
|
|
2347
|
+
* Route after a successful P2-only fix. HEAD-bound CLEAN/review stamps are
|
|
2348
|
+
* SHA-specific: a push that moves HEAD must re-enter CONVERGE so those stamps
|
|
2349
|
+
* rebuild. A no-push progress (resolvedWithoutCommit) advances to the next
|
|
2350
|
+
* phase on the same HEAD.
|
|
2351
|
+
*
|
|
2352
|
+
* ::
|
|
2353
|
+
*
|
|
2354
|
+
* routeAfterP2OnlyFix('aaa', {newSha: 'bbb'}, 'COPILOT')
|
|
2355
|
+
* -> {fixedHead: 'bbb', nextPhase: 'CONVERGE', didMoveHead: true}
|
|
2356
|
+
* routeAfterP2OnlyFix('aaa', {newSha: 'aaa'}, 'COPILOT')
|
|
2357
|
+
* -> {fixedHead: 'aaa', nextPhase: 'COPILOT', didMoveHead: false}
|
|
2358
|
+
*
|
|
2359
|
+
* @param {string} priorHead HEAD SHA before the P2-only fix
|
|
2360
|
+
* @param {{newSha: string}} p2Fix successful applyP2OnlyFix result
|
|
2361
|
+
* @param {string} advancePhase phase to enter when HEAD did not move
|
|
2362
|
+
* @returns {{fixedHead: string, nextPhase: string, didMoveHead: boolean}} routing
|
|
2363
|
+
*/
|
|
2364
|
+
function routeAfterP2OnlyFix(priorHead, p2Fix, advancePhase) {
|
|
2365
|
+
const fixedHead = p2Fix.newSha
|
|
2366
|
+
const didMoveHead =
|
|
2367
|
+
normalizeShaForComparison(fixedHead) !== normalizeShaForComparison(priorHead)
|
|
2368
|
+
if (didMoveHead) {
|
|
2369
|
+
return { fixedHead, nextPhase: 'CONVERGE', didMoveHead: true }
|
|
2370
|
+
}
|
|
2371
|
+
return { fixedHead, nextPhase: advancePhase, didMoveHead: false }
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2266
2374
|
/**
|
|
2267
2375
|
* Decide whether a standards-only round should file the follow-up fix issue and
|
|
2268
2376
|
* open the environment-hardening PR. Standards findings are deferred rather than
|
|
@@ -2536,6 +2644,7 @@ let copilotDown = input.copilotDisabled || false
|
|
|
2536
2644
|
let codexDown = false
|
|
2537
2645
|
let codexCleanAt = null
|
|
2538
2646
|
let copilotNote = null
|
|
2647
|
+
let codexNote = null
|
|
2539
2648
|
let standardsNote = null
|
|
2540
2649
|
let cleanAuditNote = null
|
|
2541
2650
|
let hasStandardsFollowUpFiled = false
|
|
@@ -2549,6 +2658,7 @@ const assembleResult = (outcomeFields) => ({
|
|
|
2549
2658
|
...outcomeFields,
|
|
2550
2659
|
standardsNote,
|
|
2551
2660
|
copilotNote,
|
|
2661
|
+
codexNote,
|
|
2552
2662
|
cleanAuditNote,
|
|
2553
2663
|
reuseNote,
|
|
2554
2664
|
deferredPrs,
|
|
@@ -2645,6 +2755,23 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2645
2755
|
phase = 'BUGBOT'
|
|
2646
2756
|
continue
|
|
2647
2757
|
}
|
|
2758
|
+
if (isP2OnlyFindings(findings)) {
|
|
2759
|
+
resetNoLensRounds()
|
|
2760
|
+
const advancePhase = nextPhaseAfterP2OnlyFix('CONVERGE')
|
|
2761
|
+
log(`Round ${rounds}: ${findings.length} P2-only finding(s) — fixing once then routing from CONVERGE`)
|
|
2762
|
+
const p2Fix = await applyP2OnlyFix(head, findings, 'converge-round', 'converge round', 'fix lens')
|
|
2763
|
+
if (!p2Fix.progressed) {
|
|
2764
|
+
blocker = p2Fix.blocker
|
|
2765
|
+
break
|
|
2766
|
+
}
|
|
2767
|
+
const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
|
|
2768
|
+
head = p2Route.fixedHead
|
|
2769
|
+
phase = p2Route.nextPhase
|
|
2770
|
+
if (p2Route.didMoveHead) {
|
|
2771
|
+
log(`Round ${rounds}: P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild`)
|
|
2772
|
+
}
|
|
2773
|
+
continue
|
|
2774
|
+
}
|
|
2648
2775
|
if (findings.length > 0) {
|
|
2649
2776
|
resetNoLensRounds()
|
|
2650
2777
|
log(`Round ${rounds}: ${findings.length} finding(s) — applying fixes`)
|
|
@@ -2717,6 +2844,23 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2717
2844
|
phase = 'COPILOT'
|
|
2718
2845
|
continue
|
|
2719
2846
|
}
|
|
2847
|
+
if (isP2OnlyFindings(bugbotOutcome.findings)) {
|
|
2848
|
+
const advancePhase = nextPhaseAfterP2OnlyFix('BUGBOT')
|
|
2849
|
+
log(`Bugbot raised ${bugbotOutcome.findings.length} P2-only finding(s) — fixing once then routing from BUGBOT`)
|
|
2850
|
+
const p2Fix = await applyP2OnlyFix(head, bugbotOutcome.findings, 'bugbot', 'bugbot gate', 'bugbot fix lens')
|
|
2851
|
+
if (!p2Fix.progressed) {
|
|
2852
|
+
blocker = p2Fix.blocker
|
|
2853
|
+
break
|
|
2854
|
+
}
|
|
2855
|
+
const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
|
|
2856
|
+
head = p2Route.fixedHead
|
|
2857
|
+
bugbotDown = false
|
|
2858
|
+
phase = p2Route.nextPhase
|
|
2859
|
+
if (p2Route.didMoveHead) {
|
|
2860
|
+
log('Bugbot P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
|
|
2861
|
+
}
|
|
2862
|
+
continue
|
|
2863
|
+
}
|
|
2720
2864
|
log(`Bugbot raised ${bugbotOutcome.findings.length} finding(s) — fixing and re-converging`)
|
|
2721
2865
|
const fixResult = await applyFixes(head, bugbotOutcome.findings, 'bugbot')
|
|
2722
2866
|
const hadThreadBearingFinding = bugbotOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
@@ -2798,6 +2942,22 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2798
2942
|
phase = 'CODEX'
|
|
2799
2943
|
continue
|
|
2800
2944
|
}
|
|
2945
|
+
if (isP2OnlyFindings(roundFindings)) {
|
|
2946
|
+
const advancePhase = nextPhaseAfterP2OnlyFix('COPILOT')
|
|
2947
|
+
log(`Copilot raised ${roundFindings.length} P2-only finding(s) — fixing once then routing from COPILOT`)
|
|
2948
|
+
const p2Fix = await applyP2OnlyFix(head, roundFindings, 'copilot', 'copilot round', 'copilot fix lens')
|
|
2949
|
+
if (!p2Fix.progressed) {
|
|
2950
|
+
blocker = p2Fix.blocker
|
|
2951
|
+
break
|
|
2952
|
+
}
|
|
2953
|
+
const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
|
|
2954
|
+
head = p2Route.fixedHead
|
|
2955
|
+
phase = p2Route.nextPhase
|
|
2956
|
+
if (p2Route.didMoveHead) {
|
|
2957
|
+
log('Copilot P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
|
|
2958
|
+
}
|
|
2959
|
+
continue
|
|
2960
|
+
}
|
|
2801
2961
|
log(`Copilot raised ${roundFindings.length} finding(s) — fixing and re-converging`)
|
|
2802
2962
|
const fixResult = await applyFixes(head, roundFindings, 'copilot')
|
|
2803
2963
|
const hadThreadBearingFinding = roundFindings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
@@ -2840,7 +3000,8 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2840
3000
|
continue
|
|
2841
3001
|
}
|
|
2842
3002
|
if (codexOutcome.kind === 'down') {
|
|
2843
|
-
|
|
3003
|
+
codexNote = `Codex was unreachable this run (the codex-review wrapper classified codex_down — a Codex CLI error, or a review that ran past its timeout budget). The terminal Codex gate was skipped with the gate bypassed so a Codex outage does not hinder convergence; Codex runs again on the next convergence.`
|
|
3004
|
+
log(`Codex gate: ${codexNote}`)
|
|
2844
3005
|
codexDown = true
|
|
2845
3006
|
codexCleanAt = null
|
|
2846
3007
|
phase = 'FINALIZE'
|
|
@@ -2857,6 +3018,28 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2857
3018
|
phase = 'FINALIZE'
|
|
2858
3019
|
continue
|
|
2859
3020
|
}
|
|
3021
|
+
if (isP2OnlyFindings(codexOutcome.findings)) {
|
|
3022
|
+
const advancePhase = nextPhaseAfterP2OnlyFix('CODEX')
|
|
3023
|
+
log(`Codex raised ${codexOutcome.findings.length} P2-only finding(s) — fixing once then routing from CODEX`)
|
|
3024
|
+
const p2Fix = await applyP2OnlyFix(head, codexOutcome.findings, 'codex', 'codex gate', 'codex fix lens')
|
|
3025
|
+
if (!p2Fix.progressed) {
|
|
3026
|
+
blocker = p2Fix.blocker
|
|
3027
|
+
break
|
|
3028
|
+
}
|
|
3029
|
+
const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
|
|
3030
|
+
head = p2Route.fixedHead
|
|
3031
|
+
if (p2Route.didMoveHead) {
|
|
3032
|
+
log('Codex P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
|
|
3033
|
+
codexDown = false
|
|
3034
|
+
codexCleanAt = null
|
|
3035
|
+
phase = p2Route.nextPhase
|
|
3036
|
+
continue
|
|
3037
|
+
}
|
|
3038
|
+
codexDown = false
|
|
3039
|
+
codexCleanAt = head
|
|
3040
|
+
phase = p2Route.nextPhase
|
|
3041
|
+
continue
|
|
3042
|
+
}
|
|
2860
3043
|
log(`Codex raised ${codexOutcome.findings.length} finding(s) — fixing and re-converging`)
|
|
2861
3044
|
const fixResult = await applyFixes(head, codexOutcome.findings, 'codex')
|
|
2862
3045
|
const hadThreadBearingFinding = codexOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const workflowDirectory = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
|
|
9
|
+
|
|
10
|
+
function functionBody(functionName) {
|
|
11
|
+
const functionStart = convergeSource.indexOf(`function ${functionName}(`);
|
|
12
|
+
assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
|
|
13
|
+
const nextFunctionStart = convergeSource.indexOf('\nfunction ', functionStart + 1);
|
|
14
|
+
const functionEnd = nextFunctionStart === -1 ? convergeSource.length : nextFunctionStart;
|
|
15
|
+
return convergeSource.slice(functionStart, functionEnd);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const productionModule = new Function(
|
|
19
|
+
`const SHA_COMPARISON_PREFIX_LENGTH = 7;\n` +
|
|
20
|
+
`${functionBody('normalizeShaForComparison')}\n` +
|
|
21
|
+
`${functionBody('isP2OnlyFindings')}\n` +
|
|
22
|
+
`${functionBody('nextPhaseAfterP2OnlyFix')}\n` +
|
|
23
|
+
`${functionBody('routeAfterP2OnlyFix')}\n` +
|
|
24
|
+
'return { isP2OnlyFindings, nextPhaseAfterP2OnlyFix, routeAfterP2OnlyFix };',
|
|
25
|
+
)();
|
|
26
|
+
|
|
27
|
+
const { isP2OnlyFindings, nextPhaseAfterP2OnlyFix, routeAfterP2OnlyFix } = productionModule;
|
|
28
|
+
|
|
29
|
+
function finding(overrides) {
|
|
30
|
+
return {
|
|
31
|
+
file: 'a.py',
|
|
32
|
+
line: 1,
|
|
33
|
+
severity: 'P2',
|
|
34
|
+
category: 'bug',
|
|
35
|
+
title: 't',
|
|
36
|
+
detail: 'd',
|
|
37
|
+
replyToCommentId: null,
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function phaseBranch(phaseMarker) {
|
|
43
|
+
const phaseStart = convergeSource.indexOf(phaseMarker);
|
|
44
|
+
assert.notEqual(phaseStart, -1, `expected ${phaseMarker} to exist`);
|
|
45
|
+
const nextPhaseStart = convergeSource.indexOf('\n if (phase === ', phaseStart + 1);
|
|
46
|
+
const phaseEnd = nextPhaseStart === -1 ? convergeSource.length : nextPhaseStart;
|
|
47
|
+
return convergeSource.slice(phaseStart, phaseEnd);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test('isP2OnlyFindings is false for an empty findings list', () => {
|
|
51
|
+
assert.equal(isP2OnlyFindings([]), false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('isP2OnlyFindings is true when every finding is severity P2', () => {
|
|
55
|
+
assert.equal(isP2OnlyFindings([finding(), finding({ file: 'b.py', line: 2 })]), true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('isP2OnlyFindings is false when a P1 is mixed in with P2 findings', () => {
|
|
59
|
+
assert.equal(isP2OnlyFindings([finding(), finding({ severity: 'P1' })]), false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('isP2OnlyFindings is false when every finding is severity P1', () => {
|
|
63
|
+
assert.equal(isP2OnlyFindings([finding({ severity: 'P1' })]), false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('isP2OnlyFindings is false when a finding is missing severity', () => {
|
|
67
|
+
assert.equal(isP2OnlyFindings([{ file: 'a.py', line: 1, category: 'bug' }]), false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('nextPhaseAfterP2OnlyFix advances CONVERGE to BUGBOT', () => {
|
|
71
|
+
assert.equal(nextPhaseAfterP2OnlyFix('CONVERGE'), 'BUGBOT');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('nextPhaseAfterP2OnlyFix advances BUGBOT to COPILOT', () => {
|
|
75
|
+
assert.equal(nextPhaseAfterP2OnlyFix('BUGBOT'), 'COPILOT');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('nextPhaseAfterP2OnlyFix advances COPILOT to CODEX', () => {
|
|
79
|
+
assert.equal(nextPhaseAfterP2OnlyFix('COPILOT'), 'CODEX');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('nextPhaseAfterP2OnlyFix advances CODEX to FINALIZE', () => {
|
|
83
|
+
assert.equal(nextPhaseAfterP2OnlyFix('CODEX'), 'FINALIZE');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('routeAfterP2OnlyFix re-converges when the fix moved HEAD', () => {
|
|
87
|
+
const route = routeAfterP2OnlyFix('aaa111', { newSha: 'bbb222' }, 'COPILOT');
|
|
88
|
+
assert.equal(route.didMoveHead, true);
|
|
89
|
+
assert.equal(route.fixedHead, 'bbb222');
|
|
90
|
+
assert.equal(route.nextPhase, 'CONVERGE');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('routeAfterP2OnlyFix advances when HEAD is unchanged', () => {
|
|
94
|
+
const route = routeAfterP2OnlyFix('aaa111', { newSha: 'aaa111' }, 'COPILOT');
|
|
95
|
+
assert.equal(route.didMoveHead, false);
|
|
96
|
+
assert.equal(route.fixedHead, 'aaa111');
|
|
97
|
+
assert.equal(route.nextPhase, 'COPILOT');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('nextPhaseAfterP2OnlyFix uses an in-function lookup map', () => {
|
|
101
|
+
const body = functionBody('nextPhaseAfterP2OnlyFix');
|
|
102
|
+
assert.match(body, /nextPhaseByCurrent/);
|
|
103
|
+
assert.match(body, /CONVERGE:\s*'BUGBOT'/);
|
|
104
|
+
assert.match(body, /BUGBOT:\s*'COPILOT'/);
|
|
105
|
+
assert.match(body, /COPILOT:\s*'CODEX'/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('applyP2OnlyFix runs applyFixes then detectFixProgress in order', () => {
|
|
109
|
+
const body = functionBody('applyP2OnlyFix');
|
|
110
|
+
const applyIndex = body.indexOf('applyFixes(head, findings, sourceLabel)');
|
|
111
|
+
const detectIndex = body.indexOf('detectFixProgress(fixResult, head, hadThreadBearingFinding)');
|
|
112
|
+
assert.notEqual(applyIndex, -1, 'expected applyFixes in applyP2OnlyFix');
|
|
113
|
+
assert.notEqual(detectIndex, -1, 'expected detectFixProgress in applyP2OnlyFix');
|
|
114
|
+
assert.ok(applyIndex < detectIndex, 'applyFixes must precede detectFixProgress');
|
|
115
|
+
assert.match(body, /collectFindingThreadIds/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('CONVERGE places the P2-only branch between standards-only and generic fix', () => {
|
|
119
|
+
const convergeBranch = phaseBranch("if (phase === 'CONVERGE')");
|
|
120
|
+
const standardsIndex = convergeBranch.indexOf('isStandardsOnlyRound(findings)');
|
|
121
|
+
const p2OnlyIndex = convergeBranch.indexOf('isP2OnlyFindings(findings)');
|
|
122
|
+
const genericFixIndex = convergeBranch.indexOf("finding(s) — applying fixes");
|
|
123
|
+
assert.notEqual(standardsIndex, -1, 'expected the standards-only branch');
|
|
124
|
+
assert.notEqual(p2OnlyIndex, -1, 'expected the P2-only branch');
|
|
125
|
+
assert.notEqual(genericFixIndex, -1, 'expected the generic fix branch');
|
|
126
|
+
assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only');
|
|
127
|
+
assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic fix');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('CONVERGE P2-only branch fixes once and routes via routeAfterP2OnlyFix', () => {
|
|
131
|
+
const convergeBranch = phaseBranch("if (phase === 'CONVERGE')");
|
|
132
|
+
const p2Start = convergeBranch.indexOf('isP2OnlyFindings(findings)');
|
|
133
|
+
const genericFixStart = convergeBranch.indexOf("finding(s) — applying fixes");
|
|
134
|
+
const p2Branch = convergeBranch.slice(p2Start, genericFixStart);
|
|
135
|
+
assert.match(p2Branch, /P2-only/);
|
|
136
|
+
assert.match(p2Branch, /applyP2OnlyFix\(head, findings, 'converge-round'/);
|
|
137
|
+
assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
|
|
138
|
+
assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('CONVERGE'\)/);
|
|
139
|
+
assert.match(p2Branch, /head = p2Route\.fixedHead/);
|
|
140
|
+
assert.match(p2Branch, /phase = p2Route\.nextPhase/);
|
|
141
|
+
assert.doesNotMatch(p2Branch, /head = null/);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('BUGBOT P2-only branch routes through routeAfterP2OnlyFix', () => {
|
|
145
|
+
const bugbotBranch = phaseBranch("if (phase === 'BUGBOT')");
|
|
146
|
+
const standardsIndex = bugbotBranch.indexOf('isStandardsOnlyRound(bugbotOutcome.findings)');
|
|
147
|
+
const p2OnlyIndex = bugbotBranch.indexOf('isP2OnlyFindings(bugbotOutcome.findings)');
|
|
148
|
+
const genericFixIndex = bugbotBranch.indexOf('fixing and re-converging');
|
|
149
|
+
assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on BUGBOT');
|
|
150
|
+
assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on BUGBOT');
|
|
151
|
+
const p2Branch = bugbotBranch.slice(p2OnlyIndex, genericFixIndex);
|
|
152
|
+
assert.match(p2Branch, /P2-only/);
|
|
153
|
+
assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('BUGBOT'\)/);
|
|
154
|
+
assert.match(p2Branch, /applyP2OnlyFix\(head, bugbotOutcome\.findings, 'bugbot'/);
|
|
155
|
+
assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
|
|
156
|
+
assert.match(p2Branch, /phase = p2Route\.nextPhase/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('COPILOT P2-only branch routes through routeAfterP2OnlyFix', () => {
|
|
160
|
+
const copilotBranch = phaseBranch("if (phase === 'COPILOT')");
|
|
161
|
+
const standardsIndex = copilotBranch.indexOf('isStandardsOnlyRound(roundFindings)');
|
|
162
|
+
const p2OnlyIndex = copilotBranch.indexOf('isP2OnlyFindings(roundFindings)');
|
|
163
|
+
const genericFixIndex = copilotBranch.indexOf('fixing and re-converging');
|
|
164
|
+
assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on COPILOT');
|
|
165
|
+
assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on COPILOT');
|
|
166
|
+
const p2Branch = copilotBranch.slice(p2OnlyIndex, genericFixIndex);
|
|
167
|
+
assert.match(p2Branch, /P2-only/);
|
|
168
|
+
assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('COPILOT'\)/);
|
|
169
|
+
assert.match(p2Branch, /applyP2OnlyFix\(head, roundFindings, 'copilot'/);
|
|
170
|
+
assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
|
|
171
|
+
assert.match(p2Branch, /phase = p2Route\.nextPhase/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('CODEX P2-only branch routes through routeAfterP2OnlyFix and stamps codexCleanAt only when HEAD is stable', () => {
|
|
175
|
+
const codexBranch = phaseBranch("if (phase === 'CODEX')");
|
|
176
|
+
const standardsIndex = codexBranch.indexOf('isStandardsOnlyRound(codexOutcome.findings)');
|
|
177
|
+
const p2OnlyIndex = codexBranch.indexOf('isP2OnlyFindings(codexOutcome.findings)');
|
|
178
|
+
const genericFixIndex = codexBranch.indexOf('fixing and re-converging');
|
|
179
|
+
assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on CODEX');
|
|
180
|
+
assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on CODEX');
|
|
181
|
+
const p2Branch = codexBranch.slice(p2OnlyIndex, genericFixIndex);
|
|
182
|
+
assert.match(p2Branch, /P2-only/);
|
|
183
|
+
assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('CODEX'\)/);
|
|
184
|
+
assert.match(p2Branch, /applyP2OnlyFix\(head, codexOutcome\.findings, 'codex'/);
|
|
185
|
+
assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
|
|
186
|
+
assert.match(p2Branch, /codexCleanAt = head/);
|
|
187
|
+
assert.match(p2Branch, /didMoveHead/);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('generic fix branches still re-converge for mixed or P0/P1 findings', () => {
|
|
191
|
+
const bugbotBranch = phaseBranch("if (phase === 'BUGBOT')");
|
|
192
|
+
const genericStart = bugbotBranch.indexOf('fixing and re-converging');
|
|
193
|
+
const genericBranch = bugbotBranch.slice(genericStart);
|
|
194
|
+
assert.match(genericBranch, /phase = 'CONVERGE'/);
|
|
195
|
+
assert.match(genericBranch, /head = null/);
|
|
196
|
+
|
|
197
|
+
const copilotBranch = phaseBranch("if (phase === 'COPILOT')");
|
|
198
|
+
const copilotGenericStart = copilotBranch.indexOf('fixing and re-converging');
|
|
199
|
+
const copilotGeneric = copilotBranch.slice(copilotGenericStart);
|
|
200
|
+
assert.match(copilotGeneric, /phase = 'CONVERGE'/);
|
|
201
|
+
assert.match(copilotGeneric, /head = null/);
|
|
202
|
+
});
|
|
@@ -109,11 +109,14 @@ function classifyMultiInput(rawArgs) {
|
|
|
109
109
|
* fan-out honors it: bugbotDisabled skips the Bugbot phase, and copilotDisabled
|
|
110
110
|
* short-circuits the Copilot quota gate when the account is out of premium
|
|
111
111
|
* requests. An entry that omits an opt-out defaults it to false so the child
|
|
112
|
-
* runs that phase.
|
|
112
|
+
* runs that phase. homeDirectory is a run-level value forwarded to every child
|
|
113
|
+
* so the child can build its codex-review scripts path: the workflow sandbox
|
|
114
|
+
* exposes no process global, so the child reads home from its args, not env.
|
|
113
115
|
* @param {object} prEntry one validated element of the args.prs array
|
|
116
|
+
* @param {string} homeDirectory the run-level home directory for every child
|
|
114
117
|
* @returns {object} the args object passed to the converge.mjs child run
|
|
115
118
|
*/
|
|
116
|
-
function childRunInput(prEntry) {
|
|
119
|
+
function childRunInput(prEntry, homeDirectory) {
|
|
117
120
|
return {
|
|
118
121
|
owner: prEntry.owner,
|
|
119
122
|
repo: prEntry.repo,
|
|
@@ -121,6 +124,7 @@ function childRunInput(prEntry) {
|
|
|
121
124
|
repoPath: prEntry.repoPath,
|
|
122
125
|
bugbotDisabled: Boolean(prEntry.bugbotDisabled),
|
|
123
126
|
copilotDisabled: Boolean(prEntry.copilotDisabled),
|
|
127
|
+
homeDirectory,
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
@@ -137,7 +141,7 @@ const childResults = await parallel(
|
|
|
137
141
|
input.prs.map((eachPr) => async () => {
|
|
138
142
|
const childOutcome = await workflow(
|
|
139
143
|
{ scriptPath: input.convergeScriptPath },
|
|
140
|
-
childRunInput(eachPr),
|
|
144
|
+
childRunInput(eachPr, input.homeDirectory),
|
|
141
145
|
)
|
|
142
146
|
return {
|
|
143
147
|
owner: eachPr.owner,
|
|
@@ -138,6 +138,11 @@ test('childRunInput defaults bugbotDisabled to false when the entry omits it', (
|
|
|
138
138
|
assert.equal(childRunInput(validEntry(101)).bugbotDisabled, false);
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
test('childRunInput forwards the run-level home directory to the child run', () => {
|
|
142
|
+
const childArgs = childRunInput(validEntry(101), '/home/dev');
|
|
143
|
+
assert.equal(childArgs.homeDirectory, '/home/dev');
|
|
144
|
+
});
|
|
145
|
+
|
|
141
146
|
test('the malformed-input blocker return carries an empty allDeferredPrs list', async () => {
|
|
142
147
|
const blockerOutcome = await runMultiWorkflowBody(
|
|
143
148
|
'not json at all',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a11d903476b803493-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a11d903476b803493-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a11d903476b803493", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": false, "findings": [{"file": "src/exports/core/export_runner.py", "line": 96, "severity": "P2", "title": "New inline log string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): the new log message at this line introduces a new string literal in production code. Prefer reusing an existing configured UI message (e.g. web_editor_config.ui_messages.msg_detected_version) instead of adding a new inline literal.", "replyToCommentId": 3407912224}, {"file": "src/web/services/os_generation_ssot.py", "line": 265, "severity": "P2", "title": "New inline warning string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): read_current_version_name_or_none() adds a new inline warning string in production code. Per repo convention, extract this message into a shared config constant (in src/shared/web_automation/config/) so future callers reuse it consistently.", "replyToCommentId": 3407912234}], "blocker": null}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a11d903476b803493-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a11d903476b803493-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a11d903476b803493", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": false, "findings": [{"file": "src/exports/core/export_runner.py", "line": 96, "severity": "P2", "title": "New inline log string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): the new log message at this line introduces a new string literal in production code. Prefer reusing an existing configured UI message (e.g. web_editor_config.ui_messages.msg_detected_version) instead of adding a new inline literal.", "replyToCommentId": 3407912224}, {"file": "src/web/services/os_generation_ssot.py", "line": 265, "severity": "P2", "title": "New inline warning string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): read_current_version_name_or_none() adds a new inline warning string in production code. Per repo convention, extract this message into a shared config constant (in src/shared/web_automation/config/) so future callers reuse it consistently.", "replyToCommentId": 3407912234}], "blocker": null}}]}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a26213978adeef6fb-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a26213978adeef6fb-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a26213978adeef6fb", "name": "StructuredOutput", "input": {"newSha": "620382f766c4731ba92eefeb30bdbb9d0bcd4c90", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed all 11 Copilot findings in one commit (620382f7): renamed banned `result` to `skip_path` in test_resume_skip_export.py and added `-> None` return annotations to the test functions across test_resume_skip_export.py, test_stp_path_selection.py, test_version_export_guards.py, and test_import_isolation.py; all 9 affected tests pass; pushed to fix/example-feature, posted 11 inline replies, and all 11 review threads are resolved."}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a26213978adeef6fb-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a26213978adeef6fb-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a26213978adeef6fb", "name": "StructuredOutput", "input": {"newSha": "620382f766c4731ba92eefeb30bdbb9d0bcd4c90", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed all 11 Copilot findings in one commit (620382f7): renamed banned `result` to `skip_path` in test_resume_skip_export.py and added `-> None` return annotations to the test functions across test_resume_skip_export.py, test_stp_path_selection.py, test_version_export_guards.py, and test_import_isolation.py; all 9 affected tests pass; pushed to fix/example-feature, posted 11 inline replies, and all 11 review threads are resolved."}}]}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a3def0d15ed9d9110-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a3def0d15ed9d9110-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a3def0d15ed9d9110", "name": "StructuredOutput", "input": {"sha": "6a5169ef635ddb841e6f0465a309691ff77f033e", "clean": false, "blocker": null, "findings": [{"file": "src/exports/tests/test_resume_skip_export.py", "line": 35, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883689}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 47, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883707}, {"file": "src/logging/tests/test_import_isolation.py", "line": 33, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883713}, {"file": "src/logging/tests/test_import_isolation.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883717}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 37, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883723}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 74, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883727}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 19, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883731}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 31, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883736}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883740}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 21, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883749}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 38, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883755}]}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a3def0d15ed9d9110-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a3def0d15ed9d9110-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a3def0d15ed9d9110", "name": "StructuredOutput", "input": {"sha": "6a5169ef635ddb841e6f0465a309691ff77f033e", "clean": false, "blocker": null, "findings": [{"file": "src/exports/tests/test_resume_skip_export.py", "line": 35, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883689}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 47, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883707}, {"file": "src/logging/tests/test_import_isolation.py", "line": 33, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883713}, {"file": "src/logging/tests/test_import_isolation.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883717}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 37, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883723}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 74, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883727}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 19, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883731}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 31, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883736}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883740}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 21, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883749}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 38, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883755}]}}]}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a41f41b1b708ee3b7-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a41f41b1b708ee3b7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a41f41b1b708ee3b7", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": true, "findings": [], "blocker": null}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a41f41b1b708ee3b7-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a41f41b1b708ee3b7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a41f41b1b708ee3b7", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": true, "findings": [], "blocker": null}}]}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a758b880abecc3ff7-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a758b880abecc3ff7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a758b880abecc3ff7", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": false, "resolvedWithoutCommit": true, "summary": "Both Copilot magic-value findings were already fixed at HEAD 7c2f420c (export_runner.py uses web_editor_config.ui_messages.msg_detected_version; os_generation_ssot.py uses os_generation_ssot_constants.unavailable_warning_template); posted inline replies and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a758b880abecc3ff7-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a758b880abecc3ff7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a758b880abecc3ff7", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": false, "resolvedWithoutCommit": true, "summary": "Both Copilot magic-value findings were already fixed at HEAD 7c2f420c (export_runner.py uses web_editor_config.ui_messages.msg_detected_version; os_generation_ssot.py uses os_generation_ssot_constants.unavailable_warning_template); posted inline replies and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{"type": "user", "uuid": "a8897b89656b1bd16-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
-
{"type": "assistant", "uuid": "a8897b89656b1bd16-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a8897b89656b1bd16", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed both Copilot magic-value findings on PR #211: export_runner.py now reuses web_editor_config.ui_messages.msg_detected_version, and os_generation_ssot.py references a new os_generation_ssot_constants.unavailable_warning_template. Added a behavioral TDD test, committed (7c2f420c) and pushed; posted inline replies to both comments (3407912224, 3407912234) and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
|
|
1
|
+
{"type": "user", "uuid": "a8897b89656b1bd16-u", "message": {"role": "user", "content": "Return the structured result."}}
|
|
2
|
+
{"type": "assistant", "uuid": "a8897b89656b1bd16-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a8897b89656b1bd16", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed both Copilot magic-value findings on PR #211: export_runner.py now reuses web_editor_config.ui_messages.msg_detected_version, and os_generation_ssot.py references a new os_generation_ssot_constants.unavailable_warning_template. Added a behavioral TDD test, committed (7c2f420c) and pushed; posted inline replies to both comments (3407912224, 3407912234) and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
|