claude-dev-env 1.79.0 → 1.80.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 +1 -0
- package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +7 -0
- package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -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/hooks/blocking/code_rules_enforcer.py +4 -0
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_unused_imports.py +7 -63
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +96 -15
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/hooks_constants/blocking_check_limits.py +1 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +14 -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 +1 -0
- package/skills/autoconverge/SKILL.md +26 -5
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +56 -120
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +45 -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 +110 -228
- 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,7 +16,7 @@ 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 three times, 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
|
}
|
|
@@ -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,21 @@ 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
|
-
* continues the same session. The spawn makes no edits — each resume carries the
|
|
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, hardening-commit,
|
|
204
|
+
* commit-recover, verify-recover). Each task carries its own edit instructions.
|
|
265
205
|
* @param {string} task the short task name
|
|
266
206
|
* @param {object} context task-specific context
|
|
267
|
-
* @returns {Promise<object>} the structured output
|
|
207
|
+
* @returns {Promise<object|string>} the structured output, or the transcript string when the 'hardening-commit' resume runs schema-less
|
|
268
208
|
*/
|
|
269
|
-
function
|
|
209
|
+
function runCodeEditorTask(task, context) {
|
|
270
210
|
const label = `code-editor:${task}`
|
|
271
211
|
if (task === 'fix-edit') {
|
|
272
212
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
@@ -287,7 +227,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
287
227
|
`- 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
228
|
`Always include a one-line summary.` +
|
|
289
229
|
PRE_COMMIT_GATE_STEP,
|
|
290
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
230
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
291
231
|
)
|
|
292
232
|
}
|
|
293
233
|
if (task === 'conflict-edit') {
|
|
@@ -298,7 +238,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
298
238
|
`- 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
239
|
`- 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
240
|
`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'
|
|
241
|
+
{ label, phase: 'Converge', schema: CONFLICT_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
302
242
|
)
|
|
303
243
|
}
|
|
304
244
|
if (task === 'repair-edit') {
|
|
@@ -318,7 +258,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
318
258
|
`- 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
259
|
`Always include a one-line summary.` +
|
|
320
260
|
PRE_COMMIT_GATE_STEP,
|
|
321
|
-
{ label, phase: 'Finalize', schema: REPAIR_EDIT_SCHEMA, agentType: 'clean-coder'
|
|
261
|
+
{ label, phase: 'Finalize', schema: REPAIR_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
322
262
|
)
|
|
323
263
|
}
|
|
324
264
|
if (task === 'repair-commit') {
|
|
@@ -334,7 +274,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
334
274
|
`- 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
275
|
`- 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
276
|
`- 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'
|
|
277
|
+
{ label, phase: 'Finalize', schema: FIX_SCHEMA, agentType: 'clean-coder' },
|
|
338
278
|
)
|
|
339
279
|
}
|
|
340
280
|
if (task === 'standards-edit') {
|
|
@@ -350,7 +290,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
350
290
|
`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
291
|
`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
292
|
PRE_COMMIT_GATE_STEP,
|
|
353
|
-
{ label, phase: 'Converge', schema: STANDARDS_EDIT_SCHEMA, agentType: 'clean-coder'
|
|
293
|
+
{ label, phase: 'Converge', schema: STANDARDS_EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
354
294
|
)
|
|
355
295
|
}
|
|
356
296
|
if (task === 'hardening-commit') {
|
|
@@ -360,7 +300,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
360
300
|
`- 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
301
|
`- 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
302
|
`Return a one-line summary naming the hardening PR URL.`,
|
|
363
|
-
{ label, phase: 'Converge', agentType: 'clean-coder'
|
|
303
|
+
{ label, phase: 'Converge', agentType: 'clean-coder' },
|
|
364
304
|
)
|
|
365
305
|
}
|
|
366
306
|
if (task === 'commit-recover') {
|
|
@@ -374,7 +314,7 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
374
314
|
`- 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
315
|
`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
316
|
PRE_COMMIT_GATE_STEP,
|
|
377
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
317
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
378
318
|
)
|
|
379
319
|
}
|
|
380
320
|
// verify-recover
|
|
@@ -389,34 +329,19 @@ function resumeCodeEditorAgent(agentId, task, context) {
|
|
|
389
329
|
`- 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
330
|
`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
331
|
PRE_COMMIT_GATE_STEP,
|
|
392
|
-
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder'
|
|
332
|
+
{ label, phase: 'Converge', schema: EDIT_SCHEMA, agentType: 'clean-coder' },
|
|
393
333
|
)
|
|
394
334
|
}
|
|
395
335
|
|
|
396
336
|
/**
|
|
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
|
|
337
|
+
* Spawn a fresh verifier code-verifier agent for a specific verify task
|
|
338
|
+
* (fix-verify, repair-verify, hardening-verify). The agent makes no edits —
|
|
339
|
+
* verification only — and ends its message with a fenced verdict block.
|
|
415
340
|
* @param {string} task the short task name
|
|
416
341
|
* @param {object} context task-specific context
|
|
417
342
|
* @returns {Promise<string>} the verifier transcript carrying the verdict fence
|
|
418
343
|
*/
|
|
419
|
-
function
|
|
344
|
+
function runVerifierTask(task, context) {
|
|
420
345
|
const label = `verifier:${task}`
|
|
421
346
|
if (task === 'fix-verify') {
|
|
422
347
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
@@ -427,7 +352,7 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
427
352
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
428
353
|
`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
354
|
`3. ${buildVerdictFenceSteps(input.owner, input.repo, input.prNumber)}`,
|
|
430
|
-
{ label, phase: 'Converge', agentType: 'code-verifier'
|
|
355
|
+
{ label, phase: 'Converge', agentType: 'code-verifier' },
|
|
431
356
|
)
|
|
432
357
|
}
|
|
433
358
|
if (task === 'repair-verify') {
|
|
@@ -441,7 +366,7 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
441
366
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
442
367
|
`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
368
|
`3. ${buildVerdictFenceSteps(input.owner, input.repo, input.prNumber)}`,
|
|
444
|
-
{ label, phase: 'Finalize', agentType: 'code-verifier'
|
|
369
|
+
{ label, phase: 'Finalize', agentType: 'code-verifier' },
|
|
445
370
|
)
|
|
446
371
|
}
|
|
447
372
|
return convergeAgent(
|
|
@@ -460,37 +385,20 @@ function resumeVerifierAgent(agentId, task, context) {
|
|
|
460
385
|
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
461
386
|
" ```\n" +
|
|
462
387
|
` 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'
|
|
388
|
+
{ label, phase: 'Converge', agentType: 'code-verifier' },
|
|
464
389
|
)
|
|
465
390
|
}
|
|
466
391
|
|
|
467
392
|
/**
|
|
468
|
-
* Spawn
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
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' },
|
|
480
|
-
)
|
|
481
|
-
return result?.agentId
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Resume the general-utility agent for one of its two administrative tasks:
|
|
486
|
-
* 'post-clean-audit' posts the terminal CLEAN bugteam review, and 'mark-ready'
|
|
487
|
-
* marks the PR ready and confirms it left draft state.
|
|
488
|
-
* @param {string} agentId the agent id from spawnGeneralUtilityAgent
|
|
393
|
+
* Spawn a fresh general-utility general-purpose agent for one of its two
|
|
394
|
+
* administrative tasks: 'post-clean-audit' posts the terminal CLEAN bugteam
|
|
395
|
+
* review, and 'mark-ready' marks the PR ready and confirms it left draft state.
|
|
396
|
+
* The agent edits no code.
|
|
489
397
|
* @param {'post-clean-audit'|'mark-ready'} task the short task name
|
|
490
398
|
* @param {object} context task-specific context
|
|
491
399
|
* @returns {Promise<object>} the task result
|
|
492
400
|
*/
|
|
493
|
-
function
|
|
401
|
+
function runGeneralUtilityTask(task, context) {
|
|
494
402
|
const label = `general-utility:${task}`
|
|
495
403
|
if (task === 'post-clean-audit') {
|
|
496
404
|
return convergeAgent(
|
|
@@ -499,7 +407,7 @@ function resumeGeneralUtilityAgent(agentId, task, context) {
|
|
|
499
407
|
`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
408
|
`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
409
|
`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'
|
|
410
|
+
{ label, phase: 'Converge', schema: CLEAN_AUDIT_SCHEMA, agentType: 'general-purpose' },
|
|
503
411
|
)
|
|
504
412
|
}
|
|
505
413
|
if (task === 'mark-ready') {
|
|
@@ -512,35 +420,18 @@ function resumeGeneralUtilityAgent(agentId, task, context) {
|
|
|
512
420
|
`1. Run: gh pr ready ${input.prNumber} --repo ${input.owner}/${input.repo}\n` +
|
|
513
421
|
`2. Re-query the draft state: gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .draft\n` +
|
|
514
422
|
`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'
|
|
423
|
+
{ label, phase: 'Finalize', schema: READY_SCHEMA, agentType: 'general-purpose' },
|
|
516
424
|
)
|
|
517
425
|
}
|
|
518
|
-
throw new Error(`
|
|
426
|
+
throw new Error(`runGeneralUtilityTask: unknown task ${task}`)
|
|
519
427
|
}
|
|
520
428
|
|
|
521
429
|
/**
|
|
522
|
-
* Spawn
|
|
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
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Resume the convergence-check agent for the convergence check.
|
|
539
|
-
* @param {string} agentId the agent id from spawnConvergenceCheckAgent
|
|
430
|
+
* Spawn a fresh convergence-check Explore agent for the convergence check.
|
|
540
431
|
* @param {object} context carries bugbotDown and copilotDown
|
|
541
432
|
* @returns {Promise<object>} CONVERGENCE_SCHEMA result
|
|
542
433
|
*/
|
|
543
|
-
function
|
|
434
|
+
function runConvergenceCheck(context) {
|
|
544
435
|
const label = 'check-convergence'
|
|
545
436
|
const bugbotDownFlag = context.bugbotDown ? ' --bugbot-down' : ''
|
|
546
437
|
const copilotDownFlag = context.copilotDown ? ' --copilot-down' : ''
|
|
@@ -550,7 +441,7 @@ function resumeConvergenceCheckAgent(agentId, context) {
|
|
|
550
441
|
`Exit 0 -> every gate passed: return {pass:true, failures:[]}.\n` +
|
|
551
442
|
`Exit 1 -> return {pass:false, failures:[<each printed FAIL line verbatim>]}.\n` +
|
|
552
443
|
`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'
|
|
444
|
+
{ label, phase: 'Finalize', schema: CONVERGENCE_SCHEMA, agentType: 'Explore' },
|
|
554
445
|
)
|
|
555
446
|
}
|
|
556
447
|
|
|
@@ -1029,7 +920,7 @@ function commitNeedsCodeRecovery(commitResult) {
|
|
|
1029
920
|
* resolve-head agent or a malformed result yields a falsy SHA; spawning lenses
|
|
1030
921
|
* against it interpolates the literal string 'HEAD undefined' into their prompts
|
|
1031
922
|
* and produces a spurious clean verdict on a non-existent commit.
|
|
1032
|
-
* @param {string|null|undefined} resolvedHead the SHA from the git-utility
|
|
923
|
+
* @param {string|null|undefined} resolvedHead the SHA from the git-utility 'resolve-head' task
|
|
1033
924
|
* @returns {boolean} true only when the SHA is a non-empty string
|
|
1034
925
|
*/
|
|
1035
926
|
function isResolvedHeadUsable(resolvedHead) {
|
|
@@ -1042,7 +933,7 @@ function isResolvedHeadUsable(resolvedHead) {
|
|
|
1042
933
|
* not-conflicting so the run proceeds straight to the bug checks rather than
|
|
1043
934
|
* force-pushing a rebase on a verdict that does not exist — a transient check
|
|
1044
935
|
* failure must never trigger a destructive rebase.
|
|
1045
|
-
* @param {object|null|undefined} mergeState the git-utility
|
|
936
|
+
* @param {object|null|undefined} mergeState the git-utility 'check-merge-conflicts' task result
|
|
1046
937
|
* @returns {boolean} true only when the check reported conflicting:true
|
|
1047
938
|
*/
|
|
1048
939
|
function isMergeConflicting(mergeState) {
|
|
@@ -1333,10 +1224,10 @@ async function verifyWithRecovery({ runVerify, runRecoverEdit }) {
|
|
|
1333
1224
|
/**
|
|
1334
1225
|
* Fix lens: edit (clean-coder, no commit) -> verify (a separate code-verifier
|
|
1335
1226
|
* 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
|
-
*
|
|
1227
|
+
* commit + push, no edits). The verifier is a distinct agent from the fixer, so
|
|
1228
|
+
* the verdict that gates the commit comes from a different agent than the one
|
|
1229
|
+
* that edits and pushes — the same editor/verifier separation the repair and
|
|
1230
|
+
* conflict paths use, and the separation a workflow code-verifier needs to
|
|
1340
1231
|
* produce the verdict the verified-commit gate requires, which the SubagentStop
|
|
1341
1232
|
* minter cannot mint for workflow-spawned agents. When verification fails (or the
|
|
1342
1233
|
* edit step stalled with no thread to resolve), the commit step is skipped and the
|
|
@@ -1347,8 +1238,7 @@ async function verifyWithRecovery({ runVerify, runRecoverEdit }) {
|
|
|
1347
1238
|
* @returns {Promise<object>} FIX_SCHEMA result
|
|
1348
1239
|
*/
|
|
1349
1240
|
async function applyFixes(head, findings, sourceLabel) {
|
|
1350
|
-
const
|
|
1351
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'fix-edit', { head, findings, sourceLabel })
|
|
1241
|
+
const editResult = await runCodeEditorTask('fix-edit', { head, findings, sourceLabel })
|
|
1352
1242
|
if (editResult?.resolvedWithoutCommit === true && editResult?.edited !== true) {
|
|
1353
1243
|
return {
|
|
1354
1244
|
newSha: head,
|
|
@@ -1359,9 +1249,7 @@ async function applyFixes(head, findings, sourceLabel) {
|
|
|
1359
1249
|
blockerDetail: '',
|
|
1360
1250
|
}
|
|
1361
1251
|
}
|
|
1362
|
-
|
|
1363
|
-
const verifierId = await spawnVerifierAgent()
|
|
1364
|
-
return fixerWithRecovery(fixerAgentId, verifierId, head, findings, sourceLabel)
|
|
1252
|
+
return fixerWithRecovery(head, findings, sourceLabel)
|
|
1365
1253
|
}
|
|
1366
1254
|
|
|
1367
1255
|
/**
|
|
@@ -1370,7 +1258,7 @@ async function applyFixes(head, findings, sourceLabel) {
|
|
|
1370
1258
|
* post stops the run with an actionable message rather than re-converging until
|
|
1371
1259
|
* the iteration cap. Handles a dead post agent (a null result) as not posted.
|
|
1372
1260
|
* @param {string} head converged PR HEAD SHA
|
|
1373
|
-
* @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit
|
|
1261
|
+
* @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit task, or null when the agent died
|
|
1374
1262
|
* @returns {string} the blocker message naming the post failure and the unblock path
|
|
1375
1263
|
*/
|
|
1376
1264
|
function cleanAuditBlocker(head, auditResult) {
|
|
@@ -1423,8 +1311,7 @@ function runCopilotGate(head) {
|
|
|
1423
1311
|
* @returns {Promise<object>} FIX_SCHEMA result
|
|
1424
1312
|
*/
|
|
1425
1313
|
async function repairConvergence(head, failures) {
|
|
1426
|
-
const
|
|
1427
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'repair-edit', { head, failures })
|
|
1314
|
+
const editResult = await runCodeEditorTask('repair-edit', { head, failures })
|
|
1428
1315
|
const hasPushWork = editResult?.edited === true || editResult?.rebased === true
|
|
1429
1316
|
if (!hasPushWork) {
|
|
1430
1317
|
return {
|
|
@@ -1436,10 +1323,9 @@ async function repairConvergence(head, failures) {
|
|
|
1436
1323
|
blockerDetail: '',
|
|
1437
1324
|
}
|
|
1438
1325
|
}
|
|
1439
|
-
const verifierId = await spawnVerifierAgent()
|
|
1440
1326
|
const verifyTranscript = await verifyWithRecovery({
|
|
1441
|
-
runVerify: () =>
|
|
1442
|
-
runRecoverEdit: (objection, attempt) =>
|
|
1327
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1328
|
+
runRecoverEdit: (objection, attempt) => runCodeEditorTask('verify-recover', { head, sourceLabel: 'repair', objection, attempt }),
|
|
1443
1329
|
})
|
|
1444
1330
|
if (!verdictPassed(verifyTranscript)) {
|
|
1445
1331
|
return {
|
|
@@ -1453,9 +1339,9 @@ async function repairConvergence(head, failures) {
|
|
|
1453
1339
|
}
|
|
1454
1340
|
const wasRebased = editResult?.rebased === true
|
|
1455
1341
|
return commitWithRecovery({
|
|
1456
|
-
runCommit: () =>
|
|
1457
|
-
runVerify: () =>
|
|
1458
|
-
runRecoverEdit: (detail, attempt) =>
|
|
1342
|
+
runCommit: () => runCodeEditorTask('repair-commit', { head, wasRebased }),
|
|
1343
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1344
|
+
runRecoverEdit: (detail, attempt) => runCodeEditorTask('commit-recover', { head, sourceLabel: 'repair', blockerDetail: detail, attempt }),
|
|
1459
1345
|
})
|
|
1460
1346
|
}
|
|
1461
1347
|
|
|
@@ -1473,24 +1359,22 @@ async function repairConvergence(head, failures) {
|
|
|
1473
1359
|
* @param {string} head PR HEAD SHA before any rebase
|
|
1474
1360
|
* @returns {Promise<string>} the HEAD SHA after a successful rebase push, or the unchanged head
|
|
1475
1361
|
*/
|
|
1476
|
-
async function resolveMergeConflicts(head
|
|
1477
|
-
const mergeState = await
|
|
1362
|
+
async function resolveMergeConflicts(head) {
|
|
1363
|
+
const mergeState = await runGitTask('check-merge-conflicts', head)
|
|
1478
1364
|
if (!isMergeConflicting(mergeState)) return head
|
|
1479
1365
|
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 })
|
|
1366
|
+
const editResult = await runCodeEditorTask('conflict-edit', { head })
|
|
1482
1367
|
if (editResult?.rebased !== true) return head
|
|
1483
1368
|
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
1369
|
const verifyTranscript = await verifyWithRecovery({
|
|
1486
|
-
runVerify: () =>
|
|
1487
|
-
runRecoverEdit: (objection, attempt) =>
|
|
1370
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1371
|
+
runRecoverEdit: (objection, attempt) => runCodeEditorTask('verify-recover', { head, sourceLabel: 'conflict-rebase', objection, attempt }),
|
|
1488
1372
|
})
|
|
1489
1373
|
if (!verdictPassed(verifyTranscript)) return head
|
|
1490
1374
|
const commitResult = await commitWithRecovery({
|
|
1491
|
-
runCommit: () =>
|
|
1492
|
-
runVerify: () =>
|
|
1493
|
-
runRecoverEdit: (detail, attempt) =>
|
|
1375
|
+
runCommit: () => runCodeEditorTask('repair-commit', { head, wasRebased: true }),
|
|
1376
|
+
runVerify: () => runVerifierTask('repair-verify', { head, failures }),
|
|
1377
|
+
runRecoverEdit: (detail, attempt) => runCodeEditorTask('commit-recover', { head, sourceLabel: 'conflict-rebase', blockerDetail: detail, attempt }),
|
|
1494
1378
|
})
|
|
1495
1379
|
return commitResult?.newSha || head
|
|
1496
1380
|
}
|
|
@@ -1539,19 +1423,17 @@ function standardsDeferralNote(findingsCount, hardeningPrOpened) {
|
|
|
1539
1423
|
* @returns {Promise<object>} `{ hardeningPrOpened }` — true only when the hardening PR was opened this round
|
|
1540
1424
|
*/
|
|
1541
1425
|
async function spawnStandardsFollowUp(head, findings, sourceLabel) {
|
|
1542
|
-
const
|
|
1543
|
-
const editResult = await resumeCodeEditorAgent(codeEditorId, 'standards-edit', { head, findings, sourceLabel })
|
|
1426
|
+
const editResult = await runCodeEditorTask('standards-edit', { head, findings, sourceLabel })
|
|
1544
1427
|
if (editResult?.hardeningEdited !== true || !editResult?.hardeningRepoPath) {
|
|
1545
1428
|
return { hardeningPrOpened: false }
|
|
1546
1429
|
}
|
|
1547
|
-
const
|
|
1548
|
-
const verifyTranscript = await resumeVerifierAgent(verifierId, 'hardening-verify', {
|
|
1430
|
+
const verifyTranscript = await runVerifierTask('hardening-verify', {
|
|
1549
1431
|
head, sourceLabel, hardeningRepoPath: editResult.hardeningRepoPath, hardeningBranch: editResult.hardeningBranch,
|
|
1550
1432
|
})
|
|
1551
1433
|
if (!verdictPassed(verifyTranscript)) {
|
|
1552
1434
|
return { hardeningPrOpened: false }
|
|
1553
1435
|
}
|
|
1554
|
-
await
|
|
1436
|
+
await runCodeEditorTask('hardening-commit', {
|
|
1555
1437
|
head, sourceLabel, hardeningRepoPath: editResult.hardeningRepoPath, hardeningBranch: editResult.hardeningBranch, issueUrl: editResult.issueUrl,
|
|
1556
1438
|
})
|
|
1557
1439
|
return { hardeningPrOpened: true }
|
|
@@ -1563,23 +1445,21 @@ let rounds = 0
|
|
|
1563
1445
|
let iterations = 0
|
|
1564
1446
|
let blocker = null
|
|
1565
1447
|
let bugbotDown = input.bugbotDisabled || false
|
|
1566
|
-
let copilotDown = false
|
|
1448
|
+
let copilotDown = input.copilotDisabled || false
|
|
1567
1449
|
let copilotNote = null
|
|
1568
1450
|
let standardsNote = null
|
|
1569
1451
|
let reuseNote = null
|
|
1570
1452
|
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
const preflightHead = await resumeGitAgent(gitAgentId, 'resolve-head')
|
|
1453
|
+
const preflightHead = await runGitTask('resolve-head')
|
|
1574
1454
|
if (isResolvedHeadUsable(preflightHead?.sha)) {
|
|
1575
|
-
await resolveMergeConflicts(preflightHead.sha
|
|
1455
|
+
await resolveMergeConflicts(preflightHead.sha)
|
|
1576
1456
|
}
|
|
1577
1457
|
|
|
1578
1458
|
log('Reuse pass: scanning the full diff for certain, behaviorally identical, autonomously implementable reuse improvements before convergence')
|
|
1579
|
-
const reuseHeadResult = await
|
|
1459
|
+
const reuseHeadResult = await runGitTask('resolve-head')
|
|
1580
1460
|
const reuseHead = reuseHeadResult?.sha
|
|
1581
1461
|
if (isResolvedHeadUsable(reuseHead)) {
|
|
1582
|
-
await
|
|
1462
|
+
await runGitTask('prefetch-main')
|
|
1583
1463
|
const reuse = await runReuseAuditPass(reuseHead)
|
|
1584
1464
|
const reuseFindings = reuse?.findings || []
|
|
1585
1465
|
if (reuseFindings.length > 0) {
|
|
@@ -1595,19 +1475,17 @@ if (isResolvedHeadUsable(reuseHead)) {
|
|
|
1595
1475
|
log('Reuse pass: could not resolve HEAD — proceeding to convergence')
|
|
1596
1476
|
}
|
|
1597
1477
|
|
|
1598
|
-
const convergenceId = await spawnConvergenceCheckAgent()
|
|
1599
|
-
|
|
1600
1478
|
while (iterations < CONFIG.maxIterations) {
|
|
1601
1479
|
iterations += 1
|
|
1602
1480
|
if (phase === 'CONVERGE') {
|
|
1603
1481
|
rounds += 1
|
|
1604
|
-
const headResult = await
|
|
1482
|
+
const headResult = await runGitTask('resolve-head')
|
|
1605
1483
|
head = headResult?.sha
|
|
1606
1484
|
if (!isResolvedHeadUsable(head)) {
|
|
1607
1485
|
log(`Round ${rounds}: resolve-head agent returned no SHA — retrying without spawning lenses`)
|
|
1608
1486
|
continue
|
|
1609
1487
|
}
|
|
1610
|
-
await
|
|
1488
|
+
await runGitTask('prefetch-main')
|
|
1611
1489
|
log(`Round ${rounds}: parallel Bugbot + code-review + bug-audit on ${head?.slice(0, 7)}`)
|
|
1612
1490
|
const lenses = await parallel([
|
|
1613
1491
|
() => runBugbotLens(head),
|
|
@@ -1623,10 +1501,9 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1623
1501
|
const findings = roundOutcome.findings
|
|
1624
1502
|
if (isStandardsOnlyRound(findings)) {
|
|
1625
1503
|
log(`Round ${rounds}: ${findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the round as passed`)
|
|
1626
|
-
const generalId = await spawnGeneralUtilityAgent()
|
|
1627
1504
|
const standardsOutcome = await spawnStandardsFollowUp(head, findings, 'converge-round')
|
|
1628
1505
|
standardsNote = standardsDeferralNote(findings.length, standardsOutcome?.hardeningPrOpened === true)
|
|
1629
|
-
const auditResult = await
|
|
1506
|
+
const auditResult = await runGeneralUtilityTask('post-clean-audit', { head })
|
|
1630
1507
|
if (!auditResult?.posted) {
|
|
1631
1508
|
blocker = cleanAuditBlocker(head, auditResult)
|
|
1632
1509
|
break
|
|
@@ -1652,8 +1529,7 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1652
1529
|
continue
|
|
1653
1530
|
}
|
|
1654
1531
|
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 })
|
|
1532
|
+
const auditResult = await runGeneralUtilityTask('post-clean-audit', { head })
|
|
1657
1533
|
if (!auditResult?.posted) {
|
|
1658
1534
|
blocker = cleanAuditBlocker(head, auditResult)
|
|
1659
1535
|
break
|
|
@@ -1663,6 +1539,13 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1663
1539
|
}
|
|
1664
1540
|
|
|
1665
1541
|
if (phase === 'COPILOT') {
|
|
1542
|
+
if (input.copilotDisabled) {
|
|
1543
|
+
copilotDown = true
|
|
1544
|
+
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'
|
|
1545
|
+
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.')
|
|
1546
|
+
phase = 'FINALIZE'
|
|
1547
|
+
continue
|
|
1548
|
+
}
|
|
1666
1549
|
const copilot = await runCopilotGate(head)
|
|
1667
1550
|
const copilotOutcome = classifyCopilotOutcome(copilot)
|
|
1668
1551
|
copilotDown = resolveCopilotDown(copilotOutcome)
|
|
@@ -1708,15 +1591,14 @@ while (iterations < CONFIG.maxIterations) {
|
|
|
1708
1591
|
}
|
|
1709
1592
|
|
|
1710
1593
|
if (phase === 'FINALIZE') {
|
|
1711
|
-
const convergence = await
|
|
1594
|
+
const convergence = await runConvergenceCheck({ bugbotDown, copilotDown })
|
|
1712
1595
|
const convergenceOutcome = classifyConvergenceOutcome(convergence)
|
|
1713
1596
|
if (convergenceOutcome.kind === 'retry') {
|
|
1714
1597
|
log('Convergence check agent died or returned no FAIL lines — re-running the check on the same HEAD')
|
|
1715
1598
|
continue
|
|
1716
1599
|
}
|
|
1717
1600
|
if (convergenceOutcome.kind === 'ready') {
|
|
1718
|
-
const
|
|
1719
|
-
const readyResult = await resumeGeneralUtilityAgent(finalGeneralId, 'mark-ready', { head, copilotDown })
|
|
1601
|
+
const readyResult = await runGeneralUtilityTask('mark-ready', { head, copilotDown })
|
|
1720
1602
|
const readyOutcome = classifyReadyOutcome(readyResult)
|
|
1721
1603
|
if (readyOutcome.converged) {
|
|
1722
1604
|
return { converged: true, rounds, finalSha: head, blocker: null, standardsNote, copilotNote, reuseNote }
|