Conversation
…igration * ts(slice-16, wip 1/N): port peer-call/gemini (.sh→.ts) Sibling of slice-15 grok port (PR #896). Wraps 'gemini -p' non-interactive headless mode. Read-only safety preserved (--approval-mode plan + --skip-trust per the Copilot fix on the bash original PR #28). Reuses the structural pattern from grok.ts: - classifyFlag + MutableArgState parser (under cog-complexity 15) - spawnSync + stdio:inherit for live LLM streaming - isRegularFile + readHead helpers - /bin/sh -c for context-cmd (same trust as bash eval) - PREAMBLE preserved verbatim with Gemini-specific framing (proposes role per four-ferry consensus) Lint-clean: bun --bun tsc --noEmit + eslint strictTypeChecked + sonarjs all pass. Argument-validation byte-equivalent. Note: the same CodeQL js/indirect-command-line-injection alert will fire here as on grok.ts; per B-0107 (filed in PR #897) the per-PR dismissal pattern applies. Will dismiss after PR opens.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1bfc147e07
ℹ️ 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".
…through-head context-cmd Two real Codex findings on PR #898 (both also apply to grok.ts — backport in follow-up PR): P1 — Read only file head instead of loading full file: Original used readFileSync + Buffer.subarray which loads the entire file into memory then slices. For large artifacts (logs, dumps) this is a memory-spike regression vs bash 'head -c 20000'. Replaced with openSync + readSync(fd, buf, 0, bytes, 0) which reads only the first FILE_HEAD_BYTES bytes from disk. Wrapped in try/finally for fd cleanup. P2 — Truncate context command output at source: Original used spawnSync with maxBuffer up to 64MB then sliced to CTX_HEAD_BYTES afterward. For high-volume commands (wide 'git diff' ranges) this blocks longer and uses more memory than the bash original's '... | head -c 20000' pipeline that short-circuits at the boundary. Reframed wrapped command as: (cmd) 2>&1 | head -c <N> so the shell pipeline truncates at source. Same trust contract preserved (user supplies cmd; we just augment with head-c). Verified locally: 100MB test file reads only 20000 bytes via the new head-only path.
There was a problem hiding this comment.
Pull request overview
Ports the peer-call Gemini proposer script from bash to TypeScript/Bun as part of the ongoing TS/Bun migration, and updates the migration trajectory status to reflect the new slice in flight.
Changes:
- Add
tools/peer-call/gemini.tsimplementing the Gemini peer-caller in TS/Bun (arg parsing, prompt construction, optional file/context attachments,gemini -pinvocation). - Update the TS/Bun migration RESUME trajectory status/milestone for slice 16.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tools/peer-call/gemini.ts | New TS/Bun Gemini peer-call script (port of gemini.sh) with streaming CLI invocation and prompt assembly. |
| docs/trajectories/typescript-bun-migration/RESUME.md | Updates trajectory status/milestone to include slice 15 merged and slice 16 in-flight. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be2507fd6f
ℹ️ 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".
Both reviewers caught: runContextCmd was returning only result.stdout, which drops diagnostics emitted on the shell's OWN stderr (parse/syntax errors in --context-cmd). The (cmd) 2>&1 redirect only captures the command's stdout/stderr; the SHELL's stderr (where parse errors land) falls outside that redirect. Fix: concat result.stdout + result.stderr, then slice to CTX_HEAD_BYTES. On a clean run stderr is empty so output is unchanged; on malformed cmd the parse error reaches the prompt instead of an empty context block. Same fix needed on gemini.ts in #898 — opening a follow-up commit there.
…read + pipe-through-head context-cmd (#899) * fix(peer-call/grok): backport Codex P1+P2 from #898 — head-only file read + pipe-through-head context-cmd Backports the same two perf fixes that landed on gemini.ts in PR #898 — the bugs are identical because grok.ts and gemini.ts share the same readHead + runContextCmd helper shape (sibling-port pattern from slice-15 / slice-16). P1 — Read only file head instead of loading full file: Original used readFileSync + Buffer.subarray which loads the entire file into memory then slices. For large artifacts (logs, dumps) this is a memory-spike regression vs bash 'head -c 20000'. Replaced with openSync + readSync(fd, buf, 0, bytes, 0) which reads only the first FILE_HEAD_BYTES bytes from disk. Wrapped in try/finally for fd cleanup. P2 — Truncate context command output at source: Original used spawnSync with maxBuffer up to 64MB then sliced to CTX_HEAD_BYTES afterward. For high-volume commands (wide 'git diff' ranges) this blocks longer and uses more memory than the bash original's '... | head -c 20000' pipeline that short-circuits at the boundary. Reframed wrapped command as: (cmd) 2>&1 | head -c <N> so the shell pipeline truncates at source. Same trust contract preserved (user supplies cmd; we just augment with head-c). Same fixes verified on gemini.ts in #898 (100MB test file reads only 20000 bytes via the new head-only path). * review(#899): preserve shell parse errors per Codex P2 + Copilot Both reviewers caught: runContextCmd was returning only result.stdout, which drops diagnostics emitted on the shell's OWN stderr (parse/syntax errors in --context-cmd). The (cmd) 2>&1 redirect only captures the command's stdout/stderr; the SHELL's stderr (where parse errors land) falls outside that redirect. Fix: concat result.stdout + result.stderr, then slice to CTX_HEAD_BYTES. On a clean run stderr is empty so output is unchanged; on malformed cmd the parse error reaches the prompt instead of an empty context block. Same fix needed on gemini.ts in #898 — opening a follow-up commit there.
…on, bash shell, file-read errors Five real findings from Copilot+Codex on PR #898: Copilot P1 — Exit codes 0|2|64 per repo-scripting.md: Switched return 1 → return 64 for invocation/usage errors (--model/--file/--context-cmd missing values, unknown flag, empty prompt). Aligns with the conventions in docs/best- practices/repo-scripting.md §exit-codes. Process-related errors (gemini missing on PATH, build-prompt error) keep 1 for tooling/input failure. Copilot P1 — spawnSync result.status ?? 1 collapses launch failures: Added classifySpawnFailure helper (4-case: status set / ENOENT → 127 / signal / other) — same pattern from PR #887. Distinguishes ENOENT/permission/signal from a normal non-zero exit, with a contextual stderr message. Codex P2 — eval vs /bin/sh syntax difference: Switched /bin/sh -c → /bin/bash -c so the bash original's eval semantics (accepting bash-only features like `[[ ]]`, brace expansion, process substitution) are preserved. /bin/sh on Ubuntu is dash and rejects these. Codex P2 — file-read failures silently dropping context: readHead() now returns ReadHeadResult { ok, content, error }. buildFullPrompt() propagates the error to the user via stderr instead of pretending context was attached when read failed (permission denied, race, etc.). Threads on already-addressed findings (PRRT_kwDOSF9kNM5-pd16 runContextCmd full-buffering + PRRT_kwDOSF9kNM5-pd2a readHead full-file-read) were resolved in the parse-error-fix commit ceed1e7. Threads on PRRT_kwDOSF9kNM5-pd2U PREAMBLE-names: pushing back because the PREAMBLE is prompt content sent verbatim to the LLM peer, not pure code-surface; changing it diverges from the bash original byte-equivalence. A separate task to retool PREAMBLE attribution on both bash AND TS together is the right shape, not a one-side rename here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c46d1792f0
ℹ️ 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".
Three new findings on the latest push:
Codex P2 (PRRT_kwDOSF9kNM5-pon2) + Copilot P0 x2 — exit codes:
Last commit switched to exit 64 per docs/best-practices/repo-
scripting.md. Reviewers correctly point out tools/peer-call/
README.md is the more specific spec and says 0/1/2 uniform
across all three peer-call wrappers. Specific wins on overlap.
Reverted ArgError.exitCode 64 → 1 + the empty-prompt return
64 → 1. Matches grok.ts (slice 15) and the bash original.
Copilot P1 — commandAvailable() shape:
Was using `<cmd> --version` and requiring exit 0. Some CLIs
exit non-zero on --version. Bash uses `command -v <cmd>`
(PATH existence check, no execution). Switched to
`spawnSync('/bin/sh', ['-c', \`command -v "${cmd}"\`])`
to match bash semantics.
Copilot P1 (PRRT_kwDOSF9kNM5-ppuR) — PREAMBLE names: same as
prior thread; pushing back with same rationale (PREAMBLE is
verbatim prompt content sent to LLM, not pure code-surface;
preserving bash-equivalence). Will reply on the new thread
the same way.
Copilot P2 (PRRT_kwDOSF9kNM5-ppt6) — PR description says /bin/sh
but code uses /bin/bash. The /bin/bash switch was a Codex P2 from
the prior round (preserve eval semantics for bash-only features).
Will update PR description, not the code.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…igration (#900) * ts(slice-17, wip 1/N): port peer-call/codex (.sh→.ts) Third sibling of the peer-call cluster (after grok #896 and gemini #898). Wraps 'codex exec -s read-only --skip-git-repo-check' (default) or 'codex review' (with --review). --model is ignored in --review mode with a warning to stderr. Bakes in all the round-2 + round-3 fixes from the gemini.ts review cycle: - exit codes 0/1/2 uniform per tools/peer-call/README.md (NOT 64) - commandAvailable via /bin/sh -c 'command -v <cmd>' (PATH check, not --version) - /bin/bash -c (not /bin/sh -c) for context-cmd to preserve bash-only feature support (Codex P2) - ReadHeadResult { ok, content, error } so file-read failures surface to stderr instead of silently dropping context - classifySpawnFailure 4-case helper (status / ENOENT → 127 / signal / other) reused from PR #887 + slice-16 #898 - Concat stdout + stderr from runContextCmd to preserve shell parse errors that fall outside the inner ( ) 2>&1 redirect Lint-clean: bun --bun tsc --noEmit + eslint strictTypeChecked + sonarjs all pass. Argument-validation byte-equivalent. Note: same CodeQL js/indirect-command-line-injection alert will fire here as on grok.ts and gemini.ts. Per B-0107 (filed in PR #897) the per-PR dismissal pattern applies until B-0107 implements the structural fix.
…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.
…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.
…tion (#901) * ts(B-0086): port 1 budget script (.sh→.ts) — slice 18 of TS/Bun migration * 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). * review(slice-18): use statSync.isFile() instead of existsSync per Codex 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. * review(slice-18): address Copilot P0+P0+P2 — spawn classification + null-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. * review(slice-18) round-2: extract step-helpers + suppress no-unnecessary-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. * review(slice-18): preserve stdout/stderr ordering via shell-side 2>&1 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).
…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>
…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>
Summary
Slice 16 — sibling port of grok.ts (slice 15 #896):
tools/peer-call/gemini.{sh→ts}(Gemini propose-role peer-caller via gemini CLI)Reuses the structural pattern from grok.ts:
classifyFlag+MutableArgStateparser,spawnSync+stdio: "inherit"for live LLM streaming,/bin/sh -cfor context-cmd. PREAMBLE preserved verbatim with Gemini-specific propose-role framing per the four-ferry consensus.Read-only safety preserved (
--approval-mode plan+--skip-trustper the Copilot fix on bash original PR #28).Test plan
bun --bun tsc --noEmitclean (lint-tsc-tools gate from ci(B-0106): add lint-tsc-tools gate job — tsc --noEmit on tsconfig.json #890 will validate this in CI).eslintclean.--modelwithout value → exit 1; no prompt → exit 1.Note on CodeQL alert
The same
js/indirect-command-line-injectionalert that fired on grok.ts (#896) will fire here onrunContextCmd. Per B-0107 (filed in #897) the per-PR dismissal pattern applies until B-0107 implements the structural fix.Trajectory
docs/trajectories/typescript-bun-migration/RESUME.md.🤖 Generated with Claude Code