claude-dev-env 1.79.0 → 1.81.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/_shared/pr-loop/scripts/CLAUDE.md +3 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +116 -30
- package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +13 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +113 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +467 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +8 -0
- package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +339 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +242 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota_constants.py +63 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +297 -0
- package/audit-rubrics/CLAUDE.md +3 -2
- package/audit-rubrics/category_rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +2 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +13 -1
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +19 -0
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +46 -0
- package/audit-rubrics/prompts/CLAUDE.md +2 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +1 -0
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +19 -7
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +14 -0
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +51 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/code_rules_annotations_length.py +59 -11
- package/hooks/blocking/code_rules_banned_identifiers.py +48 -9
- package/hooks/blocking/code_rules_command_dispatch.py +140 -0
- package/hooks/blocking/code_rules_docstrings.py +93 -0
- package/hooks/blocking/code_rules_enforcer.py +58 -4
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_js_conventions.py +246 -0
- package/hooks/blocking/code_rules_naming_collection.py +5 -0
- package/hooks/blocking/code_rules_test_assertions.py +3 -0
- package/hooks/blocking/code_rules_unused_imports.py +7 -66
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +7 -0
- package/hooks/blocking/test_code_rules_command_dispatch.py +95 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +20 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +10 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +8 -4
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +9 -3
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +164 -0
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +112 -18
- package/hooks/blocking/test_code_rules_js_conventions.py +167 -0
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/blocking_check_limits.py +10 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +14 -0
- package/hooks/hooks_constants/command_dispatch_constants.py +28 -0
- package/hooks/hooks_constants/js_conventions_constants.py +54 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
- package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +3 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +43 -1
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +2 -2
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +1 -4
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +100 -4
- package/skills/autoconverge/SKILL.md +28 -7
- package/skills/autoconverge/reference/convergence.md +3 -3
- package/skills/autoconverge/reference/stop-conditions.md +1 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +58 -122
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +289 -5
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +50 -46
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +6 -6
- package/skills/autoconverge/workflow/converge.mjs +235 -247
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +11 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +23 -7
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +28 -2
- package/skills/pr-converge/SKILL.md +28 -2
- package/skills/pr-converge/reference/convergence-gates.md +13 -1
- package/skills/pr-converge/reference/state-schema.md +11 -0
|
@@ -16,14 +16,14 @@ export const meta = {
|
|
|
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
18
|
{ title: 'Converge', detail: 'Bugbot + code-review + bug-audit in parallel each round; one clean-coder applies all fixes; loop until all three are clean on a stable HEAD' },
|
|
19
|
-
{ title: 'Copilot gate', detail: '
|
|
19
|
+
{ 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, route findings back into Converge, and when Copilot is down or out of quota log a notice and mark the PR ready with the gate bypassed' },
|
|
20
20
|
{ title: 'Finalize', detail: 'Run check_convergence.py; mark draft=false on a full pass' },
|
|
21
21
|
],
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const CONFIG = {
|
|
25
25
|
maxIterations: 20,
|
|
26
|
-
copilotMaxPolls:
|
|
26
|
+
copilotMaxPolls: 8,
|
|
27
27
|
sharedScripts: '$HOME/.claude/skills/pr-converge/scripts',
|
|
28
28
|
prLoopScripts: '$HOME/.claude/_shared/pr-loop/scripts',
|
|
29
29
|
bugteamRubric: '$HOME/.claude/skills/bugteam/reference/audit-contract.md',
|
|
@@ -69,43 +69,25 @@ const worktreeDirective = (repoPath) =>
|
|
|
69
69
|
* @param {object} options the agent() options (label, phase, schema, agentType, model)
|
|
70
70
|
* @returns {Promise<*>} the agent() result
|
|
71
71
|
*/
|
|
72
|
-
const convergeAgent = (prompt, options) =>
|
|
73
|
-
|
|
74
|
-
const fullPrompt = isResume
|
|
75
|
-
? prompt
|
|
76
|
-
: `${HEADLESS_SAFETY_PREAMBLE}${worktreeDirective(activeRepoPath)}${prompt}`
|
|
77
|
-
return agent(fullPrompt, options)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Spawn the git/utility Explore agent once before the converge loop.
|
|
82
|
-
* @returns {Promise<string>} the agent id
|
|
83
|
-
*/
|
|
84
|
-
async function spawnGitAgent() {
|
|
85
|
-
const result = await convergeAgent(
|
|
86
|
-
`You are the git-utility agent for ${prCoordinates}. Your role is to resolve the PR HEAD SHA, fetch origin main, and check merge conflicts when asked. Do not edit code.\n\n` +
|
|
87
|
-
`Initial task: print the current HEAD SHA of ${prCoordinates}. Run exactly:\n` +
|
|
88
|
-
`gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .head.sha\n` +
|
|
89
|
-
`Return the full 40-character SHA in the sha field.`,
|
|
90
|
-
{ label: 'git-utility', phase: 'Converge', schema: HEAD_SCHEMA, agentType: 'Explore' },
|
|
91
|
-
)
|
|
92
|
-
return result?.agentId
|
|
93
|
-
}
|
|
72
|
+
const convergeAgent = (prompt, options) =>
|
|
73
|
+
agent(`${HEADLESS_SAFETY_PREAMBLE}${worktreeDirective(activeRepoPath)}${prompt}`, options)
|
|
94
74
|
|
|
95
75
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
76
|
+
* Spawn a fresh git-utility Explore agent for a specific task. 'resolve-head'
|
|
77
|
+
* prints the PR HEAD SHA, 'prefetch-main' fetches origin main so the review
|
|
78
|
+
* lenses diff against an up-to-date base, and any other task reports whether the
|
|
79
|
+
* branch has merge conflicts. The agent never edits code.
|
|
98
80
|
* @param {string} task the short task name
|
|
99
81
|
* @param {string} head optional HEAD SHA for conflict checks
|
|
100
|
-
* @returns {Promise<object>} the structured output
|
|
82
|
+
* @returns {Promise<object|string>} the structured output, or the transcript string when the 'prefetch-main' resume runs schema-less
|
|
101
83
|
*/
|
|
102
|
-
function
|
|
84
|
+
function runGitTask(task, head) {
|
|
103
85
|
if (task === 'resolve-head') {
|
|
104
86
|
return convergeAgent(
|
|
105
87
|
`Print the current HEAD SHA of ${prCoordinates}. Run exactly:\n` +
|
|
106
88
|
`gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .head.sha\n` +
|
|
107
89
|
`Return the full 40-character SHA in the sha field. Do not modify any files.`,
|
|
108
|
-
{ label: 'git-utility', phase: 'Converge', schema: HEAD_SCHEMA, agentType: 'Explore'
|
|
90
|
+
{ label: 'git-utility', phase: 'Converge', schema: HEAD_SCHEMA, agentType: 'Explore' },
|
|
109
91
|
)
|
|
110
92
|
}
|
|
111
93
|
if (task === 'prefetch-main') {
|
|
@@ -113,7 +95,7 @@ function resumeGitAgent(agentId, task, head) {
|
|
|
113
95
|
`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` +
|
|
114
96
|
`git fetch origin main\n` +
|
|
115
97
|
`Do not edit, commit, push, rebase, or modify any files — fetch only.`,
|
|
116
|
-
{ label: 'git-utility', phase: 'Converge', agentType: 'Explore'
|
|
98
|
+
{ label: 'git-utility', phase: 'Converge', agentType: 'Explore' },
|
|
117
99
|
)
|
|
118
100
|
}
|
|
119
101
|
return convergeAgent(
|
|
@@ -122,44 +104,20 @@ function resumeGitAgent(agentId, task, head) {
|
|
|
122
104
|
` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq '{mergeable: .mergeable, state: .mergeable_state}'\n` +
|
|
123
105
|
`up to 5 times, 5 seconds apart (delay each retry with "sleep 5", or the PowerShell alternative "Start-Sleep -Seconds 5"), stopping as soon as mergeable is true or false.\n\n` +
|
|
124
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.`,
|
|
125
|
-
{ label: 'git-utility', phase: 'Converge', schema: MERGE_CONFLICT_SCHEMA, agentType: 'Explore'
|
|
107
|
+
{ label: 'git-utility', phase: 'Converge', schema: MERGE_CONFLICT_SCHEMA, agentType: 'Explore' },
|
|
126
108
|
)
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
/**
|
|
130
|
-
* Spawn
|
|
131
|
-
*
|
|
132
|
-
* the
|
|
133
|
-
*
|
|
134
|
-
* that commits and recovers, mirroring the repair and conflict paths. The spawn
|
|
135
|
-
* makes no edits — the first recovery resume is the earliest step that touches
|
|
136
|
-
* the working tree. Returns the runtime agent id so the resume calls target the
|
|
137
|
-
* live session; a runtime without resume support returns no agent id, and each
|
|
138
|
-
* resume falls back to a fresh spawn.
|
|
139
|
-
* @param {string} head PR HEAD SHA
|
|
140
|
-
* @param {Array<object>} findings the findings to fix
|
|
141
|
-
* @param {string} sourceLabel short description of where the findings came from
|
|
142
|
-
* @returns {Promise<string|undefined>} the runtime agent id, or undefined when the runtime returns none
|
|
143
|
-
*/
|
|
144
|
-
async function spawnFixerAgent(head, findings, sourceLabel) {
|
|
145
|
-
const result = await convergeAgent(
|
|
146
|
-
`You are the fixer agent for ${findings.length} finding(s) (${sourceLabel}) on ${prCoordinates}, HEAD ${head}. The edit step left fixes in the working tree, uncommitted. Across this session you run a sequence of steps: commit and push the working-tree fixes once a separate verifier passes them, and recover when a verify objection or a commit-gate block needs another edit. A separate verifier agent grades the working tree, so you never verify your own edits. Make NO edits in this first turn — confirm only that the working tree is on the PR branch at HEAD ${head} with uncommitted fixes present, then wait for the next step's instructions. Reply READY.`,
|
|
147
|
-
{ label: `fixer:${sourceLabel}`, phase: 'Converge', agentType: 'clean-coder' },
|
|
148
|
-
)
|
|
149
|
-
return result?.agentId
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Resume the fixer agent for commit or recovery edits. The fixer never verifies;
|
|
154
|
-
* a separate verifier agent emits the verdict, so commit, verify-recover, and
|
|
155
|
-
* commit-recover all run on the editing session while the verdict that gates the
|
|
156
|
-
* commit comes from a different session.
|
|
157
|
-
* @param {string} agentId the agent id from spawnFixerAgent
|
|
112
|
+
* Spawn a fresh fixer clean-coder agent for a commit or recovery edit. The fixer
|
|
113
|
+
* never verifies; a separate verifier agent emits the verdict, so the verdict
|
|
114
|
+
* that gates the commit comes from a different agent than the one that commits
|
|
115
|
+
* and recovers.
|
|
158
116
|
* @param {string} task the short task name
|
|
159
117
|
* @param {object} context task-specific context
|
|
160
118
|
* @returns {Promise<object>} the structured output
|
|
161
119
|
*/
|
|
162
|
-
function
|
|
120
|
+
function runFixerTask(task, context) {
|
|
163
121
|
const label = `fixer:${context.sourceLabel}`
|
|
164
122
|
if (task === 'commit') {
|
|
165
123
|
return convergeAgent(
|
|
@@ -171,7 +129,7 @@ function resumeFixerAgent(agentId, task, context) {
|
|
|
171
129
|
`- On a successful push: newSha=the new HEAD SHA after your push, pushed=true, resolvedWithoutCommit=false, blockedNeedingEdit=false, blockerDetail="", and a one-line summary.\n` +
|
|
172
130
|
`- When a commit-time hook or gate (for example code_rules_gate, the CODE_RULES commit gate) rejects the commit because the fix needs a code change: keep the no-edit rule, return newSha=${context.head}, pushed=false, resolvedWithoutCommit=false, blockedNeedingEdit=true, blockerDetail=<the verbatim hook message naming the file and rule>, and a summary. A recovery fixer runs after you to clear it.\n` +
|
|
173
131
|
`- On a transient or non-code failure (auth, network, a non-fast-forward, a lock): newSha=${context.head}, pushed=false, resolvedWithoutCommit=false, blockedNeedingEdit=false, blockerDetail="", and a summary naming the failure.`,
|
|
174
|
-
{ label, phase: 'Converge', schema: FIX_SCHEMA, agentType: 'clean-coder'
|
|
132
|
+
{ label, phase: 'Converge', schema: FIX_SCHEMA, agentType: 'clean-coder' },
|
|
175
133
|
)
|
|
176
134
|
}
|
|
177
135
|
if (task === 'commit-recover') {
|
|
@@ -185,7 +143,7 @@ function resumeFixerAgent(agentId, task, context) {
|
|
|
185
143
|
`- Leave the corrected fixes in the working tree. Do NOT commit and do NOT push — the verify step re-binds a verdict and the commit step pushes after you.\n\n` +
|
|
186
144
|
`Return values: edited=true with a one-line summary when you changed code to clear the block; edited=false, resolvedWithoutCommit=false when the block cannot be cleared with a code change.` +
|
|
187
145
|
PRE_COMMIT_GATE_STEP,
|
|
188
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
146
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
189
147
|
)
|
|
190
148
|
}
|
|
191
149
|
const objection = context.objection || VERIFY_OBJECTION_FALLBACK
|
|
@@ -199,30 +157,29 @@ function resumeFixerAgent(agentId, task, context) {
|
|
|
199
157
|
`- Leave the corrected fixes in the working tree. Do NOT commit and do NOT push — the verify step re-binds a verdict and the commit step pushes after you.\n\n` +
|
|
200
158
|
`Return values: edited=true with a one-line summary when you changed code to address the objections; edited=false, resolvedWithoutCommit=false when the objections cannot be cleared with a code change.` +
|
|
201
159
|
PRE_COMMIT_GATE_STEP,
|
|
202
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
160
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
203
161
|
)
|
|
204
162
|
}
|
|
205
163
|
|
|
206
164
|
/**
|
|
207
|
-
* Joined fixer recovery loop: a
|
|
208
|
-
* fixes while the
|
|
209
|
-
*
|
|
210
|
-
* pushes — the same editor/verifier
|
|
211
|
-
* use. The verify step routes through
|
|
212
|
-
* recover on the fixer); the commit
|
|
213
|
-
* (commit and commit-recover on the fixer,
|
|
214
|
-
* verdict returns the unchanged HEAD so the
|
|
215
|
-
*
|
|
216
|
-
* @param {string} verifierId the verifier agent id from spawnVerifierAgent
|
|
165
|
+
* Joined fixer recovery loop: the verify step spawns a code-verifier agent to
|
|
166
|
+
* grade the working-tree fixes while the commit and recovery steps spawn
|
|
167
|
+
* clean-coder fixer agents, so the verdict that gates the commit comes from a
|
|
168
|
+
* different agent than the one that edits and pushes — the same editor/verifier
|
|
169
|
+
* separation the repair and conflict paths use. The verify step routes through
|
|
170
|
+
* verifyWithRecovery (verify on the verifier, recover on the fixer); the commit
|
|
171
|
+
* step routes through commitWithRecovery (commit and commit-recover on the fixer,
|
|
172
|
+
* re-verify on the verifier). A failed verdict returns the unchanged HEAD so the
|
|
173
|
+
* round reads as not-progressed.
|
|
217
174
|
* @param {string} head PR HEAD SHA
|
|
218
175
|
* @param {Array<object>} findings the findings to fix
|
|
219
176
|
* @param {string} sourceLabel short description of where the findings came from
|
|
220
177
|
* @returns {Promise<object>} FIX_SCHEMA result
|
|
221
178
|
*/
|
|
222
|
-
async function fixerWithRecovery(
|
|
179
|
+
async function fixerWithRecovery(head, findings, sourceLabel) {
|
|
223
180
|
const verifyTranscript = await verifyWithRecovery({
|
|
224
|
-
runVerify: () =>
|
|
225
|
-
runRecoverEdit: (objection, attempt) =>
|
|
181
|
+
runVerify: () => runVerifierTask('fix-verify', { head, findings, sourceLabel }),
|
|
182
|
+
runRecoverEdit: (objection, attempt) => runFixerTask('verify-recover', { head, findings, sourceLabel, objection, attempt }),
|
|
226
183
|
})
|
|
227
184
|
if (!verdictPassed(verifyTranscript)) {
|
|
228
185
|
return {
|
|
@@ -235,38 +192,22 @@ async function fixerWithRecovery(fixerAgentId, verifierId, head, findings, sourc
|
|
|
235
192
|
}
|
|
236
193
|
}
|
|
237
194
|
return commitWithRecovery({
|
|
238
|
-
runCommit: () =>
|
|
239
|
-
runVerify: () =>
|
|
240
|
-
runRecoverEdit: (detail, attempt) =>
|
|
195
|
+
runCommit: () => runFixerTask('commit', { head, findings, sourceLabel }),
|
|
196
|
+
runVerify: () => runVerifierTask('fix-verify', { head, findings, sourceLabel }),
|
|
197
|
+
runRecoverEdit: (detail, attempt) => runFixerTask('commit-recover', { head, findings, sourceLabel, blockerDetail: detail, attempt }),
|
|
241
198
|
})
|
|
242
199
|
}
|
|
243
200
|
|
|
244
201
|
/**
|
|
245
|
-
* Spawn
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* task-specific edit instructions. Returns the runtime agent id so the resume
|
|
250
|
-
* calls target the live session; a runtime without resume support returns no
|
|
251
|
-
* agent id, and each resume falls back to a fresh spawn.
|
|
252
|
-
* @returns {Promise<string|undefined>} the runtime agent id, or undefined when the runtime returns none
|
|
253
|
-
*/
|
|
254
|
-
async function spawnCodeEditorAgent() {
|
|
255
|
-
const result = await convergeAgent(
|
|
256
|
-
`You are the code-editor agent for ${prCoordinates}. Across this converge round you run a sequence of edit and commit steps, each delivered as its own instruction. Make NO edits in this first turn — wait for the first task's instructions. Reply READY.`,
|
|
257
|
-
{ label: 'code-editor', phase: 'Converge', agentType: 'clean-coder' },
|
|
258
|
-
)
|
|
259
|
-
return result?.agentId
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Resume the code-editor agent for a specific edit task.
|
|
264
|
-
* @param {string} agentId the agent id from spawnCodeEditorAgent
|
|
202
|
+
* Spawn a fresh code-editor clean-coder agent for a specific edit task (fix-edit,
|
|
203
|
+
* conflict-edit, repair-edit, repair-commit, standards-edit,
|
|
204
|
+
* standards-resolve-threads, hardening-commit, commit-recover, verify-recover).
|
|
205
|
+
* Each task carries its own edit instructions.
|
|
265
206
|
* @param {string} task the short task name
|
|
266
207
|
* @param {object} context task-specific context
|
|
267
|
-
* @returns {Promise<object>} the structured output
|
|
208
|
+
* @returns {Promise<object|string>} the structured output, or the transcript string when a schema-less task ('hardening-commit', 'standards-resolve-threads') runs
|
|
268
209
|
*/
|
|
269
|
-
function
|
|
210
|
+
function runCodeEditorTask(task, context) {
|
|
270
211
|
const label = `code-editor:${task}`
|
|
271
212
|
if (task === 'fix-edit') {
|
|
272
213
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
@@ -287,7 +228,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
287
228
|
`- When every finding was already addressed so no code change was needed — yet you still resolved each GitHub review thread above: edited=false, resolvedWithoutCommit=true. Only set this when every thread that carries a comment id is resolved; otherwise the round is treated as stalled.\n` +
|
|
288
229
|
`Always include a one-line summary.` +
|
|
289
230
|
PRE_COMMIT_GATE_STEP,
|
|
290
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
231
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
291
232
|
)
|
|
292
233
|
}
|
|
293
234
|
if (task === 'conflict-edit') {
|
|
@@ -298,7 +239,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
298
239
|
`- Rebase the branch onto origin/main and resolve every conflict so the tree is clean and conflict-free: git fetch origin main; git rebase origin/main; resolve each conflict, preserving the intent of both the PR's change and the incoming base change. A rebase creates local commits, which is fine.\n` +
|
|
299
240
|
`- Do NOT push and do NOT force-push — the commit step force-pushes after the verify step binds a verdict. Pushing here would change the surface the verifier binds to.\n\n` +
|
|
300
241
|
`Return rebased=true with a one-line summary when you rebased onto origin/main and resolved the conflicts; rebased=false with a summary when the branch did not actually need a rebase or you could not complete it.`,
|
|
301
|
-
{ label, phase: 'Converge', schema: CONFLICT_EDIT_SCHEMA, agentType: 'clean-coder'
|
|
242
|
+
{ label, phase: 'Converge', schema: CONFLICT_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
302
243
|
)
|
|
303
244
|
}
|
|
304
245
|
if (task === 'repair-edit') {
|
|
@@ -318,7 +259,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
318
259
|
`- resolvedWithoutCommit=true only when you addressed the gates with neither a code change nor a rebase (bot threads resolved only), so there is nothing for the commit step to push.\n` +
|
|
319
260
|
`Always include a one-line summary.` +
|
|
320
261
|
PRE_COMMIT_GATE_STEP,
|
|
321
|
-
{ label, phase: 'Finalize', schema: REPAIR_EDIT_SCHEMA, agentType: 'clean-coder'
|
|
262
|
+
{ label, phase: 'Finalize', schema: REPAIR_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
322
263
|
)
|
|
323
264
|
}
|
|
324
265
|
if (task === 'repair-commit') {
|
|
@@ -334,7 +275,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
334
275
|
`- On a successful push: newSha=the new HEAD SHA after your push, pushed=true, resolvedWithoutCommit=false, blockedNeedingEdit=false, blockerDetail="", and a one-line summary.\n` +
|
|
335
276
|
`- When a commit-time hook or gate (for example code_rules_gate, the CODE_RULES commit gate) rejects the commit because the fix needs a code change: keep the no-edit rule, return newSha=${context.head}, pushed=false, resolvedWithoutCommit=false, blockedNeedingEdit=true, blockerDetail=<the verbatim hook message naming the file and rule>, and a summary. A recovery fixer runs after you to clear it.\n` +
|
|
336
277
|
`- On a transient or non-code failure (auth, network, a non-fast-forward, a lock): newSha=${context.head}, pushed=false, resolvedWithoutCommit=false, blockedNeedingEdit=false, blockerDetail="", and a summary naming the failure.`,
|
|
337
|
-
{ label, phase: 'Finalize', schema: FIX_SCHEMA, agentType: 'clean-coder'
|
|
278
|
+
{ label, phase: 'Finalize', schema: FIX_SCHEMA, agentType: 'clean-coder' },
|
|
338
279
|
)
|
|
339
280
|
}
|
|
340
281
|
if (task === 'standards-edit') {
|
|
@@ -350,7 +291,20 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
350
291
|
`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` +
|
|
351
292
|
`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.` +
|
|
352
293
|
PRE_COMMIT_GATE_STEP,
|
|
353
|
-
{ label, phase: 'Converge', schema: STANDARDS_EDIT_SCHEMA, agentType: 'clean-coder'
|
|
294
|
+
{ label, phase: 'Converge', schema: STANDARDS_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
if (task === 'standards-resolve-threads') {
|
|
298
|
+
const findingsBlock = renderFindingsBlock(context.findings)
|
|
299
|
+
const threadIds = context.findings
|
|
300
|
+
.flatMap((each) => collectFindingThreadIds(each))
|
|
301
|
+
.filter((each) => typeof each === 'number')
|
|
302
|
+
return convergeAgent(
|
|
303
|
+
`You are the THREAD-RESOLUTION step for a code-standard-only round on ${prCoordinates}, HEAD ${context.head} (${context.sourceLabel}). This run already filed the deferred-fix follow-up issue ${context.issueUrl}, so this batch's code-standard findings defer to that same issue. Make NO code edits, NO commit, and NO push — only reply to and resolve the review threads this batch carries.\n\n` +
|
|
304
|
+
`Findings:\n${findingsBlock}\n\n` +
|
|
305
|
+
`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 ${context.issueUrl}." 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` +
|
|
306
|
+
`Return a one-line summary naming the threads you resolved.`,
|
|
307
|
+
{ label, phase: 'Converge', agentType: 'clean-coder' },
|
|
354
308
|
)
|
|
355
309
|
}
|
|
356
310
|
if (task === 'hardening-commit') {
|
|
@@ -360,7 +314,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
360
314
|
`- Make NO further file edits of any kind. Any edit changes the surface and invalidates the verdict that unlocks the commit gate, so the push would be blocked. Only commit and push what is already there.\n` +
|
|
361
315
|
`- In ${context.hardeningRepoPath}: make ONE commit of the staged hooks/rules change on branch ${context.hardeningBranch}, push it, then open a DRAFT PR. The PR body references the follow-up issue ${context.issueUrl || '(none)'} and states the PR hardens the environment so the deferred violation classes are blocked at Write/Edit time. Honor the gh-body-file rule: write a BOM-free temp file and pass --body-file.\n\n` +
|
|
362
316
|
`Return a one-line summary naming the hardening PR URL.`,
|
|
363
|
-
{ label, phase: 'Converge', agentType: 'clean-coder'
|
|
317
|
+
{ label, phase: 'Converge', agentType: 'clean-coder' },
|
|
364
318
|
)
|
|
365
319
|
}
|
|
366
320
|
if (task === 'commit-recover') {
|
|
@@ -374,7 +328,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
374
328
|
`- Leave the corrected fixes in the working tree. Do NOT commit and do NOT push — the verify step re-binds a verdict and the commit step pushes after you.\n\n` +
|
|
375
329
|
`Return values: edited=true with a one-line summary when you changed code to clear the block; edited=false, resolvedWithoutCommit=false when the block cannot be cleared with a code change.` +
|
|
376
330
|
PRE_COMMIT_GATE_STEP,
|
|
377
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
331
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
378
332
|
)
|
|
379
333
|
}
|
|
380
334
|
// verify-recover
|
|
@@ -389,34 +343,19 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
389
343
|
`- Leave the corrected fixes in the working tree. Do NOT commit and do NOT push — the verify step re-binds a verdict and the commit step pushes after you.\n\n` +
|
|
390
344
|
`Return values: edited=true with a one-line summary when you changed code to address the objections; edited=false, resolvedWithoutCommit=false when the objections cannot be cleared with a code change.` +
|
|
391
345
|
PRE_COMMIT_GATE_STEP,
|
|
392
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
346
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
393
347
|
)
|
|
394
348
|
}
|
|
395
349
|
|
|
396
350
|
/**
|
|
397
|
-
* Spawn
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* agent id so the resume calls target the live session; a runtime without resume
|
|
401
|
-
* support returns no agent id, and each resume falls back to a fresh spawn.
|
|
402
|
-
* @returns {Promise<string|undefined>} the runtime agent id, or undefined when the runtime returns none
|
|
403
|
-
*/
|
|
404
|
-
async function spawnVerifierAgent() {
|
|
405
|
-
const result = await convergeAgent(
|
|
406
|
-
`You are the verifier agent for ${prCoordinates}. Across this converge round you run a sequence of verify steps, each delivered as its own instruction; each ends with a fenced verdict block. Do NO edits of any kind — verification only. Make no move in this first turn — wait for the first verify task's instructions. Reply READY.`,
|
|
407
|
-
{ label: 'verifier', phase: 'Converge', agentType: 'code-verifier' },
|
|
408
|
-
)
|
|
409
|
-
return result?.agentId
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Resume the verifier agent for a specific verify task.
|
|
414
|
-
* @param {string} agentId the agent id from spawnVerifierAgent
|
|
351
|
+
* Spawn a fresh verifier code-verifier agent for a specific verify task
|
|
352
|
+
* (fix-verify, repair-verify, hardening-verify). The agent makes no edits —
|
|
353
|
+
* verification only — and ends its message with a fenced verdict block.
|
|
415
354
|
* @param {string} task the short task name
|
|
416
355
|
* @param {object} context task-specific context
|
|
417
356
|
* @returns {Promise<string>} the verifier transcript carrying the verdict fence
|
|
418
357
|
*/
|
|
419
|
-
function
|
|
358
|
+
function runVerifierTask(task, context) {
|
|
420
359
|
const label = `verifier:${task}`
|
|
421
360
|
if (task === 'fix-verify') {
|
|
422
361
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
@@ -427,7 +366,7 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
427
366
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
428
367
|
`2. Verify the uncommitted working-tree changes resolve every finding above: run the relevant tests and the named gates against the working tree. Read the diff (git diff) and confirm each finding is fixed test-first per CODE_RULES.\n` +
|
|
429
368
|
`3. ${buildVerdictFenceSteps(input.owner, input.repo, input.prNumber)}`,
|
|
430
|
-
{ label, phase: 'Converge', agentType: 'code-verifier'
|
|
369
|
+
{ label, phase: 'Converge', agentType: 'code-verifier' },
|
|
431
370
|
)
|
|
432
371
|
}
|
|
433
372
|
if (task === 'repair-verify') {
|
|
@@ -441,7 +380,7 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
441
380
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
442
381
|
`2. Verify the working tree against origin/main: any bot-thread code fix is correct test-first per CODE_RULES, and a rebase (if any) left a clean, conflict-free tree. Read the diff (git diff origin/main) and run the relevant tests and named gates.\n` +
|
|
443
382
|
`3. ${buildVerdictFenceSteps(input.owner, input.repo, input.prNumber)}`,
|
|
444
|
-
{ label, phase: 'Finalize', agentType: 'code-verifier'
|
|
383
|
+
{ label, phase: 'Finalize', agentType: 'code-verifier' },
|
|
445
384
|
)
|
|
446
385
|
}
|
|
447
386
|
return convergeAgent(
|
|
@@ -460,37 +399,20 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
460
399
|
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
461
400
|
" ```\n" +
|
|
462
401
|
` When verification fails, set all_pass to false and list the unresolved concerns in findings; still include the manifest_sha256. The verdict fence must be the last thing in your message.`,
|
|
463
|
-
{ label, phase: 'Converge', agentType: 'code-verifier'
|
|
464
|
-
)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Spawn the general-utility general-purpose agent once per converge round,
|
|
469
|
-
* establishing its role so each later resume (post-clean-audit, mark-ready)
|
|
470
|
-
* continues the same session. The spawn edits no code.
|
|
471
|
-
* Returns the runtime agent id so the resume calls target the live session; a
|
|
472
|
-
* runtime without resume support returns no agent id, and each resume falls back
|
|
473
|
-
* to a fresh spawn.
|
|
474
|
-
* @returns {Promise<string|undefined>} the runtime agent id, or undefined when the runtime returns none
|
|
475
|
-
*/
|
|
476
|
-
async function spawnGeneralUtilityAgent() {
|
|
477
|
-
const result = await convergeAgent(
|
|
478
|
-
`You are the general-utility agent for ${prCoordinates}. Across this converge round you run a sequence of administrative steps (posting the clean audit, marking the PR ready), each delivered as its own instruction. Do not edit code, commit, or push. Make no move in this first turn — wait for the first task's instructions. Reply READY.`,
|
|
479
|
-
{ label: 'general-utility', phase: 'Converge', agentType: 'general-purpose' },
|
|
402
|
+
{ label, phase: 'Converge', agentType: 'code-verifier' },
|
|
480
403
|
)
|
|
481
|
-
return result?.agentId
|
|
482
404
|
}
|
|
483
405
|
|
|
484
406
|
/**
|
|
485
|
-
*
|
|
486
|
-
* 'post-clean-audit' posts the terminal CLEAN bugteam
|
|
487
|
-
* marks the PR ready and confirms it left draft state.
|
|
488
|
-
*
|
|
407
|
+
* Spawn a fresh general-utility general-purpose agent for one of its two
|
|
408
|
+
* administrative tasks: 'post-clean-audit' posts the terminal CLEAN bugteam
|
|
409
|
+
* review, and 'mark-ready' marks the PR ready and confirms it left draft state.
|
|
410
|
+
* The agent edits no code.
|
|
489
411
|
* @param {'post-clean-audit'|'mark-ready'} task the short task name
|
|
490
412
|
* @param {object} context task-specific context
|
|
491
413
|
* @returns {Promise<object>} the task result
|
|
492
414
|
*/
|
|
493
|
-
function
|
|
415
|
+
function runGeneralUtilityTask(task, context) {
|
|
494
416
|
const label = `general-utility:${task}`
|
|
495
417
|
if (task === 'post-clean-audit') {
|
|
496
418
|
return convergeAgent(
|
|
@@ -499,7 +421,7 @@ function resumeGeneralUtilityAgent(agentId, task, context) {
|
|
|
499
421
|
`python "${CONFIG.prLoopScripts}/post_audit_thread.py" --skill bugteam --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --commit ${context.head} --state CLEAN --findings-json <temp-file>\n` +
|
|
500
422
|
`Run the script with --help first if any flag name differs. This posts the APPROVE review body that check_convergence.py reads for the bugteam gate. Do not edit code, commit, or push.\n\n` +
|
|
501
423
|
`Report whether the review landed. When the script prints a review URL, return {posted:true, reviewUrl:<that URL>, reason:""}. When the script is denied (a permission prompt or auto-mode-classifier block), errors, or prints anything other than a review URL, return {posted:false, reviewUrl:"", reason:<the denial message or error as one line>}. Do not retry a denied post.`,
|
|
502
|
-
{ label, phase: 'Converge', schema: CLEAN_AUDIT_SCHEMA, agentType: 'general-purpose'
|
|
424
|
+
{ label, phase: 'Converge', schema: CLEAN_AUDIT_SCHEMA, agentType: 'general-purpose' },
|
|
503
425
|
)
|
|
504
426
|
}
|
|
505
427
|
if (task === 'mark-ready') {
|
|
@@ -512,35 +434,18 @@ function resumeGeneralUtilityAgent(agentId, task, context) {
|
|
|
512
434
|
`1. Run: gh pr ready ${input.prNumber} --repo ${input.owner}/${input.repo}\n` +
|
|
513
435
|
`2. Re-query the draft state: gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .draft\n` +
|
|
514
436
|
`Return {ready:true} only when step 2 prints false (the PR is no longer a draft). If step 1 errors or step 2 still prints true, return {ready:false}.`,
|
|
515
|
-
{ label, phase: 'Finalize', schema: READY_SCHEMA, agentType: 'general-purpose'
|
|
437
|
+
{ label, phase: 'Finalize', schema: READY_SCHEMA, agentType: 'general-purpose' },
|
|
516
438
|
)
|
|
517
439
|
}
|
|
518
|
-
throw new Error(`
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Spawn the convergence-check Explore agent once before the converge loop,
|
|
523
|
-
* establishing its role so each per-round resume runs the convergence gate in the
|
|
524
|
-
* same session. The spawn edits no code. Returns the runtime agent id so the
|
|
525
|
-
* resume calls target the live session; a runtime without resume support returns
|
|
526
|
-
* no agent id, and each resume falls back to a fresh spawn.
|
|
527
|
-
* @returns {Promise<string|undefined>} the runtime agent id, or undefined when the runtime returns none
|
|
528
|
-
*/
|
|
529
|
-
async function spawnConvergenceCheckAgent() {
|
|
530
|
-
const result = await convergeAgent(
|
|
531
|
-
`You are the convergence-check agent for ${prCoordinates}. Each round you run the authoritative convergence gate and report its result, delivered as its own instruction. Do not edit code. Make no move in this first turn — wait for the first check instruction. Reply READY.`,
|
|
532
|
-
{ label: 'convergence-check', phase: 'Converge', agentType: 'Explore' },
|
|
533
|
-
)
|
|
534
|
-
return result?.agentId
|
|
440
|
+
throw new Error(`runGeneralUtilityTask: unknown task ${task}`)
|
|
535
441
|
}
|
|
536
442
|
|
|
537
443
|
/**
|
|
538
|
-
*
|
|
539
|
-
* @param {string} agentId the agent id from spawnConvergenceCheckAgent
|
|
444
|
+
* Spawn a fresh convergence-check Explore agent for the convergence check.
|
|
540
445
|
* @param {object} context carries bugbotDown and copilotDown
|
|
541
446
|
* @returns {Promise<object>} CONVERGENCE_SCHEMA result
|
|
542
447
|
*/
|
|
543
|
-
function
|
|
448
|
+
function runConvergenceCheck(context) {
|
|
544
449
|
const label = 'check-convergence'
|
|
545
450
|
const bugbotDownFlag = context.bugbotDown ? ' --bugbot-down' : ''
|
|
546
451
|
const copilotDownFlag = context.copilotDown ? ' --copilot-down' : ''
|
|
@@ -550,7 +455,7 @@ function resumeConvergenceCheckAgent(agentId, context) {
|
|
|
550
455
|
`Exit 0 -> every gate passed: return {pass:true, failures:[]}.\n` +
|
|
551
456
|
`Exit 1 -> return {pass:false, failures:[<each printed FAIL line verbatim>]}.\n` +
|
|
552
457
|
`Exit 2 -> retry once; if it still errors, return {pass:false, failures:["check_convergence gh error"]}.`,
|
|
553
|
-
{ label, phase: 'Finalize', schema: CONVERGENCE_SCHEMA, agentType: 'Explore'
|
|
458
|
+
{ label, phase: 'Finalize', schema: CONVERGENCE_SCHEMA, agentType: 'Explore' },
|
|
554
459
|
)
|
|
555
460
|
}
|
|
556
461
|
|
|
@@ -1029,7 +934,7 @@ function commitNeedsCodeRecovery(commitResult) {
|
|
|
1029
934
|
* resolve-head agent or a malformed result yields a falsy SHA; spawning lenses
|
|
1030
935
|
* against it interpolates the literal string 'HEAD undefined' into their prompts
|
|
1031
936
|
* and produces a spurious clean verdict on a non-existent commit.
|
|
1032
|
-
* @param {string|null|undefined} resolvedHead the SHA from the git-utility
|
|
937
|
+
* @param {string|null|undefined} resolvedHead the SHA from the git-utility 'resolve-head' task
|
|
1033
938
|
* @returns {boolean} true only when the SHA is a non-empty string
|
|
1034
939
|
*/
|
|
1035
940
|
function isResolvedHeadUsable(resolvedHead) {
|
|
@@ -1042,7 +947,7 @@ function isResolvedHeadUsable(resolvedHead) {
|
|
|
1042
947
|
* not-conflicting so the run proceeds straight to the bug checks rather than
|
|
1043
948
|
* force-pushing a rebase on a verdict that does not exist — a transient check
|
|
1044
949
|
* failure must never trigger a destructive rebase.
|
|
1045
|
-
* @param {object|null|undefined} mergeState the git-utility
|
|
950
|
+
* @param {object|null|undefined} mergeState the git-utility 'check-merge-conflicts' task result
|
|
1046
951
|
* @returns {boolean} true only when the check reported conflicting:true
|
|
1047
952
|
*/
|
|
1048
953
|
function isMergeConflicting(mergeState) {
|
|
@@ -1333,10 +1238,10 @@ async function verifyWithRecovery({ runVerify, runRecoverEdit }) {
|
|
|
1333
1238
|
/**
|
|
1334
1239
|
* Fix lens: edit (clean-coder, no commit) -> verify (a separate code-verifier
|
|
1335
1240
|
* emits a verdict fence binding the working tree) -> commit (clean-coder, one
|
|
1336
|
-
* commit + push, no edits). The verifier is a distinct
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1241
|
+
* commit + push, no edits). The verifier is a distinct agent from the fixer, so
|
|
1242
|
+
* the verdict that gates the commit comes from a different agent than the one
|
|
1243
|
+
* that edits and pushes — the same editor/verifier separation the repair and
|
|
1244
|
+
* conflict paths use, and the separation a workflow code-verifier needs to
|
|
1340
1245
|
* produce the verdict the verified-commit gate requires, which the SubagentStop
|
|
1341
1246
|
* minter cannot mint for workflow-spawned agents. When verification fails (or the
|
|
1342
1247
|
* edit step stalled with no thread to resolve), the commit step is skipped and the
|
|
@@ -1347,8 +1252,7 @@ async function verifyWithRecovery({ runVerify, runRecoverEdit }) {
|
|
|
1347
1252
|
* @returns {Promise<object>} FIX_SCHEMA result
|
|
1348
1253
|
*/
|
|
1349
1254
|
async function applyFixes(head, findings, sourceLabel) {
|
|
1350
|
-
const
|
|
1351
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'fix-edit', { head, findings, sourceLabel })
|
|
1255
|
+
const editResult = await runCodeEditorTask('fix-edit', { head, findings, sourceLabel })
|
|
1352
1256
|
if (editResult?.resolvedWithoutCommit === true && editResult?.edited !== true) {
|
|
1353
1257
|
return {
|
|
1354
1258
|
newSha: head,
|
|
@@ -1359,9 +1263,7 @@ async function applyFixes(head, findings, sourceLabel) {
|
|
|
1359
1263
|
blockerDetail: '',
|
|
1360
1264
|
}
|
|
1361
1265
|
}
|
|
1362
|
-
|
|
1363
|
-
const verifierId = await spawnVerifierAgent()
|
|
1364
|
-
return fixerWithRecovery(fixerAgentId, verifierId, head, findings, sourceLabel)
|
|
1266
|
+
return fixerWithRecovery(head, findings, sourceLabel)
|
|
1365
1267
|
}
|
|
1366
1268
|
|
|
1367
1269
|
/**
|
|
@@ -1370,7 +1272,7 @@ async function applyFixes(head, findings, sourceLabel) {
|
|
|
1370
1272
|
* post stops the run with an actionable message rather than re-converging until
|
|
1371
1273
|
* the iteration cap. Handles a dead post agent (a null result) as not posted.
|
|
1372
1274
|
* @param {string} head converged PR HEAD SHA
|
|
1373
|
-
* @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit
|
|
1275
|
+
* @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit task, or null when the agent died
|
|
1374
1276
|
* @returns {string} the blocker message naming the post failure and the unblock path
|
|
1375
1277
|
*/
|
|
1376
1278
|
function cleanAuditBlocker(head, auditResult) {
|
|
@@ -1400,7 +1302,7 @@ function runCopilotGate(head) {
|
|
|
1400
1302
|
` gh api --method POST repos/${input.owner}/${input.repo}/pulls/${input.prNumber}/requested_reviewers -f 'reviewers[]=copilot-pull-request-reviewer[bot]'\n` +
|
|
1401
1303
|
`2. Poll for Copilot's review on HEAD ${head}: up to ${CONFIG.copilotMaxPolls} attempts, 360 seconds apart (delay each attempt with "sleep 360", or the PowerShell alternative "Start-Sleep -Seconds 360"). 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` +
|
|
1402
1304
|
` - Out-of-usage notice on HEAD -> return the down result above (clean:true, down:true) and stop.\n` +
|
|
1403
|
-
` - Copilot review present
|
|
1305
|
+
` - 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` +
|
|
1404
1306
|
` - 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` +
|
|
1405
1307
|
` - 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` +
|
|
1406
1308
|
`Return strictly the schema.`,
|
|
@@ -1423,8 +1325,7 @@ function runCopilotGate(head) {
|
|
|
1423
1325
|
* @returns {Promise<object>} FIX_SCHEMA result
|
|
1424
1326
|
*/
|
|
1425
1327
|
async function repairConvergence(head, failures) {
|
|
1426
|
-
const
|
|
1427
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'repair-edit', { head, failures })
|
|
1328
|
+
const editResult = await runCodeEditorTask('repair-edit', { head, failures })
|
|
1428
1329
|
const hasPushWork = editResult?.edited === true || editResult?.rebased === true
|
|
1429
1330
|
if (!hasPushWork) {
|
|
1430
1331
|
return {
|
|
@@ -1436,10 +1337,9 @@ async function repairConvergence(head, failures) {
|
|
|
1436
1337
|
blockerDetail: '',
|
|
1437
1338
|
}
|
|
1438
1339
|
}
|
|
1439
|
-
const verifierId = await spawnVerifierAgent()
|
|
1440
1340
|
const verifyTranscript = await verifyWithRecovery({
|
|
1441
|
-
runVerify: () =>
|
|
1442
|
-
runRecoverEdit: (objection, attempt) =>
|
|
1341
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1342
|
+
runRecoverEdit: (objection, attempt) => runCodeEditorTask('verify-recover', { head, sourceLabel: 'repair', objection, attempt }),
|
|
1443
1343
|
})
|
|
1444
1344
|
if (!verdictPassed(verifyTranscript)) {
|
|
1445
1345
|
return {
|
|
@@ -1453,9 +1353,9 @@ async function repairConvergence(head, failures) {
|
|
|
1453
1353
|
}
|
|
1454
1354
|
const wasRebased = editResult?.rebased === true
|
|
1455
1355
|
return commitWithRecovery({
|
|
1456
|
-
runCommit: () =>
|
|
1457
|
-
runVerify: () =>
|
|
1458
|
-
runRecoverEdit: (detail, attempt) =>
|
|
1356
|
+
runCommit: () => runCodeEditorTask('repair-commit', { head, wasRebased }),
|
|
1357
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1358
|
+
runRecoverEdit: (detail, attempt) => runCodeEditorTask('commit-recover', { head, sourceLabel: 'repair', blockerDetail: detail, attempt }),
|
|
1459
1359
|
})
|
|
1460
1360
|
}
|
|
1461
1361
|
|
|
@@ -1473,24 +1373,22 @@ async function repairConvergence(head, failures) {
|
|
|
1473
1373
|
* @param {string} head PR HEAD SHA before any rebase
|
|
1474
1374
|
* @returns {Promise<string>} the HEAD SHA after a successful rebase push, or the unchanged head
|
|
1475
1375
|
*/
|
|
1476
|
-
async function resolveMergeConflicts(head
|
|
1477
|
-
const mergeState = await
|
|
1376
|
+
async function resolveMergeConflicts(head) {
|
|
1377
|
+
const mergeState = await runGitTask('check-merge-conflicts', head)
|
|
1478
1378
|
if (!isMergeConflicting(mergeState)) return head
|
|
1479
1379
|
log(`Pre-flight: ${prCoordinates} conflicts with origin/main — rebasing clean before the bug checks`)
|
|
1480
|
-
const
|
|
1481
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'conflict-edit', { head })
|
|
1380
|
+
const editResult = await runCodeEditorTask('conflict-edit', { head })
|
|
1482
1381
|
if (editResult?.rebased !== true) return head
|
|
1483
1382
|
const failures = ['PR branch had merge conflicts with origin/main; the rebase must leave a clean, conflict-free tree']
|
|
1484
|
-
const verifierId = await spawnVerifierAgent()
|
|
1485
1383
|
const verifyTranscript = await verifyWithRecovery({
|
|
1486
|
-
runVerify: () =>
|
|
1487
|
-
runRecoverEdit: (objection, attempt) =>
|
|
1384
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1385
|
+
runRecoverEdit: (objection, attempt) => runCodeEditorTask('verify-recover', { head, sourceLabel: 'conflict-rebase', objection, attempt }),
|
|
1488
1386
|
})
|
|
1489
1387
|
if (!verdictPassed(verifyTranscript)) return head
|
|
1490
1388
|
const commitResult = await commitWithRecovery({
|
|
1491
|
-
runCommit: () =>
|
|
1492
|
-
runVerify: () =>
|
|
1493
|
-
runRecoverEdit: (detail, attempt) =>
|
|
1389
|
+
runCommit: () => runCodeEditorTask('repair-commit', { head, wasRebased: true }),
|
|
1390
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1391
|
+
runRecoverEdit: (detail, attempt) => runCodeEditorTask('commit-recover', { head, sourceLabel: 'conflict-rebase', blockerDetail: detail, attempt }),
|
|
1494
1392
|
})
|
|
1495
1393
|
return commitResult?.newSha || head
|
|
1496
1394
|
}
|
|
@@ -1507,19 +1405,35 @@ function isStandardsOnlyRound(findings) {
|
|
|
1507
1405
|
return findings.length > 0 && findings.every((each) => each.category === 'code-standard')
|
|
1508
1406
|
}
|
|
1509
1407
|
|
|
1408
|
+
/**
|
|
1409
|
+
* Decide whether a standards-only round should file the follow-up fix issue and
|
|
1410
|
+
* open the environment-hardening PR. Standards findings are deferred rather than
|
|
1411
|
+
* fixed on this PR, so the same code-standard findings re-surface on every
|
|
1412
|
+
* converge round and on the Copilot gate; without this guard each re-entry files
|
|
1413
|
+
* a fresh duplicate follow-up issue and hardening PR for the one deferred finding
|
|
1414
|
+
* class. The follow-up issue is filed once per convergence run — a round whose
|
|
1415
|
+
* filing succeeds latches the guard, while a round whose filing failed leaves it
|
|
1416
|
+
* clear so a later round retries the filing.
|
|
1417
|
+
* @param {boolean} hasAlreadyFiled true when an earlier standards-only round in this run already filed the follow-up issue
|
|
1418
|
+
* @returns {boolean} true when this round should attempt the follow-up filing
|
|
1419
|
+
*/
|
|
1420
|
+
function shouldOpenStandardsFollowUp(hasAlreadyFiled) {
|
|
1421
|
+
return hasAlreadyFiled !== true
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1510
1424
|
/**
|
|
1511
1425
|
* Build the standards-deferral note for the closing report, naming the
|
|
1512
|
-
* environment-hardening PR only when one was opened this
|
|
1426
|
+
* environment-hardening PR only when one was opened for this run so the note
|
|
1513
1427
|
* never claims a PR the skip paths did not produce.
|
|
1514
1428
|
* @param {number} findingsCount count of deferred code-standard findings
|
|
1515
|
-
* @param {boolean}
|
|
1429
|
+
* @param {boolean} wasHardeningPrOpened true when the hardening PR was opened for this run
|
|
1516
1430
|
* @returns {string} the human-facing deferral note
|
|
1517
1431
|
*/
|
|
1518
|
-
function standardsDeferralNote(findingsCount,
|
|
1432
|
+
function standardsDeferralNote(findingsCount, wasHardeningPrOpened) {
|
|
1519
1433
|
const base = `${findingsCount} code-standard finding(s) deferred to a follow-up fix issue`
|
|
1520
|
-
return
|
|
1434
|
+
return wasHardeningPrOpened
|
|
1521
1435
|
? `${base} plus an environment-hardening PR — verify both land`
|
|
1522
|
-
: `${base} — verify it lands (no environment-hardening PR was opened this
|
|
1436
|
+
: `${base} — verify it lands (no environment-hardening PR was opened for this run)`
|
|
1523
1437
|
}
|
|
1524
1438
|
|
|
1525
1439
|
/**
|
|
@@ -1530,31 +1444,102 @@ function standardsDeferralNote(findingsCount, hardeningPrOpened) {
|
|
|
1530
1444
|
* commit (clean-coder makes one commit, pushes, and opens the DRAFT hardening
|
|
1531
1445
|
* PR). Splitting the edit from the push lets a workflow code-verifier produce the
|
|
1532
1446
|
* verdict the verified-commit gate requires for the cross-repo hardening commit.
|
|
1533
|
-
* This PR's own branch is never touched. When
|
|
1534
|
-
*
|
|
1535
|
-
* skipped.
|
|
1447
|
+
* This PR's own branch is never touched. When a hardening PR already opened for
|
|
1448
|
+
* this run, the edit staged no hardening, or the verify step fails, the follow-up
|
|
1449
|
+
* issue still stands and the commit step is skipped.
|
|
1536
1450
|
* @param {string} head PR HEAD SHA the findings were raised against
|
|
1537
1451
|
* @param {Array<object>} findings deduped code-standard-only findings
|
|
1538
1452
|
* @param {string} sourceLabel short description of where the findings came from
|
|
1539
|
-
* @
|
|
1453
|
+
* @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
|
|
1454
|
+
* @returns {Promise<object>} `{ followUpIssueFiled, issueUrl, hardeningPrOpened }` — 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 only when the hardening PR was opened on this call
|
|
1540
1455
|
*/
|
|
1541
|
-
async function spawnStandardsFollowUp(head, findings, sourceLabel) {
|
|
1542
|
-
const
|
|
1543
|
-
const
|
|
1456
|
+
async function spawnStandardsFollowUp(head, findings, sourceLabel, hasHardeningPrAlreadyOpened) {
|
|
1457
|
+
const editResult = await runCodeEditorTask('standards-edit', { head, findings, sourceLabel })
|
|
1458
|
+
const followUpIssueFiled = typeof editResult?.issueUrl === 'string' && editResult.issueUrl.length > 0
|
|
1459
|
+
const followUpIssueUrl = followUpIssueFiled ? editResult.issueUrl : ''
|
|
1460
|
+
if (hasHardeningPrAlreadyOpened === true) {
|
|
1461
|
+
return { followUpIssueFiled, issueUrl: followUpIssueUrl, hardeningPrOpened: false }
|
|
1462
|
+
}
|
|
1544
1463
|
if (editResult?.hardeningEdited !== true || !editResult?.hardeningRepoPath) {
|
|
1545
|
-
return { hardeningPrOpened: false }
|
|
1464
|
+
return { followUpIssueFiled, issueUrl: followUpIssueUrl, hardeningPrOpened: false }
|
|
1546
1465
|
}
|
|
1547
|
-
const
|
|
1548
|
-
const verifyTranscript = await resumeVerifierAgent(verifierId, 'hardening-verify', {
|
|
1466
|
+
const verifyTranscript = await runVerifierTask('hardening-verify', {
|
|
1549
1467
|
head, sourceLabel, hardeningRepoPath: editResult.hardeningRepoPath, hardeningBranch: editResult.hardeningBranch,
|
|
1550
1468
|
})
|
|
1551
1469
|
if (!verdictPassed(verifyTranscript)) {
|
|
1552
|
-
return { hardeningPrOpened: false }
|
|
1470
|
+
return { followUpIssueFiled, issueUrl: followUpIssueUrl, hardeningPrOpened: false }
|
|
1553
1471
|
}
|
|
1554
|
-
await
|
|
1472
|
+
await runCodeEditorTask('hardening-commit', {
|
|
1555
1473
|
head, sourceLabel, hardeningRepoPath: editResult.hardeningRepoPath, hardeningBranch: editResult.hardeningBranch, issueUrl: editResult.issueUrl,
|
|
1556
1474
|
})
|
|
1557
|
-
return { hardeningPrOpened: true }
|
|
1475
|
+
return { followUpIssueFiled, issueUrl: followUpIssueUrl, hardeningPrOpened: true }
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Report whether any finding in a batch carries a GitHub review thread that needs
|
|
1480
|
+
* an inline reply and resolution.
|
|
1481
|
+
* @param {Array<object>} findings the batch of findings
|
|
1482
|
+
* @returns {boolean} true when at least one finding carries a review comment id
|
|
1483
|
+
*/
|
|
1484
|
+
function findingsCarryThreads(findings) {
|
|
1485
|
+
return findings.some((each) => collectFindingThreadIds(each).length > 0)
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* On the reuse path — after this run already filed the deferred-fix follow-up
|
|
1490
|
+
* issue — reply to and resolve this batch's code-standard review threads against
|
|
1491
|
+
* that same issue. The issue filing and hardening PR stay gated behind the
|
|
1492
|
+
* run-once flags; only the per-batch thread resolution runs here, so a later
|
|
1493
|
+
* standards-only round's bot threads are still marked resolved and the FINALIZE
|
|
1494
|
+
* zero-unresolved-bot-threads gate passes. A batch of in-memory audit findings
|
|
1495
|
+
* that carries no review thread needs no agent, so the resolve step is skipped.
|
|
1496
|
+
* @param {string} head PR HEAD SHA the findings were raised against
|
|
1497
|
+
* @param {Array<object>} findings this batch's deduped code-standard-only findings
|
|
1498
|
+
* @param {string} sourceLabel short description of where the findings came from
|
|
1499
|
+
* @returns {Promise<void>}
|
|
1500
|
+
*/
|
|
1501
|
+
async function resolveStandardsThreadsForBatch(head, findings, sourceLabel) {
|
|
1502
|
+
if (!findingsCarryThreads(findings)) {
|
|
1503
|
+
return
|
|
1504
|
+
}
|
|
1505
|
+
await runCodeEditorTask('standards-resolve-threads', {
|
|
1506
|
+
head, findings, sourceLabel, issueUrl: standardsFollowUpIssueUrl,
|
|
1507
|
+
})
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* File the deferred follow-up issue and open the environment-hardening PR at most
|
|
1512
|
+
* once per convergence run, then reuse them. The two guards latch independently:
|
|
1513
|
+
* the follow-up issue latches only when its filing succeeds, and the hardening PR
|
|
1514
|
+
* latches the moment one opens and never re-opens. A standards-only round whose
|
|
1515
|
+
* issue filing already succeeded — in the converge phase or at the Copilot gate —
|
|
1516
|
+
* skips spawnStandardsFollowUp, resolves this batch's own code-standard review
|
|
1517
|
+
* threads against the already-filed issue via resolveStandardsThreadsForBatch, and
|
|
1518
|
+
* returns the remembered hardening outcome. A round whose issue filing failed
|
|
1519
|
+
* re-runs spawnStandardsFollowUp to retry the filing, passing the remembered
|
|
1520
|
+
* hardening state so an already-opened hardening PR is never re-opened. The run
|
|
1521
|
+
* therefore files one follow-up issue and opens one hardening PR rather than a
|
|
1522
|
+
* fresh duplicate per re-entry — while every round still resolves the review
|
|
1523
|
+
* threads its own findings carry, even when the filing must retry after the
|
|
1524
|
+
* hardening PR has opened.
|
|
1525
|
+
* @param {string} head PR HEAD SHA the findings were raised against
|
|
1526
|
+
* @param {Array<object>} findings deduped code-standard-only findings
|
|
1527
|
+
* @param {string} sourceLabel short description of where the findings came from
|
|
1528
|
+
* @returns {Promise<boolean>} true when a hardening PR was opened for this run
|
|
1529
|
+
*/
|
|
1530
|
+
async function openStandardsFollowUpOnce(head, findings, sourceLabel) {
|
|
1531
|
+
if (!shouldOpenStandardsFollowUp(hasStandardsFollowUpFiled)) {
|
|
1532
|
+
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'}`)
|
|
1533
|
+
await resolveStandardsThreadsForBatch(head, findings, sourceLabel)
|
|
1534
|
+
return wasStandardsHardeningPrOpened
|
|
1535
|
+
}
|
|
1536
|
+
const standardsOutcome = await spawnStandardsFollowUp(head, findings, sourceLabel, wasStandardsHardeningPrOpened)
|
|
1537
|
+
hasStandardsFollowUpFiled = standardsOutcome?.followUpIssueFiled === true
|
|
1538
|
+
if (hasStandardsFollowUpFiled) {
|
|
1539
|
+
standardsFollowUpIssueUrl = standardsOutcome.issueUrl
|
|
1540
|
+
}
|
|
1541
|
+
wasStandardsHardeningPrOpened = wasStandardsHardeningPrOpened || standardsOutcome?.hardeningPrOpened === true
|
|
1542
|
+
return wasStandardsHardeningPrOpened
|
|
1558
1543
|
}
|
|
1559
1544
|
|
|
1560
1545
|
let phase = 'CONVERGE'
|
|
@@ -1563,23 +1548,24 @@ let rounds = 0
|
|
|
1563
1548
|
let iterations = 0
|
|
1564
1549
|
let blocker = null
|
|
1565
1550
|
let bugbotDown = input.bugbotDisabled || false
|
|
1566
|
-
let copilotDown = false
|
|
1551
|
+
let copilotDown = input.copilotDisabled || false
|
|
1567
1552
|
let copilotNote = null
|
|
1568
1553
|
let standardsNote = null
|
|
1554
|
+
let hasStandardsFollowUpFiled = false
|
|
1555
|
+
let wasStandardsHardeningPrOpened = false
|
|
1556
|
+
let standardsFollowUpIssueUrl = ''
|
|
1569
1557
|
let reuseNote = null
|
|
1570
1558
|
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
const preflightHead = await resumeGitAgent(gitAgentId, 'resolve-head')
|
|
1559
|
+
const preflightHead = await runGitTask('resolve-head')
|
|
1574
1560
|
if (isResolvedHeadUsable(preflightHead?.sha)) {
|
|
1575
|
-
await resolveMergeConflicts(preflightHead.sha
|
|
1561
|
+
await resolveMergeConflicts(preflightHead.sha)
|
|
1576
1562
|
}
|
|
1577
1563
|
|
|
1578
1564
|
log('Reuse pass: scanning the full diff for certain, behaviorally identical, autonomously implementable reuse improvements before convergence')
|
|
1579
|
-
const reuseHeadResult = await
|
|
1565
|
+
const reuseHeadResult = await runGitTask('resolve-head')
|
|
1580
1566
|
const reuseHead = reuseHeadResult?.sha
|
|
1581
1567
|
if (isResolvedHeadUsable(reuseHead)) {
|
|
1582
|
-
await
|
|
1568
|
+
await runGitTask('prefetch-main')
|
|
1583
1569
|
const reuse = await runReuseAuditPass(reuseHead)
|
|
1584
1570
|
const reuseFindings = reuse?.findings || []
|
|
1585
1571
|
if (reuseFindings.length > 0) {
|
|
@@ -1595,19 +1581,17 @@ if (isResolvedHeadUsable(reuseHead)) {
|
|
|
1595
1581
|
log('Reuse pass: could not resolve HEAD — proceeding to convergence')
|
|
1596
1582
|
}
|
|
1597
1583
|
|
|
1598
|
-
const convergenceId = await spawnConvergenceCheckAgent()
|
|
1599
|
-
|
|
1600
1584
|
while (iterations < CONFIG.maxIterations) {
|
|
1601
1585
|
iterations += 1
|
|
1602
1586
|
if (phase === 'CONVERGE') {
|
|
1603
1587
|
rounds += 1
|
|
1604
|
-
const headResult = await
|
|
1588
|
+
const headResult = await runGitTask('resolve-head')
|
|
1605
1589
|
head = headResult?.sha
|
|
1606
1590
|
if (!isResolvedHeadUsable(head)) {
|
|
1607
1591
|
log(`Round ${rounds}: resolve-head agent returned no SHA — retrying without spawning lenses`)
|
|
1608
1592
|
continue
|
|
1609
1593
|
}
|
|
1610
|
-
await
|
|
1594
|
+
await runGitTask('prefetch-main')
|
|
1611
1595
|
log(`Round ${rounds}: parallel Bugbot + code-review + bug-audit on ${head?.slice(0, 7)}`)
|
|
1612
1596
|
const lenses = await parallel([
|
|
1613
1597
|
() => runBugbotLens(head),
|
|
@@ -1623,10 +1607,9 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1623
1607
|
const findings = roundOutcome.findings
|
|
1624
1608
|
if (isStandardsOnlyRound(findings)) {
|
|
1625
1609
|
log(`Round ${rounds}: ${findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the round as passed`)
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const auditResult = await resumeGeneralUtilityAgent(generalId, 'post-clean-audit', { head })
|
|
1610
|
+
const wasHardeningPrOpened = await openStandardsFollowUpOnce(head, findings, 'converge-round')
|
|
1611
|
+
standardsNote = standardsDeferralNote(findings.length, wasHardeningPrOpened)
|
|
1612
|
+
const auditResult = await runGeneralUtilityTask('post-clean-audit', { head })
|
|
1630
1613
|
if (!auditResult?.posted) {
|
|
1631
1614
|
blocker = cleanAuditBlocker(head, auditResult)
|
|
1632
1615
|
break
|
|
@@ -1652,8 +1635,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1652
1635
|
continue
|
|
1653
1636
|
}
|
|
1654
1637
|
log(`Round ${rounds}: all lenses clean on ${head?.slice(0, 7)} — posting clean audit artifact`)
|
|
1655
|
-
const
|
|
1656
|
-
const auditResult = await resumeGeneralUtilityAgent(cleanGeneralId, 'post-clean-audit', { head })
|
|
1638
|
+
const auditResult = await runGeneralUtilityTask('post-clean-audit', { head })
|
|
1657
1639
|
if (!auditResult?.posted) {
|
|
1658
1640
|
blocker = cleanAuditBlocker(head, auditResult)
|
|
1659
1641
|
break
|
|
@@ -1663,6 +1645,13 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1663
1645
|
}
|
|
1664
1646
|
|
|
1665
1647
|
if (phase === 'COPILOT') {
|
|
1648
|
+
if (input.copilotDisabled) {
|
|
1649
|
+
copilotDown = true
|
|
1650
|
+
copilotNote = 'Copilot was skipped by the quota pre-check (out of premium-request quota, unreachable, or unconfigured) — the Copilot gate was bypassed and the PR was marked ready without a Copilot review'
|
|
1651
|
+
log('Copilot gate: the quota pre-check reported Copilot unavailable — skipping the Copilot gate with no agent spawned and proceeding to mark-ready with the gate bypassed.')
|
|
1652
|
+
phase = 'FINALIZE'
|
|
1653
|
+
continue
|
|
1654
|
+
}
|
|
1666
1655
|
const copilot = await runCopilotGate(head)
|
|
1667
1656
|
const copilotOutcome = classifyCopilotOutcome(copilot)
|
|
1668
1657
|
copilotDown = resolveCopilotDown(copilotOutcome)
|
|
@@ -1681,8 +1670,8 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1681
1670
|
if (copilotOutcome.kind === 'fix') {
|
|
1682
1671
|
if (isStandardsOnlyRound(copilotOutcome.findings)) {
|
|
1683
1672
|
log(`Copilot raised ${copilotOutcome.findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
|
|
1684
|
-
const
|
|
1685
|
-
standardsNote = standardsDeferralNote(copilotOutcome.findings.length,
|
|
1673
|
+
const wasHardeningPrOpened = await openStandardsFollowUpOnce(head, copilotOutcome.findings, 'copilot')
|
|
1674
|
+
standardsNote = standardsDeferralNote(copilotOutcome.findings.length, wasHardeningPrOpened)
|
|
1686
1675
|
copilotDown = false
|
|
1687
1676
|
copilotNote = null
|
|
1688
1677
|
phase = 'FINALIZE'
|
|
@@ -1708,15 +1697,14 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1708
1697
|
}
|
|
1709
1698
|
|
|
1710
1699
|
if (phase === 'FINALIZE') {
|
|
1711
|
-
const convergence = await
|
|
1700
|
+
const convergence = await runConvergenceCheck({ bugbotDown, copilotDown })
|
|
1712
1701
|
const convergenceOutcome = classifyConvergenceOutcome(convergence)
|
|
1713
1702
|
if (convergenceOutcome.kind === 'retry') {
|
|
1714
1703
|
log('Convergence check agent died or returned no FAIL lines — re-running the check on the same HEAD')
|
|
1715
1704
|
continue
|
|
1716
1705
|
}
|
|
1717
1706
|
if (convergenceOutcome.kind === 'ready') {
|
|
1718
|
-
const
|
|
1719
|
-
const readyResult = await resumeGeneralUtilityAgent(finalGeneralId, 'mark-ready', { head, copilotDown })
|
|
1707
|
+
const readyResult = await runGeneralUtilityTask('mark-ready', { head, copilotDown })
|
|
1720
1708
|
const readyOutcome = classifyReadyOutcome(readyResult)
|
|
1721
1709
|
if (readyOutcome.converged) {
|
|
1722
1710
|
return { converged: true, rounds, finalSha: head, blocker: null, standardsNote, copilotNote, reuseNote }
|