claude-dev-env 1.74.0 → 1.76.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/code_rules_gate.py +60 -5
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/inline_duplicate_body_span_constants.py +22 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +147 -3
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +3 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +170 -20
- package/hooks/blocking/code_rules_duplicate_body.py +378 -26
- package/hooks/blocking/code_rules_enforcer.py +36 -5
- package/hooks/blocking/code_rules_imports_logging.py +679 -1
- package/hooks/blocking/code_rules_shared.py +8 -5
- package/hooks/blocking/code_rules_test_assertions.py +6 -7
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +2 -1
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +155 -0
- package/hooks/blocking/hedging_language_blocker.py +1 -13
- package/hooks/blocking/intent_only_ending_blocker.py +1 -15
- package/hooks/blocking/pre_tool_use_dispatcher.py +4 -5
- package/hooks/blocking/question_to_user_enforcer.py +1 -11
- package/hooks/blocking/session_handoff_blocker.py +1 -15
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +484 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +157 -0
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +466 -0
- package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +11 -9
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +758 -0
- package/hooks/blocking/test_code_rules_logging_printf_tokens.py +134 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +16 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +328 -0
- package/hooks/blocking/test_hedging_language_blocker.py +6 -0
- package/hooks/blocking/test_intent_only_ending_blocker.py +5 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +52 -5
- package/hooks/blocking/test_question_to_user_enforcer.py +6 -0
- package/hooks/blocking/test_session_handoff_blocker.py +6 -0
- package/hooks/blocking/test_verification_verdict_store.py +66 -1
- package/hooks/blocking/test_verifier_verdict_minter.py +64 -5
- package/hooks/blocking/verification_verdict_store.py +19 -5
- package/hooks/blocking/verifier_verdict_minter.py +18 -15
- package/hooks/hooks_constants/CLAUDE.md +4 -1
- package/hooks/hooks_constants/blocking_check_limits.py +30 -1
- package/hooks/hooks_constants/claude_md_orphan_file_blocker_constants.py +52 -24
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -1
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +2 -1
- package/hooks/hooks_constants/duplicate_function_body_constants.py +21 -5
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +27 -0
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +2 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +8 -2
- package/hooks/hooks_constants/test_post_tool_use_dispatcher_constants.py +43 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +99 -0
- package/hooks/hooks_constants/test_text_stripping.py +39 -0
- package/hooks/hooks_constants/text_stripping.py +36 -0
- package/hooks/validation/CLAUDE.md +1 -0
- package/hooks/validation/post_tool_use_dispatcher.py +2 -2
- package/hooks/validation/test_mypy_validator.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +6 -0
- package/hooks/workflow/auto_formatter.py +8 -5
- package/hooks/workflow/test_auto_formatter.py +33 -0
- package/package.json +1 -1
- package/rules/claude-md-orphan-file.md +7 -8
- package/rules/docstring-prose-matches-implementation.md +1 -0
- package/rules/package-inventory-stale-entry.md +8 -0
- package/rules/windows-filesystem-safe.md +2 -0
- package/skills/anthropic-plan/CLAUDE.md +1 -1
- package/skills/anthropic-plan/SKILL.md +15 -2
- package/skills/autoconverge/SKILL.md +6 -3
- package/skills/autoconverge/reference/stop-conditions.md +7 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +5 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +306 -137
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +16 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +107 -44
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -24
- package/skills/autoconverge/workflow/converge.mjs +581 -613
- package/skills/autoconverge/workflow/convergence_summary.py +1 -1
- package/skills/autoconverge/workflow/render_report.py +2 -6
- package/skills/autoconverge/workflow/test_convergence_summary.py +17 -0
- package/skills/autoconverge/workflow/test_render_report.py +1 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +58 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +9 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +42 -0
|
@@ -129,12 +129,12 @@ test('a Copilot no-show after the poll cap returns a down result rather than a b
|
|
|
129
129
|
);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
test('
|
|
133
|
-
const checkConvergenceBody = functionBody('
|
|
132
|
+
test('resumeConvergenceCheckAgent wires the --copilot-down flag from the copilotDown context', () => {
|
|
133
|
+
const checkConvergenceBody = functionBody('resumeConvergenceCheckAgent');
|
|
134
134
|
assert.match(
|
|
135
135
|
checkConvergenceBody,
|
|
136
|
-
/copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
-
'expected
|
|
136
|
+
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
+
'expected resumeConvergenceCheckAgent to append --copilot-down when copilotDown is set',
|
|
138
138
|
);
|
|
139
139
|
assert.match(
|
|
140
140
|
checkConvergenceBody,
|
|
@@ -195,7 +195,7 @@ test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', (
|
|
|
195
195
|
-1,
|
|
196
196
|
'expected the COPILOT phase to handle a standards-only Copilot fix outcome',
|
|
197
197
|
);
|
|
198
|
-
const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart +
|
|
198
|
+
const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart + 800);
|
|
199
199
|
const resetIndex = standardsBranch.indexOf('copilotDown = false');
|
|
200
200
|
const finalizeIndex = standardsBranch.indexOf("phase = 'FINALIZE'");
|
|
201
201
|
assert.notEqual(
|
|
@@ -234,32 +234,32 @@ test('the COPILOT phase recomputes copilotDown from each gate outcome via resolv
|
|
|
234
234
|
test('markReady receives copilotDown so it can opt the unflagged hook out of the Copilot gate', () => {
|
|
235
235
|
const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
|
|
236
236
|
assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
|
|
237
|
-
const markReadyCall = convergeSource.indexOf('
|
|
238
|
-
assert.notEqual(markReadyCall, -1, 'expected the FINALIZE phase to
|
|
239
|
-
const callSlice = convergeSource.slice(markReadyCall, markReadyCall +
|
|
237
|
+
const markReadyCall = convergeSource.indexOf("'mark-ready'", finalizeStart);
|
|
238
|
+
assert.notEqual(markReadyCall, -1, 'expected the FINALIZE phase to route mark-ready through the general-utility agent');
|
|
239
|
+
const callSlice = convergeSource.slice(markReadyCall - 20, markReadyCall + 60);
|
|
240
240
|
assert.match(
|
|
241
241
|
callSlice,
|
|
242
|
-
/
|
|
243
|
-
'expected
|
|
242
|
+
/copilotDown/,
|
|
243
|
+
'expected mark-ready context to include copilotDown so the agent can opt the unflagged hook out of the Copilot gate',
|
|
244
244
|
);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
test('the
|
|
248
|
-
const markReadyBody = functionBody('
|
|
247
|
+
test('the mark-ready task in resumeGeneralUtilityAgent opts the unflagged convergence hook out of Copilot when copilotDown', () => {
|
|
248
|
+
const markReadyBody = functionBody('resumeGeneralUtilityAgent');
|
|
249
249
|
assert.match(
|
|
250
250
|
markReadyBody,
|
|
251
|
-
/copilotDown/,
|
|
252
|
-
'expected
|
|
251
|
+
/context\.copilotDown/,
|
|
252
|
+
'expected the mark-ready task to branch on copilotDown',
|
|
253
253
|
);
|
|
254
254
|
assert.match(
|
|
255
255
|
markReadyBody,
|
|
256
256
|
/CLAUDE_REVIEWS_DISABLED/,
|
|
257
|
-
'expected the
|
|
257
|
+
'expected the mark-ready prompt to set CLAUDE_REVIEWS_DISABLED so the unflagged hook re-derives the Copilot bypass',
|
|
258
258
|
);
|
|
259
259
|
assert.match(
|
|
260
260
|
markReadyBody,
|
|
261
261
|
/copilot/,
|
|
262
|
-
'expected the
|
|
262
|
+
'expected the mark-ready opt-out to name the copilot token',
|
|
263
263
|
);
|
|
264
264
|
});
|
|
265
265
|
|
|
@@ -99,30 +99,28 @@ test('FIX_RECOVERY_MAX_ATTEMPTS is declared and bounds the recovery loop at 2',
|
|
|
99
99
|
assert.match(constantLine('FIX_RECOVERY_MAX_ATTEMPTS'), /=\s*2\s*$/);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
}
|
|
102
|
+
test('the commit path in resumeCodeEditorAgent separates an edit-requiring block from a transient failure', () => {
|
|
103
|
+
const commitBody = functionSource('resumeCodeEditorAgent');
|
|
104
|
+
assert.match(commitBody, /blockedNeedingEdit/, 'expected the edit-block flag to be set in the prompt');
|
|
105
|
+
assert.match(commitBody, /blockerDetail/, 'expected the verbatim blocker detail to be requested');
|
|
106
|
+
assert.match(
|
|
107
|
+
commitBody,
|
|
108
|
+
/code_rules_gate|CODE_RULES/,
|
|
109
|
+
'expected the commit prompt to name the CODE_RULES commit gate as an edit-requiring block',
|
|
110
|
+
);
|
|
111
|
+
assert.match(
|
|
112
|
+
commitBody,
|
|
113
|
+
/transient/i,
|
|
114
|
+
'expected the commit prompt to name the transient (non-code) failure case',
|
|
115
|
+
);
|
|
116
|
+
});
|
|
119
117
|
|
|
120
|
-
test('
|
|
121
|
-
const recoverBody = functionSource('
|
|
118
|
+
test('the commit-recover task in resumeCodeEditorAgent is a clean-coder edit step bound to the blocker detail and leaves changes uncommitted', () => {
|
|
119
|
+
const recoverBody = functionSource('resumeCodeEditorAgent');
|
|
122
120
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
123
121
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
124
|
-
assert.match(recoverBody, /
|
|
125
|
-
assert.match(recoverBody, /blockerDetail/, 'expected the fixer prompt to consume the blocker detail');
|
|
122
|
+
assert.match(recoverBody, /task === 'commit-recover'/, 'expected the commit-recover task branch');
|
|
123
|
+
assert.match(recoverBody, /context\.blockerDetail/, 'expected the fixer prompt to consume the blocker detail');
|
|
126
124
|
assert.match(
|
|
127
125
|
recoverBody,
|
|
128
126
|
/only the (?:violation|finding|block)/i,
|
|
@@ -163,20 +161,88 @@ test('commitWithRecovery bounds the loop, re-verifies, and retries the commit on
|
|
|
163
161
|
);
|
|
164
162
|
});
|
|
165
163
|
|
|
166
|
-
test('applyFixes routes
|
|
164
|
+
test('applyFixes routes through spawnFixerAgent and fixerWithRecovery', () => {
|
|
165
|
+
const applyFixesBody = functionSource('applyFixes');
|
|
166
|
+
assert.match(applyFixesBody, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
|
|
167
|
+
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('applyFixes spawns a separate verifier and passes both ids into fixerWithRecovery', () => {
|
|
167
171
|
const applyFixesBody = functionSource('applyFixes');
|
|
168
|
-
assert.match(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
assert.match(
|
|
173
|
+
applyFixesBody,
|
|
174
|
+
/spawnVerifierAgent\(/,
|
|
175
|
+
'expected applyFixes to spawn a separate verifier agent so the fix-path verdict is independent of the fixer',
|
|
176
|
+
);
|
|
177
|
+
const fixerSpawnIndex = applyFixesBody.search(/spawnFixerAgent\(/);
|
|
178
|
+
const verifierSpawnIndex = applyFixesBody.search(/spawnVerifierAgent\(/);
|
|
179
|
+
assert.notEqual(fixerSpawnIndex, -1, 'expected applyFixes to spawn the fixer');
|
|
180
|
+
assert.notEqual(verifierSpawnIndex, -1, 'expected applyFixes to spawn the verifier');
|
|
181
|
+
assert.match(
|
|
182
|
+
applyFixesBody,
|
|
183
|
+
/fixerWithRecovery\(\s*fixerAgentId,\s*verifierId,/,
|
|
184
|
+
'expected applyFixes to pass both the fixer and verifier ids into fixerWithRecovery',
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('fixerWithRecovery runs verify on the verifier id and edits/commits on the fixer id', () => {
|
|
189
|
+
const recoveryBody = functionSource('fixerWithRecovery');
|
|
190
|
+
assert.match(
|
|
191
|
+
recoveryBody,
|
|
192
|
+
/resumeVerifierAgent\(\s*verifierId,/,
|
|
193
|
+
'expected fixerWithRecovery to resume the separate verifier for the verify step',
|
|
194
|
+
);
|
|
195
|
+
assert.match(
|
|
196
|
+
recoveryBody,
|
|
197
|
+
/resumeFixerAgent\(\s*fixerAgentId,\s*'commit'/,
|
|
198
|
+
'expected fixerWithRecovery to resume the fixer for the commit step',
|
|
199
|
+
);
|
|
200
|
+
assert.doesNotMatch(
|
|
201
|
+
recoveryBody,
|
|
202
|
+
/resumeFixerAgent\(\s*fixerAgentId,\s*'(?:verify-commit|fix-verify)'/,
|
|
203
|
+
'expected the verify step never to resume the fixer session — the verifier must grade a different session than the one that edits',
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('the fix-path verify resumes a different agent id than the fix-path edits, mirroring the repair path', () => {
|
|
208
|
+
const recoveryBody = functionSource('fixerWithRecovery');
|
|
209
|
+
const verifyResumeMatch = /resumeVerifierAgent\(\s*(\w+),/.exec(recoveryBody);
|
|
210
|
+
const editResumeMatch = /resumeFixerAgent\(\s*(\w+),/.exec(recoveryBody);
|
|
211
|
+
assert.ok(verifyResumeMatch, 'expected a verify resume call naming its agent id');
|
|
212
|
+
assert.ok(editResumeMatch, 'expected an edit/commit resume call naming its agent id');
|
|
213
|
+
assert.notEqual(
|
|
214
|
+
verifyResumeMatch[1],
|
|
215
|
+
editResumeMatch[1],
|
|
216
|
+
'expected the verify step to resume a different agent id than the edit/commit step',
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('the verifier carries a fix-verify task so the fix path verdict comes from the verifier group', () => {
|
|
221
|
+
const verifierBody = functionSource('resumeVerifierAgent');
|
|
222
|
+
assert.match(verifierBody, /task === 'fix-verify'/, 'expected resumeVerifierAgent to handle the fix-verify task');
|
|
223
|
+
assert.match(verifierBody, /buildVerdictFenceSteps\(/, 'expected the fix-verify task to emit a verdict fence');
|
|
224
|
+
assert.match(verifierBody, /agentType:\s*'code-verifier'/, 'expected the fix-verify task to run as code-verifier');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('resumeFixerAgent no longer verifies its own edits — no code-verifier verify-commit task remains', () => {
|
|
228
|
+
const fixerBody = functionSource('resumeFixerAgent');
|
|
229
|
+
assert.doesNotMatch(
|
|
230
|
+
fixerBody,
|
|
231
|
+
/task === 'verify-commit'/,
|
|
232
|
+
'expected the fixer to no longer carry the verify-commit task that graded its own session',
|
|
233
|
+
);
|
|
234
|
+
assert.doesNotMatch(
|
|
235
|
+
fixerBody,
|
|
236
|
+
/agentType:\s*'code-verifier'/,
|
|
237
|
+
'expected the fixer session to be clean-coder only — a separate verifier grades the working tree',
|
|
238
|
+
);
|
|
172
239
|
});
|
|
173
240
|
|
|
174
241
|
test('repairConvergence routes its commit through commitWithRecovery wired to the repair-path steps', () => {
|
|
175
242
|
const repairBody = functionSource('repairConvergence');
|
|
176
243
|
assert.match(repairBody, /commitWithRecovery\(/, 'expected repairConvergence to call commitWithRecovery');
|
|
177
|
-
assert.match(repairBody, /
|
|
178
|
-
assert.match(repairBody, /
|
|
179
|
-
assert.match(repairBody, /runRecoverEdit:[\s\S]*?recoverCommitBlockEdit\(/);
|
|
244
|
+
assert.match(repairBody, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent');
|
|
245
|
+
assert.match(repairBody, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent');
|
|
180
246
|
});
|
|
181
247
|
|
|
182
248
|
test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
@@ -185,7 +251,8 @@ test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
|
185
251
|
});
|
|
186
252
|
|
|
187
253
|
const verifyObjectionModule = new Function(
|
|
188
|
-
`${
|
|
254
|
+
`${functionSource('parseLastVerdictFence')}\n` +
|
|
255
|
+
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
189
256
|
`${functionSource('renderVerifyObjectionLine')}\n` +
|
|
190
257
|
`${functionSource('extractVerifyObjection')}\n` +
|
|
191
258
|
'return { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK };',
|
|
@@ -267,12 +334,12 @@ test('extractVerifyObjection falls back when no finding yields usable text', ()
|
|
|
267
334
|
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
268
335
|
});
|
|
269
336
|
|
|
270
|
-
test('
|
|
271
|
-
const recoverBody = functionSource('
|
|
337
|
+
test('the verify-recover task in resumeCodeEditorAgent is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
338
|
+
const recoverBody = functionSource('resumeCodeEditorAgent');
|
|
272
339
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
273
340
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
274
|
-
assert.match(recoverBody, /
|
|
275
|
-
assert.match(recoverBody, /objection/, 'expected the fixer prompt to consume the verifier objection');
|
|
341
|
+
assert.match(recoverBody, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery prompt body');
|
|
342
|
+
assert.match(recoverBody, /context\.objection/, 'expected the fixer prompt to consume the verifier objection');
|
|
276
343
|
assert.match(
|
|
277
344
|
recoverBody,
|
|
278
345
|
/do not commit and do not push|Do NOT commit|leave .*uncommitted|uncommitted/i,
|
|
@@ -302,19 +369,15 @@ test('verifyWithRecovery bounds the loop, re-fixes on a failed verdict, and re-v
|
|
|
302
369
|
assert.ok(recoverEditIndex < reverifyIndex, 'expected order recover-edit -> re-verify, so a swap fails');
|
|
303
370
|
});
|
|
304
371
|
|
|
305
|
-
test('applyFixes routes
|
|
372
|
+
test('applyFixes routes through fixerWithRecovery which handles verify and commit', () => {
|
|
306
373
|
const applyFixesBody = functionSource('applyFixes');
|
|
307
|
-
assert.match(applyFixesBody, /
|
|
308
|
-
assert.match(applyFixesBody, /
|
|
309
|
-
assert.match(applyFixesBody, /runRecoverEdit:[\s\S]*?recoverVerifyFailEdit\(/);
|
|
310
|
-
const verifyIndex = applyFixesBody.search(/verifyWithRecovery\(/);
|
|
311
|
-
const commitIndex = applyFixesBody.search(/commitWithRecovery\(/);
|
|
312
|
-
assert.ok(verifyIndex < commitIndex, 'expected verify-recovery to precede commit-recovery');
|
|
374
|
+
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
375
|
+
assert.match(applyFixesBody, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
|
|
313
376
|
});
|
|
314
377
|
|
|
315
|
-
test('repairConvergence routes its verify through verifyWithRecovery wired to
|
|
378
|
+
test('repairConvergence routes its verify through verifyWithRecovery wired to resume helpers', () => {
|
|
316
379
|
const repairBody = functionSource('repairConvergence');
|
|
317
380
|
assert.match(repairBody, /verifyWithRecovery\(/, 'expected repairConvergence to call verifyWithRecovery');
|
|
318
|
-
assert.match(repairBody, /
|
|
319
|
-
assert.match(repairBody, /
|
|
381
|
+
assert.match(repairBody, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent for verify');
|
|
382
|
+
assert.match(repairBody, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent for recover');
|
|
320
383
|
});
|
|
@@ -31,39 +31,36 @@ test('isMergeConflicting reports a conflict only when the check returned conflic
|
|
|
31
31
|
assert.equal(isMergeConflicting({ conflicting: false }), false);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test('
|
|
35
|
-
const
|
|
36
|
-
assert.match(
|
|
34
|
+
test('the git agent handles merge-conflict checks with shell-agnostic polling', () => {
|
|
35
|
+
const gitBody = functionBody('resumeGitAgent');
|
|
36
|
+
assert.match(gitBody, /mergeable/, 'expected the git agent to read the PR mergeable field');
|
|
37
37
|
assert.match(
|
|
38
|
-
|
|
38
|
+
gitBody,
|
|
39
39
|
/do not edit, commit, push, or rebase|read only/i,
|
|
40
|
-
'expected the
|
|
40
|
+
'expected the git agent merge check to be read-only',
|
|
41
41
|
);
|
|
42
|
-
assert.match(
|
|
43
|
-
assert.match(
|
|
44
|
-
assert.match(body, /null/, 'expected the probe to handle GitHub returning mergeable:null while it computes');
|
|
45
|
-
assert.match(body, /sleep 5|Start-Sleep/, 'expected a shell-agnostic poll delay');
|
|
42
|
+
assert.match(gitBody, /MERGE_CONFLICT_SCHEMA/, 'expected the git agent to return MERGE_CONFLICT_SCHEMA');
|
|
43
|
+
assert.match(gitBody, /sleep 5|Start-Sleep/, 'expected a shell-agnostic poll delay');
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
test('
|
|
49
|
-
const body = functionBody('
|
|
50
|
-
assert.match(body, /git rebase origin\/main/, 'expected the edit
|
|
46
|
+
test('resumeCodeEditorAgent conflict-edit path rebases onto origin/main and makes no push', () => {
|
|
47
|
+
const body = functionBody('resumeCodeEditorAgent');
|
|
48
|
+
assert.match(body, /git rebase origin\/main/, 'expected the edit path to rebase onto origin/main');
|
|
51
49
|
assert.match(
|
|
52
50
|
body,
|
|
53
51
|
/do not push|no push|not push/i,
|
|
54
|
-
'expected the edit
|
|
52
|
+
'expected the edit path to leave the push to the commit step',
|
|
55
53
|
);
|
|
56
|
-
assert.match(body, /agentType:\s*'clean-coder'/, 'expected the edit step to use clean-coder');
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the push on the verdict', () => {
|
|
60
57
|
const body = functionBody('resolveMergeConflicts');
|
|
61
|
-
const checkIndex = body.indexOf(
|
|
62
|
-
const editIndex = body.indexOf('
|
|
63
|
-
const verifyIndex = body.indexOf('
|
|
64
|
-
const commitIndex = body.indexOf('
|
|
58
|
+
const checkIndex = body.indexOf("resumeGitAgent(gitAgentId, 'check-merge-conflicts'");
|
|
59
|
+
const editIndex = body.indexOf('spawnCodeEditorAgent(');
|
|
60
|
+
const verifyIndex = body.indexOf('spawnVerifierAgent(');
|
|
61
|
+
const commitIndex = body.indexOf('commitWithRecovery(');
|
|
65
62
|
assert.notEqual(checkIndex, -1, 'expected the conflict check to run');
|
|
66
|
-
assert.notEqual(editIndex, -1, 'expected the
|
|
63
|
+
assert.notEqual(editIndex, -1, 'expected the edit step to run');
|
|
67
64
|
assert.notEqual(verifyIndex, -1, 'expected the verify step to run');
|
|
68
65
|
assert.notEqual(commitIndex, -1, 'expected the commit step to run');
|
|
69
66
|
assert.ok(
|
|
@@ -71,11 +68,6 @@ test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the
|
|
|
71
68
|
'expected the order check -> edit -> verify -> commit',
|
|
72
69
|
);
|
|
73
70
|
assert.match(body, /verdictPassed\(/, 'expected the verifier verdict to gate the force-push');
|
|
74
|
-
assert.match(
|
|
75
|
-
body,
|
|
76
|
-
/commitRepairFixes\(head,\s*true\)/,
|
|
77
|
-
'expected the commit to force-with-lease (wasRebased=true) after a rebase',
|
|
78
|
-
);
|
|
79
71
|
});
|
|
80
72
|
|
|
81
73
|
test('resolveMergeConflicts rebases only when the check reports a conflict', () => {
|