claude-dev-env 1.81.0 → 1.83.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 (74) hide show
  1. package/CLAUDE.md +16 -13
  2. package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  3. package/_shared/pr-loop/scripts/README.md +1 -0
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
  7. package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
  8. package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
  9. package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
  10. package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
  11. package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
  12. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
  13. package/hooks/blocking/CLAUDE.md +3 -0
  14. package/hooks/blocking/code_rules_constants_config.py +159 -1
  15. package/hooks/blocking/code_rules_docstrings.py +312 -9
  16. package/hooks/blocking/code_rules_enforcer.py +29 -0
  17. package/hooks/blocking/code_rules_imports_logging.py +867 -1
  18. package/hooks/blocking/code_rules_naming_collection.py +141 -0
  19. package/hooks/blocking/code_rules_string_magic.py +68 -0
  20. package/hooks/blocking/conventional_pr_title_gate.py +444 -0
  21. package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
  22. package/hooks/blocking/reviewer_spawn_gate.py +182 -0
  23. package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
  24. package/hooks/blocking/state_description_blocker.py +96 -5
  25. package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
  26. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
  27. package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
  28. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
  29. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
  30. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
  31. package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
  32. package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
  33. package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
  34. package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
  35. package/hooks/blocking/test_conventional_pr_title_gate.py +640 -0
  36. package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
  37. package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
  38. package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
  39. package/hooks/blocking/test_state_description_blocker.py +135 -0
  40. package/hooks/hooks.json +10 -0
  41. package/hooks/hooks_constants/CLAUDE.md +4 -1
  42. package/hooks/hooks_constants/blocking_check_limits.py +43 -0
  43. package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
  44. package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +58 -0
  45. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  46. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
  47. package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
  48. package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
  49. package/package.json +1 -1
  50. package/rules/CLAUDE.md +4 -1
  51. package/rules/claude-md-orphan-file.md +5 -0
  52. package/rules/docstring-prose-matches-implementation.md +10 -1
  53. package/rules/env-var-table-code-drift.md +5 -0
  54. package/rules/es-exe-file-search.md +17 -0
  55. package/rules/no-historical-clutter.md +12 -1
  56. package/rules/orphan-css-class.md +5 -0
  57. package/rules/package-inventory-stale-entry.md +10 -0
  58. package/rules/paired-test-coverage.md +5 -0
  59. package/rules/plain-illustrative-docstrings.md +5 -0
  60. package/rules/verify-before-asking.md +7 -0
  61. package/rules/verify-runtime-state.md +40 -0
  62. package/rules/windows-filesystem-safe.md +8 -0
  63. package/rules/workers-done-before-complete.md +33 -0
  64. package/rules/workflow-substitution-slots.md +5 -0
  65. package/skills/CLAUDE.md +1 -1
  66. package/skills/autoconverge/SKILL.md +96 -5
  67. package/skills/autoconverge/reference/gotchas.md +5 -5
  68. package/skills/autoconverge/workflow/converge.contract.test.mjs +228 -27
  69. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +154 -21
  70. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +2 -2
  71. package/skills/autoconverge/workflow/converge.mjs +153 -49
  72. package/skills/autoconverge/workflow/converge_multi.mjs +6 -2
  73. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +26 -0
  74. package/skills/everything-search/SKILL.md +5 -0
