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.
Files changed (63) hide show
  1. package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  2. package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
  5. package/_shared/pr-loop/scripts/tests/CLAUDE.md +7 -0
  6. package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
  7. package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +242 -0
  8. package/_shared/pr-loop/scripts/tests/test_copilot_quota_constants.py +63 -0
  9. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
  10. package/bin/install.mjs +1 -0
  11. package/bin/install.test.mjs +3 -2
  12. package/hooks/blocking/CLAUDE.md +3 -2
  13. package/hooks/blocking/code_rules_docstrings.py +609 -16
  14. package/hooks/blocking/code_rules_enforcer.py +41 -0
  15. package/hooks/blocking/code_rules_imports_logging.py +136 -1
  16. package/hooks/blocking/code_rules_naming_collection.py +76 -1
  17. package/hooks/blocking/code_rules_paired_test.py +240 -22
  18. package/hooks/blocking/code_rules_test_assertions.py +159 -1
  19. package/hooks/blocking/code_rules_unused_imports.py +7 -63
  20. package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
  21. package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
  22. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
  23. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
  24. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
  25. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
  26. package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
  27. package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
  28. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +190 -0
  29. package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
  30. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +96 -15
  31. package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
  32. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
  33. package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
  34. package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
  35. package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
  36. package/hooks/hooks_constants/CLAUDE.md +1 -0
  37. package/hooks/hooks_constants/blocking_check_limits.py +79 -0
  38. package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
  39. package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
  40. package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
  41. package/hooks/hooks_constants/paired_test_coverage_constants.py +11 -3
  42. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  43. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
  44. package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
  45. package/package.json +1 -1
  46. package/rules/CLAUDE.md +1 -0
  47. package/rules/docstring-prose-matches-implementation.md +57 -54
  48. package/rules/env-var-table-code-drift.md +24 -0
  49. package/rules/package-inventory-stale-entry.md +4 -4
  50. package/rules/paired-test-coverage.md +12 -5
  51. package/skills/autoconverge/SKILL.md +26 -5
  52. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  53. package/skills/autoconverge/workflow/converge.contract.test.mjs +56 -120
  54. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +45 -5
  55. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +50 -46
  56. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +6 -6
  57. package/skills/autoconverge/workflow/converge.mjs +110 -228
  58. package/skills/autoconverge/workflow/converge.run-input.test.mjs +11 -0
  59. package/skills/autoconverge/workflow/converge_multi.mjs +23 -7
  60. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +28 -2
  61. package/skills/pr-converge/SKILL.md +28 -2
  62. package/skills/pr-converge/reference/convergence-gates.md +13 -1
  63. 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("resumeGitAgent(gitAgentId, 'prefetch-main')"));
