claude-dev-env 1.82.0 → 1.84.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 +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
|
@@ -26,9 +26,11 @@ const CONFIG = {
|
|
|
26
26
|
copilotMaxPolls: 8,
|
|
27
27
|
sharedScripts: '$HOME/.claude/skills/pr-converge/scripts',
|
|
28
28
|
prLoopScripts: '$HOME/.claude/_shared/pr-loop/scripts',
|
|
29
|
-
bugteamRubric: '$HOME/.claude/
|
|
29
|
+
bugteamRubric: '$HOME/.claude/_shared/pr-loop/audit-contract.md',
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const REVIEWER_GATE_SENTINEL = 'CLAUDE_REVIEWER_GATE=autoconverge '
|
|
33
|
+
|
|
32
34
|
const HEADLESS_SAFETY_PREAMBLE =
|
|
33
35
|
'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' +
|
|
34
36
|
'- 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' +
|
|
@@ -77,38 +79,32 @@ const convergeAgent = (prompt, options) =>
|
|
|
77
79
|
agent(`${HEADLESS_SAFETY_PREAMBLE}${worktreeDirective(activeRepoPath)}${prompt}`, options)
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
|
-
* Spawn a fresh git-utility Explore agent for a specific task.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* @
|
|
82
|
+
* Spawn a fresh git-utility Explore agent for a specific task. The one task,
|
|
83
|
+
* 'preflight-git', bundles the three mechanical git reads into a single agent
|
|
84
|
+
* startup: it prints the PR HEAD SHA, fetches origin main so the review lenses
|
|
85
|
+
* diff against an up-to-date base, and polls GitHub mergeability, returning
|
|
86
|
+
* {sha, conflicting, fetched} in one structured result. The agent never edits
|
|
87
|
+
* code, so it runs on the cheapest model at low effort.
|
|
88
|
+
* @param {string} task the short task name ('preflight-git')
|
|
89
|
+
* @returns {Promise<object>} the structured PREFLIGHT_GIT_SCHEMA output
|
|
87
90
|
*/
|
|
88
|
-
function runGitTask(task
|
|
89
|
-
if (task
|
|
90
|
-
|
|
91
|
-
`Print the current HEAD SHA of ${prCoordinates}. Run exactly:\n` +
|
|
92
|
-
`gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .head.sha\n` +
|
|
93
|
-
`Return the full 40-character SHA in the sha field. Do not modify any files.`,
|
|
94
|
-
{ label: 'git-utility', phase: 'Converge', schema: HEAD_SCHEMA, agentType: 'Explore' },
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
if (task === 'prefetch-main') {
|
|
98
|
-
return convergeAgent(
|
|
99
|
-
`Refresh the base ref for ${prCoordinates} so the parallel review lenses can diff against an up-to-date origin/main without each running its own fetch. Run exactly:\n` +
|
|
100
|
-
`git fetch origin main\n` +
|
|
101
|
-
`Do not edit, commit, push, rebase, or modify any files — fetch only.`,
|
|
102
|
-
{ label: 'git-utility', phase: 'Converge', agentType: 'Explore' },
|
|
103
|
-
)
|
|
91
|
+
function runGitTask(task) {
|
|
92
|
+
if (task !== 'preflight-git') {
|
|
93
|
+
throw new Error(`runGitTask has no handler for task ${task}`)
|
|
104
94
|
}
|
|
105
95
|
return convergeAgent(
|
|
106
|
-
`
|
|
107
|
-
`
|
|
96
|
+
`Run three read-only git preflight steps for ${prCoordinates}. Do not edit, commit, push, rebase, or modify any files — read only.\n\n` +
|
|
97
|
+
`STEP 1 — resolve HEAD. Print the current PR HEAD SHA. Run exactly:\n` +
|
|
98
|
+
` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .head.sha\n` +
|
|
99
|
+
`Return the full 40-character SHA in the sha field.\n\n` +
|
|
100
|
+
`STEP 2 — refresh the base ref so the parallel review lenses can diff against an up-to-date origin/main without each running its own fetch. Run exactly:\n` +
|
|
101
|
+
` git fetch origin main\n` +
|
|
102
|
+
`Return fetched:true when the fetch completed, fetched:false when it failed.\n\n` +
|
|
103
|
+
`STEP 3 — report whether the PR has merge conflicts with its base branch. GitHub computes mergeability asynchronously, so .mergeable is null right after a push until it finishes. Poll until it resolves: run\n` +
|
|
108
104
|
` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq '{mergeable: .mergeable, state: .mergeable_state}'\n` +
|
|
109
|
-
`up to 5 times, 5 seconds apart (wait each 5-second interval inside this turn with the Monitor tool, per the WAITS AND POLLS rule above), stopping as soon as mergeable is true or false.\n
|
|
105
|
+
`up to 5 times, 5 seconds apart (wait each 5-second interval inside this turn with the Monitor tool, per the WAITS AND POLLS rule above), stopping as soon as mergeable is true or false.\n` +
|
|
110
106
|
`Return conflicting:true when mergeable is false or state is "dirty" (the branch conflicts with the base). Return conflicting:false when mergeable is true, or when mergeable stays null after the full poll budget — mergeability is unknown, so let the bug checks proceed rather than rebase on a guess.`,
|
|
111
|
-
{ label: 'git-utility', phase: 'Converge', schema:
|
|
107
|
+
{ label: 'git-utility', phase: 'Converge', schema: PREFLIGHT_GIT_SCHEMA, agentType: 'Explore', model: 'haiku', effort: 'low' },
|
|
112
108
|
)
|
|
113
109
|
}
|
|
114
110
|
|
|
@@ -464,6 +460,27 @@ function runConvergenceCheck(context) {
|
|
|
464
460
|
)
|
|
465
461
|
}
|
|
466
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Spawn a single reviewer-availability Explore agent once per CONVERGE round,
|
|
465
|
+
* before either reviewer's own agent is spawned. It runs the shared
|
|
466
|
+
* reviewer_availability.py CLI for both Copilot and Bugbot and reports each
|
|
467
|
+
* one's down state and reason, so resolveReviewerDown can decide whether to
|
|
468
|
+
* spawn the Bugbot lens and the Copilot gate without depending on an
|
|
469
|
+
* externally pre-set input flag.
|
|
470
|
+
* @returns {Promise<object>} REVIEWER_AVAILABILITY_SCHEMA result
|
|
471
|
+
*/
|
|
472
|
+
function runReviewerAvailabilityCheck() {
|
|
473
|
+
return convergeAgent(
|
|
474
|
+
`Check whether GitHub Copilot and Cursor Bugbot are available to review ${prCoordinates} this round, before either reviewer's agent is spawned. Do not edit code, commit, or push.\n\n` +
|
|
475
|
+
`Run exactly:\n` +
|
|
476
|
+
`python "${CONFIG.prLoopScripts}/reviewer_availability.py" --reviewer copilot\n` +
|
|
477
|
+
`python "${CONFIG.prLoopScripts}/reviewer_availability.py" --reviewer bugbot\n` +
|
|
478
|
+
`Each run exits 0 when that reviewer is available and non-zero when it is down, and prints one line naming the reason (stdout when available, stderr when down) — capture that line.\n\n` +
|
|
479
|
+
`Return strictly the schema: copilot.down and bugbot.down report whether that reviewer's run exited non-zero, and copilot.reason / bugbot.reason carry its printed line.`,
|
|
480
|
+
{ label: 'reviewer-availability', phase: 'Converge', schema: REVIEWER_AVAILABILITY_SCHEMA, agentType: 'Explore' },
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
467
484
|
const PRE_COMMIT_GATE_STEP =
|
|
468
485
|
`\n\nFINAL STEP — pre-commit gate check (do NOT commit): before your turn ends, prove your working-tree changes CAN be committed by dry-running the CODE_RULES commit gate that gates git commit (precommit_code_rules_gate). From inside the checkout that holds your changes, resolve its root with git rev-parse --show-toplevel, stage your changes with git add -A, then run exactly:\n` +
|
|
469
486
|
` python "${CONFIG.prLoopScripts}/code_rules_gate.py" --repo-root "<that root>" --staged\n` +
|
|
@@ -509,11 +526,44 @@ const COPILOT_SCHEMA = {
|
|
|
509
526
|
required: ['sha', 'clean', 'down', 'findings'],
|
|
510
527
|
}
|
|
511
528
|
|
|
512
|
-
const
|
|
529
|
+
const REVIEWER_AVAILABILITY_SCHEMA = {
|
|
513
530
|
type: 'object',
|
|
514
531
|
additionalProperties: false,
|
|
515
|
-
properties: {
|
|
516
|
-
|
|
532
|
+
properties: {
|
|
533
|
+
copilot: {
|
|
534
|
+
type: 'object',
|
|
535
|
+
additionalProperties: false,
|
|
536
|
+
properties: {
|
|
537
|
+
down: { type: 'boolean', description: 'true when reviewer_availability.py --reviewer copilot exited non-zero (opted out or out of premium-request quota)' },
|
|
538
|
+
reason: { type: 'string', description: 'the one-line reason reviewer_availability.py printed for Copilot' },
|
|
539
|
+
},
|
|
540
|
+
required: ['down', 'reason'],
|
|
541
|
+
},
|
|
542
|
+
bugbot: {
|
|
543
|
+
type: 'object',
|
|
544
|
+
additionalProperties: false,
|
|
545
|
+
properties: {
|
|
546
|
+
down: { type: 'boolean', description: 'true when reviewer_availability.py --reviewer bugbot exited non-zero (opted out via CLAUDE_REVIEWS_DISABLED)' },
|
|
547
|
+
reason: { type: 'string', description: 'the one-line reason reviewer_availability.py printed for Bugbot' },
|
|
548
|
+
},
|
|
549
|
+
required: ['down', 'reason'],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
required: ['copilot', 'bugbot'],
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const PREFLIGHT_GIT_SCHEMA = {
|
|
556
|
+
type: 'object',
|
|
557
|
+
additionalProperties: false,
|
|
558
|
+
properties: {
|
|
559
|
+
sha: { type: 'string', description: 'the full 40-character PR HEAD SHA' },
|
|
560
|
+
conflicting: {
|
|
561
|
+
type: 'boolean',
|
|
562
|
+
description: 'true only when GitHub reports the PR branch conflicts with its base (mergeable:false or mergeable_state:dirty); false when it merges cleanly or mergeability could not be computed',
|
|
563
|
+
},
|
|
564
|
+
fetched: { type: 'boolean', description: 'true when git fetch origin main completed successfully' },
|
|
565
|
+
},
|
|
566
|
+
required: ['sha', 'conflicting', 'fetched'],
|
|
517
567
|
}
|
|
518
568
|
|
|
519
569
|
const FIX_SCHEMA = {
|
|
@@ -553,18 +603,6 @@ const REPAIR_EDIT_SCHEMA = {
|
|
|
553
603
|
required: ['edited', 'rebased', 'resolvedWithoutCommit', 'summary'],
|
|
554
604
|
}
|
|
555
605
|
|
|
556
|
-
const MERGE_CONFLICT_SCHEMA = {
|
|
557
|
-
type: 'object',
|
|
558
|
-
additionalProperties: false,
|
|
559
|
-
properties: {
|
|
560
|
-
conflicting: {
|
|
561
|
-
type: 'boolean',
|
|
562
|
-
description: 'true only when GitHub reports the PR branch conflicts with its base (mergeable:false or mergeable_state:dirty); false when it merges cleanly or mergeability could not be computed',
|
|
563
|
-
},
|
|
564
|
-
},
|
|
565
|
-
required: ['conflicting'],
|
|
566
|
-
}
|
|
567
|
-
|
|
568
606
|
const CONFLICT_EDIT_SCHEMA = {
|
|
569
607
|
type: 'object',
|
|
570
608
|
additionalProperties: false,
|
|
@@ -749,19 +787,27 @@ function isMoreSevere(candidateSeverity, currentSeverity) {
|
|
|
749
787
|
}
|
|
750
788
|
|
|
751
789
|
/**
|
|
752
|
-
* Decide whether
|
|
753
|
-
*
|
|
754
|
-
* run
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
790
|
+
* Decide whether a reviewer is skipped this round — the one shared gate both
|
|
791
|
+
* Bugbot and Copilot consult before either reviewer's agent is spawned. The
|
|
792
|
+
* run's own disable flag always wins, so a deferred PR seeded with
|
|
793
|
+
* copilotDisabled or bugbotDisabled skips the reviewer without a probe.
|
|
794
|
+
* Otherwise the decision comes from the carried entry's down field, read from
|
|
795
|
+
* the round-start availability probe for a pre-spawn decision. A missing entry
|
|
796
|
+
* (a dead probe agent, or no result yet) reads as available rather than down,
|
|
797
|
+
* so an outage in the probe itself never wedges convergence — the reviewer's
|
|
798
|
+
* own runtime detection still runs and can report down on its own. This
|
|
799
|
+
* fail-open null handling suits a pre-spawn decision; a caller computing a
|
|
800
|
+
* post-round verdict from a reviewer's own lens/gate result — where a dead
|
|
801
|
+
* agent has no verdict to report, not an available one — guards that null
|
|
802
|
+
* case itself before calling this function.
|
|
803
|
+
* @param {{down: boolean}|null|undefined} reviewerAvailabilityEntry the probe's per-reviewer entry, or a reviewer's own result, carrying a down field
|
|
804
|
+
* @param {boolean} isReviewerDisabledByInput true when the run input opts this reviewer out for the whole run
|
|
805
|
+
* @returns {boolean} true when the reviewer is treated as down this round
|
|
760
806
|
*/
|
|
761
|
-
function
|
|
762
|
-
if (
|
|
763
|
-
if (
|
|
764
|
-
return
|
|
807
|
+
function resolveReviewerDown(reviewerAvailabilityEntry, isReviewerDisabledByInput) {
|
|
808
|
+
if (isReviewerDisabledByInput) return true
|
|
809
|
+
if (reviewerAvailabilityEntry == null) return false
|
|
810
|
+
return reviewerAvailabilityEntry.down === true
|
|
765
811
|
}
|
|
766
812
|
|
|
767
813
|
/**
|
|
@@ -946,10 +992,10 @@ function commitNeedsCodeRecovery(commitResult) {
|
|
|
946
992
|
|
|
947
993
|
/**
|
|
948
994
|
* Decide whether a resolved HEAD SHA is safe to spawn lenses against. A dead
|
|
949
|
-
*
|
|
995
|
+
* preflight-git agent or a malformed result yields a falsy SHA; spawning lenses
|
|
950
996
|
* against it interpolates the literal string 'HEAD undefined' into their prompts
|
|
951
997
|
* and produces a spurious clean verdict on a non-existent commit.
|
|
952
|
-
* @param {string|null|undefined} resolvedHead the SHA from the git-utility '
|
|
998
|
+
* @param {string|null|undefined} resolvedHead the SHA from the git-utility 'preflight-git' task
|
|
953
999
|
* @returns {boolean} true only when the SHA is a non-empty string
|
|
954
1000
|
*/
|
|
955
1001
|
function isResolvedHeadUsable(resolvedHead) {
|
|
@@ -957,12 +1003,12 @@ function isResolvedHeadUsable(resolvedHead) {
|
|
|
957
1003
|
}
|
|
958
1004
|
|
|
959
1005
|
/**
|
|
960
|
-
* Decide whether the pre-flight
|
|
961
|
-
* conflict with its base. A dead
|
|
1006
|
+
* Decide whether the pre-flight mergeability probe found the PR branch in
|
|
1007
|
+
* conflict with its base. A dead preflight agent (null/undefined result) reports
|
|
962
1008
|
* not-conflicting so the run proceeds straight to the bug checks rather than
|
|
963
|
-
* force-pushing a rebase on a verdict that does not exist — a transient
|
|
1009
|
+
* force-pushing a rebase on a verdict that does not exist — a transient probe
|
|
964
1010
|
* failure must never trigger a destructive rebase.
|
|
965
|
-
* @param {object|null|undefined} mergeState the git-utility '
|
|
1011
|
+
* @param {object|null|undefined} mergeState the git-utility 'preflight-git' task result carrying the conflicting field
|
|
966
1012
|
* @returns {boolean} true only when the check reported conflicting:true
|
|
967
1013
|
*/
|
|
968
1014
|
function isMergeConflicting(mergeState) {
|
|
@@ -1015,9 +1061,10 @@ function classifyCopilotOutcome(copilot) {
|
|
|
1015
1061
|
|
|
1016
1062
|
/**
|
|
1017
1063
|
* Decide whether the Copilot review gate is bypassed for this COPILOT pass from
|
|
1018
|
-
* the gate outcome, mirroring
|
|
1019
|
-
* pass rather than left sticky. Only
|
|
1020
|
-
* unreachable after the poll cap)
|
|
1064
|
+
* the gate outcome, mirroring resolveReviewerDown's post-round bugbotDown
|
|
1065
|
+
* recompute so the flag is recomputed every pass rather than left sticky. Only
|
|
1066
|
+
* a 'down' outcome (Copilot out of quota or unreachable after the poll cap)
|
|
1067
|
+
* bypasses the convergence Copilot gate; an
|
|
1021
1068
|
* 'approved', 'fix', or 'retry' outcome means Copilot answered this pass, so the
|
|
1022
1069
|
* gate must be evaluated against its review and is never bypassed. Recomputing
|
|
1023
1070
|
* from the current outcome is what lets a recovered Copilot — one that returns
|
|
@@ -1105,9 +1152,6 @@ const prCoordinates = `owner=${input.owner} repo=${input.repo} PR #${input.prNum
|
|
|
1105
1152
|
* @returns {Promise<object>} LENS_SCHEMA result
|
|
1106
1153
|
*/
|
|
1107
1154
|
function runBugbotLens(head) {
|
|
1108
|
-
if (input.bugbotDisabled) {
|
|
1109
|
-
return Promise.resolve({ sha: head, clean: true, down: true, findings: [] })
|
|
1110
|
-
}
|
|
1111
1155
|
return convergeAgent(
|
|
1112
1156
|
`You are the Cursor Bugbot lens for ${prCoordinates}, HEAD ${head}. Cursor Bugbot participates this run.\n\n` +
|
|
1113
1157
|
`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` +
|
|
@@ -1120,7 +1164,7 @@ function runBugbotLens(head) {
|
|
|
1120
1164
|
` Only count entries whose commit_id starts with ${head}.\n` +
|
|
1121
1165
|
` - If findings exist on HEAD -> return them (each with its inline comment id in replyToCommentId when present, else null).\n` +
|
|
1122
1166
|
` - If a clean review exists on HEAD -> return clean.\n` +
|
|
1123
|
-
`4. No review yet on HEAD: check_bugbot_ci.py --check-active. If active (exit 0), poll: repeat check_bugbot_ci.py --check-clean / --check-active every 60 seconds (wait each 60-second interval inside this turn with the Monitor tool, per the WAITS AND POLLS rule above) for up to 25 iterations, then re-fetch the review. If not active (exit 1), post the literal comment "bugbot run" (no @mention, no other text) via python "${CONFIG.sharedScripts}/post_fix_reply.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --body "bugbot run", wait 8 seconds inside this turn with the Monitor tool (per the WAITS AND POLLS rule above), then poll as above.\n` +
|
|
1167
|
+
`4. No review yet on HEAD: check_bugbot_ci.py --check-active. If active (exit 0), poll: repeat check_bugbot_ci.py --check-clean / --check-active every 60 seconds (wait each 60-second interval inside this turn with the Monitor tool, per the WAITS AND POLLS rule above) for up to 25 iterations, then re-fetch the review. If not active (exit 1), post the literal comment "bugbot run" (no @mention, no other text) via ${REVIEWER_GATE_SENTINEL}python "${CONFIG.sharedScripts}/post_fix_reply.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --body "bugbot run", wait 8 seconds inside this turn with the Monitor tool (per the WAITS AND POLLS rule above), then poll as above.\n` +
|
|
1124
1168
|
`5. If after the full poll budget Bugbot has neither a check run nor a review on HEAD -> return {sha:${'`'}${head}${'`'}, clean:true, down:true, findings:[]} (treat as down).\n\n` +
|
|
1125
1169
|
`Scope is the whole PR; you are only reading Bugbot's own output here. For each finding set category: 'code-standard' when it is a pure CODE_RULES/style violation (naming, comments, type hints, magic values, structure) with no behavioral impact; 'bug' otherwise. Return strictly the schema.`,
|
|
1126
1170
|
{ label: 'lens:bugbot', phase: 'Converge', schema: LENS_SCHEMA },
|
|
@@ -1314,7 +1358,7 @@ function runCopilotGate(head) {
|
|
|
1314
1358
|
`You are the Copilot gate for ${prCoordinates}, HEAD ${head}. Do not edit code, commit, or push.\n\n` +
|
|
1315
1359
|
`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` +
|
|
1316
1360
|
`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` +
|
|
1317
|
-
` gh api --method POST repos/${input.owner}/${input.repo}/pulls/${input.prNumber}/requested_reviewers -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n` +
|
|
1361
|
+
` ${REVIEWER_GATE_SENTINEL}gh api --method POST repos/${input.owner}/${input.repo}/pulls/${input.prNumber}/requested_reviewers -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n` +
|
|
1318
1362
|
`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 down: true). 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` +
|
|
1319
1363
|
` - Out-of-usage notice on HEAD -> return the down result above (clean:true, down:true) and stop.\n` +
|
|
1320
1364
|
` - 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` +
|
|
@@ -1377,20 +1421,22 @@ async function repairConvergence(head, failures) {
|
|
|
1377
1421
|
|
|
1378
1422
|
/**
|
|
1379
1423
|
* Pre-flight conflict resolution: when the PR branch conflicts with its base,
|
|
1380
|
-
* rebase it clean before the bug checks run —
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1424
|
+
* rebase it clean before the bug checks run — edit (clean-coder rebases and
|
|
1425
|
+
* resolves, no push) -> verify (code-verifier binds a verdict to the rebased
|
|
1426
|
+
* tree) -> commit (clean-coder force-with-lease pushes). The conflict decision
|
|
1427
|
+
* comes from the merged 'preflight-git' probe the caller already ran, so this
|
|
1428
|
+
* function spawns no mergeability agent of its own. Returns the post-rebase
|
|
1429
|
+
* HEAD so the first converge round runs its lenses on the conflict-free diff.
|
|
1430
|
+
* A non-conflicting PR, a rebase the edit step declined, or a failed verdict
|
|
1431
|
+
* returns the unchanged HEAD so the run proceeds to the bug checks unchanged.
|
|
1432
|
+
* A mid-run conflict (origin/main advancing later) is still caught by the
|
|
1433
|
+
* FINALIZE convergence repair, which also rebases.
|
|
1388
1434
|
* @param {string} head PR HEAD SHA before any rebase
|
|
1435
|
+
* @param {boolean} isConflicting the isMergeConflicting decision over the preflight-git result
|
|
1389
1436
|
* @returns {Promise<string>} the HEAD SHA after a successful rebase push, or the unchanged head
|
|
1390
1437
|
*/
|
|
1391
|
-
async function resolveMergeConflicts(head) {
|
|
1392
|
-
|
|
1393
|
-
if (!isMergeConflicting(mergeState)) return head
|
|
1438
|
+
async function resolveMergeConflicts(head, isConflicting) {
|
|
1439
|
+
if (!isConflicting) return head
|
|
1394
1440
|
log(`Pre-flight: ${prCoordinates} conflicts with origin/main — rebasing clean before the bug checks`)
|
|
1395
1441
|
const editResult = await runCodeEditorTask('conflict-edit', { head })
|
|
1396
1442
|
if (editResult?.rebased !== true) return head
|
|
@@ -1485,9 +1531,10 @@ function parseDeferredPr(prUrl) {
|
|
|
1485
1531
|
* @param {Array<object>} findings deduped code-standard-only findings
|
|
1486
1532
|
* @param {string} sourceLabel short description of where the findings came from
|
|
1487
1533
|
* @param {boolean} hasHardeningPrAlreadyOpened true when an earlier round already opened the environment-hardening PR for this run, so the verify and commit steps are skipped and no second PR opens while the edit retries the issue filing
|
|
1488
|
-
* @
|
|
1534
|
+
* @param {{copilotDisabled: boolean, bugbotDisabled: boolean}} deferredReviewerFlags this run's latest resolved reviewer-down state, carried onto the deferred PR so a later generation converging it seeds the known-unavailable state instead of re-learning it
|
|
1535
|
+
* @returns {Promise<object>} `{ followUpIssueFiled, issueUrl, hardeningPrOpened, deferredPr }` — followUpIssueFiled true when the standards-edit step returned a non-empty issue URL, issueUrl that filed URL (empty string when the filing failed) so a later reuse-path round can reference it when resolving its own threads, hardeningPrOpened true when the hardening-commit step returned a non-empty hardeningPrUrl (a PR opened) so the run-once latch holds even when that URL does not parse into coordinates, and false when the commit step returned an empty URL (no PR opened) so a later round retries the open, and deferredPr the opened PR's `{owner, repo, prNumber, copilotDisabled, bugbotDisabled}` (null when no PR was opened or the committed URL does not parse) so the self-closing orchestrator can converge it in turn
|
|
1489
1536
|
*/
|
|
1490
|
-
async function spawnStandardsFollowUp(head, findings, sourceLabel, hasHardeningPrAlreadyOpened) {
|
|
1537
|
+
async function spawnStandardsFollowUp(head, findings, sourceLabel, hasHardeningPrAlreadyOpened, deferredReviewerFlags) {
|
|
1491
1538
|
const editResult = await runCodeEditorTask('standards-edit', { head, findings, sourceLabel })
|
|
1492
1539
|
const followUpIssueFiled = typeof editResult?.issueUrl === 'string' && editResult.issueUrl.length > 0
|
|
1493
1540
|
const followUpIssueUrl = followUpIssueFiled ? editResult.issueUrl : ''
|
|
@@ -1506,7 +1553,11 @@ async function spawnStandardsFollowUp(head, findings, sourceLabel, hasHardeningP
|
|
|
1506
1553
|
const commitResult = await runCodeEditorTask('hardening-commit', {
|
|
1507
1554
|
head, sourceLabel, hardeningRepoPath: editResult.hardeningRepoPath, hardeningBranch: editResult.hardeningBranch, issueUrl: editResult.issueUrl,
|
|
1508
1555
|
})
|
|
1509
|
-
const
|
|
1556
|
+
const parsedDeferredPr = parseDeferredPr(commitResult?.hardeningPrUrl)
|
|
1557
|
+
const deferredPr =
|
|
1558
|
+
parsedDeferredPr === null
|
|
1559
|
+
? null
|
|
1560
|
+
: { ...parsedDeferredPr, copilotDisabled: deferredReviewerFlags.copilotDisabled, bugbotDisabled: deferredReviewerFlags.bugbotDisabled }
|
|
1510
1561
|
const hardeningPrOpened = typeof commitResult?.hardeningPrUrl === 'string' && commitResult.hardeningPrUrl.length > 0
|
|
1511
1562
|
return { followUpIssueFiled, issueUrl: followUpIssueUrl, hardeningPrOpened, deferredPr }
|
|
1512
1563
|
}
|
|
@@ -1561,15 +1612,16 @@ async function resolveStandardsThreadsForBatch(head, findings, sourceLabel) {
|
|
|
1561
1612
|
* @param {string} head PR HEAD SHA the findings were raised against
|
|
1562
1613
|
* @param {Array<object>} findings deduped code-standard-only findings
|
|
1563
1614
|
* @param {string} sourceLabel short description of where the findings came from
|
|
1564
|
-
* @
|
|
1615
|
+
* @param {{copilotDisabled: boolean, bugbotDisabled: boolean}} deferredReviewerFlags this run's latest resolved reviewer-down state, carried onto the deferred PR so a later generation converging it seeds the known-unavailable state instead of re-learning it
|
|
1616
|
+
* @returns {Promise<object>} `{ hardeningPrOpened, deferredPr }` — hardeningPrOpened true when a hardening PR was opened for this run, and deferredPr the opened PR's `{owner, repo, prNumber, copilotDisabled, bugbotDisabled}` when this call opened it (null otherwise) so the self-closing orchestrator can converge it in turn
|
|
1565
1617
|
*/
|
|
1566
|
-
async function openStandardsFollowUpOnce(head, findings, sourceLabel) {
|
|
1618
|
+
async function openStandardsFollowUpOnce(head, findings, sourceLabel, deferredReviewerFlags) {
|
|
1567
1619
|
if (!shouldOpenStandardsFollowUp(hasStandardsFollowUpFiled)) {
|
|
1568
1620
|
log(`Standards deferral (${sourceLabel}): reusing the follow-up fix issue already filed for this run rather than filing a duplicate; environment-hardening PR ${wasStandardsHardeningPrOpened ? 'was opened for this run' : 'was not opened for this run'}`)
|
|
1569
1621
|
await resolveStandardsThreadsForBatch(head, findings, sourceLabel)
|
|
1570
1622
|
return { hardeningPrOpened: wasStandardsHardeningPrOpened, deferredPr: null }
|
|
1571
1623
|
}
|
|
1572
|
-
const standardsOutcome = await spawnStandardsFollowUp(head, findings, sourceLabel, wasStandardsHardeningPrOpened)
|
|
1624
|
+
const standardsOutcome = await spawnStandardsFollowUp(head, findings, sourceLabel, wasStandardsHardeningPrOpened, deferredReviewerFlags)
|
|
1573
1625
|
hasStandardsFollowUpFiled = standardsOutcome?.followUpIssueFiled === true
|
|
1574
1626
|
if (hasStandardsFollowUpFiled) {
|
|
1575
1627
|
standardsFollowUpIssueUrl = standardsOutcome.issueUrl
|
|
@@ -1591,26 +1643,27 @@ let hasStandardsFollowUpFiled = false
|
|
|
1591
1643
|
let wasStandardsHardeningPrOpened = false
|
|
1592
1644
|
let standardsFollowUpIssueUrl = ''
|
|
1593
1645
|
let reuseNote = null
|
|
1646
|
+
let reviewerAvailability = null
|
|
1594
1647
|
const deferredPrs = []
|
|
1595
1648
|
|
|
1596
|
-
const
|
|
1597
|
-
if (isResolvedHeadUsable(
|
|
1598
|
-
await resolveMergeConflicts(
|
|
1649
|
+
const preflight = await runGitTask('preflight-git')
|
|
1650
|
+
if (isResolvedHeadUsable(preflight?.sha)) {
|
|
1651
|
+
head = await resolveMergeConflicts(preflight.sha, isMergeConflicting(preflight))
|
|
1599
1652
|
}
|
|
1600
1653
|
|
|
1601
1654
|
log('Reuse pass: scanning the full diff for certain, behaviorally identical, autonomously implementable reuse improvements before convergence')
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
if (isResolvedHeadUsable(reuseHead)) {
|
|
1605
|
-
await runGitTask('prefetch-main')
|
|
1606
|
-
const reuse = await runReuseAuditPass(reuseHead)
|
|
1655
|
+
if (isResolvedHeadUsable(head)) {
|
|
1656
|
+
const reuse = await runReuseAuditPass(head)
|
|
1607
1657
|
const reuseFindings = reuse?.findings || []
|
|
1608
1658
|
if (reuseFindings.length > 0) {
|
|
1609
1659
|
log(`Reuse pass: ${reuseFindings.length} qualifying reuse improvement(s) — applying before convergence`)
|
|
1610
|
-
const reuseFix = await applyFixes(
|
|
1660
|
+
const reuseFix = await applyFixes(head, reuseFindings, 'reuse-pass')
|
|
1611
1661
|
reuseNote = reuseFix?.pushed === true
|
|
1612
1662
|
? `${reuseFindings.length} reuse improvement(s) applied before convergence (${reuseFix.newSha?.slice(0, SHA_COMPARISON_PREFIX_LENGTH)})`
|
|
1613
1663
|
: `${reuseFindings.length} reuse improvement(s) identified before convergence but not landed — the code-review lens re-surfaces any that remain`
|
|
1664
|
+
if (reuseFix?.pushed === true) {
|
|
1665
|
+
head = isResolvedHeadUsable(reuseFix.newSha) ? reuseFix.newSha : null
|
|
1666
|
+
}
|
|
1614
1667
|
} else {
|
|
1615
1668
|
log('Reuse pass: no reuse case cleared all three criteria — proceeding to convergence')
|
|
1616
1669
|
}
|
|
@@ -1622,29 +1675,33 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1622
1675
|
iterations += 1
|
|
1623
1676
|
if (phase === 'CONVERGE') {
|
|
1624
1677
|
rounds += 1
|
|
1625
|
-
const headResult = await runGitTask('resolve-head')
|
|
1626
|
-
head = headResult?.sha
|
|
1627
1678
|
if (!isResolvedHeadUsable(head)) {
|
|
1628
|
-
|
|
1679
|
+
const refreshedPreflight = await runGitTask('preflight-git')
|
|
1680
|
+
head = isResolvedHeadUsable(refreshedPreflight?.sha) ? refreshedPreflight.sha : null
|
|
1681
|
+
}
|
|
1682
|
+
if (!isResolvedHeadUsable(head)) {
|
|
1683
|
+
log(`Round ${rounds}: preflight-git agent returned no SHA — retrying without spawning lenses`)
|
|
1629
1684
|
continue
|
|
1630
1685
|
}
|
|
1631
|
-
await
|
|
1686
|
+
reviewerAvailability = await runReviewerAvailabilityCheck()
|
|
1687
|
+
const isBugbotDownPreSpawn = resolveReviewerDown(reviewerAvailability?.bugbot, input.bugbotDisabled || false)
|
|
1632
1688
|
log(`Round ${rounds}: parallel Bugbot + code-review + bug-audit on ${head?.slice(0, 7)}`)
|
|
1633
1689
|
const lenses = await parallel([
|
|
1634
|
-
() => runBugbotLens(head),
|
|
1690
|
+
() => (isBugbotDownPreSpawn ? Promise.resolve({ sha: head, clean: true, down: true, findings: [] }) : runBugbotLens(head)),
|
|
1635
1691
|
() => runCodeReviewLens(head),
|
|
1636
1692
|
() => runAuditLens(head),
|
|
1637
1693
|
])
|
|
1638
|
-
bugbotDown =
|
|
1694
|
+
bugbotDown = lenses[0] == null ? true : resolveReviewerDown(lenses[0], input.bugbotDisabled || false)
|
|
1639
1695
|
const roundOutcome = resolveRoundOutcome(lenses)
|
|
1640
1696
|
if (roundOutcome.allLensesDead) {
|
|
1641
1697
|
log(`Round ${rounds}: every lens agent died — retrying without posting a clean artifact`)
|
|
1698
|
+
head = null
|
|
1642
1699
|
continue
|
|
1643
1700
|
}
|
|
1644
1701
|
const findings = roundOutcome.findings
|
|
1645
1702
|
if (isStandardsOnlyRound(findings)) {
|
|
1646
1703
|
log(`Round ${rounds}: ${findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the round as passed`)
|
|
1647
|
-
const standardsOutcome = await openStandardsFollowUpOnce(head, findings, 'converge-round')
|
|
1704
|
+
const standardsOutcome = await openStandardsFollowUpOnce(head, findings, 'converge-round', { copilotDisabled: copilotDown, bugbotDisabled: bugbotDown })
|
|
1648
1705
|
standardsNote = standardsDeferralNote(findings.length, standardsOutcome.hardeningPrOpened)
|
|
1649
1706
|
if (standardsOutcome?.deferredPr) deferredPrs.push(standardsOutcome.deferredPr)
|
|
1650
1707
|
const auditResult = await runGeneralUtilityTask('post-clean-audit', { head })
|
|
@@ -1666,10 +1723,12 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1666
1723
|
: `fix lens landed no push for ${findings.length} finding(s) on HEAD ${head}`
|
|
1667
1724
|
break
|
|
1668
1725
|
}
|
|
1726
|
+
head = null
|
|
1669
1727
|
continue
|
|
1670
1728
|
}
|
|
1671
1729
|
if (!roundOutcome.roundClean) {
|
|
1672
1730
|
log(`Round ${rounds}: a lens reported not-clean with no findings on ${head?.slice(0, 7)} — re-converging without a clean artifact`)
|
|
1731
|
+
head = null
|
|
1673
1732
|
continue
|
|
1674
1733
|
}
|
|
1675
1734
|
log(`Round ${rounds}: all lenses clean on ${head?.slice(0, 7)} — posting clean audit artifact`)
|
|
@@ -1683,10 +1742,10 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1683
1742
|
}
|
|
1684
1743
|
|
|
1685
1744
|
if (phase === 'COPILOT') {
|
|
1686
|
-
if (input.copilotDisabled) {
|
|
1745
|
+
if (resolveReviewerDown(reviewerAvailability?.copilot, input.copilotDisabled || false)) {
|
|
1687
1746
|
copilotDown = true
|
|
1688
|
-
copilotNote = 'Copilot was
|
|
1689
|
-
log('Copilot gate: the
|
|
1747
|
+
copilotNote = 'Copilot was unavailable or out of premium-request quota this round — the Copilot gate was bypassed with no agent spawned and the PR was marked ready without a Copilot review'
|
|
1748
|
+
log('Copilot gate: the shared reviewer-availability probe (or the run input) reported Copilot unavailable — skipping the Copilot gate with no agent spawned and proceeding to mark-ready with the gate bypassed.')
|
|
1690
1749
|
phase = 'FINALIZE'
|
|
1691
1750
|
continue
|
|
1692
1751
|
}
|
|
@@ -1708,7 +1767,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1708
1767
|
if (copilotOutcome.kind === 'fix') {
|
|
1709
1768
|
if (isStandardsOnlyRound(copilotOutcome.findings)) {
|
|
1710
1769
|
log(`Copilot raised ${copilotOutcome.findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
|
|
1711
|
-
const standardsOutcome = await openStandardsFollowUpOnce(head, copilotOutcome.findings, 'copilot')
|
|
1770
|
+
const standardsOutcome = await openStandardsFollowUpOnce(head, copilotOutcome.findings, 'copilot', { copilotDisabled: copilotDown, bugbotDisabled: bugbotDown })
|
|
1712
1771
|
standardsNote = standardsDeferralNote(copilotOutcome.findings.length, standardsOutcome.hardeningPrOpened)
|
|
1713
1772
|
if (standardsOutcome?.deferredPr) deferredPrs.push(standardsOutcome.deferredPr)
|
|
1714
1773
|
copilotDown = false
|
|
@@ -1726,6 +1785,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1726
1785
|
: `copilot fix lens landed no push for ${copilotOutcome.findings.length} finding(s) on HEAD ${head}`
|
|
1727
1786
|
break
|
|
1728
1787
|
}
|
|
1788
|
+
head = null
|
|
1729
1789
|
phase = 'CONVERGE'
|
|
1730
1790
|
continue
|
|
1731
1791
|
}
|
|
@@ -1753,6 +1813,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1753
1813
|
}
|
|
1754
1814
|
log(`Convergence check failed: ${convergenceOutcome.failures.join('; ')} — repairing then re-converging`)
|
|
1755
1815
|
await repairConvergence(head, convergenceOutcome.failures)
|
|
1816
|
+
head = null
|
|
1756
1817
|
phase = 'CONVERGE'
|
|
1757
1818
|
continue
|
|
1758
1819
|
}
|
|
@@ -17,8 +17,7 @@ from autoconverge_report_constants.render_report_constants import (
|
|
|
17
17
|
DEFAULT_FINDING_CATEGORY,
|
|
18
18
|
DEFAULT_FINDING_SEVERITY,
|
|
19
19
|
DEFAULT_ISSUE_ICON,
|
|
20
|
-
|
|
21
|
-
HTML_HEAD_TEMPLATE,
|
|
20
|
+
HTML_TITLE_AND_STYLE_TEMPLATE,
|
|
22
21
|
HTML_STYLE_BLOCK,
|
|
23
22
|
ISO_DATE_LENGTH,
|
|
24
23
|
ISSUE_CLASS_FIELD_AFTER_LINES,
|
|
@@ -890,7 +889,9 @@ def render_report_html(
|
|
|
890
889
|
generated_date: ISO date string derived from the journal timestamp.
|
|
891
890
|
|
|
892
891
|
Returns:
|
|
893
|
-
A
|
|
892
|
+
A page-content HTML string (a title, a style block, and the body
|
|
893
|
+
markup) with no surrounding <!DOCTYPE>, <html>, <head>, or <body>
|
|
894
|
+
tags, ready for the Artifact tool to wrap and publish.
|
|
894
895
|
"""
|
|
895
896
|
short_sha_length = SHORT_SHA_LENGTH
|
|
896
897
|
pr_number = pr_metadata.number
|
|
@@ -899,7 +900,7 @@ def render_report_html(
|
|
|
899
900
|
final_sha_short = pr_metadata.final_sha[:short_sha_length]
|
|
900
901
|
round_count = pr_metadata.round_count
|
|
901
902
|
|
|
902
|
-
|
|
903
|
+
title_and_style_html = HTML_TITLE_AND_STYLE_TEMPLATE.format(
|
|
903
904
|
pr_number=pr_number,
|
|
904
905
|
style_block=HTML_STYLE_BLOCK,
|
|
905
906
|
)
|
|
@@ -933,15 +934,10 @@ def render_report_html(
|
|
|
933
934
|
)
|
|
934
935
|
|
|
935
936
|
return (
|
|
936
|
-
f"{
|
|
937
|
-
f"<html lang='en'>\n"
|
|
938
|
-
f"{head_html}\n"
|
|
939
|
-
f"<body>\n"
|
|
937
|
+
f"{title_and_style_html}\n"
|
|
940
938
|
f'<div class="container">\n'
|
|
941
939
|
f"{body_content}\n"
|
|
942
|
-
f"</div
|
|
943
|
-
f"</body>\n"
|
|
944
|
-
f"</html>"
|
|
940
|
+
f"</div>"
|
|
945
941
|
)
|
|
946
942
|
|
|
947
943
|
|
package/skills/bugteam/CLAUDE.md
CHANGED
|
@@ -10,7 +10,7 @@ Each loop: a `code-quality-agent` (fresh context, all A–P audit categories) pr
|
|
|
10
10
|
|
|
11
11
|
| File | Purpose |
|
|
12
12
|
|---|---|
|
|
13
|
-
| `SKILL.md` | Hub — pre-flight call, refusals, audit-posting
|
|
13
|
+
| `SKILL.md` | Hub — pre-flight call, refusals, the audit-posting step (applies the `post-audit-findings` skill), progress checklist, and situation-to-reference table. Read this first. |
|
|
14
14
|
| `PROMPTS.md` | Spawn XML, A–P category bindings, outcome XML schemas. |
|
|
15
15
|
| `CONSTRAINTS.md` | Invariants — what the loop must never violate. |
|
|
16
16
|
| `EXAMPLES.md` | Exit scenarios: converged, cap-reached, stuck, refusal, mixed-outcome. |
|
|
@@ -278,7 +278,7 @@ cd into `<worktree_path>` before any git or file operation.
|
|
|
278
278
|
[ ] git add + commit
|
|
279
279
|
[ ] git push
|
|
280
280
|
[ ] Per finding: atomically post the unified-template reply, then call resolve_thread (no yield between them)
|
|
281
|
-
[ ] Publish fix summary via
|
|
281
|
+
[ ] Publish fix summary via the Artifact tool, capture URL
|
|
282
282
|
[ ] Append fix summary URL to parent review via add_reply_to_pull_request_comment
|
|
283
283
|
[ ] Write fix outcomes XML
|
|
284
284
|
</self_audit_checklist>
|
|
@@ -343,14 +343,15 @@ cd into `<worktree_path>` before any git or file operation.
|
|
|
343
343
|
`loop_comment_index` entry alongside `finding_comment_id`, see
|
|
344
344
|
[reference/obstacles/fix-resolve-thread.md](reference/obstacles/fix-resolve-thread.md)).
|
|
345
345
|
|
|
346
|
-
9.
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
9. Write the fix summary (what was fixed, what was skipped, what was left
|
|
347
|
+
unaddressed) to a self-contained HTML file, load the `artifact-design`
|
|
348
|
+
skill, then publish it with the `Artifact` tool. Capture the returned
|
|
349
|
+
URL.
|
|
349
350
|
|
|
350
|
-
10. Append the fix summary
|
|
351
|
+
10. Append the fix summary URL (from step 9) to the parent review
|
|
351
352
|
via `add_reply_to_pull_request_comment(commentId=<id>, body=...,
|
|
352
353
|
owner=<O>, repo=<R>, pullNumber=<N>)`. The body carries the
|
|
353
|
-
|
|
354
|
+
artifact URL plus a one-line summary of fixes applied this loop.
|
|
354
355
|
|
|
355
356
|
11. Write `.bugteam-pr<N>-loop<L>.fix-outcomes.xml` inside
|
|
356
357
|
`<worktree_path>` (schema below) and return its path.
|