claude-dev-env 1.78.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/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
- package/bin/install.mjs +1 -0
- package/bin/install.test.mjs +3 -2
- package/hooks/blocking/CLAUDE.md +3 -2
- package/hooks/blocking/code_rules_docstrings.py +609 -16
- package/hooks/blocking/code_rules_enforcer.py +41 -0
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_naming_collection.py +76 -1
- package/hooks/blocking/code_rules_paired_test.py +240 -22
- package/hooks/blocking/code_rules_test_assertions.py +159 -1
- package/hooks/blocking/code_rules_unused_imports.py +7 -63
- package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
- package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +190 -0
- package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +96 -15
- package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
- package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/blocking_check_limits.py +79 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
- package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
- package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
- package/hooks/hooks_constants/paired_test_coverage_constants.py +11 -3
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -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/CLAUDE.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +57 -54
- package/rules/env-var-table-code-drift.md +24 -0
- package/rules/package-inventory-stale-entry.md +4 -4
- package/rules/paired-test-coverage.md +12 -5
- 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
|
@@ -45,8 +45,8 @@ test('bug-audit lens prompt no longer instructs a per-lens git fetch', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('a single round-level prefetch step fetches origin/main before the parallel lenses', () => {
|
|
48
|
-
assert.ok(convergeSource.includes("
|
|
49
|
-
const prefetchCallIndex = convergeSource.indexOf("
|
|
48
|
+
assert.ok(convergeSource.includes("runGitTask('prefetch-main')"));
|
|
49
|
+
const prefetchCallIndex = convergeSource.indexOf("runGitTask('prefetch-main')");
|
|
50
50
|
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
51
51
|
assert.notEqual(prefetchCallIndex, -1, 'expected prefetch to be invoked');
|
|
52
52
|
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
@@ -85,7 +85,7 @@ test('gotchas doc states parallel lenses must avoid concurrent git operations',
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test('repair-convergence edit step filters unresolved threads to bot authors and skips human threads', () => {
|
|
88
|
-
const repairPrompt = functionSource('
|
|
88
|
+
const repairPrompt = functionSource('runCodeEditorTask');
|
|
89
89
|
assert.match(
|
|
90
90
|
repairPrompt,
|
|
91
91
|
/cursor.*claude.*copilot|copilot.*cursor.*claude|claude.*cursor.*copilot/is,
|
|
@@ -99,7 +99,7 @@ test('repair-convergence edit step filters unresolved threads to bot authors and
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
test('repair-convergence edit step no longer instructs resolving every unresolved thread without an author filter', () => {
|
|
102
|
-
const repairPrompt = functionSource('
|
|
102
|
+
const repairPrompt = functionSource('runCodeEditorTask');
|
|
103
103
|
assert.doesNotMatch(
|
|
104
104
|
repairPrompt,
|
|
105
105
|
/fetch every thread where isResolved is false/,
|
|
@@ -195,12 +195,12 @@ test('the COPILOT fix branch does not re-assign head from the fix before re-conv
|
|
|
195
195
|
test('the CONVERGE branch re-resolves HEAD from GitHub on every entry', () => {
|
|
196
196
|
const convergeBranchStart = convergeSource.indexOf("if (phase === 'CONVERGE')");
|
|
197
197
|
assert.notEqual(convergeBranchStart, -1, 'expected the CONVERGE branch to exist');
|
|
198
|
-
const headResolveCallIndex = convergeSource.indexOf("
|
|
199
|
-
assert.notEqual(headResolveCallIndex, -1, 'expected CONVERGE to re-resolve HEAD via
|
|
198
|
+
const headResolveCallIndex = convergeSource.indexOf("runGitTask('resolve-head')", convergeBranchStart);
|
|
199
|
+
assert.notEqual(headResolveCallIndex, -1, 'expected CONVERGE to re-resolve HEAD via runGitTask');
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
test('fix edit prompt resolves threads by PRRT thread node id looked up from the comment databaseId', () => {
|
|
203
|
-
const editPrompt = functionSource('
|
|
203
|
+
const editPrompt = functionSource('runCodeEditorTask');
|
|
204
204
|
assert.match(editPrompt, /PRRT/, 'expected the thread node id form (PRRT_...) to be named');
|
|
205
205
|
assert.match(
|
|
206
206
|
editPrompt,
|
|
@@ -216,15 +216,15 @@ test('fix edit prompt resolves threads by PRRT thread node id looked up from the
|
|
|
216
216
|
|
|
217
217
|
test('fix edit prompt does not pass the numeric comment id straight to resolve_thread', () => {
|
|
218
218
|
assert.doesNotMatch(
|
|
219
|
-
functionSource('
|
|
219
|
+
functionSource('runCodeEditorTask'),
|
|
220
220
|
/then resolve that thread \(use the github MCP pull_request_review_write/,
|
|
221
221
|
'resolve_thread and resolveReviewThread require a PRRT_... thread node id, not the comment id',
|
|
222
222
|
);
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
-
test('the fix flow
|
|
225
|
+
test('the fix flow runs the edit task then fixerWithRecovery after the edit step', () => {
|
|
226
226
|
const applyFixesBody = lensPromptBody('applyFixes');
|
|
227
|
-
assert.match(applyFixesBody, /
|
|
227
|
+
assert.match(applyFixesBody, /runCodeEditorTask\('fix-edit'/, "expected applyFixes to call runCodeEditorTask('fix-edit')");
|
|
228
228
|
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
229
229
|
});
|
|
230
230
|
|
|
@@ -269,7 +269,7 @@ test('the verdict-fence binding does not self-resolve a cwd via git rev-parse fo
|
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
test('every verify step calls buildVerdictFenceSteps, uses code-verifier, and forbids edits', () => {
|
|
272
|
-
for (const verifyFunctionName of ['
|
|
272
|
+
for (const verifyFunctionName of ['runVerifierTask']) {
|
|
273
273
|
const verifyBody = lensPromptBody(verifyFunctionName);
|
|
274
274
|
assert.match(
|
|
275
275
|
verifyBody,
|
|
@@ -294,15 +294,15 @@ test('every verify step calls buildVerdictFenceSteps, uses code-verifier, and fo
|
|
|
294
294
|
}
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
-
test('
|
|
298
|
-
const fixerBody = lensPromptBody('
|
|
297
|
+
test('runFixerTask never verifies — verification belongs to the separate verifier', () => {
|
|
298
|
+
const fixerBody = lensPromptBody('runFixerTask');
|
|
299
299
|
assert.doesNotMatch(fixerBody, /buildVerdictFenceSteps\(/, 'expected the fixer to not emit a verdict fence — the separate verifier does');
|
|
300
|
-
assert.doesNotMatch(fixerBody, /agentType:\s*'code-verifier'/, 'expected the fixer
|
|
300
|
+
assert.doesNotMatch(fixerBody, /agentType:\s*'code-verifier'/, 'expected the fixer to be clean-coder only');
|
|
301
301
|
assert.match(fixerBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder for its commit and recovery edits');
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
-
test('
|
|
305
|
-
const verifyBody = lensPromptBody('
|
|
304
|
+
test('runVerifierTask uses --manifest-hash-for-branch with the hardening branch and forbids edits', () => {
|
|
305
|
+
const verifyBody = lensPromptBody('runVerifierTask');
|
|
306
306
|
assert.match(
|
|
307
307
|
verifyBody,
|
|
308
308
|
/--manifest-hash-for-branch/,
|
|
@@ -315,18 +315,18 @@ test('resumeVerifierAgent uses --manifest-hash-for-branch with the hardening bra
|
|
|
315
315
|
);
|
|
316
316
|
});
|
|
317
317
|
|
|
318
|
-
test('
|
|
319
|
-
const verifyBody = lensPromptBody('
|
|
320
|
-
assert.match(verifyBody, /task === 'fix-verify'/, 'expected
|
|
318
|
+
test('runVerifierTask passes PR coordinates to buildVerdictFenceSteps for the fix-verify and repair-verify tasks', () => {
|
|
319
|
+
const verifyBody = lensPromptBody('runVerifierTask');
|
|
320
|
+
assert.match(verifyBody, /task === 'fix-verify'/, 'expected runVerifierTask to carry the fix-path verify task');
|
|
321
321
|
assert.match(
|
|
322
322
|
verifyBody,
|
|
323
323
|
/buildVerdictFenceSteps\(input\.owner, input\.repo, input\.prNumber\)/,
|
|
324
|
-
'expected
|
|
324
|
+
'expected runVerifierTask to pass PR coordinates to buildVerdictFenceSteps',
|
|
325
325
|
);
|
|
326
326
|
});
|
|
327
327
|
|
|
328
|
-
test('the commit path in
|
|
329
|
-
const fixerBody = lensPromptBody('
|
|
328
|
+
test('the commit path in runFixerTask forbids further edits and uses clean-coder', () => {
|
|
329
|
+
const fixerBody = lensPromptBody('runFixerTask');
|
|
330
330
|
assert.match(
|
|
331
331
|
fixerBody,
|
|
332
332
|
/no (?:further |additional )?(?:file )?edits|do not edit|make no edits/i,
|
|
@@ -339,22 +339,22 @@ test('the commit path in resumeFixerAgent forbids further edits and uses clean-c
|
|
|
339
339
|
);
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
-
test('the repair flow uses
|
|
342
|
+
test('the repair flow uses the direct-spawn task helpers for edit, verify, and commit', () => {
|
|
343
343
|
const repairBody = lensPromptBody('repairConvergence');
|
|
344
|
-
assert.match(repairBody, /
|
|
345
|
-
assert.match(repairBody, /
|
|
344
|
+
assert.match(repairBody, /runCodeEditorTask\(/, 'expected repairConvergence to call runCodeEditorTask');
|
|
345
|
+
assert.match(repairBody, /runVerifierTask\(/, 'expected repairConvergence to call runVerifierTask');
|
|
346
346
|
assert.match(repairBody, /verdictPassed\(/, 'expected the verify verdict to gate the repair commit step');
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
test('the standards-deferral flow uses
|
|
349
|
+
test('the standards-deferral flow uses the direct-spawn task helpers for edit, verify, and commit', () => {
|
|
350
350
|
const standardsBody = lensPromptBody('spawnStandardsFollowUp');
|
|
351
|
-
assert.match(standardsBody, /
|
|
352
|
-
assert.match(standardsBody, /
|
|
351
|
+
assert.match(standardsBody, /runCodeEditorTask\(/, 'expected spawnStandardsFollowUp to call runCodeEditorTask');
|
|
352
|
+
assert.match(standardsBody, /runVerifierTask\(/, 'expected spawnStandardsFollowUp to call runVerifierTask');
|
|
353
353
|
assert.match(standardsBody, /verdictPassed\(/, 'expected the verify verdict to gate the hardening commit step');
|
|
354
354
|
});
|
|
355
355
|
|
|
356
356
|
test('repair-commit and hardening-commit paths use clean-coder and forbid edits', () => {
|
|
357
|
-
const codeEditorBody = lensPromptBody('
|
|
357
|
+
const codeEditorBody = lensPromptBody('runCodeEditorTask');
|
|
358
358
|
assert.match(
|
|
359
359
|
codeEditorBody,
|
|
360
360
|
/no (?:further |additional )?(?:file )?edits|do not edit|make no edits/i,
|
|
@@ -368,7 +368,7 @@ test('repair-commit and hardening-commit paths use clean-coder and forbid edits'
|
|
|
368
368
|
});
|
|
369
369
|
|
|
370
370
|
test('the code-editor standards-edit path stages hardening without committing and uses clean-coder', () => {
|
|
371
|
-
const editBody = lensPromptBody('
|
|
371
|
+
const editBody = lensPromptBody('runCodeEditorTask');
|
|
372
372
|
assert.match(
|
|
373
373
|
editBody,
|
|
374
374
|
/do not commit and do not push|NO commit and NO push|Do NOT commit/i,
|
|
@@ -495,9 +495,9 @@ test('the pre-commit gate step is a shared constant that dry-runs the CODE_RULES
|
|
|
495
495
|
);
|
|
496
496
|
});
|
|
497
497
|
|
|
498
|
-
const
|
|
498
|
+
const editStepTaskDispatchers = ['runCodeEditorTask', 'runFixerTask'];
|
|
499
499
|
|
|
500
|
-
for (const helperName of
|
|
500
|
+
for (const helperName of editStepTaskDispatchers) {
|
|
501
501
|
test(`${helperName} appends the pre-commit gate step to its edit prompts`, () => {
|
|
502
502
|
assert.match(
|
|
503
503
|
functionSource(helperName),
|
|
@@ -507,14 +507,14 @@ for (const helperName of editStepResumeHelpers) {
|
|
|
507
507
|
});
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
const
|
|
511
|
-
['
|
|
512
|
-
['
|
|
513
|
-
['
|
|
514
|
-
['
|
|
510
|
+
const editStepTasks = [
|
|
511
|
+
['runCodeEditorTask', 'fix-edit'],
|
|
512
|
+
['runCodeEditorTask', 'repair-edit'],
|
|
513
|
+
['runCodeEditorTask', 'standards-edit'],
|
|
514
|
+
['runCodeEditorTask', 'commit-recover'],
|
|
515
515
|
];
|
|
516
516
|
|
|
517
|
-
for (const [helperName, taskName] of
|
|
517
|
+
for (const [helperName, taskName] of editStepTasks) {
|
|
518
518
|
test(`${helperName} routes the ${taskName} task to a pre-commit-gated edit prompt`, () => {
|
|
519
519
|
assert.match(
|
|
520
520
|
functionSource(helperName),
|
|
@@ -634,37 +634,26 @@ test('SKILL.md attributes the known-temp-var resolution to the cwd-scoped auto-a
|
|
|
634
634
|
);
|
|
635
635
|
});
|
|
636
636
|
|
|
637
|
-
test('convergeAgent prepends HEADLESS_SAFETY_PREAMBLE and worktree directive
|
|
637
|
+
test('convergeAgent prepends HEADLESS_SAFETY_PREAMBLE and the worktree directive to every prompt', () => {
|
|
638
638
|
const convergeAgentBody = lensPromptBody('convergeAgent');
|
|
639
639
|
assert.match(
|
|
640
640
|
convergeAgentBody,
|
|
641
641
|
/HEADLESS_SAFETY_PREAMBLE.*worktreeDirective/,
|
|
642
|
-
'expected
|
|
642
|
+
'expected convergeAgent to prepend both preamble and worktree directive',
|
|
643
643
|
);
|
|
644
644
|
});
|
|
645
645
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
const newSpawnResumeHelpers = [
|
|
652
|
-
{ name: 'spawnGitAgent', isAsync: true },
|
|
653
|
-
{ name: 'resumeGitAgent', isAsync: false },
|
|
654
|
-
{ name: 'spawnFixerAgent', isAsync: true },
|
|
655
|
-
{ name: 'resumeFixerAgent', isAsync: false },
|
|
646
|
+
const taskDispatchers = [
|
|
647
|
+
{ name: 'runGitTask', isAsync: false },
|
|
648
|
+
{ name: 'runFixerTask', isAsync: false },
|
|
656
649
|
{ name: 'fixerWithRecovery', isAsync: true },
|
|
657
|
-
{ name: '
|
|
658
|
-
{ name: '
|
|
659
|
-
{ name: '
|
|
660
|
-
{ name: '
|
|
661
|
-
{ name: 'spawnGeneralUtilityAgent', isAsync: true },
|
|
662
|
-
{ name: 'resumeGeneralUtilityAgent', isAsync: false },
|
|
663
|
-
{ name: 'spawnConvergenceCheckAgent', isAsync: true },
|
|
664
|
-
{ name: 'resumeConvergenceCheckAgent', isAsync: false },
|
|
650
|
+
{ name: 'runCodeEditorTask', isAsync: false },
|
|
651
|
+
{ name: 'runVerifierTask', isAsync: false },
|
|
652
|
+
{ name: 'runGeneralUtilityTask', isAsync: false },
|
|
653
|
+
{ name: 'runConvergenceCheck', isAsync: false },
|
|
665
654
|
];
|
|
666
655
|
|
|
667
|
-
for (const { name, isAsync } of
|
|
656
|
+
for (const { name, isAsync } of taskDispatchers) {
|
|
668
657
|
const prefix = isAsync ? 'async ' : '';
|
|
669
658
|
test(`function ${prefix}${name} exists in converge.mjs`, () => {
|
|
670
659
|
const needle = isAsync ? `async function ${name}(` : `function ${name}(`;
|
|
@@ -672,60 +661,8 @@ for (const { name, isAsync } of newSpawnResumeHelpers) {
|
|
|
672
661
|
});
|
|
673
662
|
}
|
|
674
663
|
|
|
675
|
-
|
|
676
|
-
'
|
|
677
|
-
'spawnFixerAgent',
|
|
678
|
-
'spawnCodeEditorAgent',
|
|
679
|
-
'spawnVerifierAgent',
|
|
680
|
-
'spawnGeneralUtilityAgent',
|
|
681
|
-
'spawnConvergenceCheckAgent',
|
|
682
|
-
];
|
|
683
|
-
|
|
684
|
-
for (const spawnName of spawnHelperNames) {
|
|
685
|
-
test(`${spawnName} actually spawns an agent via convergeAgent`, () => {
|
|
686
|
-
const spawnBody = functionSource(spawnName);
|
|
687
|
-
assert.match(
|
|
688
|
-
spawnBody,
|
|
689
|
-
/await convergeAgent\(/,
|
|
690
|
-
`expected ${spawnName} to spawn a real agent through convergeAgent rather than return a hardcoded label`,
|
|
691
|
-
);
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
test(`${spawnName} returns the spawned agent's runtime id`, () => {
|
|
695
|
-
const spawnBody = functionSource(spawnName);
|
|
696
|
-
assert.match(
|
|
697
|
-
spawnBody,
|
|
698
|
-
/return\s+result\?\.agentId/,
|
|
699
|
-
`expected ${spawnName} to return result?.agentId so resume targets the real session`,
|
|
700
|
-
);
|
|
701
|
-
assert.doesNotMatch(
|
|
702
|
-
spawnBody,
|
|
703
|
-
/return\s+['"`]/,
|
|
704
|
-
`expected ${spawnName} not to return a hardcoded label string`,
|
|
705
|
-
);
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
test('spawnGitAgent returns result?.agentId without a tautological ternary', () => {
|
|
710
|
-
const spawnBody = functionSource('spawnGitAgent');
|
|
711
|
-
assert.doesNotMatch(
|
|
712
|
-
spawnBody,
|
|
713
|
-
/\?\s*'git-utility'\s*:\s*'git-utility'/,
|
|
714
|
-
'expected the identical-branch ternary that discards the spawn outcome to be gone',
|
|
715
|
-
);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
test('resume helpers fall back to a fresh spawn when no agentId is available', () => {
|
|
719
|
-
const convergeAgentBody = lensPromptBody('convergeAgent');
|
|
720
|
-
assert.match(
|
|
721
|
-
convergeAgentBody,
|
|
722
|
-
/options\?\.resume.*length\s*>\s*0|length\s*>\s*0.*resume/s,
|
|
723
|
-
'expected convergeAgent to treat a missing agentId as a fresh spawn (isResume false), restoring the preamble and worktree directive',
|
|
724
|
-
);
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
test('resumeGeneralUtilityAgent only handles the two tasks it is called with', () => {
|
|
728
|
-
const generalBody = functionSource('resumeGeneralUtilityAgent');
|
|
664
|
+
test('runGeneralUtilityTask only handles the two tasks it is called with', () => {
|
|
665
|
+
const generalBody = functionSource('runGeneralUtilityTask');
|
|
729
666
|
assert.doesNotMatch(
|
|
730
667
|
generalBody,
|
|
731
668
|
/task === 'bugbot-lens'/,
|
|
@@ -761,7 +698,7 @@ const orphanedHelperNames = [
|
|
|
761
698
|
];
|
|
762
699
|
|
|
763
700
|
for (const orphanName of orphanedHelperNames) {
|
|
764
|
-
test(`${orphanName} is removed — its behavior lives in a
|
|
701
|
+
test(`${orphanName} is removed — its behavior lives in a direct-spawn task dispatcher`, () => {
|
|
765
702
|
assert.ok(
|
|
766
703
|
!convergeSource.includes(`function ${orphanName}(`),
|
|
767
704
|
`expected the orphaned ${orphanName} definition to be deleted (CODE_RULES 9.8)`,
|
|
@@ -769,12 +706,11 @@ for (const orphanName of orphanedHelperNames) {
|
|
|
769
706
|
});
|
|
770
707
|
}
|
|
771
708
|
|
|
772
|
-
test('
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
'expected the resume path to pass prompt through without the preamble',
|
|
709
|
+
test('the whole priming spawn-agent family is removed — every dispatcher spawns fresh', () => {
|
|
710
|
+
assert.doesNotMatch(
|
|
711
|
+
convergeSource,
|
|
712
|
+
/function\s+spawn\w+Agent\s*\(/,
|
|
713
|
+
'expected no spawn<Role>Agent priming function to survive — each task dispatcher spawns a fresh agent',
|
|
778
714
|
);
|
|
779
715
|
});
|
|
780
716
|
|
|
@@ -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('runConvergenceCheck wires the --copilot-down flag from the copilotDown context', () => {
|
|
133
|
+
const checkConvergenceBody = functionBody('runConvergenceCheck');
|
|
134
134
|
assert.match(
|
|
135
135
|
checkConvergenceBody,
|
|
136
136
|
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
-
'expected
|
|
137
|
+
'expected runConvergenceCheck to append --copilot-down when copilotDown is set',
|
|
138
138
|
);
|
|
139
139
|
assert.match(
|
|
140
140
|
checkConvergenceBody,
|
|
@@ -244,8 +244,8 @@ test('markReady receives copilotDown so it can opt the unflagged hook out of the
|
|
|
244
244
|
);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
test('the mark-ready task in
|
|
248
|
-
const markReadyBody = functionBody('
|
|
247
|
+
test('the mark-ready task in runGeneralUtilityTask opts the unflagged convergence hook out of Copilot when copilotDown', () => {
|
|
248
|
+
const markReadyBody = functionBody('runGeneralUtilityTask');
|
|
249
249
|
assert.match(
|
|
250
250
|
markReadyBody,
|
|
251
251
|
/context\.copilotDown/,
|
|
@@ -263,3 +263,43 @@ test('the mark-ready task in resumeGeneralUtilityAgent opts the unflagged conver
|
|
|
263
263
|
);
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
test('the COPILOT phase short-circuits on input.copilotDisabled before spawning the gate agent', () => {
|
|
267
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
268
|
+
assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
|
|
269
|
+
const gateCallIndex = convergeSource.indexOf('await runCopilotGate(head)', copilotPhaseStart);
|
|
270
|
+
assert.notEqual(gateCallIndex, -1, 'expected the COPILOT phase to call runCopilotGate when Copilot is enabled');
|
|
271
|
+
const beforeGate = convergeSource.slice(copilotPhaseStart, gateCallIndex);
|
|
272
|
+
assert.match(
|
|
273
|
+
beforeGate,
|
|
274
|
+
/if \(input\.copilotDisabled\)/,
|
|
275
|
+
'expected the quota pre-check bypass to guard the COPILOT phase before any gate agent spawns',
|
|
276
|
+
);
|
|
277
|
+
assert.match(beforeGate, /copilotDown = true/, 'expected the bypass to mark copilotDown');
|
|
278
|
+
assert.match(beforeGate, /copilotNote =/, 'expected the bypass to set a copilotNote');
|
|
279
|
+
assert.match(beforeGate, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
|
|
280
|
+
assert.match(beforeGate, /continue/, 'expected the bypass to continue without spawning the gate agent');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('copilotDown initializes from input.copilotDisabled so the pre-check decision carries into the loop', () => {
|
|
284
|
+
assert.match(
|
|
285
|
+
convergeSource,
|
|
286
|
+
/let copilotDown = input\.copilotDisabled \|\| false/,
|
|
287
|
+
'expected copilotDown to seed from the copilotDisabled run input',
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('a copilotDisabled run reaches FINALIZE with --copilot-down', () => {
|
|
292
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
293
|
+
const bypassStart = convergeSource.indexOf('if (input.copilotDisabled)', copilotPhaseStart);
|
|
294
|
+
assert.notEqual(bypassStart, -1, 'expected an input.copilotDisabled bypass in the COPILOT phase');
|
|
295
|
+
const bypassBlock = convergeSource.slice(bypassStart, bypassStart + 800);
|
|
296
|
+
assert.match(bypassBlock, /copilotDown = true/, 'expected the bypass to set copilotDown');
|
|
297
|
+
assert.match(bypassBlock, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
|
|
298
|
+
const convergenceCheckBody = functionBody('resumeConvergenceCheckAgent');
|
|
299
|
+
assert.match(
|
|
300
|
+
convergenceCheckBody,
|
|
301
|
+
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
302
|
+
'expected the convergence check to pass --copilot-down when the bypassed copilotDown reaches FINALIZE',
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
@@ -99,8 +99,8 @@ 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
|
-
test('the commit path in
|
|
103
|
-
const commitBody = functionSource('
|
|
102
|
+
test('the commit path in runCodeEditorTask separates an edit-requiring block from a transient failure', () => {
|
|
103
|
+
const commitBody = functionSource('runCodeEditorTask');
|
|
104
104
|
assert.match(commitBody, /blockedNeedingEdit/, 'expected the edit-block flag to be set in the prompt');
|
|
105
105
|
assert.match(commitBody, /blockerDetail/, 'expected the verbatim blocker detail to be requested');
|
|
106
106
|
assert.match(
|
|
@@ -115,8 +115,8 @@ test('the commit path in resumeCodeEditorAgent separates an edit-requiring block
|
|
|
115
115
|
);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
test('the commit-recover task in
|
|
119
|
-
const recoverBody = functionSource('
|
|
118
|
+
test('the commit-recover task in runCodeEditorTask is a clean-coder edit step bound to the blocker detail and leaves changes uncommitted', () => {
|
|
119
|
+
const recoverBody = functionSource('runCodeEditorTask');
|
|
120
120
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
121
121
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
122
122
|
assert.match(recoverBody, /task === 'commit-recover'/, 'expected the commit-recover task branch');
|
|
@@ -161,88 +161,92 @@ test('commitWithRecovery bounds the loop, re-verifies, and retries the commit on
|
|
|
161
161
|
);
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
test('applyFixes routes through
|
|
164
|
+
test('applyFixes routes through the fix-edit task and fixerWithRecovery', () => {
|
|
165
165
|
const applyFixesBody = functionSource('applyFixes');
|
|
166
|
-
assert.match(applyFixesBody, /
|
|
166
|
+
assert.match(applyFixesBody, /runCodeEditorTask\('fix-edit'/, "expected applyFixes to call runCodeEditorTask('fix-edit')");
|
|
167
167
|
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
test('applyFixes
|
|
170
|
+
test('applyFixes delegates to fixerWithRecovery, which grades fixes with a separate verifier from the fixer', () => {
|
|
171
171
|
const applyFixesBody = functionSource('applyFixes');
|
|
172
172
|
assert.match(
|
|
173
173
|
applyFixesBody,
|
|
174
|
-
/
|
|
175
|
-
'expected applyFixes to
|
|
174
|
+
/fixerWithRecovery\(head, findings, sourceLabel\)/,
|
|
175
|
+
'expected applyFixes to delegate the verify and commit flow to fixerWithRecovery',
|
|
176
176
|
);
|
|
177
|
-
const
|
|
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');
|
|
177
|
+
const recoveryBody = functionSource('fixerWithRecovery');
|
|
181
178
|
assert.match(
|
|
182
|
-
|
|
183
|
-
/
|
|
184
|
-
'expected
|
|
179
|
+
recoveryBody,
|
|
180
|
+
/runVerifierTask\('fix-verify'/,
|
|
181
|
+
'expected fixerWithRecovery to grade the fixes with the code-verifier task so the verdict is independent of the fixer',
|
|
182
|
+
);
|
|
183
|
+
assert.match(
|
|
184
|
+
recoveryBody,
|
|
185
|
+
/runFixerTask\('commit'/,
|
|
186
|
+
'expected fixerWithRecovery to commit through the clean-coder fixer task',
|
|
185
187
|
);
|
|
186
188
|
});
|
|
187
189
|
|
|
188
|
-
test('fixerWithRecovery runs verify
|
|
190
|
+
test('fixerWithRecovery runs verify via runVerifierTask and edits/commits via runFixerTask', () => {
|
|
189
191
|
const recoveryBody = functionSource('fixerWithRecovery');
|
|
190
192
|
assert.match(
|
|
191
193
|
recoveryBody,
|
|
192
|
-
/
|
|
193
|
-
'expected fixerWithRecovery to
|
|
194
|
+
/runVerifierTask\('fix-verify'/,
|
|
195
|
+
'expected fixerWithRecovery to run the verify step through the separate verifier task',
|
|
194
196
|
);
|
|
195
197
|
assert.match(
|
|
196
198
|
recoveryBody,
|
|
197
|
-
/
|
|
198
|
-
'expected fixerWithRecovery to
|
|
199
|
+
/runFixerTask\('commit'/,
|
|
200
|
+
'expected fixerWithRecovery to run the commit step through the fixer task',
|
|
199
201
|
);
|
|
200
202
|
assert.doesNotMatch(
|
|
201
203
|
recoveryBody,
|
|
202
|
-
/
|
|
203
|
-
'expected the verify step never to
|
|
204
|
+
/runFixerTask\('(?:verify-commit|fix-verify)'/,
|
|
205
|
+
'expected the verify step never to route through the fixer task — the verifier grades a different agent than the one that edits',
|
|
204
206
|
);
|
|
205
207
|
});
|
|
206
208
|
|
|
207
|
-
test('the fix-path verify
|
|
209
|
+
test('the fix-path verify routes through a different helper and agentType than the fix-path edits, mirroring the repair path', () => {
|
|
208
210
|
const recoveryBody = functionSource('fixerWithRecovery');
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
assert.ok(
|
|
212
|
-
assert.ok(
|
|
211
|
+
const verifyHelperMatch = /(\w+)\('fix-verify'/.exec(recoveryBody);
|
|
212
|
+
const editHelperMatch = /(\w+)\('commit'/.exec(recoveryBody);
|
|
213
|
+
assert.ok(verifyHelperMatch, 'expected a verify call naming its task helper');
|
|
214
|
+
assert.ok(editHelperMatch, 'expected an edit/commit call naming its task helper');
|
|
215
|
+
assert.equal(verifyHelperMatch[1], 'runVerifierTask', 'expected verify to route through runVerifierTask (code-verifier)');
|
|
216
|
+
assert.equal(editHelperMatch[1], 'runFixerTask', 'expected commit to route through runFixerTask (clean-coder)');
|
|
213
217
|
assert.notEqual(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
'expected the verify step to
|
|
218
|
+
verifyHelperMatch[1],
|
|
219
|
+
editHelperMatch[1],
|
|
220
|
+
'expected the verify step to route through a different helper than the edit/commit step',
|
|
217
221
|
);
|
|
218
222
|
});
|
|
219
223
|
|
|
220
|
-
test('the verifier carries a fix-verify task so the fix path verdict comes from the verifier
|
|
221
|
-
const verifierBody = functionSource('
|
|
222
|
-
assert.match(verifierBody, /task === 'fix-verify'/, 'expected
|
|
224
|
+
test('the verifier carries a fix-verify task so the fix path verdict comes from the verifier', () => {
|
|
225
|
+
const verifierBody = functionSource('runVerifierTask');
|
|
226
|
+
assert.match(verifierBody, /task === 'fix-verify'/, 'expected runVerifierTask to handle the fix-verify task');
|
|
223
227
|
assert.match(verifierBody, /buildVerdictFenceSteps\(/, 'expected the fix-verify task to emit a verdict fence');
|
|
224
228
|
assert.match(verifierBody, /agentType:\s*'code-verifier'/, 'expected the fix-verify task to run as code-verifier');
|
|
225
229
|
});
|
|
226
230
|
|
|
227
|
-
test('
|
|
228
|
-
const fixerBody = functionSource('
|
|
231
|
+
test('runFixerTask no longer verifies its own edits — no code-verifier verify-commit task remains', () => {
|
|
232
|
+
const fixerBody = functionSource('runFixerTask');
|
|
229
233
|
assert.doesNotMatch(
|
|
230
234
|
fixerBody,
|
|
231
235
|
/task === 'verify-commit'/,
|
|
232
|
-
'expected the fixer to no longer carry the verify-commit task that graded its own
|
|
236
|
+
'expected the fixer to no longer carry the verify-commit task that graded its own edits',
|
|
233
237
|
);
|
|
234
238
|
assert.doesNotMatch(
|
|
235
239
|
fixerBody,
|
|
236
240
|
/agentType:\s*'code-verifier'/,
|
|
237
|
-
'expected the fixer
|
|
241
|
+
'expected the fixer to be clean-coder only — a separate verifier grades the working tree',
|
|
238
242
|
);
|
|
239
243
|
});
|
|
240
244
|
|
|
241
245
|
test('repairConvergence routes its commit through commitWithRecovery wired to the repair-path steps', () => {
|
|
242
246
|
const repairBody = functionSource('repairConvergence');
|
|
243
247
|
assert.match(repairBody, /commitWithRecovery\(/, 'expected repairConvergence to call commitWithRecovery');
|
|
244
|
-
assert.match(repairBody, /
|
|
245
|
-
assert.match(repairBody, /
|
|
248
|
+
assert.match(repairBody, /runCodeEditorTask\(/, 'expected repairConvergence to use runCodeEditorTask');
|
|
249
|
+
assert.match(repairBody, /runVerifierTask\(/, 'expected repairConvergence to use runVerifierTask');
|
|
246
250
|
});
|
|
247
251
|
|
|
248
252
|
test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
@@ -334,8 +338,8 @@ test('extractVerifyObjection falls back when no finding yields usable text', ()
|
|
|
334
338
|
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
335
339
|
});
|
|
336
340
|
|
|
337
|
-
test('the verify-recover task in
|
|
338
|
-
const recoverBody = functionSource('
|
|
341
|
+
test('the verify-recover task in runCodeEditorTask is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
342
|
+
const recoverBody = functionSource('runCodeEditorTask');
|
|
339
343
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
340
344
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
341
345
|
assert.match(recoverBody, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery prompt body');
|
|
@@ -372,12 +376,12 @@ test('verifyWithRecovery bounds the loop, re-fixes on a failed verdict, and re-v
|
|
|
372
376
|
test('applyFixes routes through fixerWithRecovery which handles verify and commit', () => {
|
|
373
377
|
const applyFixesBody = functionSource('applyFixes');
|
|
374
378
|
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
375
|
-
assert.match(applyFixesBody, /
|
|
379
|
+
assert.match(applyFixesBody, /runCodeEditorTask\('fix-edit'/, "expected applyFixes to call runCodeEditorTask('fix-edit')");
|
|
376
380
|
});
|
|
377
381
|
|
|
378
|
-
test('repairConvergence routes its verify through verifyWithRecovery wired to
|
|
382
|
+
test('repairConvergence routes its verify through verifyWithRecovery wired to the task helpers', () => {
|
|
379
383
|
const repairBody = functionSource('repairConvergence');
|
|
380
384
|
assert.match(repairBody, /verifyWithRecovery\(/, 'expected repairConvergence to call verifyWithRecovery');
|
|
381
|
-
assert.match(repairBody, /
|
|
382
|
-
assert.match(repairBody, /
|
|
385
|
+
assert.match(repairBody, /runVerifierTask\(/, 'expected repairConvergence to use runVerifierTask for verify');
|
|
386
|
+
assert.match(repairBody, /runCodeEditorTask\(/, 'expected repairConvergence to use runCodeEditorTask for recover');
|
|
383
387
|
});
|
|
@@ -32,7 +32,7 @@ test('isMergeConflicting reports a conflict only when the check returned conflic
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
test('the git agent handles merge-conflict checks with shell-agnostic polling', () => {
|
|
35
|
-
const gitBody = functionBody('
|
|
35
|
+
const gitBody = functionBody('runGitTask');
|
|
36
36
|
assert.match(gitBody, /mergeable/, 'expected the git agent to read the PR mergeable field');
|
|
37
37
|
assert.match(
|
|
38
38
|
gitBody,
|
|
@@ -43,8 +43,8 @@ test('the git agent handles merge-conflict checks with shell-agnostic polling',
|
|
|
43
43
|
assert.match(gitBody, /sleep 5|Start-Sleep/, 'expected a shell-agnostic poll delay');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test('
|
|
47
|
-
const body = functionBody('
|
|
46
|
+
test('runCodeEditorTask conflict-edit path rebases onto origin/main and makes no push', () => {
|
|
47
|
+
const body = functionBody('runCodeEditorTask');
|
|
48
48
|
assert.match(body, /git rebase origin\/main/, 'expected the edit path to rebase onto origin/main');
|
|
49
49
|
assert.match(
|
|
50
50
|
body,
|
|
@@ -55,9 +55,9 @@ test('resumeCodeEditorAgent conflict-edit path rebases onto origin/main and make
|
|
|
55
55
|
|
|
56
56
|
test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the push on the verdict', () => {
|
|
57
57
|
const body = functionBody('resolveMergeConflicts');
|
|
58
|
-
const checkIndex = body.indexOf("
|
|
59
|
-
const editIndex = body.indexOf(
|
|
60
|
-
const verifyIndex = body.indexOf('
|
|
58
|
+
const checkIndex = body.indexOf("runGitTask('check-merge-conflicts'");
|
|
59
|
+
const editIndex = body.indexOf("runCodeEditorTask('conflict-edit'");
|
|
60
|
+
const verifyIndex = body.indexOf('runVerifierTask(');
|
|
61
61
|
const commitIndex = body.indexOf('commitWithRecovery(');
|
|
62
62
|
assert.notEqual(checkIndex, -1, 'expected the conflict check to run');
|
|
63
63
|
assert.notEqual(editIndex, -1, 'expected the edit step to run');
|