SIGN IN SIGN UP

cli(doctor --issue): cap body at GitHub's 64KB limit + dedicated errors section

GitHub issue bodies have a hard ~64KB limit. The pre-fix bundlers
dumped the full 400-line log tail unconditionally, so long-running
sessions produced reports that exceeded the limit. Worse, when an
issue body was over budget the GitHub API rejected the whole payload
— the bundler completed, but the issue couldn't be opened.

Two changes:

1. New "## Recent errors (last 20, sanitized)" section above the
   main log tail. The body cap (point 2 below) only shrinks the main
   log block, so promoting recent errors into their own section
   guarantees they survive truncation. We scan the last 4000 log
   lines for ERROR-shaped patterns:

   - `failed:` (Magic Context's sessionLog convention)
   - `Error:` / `TypeError:` / typed-error rendering
   - `EMERGENCY` (95% abort marker)
   - `exception` (generic throw text)
   - V8/JSC stack-trace frames (`    at SomeFn (file:line:col)`)

   The `failed:` colon suffix is deliberate — it avoids the common
   false positive where a sessionLog message includes "failed" as
   past-tense status (e.g. `historian: 12 published; 0 failed` is
   telemetry, not an error). Stack frames are kept so the agent
   reading the issue sees enough context to identify the call site.

2. New `capBodyToGithubLimit(body, maxBytes = 60_000)` helper that
   enforces the byte budget. Budget is 60KB (4KB headroom below
   GH's 64KB cap for URL encoding via `gh issue create --web` and
   future minor section growth).

   When the body exceeds budget, the helper rewrites the main
   `## Log (last N lines, sanitized)` fenced block — drops oldest
   lines first (keeps newest) until the body fits, prefixes a
   visible `[truncated for GitHub 64KB limit — older log lines
   dropped]` marker. Diagnostics, configuration, historian failure
   signals, and recent-errors sections are preserved intact.

   UTF-8 byte length is the budget. The fallback path (no log
   heading found) uses a code-point-aware byte truncator so cutting
   mid-character doesn't produce U+FFFD replacement bytes that push
   the output back over budget.

Both helpers live in a new shared `issue-body.ts` so OpenCode and
Pi share the same budget, same truncation marker, and same
precision/false-positive tradeoff on what counts as an error line.

Tests: 14 new in `issue-body.test.ts` covering:
- Documented sessionLog shapes + "0 failed" false-positive
- V8 stack-trace frame matching
- Chronological-order output / newest-first selection
- Limit cap behavior
- Pass-through under budget
- Truncation when over budget (correct marker, oldest dropped first)
- Recent errors section survives truncation
- Description/Environment sections preserved
- Default MAX_GITHUB_BODY_BYTES applied
- UTF-8-safe fallback truncation

CLI suite: 138 pass / 2 skip / 0 fail (+14 from 124 baseline).
Typecheck, lint, format, build all clean across all 3 packages.
U
ualtinok committed
1bf4a641bf968a09d851a8ee82abf0f88c040609
Parent: aa1356b