ReviewOS

stacks/buddy

Structured PR metadata manifest — replace regex PR-body parsing

#1368
Closed chris opened this 9 days ago ·
9 days ago

Part of #1367 (Phase 0 — Foundation).

Problem

Buddy Bot reconstructs PR state by regex-matching its own rendered markdown. rebase and the auto-close logic re-parse package names/versions out of PR body tables, and the dashboard extracts packages from PR titles/bodies the same way:

  • bin/cli.ts:873 — rebase reconstructs updates from the PR body
  • src/buddy.ts:1834, 1883, 1915 — auto-close logic round-trips markdown tables
  • src/dashboard/dashboard-generator.ts:428-570 — package extraction from PR bodies/titles

This is fragile (source of the bugs referenced near #1360/#1361 in code comments), breaks whenever the body template changes, and blocks features like custom pullRequest.bodyTemplate (see the dead-config issue) — a custom template would silently break rebase and auto-close.

Spec

Embed a machine-readable manifest in every Buddy Bot PR body inside an HTML comment, and make all state-reading paths consume it (with the regex path kept only as a legacy fallback for pre-existing PRs):

<!-- buddy-bot:manifest v1
{ "schemaVersion": 1,
  "updates": [
    { "name": "typescript", "current": "^5.8.2", "target": "^5.8.3",
      "type": "patch", "ecosystem": "npm", "file": "package.json",
      "dependencyType": "devDependencies" }
  ],
  "strategy": "all", "group": "Non-Major Updates",
  "branch": "buddy-bot/update-...", "generatedAt": "..." }
-->

Requirements

  1. New module src/pr/pr-manifest.ts: serializeManifest(updates: PackageUpdate[], meta): string, parseManifest(body: string): PRManifest | null, hasManifest(body). Zero-dependency JSON in comment; tolerant parser (returns null on malformed JSON, never throws).
  2. src/pr/pr-generator.ts appends the manifest to every generated body (PRs and rebases).
  3. Consumers switch to manifest-first, regex-fallback:
    • rebase (bin/cli.ts rebase command)
    • checkAndCloseSatisfiedPRs / checkAndCloseObsoletePRs (src/buddy.ts)
    • dashboard package extraction (src/dashboard/dashboard-generator.ts)
  4. Manifest must survive body-length truncation: append it before release notes are truncated to releaseNotes.maxBodyLength, or place it at the top of the body.
  5. GitHub PR body limit is 65,536 chars — budget the manifest and truncate release notes first, never the manifest.
  6. Schema is versioned (schemaVersion); parser must accept unknown future fields.

Tasks

  • src/pr/pr-manifest.ts with types in src/types.ts (PRManifest, PRManifestUpdate)
  • Emit manifest in pr-generator.ts (all PR types: npm, composer, actions, docker, deps-files, security)
  • Convert the three consumer sites to manifest-first
  • Legacy fallback: keep regex parsing behind parseLegacyBody() and log a warning when it's used
  • Tests: round-trip for every ecosystem, malformed manifest, legacy body without manifest, truncation interplay, body-size budget

Acceptance criteria

  • Rebasing a manifest-bearing PR produces identical update sets without touching regex paths
  • A PR with a fully custom bodyTemplate (once that ships) still rebases and auto-closes correctly
  • Existing open PRs (no manifest) continue to work via fallback
  • bun test passes; new tests cover success/failure/edge per CLAUDE.md conventions

Dependencies

None — this is the first Phase 0 issue; #1369 (auto-merge) and #1370 (dashboard) build on it.

Sign in to comment on this issue.