ReviewOS

also looking at this

stacks/buddy

fix: let the satisfied-PR closer consult the manifest, and reject phantom paths from PR bodies

#1447
Merged glennmichael123 wants to merge fix/satisfied-pr-verification into main
2 files +134 -11
test/buddy-orchestration.test.tsmodified+65-0
Changes to test/buddy-orchestration.test.ts
@@ -364,6 +364,46 @@ describe('buddy orchestration', () => {
364364 expect(provider.deleteBranch).toHaveBeenCalledWith('buddy/update-lodash')
365365 })
366366
367 it('success case - closes a fully satisfied PR by consulting the manifest when its packages left the scan', async () => {
368 // The scan lists only what is still outdated, so a fully satisfied PR
369 // has no packages in it at all — the manifest is the positive evidence.
370 const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'buddy-close-'))
371 fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({
372 devDependencies: { typescript: '^7.0.2' },
373 }))
374 restorers.push(() => fs.rmSync(projectDir, { recursive: true, force: true }))
375
376 stubScan([makeUpdate({ name: 'react', currentVersion: '18.0.0', newVersion: '18.2.0' })])
377 const provider = makeCloseProvider([makePR({
378 number: 125,
379 head: 'buddy/update-typescript',
380 body: `updates${serializeManifest([makeUpdate({ name: 'typescript', currentVersion: '6.0.3', newVersion: '7.0.2' })])}`,
381 })])
382 const buddy = new Buddy(baseConfig(), projectDir)
383
384 await buddy.checkAndCloseSatisfiedPRs(provider, false)
385
386 expect(provider.closePullRequest).toHaveBeenCalledWith(125)
387 const [, comment] = provider.createComment.mock.calls[0]
388 expect(comment).toContain('typescript')
389 })
390
391 it('failure case - keeps the PR when the manifest declares a version still behind the target', async () => {
392 const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'buddy-close-'))
393 fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({
394 dependencies: { lodash: '^4.17.20' },
395 }))
396 restorers.push(() => fs.rmSync(projectDir, { recursive: true, force: true }))
397
398 stubScan([makeUpdate({ name: 'react', currentVersion: '18.0.0', newVersion: '18.2.0' })])
399 const provider = makeCloseProvider([lodashPR()])
400 const buddy = new Buddy(baseConfig(), projectDir)
401
402 await buddy.checkAndCloseSatisfiedPRs(provider, false)
403
404 expect(provider.closePullRequest).not.toHaveBeenCalled()
405 })
406
367407 it('failure case - never closes on an empty scan (mass-closure guard)', async () => {
368408 // A scan returning nothing may mean bun outdated failed or the registry
369409 // rate-limited — closing every PR on that evidence would wipe them out.
@@ -430,6 +470,31 @@ describe('buddy orchestration', () => {
430470 })
431471 })
432472
473 describe('extractFilePathsFromPRBody', () => {
474 it('failure case - renovate-era badge tables yield no phantom paths', () => {
475 const buddy = new Buddy(baseConfig()) as any
476 const body = [
477 '| Package | Change | Age | Adoption | Passing | Confidence |',
478 '|---|---|---|---|---|---|',
479 '| [typescript](https://github.com/microsoft/TypeScript/tree/master) | [`6.0.3` -> `7.0.2`](https://renovatebot.com/diffs/npm/typescript/6.0.3/7.0.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/typescript/7.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/typescript/7.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | ok | ok |',
480 ].join('\n')
481
482 expect(buddy.extractFilePathsFromPRBody(body)).toEqual([])
483 })
484
485 it('success case - real file cells and bold paths still extract', () => {
486 const buddy = new Buddy(baseConfig()) as any
487 const body = [
488 '| [lodash](https://npmjs.com/lodash) | ^4.17.20 → ^4.17.21 | **package.json** | ✅ |',
489 'Also touches **.github/workflows/ci.yml** along the way.',
490 ].join('\n')
491
492 const paths = buddy.extractFilePathsFromPRBody(body)
493 expect(paths).toContain('package.json')
494 expect(paths).toContain('.github/workflows/ci.yml')
495 })
496 })
497
433498 describe('compareVersionsSafe', () => {
434499 it('distinguishes at-or-beyond, behind, and failed', () => {
435500 const buddy = new Buddy(baseConfig()) as any