Skip to content

ts(B-0086): port 1 pr-preservation script (.sh→.ts) — slice 21 of TS/Bun migration#908

Merged
AceHack merged 3 commits intomainfrom
lane-b/ts-bun-slice-21-archive-pr-2026-04-30
Apr 30, 2026
Merged

ts(B-0086): port 1 pr-preservation script (.sh→.ts) — slice 21 of TS/Bun migration#908
AceHack merged 3 commits intomainfrom
lane-b/ts-bun-slice-21-archive-pr-2026-04-30

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 30, 2026

Summary

Slice 21 of the TS/Bun migration (B-0086). Ports tools/pr-preservation/archive-pr.sh (217 bash + ~457 embedded Python = 674 lines) → tools/pr-preservation/archive-pr.ts (590 lines TS). Last Bucket B file — after this PR merges, Bucket B is empty and the trajectory transitions from "porting" to "soak + bash retirement" phase.

Behavioural improvements (deliberate, not drift)

  1. Drops Python from runtime deps — bash + Python mix collapses to single Bun runtime. No more bash/Python boundary, mktemp + trap cleanup, set +e to capture Python exit code.
  2. Generic paginateTopLevel<T> helper with type-safe extractor — handles all 3 top-level connections (threads/reviews/comments) with one function.
  3. yamlQuote Unicode-escape fix — JS JSON.stringify preserves non-ASCII; Python json.dumps defaults to ensure_ascii=True. The TS port post-processes to escape non-ASCII codepoints as \uXXXX — preserves byte-equivalence on titles with / etc.

All bash safety rails preserved: positive-integer PR validation, GH_REPO env-var preference + gh repo view fallback, 2/3-segment NWO parsing with Enterprise HOST validation (dot required), slash-injection defence on owner/name, paginated GraphQL fetch (top-level + per-thread), GraphQL errors array inspection, null-pullRequest detection, idempotent archive path via PR-<NNNN>-* glob (Otto-235), CommonMark §4.5 fence detection (Otto-241), trailing-newline-only rstrip.

Verification

  • 2 argument-validation paths byte-equivalent (no args / bad PR number) — same exit code 1 + same message
  • Live archive run on PR ts(B-0086): port 1 budget script (.sh→.ts) — slice 19 of TS/Bun migration #902 (4 threads, 2 reviews, 0 comments) byte-equivalent EXCEPT archived_at (timestamp) + archive_tool (.sh vs .ts — deliberate self-reference)
  • Title with non-ASCII chars ( / ) escapes correctly via yamlQuote fix
  • bun --bun tsc --noEmit -p tsconfig.json clean

Test plan

  • tsc --noEmit clean locally
  • Argument-validation byte-equivalent (2 sampled)
  • Live archive byte-equivalent on real PR (modulo timestamp + self-reference)
  • CI: lint (tsc tools) gate passes
  • CI: gate.yml matrix passes
  • CodeQL clean

Composes with

🤖 Generated with Claude Code

…Bun migration

Last Bucket B file: tools/pr-preservation/archive-pr.{sh→ts}.
After this PR merges, **Bucket B is empty** — every file flagged
for TS port has been ported. The trajectory transitions from
"porting" phase to "soak + bash retirement" phase.

Behavioural improvements over bash original (deliberate, not drift):

- Drops Python from runtime deps entirely. Bash original was
  217 lines bash + ~457 lines embedded Python (GraphQL fetcher +
  Markdown formatter); TS port collapses both into single Bun
  runtime. No more bash/Python boundary, no more mktemp + trap
  cleanup, no `set +e` to capture Python exit code.

- Native JSON parsing replaces all jq/Python json shells.

- Generic `paginateTopLevel<T>` helper with type-safe extractor
  handles the cursor loop for all 3 top-level connections
  (reviewThreads/reviews/comments). `paginateThreadComments`
  handles the per-thread case.

- `detectFenceMarker` preserves CommonMark §4.5 fence rules
  strictly: leading-space-count ≤ 3 + no tab in prefix; closing
  fence same marker char + length ≥ opener. This matches the
  Python original's nuanced fence detection (Otto-241 etc).

- `yamlQuote` post-processes `JSON.stringify` output to escape
  non-ASCII codepoints as \uXXXX, matching Python's
  `json.dumps(ensure_ascii=True)` wire-format default. Without
  this, titles with → / — would diverge from bash output.