49
- const prefetchCallIndex = convergeSource.indexOf("resumeGitAgent(gitAgentId, 'prefetch-main')");
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('resumeCodeEditorAgent');
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('resumeCodeEditorAgent');
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("resumeGitAgent(gitAgentId, 'resolve-head')", convergeBranchStart);
199
- assert.notEqual(headResolveCallIndex, -1, 'expected CONVERGE to re-resolve HEAD via resumeGitAgent');
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('resumeCodeEditorAgent');
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('resumeCodeEditorAgent'),
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 spawns a fixer agent and runs fixerWithRecovery after the edit step', () => {
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, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
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 ['resumeVerifierAgent']) {
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('resumeFixerAgent never verifies its own session — verification belongs to the separate verifier', () => {
298
- const fixerBody = lensPromptBody('resumeFixerAgent');
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 session to be clean-coder only');
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('resumeVerifierAgent uses --manifest-hash-for-branch with the hardening branch and forbids edits', () => {
305
- const verifyBody = lensPromptBody('resumeVerifierAgent');
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('resumeVerifierAgent passes PR coordinates to buildVerdictFenceSteps for the fix-verify and repair-verify tasks', () => {
319
- const verifyBody = lensPromptBody('resumeVerifierAgent');
320
- assert.match(verifyBody, /task === 'fix-verify'/, 'expected resumeVerifierAgent to carry the fix-path verify task');
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 resumeVerifierAgent to pass PR coordinates to buildVerdictFenceSteps',
324
+ 'expected runVerifierTask to pass PR coordinates to buildVerdictFenceSteps',
325
325
  );
326
326
  });
327
327
 
328
- test('the commit path in resumeFixerAgent forbids further edits and uses clean-coder', () => {
329
- const fixerBody = lensPromptBody('resumeFixerAgent');
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 resume helpers for edit, verify, and commit', () => {
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, /spawnCodeEditorAgent\(/, 'expected repairConvergence to spawn a code-editor');
345
- assert.match(repairBody, /spawnVerifierAgent\(/, 'expected repairConvergence to spawn a verifier');
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 resume helpers for edit, verify, and commit', () => {
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, /spawnCodeEditorAgent\(/, 'expected spawnStandardsFollowUp to spawn a code-editor');
352
- assert.match(standardsBody, /spawnVerifierAgent\(/, 'expected spawnStandardsFollowUp to spawn a verifier');
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('resumeCodeEditorAgent');
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('resumeCodeEditorAgent');
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 editStepResumeHelpers = ['resumeCodeEditorAgent', 'resumeFixerAgent'];
498
+ const editStepTaskDispatchers = ['runCodeEditorTask', 'runFixerTask'];
499
499
 
500
- for (const helperName of editStepResumeHelpers) {
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 editStepResumeTasks = [
511
- ['resumeCodeEditorAgent', 'fix-edit'],
512
- ['resumeCodeEditorAgent', 'repair-edit'],
513
- ['resumeCodeEditorAgent', 'standards-edit'],
514
- ['resumeCodeEditorAgent', 'commit-recover'],
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 editStepResumeTasks) {
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 on fresh spawn', () => {
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 fresh-spawn path to prepend both preamble and worktree directive',
642
+ 'expected convergeAgent to prepend both preamble and worktree directive',
643
643
  );
644
644
  });
645
645
 
646
- test('convergeAgent checks isResume before prepending the preamble', () => {
647
- const convergeAgentBody = lensPromptBody('convergeAgent');
648
- assert.match(convergeAgentBody, /isResume/, 'expected an isResume guard in convergeAgent');
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: 'spawnCodeEditorAgent', isAsync: true },
658
- { name: 'resumeCodeEditorAgent', isAsync: false },
659
- { name: 'spawnVerifierAgent', isAsync: true },
660
- { name: 'resumeVerifierAgent', isAsync: false },
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 newSpawnResumeHelpers) {
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
- const spawnHelperNames = [
676
- 'spawnGitAgent',
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 resume helper`, () => {
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('convergeAgent omits the preamble when options.resume is a non-empty string', () => {
773
- const convergeAgentBody = lensPromptBody('convergeAgent');
774
- assert.match(
775
- convergeAgentBody,
776
- /isResume\s*\?\s*prompt/,
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('resumeConvergenceCheckAgent wires the --copilot-down flag from the copilotDown context', () => {
133
- const checkConvergenceBody = functionBody('resumeConvergenceCheckAgent');
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 resumeConvergenceCheckAgent to append --copilot-down when copilotDown is set',
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 resumeGeneralUtilityAgent opts the unflagged convergence hook out of Copilot when copilotDown', () => {
248
- const markReadyBody = functionBody('resumeGeneralUtilityAgent');
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 resumeCodeEditorAgent separates an edit-requiring block from a transient failure', () => {
103
- const commitBody = functionSource('resumeCodeEditorAgent');
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 resumeCodeEditorAgent is a clean-coder edit step bound to the blocker detail and leaves changes uncommitted', () => {
119
- const recoverBody = functionSource('resumeCodeEditorAgent');
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 spawnFixerAgent and fixerWithRecovery', () => {
164
+ test('applyFixes routes through the fix-edit task and fixerWithRecovery', () => {
165
165
  const applyFixesBody = functionSource('applyFixes');
166
- assert.match(applyFixesBody, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
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 spawns a separate verifier and passes both ids into fixerWithRecovery', () => {
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
- /spawnVerifierAgent\(/,
175
- 'expected applyFixes to spawn a separate verifier agent so the fix-path verdict is independent of the fixer',
174
+ /fixerWithRecovery\(head, findings, sourceLabel\)/,
175
+ 'expected applyFixes to delegate the verify and commit flow to fixerWithRecovery',
176
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');
177
+ const recoveryBody = functionSource('fixerWithRecovery');
181
178
  assert.match(
182
- applyFixesBody,
183
- /fixerWithRecovery\(\s*fixerAgentId,\s*verifierId,/,
184
- 'expected applyFixes to pass both the fixer and verifier ids into fixerWithRecovery',
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 on the verifier id and edits/commits on the fixer id', () => {
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
- /resumeVerifierAgent\(\s*verifierId,/,
193
- 'expected fixerWithRecovery to resume the separate verifier for the verify step',
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
- /resumeFixerAgent\(\s*fixerAgentId,\s*'commit'/,
198
- 'expected fixerWithRecovery to resume the fixer for the commit step',
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
- /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
+ /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 resumes a different agent id than the fix-path edits, mirroring the repair path', () => {
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 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');
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
- verifyResumeMatch[1],
215
- editResumeMatch[1],
216
- 'expected the verify step to resume a different agent id than the edit/commit step',
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 group', () => {
221
- const verifierBody = functionSource('resumeVerifierAgent');
222
- assert.match(verifierBody, /task === 'fix-verify'/, 'expected resumeVerifierAgent to handle the fix-verify task');
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('resumeFixerAgent no longer verifies its own edits — no code-verifier verify-commit task remains', () => {
228
- const fixerBody = functionSource('resumeFixerAgent');
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 session',
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 session to be clean-coder only — a separate verifier grades the working tree',
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, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent');
245
- assert.match(repairBody, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent');
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 resumeCodeEditorAgent is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
338
- const recoverBody = functionSource('resumeCodeEditorAgent');
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, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
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 resume helpers', () => {
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, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent for verify');
382
- assert.match(repairBody, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent for recover');
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('resumeGitAgent');
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('resumeCodeEditorAgent conflict-edit path rebases onto origin/main and makes no push', () => {
47
- const body = functionBody('resumeCodeEditorAgent');
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("resumeGitAgent(gitAgentId, 'check-merge-conflicts'");
59
- const editIndex = body.indexOf('spawnCodeEditorAgent(');
60
- const verifyIndex = body.indexOf('spawnVerifierAgent(');
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');