Skip to content

ts(B-0086): port 1 budget script (.sh→.ts) — slice 18 of TS/Bun migration#901

Merged
AceHack merged 5 commits intomainfrom
lane-b/ts-bun-slice-18-daily-cost-report-2026-04-30
Apr 30, 2026
Merged

ts(B-0086): port 1 budget script (.sh→.ts) — slice 18 of TS/Bun migration#901
AceHack merged 5 commits intomainfrom
lane-b/ts-bun-slice-18-daily-cost-report-2026-04-30

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 30, 2026

Summary

Slice 18 of the TS/Bun migration:

  • tools/budget/daily-cost-report.{sh→ts} (daily cost-monitoring entry point; wraps snapshot-burn.sh + project-runway.sh + writes report to docs/budget-history/latest-report.md)

This wrapper still spawns the bash siblings (snapshot-burn.sh + project-runway.sh), NOT the TS port — the bash versions are the soak-period reference until they retire. Once project-runway is also TS-ported, this wrapper can switch to spawning the .ts versions.

Test plan

Trajectory

After-merge: 39 ports total, 4 Bucket B files remain:

  • tools/budget/project-runway.sh (297 lines, projection math)
  • tools/git/batch-resolve-pr-threads.sh (390 lines, gh GraphQL + pagination)
  • tools/pr-preservation/archive-pr.sh (674 lines, largest)

🤖 Generated with Claude Code

…tion

* ts(slice-18, wip 1/N): port budget/daily-cost-report (.sh→.ts)