@@ -18,9 +18,10 @@ function functionBody(functionName) {
18
18
  const productionModule = new Function(
19
19
  `${functionBody('classifyCopilotOutcome')}\n` +
20
20
  `${functionBody('resolveCopilotDown')}\n` +
21
- 'return { classifyCopilotOutcome, resolveCopilotDown };',
21
+ `${functionBody('resolveReviewerDown')}\n` +
22
+ 'return { classifyCopilotOutcome, resolveCopilotDown, resolveReviewerDown };',
22
23
  )();
23
- const { classifyCopilotOutcome, resolveCopilotDown } = productionModule;
24
+ const { classifyCopilotOutcome, resolveCopilotDown, resolveReviewerDown } = productionModule;
24
25
 
25
26
  function copilotResult(overrides) {
26
27
  return {
@@ -281,7 +282,7 @@ test('the mark-ready task in runGeneralUtilityTask opts the unflagged convergenc
281
282
  );
282
283
  });
283
284
 
284
- test('the COPILOT phase short-circuits on input.copilotDisabled before spawning the gate agent', () => {
285
+ test('the COPILOT phase short-circuits on the unified reviewer-down gate before spawning the gate agent', () => {
285
286
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
286
287
  assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
287
288
  const gateCallIndex = convergeSource.indexOf('await runCopilotGate(head)', copilotPhaseStart);
@@ -289,8 +290,8 @@ test('the COPILOT phase short-circuits on input.copilotDisabled before spawning
289
290
  const beforeGate = convergeSource.slice(copilotPhaseStart, gateCallIndex);
290
291
  assert.match(
291
292
  beforeGate,
292
- /if \(input\.copilotDisabled\)/,
293
- 'expected the quota pre-check bypass to guard the COPILOT phase before any gate agent spawns',
293
+ /if \(resolveReviewerDown\(reviewerAvailability\?\.copilot, input\.copilotDisabled \|\| false\)\)/,
294
+ 'expected the unified resolveReviewerDown gate — fed by the reviewer-availability probe or the input override — to guard the COPILOT phase before any gate agent spawns',
294
295
  );
295
296
  assert.match(beforeGate, /copilotDown = true/, 'expected the bypass to mark copilotDown');
296
297
  assert.match(beforeGate, /copilotNote =/, 'expected the bypass to set a copilotNote');
@@ -298,6 +299,16 @@ test('the COPILOT phase short-circuits on input.copilotDisabled before spawning
298
299
  assert.match(beforeGate, /continue/, 'expected the bypass to continue without spawning the gate agent');
299
300
  });
300
301
 
302
+ test('the COPILOT phase pre-spawn gate skips the gate agent via input.copilotDisabled with no probe entry', () => {
303
+ const isDownFromInput = resolveReviewerDown(undefined, true);
304
+ assert.equal(isDownFromInput, true, 'expected the input override alone to report Copilot down with no probe entry');
305
+ });
306
+
307
+ test('the COPILOT phase pre-spawn gate skips the gate agent via a probe down entry with no input override', () => {
308
+ const isDownFromProbe = resolveReviewerDown({ down: true, reason: 'copilot-quota: out of premium-interaction quota' }, false);
309
+ assert.equal(isDownFromProbe, true, 'expected a probe entry reporting down to skip the gate agent with no input override set');
310
+ });
311
+
301
312
  test('copilotDown initializes from input.copilotDisabled so the pre-check decision carries into the loop', () => {
302
313
  assert.match(
303
314
  convergeSource,
@@ -308,8 +319,11 @@ test('copilotDown initializes from input.copilotDisabled so the pre-check decisi
308
319
 
309
320
  test('a copilotDisabled run reaches FINALIZE with --copilot-down', () => {
310
321
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
311
- const bypassStart = convergeSource.indexOf('if (input.copilotDisabled)', copilotPhaseStart);
312
- assert.notEqual(bypassStart, -1, 'expected an input.copilotDisabled bypass in the COPILOT phase');
322
+ const bypassStart = convergeSource.indexOf(
323
+ 'if (resolveReviewerDown(reviewerAvailability?.copilot, input.copilotDisabled || false))',
324
+ copilotPhaseStart,
325
+ );
326
+ assert.notEqual(bypassStart, -1, 'expected the unified resolveReviewerDown bypass in the COPILOT phase');
313
327
  const bypassBlock = convergeSource.slice(bypassStart, bypassStart + 800);
314
328
  assert.match(bypassBlock, /copilotDown = true/, 'expected the bypass to set copilotDown');
315
329
  assert.match(bypassBlock, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
@@ -373,7 +387,7 @@ test('openStandardsFollowUpOnce gates spawnStandardsFollowUp behind the run-once
373
387
  );
374
388
  assert.match(
375
389
  onceBody,
376
- /await spawnStandardsFollowUp\(head, findings, sourceLabel, wasStandardsHardeningPrOpened\)/,
390
+ /await spawnStandardsFollowUp\(head, findings, sourceLabel, wasStandardsHardeningPrOpened, deferredReviewerFlags\)/,
377
391
  'expected the helper to pass the remembered hardening state into the spawn so an already-opened PR is never re-opened',
378
392
  );
379
393
  assert.match(
@@ -388,8 +402,8 @@ test('openStandardsFollowUpOnce gates spawnStandardsFollowUp behind the run-once
388
402
  );
389
403
  assert.match(
390
404
  onceBody,
391
- /return wasStandardsHardeningPrOpened/,
392
- 'expected the skip path to return the cached hardening outcome',
405
+ /hardeningPrOpened: wasStandardsHardeningPrOpened/,
406
+ 'expected the helper to return the cached hardening outcome as hardeningPrOpened',
393
407
  );
394
408
  });
395
409
 
@@ -431,14 +445,18 @@ function extractCallableSource(functionName) {
431
445
  return convergeSource.slice(declarationStart, index);
432
446
  }
433
447
 
434
- function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult) {
448
+ const parseableHardeningCommitResult = { hardeningPrUrl: 'https://github.com/owner/repo/pull/7', summary: 'opened' };
449
+
450
+ function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult, hardeningCommitResult = parseableHardeningCommitResult) {
435
451
  const runtimeSource =
436
452
  'let hasStandardsFollowUpFiled = false;\n' +
437
453
  'let wasStandardsHardeningPrOpened = false;\n' +
438
454
  "let standardsFollowUpIssueUrl = '';\n" +
439
455
  'async function runCodeEditorTask(taskName, context) {\n' +
440
456
  ' recordedCalls.push({ task: taskName, context });\n' +
441
- " return taskName === 'standards-edit' ? standardsEditResult : {};\n" +
457
+ " if (taskName === 'standards-edit') return standardsEditResult;\n" +
458
+ " if (taskName === 'hardening-commit') return hardeningCommitResult;\n" +
459
+ ' return {};\n' +
442
460
  '}\n' +
443
461
  'async function runVerifierTask() {\n' +
444
462
  ' return { passed: true };\n' +
@@ -450,6 +468,7 @@ function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult) {
450
468
  `${extractCallableSource('collectFindingThreadIds')}\n` +
451
469
  `${extractCallableSource('findingsCarryThreads')}\n` +
452
470
  `${extractCallableSource('shouldOpenStandardsFollowUp')}\n` +
471
+ `${extractCallableSource('parseDeferredPr')}\n` +
453
472
  `${extractCallableSource('spawnStandardsFollowUp')}\n` +
454
473
  `${extractCallableSource('resolveStandardsThreadsForBatch')}\n` +
455
474
  `${extractCallableSource('openStandardsFollowUpOnce')}\n` +
@@ -457,12 +476,45 @@ function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult) {
457
476
  ' openStandardsFollowUpOnce,\n' +
458
477
  ' guards: () => ({ hasStandardsFollowUpFiled, wasStandardsHardeningPrOpened, standardsFollowUpIssueUrl }),\n' +
459
478
  '};';
460
- return new Function('recordedCalls', 'standardsEditResult', runtimeSource)(
479
+ return new Function('recordedCalls', 'standardsEditResult', 'hardeningCommitResult', runtimeSource)(
461
480
  recordedCalls,
462
481
  standardsEditResult,
482
+ hardeningCommitResult,
463
483
  );
464
484
  }
465
485
 
486
+ function loadParseDeferredPr() {
487
+ return new Function(`${extractCallableSource('parseDeferredPr')}\nreturn parseDeferredPr;`)();
488
+ }
489
+
490
+ test('parseDeferredPr parses a full canonical hardening PR URL into its coordinates', () => {
491
+ const parseDeferredPr = loadParseDeferredPr();
492
+ assert.deepEqual(
493
+ parseDeferredPr('https://github.com/jl-cmd/claude-code-config/pull/824'),
494
+ { owner: 'jl-cmd', repo: 'claude-code-config', prNumber: 824 },
495
+ );
496
+ });
497
+
498
+ test('parseDeferredPr accepts a trailing slash, query string, or fragment on the PR URL', () => {
499
+ const parseDeferredPr = loadParseDeferredPr();
500
+ assert.equal(parseDeferredPr('https://github.com/owner/repo/pull/7/').prNumber, 7);
501
+ assert.equal(parseDeferredPr('https://github.com/owner/repo/pull/7?w=1').prNumber, 7);
502
+ assert.equal(parseDeferredPr('https://github.com/owner/repo/pull/7#issuecomment-42').prNumber, 7);
503
+ });
504
+
505
+ test('parseDeferredPr rejects a PR URL embedded in surrounding log text so it never parses the wrong number', () => {
506
+ const parseDeferredPr = loadParseDeferredPr();
507
+ assert.equal(
508
+ parseDeferredPr('opened https://github.com/owner/repo/pull/7 then https://github.com/owner/repo/pull/9'),
509
+ null,
510
+ );
511
+ });
512
+
513
+ test('parseDeferredPr rejects a deep-linked pull path so a non-canonical URL parses no coordinate', () => {
514
+ const parseDeferredPr = loadParseDeferredPr();
515
+ assert.equal(parseDeferredPr('https://github.com/owner/repo/pull/7/files'), null);
516
+ });
517
+
466
518
  test('a second standards-only round never re-opens a hardening PR after the first round opened one but failed to file the issue', async () => {
467
519
  const recordedCalls = [];
468
520
  const issueFailedHardeningStaged = {
@@ -473,8 +525,8 @@ test('a second standards-only round never re-opens a hardening PR after the firs
473
525
  };
474
526
  const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFailedHardeningStaged);
475
527
 
476
- const firstRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'converge-round');
477
- const secondRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'copilot');
528
+ const firstRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'converge-round', { copilotDisabled: false, bugbotDisabled: false });
529
+ const secondRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'copilot', { copilotDisabled: false, bugbotDisabled: false });
478
530
 
479
531
  const hardeningCommitCalls = recordedCalls.filter((call) => call.task === 'hardening-commit').length;
480
532
  assert.equal(
@@ -482,8 +534,8 @@ test('a second standards-only round never re-opens a hardening PR after the firs
482
534
  1,
483
535
  'expected the hardening PR to be committed exactly once even when the follow-up issue filing must retry on the second round',
484
536
  );
485
- assert.equal(firstRoundHardeningPr, true, 'expected the first round to open the hardening PR');
486
- assert.equal(secondRoundHardeningPr, true, 'expected the second round to report the hardening PR as opened for this run');
537
+ assert.equal(firstRoundHardeningPr.hardeningPrOpened, true, 'expected the first round to open the hardening PR');
538
+ assert.equal(secondRoundHardeningPr.hardeningPrOpened, true, 'expected the second round to report the hardening PR as opened for this run');
487
539
  assert.equal(
488
540
  runtime.guards().wasStandardsHardeningPrOpened,
489
541
  true,
@@ -496,6 +548,65 @@ test('a second standards-only round never re-opens a hardening PR after the firs
496
548
  );
497
549
  });
498
550
 
551
+ test('a hardening-commit that opens a PR but returns an unparseable URL still latches the run-once guard', async () => {
552
+ const recordedCalls = [];
553
+ const issueFailedHardeningStaged = {
554
+ issueUrl: '',
555
+ hardeningEdited: true,
556
+ hardeningRepoPath: '/tmp/hardening',
557
+ hardeningBranch: 'harden-standards',
558
+ };
559
+ const unparseableUrlHardeningCommitResult = { hardeningPrUrl: 'draft-hardening-pr-opened', summary: 'opened' };
560
+ const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFailedHardeningStaged, unparseableUrlHardeningCommitResult);
561
+
562
+ const firstRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'converge-round', { copilotDisabled: false, bugbotDisabled: false });
563
+ const secondRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'copilot', { copilotDisabled: false, bugbotDisabled: false });
564
+
565
+ const hardeningCommitCalls = recordedCalls.filter((call) => call.task === 'hardening-commit').length;
566
+ assert.equal(
567
+ hardeningCommitCalls,
568
+ 1,
569
+ 'expected the non-empty-URL commit to latch the guard so a second round opens no duplicate hardening PR',
570
+ );
571
+ assert.equal(firstRoundHardeningPr.hardeningPrOpened, true, 'expected a non-empty (though unparseable) URL to report the hardening PR as opened');
572
+ assert.equal(firstRoundHardeningPr.deferredPr, null, 'expected the unparseable URL to contribute no deferred coordinate');
573
+ assert.equal(secondRoundHardeningPr.hardeningPrOpened, true, 'expected the second round to report the hardening PR as opened for this run');
574
+ assert.equal(
575
+ runtime.guards().wasStandardsHardeningPrOpened,
576
+ true,
577
+ 'expected the hardening guard to latch even though the returned URL never parsed',
578
+ );
579
+ });
580
+
581
+ test('a hardening-commit that opens no PR (empty hardeningPrUrl) leaves the run-once guard clear so a later round retries the open', async () => {
582
+ const recordedCalls = [];
583
+ const issueFailedHardeningStaged = {
584
+ issueUrl: '',
585
+ hardeningEdited: true,
586
+ hardeningRepoPath: '/tmp/hardening',
587
+ hardeningBranch: 'harden-standards',
588
+ };
589
+ const noPrHardeningCommitResult = { hardeningPrUrl: '', summary: 'no PR opened' };
590
+ const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFailedHardeningStaged, noPrHardeningCommitResult);
591
+
592
+ const firstRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'converge-round', { copilotDisabled: false, bugbotDisabled: false });
593
+ const secondRoundHardeningPr = await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1 }], 'copilot', { copilotDisabled: false, bugbotDisabled: false });
594
+
595
+ const hardeningCommitCalls = recordedCalls.filter((call) => call.task === 'hardening-commit').length;
596
+ assert.equal(
597
+ hardeningCommitCalls,
598
+ 2,
599
+ 'expected the empty-URL commit (no PR opened) to leave the guard clear so a later round retries the open',
600
+ );
601
+ assert.equal(firstRoundHardeningPr.hardeningPrOpened, false, 'expected an empty URL to report no hardening PR opened');
602
+ assert.equal(secondRoundHardeningPr.hardeningPrOpened, false, 'expected the retry round to still report no hardening PR opened');
603
+ assert.equal(
604
+ runtime.guards().wasStandardsHardeningPrOpened,
605
+ false,
606
+ 'expected the hardening guard to stay clear when no PR opened so the open keeps retrying',
607
+ );
608
+ });
609
+
499
610
  test('a later standards-only round resolves its own review threads after the follow-up issue was already filed', async () => {
500
611
  const recordedCalls = [];
501
612
  const issueFiledNoHardening = {
@@ -506,8 +617,8 @@ test('a later standards-only round resolves its own review threads after the fol
506
617
  };
507
618
  const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFiledNoHardening);
508
619
 
509
- await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1, replyToCommentId: null }], 'converge-round');
510
- await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'b.py', line: 2, replyToCommentId: 42 }], 'copilot');
620
+ await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1, replyToCommentId: null }], 'converge-round', { copilotDisabled: false, bugbotDisabled: false });
621
+ await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'b.py', line: 2, replyToCommentId: 42 }], 'copilot', { copilotDisabled: false, bugbotDisabled: false });
511
622
 
