claude-dev-env 1.92.0 → 1.93.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 +5 -39
- package/_shared/CLAUDE.md +2 -1
- package/_shared/advisor/CLAUDE.md +21 -0
- package/_shared/advisor/advisor-protocol.md +142 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
- package/_shared/advisor/scripts/pyproject.toml +3 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
- package/_shared/advisor/scripts/tier_model_ids.py +164 -0
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/precatch-rubric.md +65 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
- package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
- package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
- package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
- package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
- package/agents/CLAUDE.md +3 -2
- package/agents/clasp-deployment-orchestrator.md +2 -2
- package/agents/code-advisor.md +7 -5
- package/agents/code-verifier.md +6 -2
- package/agents/session-advisor.md +27 -0
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
- package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
- package/bin/CLAUDE.md +1 -0
- package/bin/expand_home_directory_tokens.mjs +75 -0
- package/bin/install.mjs +21 -7
- package/bin/install.test.mjs +220 -10
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +4 -1
- package/hooks/blocking/_path_setup.py +13 -0
- package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/code_rules_imports_logging.py +34 -1
- package/hooks/blocking/code_rules_paths_syspath.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +272 -0
- package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
- package/hooks/blocking/pii_prevention_blocker.py +781 -0
- package/hooks/blocking/pii_scanner.py +340 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/stop_dispatcher.py +84 -0
- package/hooks/blocking/test__path_setup.py +39 -0
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
- package/hooks/blocking/test_destructive_command_blocker.py +29 -9
- package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
- package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
- package/hooks/blocking/test_pii_scanner.py +165 -0
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
- package/hooks/blocking/test_stop_dispatcher.py +187 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
- package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
- package/hooks/diagnostic/migrations/README.md +25 -26
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
- package/hooks/hooks.json +10 -120
- package/hooks/hooks_constants/CLAUDE.md +12 -3
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
- package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
- package/hooks/hooks_constants/local_identity.py +182 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
- package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
- package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
- package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
- package/hooks/hooks_constants/test_layout_constants.py +28 -0
- package/hooks/hooks_constants/test_local_identity.py +88 -0
- package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
- package/hooks/validators/README.md +5 -6
- package/hooks/validators/__init__.py +2 -2
- package/hooks/validators/python_style_checks.py +298 -243
- package/hooks/validators/run_all_validators.py +6 -0
- package/hooks/validators/test_python_style_checks.py +278 -163
- package/package.json +2 -2
- package/rules/CLAUDE.md +7 -1
- package/rules/bdd.md +1 -1
- package/rules/nas-ssh-invocation.md +6 -4
- package/rules/no-justification-noise.md +61 -0
- package/rules/testing.md +0 -2
- package/scripts/test_setup_project_paths.py +1 -1
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +113 -35
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +44 -22
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/reference/stop-conditions.md +23 -13
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
- package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.mjs +329 -68
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
- package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
- package/skills/bdd-protocol/SKILL.md +4 -5
- package/skills/bdd-protocol/references/anti-patterns.md +1 -1
- package/skills/bugteam/SKILL.md +8 -0
- package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +7 -5
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/copilot-finding-triage/SKILL.md +124 -0
- package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
- package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
- package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
- package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
- package/skills/copilot-finding-triage/templates/notification.md +34 -0
- package/skills/copilot-review/SKILL.md +8 -6
- package/skills/findbugs/SKILL.md +4 -0
- package/skills/fixbugs/SKILL.md +8 -7
- package/skills/gotcha/CLAUDE.md +2 -2
- package/skills/gotcha/SKILL.md +4 -4
- package/skills/log-audit/SKILL.md +4 -6
- package/skills/monitor-open-prs/CLAUDE.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -1
- package/skills/orchestrator/SKILL.md +184 -0
- package/skills/orchestrator-refresh/SKILL.md +37 -0
- package/skills/post-audit-findings/SKILL.md +5 -9
- package/skills/pr-consistency-audit/SKILL.md +5 -1
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +86 -47
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +20 -17
- package/skills/pr-converge/reference/examples.md +63 -47
- package/skills/pr-converge/reference/fix-protocol.md +15 -15
- package/skills/pr-converge/reference/ground-rules.md +11 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
- package/skills/pr-converge/reference/per-tick.md +129 -107
- package/skills/pr-converge/reference/state-schema.md +15 -10
- package/skills/pr-converge/scripts/check_convergence.py +5 -4
- package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
- package/skills/pr-fix-protocol/SKILL.md +3 -8
- package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
- package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
- package/skills/pr-loop-lifecycle/SKILL.md +5 -9
- package/skills/pr-scope-resolve/SKILL.md +3 -6
- package/skills/privacy-hygiene/SKILL.md +114 -0
- package/skills/qbug/SKILL.md +8 -8
- package/skills/rebase/SKILL.md +5 -1
- package/skills/refine/SKILL.md +4 -5
- package/skills/reviewer-gates/SKILL.md +7 -11
- package/skills/session-log/SKILL.md +4 -1
- package/skills/skill-builder/SKILL.md +3 -6
- package/skills/structure-prompt/SKILL.md +4 -5
- package/skills/team-advisor/SKILL.md +32 -164
- package/skills/test_markdown_link_integrity.py +10 -6
- package/skills/update/SKILL.md +5 -1
- package/skills/usage-pause/SKILL.md +14 -5
- package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
- package/skills/verified-build/SKILL.md +4 -9
- package/skills/team-advisor-refresh/SKILL.md +0 -25
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
export const meta = {
|
|
13
13
|
name: 'autoconverge',
|
|
14
|
-
description: 'Drive one draft PR to convergence in a single autonomous run:
|
|
14
|
+
description: 'Drive one draft PR to convergence in a single autonomous run: each round runs a deterministic static sweep first, then code-review, bug-audit, and self-review lenses in parallel on the same HEAD, dedups findings, fixes once, and re-verifies; Bugbot and Copilot then run as terminal confirmation gates before a final convergence check marks the PR ready.',
|
|
15
15
|
whenToUse: 'Launched by the /autoconverge skill after it resolves PR scope, enters a worktree, and grants project .claude permissions.',
|
|
16
16
|
phases: [
|
|
17
17
|
{ title: 'Reuse', detail: 'Before convergence, one reuse lens scans the full diff for reuse improvements that are certain, behaviorally identical, and autonomously implementable, and applies the qualifying ones in one commit' },
|
|
18
|
-
{ title: 'Converge', detail: '
|
|
19
|
-
{ title: '
|
|
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
|
+
{ 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
|
+
{ 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 one or more code-concern findings return blocker user-review with the findings 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 mark the PR ready with the gate bypassed' },
|
|
20
21
|
{ title: 'Finalize', detail: 'Run check_convergence.py; mark draft=false on a full pass' },
|
|
21
22
|
],
|
|
22
23
|
}
|
|
@@ -28,6 +29,7 @@ const CONFIG = {
|
|
|
28
29
|
sharedScripts: '$HOME/.claude/skills/pr-converge/scripts',
|
|
29
30
|
prLoopScripts: '$HOME/.claude/_shared/pr-loop/scripts',
|
|
30
31
|
bugteamRubric: '$HOME/.claude/_shared/pr-loop/audit-contract.md',
|
|
32
|
+
precatchRubric: '$HOME/.claude/_shared/pr-loop/precatch-rubric.md',
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const REVIEWER_GATE_SENTINEL = 'CLAUDE_REVIEWER_GATE=autoconverge '
|
|
@@ -38,7 +40,7 @@ const TIERS = {
|
|
|
38
40
|
haikuLow: { model: 'haiku', effort: 'low' },
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const
|
|
43
|
+
const HEADLESS_EDIT_PREAMBLE =
|
|
42
44
|
'HEADLESS RUN — you run unattended: no human can answer a permission or confirmation prompt, and any such prompt stalls the entire convergence run. The destructive_command_blocker hook matches dangerous patterns (rm -rf, git reset --hard, dd, mkfs, chmod -R, fork bombs) as raw text anywhere in a Bash command, with no quote-awareness — so a destructive string stalls you even when it is only data you never execute. Therefore:\n' +
|
|
43
45
|
'- 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' +
|
|
44
46
|
'- 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' +
|
|
@@ -50,6 +52,22 @@ const HEADLESS_SAFETY_PREAMBLE =
|
|
|
50
52
|
'- Size the Monitor to the whole wait: its timeout_ms defaults to 300000 (300 seconds) and the Monitor is killed the moment that elapses, so a poll whose interval or whose interval-times-attempts span runs longer registers a false time-out. Before you arm a Monitor, set timeout_ms to at least the poll interval times the attempt count the step names — up to the 3600000ms ceiling — or pass persistent: true so the Monitor is never timed out. A 360-second interval, or a many-minute total, exceeds the 300000 default, so arming the Monitor with the default truncates the wait; set timeout_ms to the step span instead.\n' +
|
|
51
53
|
'- When your run was given a result schema, your final action is always the StructuredOutput call. If the poll budget is spent before the awaited signal arrives, call StructuredOutput with the whole time-out result the step documents — for the Copilot gate, the full down result {sha, clean:false, down:true, findings:[]}, never a bare down flag — rather than ending the turn without a result.\n\n'
|
|
52
54
|
|
|
55
|
+
const HEADLESS_READONLY_DESTRUCTIVE_POINTER =
|
|
56
|
+
'- Never run a destructive command (rm -rf, git reset --hard, dd, mkfs, chmod -R, a fork bomb) and never place its literal text in a Bash command: this step reads only and edits nothing, so it needs no destructive command. If a step seems to require one, report it as a blocker rather than running it.\n'
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The read-only preamble a review, verify, or utility agent receives: the full
|
|
60
|
+
* edit preamble with the rm-shape-rules bullet dropped, since an agent that edits
|
|
61
|
+
* nothing never runs rm and the shape rules add no value to its prompt. The
|
|
62
|
+
* one-line destructive pointer keeps the escape-hatch guidance in view. The
|
|
63
|
+
* derivation reads the single rm-shape bullet out of the edit preamble and swaps
|
|
64
|
+
* the pointer in, so the two preambles share every other clause from one source.
|
|
65
|
+
*/
|
|
66
|
+
const HEADLESS_READONLY_PREAMBLE = HEADLESS_EDIT_PREAMBLE.replace(
|
|
67
|
+
/- rm shape rules — [\s\S]*?\n(?=- If a step appears to require)/,
|
|
68
|
+
HEADLESS_READONLY_DESTRUCTIVE_POINTER,
|
|
69
|
+
)
|
|
70
|
+
|
|
53
71
|
let activeRepoPath = null
|
|
54
72
|
|
|
55
73
|
/**
|
|
@@ -72,18 +90,32 @@ const worktreeDirective = (repoPath) =>
|
|
|
72
90
|
: ''
|
|
73
91
|
|
|
74
92
|
/**
|
|
75
|
-
* Spawn a workflow agent with the
|
|
76
|
-
* prompt.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
93
|
+
* Spawn a workflow agent that edits code, with the full edit preamble prepended
|
|
94
|
+
* to its prompt. An edit agent may run rm during cleanup, so it inherits the
|
|
95
|
+
* rm-shape rules alongside the no-confirmation-prompt guidance. On a path-scoped
|
|
96
|
+
* run the worktree directive is prepended too, so the agent runs in the PR's own
|
|
97
|
+
* worktree (activeRepoPath); on a single-PR run that directive is empty and the
|
|
98
|
+
* agent keeps its own working directory. A read-only agent (a review, verify, or
|
|
99
|
+
* utility spawn) routes through convergeReadOnlyAgent for the trimmed preamble.
|
|
81
100
|
* @param {string} prompt the agent's role-specific instruction body
|
|
82
101
|
* @param {object} options the agent() options (label, phase, schema, agentType, model)
|
|
83
102
|
* @returns {Promise<*>} the agent() result
|
|
84
103
|
*/
|
|
85
104
|
const convergeAgent = (prompt, options) =>
|
|
86
|
-
agent(`${
|
|
105
|
+
agent(`${HEADLESS_EDIT_PREAMBLE}${worktreeDirective(activeRepoPath)}${prompt}`, options)
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Spawn a read-only workflow agent with the trimmed read-only preamble prepended
|
|
109
|
+
* to its prompt. A review, verify, or utility agent edits nothing and never runs
|
|
110
|
+
* rm, so it receives the read-only preamble that drops the rm-shape-rules bullet.
|
|
111
|
+
* The worktree directive is prepended on a path-scoped run exactly as for
|
|
112
|
+
* convergeAgent, so a read-only agent still runs in the PR's own worktree.
|
|
113
|
+
* @param {string} prompt the agent's role-specific instruction body
|
|
114
|
+
* @param {object} options the agent() options (label, phase, schema, agentType, model)
|
|
115
|
+
* @returns {Promise<*>} the agent() result
|
|
116
|
+
*/
|
|
117
|
+
const convergeReadOnlyAgent = (prompt, options) =>
|
|
118
|
+
agent(`${HEADLESS_READONLY_PREAMBLE}${worktreeDirective(activeRepoPath)}${prompt}`, options)
|
|
87
119
|
|
|
88
120
|
/**
|
|
89
121
|
* Spawn a fresh git-utility Explore agent for a specific task. The one task,
|
|
@@ -105,7 +137,7 @@ function runGitTask(task) {
|
|
|
105
137
|
if (task !== 'preflight-git') {
|
|
106
138
|
throw new Error(`runGitTask has no handler for task ${task}`)
|
|
107
139
|
}
|
|
108
|
-
return
|
|
140
|
+
return convergeReadOnlyAgent(
|
|
109
141
|
`Run five read-only preflight steps for ${prCoordinates}. Do not edit, commit, push, rebase, or modify any files — read only.\n\n` +
|
|
110
142
|
`STEP 1 — resolve HEAD. Print the current PR HEAD SHA. Run exactly:\n` +
|
|
111
143
|
` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .head.sha\n` +
|
|
@@ -308,7 +340,7 @@ function runCodeEditorTask(task, context) {
|
|
|
308
340
|
`You are the EDIT step deferring a code-standard-only round on ${prCoordinates}, HEAD ${context.head} (${context.sourceLabel}). The round surfaced ONLY code-standard violations (CODE_RULES/style, no behavioral impact); the run treats it as passed and defers the fixes to follow-up work, which you now stage. A separate verify step then a separate commit step open the hardening PR after you. Do NOT commit or push to the PR's own branch.\n\n` +
|
|
309
341
|
`Findings:\n${findingsBlock}\n\n` +
|
|
310
342
|
`1. Follow-up fix issue: file a GitHub issue on ${input.owner}/${input.repo} (gh issue create --body-file with a temp file) titled "Deferred code-standard fixes from PR #${input.prNumber}". The body references the PR and lists each finding with its file:line, severity, and detail. The issue carries the fix work; do not open a fix PR. Capture the issue URL.\n` +
|
|
311
|
-
`2. Stage the environment-hardening change: in the Claude environment config repo (the repo owning ~/.claude hooks and rules — JonEcho/llm-settings for hooks, jl-cmd/claude-
|
|
343
|
+
`2. Stage the environment-hardening change: in the Claude environment config repo (the repo owning ~/.claude hooks and rules — JonEcho/llm-settings for hooks, jl-cmd/claude-dev-env for rules/skills; pick whichever owns the surface that would block these violation classes), find or clone a local checkout, fetch origin, and create a branch off origin/main. Edit the hooks/rules in that checkout's WORKING TREE so each violation class found here is blocked at Write/Edit time, before code is written. Do NOT commit and do NOT push — the commit step does that after the verify step binds a verdict to the working tree. Return the checkout's absolute path in hardeningRepoPath, the branch name in hardeningBranch, and set hardeningEdited=true. When no hardening is feasible for these classes, leave hardeningRepoPath and hardeningBranch empty and hardeningEdited=false; the follow-up issue still stands.\n` +
|
|
312
344
|
`3. For each finding that carries a GitHub review comment id (${threadIds.length ? threadIds.join(', ') : 'none this batch'}): post an inline reply via python "${CONFIG.sharedScripts}/post_fix_reply.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --in-reply-to <id> --body "Code-standard-only finding — deferred to follow-up issue <url>." Then resolve the thread by its PRRT_ node id (GraphQL lookup on comment databaseId, then resolveReviewThread or the github MCP pull_request_review_write method=resolve_thread — not the numeric comment id).\n\n` +
|
|
313
345
|
`Return the issue URL in issueUrl (empty string when it could not be filed), the hardening checkout path and branch, hardeningEdited, and a one-line summary.` +
|
|
314
346
|
PRE_COMMIT_GATE_STEP,
|
|
@@ -382,7 +414,7 @@ function runVerifierTask(task, context) {
|
|
|
382
414
|
const label = `verifier:${task}`
|
|
383
415
|
if (task === 'fix-verify') {
|
|
384
416
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
385
|
-
return
|
|
417
|
+
return convergeReadOnlyAgent(
|
|
386
418
|
`You are the VERIFY step for ${context.findings.length} finding(s) (${context.sourceLabel}) on ${prCoordinates}, HEAD ${context.head}. The edit step left fixes in the working tree, uncommitted. Do NO edits of any kind — verification only; any edit invalidates the verdict you are about to emit.\n\n` +
|
|
387
419
|
`Findings the working-tree fixes must address:\n${findingsBlock}\n\n` +
|
|
388
420
|
`Steps:\n` +
|
|
@@ -396,7 +428,7 @@ function runVerifierTask(task, context) {
|
|
|
396
428
|
const failureBlock = context.failures.length
|
|
397
429
|
? context.failures.map((each, position) => `${position + 1}. ${each}`).join('\n')
|
|
398
430
|
: 'none reported'
|
|
399
|
-
return
|
|
431
|
+
return convergeReadOnlyAgent(
|
|
400
432
|
`You are the VERIFY step for the convergence repair on ${prCoordinates}, HEAD ${context.head}. The edit step left its repair in the working tree (a bot-thread fix uncommitted, and/or a rebase onto origin/main), unpushed. Do NO edits of any kind — verification only; any edit invalidates the verdict you are about to emit.\n\n` +
|
|
401
433
|
`Concerns the working-tree repair must resolve (the gates the convergence check flagged):\n${failureBlock}\n\n` +
|
|
402
434
|
`Steps:\n` +
|
|
@@ -406,7 +438,7 @@ function runVerifierTask(task, context) {
|
|
|
406
438
|
{ label, phase: 'Finalize', agentType: 'code-verifier', ...TIERS.sonnetMedium },
|
|
407
439
|
)
|
|
408
440
|
}
|
|
409
|
-
return
|
|
441
|
+
return convergeReadOnlyAgent(
|
|
410
442
|
`You are the VERIFY step for an environment-hardening change (${context.sourceLabel}) staged in the working tree of ${context.hardeningRepoPath}. The edit step left the hooks/rules edits uncommitted there. Do NO edits of any kind — verification only; any edit invalidates the verdict you are about to emit.\n\n` +
|
|
411
443
|
`Concern the working-tree change must resolve: the edited hooks/rules block the code-standard violation classes from the deferred round at Write/Edit time, and a hook change carries a passing test per CODE_RULES.\n\n` +
|
|
412
444
|
`Steps:\n` +
|
|
@@ -480,7 +512,7 @@ function runGeneralUtilityTask(task, context) {
|
|
|
480
512
|
context.deferredStandardsFindings.length > 0
|
|
481
513
|
? `The ${context.deferredStandardsFindings.length} unique code-standard finding(s) after de-duplication across the lens reports above were excluded from this CLEAN post and ${describeStandardsDeferral(context.standardsDeferral)}.\n\n`
|
|
482
514
|
: ''
|
|
483
|
-
return
|
|
515
|
+
return convergeReadOnlyAgent(
|
|
484
516
|
`Transcribe a completed audit result to the PR thread for ${prCoordinates} at commit ${context.head}. You are relaying results that ${ranCount} audit lens(es) already produced on this HEAD earlier in this run — you are not judging the code yourself or clearing a merge gate.\n\n` +
|
|
485
517
|
`${ranCount} audit lens(es) ran on HEAD ${context.head} this round: ${ranRoster}.\n\n` +
|
|
486
518
|
notRunNote +
|
|
@@ -515,7 +547,7 @@ function runConvergenceCheck(context) {
|
|
|
515
547
|
const copilotOptOut = context.copilotDown
|
|
516
548
|
? ` Copilot is down this run, so before gh pr ready export the token in this same shell session so the independent mark-ready blocker hook's convergence re-check inherits it:\n bash: export CLAUDE_REVIEWS_DISABLED="copilot" (PowerShell: $env:CLAUDE_REVIEWS_DISABLED = "copilot")\n`
|
|
517
549
|
: ''
|
|
518
|
-
return
|
|
550
|
+
return convergeReadOnlyAgent(
|
|
519
551
|
`Run the convergence gate for ${prCoordinates} on HEAD ${context.head} and, only when every gate passes, mark the PR ready. Do not edit code.\n\n` +
|
|
520
552
|
`1. Run: python "${CONFIG.sharedScripts}/check_convergence.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber}${bugbotDownFlag}${copilotDownFlag}\n` +
|
|
521
553
|
` Exit 0 -> every gate passed: set pass:true, failures:[].\n` +
|
|
@@ -565,6 +597,31 @@ const LENS_SCHEMA = {
|
|
|
565
597
|
required: ['sha', 'clean', 'down', 'findings'],
|
|
566
598
|
}
|
|
567
599
|
|
|
600
|
+
const COPILOT_FINDING_TIERS = ['self-healing', 'code-concern']
|
|
601
|
+
|
|
602
|
+
const COPILOT_FINDINGS_SCHEMA = {
|
|
603
|
+
type: 'array',
|
|
604
|
+
items: {
|
|
605
|
+
type: 'object',
|
|
606
|
+
additionalProperties: false,
|
|
607
|
+
properties: {
|
|
608
|
+
file: { type: 'string' },
|
|
609
|
+
line: { type: 'integer' },
|
|
610
|
+
severity: { type: 'string', enum: ['P0', 'P1', 'P2'] },
|
|
611
|
+
category: { type: 'string', enum: ['bug', 'code-standard'], description: 'code-standard for pure CODE_RULES/style violations with no behavioral impact; bug otherwise' },
|
|
612
|
+
tier: {
|
|
613
|
+
type: 'string',
|
|
614
|
+
enum: COPILOT_FINDING_TIERS,
|
|
615
|
+
description: 'routing signal separate from category: self-healing when the fix cannot change observable runtime behavior for production callers (style, type hints, misplaced or unused imports, formatting, magic-value extraction, test-only changes, doc-or-description vs code mismatches, code de-duplication); code-concern when the finding is behavior-changing or needs a product decision (logic or correctness, security, data handling, error-handling semantics, concurrency). Classify as code-concern whenever the tier is in doubt.',
|
|
616
|
+
},
|
|
617
|
+
title: { type: 'string' },
|
|
618
|
+
detail: { type: 'string' },
|
|
619
|
+
replyToCommentId: { type: ['integer', 'null'], description: 'GitHub review comment id to reply to and resolve, or null when the finding has no thread' },
|
|
620
|
+
},
|
|
621
|
+
required: ['file', 'line', 'severity', 'category', 'tier', 'title', 'detail', 'replyToCommentId'],
|
|
622
|
+
},
|
|
623
|
+
}
|
|
624
|
+
|
|
568
625
|
const COPILOT_SCHEMA = {
|
|
569
626
|
type: 'object',
|
|
570
627
|
additionalProperties: false,
|
|
@@ -572,7 +629,8 @@ const COPILOT_SCHEMA = {
|
|
|
572
629
|
sha: { type: 'string' },
|
|
573
630
|
clean: { type: 'boolean' },
|
|
574
631
|
down: { type: 'boolean', description: 'true when Copilot is down or out of quota — it posts an out-of-usage notice or never surfaces a review on HEAD after the poll cap; the gate is bypassed and the run proceeds to mark-ready' },
|
|
575
|
-
|
|
632
|
+
reviewUrl: { type: 'string', description: 'the Copilot review html_url when the gate carries findings, otherwise an empty string — the user-review payload links the orchestrator and the user to the review' },
|
|
633
|
+
findings: COPILOT_FINDINGS_SCHEMA,
|
|
576
634
|
},
|
|
577
635
|
required: ['sha', 'clean', 'down', 'findings'],
|
|
578
636
|
}
|
|
@@ -594,7 +652,7 @@ const REVIEWER_AVAILABILITY_SCHEMA = {
|
|
|
594
652
|
type: 'object',
|
|
595
653
|
additionalProperties: false,
|
|
596
654
|
properties: {
|
|
597
|
-
down: { type: 'boolean', description: 'true when reviewer_availability.py --reviewer bugbot exited non-zero (opted out via CLAUDE_REVIEWS_DISABLED)' },
|
|
655
|
+
down: { type: 'boolean', description: 'true when reviewer_availability.py --reviewer bugbot exited non-zero (off by default unless CLAUDE_REVIEWS_ENABLED lists bugbot, or opted out via CLAUDE_REVIEWS_DISABLED)' },
|
|
598
656
|
reason: { type: 'string', description: 'the one-line reason reviewer_availability.py printed for Bugbot' },
|
|
599
657
|
},
|
|
600
658
|
required: ['down', 'reason'],
|
|
@@ -743,7 +801,7 @@ const CLEAN_AUDIT_SCHEMA = {
|
|
|
743
801
|
|
|
744
802
|
const SEVERITY_RANK = { P0: 0, P1: 1, P2: 2 }
|
|
745
803
|
const SHA_COMPARISON_PREFIX_LENGTH = 7
|
|
746
|
-
const LENS_NAMES = ['
|
|
804
|
+
const LENS_NAMES = ['code-review', 'bug-audit', 'self-review']
|
|
747
805
|
const GITHUB_ISSUE_URL_PATTERN = /^(https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/\d+)\/?(?:[?#].*)?$/
|
|
748
806
|
|
|
749
807
|
/**
|
|
@@ -902,20 +960,20 @@ function resolveRoundOutcome(lensResults) {
|
|
|
902
960
|
* A lens is 'ran' when it returned a live result carrying a real review — either
|
|
903
961
|
* not down, or down but still producing findings (which resolveRoundOutcome folds
|
|
904
962
|
* into the round, so the same result is quoted here as ran). It is 'down' when the
|
|
905
|
-
* workflow never spawned it (
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
963
|
+
* workflow never spawned it (a down-stub carrying notSpawned is never presented as
|
|
964
|
+
* a returned result), 'reported-down' when its agent ran but reported itself down
|
|
965
|
+
* with no findings (an opt-out or a poll-budget timeout that surfaced no review),
|
|
966
|
+
* and 'dead' when its agent died and returned no result. Only 'ran' carries a
|
|
967
|
+
* quoted report.
|
|
910
968
|
*
|
|
911
969
|
* ::
|
|
912
970
|
*
|
|
913
|
-
* nameLensResults([
|
|
914
|
-
* -> [{lens: '
|
|
915
|
-
* {lens: '
|
|
916
|
-
* {lens: '
|
|
971
|
+
* nameLensResults([codeReviewReport, null, selfReviewReport])
|
|
972
|
+
* -> [{lens: 'code-review', status: 'ran', report: codeReviewReport},
|
|
973
|
+
* {lens: 'bug-audit', status: 'dead', report: null},
|
|
974
|
+
* {lens: 'self-review', status: 'ran', report: selfReviewReport}]
|
|
917
975
|
*
|
|
918
|
-
* The order matches the parallel([
|
|
976
|
+
* The order matches the parallel([code-review, bug-audit, self-review]) spawn order.
|
|
919
977
|
* @param {Array<object|null>} lensResults the positional lens results for the round
|
|
920
978
|
* @returns {Array<object>} one {lens, status, report} entry per lens position
|
|
921
979
|
*/
|
|
@@ -944,13 +1002,13 @@ function nameLensResults(lensResults) {
|
|
|
944
1002
|
*
|
|
945
1003
|
* ::
|
|
946
1004
|
*
|
|
947
|
-
* describeNotRunLens({lens: '
|
|
948
|
-
* -> '
|
|
949
|
-
* describeNotRunLens({lens: '
|
|
950
|
-
* -> '
|
|
1005
|
+
* describeNotRunLens({lens: 'self-review', status: 'down'})
|
|
1006
|
+
* -> 'self-review (down/disabled — did not run)'
|
|
1007
|
+
* describeNotRunLens({lens: 'self-review', status: 'reported-down'})
|
|
1008
|
+
* -> 'self-review (ran, but reported itself down — it produced no review for this HEAD)'
|
|
951
1009
|
*
|
|
952
|
-
* The reported-down wording stays mechanism-neutral because
|
|
953
|
-
*
|
|
1010
|
+
* The reported-down wording stays mechanism-neutral because a lens returns the
|
|
1011
|
+
* same down shape for an opt-out (no poll ever ran) and a poll-budget timeout.
|
|
954
1012
|
* @param {{lens: string, status: string}} lensEntry a non-ran lens provenance entry
|
|
955
1013
|
* @returns {string} the disclosure clause for that lens
|
|
956
1014
|
*/
|
|
@@ -1303,28 +1361,86 @@ function classifyReadyOutcome(readyResult) {
|
|
|
1303
1361
|
}
|
|
1304
1362
|
|
|
1305
1363
|
/**
|
|
1306
|
-
* Classify a
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1364
|
+
* Classify a terminal reviewer-gate result into the loop's next action, shared by
|
|
1365
|
+
* both external confirmation gates — Cursor Bugbot and GitHub Copilot. The logic
|
|
1366
|
+
* is reviewer-neutral over the {clean, down, findings} shape both gates return. A
|
|
1367
|
+
* dead gate agent (null result) is a retry rather than an approval, mirroring the
|
|
1368
|
+
* converge lenses' dead-agent convention so a failed gate is never mistaken for a
|
|
1369
|
+
* clean review. A down result — the reviewer out of quota or unreachable, so it
|
|
1310
1370
|
* posts an out-of-usage notice or never surfaces a review after the poll cap —
|
|
1311
|
-
* routes to the 'down' kind, which logs a notice and proceeds
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1371
|
+
* routes to the 'down' kind, which logs a notice and proceeds with the gate
|
|
1372
|
+
* bypassed; this is checked first so an outage proceeds rather than waiting on a
|
|
1373
|
+
* review that will not arrive. Findings route to a fix step. The gate otherwise
|
|
1374
|
+
* approves only when it explicitly reports clean:true with no findings — a
|
|
1375
|
+
* clean:false result with zero findings is an unreliable or malformed gate
|
|
1376
|
+
* response and retries rather than advancing, so a PR never advances on a HEAD the
|
|
1377
|
+
* reviewer did not call clean.
|
|
1378
|
+
* @param {object|null|undefined} reviewerGate the terminal gate result, or null on agent failure
|
|
1379
|
+
* @returns {{kind: string, findings?: Array<object>}} the next action
|
|
1380
|
+
*/
|
|
1381
|
+
function classifyReviewerGateOutcome(reviewerGate) {
|
|
1382
|
+
if (reviewerGate == null) return { kind: 'retry' }
|
|
1383
|
+
if (reviewerGate.down === true) return { kind: 'down' }
|
|
1384
|
+
if (reviewerGate.findings.length > 0) return { kind: 'fix', findings: reviewerGate.findings }
|
|
1385
|
+
if (reviewerGate.clean === true) return { kind: 'approved' }
|
|
1386
|
+
return { kind: 'retry' }
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Layer the Copilot-specific two-tier triage over the shared reviewer-gate
|
|
1391
|
+
* classifier. The base classifier decides retry / down / approved and routes any
|
|
1392
|
+
* findings to a fix; this wrapper then upgrades a fix round carrying a
|
|
1393
|
+
* code-concern finding to a 'user-review' outcome, so the workflow neither
|
|
1394
|
+
* auto-fixes it nor marks the PR ready. A self-healing-only fix round passes
|
|
1395
|
+
* through unchanged and flows into the fix flow. Only the Copilot gate uses this
|
|
1396
|
+
* wrapper; the terminal Bugbot gate keeps the base classifier, whose findings
|
|
1397
|
+
* carry no tier and always route to a fix.
|
|
1319
1398
|
* @param {object|null|undefined} copilot the Copilot gate result, or null on agent failure
|
|
1320
1399
|
* @returns {{kind: string, findings?: Array<object>}} the next action
|
|
1321
1400
|
*/
|
|
1322
1401
|
function classifyCopilotOutcome(copilot) {
|
|
1323
|
-
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
return
|
|
1402
|
+
const outcome = classifyReviewerGateOutcome(copilot)
|
|
1403
|
+
if (outcome.kind === 'fix' && hasCodeConcernFinding(outcome.findings)) {
|
|
1404
|
+
return { kind: 'user-review', findings: outcome.findings }
|
|
1405
|
+
}
|
|
1406
|
+
return outcome
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Decide whether a Copilot round carries a code-concern finding — one that is
|
|
1411
|
+
* behavior-changing or needs a product decision, so the workflow may neither
|
|
1412
|
+
* auto-fix it nor mark the PR ready. Any tier other than the explicit
|
|
1413
|
+
* 'self-healing' counts as a code concern, so a missing or unexpected tier
|
|
1414
|
+
* defaults to code-concern rather than silently routing into the auto-fix flow.
|
|
1415
|
+
* @param {Array<object>} findings the Copilot findings for the round
|
|
1416
|
+
* @returns {boolean} true when at least one finding is not tier self-healing
|
|
1417
|
+
*/
|
|
1418
|
+
function hasCodeConcernFinding(findings) {
|
|
1419
|
+
return findings.some((each) => each.tier !== 'self-healing')
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Build the user-review payload the orchestrator hands to the
|
|
1424
|
+
* copilot-finding-triage skill: the Copilot review link plus the code-concern
|
|
1425
|
+
* findings pared to the fields the ntfy summary and the review gate read. The
|
|
1426
|
+
* workflow returns this alongside blocker 'user-review' so the orchestrating
|
|
1427
|
+
* session runs the notify-and-wait gate; a background workflow cannot hold for a
|
|
1428
|
+
* human, so it carries the findings out rather than deciding them.
|
|
1429
|
+
* @param {object} copilot the COPILOT_SCHEMA gate result carrying the review URL
|
|
1430
|
+
* @param {Array<object>} findings the Copilot findings for this HEAD
|
|
1431
|
+
* @returns {{reviewUrl: string, findings: Array<object>}} the triage payload
|
|
1432
|
+
*/
|
|
1433
|
+
function buildUserReview(copilot, findings) {
|
|
1434
|
+
return {
|
|
1435
|
+
reviewUrl: copilot.reviewUrl || '',
|
|
1436
|
+
findings: findings.map((each) => ({
|
|
1437
|
+
file: each.file,
|
|
1438
|
+
line: each.line,
|
|
1439
|
+
severity: each.severity,
|
|
1440
|
+
tier: each.tier,
|
|
1441
|
+
title: each.title,
|
|
1442
|
+
})),
|
|
1443
|
+
}
|
|
1328
1444
|
}
|
|
1329
1445
|
|
|
1330
1446
|
/**
|
|
@@ -1338,7 +1454,7 @@ function classifyCopilotOutcome(copilot) {
|
|
|
1338
1454
|
* from the current outcome is what lets a recovered Copilot — one that returns
|
|
1339
1455
|
* standards-only findings after an earlier down pass — reach FINALIZE without
|
|
1340
1456
|
* the stale bypass that would skip its non-clean review.
|
|
1341
|
-
* @param {{kind: string}} copilotOutcome a
|
|
1457
|
+
* @param {{kind: string}} copilotOutcome a classifyReviewerGateOutcome result
|
|
1342
1458
|
* @returns {boolean} true only when this pass's Copilot gate is bypassed
|
|
1343
1459
|
*/
|
|
1344
1460
|
function resolveCopilotDown(copilotOutcome) {
|
|
@@ -1445,6 +1561,35 @@ function renderLensDiffContext(preflightResult) {
|
|
|
1445
1561
|
)
|
|
1446
1562
|
}
|
|
1447
1563
|
|
|
1564
|
+
/**
|
|
1565
|
+
* Static-sweep lens: the deterministic pre-catch pass that runs the CODE_RULES
|
|
1566
|
+
* gate, ruff, mypy, and stem-matched pytest over the PR's changed files, so the
|
|
1567
|
+
* opus reading lenses only ever review sweep-clean code. It runs on the cheaper
|
|
1568
|
+
* sonnet tier because the mechanical, static, and test classes are the right job
|
|
1569
|
+
* for a deterministic run rather than a reading judgment. It uses only local
|
|
1570
|
+
* git, python, and node commands — never the GitHub CLI — so it runs in any
|
|
1571
|
+
* session, and a gate it could not run is disclosed in a finding detail rather
|
|
1572
|
+
* than silently treated as clean.
|
|
1573
|
+
* @param {string} head PR HEAD SHA to evaluate
|
|
1574
|
+
* @param {object|null|undefined} preflightResult the preflight-git result carrying the changed-file list and diffstat for this round
|
|
1575
|
+
* @returns {Promise<object>} LENS_SCHEMA result carrying each gate or test failure as a finding
|
|
1576
|
+
*/
|
|
1577
|
+
function runStaticSweep(head, preflightResult) {
|
|
1578
|
+
return convergeReadOnlyAgent(
|
|
1579
|
+
`You are the deterministic static-sweep lens for ${prCoordinates}, HEAD ${head}. Run the mechanical gates over the PR's changed files and report each failure as a finding; do not edit, commit, or push.\n\n` +
|
|
1580
|
+
renderLensDiffContext(preflightResult) +
|
|
1581
|
+
`Use only local git, python, and node commands — never the GitHub CLI — so this runs in any session.\n\n` +
|
|
1582
|
+
`1. Detect the repository's lint, type-check, and test entrypoints from pyproject.toml, package.json, and the repo layout.\n` +
|
|
1583
|
+
`2. Resolve the repo root with git rev-parse --show-toplevel, then run:\n` +
|
|
1584
|
+
` - python "${CONFIG.prLoopScripts}/code_rules_gate.py" --repo-root <that root> --base origin/main (exit 1 means blocking CODE_RULES violations on the diff).\n` +
|
|
1585
|
+
` - ruff check on the changed files, and mypy on the changed files.\n` +
|
|
1586
|
+
` - stem-matched pytest for the changed production modules — run each changed module's paired test file.\n` +
|
|
1587
|
+
`3. Map each failure to one LENS_SCHEMA finding at its file:line: category 'code-standard' for a lint, CODE_RULES, or type violation; category 'bug' for a failing test. Set severity per impact and replyToCommentId=null.\n\n` +
|
|
1588
|
+
`When a gate cannot run in this session because a tool is absent, report that as a finding detail rather than treating the gate as clean. Return strictly the schema: clean=true with empty findings when every gate and test passes on the diff, otherwise one entry per failure. Set sha=${'`'}${head}${'`'}, down=false.`,
|
|
1589
|
+
{ label: 'lens:static-sweep', phase: 'Converge', schema: LENS_SCHEMA, agentType: 'Explore', ...TIERS.sonnetMedium },
|
|
1590
|
+
)
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1448
1593
|
/**
|
|
1449
1594
|
* Bugbot lens: ensure Cursor Bugbot has rendered a verdict on the given HEAD,
|
|
1450
1595
|
* triggering and polling its CI check run when needed, and return its findings.
|
|
@@ -1453,7 +1598,7 @@ function renderLensDiffContext(preflightResult) {
|
|
|
1453
1598
|
* @returns {Promise<object>} LENS_SCHEMA result
|
|
1454
1599
|
*/
|
|
1455
1600
|
function runBugbotLens(head, preflightResult) {
|
|
1456
|
-
return
|
|
1601
|
+
return convergeReadOnlyAgent(
|
|
1457
1602
|
`You are the Cursor Bugbot lens for ${prCoordinates}, HEAD ${head}. Cursor Bugbot participates this run.\n\n` +
|
|
1458
1603
|
`Goal: return Bugbot's verdict on HEAD ${head}. Do not edit code, commit, or push. You may post the literal trigger comment described below.\n\n` +
|
|
1459
1604
|
renderLensDiffContext(preflightResult) +
|
|
@@ -1481,7 +1626,7 @@ function runBugbotLens(head, preflightResult) {
|
|
|
1481
1626
|
* @returns {Promise<object>} LENS_SCHEMA result
|
|
1482
1627
|
*/
|
|
1483
1628
|
function runCodeReviewLens(head, preflightResult) {
|
|
1484
|
-
return
|
|
1629
|
+
return convergeReadOnlyAgent(
|
|
1485
1630
|
`You are the code-review lens for ${prCoordinates}, HEAD ${head}.\n\n` +
|
|
1486
1631
|
`Review the FULL origin/main...HEAD diff — every file the PR touches. Do NOT delta-scope to recent commits or to a single file.\n\n` +
|
|
1487
1632
|
renderLensDiffContext(preflightResult) +
|
|
@@ -1499,16 +1644,43 @@ function runCodeReviewLens(head, preflightResult) {
|
|
|
1499
1644
|
* @returns {Promise<object>} LENS_SCHEMA result
|
|
1500
1645
|
*/
|
|
1501
1646
|
function runAuditLens(head, preflightResult) {
|
|
1502
|
-
return
|
|
1647
|
+
return convergeReadOnlyAgent(
|
|
1503
1648
|
`You are the second-opinion bug-audit lens for ${prCoordinates}, HEAD ${head}.\n\n` +
|
|
1504
1649
|
`Read the audit rubric at ${CONFIG.bugteamRubric} and apply its categories (A through P) against the FULL origin/main...HEAD diff — every file the PR touches, never a delta cut.\n\n` +
|
|
1505
1650
|
renderLensDiffContext(preflightResult) +
|
|
1506
1651
|
`This is a clean-room audit: assume nothing from other lenses. Report only findings backed by concrete file:line evidence. Do NOT edit, commit, or push.\n\n` +
|
|
1652
|
+
`Adversarial second pass — mandatory after the primary A-P pass: assume your first pass missed at least 3 P1 bugs. Where are they? Re-examine the diff category by category and return either new Shape-A findings at new file:line references, or an explicit Shape-B adversarial-probe entry naming each category you re-examined and why it holds. A bare "nothing new" is not an acceptable result for this pass.\n\n` +
|
|
1653
|
+
`The doc-vs-code parity, test-assertion completeness, and PR-description-vs-diff lanes sit outside the A-P categories; read the pre-catch rubric at ${CONFIG.precatchRubric} for their checklists and fold any finding those lanes surface into your returned findings.\n\n` +
|
|
1507
1654
|
`Return strictly the schema: clean=true with empty findings when the diff passes every category, otherwise one entry per finding (severity P0/P1/P2; category 'code-standard' for pure CODE_RULES/style violations with no behavioral impact, 'bug' otherwise; replyToCommentId=null). Set sha=${'`'}${head}${'`'}, down=false.`,
|
|
1508
1655
|
{ label: 'lens:bug-audit', phase: 'Converge', schema: LENS_SCHEMA, agentType: 'code-quality-agent', ...TIERS.opusMedium },
|
|
1509
1656
|
)
|
|
1510
1657
|
}
|
|
1511
1658
|
|
|
1659
|
+
/**
|
|
1660
|
+
* Self-review lens: the semantic pre-catch parity pass over the full diff. It
|
|
1661
|
+
* covers the doc-vs-code parity, test-assertion completeness, and
|
|
1662
|
+
* PR-description-vs-diff lanes that sit outside the A-P bug categories, reusing
|
|
1663
|
+
* the pr-consistency-audit skill's canonical-source cross-reference method. It
|
|
1664
|
+
* stays on opus because line-citation accuracy, symbol attribution, and
|
|
1665
|
+
* inventory or count claims are semantic judgments a cheaper tier misreads. It
|
|
1666
|
+
* reports findings without editing.
|
|
1667
|
+
* @param {string} head PR HEAD SHA to evaluate
|
|
1668
|
+
* @param {object|null|undefined} preflightResult the preflight-git result carrying the changed-file list and diffstat for this round
|
|
1669
|
+
* @returns {Promise<object>} LENS_SCHEMA result carrying the parity findings
|
|
1670
|
+
*/
|
|
1671
|
+
function runSelfReviewLens(head, preflightResult) {
|
|
1672
|
+
return convergeReadOnlyAgent(
|
|
1673
|
+
`You are the self-review parity lens for ${prCoordinates}, HEAD ${head}. Review the FULL origin/main...HEAD diff — every file the PR touches. Do NOT edit, commit, or push.\n\n` +
|
|
1674
|
+
renderLensDiffContext(preflightResult) +
|
|
1675
|
+
`Read the pre-catch rubric at ${CONFIG.precatchRubric} for the three lane checklists, and cover each lane:\n` +
|
|
1676
|
+
`1. Doc-vs-code parity: reuse the pr-consistency-audit skill's canonical-source cross-reference method ($HOME/.claude/skills/pr-consistency-audit/SKILL.md) and the drift rubric at $HOME/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md. Verify every line citation resolves, every referenced file or script path exists, every symbol is attributed to the file that defines it, and every inventory, count, and ordering claim matches the code.\n` +
|
|
1677
|
+
`2. Test-assertion completeness: every changed or new production path has a paired test that calls it and asserts on its behavior, and a changed test pins behavior rather than hiding it behind a mock.\n` +
|
|
1678
|
+
`3. PR-description-vs-diff two-way parity: fetch the PR body read-only, then confirm every PR-body claim maps to a hunk in the diff and every hunk maps to a claim; flag invented paths, invented counts, and out-of-scope changes.\n\n` +
|
|
1679
|
+
`Before returning clean, state one proof-of-absence line per lane naming what you checked. Return strictly the schema: clean=true with empty findings when all three lanes pass, otherwise one entry per finding (severity P0/P1/P2; category 'code-standard' for a pure doc or style parity gap with no behavioral impact, 'bug' otherwise; replyToCommentId=null). Set sha=${'`'}${head}${'`'}, down=false.`,
|
|
1680
|
+
{ label: 'lens:self-review', phase: 'Converge', schema: LENS_SCHEMA, agentType: 'code-quality-agent', ...TIERS.opusMedium },
|
|
1681
|
+
)
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1512
1684
|
/**
|
|
1513
1685
|
* Reuse lens: a one-time pre-convergence pass that scans the full diff for
|
|
1514
1686
|
* places the PR re-implements behavior the codebase already provides, and
|
|
@@ -1521,7 +1693,7 @@ function runAuditLens(head, preflightResult) {
|
|
|
1521
1693
|
* @returns {Promise<object>} LENS_SCHEMA result carrying the qualifying reuse findings
|
|
1522
1694
|
*/
|
|
1523
1695
|
function runReuseAuditPass(head, preflightResult) {
|
|
1524
|
-
return
|
|
1696
|
+
return convergeReadOnlyAgent(
|
|
1525
1697
|
`You are the REUSE lens for ${prCoordinates}, HEAD ${head}. This pass runs once before convergence to find where the PR re-implements behavior the codebase already provides.\n\n` +
|
|
1526
1698
|
`Review the FULL origin/main...HEAD diff — every file the PR touches. Do NOT delta-scope to recent commits or a single file.\n\n` +
|
|
1527
1699
|
renderLensDiffContext(preflightResult) +
|
|
@@ -1554,11 +1726,22 @@ function renderFindingsBlock(findings) {
|
|
|
1554
1726
|
|
|
1555
1727
|
const FIX_RECOVERY_MAX_ATTEMPTS = 2
|
|
1556
1728
|
|
|
1729
|
+
/**
|
|
1730
|
+
* Commit-recovery attempt cap, held to one. The edit agent already pre-clears the
|
|
1731
|
+
* commit gate via PRE_COMMIT_GATE_STEP before the working tree reaches the commit
|
|
1732
|
+
* step, so a second commit-recover edit is wasted effort: a rejection that
|
|
1733
|
+
* survives one recovery pass is a genuine post-clear block that fails fast to a
|
|
1734
|
+
* blocker rather than looping. The verify-recovery loop keeps the wider
|
|
1735
|
+
* FIX_RECOVERY_MAX_ATTEMPTS budget, since a verify objection can take more than
|
|
1736
|
+
* one pass to resolve.
|
|
1737
|
+
*/
|
|
1738
|
+
const COMMIT_RECOVERY_MAX_ATTEMPTS = 1
|
|
1739
|
+
|
|
1557
1740
|
/**
|
|
1558
1741
|
* Run a commit step and, when it is blocked by a commit-time hook or gate that
|
|
1559
1742
|
* requires a code change, route back to a fixer: fix the blocking violation,
|
|
1560
1743
|
* re-verify so a fresh verdict binds the corrected surface, then retry the
|
|
1561
|
-
* commit — bounded by
|
|
1744
|
+
* commit — bounded by COMMIT_RECOVERY_MAX_ATTEMPTS. The loop breaks early when the
|
|
1562
1745
|
* fixer makes no edit or the re-verify does not pass, returning the last commit
|
|
1563
1746
|
* result so the caller's existing no-push handling still applies. A transient
|
|
1564
1747
|
* failure never enters the loop (commitNeedsCodeRecovery is false), so an auth or
|
|
@@ -1569,7 +1752,7 @@ const FIX_RECOVERY_MAX_ATTEMPTS = 2
|
|
|
1569
1752
|
async function commitWithRecovery({ runCommit, runVerify, runRecoverEdit }) {
|
|
1570
1753
|
let commitResult = await runCommit()
|
|
1571
1754
|
let attempt = 0
|
|
1572
|
-
while (commitNeedsCodeRecovery(commitResult) && attempt <
|
|
1755
|
+
while (commitNeedsCodeRecovery(commitResult) && attempt < COMMIT_RECOVERY_MAX_ATTEMPTS) {
|
|
1573
1756
|
attempt += 1
|
|
1574
1757
|
const recoverEdit = await runRecoverEdit(commitResult.blockerDetail, attempt)
|
|
1575
1758
|
if (recoverEdit?.edited !== true) break
|
|
@@ -1667,7 +1850,7 @@ function cleanAuditBlocker(head, auditResult) {
|
|
|
1667
1850
|
* @returns {Promise<object>} COPILOT_SCHEMA result
|
|
1668
1851
|
*/
|
|
1669
1852
|
function runCopilotGate(head) {
|
|
1670
|
-
return
|
|
1853
|
+
return convergeReadOnlyAgent(
|
|
1671
1854
|
`You are the Copilot gate for ${prCoordinates}, HEAD ${head}. Do not edit code, commit, or push.\n\n` +
|
|
1672
1855
|
`Copilot can run out of usage. When the newest Copilot review on HEAD carries an out-of-usage notice — a body stating Copilot was unable to review because the user who requested the review has reached their quota limit, or any equivalent quota / premium-request / usage-limit exhaustion message rather than an actual code review — Copilot is down for this run: return {sha:${'`'}${head}${'`'}, clean:true, down:true, findings:[]} and stop. Do NOT re-request a review, do NOT keep polling, and do NOT treat the notice as a finding.\n\n` +
|
|
1673
1856
|
`1. Read any existing Copilot review on HEAD first: python "${CONFIG.sharedScripts}/fetch_copilot_reviews.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber}. This lists every Copilot review across all commits newest-first; only count entries whose commit_id starts with ${head}. If the newest such HEAD-scoped Copilot review is the out-of-usage notice above -> return the down result and stop. A notice on any earlier commit is NOT down: ignore it and continue. With no Copilot review on HEAD, skip a duplicate request: python "${CONFIG.sharedScripts}/check_pending_reviews.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --user copilot. Exit 0 means a request is already pending; otherwise request one:\n` +
|
|
@@ -1675,8 +1858,13 @@ function runCopilotGate(head) {
|
|
|
1675
1858
|
`2. Poll for Copilot's review on HEAD ${head}: up to ${CONFIG.copilotMaxPolls} attempts, 360 seconds apart (wait each 360-second interval inside this turn with the Monitor tool, per the WAITS AND POLLS rule above; if the attempt budget is spent with no review on HEAD, return the full down result {sha:${'`'}${head}${'`'}, clean:false, down:true, findings:[]}). Each attempt: python "${CONFIG.sharedScripts}/fetch_copilot_reviews.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} for the top-level review state, plus gh api "repos/${input.owner}/${input.repo}/pulls/${input.prNumber}/comments" --paginate --slurp for inline comment ids (Copilot's login contains "copilot", case-insensitive). Only count entries whose commit_id starts with ${head}.\n` +
|
|
1676
1859
|
` - Out-of-usage notice on HEAD -> return the down result above (clean:true, down:true) and stop.\n` +
|
|
1677
1860
|
` - Copilot review present on HEAD whose state is APPROVED, or COMMENTED with no inline findings -> a clean pass: return {sha:${'`'}${head}${'`'}, clean:true, down:false, findings:[]}.\n` +
|
|
1678
|
-
` - Copilot findings on HEAD -> return them (each with its inline comment id in replyToCommentId; category 'code-standard' for pure CODE_RULES/style violations with no behavioral impact, 'bug' otherwise), clean:false, down:false.\n` +
|
|
1861
|
+
` - Copilot findings on HEAD -> return them (each with its inline comment id in replyToCommentId; category 'code-standard' for pure CODE_RULES/style violations with no behavioral impact, 'bug' otherwise; tier per the two-tier rubric below), reviewUrl set to the Copilot review html_url, clean:false, down:false.\n` +
|
|
1679
1862
|
` - No review after ${CONFIG.copilotMaxPolls} attempts -> Copilot is down for this run (unreachable, or silently out of quota with no notice): return {sha:${'`'}${head}${'`'}, clean:false, down:true, findings:[]}.\n\n` +
|
|
1863
|
+
`Tier every finding for routing, separate from its category:\n` +
|
|
1864
|
+
` - tier 'self-healing': pure style, type hints, misplaced or unused imports, formatting, magic-value extraction, test-only changes, doc-or-description vs code mismatches, or code de-duplication — any fix that cannot change observable runtime behavior for production callers. These route into the fix flow automatically.\n` +
|
|
1865
|
+
` - tier 'code-concern': logic or correctness, security, data handling, error-handling semantics, or concurrency — anything behavior-changing or needing a product decision. These hold for a user-review gate rather than an auto-fix.\n` +
|
|
1866
|
+
` - Classify as 'code-concern' whenever the tier is in doubt.\n\n` +
|
|
1867
|
+
`A down verdict is valid ONLY in two cases: the review request itself failed, or the FULL poll budget (${CONFIG.copilotMaxPolls} attempts x 360 seconds) elapsed with no review on HEAD. A successful review request means the review is in flight; returning down:true on a partial poll is an invalid result. Never end the poll early for any reason other than a received review on HEAD or an out-of-usage notice — not tooling friction, not turn-length pressure, not a failed attempt to write a polling helper. When your wait tooling fails, re-arm it and keep polling until the budget is spent.\n\n` +
|
|
1680
1868
|
`Return strictly the schema.`,
|
|
1681
1869
|
{ label: 'copilot-gate', phase: 'Copilot gate', schema: COPILOT_SCHEMA, ...TIERS.haikuLow },
|
|
1682
1870
|
)
|
|
@@ -2101,14 +2289,25 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2101
2289
|
log(`Round ${rounds}: preflight-git agent returned no SHA — retrying without spawning lenses`)
|
|
2102
2290
|
continue
|
|
2103
2291
|
}
|
|
2104
|
-
const
|
|
2105
|
-
|
|
2292
|
+
const sweep = await runStaticSweep(head, reviewerAvailability)
|
|
2293
|
+
if ((sweep?.findings || []).length > 0) {
|
|
2294
|
+
resetNoLensRounds()
|
|
2295
|
+
log(`Round ${rounds}: static sweep raised ${sweep.findings.length} deterministic gate/test failure(s) — fixing before the reading lenses`)
|
|
2296
|
+
const sweepFix = await applyFixes(head, sweep.findings, 'static-sweep')
|
|
2297
|
+
const sweepProgress = detectFixProgress(sweepFix, head, false)
|
|
2298
|
+
if (!sweepProgress.progressed) {
|
|
2299
|
+
blocker = `static-sweep stall: the deterministic gates raised ${sweep.findings.length} failure(s) the fixer could not clear on HEAD ${head} — re-running would loop to the iteration cap`
|
|
2300
|
+
break
|
|
2301
|
+
}
|
|
2302
|
+
head = null
|
|
2303
|
+
continue
|
|
2304
|
+
}
|
|
2305
|
+
log(`Round ${rounds}: parallel code-review + bug-audit + self-review on ${head?.slice(0, 7)}`)
|
|
2106
2306
|
const lenses = await parallel([
|
|
2107
|
-
() => (isBugbotDownPreSpawn ? Promise.resolve({ sha: head, clean: true, down: true, notSpawned: true, findings: [] }) : runBugbotLens(head, reviewerAvailability)),
|
|
2108
2307
|
() => runCodeReviewLens(head, reviewerAvailability),
|
|
2109
2308
|
() => runAuditLens(head, reviewerAvailability),
|
|
2309
|
+
() => runSelfReviewLens(head, reviewerAvailability),
|
|
2110
2310
|
])
|
|
2111
|
-
bugbotDown = lenses[0] == null ? true : resolveReviewerDown(lenses[0], input.bugbotDisabled || false)
|
|
2112
2311
|
const roundOutcome = resolveRoundOutcome(lenses)
|
|
2113
2312
|
if (roundOutcome.allLensesDead) {
|
|
2114
2313
|
if (registerNoLensRound(['a review lens agent died'])) {
|
|
@@ -2138,7 +2337,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2138
2337
|
blocker = cleanAuditBlocker(head, auditResult)
|
|
2139
2338
|
break
|
|
2140
2339
|
}
|
|
2141
|
-
phase = '
|
|
2340
|
+
phase = 'BUGBOT'
|
|
2142
2341
|
continue
|
|
2143
2342
|
}
|
|
2144
2343
|
if (findings.length > 0) {
|
|
@@ -2183,6 +2382,54 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2183
2382
|
break
|
|
2184
2383
|
}
|
|
2185
2384
|
resetNoLensRounds()
|
|
2385
|
+
phase = 'BUGBOT'
|
|
2386
|
+
continue
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
if (phase === 'BUGBOT') {
|
|
2390
|
+
if (resolveReviewerDown(reviewerAvailability?.bugbot, input.bugbotDisabled || false)) {
|
|
2391
|
+
bugbotDown = true
|
|
2392
|
+
log('Bugbot gate: the shared reviewer-availability probe (or the run input) reported Bugbot unavailable — skipping the terminal Bugbot gate with no agent spawned and proceeding to the Copilot gate.')
|
|
2393
|
+
phase = 'COPILOT'
|
|
2394
|
+
continue
|
|
2395
|
+
}
|
|
2396
|
+
const bugbot = await runBugbotLens(head, reviewerAvailability)
|
|
2397
|
+
const bugbotOutcome = classifyReviewerGateOutcome(bugbot)
|
|
2398
|
+
if (bugbotOutcome.kind === 'retry') {
|
|
2399
|
+
log('Bugbot gate agent died or returned an unreliable not-clean result with no findings — re-running the gate on the same HEAD')
|
|
2400
|
+
continue
|
|
2401
|
+
}
|
|
2402
|
+
if (bugbotOutcome.kind === 'down') {
|
|
2403
|
+
log('Bugbot gate: Bugbot is down or out of quota — no review on HEAD after the poll cap. Bypassing the terminal Bugbot gate and proceeding to the Copilot gate.')
|
|
2404
|
+
bugbotDown = true
|
|
2405
|
+
phase = 'COPILOT'
|
|
2406
|
+
continue
|
|
2407
|
+
}
|
|
2408
|
+
if (bugbotOutcome.kind === 'fix') {
|
|
2409
|
+
if (isStandardsOnlyRound(bugbotOutcome.findings)) {
|
|
2410
|
+
log(`Bugbot raised ${bugbotOutcome.findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
|
|
2411
|
+
const standardsOutcome = await openStandardsFollowUpOnce(head, bugbotOutcome.findings, 'bugbot', { copilotDisabled: copilotDown, bugbotDisabled: bugbotDown })
|
|
2412
|
+
standardsNote = standardsDeferralNote(bugbotOutcome.findings.length, buildStandardsDeferral())
|
|
2413
|
+
if (standardsOutcome?.deferredPr) deferredPrs.push(standardsOutcome.deferredPr)
|
|
2414
|
+
bugbotDown = false
|
|
2415
|
+
phase = 'COPILOT'
|
|
2416
|
+
continue
|
|
2417
|
+
}
|
|
2418
|
+
log(`Bugbot raised ${bugbotOutcome.findings.length} finding(s) — fixing and re-converging`)
|
|
2419
|
+
const fixResult = await applyFixes(head, bugbotOutcome.findings, 'bugbot')
|
|
2420
|
+
const hadThreadBearingFinding = bugbotOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
2421
|
+
const fixProgress = detectFixProgress(fixResult, head, hadThreadBearingFinding)
|
|
2422
|
+
if (!fixProgress.progressed) {
|
|
2423
|
+
blocker = fixResult?.resolvedWithoutCommit === true && !hadThreadBearingFinding
|
|
2424
|
+
? `fix stalled: bugbot gate raised ${bugbotOutcome.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`
|
|
2425
|
+
: `bugbot fix lens landed no push for ${bugbotOutcome.findings.length} finding(s) on HEAD ${head}`
|
|
2426
|
+
break
|
|
2427
|
+
}
|
|
2428
|
+
head = null
|
|
2429
|
+
phase = 'CONVERGE'
|
|
2430
|
+
continue
|
|
2431
|
+
}
|
|
2432
|
+
bugbotDown = false
|
|
2186
2433
|
phase = 'COPILOT'
|
|
2187
2434
|
continue
|
|
2188
2435
|
}
|
|
@@ -2210,6 +2457,20 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
2210
2457
|
phase = 'FINALIZE'
|
|
2211
2458
|
continue
|
|
2212
2459
|
}
|
|
2460
|
+
if (copilotOutcome.kind === 'user-review') {
|
|
2461
|
+
log(`Copilot raised ${copilotOutcome.findings.length} code-concern finding(s) — holding for user review: the workflow does not auto-fix them and does not mark the PR ready`)
|
|
2462
|
+
return {
|
|
2463
|
+
converged: false,
|
|
2464
|
+
rounds,
|
|
2465
|
+
finalSha: head,
|
|
2466
|
+
blocker: 'user-review',
|
|
2467
|
+
userReview: buildUserReview(copilot, copilotOutcome.findings),
|
|
2468
|
+
standardsNote,
|
|
2469
|
+
copilotNote,
|
|
2470
|
+
reuseNote,
|
|
2471
|
+
deferredPrs,
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2213
2474
|
if (copilotOutcome.kind === 'fix') {
|
|
2214
2475
|
if (isStandardsOnlyRound(copilotOutcome.findings)) {
|
|
2215
2476
|
log(`Copilot raised ${copilotOutcome.findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
|