Daily cost-monitoring entry point. Wraps snapshot-burn.sh +
project-runway.sh and writes human-readable projection to
docs/budget-history/latest-report.md (visibility surface for
Aaron's #287 deadline).

Note: this wrapper still spawns the bash siblings (snapshot-burn.sh
+ project-runway.sh), NOT the TS port — the bash versions are the
soak-period reference until they retire. Once project-runway is
also TS-ported, this wrapper can switch to spawning the .ts versions.

Mechanical changes:
- bash arg-parse → parseArgs with ParsedArgs | ArgError | help
- bash 'cat > "$report_path" <<EOF...EOF' heredoc → buildReport()
  template literal returning the markdown string
- bash subshell command capture ($(...)) → spawnSync with stdio
  ['inherit', 'pipe', 'pipe'] for project-runway combined output
- bash heredoc concat across multi-line → resolved via separate
  argsSuffix variable (sonarjs no-nested-template-literals)
- exit codes 0/1/2 preserved verbatim per bash original

Lint-clean: tsc --noEmit + eslint strictTypeChecked + sonarjs all
pass. Argument-validation byte-equivalent.

Trajectory: 39 ports total after merge, 4 Bucket B files remain
(1 budget project-runway + 1 git/batch-resolve + 1 pr-preservation
+ 1 in-flight = 4 remaining).
Copilot AI review requested due to automatic review settings April 30, 2026 06:55
@AceHack AceHack enabled auto-merge (squash) April 30, 2026 06:55
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bcae924c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/budget/daily-cost-report.ts Outdated
…ex P2

Codex P2: existsSync returns true for directories and other
non-regular paths; the bash original uses -f which checks
regular-file existence. If snapshots.jsonl were ever a directory,
existsSync would skip the bootstrap branch and the wrapper would
try to spawn project-runway.sh against a non-file. Switched to
statSync.isFile() with try/catch fallback to false.
auto-merge was automatically disabled April 30, 2026 06:59

Pull Request is not mergeable

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 tools/budget/daily-cost-report wrapper from bash to TypeScript/Bun as part of the ongoing TS/Bun migration, keeping behavior aligned with the existing bash entrypoint while still delegating to the bash “primitive” scripts during the soak period.

Changes:

  • Add tools/budget/daily-cost-report.ts as the new daily cost-report entrypoint.
  • Implement CLI parsing (--dry-run, --skip-snapshot, --help) and orchestration of snapshot-burn.sh + project-runway.sh.
  • Generate/overwrite docs/budget-history/latest-report.md with the latest projection output.

Comment thread tools/budget/daily-cost-report.ts
Comment thread tools/budget/daily-cost-report.ts Outdated
Comment thread tools/budget/daily-cost-report.ts Outdated
…ull-stream guards + header phrasing

Three Copilot findings on #901:

P0 — spawnSync launch failures collapsed into exitCode 1:
  Added classifySpawnFailure helper (4-case: status set / ENOENT
  → 127 / signal / other) reused from PRs #887, #898, #900. Both
  runSnapshotBurn and runProjectRunway now report a contextual
  note when the child can't start (e.g., 'snapshot-burn.sh:
  command not found on PATH (ENOENT)').

P0 — null stdout/stderr could yield 'nullnull':
  When a child fails to start, result.stdout / result.stderr
  can be null even with encoding: 'utf8'. Guarded with `?? ''`
  in runProjectRunway so the projection block doesn't end up as
  the literal string 'nullnull'.

P2 — Header comment phrasing:
  Reworded 'snapshot-burn.sh ported in #894' to 'TS port
  snapshot-burn.ts landed in #894 but this wrapper still spawns
  the .sh during the soak period' to avoid implying the .sh file
  itself is the ported artifact.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

…ary-condition

- Extracted runSnapshotStep + runProjectionStep helpers to drop
  main() under cognitive-complexity 15.
- Added eslint-disable on stdout/stderr ?? '' guards (typings claim
  string when encoding is set, but the runtime can return null when
  spawn fails — same pattern as #898).

Lint clean: tsc + eslint strictTypeChecked + sonarjs all pass.
Copilot AI review requested due to automatic review settings April 30, 2026 07:04
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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread tools/budget/daily-cost-report.ts
… per Copilot P1

Copilot caught: concatenating result.stdout + result.stderr does NOT
preserve the original chronological ordering of merged streams.
The bash original $(... 2>&1) merges at the kernel pipe level — if
project-runway.sh emits warnings on stderr while writing success
output to stdout, the messages interleave correctly.

Switched to /bin/bash -c 'path 2>&1' so the merge happens shell-side
(matches bash original semantics). Single stdout pipe = correct ordering.
result.stderr is now unused (the inherit pipe still receives nothing).
@AceHack AceHack merged commit 76f3dc9 into main Apr 30, 2026
24 checks passed
@AceHack AceHack deleted the lane-b/ts-bun-slice-18-daily-cost-report-2026-04-30 branch April 30, 2026 07:16
AceHack added a commit that referenced this pull request Apr 30, 2026
…tion (#902)

Closes the budget cluster: snapshot-burn (slice 14) +
daily-cost-report (slice 18) + project-runway (this slice) are
now all TS. Once this lands, daily-cost-report.ts can switch
from spawning project-runway.sh to project-runway.ts.

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

- File-existence check uses statSync().isFile() + try/catch
  rather than existsSync — bash `-f` rejects directories,
  existsSync accepts them (slice-18 mirror).
- JSONL parsing is native (readFileSync + split + JSON.parse)
  rather than per-line jq spawn-out — projection script reads
  already-persisted JSON, so jq is a heavy dependency for what
  is structurally a typed reduce. snapshot-burn.ts still needs
  gh api for capture; this is projection only.
- requireInt validation matches bash `case '$val' in
  ''|*[!0-9]*) ...` with TS `requireInt(flag, val)` returning
  `number | ArgError` — same exit code 2, same error wording
  (Codex P2 NM59qF00 + NM59qH2H, Copilot P1 NM59qGJ- on the
  bash original).

Byte-equivalence verified on this repo state:
  diff <(bun tools/budget/project-runway.ts) \
       <(./tools/budget/project-runway.sh)        # empty
  diff <(bun tools/budget/project-runway.ts --json) \
       <(./tools/budget/project-runway.sh --json) # empty

Error paths verified equivalent: --stages abc → exit 2 with
matching message; --file <missing> → exit 1; --bogus → exit 2.

Tools used: tsc --noEmit clean; eslint clean per the existing
tsc-tools CI gate (#890).

Composes with:
  - tools/budget/snapshot-burn.ts (slice 14, #894)
  - tools/budget/daily-cost-report.ts (slice 18, #901)
  - docs/trajectories/typescript-bun-migration/RESUME.md
  - docs/trajectories/typescript-bun-migration/slice-audits.md

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 30, 2026
…sure (#903)

Per the consolidated-row-pattern (rows from 03:41Z + 05:01Z arc
closures): when a session lands many small commits across multiple
ticks, a single consolidated row summarizing the arc is more
signal-dense than N minimal rows.

Covers the slices 15-19 arc that landed after the 05:43Z slice-14 row:
  - #896 slice 15 (grok.ts) — peer-call cluster opens
  - #898 slice 16 (gemini.ts) — peer-call sibling
  - #899 backport to grok from #898 review-cycle findings
  - #900 slice 17 (codex.ts) — peer-call cluster closes
  - #897 B-0107 row — CodeQL dismissal pattern
  - #901 slice 18 (daily-cost-report.ts) — budget wrapper
  - #902 slice 19 (project-runway.ts) — budget cluster closes (in flight)

Three new substrate observations recorded for future-Otto:
  - sibling-port-cost decreases monotonically (round-2/3 fixes
    bake into later siblings proactively)
  - kernel-pipe vs JS-space stream interleaving distinction
    (bash `2>&1` merges shell-side; `result.stdout + result.stderr`
    in JS does not preserve chronological ordering)
  - fix-the-bug + file-the-row + implement-the-row + closeout pattern
    is the durable shape (B-0106 + B-0107 are both worked examples)

Cron 98fc7424 still armed.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 30, 2026
…s updates (#904)

* docs(ts-bun-migration): backfill slice-16/17/18 audit entries + status updates

Backfills the slice-audits.md gap surfaced during the slice-19 close:
slices 16, 17, 18 had merged but their audit entries were missing.
Also flips slice 15 + slice 19 entries from "PR pending" to merged
status (PR # + commit SHA).

Slice 16 (peer-call/gemini, #898): three rounds of review-cycle fixes
documented (round 1 exit-code 1 + commandAvailable PATH check;
round 2 /bin/bash -c + shell-side truncation + ReadHeadResult;
round 3 stderr concat for parse-error preservation).

Slice 17 (peer-call/codex, #900): closes peer-call cluster. Documents
the sibling-port-cost-decreases-monotonically pattern — slice 17
shipped 357 lines in a single commit with all known fixes pre-baked.

Slice 18 (budget/daily-cost-report, #901): first wrapper-class port.
Documents the kernel-pipe vs JS-space stream-ordering distinction
that became substrate observation in the 07:21Z tick row.

No code changes. Doc-only.

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

* fix(ts-bun-audits): address #904 review threads — MD038, persona attribution, monotonic deltas

Round-1 fixes for PR #904:

- MD038 markdownlint failure (line 451) — slice-18 entry had nested
  backticks (` "${path}" 2>&1 ` inside an outer code span) that
  confused markdown's parser. Rephrased prose to avoid embedding
  backticks inside backticks.

- Persona-name attribution in current-state doc (Copilot P1, twice) —
  slice-16 + slice-17 file descriptions used "Otto's harness-side
  caller for invoking ..." Per the .github/copilot-instructions.md
  no-name-attribution rule for current-state docs, switched to
  role-ref "the harness-side caller for invoking ...". Slice-15
  pre-existing wording on main left alone (separate commit if
  needed).

- Non-monotonic Bucket B deltas (Codex P2) — backfilled rows for
  slices 16/17/18 had stale numbers (9→8, 8→7, 7→6) that were
  inconsistent with the pre-existing slice-15 row's "8 → 7".
  Recomputed to monotonic sequence: slice 16 = 7→6, slice 17 = 6→5,
  slice 18 = 5→4.

- Slice-19 outcome wording (Copilot) — "Once this lands" → "Now that
  this has landed" since slice 19 already merged at commit bfdadd9.

Doc-only.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 30, 2026
…rt pattern (#906)

Captures the substrate finding from PR #901 (slice 18 daily-cost-report)
Copilot P1 round 2 catch as durable memory. Per Otto-363
substrate-or-it-didn't-happen: this pattern was only living in commit
messages and review threads — now indexed + reachable via memory/.

Rule: when porting bash `$(... 2>&1)` to spawnSync, merge stdout+stderr
at the kernel pipe boundary via `/bin/bash -c "<cmd> 2>&1"`, NOT in
JS-space via `result.stdout + result.stderr` concat.

Why: kernel pipe preserves the child's chronological write ordering
across both streams. JS-space concat glues two pre-segregated buffers
in a fixed order, losing the original interleaving.

How to apply: documented in the memory file with edge cases
(shell-parse-error stacking, defensive `?? ""`, `/bin/bash` over
`/bin/sh` for bash-only features).

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