512
623
  const standardsEditCalls = recordedCalls.filter((call) => call.task === 'standards-edit');
513
624
  assert.equal(standardsEditCalls.length, 1, 'expected the follow-up issue to be filed exactly once across the run');
@@ -536,8 +647,8 @@ test('a reuse-path standards round carrying no review threads spawns no thread-r
536
647
  };
537
648
  const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFiledNoHardening);
538
649
 
539
- await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1, replyToCommentId: null }], 'converge-round');
540
- await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'b.py', line: 2, replyToCommentId: null }], 'copilot');
650
+ await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'a.py', line: 1, replyToCommentId: null }], 'converge-round', { copilotDisabled: false, bugbotDisabled: false });
651
+ await runtime.openStandardsFollowUpOnce('sha1', [{ file: 'b.py', line: 2, replyToCommentId: null }], 'copilot', { copilotDisabled: false, bugbotDisabled: false });
541
652
 
542
653
  const resolveCalls = recordedCalls.filter((call) => call.task === 'standards-resolve-threads');
543
654
  assert.equal(
@@ -547,3 +658,25 @@ test('a reuse-path standards round carrying no review threads spawns no thread-r
547
658
  );
548
659
  });
549
660
 
661
+ test('resolveReviewerDown is the single reviewer-down gate; resolveBugbotDown no longer exists', () => {
662
+ assert.doesNotMatch(
663
+ convergeSource,
664
+ /function resolveBugbotDown\(/,
665
+ 'expected resolveBugbotDown to be removed in favor of the shared resolveReviewerDown gate',
666
+ );
667
+ assert.match(convergeSource, /function resolveReviewerDown\(/, 'expected the shared resolveReviewerDown gate to exist');
668
+ });
669
+
670
+ test('resolveReviewerDown reports down when the input override is set, even with an available probe entry', () => {
671
+ assert.equal(resolveReviewerDown({ down: false, reason: 'available' }, true), true);
672
+ });
673
+
674
+ test('resolveReviewerDown reports available (fail-open) when the probe entry is missing and no input override is set', () => {
675
+ assert.equal(resolveReviewerDown(null, false), false);
676
+ assert.equal(resolveReviewerDown(undefined, false), false);
677
+ });
678
+
679
+ test('resolveReviewerDown reports available when the probe entry explicitly reports available', () => {
680
+ assert.equal(resolveReviewerDown({ down: false, reason: 'available' }, false), false);
681
+ });
682
+
@@ -31,7 +31,7 @@ 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('the git agent handles merge-conflict checks with shell-agnostic polling', () => {
34
+ test('the git agent handles merge-conflict checks with a Monitor-based poll', () => {
35
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(
@@ -40,7 +40,7 @@ test('the git agent handles merge-conflict checks with shell-agnostic polling',
40
40
  'expected the git agent merge check to be read-only',
41
41
  );
42
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');
43
+ assert.match(gitBody, /Monitor tool/, 'expected a Monitor-based poll delay, not a foreground sleep');
44
44
  });
45
45
 
46
46
  test('runCodeEditorTask conflict-edit path rebases onto origin/main and makes no push', () => {