claude-dev-env 1.79.0 → 1.81.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 +3 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +116 -30
- package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +13 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +113 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +467 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +8 -0
- package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +339 -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/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +297 -0
- package/audit-rubrics/CLAUDE.md +3 -2
- package/audit-rubrics/category_rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +2 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +13 -1
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +19 -0
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +46 -0
- package/audit-rubrics/prompts/CLAUDE.md +2 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +1 -0
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +19 -7
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +14 -0
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +51 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/code_rules_annotations_length.py +59 -11
- package/hooks/blocking/code_rules_banned_identifiers.py +48 -9
- package/hooks/blocking/code_rules_command_dispatch.py +140 -0
- package/hooks/blocking/code_rules_docstrings.py +93 -0
- package/hooks/blocking/code_rules_enforcer.py +58 -4
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_js_conventions.py +246 -0
- package/hooks/blocking/code_rules_naming_collection.py +5 -0
- package/hooks/blocking/code_rules_test_assertions.py +3 -0
- package/hooks/blocking/code_rules_unused_imports.py +7 -66
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +7 -0
- package/hooks/blocking/test_code_rules_command_dispatch.py +95 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +20 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +10 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +8 -4
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +9 -3
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +164 -0
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +112 -18
- package/hooks/blocking/test_code_rules_js_conventions.py +167 -0
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/blocking_check_limits.py +10 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +14 -0
- package/hooks/hooks_constants/command_dispatch_constants.py +28 -0
- package/hooks/hooks_constants/js_conventions_constants.py +54 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
- package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +3 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +43 -1
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +2 -2
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +1 -4
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +100 -4
- package/skills/autoconverge/SKILL.md +28 -7
- package/skills/autoconverge/reference/convergence.md +3 -3
- package/skills/autoconverge/reference/stop-conditions.md +1 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +58 -122
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +289 -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 +235 -247
- 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,
|
|
@@ -385,8 +385,8 @@ test('spawnStandardsFollowUp reports whether a hardening PR opened on every path
|
|
|
385
385
|
const body = lensPromptBody('spawnStandardsFollowUp');
|
|
386
386
|
const falseReturns = body.match(/hardeningPrOpened:\s*false/g) || [];
|
|
387
387
|
assert.ok(
|
|
388
|
-
falseReturns.length >=
|
|
389
|
-
'expected
|
|
388
|
+
falseReturns.length >= 3,
|
|
389
|
+
'expected every skip path (hardening PR already opened, no hardening staged, verify failed) to return hardeningPrOpened:false',
|
|
390
390
|
);
|
|
391
391
|
assert.match(
|
|
392
392
|
body,
|
|
@@ -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,30 @@ 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
|
|
132
|
+
test('the Copilot gate accepts a COMMENTED review with no inline findings as a clean pass', () => {
|
|
133
|
+
const copilotPrompt = functionBody('runCopilotGate');
|
|
134
|
+
assert.match(
|
|
135
|
+
copilotPrompt,
|
|
136
|
+
/COMMENTED with no inline findings/,
|
|
137
|
+
'expected the clean-pass branch to accept a COMMENTED review with no inline findings, matching the canonical ALL_COPILOT_CLEAN_REVIEW_STATES rule where a Copilot COMMENTED review is a clean state',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('the Copilot gate polls long enough to catch a normal-latency Copilot review', () => {
|
|
142
|
+
const budgetMatch = convergeSource.match(/copilotMaxPolls:\s*(\d+)/);
|
|
143
|
+
assert.notEqual(budgetMatch, null, 'expected copilotMaxPolls in CONFIG');
|
|
144
|
+
assert.ok(
|
|
145
|
+
Number(budgetMatch[1]) >= 6,
|
|
146
|
+
`expected copilotMaxPolls >= 6 (>= ~36 min at 360s per attempt) so a normal-latency Copilot review lands before the no-show fallback triggers, got ${budgetMatch[1]}`,
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('runConvergenceCheck wires the --copilot-down flag from the copilotDown context', () => {
|
|
151
|
+
const checkConvergenceBody = functionBody('runConvergenceCheck');
|
|
134
152
|
assert.match(
|
|
135
153
|
checkConvergenceBody,
|
|
136
154
|
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
-
'expected
|
|
155
|
+
'expected runConvergenceCheck to append --copilot-down when copilotDown is set',
|
|
138
156
|
);
|
|
139
157
|
assert.match(
|
|
140
158
|
checkConvergenceBody,
|
|
@@ -244,8 +262,8 @@ test('markReady receives copilotDown so it can opt the unflagged hook out of the
|
|
|
244
262
|
);
|
|
245
263
|
});
|
|
246
264
|
|
|
247
|
-
test('the mark-ready task in
|
|
248
|
-
const markReadyBody = functionBody('
|
|
265
|
+
test('the mark-ready task in runGeneralUtilityTask opts the unflagged convergence hook out of Copilot when copilotDown', () => {
|
|
266
|
+
const markReadyBody = functionBody('runGeneralUtilityTask');
|
|
249
267
|
assert.match(
|
|
250
268
|
markReadyBody,
|
|
251
269
|
/context\.copilotDown/,
|
|
@@ -263,3 +281,269 @@ test('the mark-ready task in resumeGeneralUtilityAgent opts the unflagged conver
|
|
|
263
281
|
);
|
|
264
282
|
});
|
|
265
283
|
|
|
284
|
+
test('the COPILOT phase short-circuits on input.copilotDisabled before spawning the gate agent', () => {
|
|
285
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
286
|
+
assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
|
|
287
|
+
const gateCallIndex = convergeSource.indexOf('await runCopilotGate(head)', copilotPhaseStart);
|
|
288
|
+
assert.notEqual(gateCallIndex, -1, 'expected the COPILOT phase to call runCopilotGate when Copilot is enabled');
|
|
289
|
+
const beforeGate = convergeSource.slice(copilotPhaseStart, gateCallIndex);
|
|
290
|
+
assert.match(
|
|
291
|
+
beforeGate,
|
|
292
|
+
/if \(input\.copilotDisabled\)/,
|
|
293
|
+
'expected the quota pre-check bypass to guard the COPILOT phase before any gate agent spawns',
|
|
294
|
+
);
|
|
295
|
+
assert.match(beforeGate, /copilotDown = true/, 'expected the bypass to mark copilotDown');
|
|
296
|
+
assert.match(beforeGate, /copilotNote =/, 'expected the bypass to set a copilotNote');
|
|
297
|
+
assert.match(beforeGate, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
|
|
298
|
+
assert.match(beforeGate, /continue/, 'expected the bypass to continue without spawning the gate agent');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('copilotDown initializes from input.copilotDisabled so the pre-check decision carries into the loop', () => {
|
|
302
|
+
assert.match(
|
|
303
|
+
convergeSource,
|
|
304
|
+
/let copilotDown = input\.copilotDisabled \|\| false/,
|
|
305
|
+
'expected copilotDown to seed from the copilotDisabled run input',
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('a copilotDisabled run reaches FINALIZE with --copilot-down', () => {
|
|
310
|
+
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');
|
|
313
|
+
const bypassBlock = convergeSource.slice(bypassStart, bypassStart + 800);
|
|
314
|
+
assert.match(bypassBlock, /copilotDown = true/, 'expected the bypass to set copilotDown');
|
|
315
|
+
assert.match(bypassBlock, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
|
|
316
|
+
const convergenceCheckBody = functionBody('runConvergenceCheck');
|
|
317
|
+
assert.match(
|
|
318
|
+
convergenceCheckBody,
|
|
319
|
+
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
320
|
+
'expected the convergence check to pass --copilot-down when the bypassed copilotDown reaches FINALIZE',
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
function loadStandardsFollowUpDecision() {
|
|
325
|
+
return new Function(
|
|
326
|
+
`${functionBody('shouldOpenStandardsFollowUp')}\n` +
|
|
327
|
+
'return shouldOpenStandardsFollowUp;',
|
|
328
|
+
)();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
test('the deferred follow-up create runs exactly once across a three-round standards run', () => {
|
|
332
|
+
const shouldOpenStandardsFollowUp = loadStandardsFollowUpDecision();
|
|
333
|
+
let alreadyOpened = false;
|
|
334
|
+
let createCount = 0;
|
|
335
|
+
for (let round = 0; round < 3; round += 1) {
|
|
336
|
+
if (shouldOpenStandardsFollowUp(alreadyOpened)) {
|
|
337
|
+
createCount += 1;
|
|
338
|
+
alreadyOpened = true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
assert.equal(
|
|
342
|
+
createCount,
|
|
343
|
+
1,
|
|
344
|
+
'expected the deferred follow-up issue and hardening PR to be created exactly once across a multi-round run',
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('shouldOpenStandardsFollowUp opens on the first standards-only round and skips afterward', () => {
|
|
349
|
+
const shouldOpenStandardsFollowUp = loadStandardsFollowUpDecision();
|
|
350
|
+
assert.equal(shouldOpenStandardsFollowUp(false), true);
|
|
351
|
+
assert.equal(shouldOpenStandardsFollowUp(true), false);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('the run seeds the standards follow-up flags to false so the first standards-only round opens the follow-up', () => {
|
|
355
|
+
assert.match(
|
|
356
|
+
convergeSource,
|
|
357
|
+
/let hasStandardsFollowUpFiled = false/,
|
|
358
|
+
'expected a run-scoped hasStandardsFollowUpFiled flag seeded false',
|
|
359
|
+
);
|
|
360
|
+
assert.match(
|
|
361
|
+
convergeSource,
|
|
362
|
+
/let wasStandardsHardeningPrOpened = false/,
|
|
363
|
+
'expected a run-scoped wasStandardsHardeningPrOpened flag seeded false',
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('openStandardsFollowUpOnce gates spawnStandardsFollowUp behind the run-once flag and remembers the outcome', () => {
|
|
368
|
+
const onceBody = extractCallableSource('openStandardsFollowUpOnce');
|
|
369
|
+
assert.match(
|
|
370
|
+
onceBody,
|
|
371
|
+
/shouldOpenStandardsFollowUp\(hasStandardsFollowUpFiled\)/,
|
|
372
|
+
'expected the helper to consult the run-once decision on the current flag',
|
|
373
|
+
);
|
|
374
|
+
assert.match(
|
|
375
|
+
onceBody,
|
|
376
|
+
/await spawnStandardsFollowUp\(head, findings, sourceLabel, wasStandardsHardeningPrOpened\)/,
|
|
377
|
+
'expected the helper to pass the remembered hardening state into the spawn so an already-opened PR is never re-opened',
|
|
378
|
+
);
|
|
379
|
+
assert.match(
|
|
380
|
+
onceBody,
|
|
381
|
+
/hasStandardsFollowUpFiled = standardsOutcome\?\.followUpIssueFiled === true/,
|
|
382
|
+
'expected the helper to latch the flag only when the follow-up issue was actually filed so a transient failure retries on a later round',
|
|
383
|
+
);
|
|
384
|
+
assert.match(
|
|
385
|
+
onceBody,
|
|
386
|
+
/wasStandardsHardeningPrOpened = wasStandardsHardeningPrOpened \|\| standardsOutcome\?\.hardeningPrOpened === true/,
|
|
387
|
+
'expected the hardening guard to latch the moment a hardening PR opens and stay latched across rounds so a later issue-filing retry never re-opens it',
|
|
388
|
+
);
|
|
389
|
+
assert.match(
|
|
390
|
+
onceBody,
|
|
391
|
+
/return wasStandardsHardeningPrOpened/,
|
|
392
|
+
'expected the skip path to return the cached hardening outcome',
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('both standards-deferral call sites route the create through openStandardsFollowUpOnce', () => {
|
|
397
|
+
const onceCalls = convergeSource.match(/await openStandardsFollowUpOnce\(/g) || [];
|
|
398
|
+
assert.equal(
|
|
399
|
+
onceCalls.length,
|
|
400
|
+
2,
|
|
401
|
+
'expected the converge-round and copilot standards call sites to both defer to openStandardsFollowUpOnce',
|
|
402
|
+
);
|
|
403
|
+
const directCreates = convergeSource.match(/await spawnStandardsFollowUp\(/g) || [];
|
|
404
|
+
assert.equal(
|
|
405
|
+
directCreates.length,
|
|
406
|
+
1,
|
|
407
|
+
'expected spawnStandardsFollowUp to be invoked once from openStandardsFollowUpOnce, never directly at a call site',
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
function extractCallableSource(functionName) {
|
|
412
|
+
const asyncStart = convergeSource.indexOf(`async function ${functionName}(`);
|
|
413
|
+
const plainStart = convergeSource.indexOf(`function ${functionName}(`);
|
|
414
|
+
const declarationStart = asyncStart !== -1 ? asyncStart : plainStart;
|
|
415
|
+
assert.notEqual(declarationStart, -1, `expected ${functionName} to exist`);
|
|
416
|
+
const bodyStart = convergeSource.indexOf('{', declarationStart);
|
|
417
|
+
let depth = 0;
|
|
418
|
+
let index = bodyStart;
|
|
419
|
+
for (; index < convergeSource.length; index += 1) {
|
|
420
|
+
const character = convergeSource[index];
|
|
421
|
+
if (character === '{') {
|
|
422
|
+
depth += 1;
|
|
423
|
+
} else if (character === '}') {
|
|
424
|
+
depth -= 1;
|
|
425
|
+
if (depth === 0) {
|
|
426
|
+
index += 1;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return convergeSource.slice(declarationStart, index);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult) {
|
|
435
|
+
const runtimeSource =
|
|
436
|
+
'let hasStandardsFollowUpFiled = false;\n' +
|
|
437
|
+
'let wasStandardsHardeningPrOpened = false;\n' +
|
|
438
|
+
"let standardsFollowUpIssueUrl = '';\n" +
|
|
439
|
+
'async function runCodeEditorTask(taskName, context) {\n' +
|
|
440
|
+
' recordedCalls.push({ task: taskName, context });\n' +
|
|
441
|
+
" return taskName === 'standards-edit' ? standardsEditResult : {};\n" +
|
|
442
|
+
'}\n' +
|
|
443
|
+
'async function runVerifierTask() {\n' +
|
|
444
|
+
' return { passed: true };\n' +
|
|
445
|
+
'}\n' +
|
|
446
|
+
'function verdictPassed() {\n' +
|
|
447
|
+
' return true;\n' +
|
|
448
|
+
'}\n' +
|
|
449
|
+
'function log() {}\n' +
|
|
450
|
+
`${extractCallableSource('collectFindingThreadIds')}\n` +
|
|
451
|
+
`${extractCallableSource('findingsCarryThreads')}\n` +
|
|
452
|
+
`${extractCallableSource('shouldOpenStandardsFollowUp')}\n` +
|
|
453
|
+
`${extractCallableSource('spawnStandardsFollowUp')}\n` +
|
|
454
|
+
`${extractCallableSource('resolveStandardsThreadsForBatch')}\n` +
|
|
455
|
+
`${extractCallableSource('openStandardsFollowUpOnce')}\n` +
|
|
456
|
+
'return {\n' +
|
|
457
|
+
' openStandardsFollowUpOnce,\n' +
|
|
458
|
+
' guards: () => ({ hasStandardsFollowUpFiled, wasStandardsHardeningPrOpened, standardsFollowUpIssueUrl }),\n' +
|
|
459
|
+
'};';
|
|
460
|
+
return new Function('recordedCalls', 'standardsEditResult', runtimeSource)(
|
|
461
|
+
recordedCalls,
|
|
462
|
+
standardsEditResult,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
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
|
+
const recordedCalls = [];
|
|
468
|
+
const issueFailedHardeningStaged = {
|
|
469
|
+
issueUrl: '',
|
|
470
|
+
hardeningEdited: true,
|
|
471
|
+
hardeningRepoPath: '/tmp/hardening',
|
|
472
|
+
hardeningBranch: 'harden-standards',
|
|
473
|
+
};
|
|
474
|
+
const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFailedHardeningStaged);
|
|
475
|
+
|
|
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');
|
|
478
|
+
|
|
479
|
+
const hardeningCommitCalls = recordedCalls.filter((call) => call.task === 'hardening-commit').length;
|
|
480
|
+
assert.equal(
|
|
481
|
+
hardeningCommitCalls,
|
|
482
|
+
1,
|
|
483
|
+
'expected the hardening PR to be committed exactly once even when the follow-up issue filing must retry on the second round',
|
|
484
|
+
);
|
|
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');
|
|
487
|
+
assert.equal(
|
|
488
|
+
runtime.guards().wasStandardsHardeningPrOpened,
|
|
489
|
+
true,
|
|
490
|
+
'expected the hardening guard to stay latched across rounds',
|
|
491
|
+
);
|
|
492
|
+
assert.equal(
|
|
493
|
+
runtime.guards().hasStandardsFollowUpFiled,
|
|
494
|
+
false,
|
|
495
|
+
'expected the issue guard to stay clear so the filing keeps retrying',
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('a later standards-only round resolves its own review threads after the follow-up issue was already filed', async () => {
|
|
500
|
+
const recordedCalls = [];
|
|
501
|
+
const issueFiledNoHardening = {
|
|
502
|
+
issueUrl: 'https://github.com/jl-cmd/claude-code-config/issues/900',
|
|
503
|
+
hardeningEdited: false,
|
|
504
|
+
hardeningRepoPath: '',
|
|
505
|
+
hardeningBranch: '',
|
|
506
|
+
};
|
|
507
|
+
const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFiledNoHardening);
|
|
508
|
+
|
|
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');
|
|
511
|
+
|
|
512
|
+
const standardsEditCalls = recordedCalls.filter((call) => call.task === 'standards-edit');
|
|
513
|
+
assert.equal(standardsEditCalls.length, 1, 'expected the follow-up issue to be filed exactly once across the run');
|
|
514
|
+
|
|
515
|
+
const resolveCalls = recordedCalls.filter((call) => call.task === 'standards-resolve-threads');
|
|
516
|
+
assert.equal(resolveCalls.length, 1, 'expected the reuse-path round to resolve its own batch review threads');
|
|
517
|
+
assert.deepEqual(
|
|
518
|
+
resolveCalls[0].context.findings,
|
|
519
|
+
[{ file: 'b.py', line: 2, replyToCommentId: 42 }],
|
|
520
|
+
'expected the resolve step to receive the reuse-path batch findings so their threads get replied-to and resolved',
|
|
521
|
+
);
|
|
522
|
+
assert.equal(
|
|
523
|
+
resolveCalls[0].context.issueUrl,
|
|
524
|
+
'https://github.com/jl-cmd/claude-code-config/issues/900',
|
|
525
|
+
'expected the resolve step to reference the already-filed follow-up issue in its inline reply',
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('a reuse-path standards round carrying no review threads spawns no thread-resolution agent', async () => {
|
|
530
|
+
const recordedCalls = [];
|
|
531
|
+
const issueFiledNoHardening = {
|
|
532
|
+
issueUrl: 'https://github.com/jl-cmd/claude-code-config/issues/901',
|
|
533
|
+
hardeningEdited: false,
|
|
534
|
+
hardeningRepoPath: '',
|
|
535
|
+
hardeningBranch: '',
|
|
536
|
+
};
|
|
537
|
+
const runtime = loadStandardsFollowUpRuntime(recordedCalls, issueFiledNoHardening);
|
|
538
|
+
|
|
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');
|
|
541
|
+
|
|
542
|
+
const resolveCalls = recordedCalls.filter((call) => call.task === 'standards-resolve-threads');
|
|
543
|
+
assert.equal(
|
|
544
|
+
resolveCalls.length,
|
|
545
|
+
0,
|
|
546
|
+
'expected no thread-resolution agent when the reuse-path batch of in-memory findings carries no review threads',
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
|