Byte-equivalence verified on this repo state:
- 2 argument-validation paths byte-equivalent (no args / bad
  PR number) — same exit code 1 + same message.
- Live archive run on PR #902 (4 threads, 2 reviews, 0 comments)
  byte-equivalent EXCEPT `archived_at` (timestamp) + `archive_tool`
  (.sh vs .ts — deliberate self-reference). Title with non-ASCII
  chars escapes correctly via the yamlQuote fix.

All bash safety rails preserved: positive-integer PR validation,
GH_REPO env-var preference + `gh repo view` fallback, 2/3-segment
NWO parsing with Enterprise HOST validation (dot required),
slash-injection defence on owner/name, paginated GraphQL fetch
(top-level + per-thread), GraphQL `errors` array inspection,
null-pullRequest detection, idempotent archive path via
PR-<NNNN>-* glob (Otto-235), CommonMark §4.5 fence detection
(Otto-241), trailing-newline-only rstrip (preserves
two-space markdown hard-line-breaks).

Bucket B 1 → 0. Bucket C: 2 (gh-api-heavy scripts pending
maintainer decision). Bucket A: 14 (stays bash by design).

Composes with:
- tools/git/batch-resolve-pr-threads.ts (slice 20, #907) — same
  GraphQL pagination shape
- docs/trajectories/typescript-bun-migration/RESUME.md updated
- docs/trajectories/typescript-bun-migration/slice-audits.md
  slice 21 audit appended

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 30, 2026 07:56
@AceHack AceHack enabled auto-merge (squash) April 30, 2026 07:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports the PR-preservation archiver from a bash+embedded-Python implementation to a single TypeScript/Bun script, and updates the TS/Bun migration trajectory docs to record slice 21.

Changes:

  • Added tools/pr-preservation/archive-pr.ts implementing PR conversation archiving via gh api graphql with pagination and markdown formatting.
  • Appended slice 21 audit notes to docs/trajectories/typescript-bun-migration/slice-audits.md.
  • Updated docs/trajectories/typescript-bun-migration/RESUME.md to reflect slice 21 in-flight status and Bucket B nearing completion.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tools/pr-preservation/archive-pr.ts New Bun/TS port of the PR archiving tool (GraphQL fetch + formatting + file write).
docs/trajectories/typescript-bun-migration/slice-audits.md Records slice 21 audit details and equivalence notes for the new port.
docs/trajectories/typescript-bun-migration/RESUME.md Updates migration dashboard status/milestones for slice 21.

Comment thread docs/trajectories/typescript-bun-migration/slice-audits.md Outdated
Comment thread docs/trajectories/typescript-bun-migration/slice-audits.md Outdated
Comment thread docs/trajectories/typescript-bun-migration/slice-audits.md Outdated
Comment thread tools/pr-preservation/archive-pr.ts
…e_ascii framing + usage-line carve-out

Round-1 fixes for PR #908 (4 Copilot P1 threads):

- Line-count drift (Copilot P1): I claimed "674 → 590" but actual is
  "674 → 806". Updated and added a one-line note explaining why TS
  is larger than bash (explicit type interfaces replace Python's
  untyped dict navigation).

- ensure_ascii=True framing was wrong (Copilot P1, twice): Python's
  json.dumps with ensure_ascii=True does NOT emit literal `→`/`—` —
  it emits \uXXXX escapes. My audit prose said "Python escapes to
  `→`" which contradicted both the byte-equivalence goal and what
  yamlQuote actually implements. Rewrote to clearly say Python emits
  \uXXXX form (e.g. → for right-arrow, — for em-dash) and
  the TS yamlQuote post-processes JSON.stringify to match.

- Usage-line not byte-equivalent (Copilot P1, twice): bash echoes
  `$0` showing the actual `./tools/...sh` path; TS hard-codes
  `bun tools/...ts` so the user sees the form they should run.
  Reframed equivalence claim to be honest: same exit code + same
  error-body, but usage-line script-path is intentionally NOT
  byte-equivalent — same carve-out as the `archive_tool` YAML
  self-reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AceHack AceHack merged commit 113248b into main Apr 30, 2026
29 of 30 checks passed
@AceHack AceHack deleted the lane-b/ts-bun-slice-21-archive-pr-2026-04-30 branch April 30, 2026 08:07
AceHack added a commit that referenced this pull request Apr 30, 2026
…+ tick-row schema + spelling drift

Round-1 fixes for PR #909 (4 unresolved threads):

- Codex P2: Bucket B section in RESUME.md still listed the 2
  bash files as "remaining" — internal contradiction with the
  new "Bucket B is empty" status line. Rewrote the Bucket B
  section to match the empty state with cluster summary.

- Copilot: tick-history column 5 had `113248b (slice-21 final;
  #908)` which is a merged slice commit, NOT this tick's commit.
  Per the documented tick-history schema, used em-dash placeholder
  per convention (the row's actual squash-merge SHA isn't known
  pre-merge).

- Copilot: tick-row had a backtick code span with literal
  non-ASCII range characters (`[U+0080-U+FFFF]`). Replaced with
  English form: "replace each codepoint in the U+0080 through
  U+FFFF range" — matches the same MD038 anti-pattern lesson
  recorded in the row's own observations.

- Copilot: spelling drift "bash-retirement" (status) vs
  "bash retirement" (milestone). Standardized on hyphenated form
  ("bash-retirement") matching the tick-history row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 30, 2026
* ops(ts-bun-migration): Bucket B closeout — porting phase complete

PR #908 (slice 21 — archive-pr.sh→ts) merged 2026-04-30T08:07:32Z.
**Bucket B is empty.** Every file flagged for TS port has been
ported. The TS+Bun migration trajectory transitions from "porting"
phase to "soak + bash-retirement" phase.

This commit captures the closeout:

- RESUME.md status flipped to "Soak + bash-retirement phase".
  Milestone bumped 41→42 ports. Next concrete actions reframed
  for the new phase: bash retirement audit, daily-cost-report.ts
  switch from .sh to .ts spawn, Bucket C maintainer decision.

- Tick-history row at 08:08:00Z: closeout for the slice 20+21 arc
  (4 review rounds across the two PRs — MD038 Unicode-in-backtick,
  4 Copilot P1 doc inversions, transient mise rate-limit
  auto-recovery). Four observations recorded:
  (1) sibling-port-cost diverges between same-shape (peer-call:
      monotonic decrease) and cross-shape (gh-api-mutating:
      stays roughly constant) siblings;
  (2) Python `ensure_ascii=True` is the canonical
      "match-Python-wire-format" pattern for TS ports — the
      `yamlQuote` 5-line helper closes the gap (worth seeding
      into TS+Bun expert skill #351 alongside classifySpawnFailure
      and the kernel-pipe pattern);
  (3) MD038 anti-pattern recognized — literal Unicode characters
      inside backtick code spans break twice (#904 + #908); use
      English ("right-arrow" / "em-dash") in audit prose;
  (4) Bucket B closure is the natural pivot point — the trajectory
      has a clear endpoint and the soak+retirement phase is
      qualitatively different work.

Cluster summary at closeout:
  - budget cluster (slices 14/18/19) — complete
  - peer-call cluster (slices 15/16/17) — complete
  - git cluster (slices 13/20) — complete
  - pr-preservation cluster (slice 21) — complete

Bucket A (14 setup-script files) stays bash by design (pre-Bun
bootstrap layer). Bucket C (2 gh-api-heavy files —
check-github-settings-drift.sh + snapshot-github-settings.sh)
remains pending maintainer decision (shell-out wrapper vs
Octokit).

Cron 98fc7424 still armed.

Doc-only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ts-bun-closeout): address #909 review threads — Bucket B section + tick-row schema + spelling drift

Round-1 fixes for PR #909 (4 unresolved threads):

- Codex P2: Bucket B section in RESUME.md still listed the 2
  bash files as "remaining" — internal contradiction with the
  new "Bucket B is empty" status line. Rewrote the Bucket B
  section to match the empty state with cluster summary.

- Copilot: tick-history column 5 had `113248b (slice-21 final;
  #908)` which is a merged slice commit, NOT this tick's commit.
  Per the documented tick-history schema, used em-dash placeholder
  per convention (the row's actual squash-merge SHA isn't known
  pre-merge).

- Copilot: tick-row had a backtick code span with literal
  non-ASCII range characters (`[U+0080-U+FFFF]`). Replaced with
  English form: "replace each codepoint in the U+0080 through
  U+FFFF range" — matches the same MD038 anti-pattern lesson
  recorded in the row's own observations.

- Copilot: spelling drift "bash-retirement" (status) vs
  "bash retirement" (milestone). Standardized on hyphenated form
  ("bash-retirement") matching the tick-history row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants