From 7d6491e9610fabf2f4ba65373f17871bbd4c27a4 Mon Sep 17 00:00:00 2001 From: omerakben Date: Wed, 13 May 2026 20:41:07 -0400 Subject: [PATCH 01/37] docs(finalize): add first-run audit evidence --- .../codex-finalize/A1-cli-first-run.md | 258 ++++++++++++++ docs/handoffs/codex-finalize/A10-hygiene.md | 220 ++++++++++++ .../codex-finalize/A2-gui-first-run.md | 238 +++++++++++++ .../codex-finalize/A3-distribution.md | 248 +++++++++++++ docs/handoffs/codex-finalize/A4-binaries.md | 234 ++++++++++++ docs/handoffs/codex-finalize/A5-docs.md | 195 ++++++++++ docs/handoffs/codex-finalize/A6-errors.md | 284 +++++++++++++++ docs/handoffs/codex-finalize/A7-providers.md | 197 ++++++++++ docs/handoffs/codex-finalize/A8-visual.md | 337 ++++++++++++++++++ docs/handoffs/codex-finalize/A9-a11y.md | 262 ++++++++++++++ .../handoffs/codex-finalize/CODEX_GOAL_R2.txt | 54 +++ .../codex-finalize/FIRST_RUN_AUDIT.md | 74 ++++ 12 files changed, 2601 insertions(+) create mode 100644 docs/handoffs/codex-finalize/A1-cli-first-run.md create mode 100644 docs/handoffs/codex-finalize/A10-hygiene.md create mode 100644 docs/handoffs/codex-finalize/A2-gui-first-run.md create mode 100644 docs/handoffs/codex-finalize/A3-distribution.md create mode 100644 docs/handoffs/codex-finalize/A4-binaries.md create mode 100644 docs/handoffs/codex-finalize/A5-docs.md create mode 100644 docs/handoffs/codex-finalize/A6-errors.md create mode 100644 docs/handoffs/codex-finalize/A7-providers.md create mode 100644 docs/handoffs/codex-finalize/A8-visual.md create mode 100644 docs/handoffs/codex-finalize/A9-a11y.md create mode 100644 docs/handoffs/codex-finalize/CODEX_GOAL_R2.txt create mode 100644 docs/handoffs/codex-finalize/FIRST_RUN_AUDIT.md diff --git a/docs/handoffs/codex-finalize/A1-cli-first-run.md b/docs/handoffs/codex-finalize/A1-cli-first-run.md new file mode 100644 index 0000000..088a075 --- /dev/null +++ b/docs/handoffs/codex-finalize/A1-cli-first-run.md @@ -0,0 +1,258 @@ +# A1-cli-first-run findings + +Sub-task: A1 +Operator: codex-subtask-A1 +Started: 2026-05-13T22:05:00Z +Finished: 2026-05-13T22:20:59Z + +## Summary + +Filed 6 findings: 2 block-ship, 3 fix-soon, 1 nit. Biggest risk: the packaged first-run smoke path cannot complete offline with `--provider fake`; it stops in DEFINE with `NEEDS_INTERVENTION.json` and never writes any gate, so the required `npm pack -> install -> init -> run --provider fake` transcript cannot reach SHIP. The explicit resume surfaces in the playbook/rules are also absent from the CLI. + +## Findings + +### F1.1 - Packaged `--provider fake` smoke cannot reach DEFINE completion + +- **Severity:** block-ship +- **Where:** `src/providers/fake.ts:101`, `src/commands/run.ts:2358`, command `code-oz run --request "build a hello cli" --provider fake` +- **Evidence:** + + ```text + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --request "build a hello cli" --provider fake + exit: 1 + + WARNING: --provider fake is active + --- ba reply (turn 0) --- + fake response + --- ba reply (turn 1) --- + fake response + DEFINE phase reached the conversation cap without converging on a SPEC. + + command: find .code-oz/state/runs -name 'GATE_*_PASSED.json' -o -name 'NEEDS_INTERVENTION.json' -o -name 'STOP.json' + exit: 0 + .code-oz/state/runs/01KRHPRFW3F0BC7QWGAZYTG24G/NEEDS_INTERVENTION.json + ``` + + `events.jsonl` confirms the run stopped after `ask_me_max_rounds_exceeded`; no `GATE_DEFINE_PASSED.json`, let alone `GATE_SHIP_PASSED.json`, was written. Source confirms the default FakeProvider response is the literal `fake response`, which cannot satisfy DEFINE's SPEC/ready-signal contract. +- **Why it matters for first-run UX:** The playbook smoke command is `code-oz run --provider fake`; a new user or CI cannot complete the advertised offline path without hidden fake-script machinery. +- **Proposed fix:** Add a package-spawn e2e that runs `code-oz init && code-oz run --provider fake` and expects a shipped run, then make the no-script FakeProvider path use a built-in first-run fixture that emits valid phase artifacts through SHIP. If fake-script remains the only full-lifecycle route, expose a first-run-safe command/fixture and update the smoke playbook, but do not leave plain `--provider fake` advertised as the offline run path. +- **Effort estimate:** m + +### F1.2 - Explicit resume surfaces are missing + +- **Severity:** block-ship +- **Where:** `src/cli.ts:58`, `src/commands/run.ts:427`, command `code-oz run --resume` +- **Evidence:** + + ```text + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz resume --help + exit: 1 + code-oz: unknown command 'resume' + + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --resume + exit: 2 + code-oz run: unknown argument: --resume + ``` + + `CLAUDE.md` rule 12 names `code-oz resume`; the A1 checklist names `code-oz run --resume`. The current CLI implements neither explicit surface. It only continues active runs via bare `code-oz run`. +- **Why it matters for first-run UX:** A user recovering from terminal death will follow the documented/prompted resume wording and hit an unknown-command or unknown-argument error instead of a recovery path. +- **Proposed fix:** Pick one canonical explicit surface, preferably `code-oz resume` with `code-oz run --resume` as an alias if the playbook keeps that spelling. Add package-spawn tests for successful resume idempotency and mid-PLAN recovery, and make help text point at the chosen command. +- **Effort estimate:** m + +### F1.3 - Doctor first-run UX is not an aggregate check and per-subcommand help runs probes + +- **Severity:** fix-soon +- **Where:** `src/commands/doctor.ts:136`, `src/commands/doctor.ts:154`, `src/commands/doctor.ts:661` +- **Evidence:** + + ```text + command: env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor + exit: 1 + Usage: code-oz doctor [options] + + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor providers --help + exit: 1 + PROVIDER AUTH MODEL LATENCY REQ ERROR + claude missing no 1ms yes provider_io_error: claude CLI not found in PATH + codex missing no 0ms yes provider_io_error: codex CLI not found in PATH + Unhealthy required providers: claude (authStatus=missing), codex (authStatus=missing). Exiting 1. + + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor run --help + exit: 0 + # code-oz doctor run + ## Active run + runId: 01KRHPRFW3F0BC7QWGAZYTG24G + ``` + + Provider failures show error codes but no one-line remediation for `claude` or `codex`. `doctor tools` does include install hints, so the provider path is inconsistent. +- **Why it matters for first-run UX:** `code-oz doctor` is the natural first diagnostic command; today it exits as usage help, and `--help` on nested doctor commands can execute probes or inspect active run state. +- **Proposed fix:** Make bare `code-oz doctor` run a concise aggregate of providers/tools/git, or explicitly make it help with exit 0 and point to the exact subcommands. Honor `--help` after every doctor subcommand before executing probes. Add provider remediation strings such as install/login commands and expected CLI/OAuth/API-key source, without printing secrets. +- **Effort estimate:** s + +### F1.4 - Brownfield init behavior does not match the A1 contract + +- **Severity:** fix-soon +- **Where:** `src/commands/init.ts:123`, `src/commands/init.ts:147`, command `code-oz init --force` +- **Evidence:** + + ```text + command: touch package.json + exit: 0 + + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init + exit: 0 + code-oz: initialized brownfield project at /private/tmp/code-oz-a1-nonempty.F6RHMg/.code-oz + + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init --force + exit: 0 + code-oz: initialized brownfield project at /private/tmp/code-oz-a1-nonempty.F6RHMg/.code-oz + + command: find .code-oz -maxdepth 3 -type f + exit: 0 + .code-oz/config.yaml + .code-oz/README.md + .code-oz/.gitignore + ``` + + The A1 checklist says non-empty dirs should refuse by default and `--force` should write brownfield-shape state with an `audit_completed` placeholder note. Current code initializes brownfield by default and writes no run state or placeholder. +- **Why it matters for first-run UX:** Brownfield is a common first-run path; this contract mismatch means a user can initialize an existing repo but receives no clear indication that AUDIT runtime is still M17/deferred. +- **Proposed fix:** Reconcile the product contract. Either update the A1/playbook docs to bless auto-brownfield init, or implement the refusal/`--force` split. If M17 remains out of scope, print an explicit brownfield runtime note and add a non-crashing placeholder state only if the orchestrator will actually consume it. +- **Effort estimate:** m + +### F1.5 - Ctrl-C STOP.json path appears unwired + +- **Severity:** fix-soon +- **Where:** `src/state/gates.ts:304`, command `rg -n "SIGINT|SIGTERM|writeStopGate|STOP\\.json|stop" src tests | head -80` +- **Evidence:** + + ```text + command: rg -n "SIGINT|SIGTERM|writeStopGate|STOP\\.json|stop" src tests | head -80 + exit: 0 + src/state/gates.ts:304:export function writeStopGate( + tests/state-gates.test.ts:370: test('writePauseGate / writeStopGate write valid files', async () => { + ``` + + The search found the gate writer and unit coverage, but no CLI-level `SIGINT` or `SIGTERM` handler wiring it during `run`. +- **Why it matters for first-run UX:** The checklist requires Ctrl-C during any phase to write a clean `STOP.json`; without signal handling, an interrupted run likely exits by default and leaves recovery ambiguous. +- **Proposed fix:** Add signal handlers once `runId` and current phase are known. On first signal, write `STOP.json` with reason, phase, and event pointer, append a stopped outcome event if that is the state contract, then exit with the conventional signal code. Add a spawn test that kills a packaged/dev CLI mid-phase and asserts `STOP.json`. +- **Effort estimate:** m + +### F1.6 - Effort vocabulary is internally inconsistent, though event order is correct + +- **Severity:** nit +- **Where:** `src/commands/run.ts:2376`, `docs/handoffs/2026-05-13-codex-finalize-distribution.md:110` +- **Evidence:** + + ```text + command: env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --request "x" --effort low + exit: 2 + code-oz run: --effort must be one of: lite | balanced | max | beast (got "low"; see code-oz run --help) + + command: nl -ba .code-oz/state/runs/01KRHPQ6YJMPN5KRW50KM9FJ1Y/events.jsonl | sed -n '1,12p' + exit: 0 + 1 {"type":"run_started",...} + 2 {"type":"effort_envelope_applied","effort":"balanced",...} + 3 {"type":"phase_entered","phase":"define",...} + ``` + + The current CLI supports `lite | balanced | max | beast`; the A1 checklist asks for `low/medium/high/max`. The required event position is correct for the tested default/balanced run. +- **Why it matters for first-run UX:** A smoke script or doc using `low`, `medium`, or `high` will fail before any run starts, even though rule 23 event order is otherwise implemented. +- **Proposed fix:** Pick one vocabulary and align docs, help, tests, and playbook. If old names may exist in user snippets, add aliases with deprecation text (`low -> lite`, `medium -> balanced`, `high -> max`) and keep `max` unambiguous. +- **Effort estimate:** xs + +## Positive checks + +- `npm --cache /private/tmp/code-oz-a1-pack.3k9StN/npm-cache pack --pack-destination /private/tmp/code-oz-a1-pack.3k9StN` exited 0 and produced `tuel-code-oz-0.20.0-alpha.0.tgz`. +- Temp-prefix install exited 0: `npm --cache /private/tmp/code-oz-a1-home.hZDZ5v/.npm-cache --prefix /private/tmp/code-oz-a1-prefix.7SFpZf install -g /private/tmp/code-oz-a1-pack.3k9StN/tuel-code-oz-0.20.0-alpha.0.tgz`. +- First packaged invocation succeeded after including Node on PATH: `code-oz --version` printed `0.20.0-alpha.0` with exit 0. +- `code-oz --help`, `code-oz run --help`, `code-oz init --help`, and `code-oz approve --help` exited 0 and printed readable help. +- Missing default provider during `run --request` did not print a raw stack trace; it wrote `NEEDS_INTERVENTION.json` and actionable Claude CLI suggestions. It does not yet mention all supported setup routes. +- `effort_envelope_applied` landed at event position 2 in the tested packaged run. + +## Commands run + +Read and orientation commands: + +```text +git status --short --branch # exit 0 +sed -n '1,260p' docs/handoffs/2026-05-13-codex-finalize-distribution.md # exit 0 +sed -n '260,370p' docs/handoffs/2026-05-13-codex-finalize-distribution.md # exit 0 +sed -n '1,260p' CLAUDE.md # exit 0 +sed -n '1,220p' docs/design/CODEX_SYNTHESIS_W3A.md # exit 0 +sed -n '1,240p' README.md # exit 0 +sed -n '1,220p' docs/ABOUT.md # exit 0 +sed -n '1,220p' docs/design/ROADMAP.md # exit 0 +rg --files src tests npm-wrapper scripts docs/contracts docs/demo | rg '(cli|commands|provider|fake|run|init|doctor|resume|effort|package|smoke|wrapper|events|gates)' # exit 0 +sed -n '1,220p' package.json # exit 0 +sed -n '1,260p' npm-wrapper/index.cjs # exit 0 +sed -n '1,280p' src/cli.ts # exit 0 +sed -n '1,280p' src/commands/init.ts # exit 0 +sed -n '1,340p' src/commands/run.ts # exit 0 +sed -n '320,760p' src/commands/run.ts # exit 0 +sed -n '2350,2395p' src/commands/run.ts # exit 0 +sed -n '1,320p' src/commands/doctor.ts # exit 0 +sed -n '320,700p' src/commands/doctor.ts # exit 0 +sed -n '700,860p' src/commands/doctor.ts # exit 0 +sed -n '1,260p' src/providers/fake.ts # exit 0 +rg -n "SIGINT|SIGTERM|writeStopGate|STOP\\.json|stop" src tests | head -80 # exit 0 +``` + +Pack/install/smoke commands: + +```text +mktemp -d /private/tmp/code-oz-a1-pack.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-pack.3k9StN +npm pack --pack-destination /private/tmp/code-oz-a1-pack.3k9StN # exit 1, npm tried to write logs under real HOME +npm --cache /private/tmp/code-oz-a1-pack.3k9StN/npm-cache pack --pack-destination /private/tmp/code-oz-a1-pack.3k9StN # exit 0 +mktemp -d /private/tmp/code-oz-a1-home.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-home.hZDZ5v +mktemp -d /private/tmp/code-oz-a1-prefix.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-prefix.7SFpZf +npm --cache /private/tmp/code-oz-a1-home.hZDZ5v/.npm-cache --prefix /private/tmp/code-oz-a1-prefix.7SFpZf install -g /private/tmp/code-oz-a1-pack.3k9StN/tuel-code-oz-0.20.0-alpha.0.tgz # exit 0 +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz --version # exit 127, node not on sanitized PATH +which node # exit 0 -> /Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin/node +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz --version # exit 0 +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz --help # exit 0 +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor # exit 1 +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --help # exit 0 +env HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init --help # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor providers # exit 1 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor tools # exit 1 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor git # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz approve --help # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor run --help # exit 0, executed inspector instead of help +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz doctor providers --help # exit 1, executed probe instead of help +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --resume # exit 2 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz resume --help # exit 1 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --request "x" --effort low # exit 2 +``` + +Temp project commands: + +```text +mktemp -d /private/tmp/code-oz-a1-work.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-work.JBuP38 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init # exit 0 +find .code-oz -maxdepth 2 -type f -o -type d # exit 0 +sed -n '1,180p' .code-oz/README.md # exit 0 +sed -n '1,220p' .code-oz/config.yaml # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --request "build a hello cli" # exit 1 +find .code-oz/state -maxdepth 4 -type f -name 'NEEDS_INTERVENTION.json' -o -name 'events.jsonl' -o -name 'current.json' -o -name 'active.json' # exit 0 +nl -ba .code-oz/state/runs/01KRHPQ6YJMPN5KRW50KM9FJ1Y/events.jsonl | sed -n '1,12p' # exit 0 +mktemp -d /private/tmp/code-oz-a1-nonempty.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-nonempty.F6RHMg +touch package.json # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init # exit 1, existing .code-oz refusal +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init --force # exit 0 +find .code-oz -maxdepth 3 -type f # exit 0 +mktemp -d /private/tmp/code-oz-a1-fake.XXXXXX # exit 0 -> /private/tmp/code-oz-a1-fake.LPMsFh +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz init # exit 0 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --request "build a hello cli" --provider fake # exit 1 +env -i HOME=/private/tmp/code-oz-a1-home.hZDZ5v XDG_CACHE_HOME=/private/tmp/code-oz-a1-home.hZDZ5v/.cache PATH=/private/tmp/code-oz-a1-prefix.7SFpZf/bin:/Users/ozzy-mac/.nvm/versions/node/v22.22.0/bin:/usr/bin:/bin:/usr/sbin:/sbin /private/tmp/code-oz-a1-prefix.7SFpZf/bin/code-oz run --provider fake # exit 1 +find .code-oz/state/runs -name 'GATE_*_PASSED.json' -o -name 'NEEDS_INTERVENTION.json' -o -name 'STOP.json' # exit 0 +nl -ba .code-oz/state/runs/01KRHPRFW3F0BC7QWGAZYTG24G/events.jsonl | sed -n '1,24p' # exit 0 +``` + +## Unchecked items and gaps + +- Did not test provider keys in isolation for `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or `XAI_API_KEY`; no live-provider credentials were used. +- Did not complete a successful packaged DEFINE->SHIP run, successful resume, successful idempotency replay, or mid-PLAN kill/resume because the plain FakeProvider path blocks in DEFINE and explicit resume surfaces are missing. +- Did not dynamically send Ctrl-C during a live provider phase. Source search found no CLI signal handler wiring to `writeStopGate`; this remains a source-evidence finding, not a runtime transcript. +- Did not exhaustively run every nested doctor baseline command help variant. Tested top-level help plus `init`, `run`, `approve`, `doctor`, `doctor providers --help`, and `doctor run --help`. +- Did not run full `bun test`, `bun run typecheck`, or binary build; this was a read-only audit plus packaged smoke, and no product code was changed. diff --git a/docs/handoffs/codex-finalize/A10-hygiene.md b/docs/handoffs/codex-finalize/A10-hygiene.md new file mode 100644 index 0000000..122b1d9 --- /dev/null +++ b/docs/handoffs/codex-finalize/A10-hygiene.md @@ -0,0 +1,220 @@ +# A10-hygiene findings + +Sub-task: A10 +Operator: codex-subtask-10 +Started: 2026-05-13T22:12:00Z +Finished: 2026-05-13T22:27:41Z + +## Summary + +Four findings. Severity mix: 0 block-ship, 3 fix-soon, 1 nit. Root CLI typecheck is clean, GUI manual `tsc` is clean, no committed real `.env` file was found, and no high-confidence secret pattern was found in JSON/JSONL fixtures. The main hygiene risks are GUI lint not being usable as a clean gate, GUI typecheck not being exposed as a script or covered by root typecheck, unresolved source TODOs without issue links, and stale GUI dependency/export surface. + +## Findings + +### F10.1 - GUI lint is not a usable hygiene gate + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/eslint.config.mjs:9`, `code-oz-gui/app/page.tsx:180`, `code-oz-gui/app/page.tsx:255`, `code-oz-gui/components/AIHelper.tsx:33` +- **Evidence:** + ```text + command: cd code-oz-gui && bun run lint + exit: 1 + + $ eslint . + /code-oz-gui/.tmp/bun-cache/playwright-core@1.60.0@@@1/lib/utilsBundle.js + error React Hook "useColor" is called in function "getOutHasColors" ... + + /code-oz-gui/app/page.tsx + 180:19 error React Hook "useWorkspacePath" cannot be called inside a callback + 255:17 error React Hook "useWorkspacePath" cannot be called inside a callback + + /code-oz-gui/components/AIHelper.tsx + 33:5 error Calling setState synchronously within an effect can trigger cascading renders + + result summary: 135 problems, 126 errors, 9 warnings + + code-oz-gui/eslint.config.mjs: + 9 export default defineConfig([{ + 10 extends: [...next], + 11 }]); + + code-oz-gui/.gitignore: + 9 .tmp/ + ``` +- **Why it matters for first-run UX:** A package-level `lint` script that fails on ignored cache files plus source files makes it hard to separate real GUI regressions from tool-noise before distribution. +- **Proposed fix:** Add explicit ESLint ignores for `.next/`, `.tmp/`, `node_modules/`, `playwright-report/`, and `test-results/`; rename the local `useWorkspacePath` callback to avoid hook-rule false positives; then address or intentionally configure the React lint rules for synchronous state resets in effects. No behavior change is required for the ignore/callback rename. If effect rewrites alter UI state timing, add a focused GUI test around switching cards/tabs. +- **Effort estimate:** s + +### F10.2 - GUI typecheck is not exposed as a script and root typecheck excludes it + +- **Severity:** fix-soon +- **Where:** `package.json:16`, `tsconfig.json:23`, `code-oz-gui/package.json:5` +- **Evidence:** + ```text + command: bun run typecheck + cwd: /Users/ozzy-mac/Projects/code-oz + exit: 0 + output: $ tsc --noEmit + + package.json: + 16 "typecheck": "tsc --noEmit", + + tsconfig.json: + 23 "include": ["src/**/*", "tests/**/*"], + + code-oz-gui/package.json: + 5 "scripts": { + 6 "dev": "next dev", + 7 "build": "NODE_ENV=production next build", + 8 "start": "next start", + 9 "lint": "eslint .", + 10 "clean": "next clean", + 11 "screenshots": "bun run scripts/capture-screenshots.ts", + 12 "test:e2e": "playwright test" + 13 } + + command: cd code-oz-gui && bun run typecheck + exit: 1 + output: error: Script not found "typecheck" + + command: cd code-oz-gui && ./node_modules/.bin/tsc --noEmit --project tsconfig.json + exit: 0 + ``` +- **Why it matters for first-run UX:** The finalize checklist can report a clean root typecheck while GUI TypeScript is not checked by the same script surface. +- **Proposed fix:** Add `typecheck: "tsc --noEmit --project tsconfig.json"` to `code-oz-gui/package.json`. Consider a root script such as `typecheck:all` that runs the CLI and GUI checks without changing the existing root `typecheck` contract. +- **Effort estimate:** xs + +### F10.3 - Source TODOs are unresolved and lack tracked issue links + +- **Severity:** fix-soon +- **Where:** `src/state/events.ts:275`, `code-oz-gui/components/Card.tsx:7` +- **Evidence:** + ```text + command: git grep -nE 'TODO|FIXME|XXX' -- src tests code-oz-gui ':!code-oz-gui/bun.lock' + exit: 0 + + code-oz-gui/components/Card.tsx:7: + // TODO(a11y): contrast - text-white/40 on #0a0a0a is about 3.77:1. Revisit subdued metadata colors in v0.2. + + src/state/events.ts:275: + // TODO(run-start emitter follow-up): either widen this narrow payload + + tests/cost-by-parent-task.test.ts:12: + const RUN = '01HXXXX0YYYY1ZZZZ22222' + + tests/guardrails.test.ts: + fixture strings include TODO/FIX text intentionally + ``` +- **Why it matters for first-run UX:** The A10 checklist requires each TODO/FIXME/XXX to be closed or linked to a tracked issue; the two source TODOs have no issue link or owner. +- **Proposed fix:** For `src/state/events.ts`, either close the telemetry contract mismatch or replace the TODO with a tracked issue link and a one-line owner/scope. For `Card.tsx`, either fix the low-contrast metadata color now or link it to the A9 accessibility follow-up. Leave test fixture strings alone. +- **Effort estimate:** xs + +### F10.4 - GUI has stale dependency and export surface + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/package.json:16`, `code-oz-gui/package.json:18`, `code-oz-gui/package.json:40`, `code-oz-gui/lib/oz-bridge.ts:60`, `code-oz-gui/lib/types.ts:17`, `code-oz-gui/lib/types.ts:23`, `code-oz-gui/hooks/use-mobile.ts:5` +- **Evidence:** + ```text + command: rg -n "@hookform/resolvers|class-variance-authority|firebase-tools" code-oz-gui/app code-oz-gui/components code-oz-gui/hooks code-oz-gui/lib code-oz-gui/tests code-oz-gui/scripts code-oz-gui/*.ts code-oz-gui/*.mjs code-oz-gui/*.json --glob '!code-oz-gui/bun.lock' + exit: 0 + + code-oz-gui/package.json:16: "@hookform/resolvers": "^5.2.1", + code-oz-gui/package.json:18: "class-variance-authority": "^0.7.1", + code-oz-gui/package.json:40: "firebase-tools": "^15.0.0", + + command: rg -n "\\b(LogEntry|SavedAction|ProjectStatus|ozBridge|useIsMobile)\\b" code-oz-gui/app code-oz-gui/components code-oz-gui/hooks code-oz-gui/lib code-oz-gui/tests code-oz-gui/scripts --glob '*.{ts,tsx}' + exit: 0 + + code-oz-gui/lib/oz-bridge.ts:60:export const ozBridge = OzBridge.getInstance(); + code-oz-gui/lib/types.ts:17:export interface SavedAction { + code-oz-gui/lib/types.ts:23:export type ProjectStatus = 'idle' | 'running' | 'warning' | 'processing'; + code-oz-gui/hooks/use-mobile.ts:5:export function useIsMobile() { + ``` +- **Why it matters for first-run UX:** Unused dependencies increase install size and lockfile churn; dead exported GUI helpers make the current CLI bridge harder to audit. +- **Proposed fix:** Remove `@hookform/resolvers`, `class-variance-authority`, and `firebase-tools` unless another worker confirms a hidden deployment path needs them. Remove or quarantine `lib/oz-bridge.ts`, `SavedAction`, `ProjectStatus`, and `useIsMobile`; if any are intentional public API, add a one-line comment explaining the public-API reason. +- **Effort estimate:** s + +### F10.5 - One TS suppression remains, but it is scoped to a negative type test + +- **Severity:** nit +- **Where:** `tests/providers-types.test.ts:50` +- **Evidence:** + ```text + command: rg -n '@ts-(ignore|expect-error)|ts-ignore|ts-expect-error' src tests code-oz-gui --glob '!code-oz-gui/.next/**' --glob '!**/node_modules/**' + exit: 0 + + tests/providers-types.test.ts:50: + // @ts-expect-error: issues is readonly + + No // @ts-ignore was found. + ``` +- **Why it matters for first-run UX:** TS suppressions can hide real type drift; this one is a narrow compile-time assertion, but it should stay intentional. +- **Proposed fix:** No immediate code change. Keep this as `@ts-expect-error`, not `@ts-ignore`, and leave the explanatory comment in place. +- **Effort estimate:** xs + +## Checked without findings + +- Root `bun run typecheck` passed with exit 0. +- GUI direct TypeScript check passed with exit 0 via `./node_modules/.bin/tsc --noEmit --project tsconfig.json`. +- Root dependency list is small: `@types/bun`, `typescript`, `yaml`. GUI and root share only `typescript`; installed versions both resolve to `5.9.3`. +- `yaml` is used by CLI source, tests, and demo scripts. +- `.env` and `code-oz-gui/.env` exist locally but are ignored. `git ls-files` only reports `code-oz-gui/.env.example`. +- High-confidence secret scan over `code-oz-gui/fixtures` and `tests/fixtures` returned no matches. +- Broad secret scan hits in `events.jsonl` were provider/model names and token budget fields, not API keys. +- No committed real `.env` file found. + +## Gaps + +- I did not run a full dead-export tool such as Knip or ts-prune. The dead-export finding is based on fast `rg` checks only. +- I did not run `bun test`, GUI Playwright e2e, or `code-oz-gui` production build; those are outside this A10 timebox and covered by other finalize gates. +- I did not inspect ignored local `.env` contents. +- I did not prove whether `firebase-tools` is needed by an external deployment flow; it is unused by tracked GUI scripts and source. + +## Commands run + +| Command | Cwd | Exit | +| --- | --- | --- | +| `git status --short --branch` | repo root | 0 | +| `pwd` | repo root | 0 | +| `sed -n '1,240p' docs/handoffs/2026-05-13-codex-finalize-distribution.md` | repo root | 0 | +| `sed -n '240,520p' docs/handoffs/2026-05-13-codex-finalize-distribution.md` | repo root | 0 | +| `sed -n '1,240p' CLAUDE.md` | repo root | 0 | +| `sed -n '1,220p' package.json` | repo root | 0 | +| `sed -n '1,220p' tsconfig.json` | repo root | 0 | +| `find code-oz-gui -maxdepth 2 -name 'package*.json' -o -name 'tsconfig*.json'` | repo root | 0 | +| `sed -n '1,240p' code-oz-gui/package.json` | repo root | 0 | +| `sed -n '1,220p' code-oz-gui/tsconfig.json` | repo root | 0 | +| `sed -n '1,200p' code-oz-gui/.next/package.json` | repo root | 0 | +| `sed -n '1,220p' code-oz-gui/README.md` | repo root | 0 | +| `rg --files src tests code-oz-gui \| sed -n '1,220p'` | repo root | 0 | +| `bun run typecheck` | repo root | 0 | +| `git grep -nE 'TODO|FIXME|XXX'` | repo root | 0 | +| `git grep -nE 'TODO|FIXME|XXX' -- src tests code-oz-gui ':!code-oz-gui/bun.lock'` | repo root | 0 | +| `bun pm ls --depth=0` | repo root | 0 | +| `bun pm ls --depth=0` | `code-oz-gui` | 0 | +| `rg -n '@ts-(ignore\|expect-error)\|ts-ignore\|ts-expect-error' src tests code-oz-gui --glob '!code-oz-gui/.next/**' --glob '!**/node_modules/**'` | repo root | 0 | +| `git ls-files \| rg '(^\|/)\\.env($\|\\.)'` | repo root | 0 | +| `find . -name '.env*' -not -path './node_modules/*' -not -path './code-oz-gui/node_modules/*' -not -path './code-oz-gui/.next/*' -not -path './.git/*'` | repo root | 0 | +| `rg -n -i '(api[_-]?key\|secret\|token\|bearer\|sk-[A-Za-z0-9]\|AIza\|anthropic\|openai\|gemini\|xai\|GEMINI_API_KEY\|OPENAI_API_KEY\|ANTHROPIC_API_KEY\|XAI_API_KEY)' code-oz-gui/fixtures tests/fixtures --glob '*.jsonl' --glob '*.json' --glob '!**/node_modules/**'` | repo root | 0 | +| `rg -n "(sk-[A-Za-z0-9]{20,}\|AIza[0-9A-Za-z_-]{20,}\|xai-[A-Za-z0-9_-]+\|anthropic_[A-Za-z0-9_-]+\|Bearer\\s+[A-Za-z0-9._-]{20,}\|[A-Z_]*API_KEY[\\\"=:[:space:]]+[^\\\"[:space:],]+)" code-oz-gui/fixtures tests/fixtures --glob '*.jsonl' --glob '*.json' --glob '!**/node_modules/**'` | repo root | 1 | +| `git grep -nE '(sk-[A-Za-z0-9]{20,}\|AIza[0-9A-Za-z_-]{20,}\|xai-[A-Za-z0-9_-]+\|anthropic_[A-Za-z0-9_-]+\|Bearer[[:space:]]+[A-Za-z0-9._-]{20,}\|[A-Z_]*API_KEY[=:\"][^\"[:space:],]+)' -- '*.env*' '*.json' '*.jsonl'` | repo root | 0 | +| `rg -n '^\\s*export\\s+(async\\s+)?(function\|const\|class\|type\|interface\|enum)\\s+\|^\\s*export\\s+\\{' src code-oz-gui --glob '*.{ts,tsx}' --glob '!code-oz-gui/.next/**' --glob '!**/node_modules/**'` | repo root | 0 | +| `bun -e "const fs=require('fs'); const root=JSON.parse(fs.readFileSync('package.json','utf8')); const gui=JSON.parse(fs.readFileSync('code-oz-gui/package.json','utf8')); const all=n=>Object.assign({}, n.dependencies\|\|{}, n.devDependencies\|\|{}); const a=all(root), b=all(gui); for (const k of Object.keys(a).filter(k=>k in b).sort()) console.log(k+' root='+a[k]+' gui='+b[k]);"` | repo root | 0 | +| `git status --short -- .env code-oz-gui/.env code-oz-gui/.env.example` | repo root | 0 | +| `git ls-files -- .env code-oz-gui/.env code-oz-gui/.env.example` | repo root | 0 | +| `git check-ignore -v .env code-oz-gui/.env code-oz-gui/.env.example` | repo root | 0 | +| `rg -n 'from ["\\''](@hookform/resolvers\|class-variance-authority\|lucide-react\|motion\|react-markdown\|remark-frontmatter\|remark-gfm\|tailwind-merge\|tw-animate-css\|@google/genai)["\\'']\|from ["\\''](@hookform/resolvers/\|class-variance-authority/\|lucide-react/\|motion/\|react-markdown/\|remark-frontmatter/\|remark-gfm/\|tailwind-merge/\|tw-animate-css/\|@google/genai/)' code-oz-gui --glob '*.{ts,tsx,js,jsx,mjs}' --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**'` | repo root | 2 | +| `rg -n "(@hookform/resolvers\|class-variance-authority\|lucide-react\|motion\|react-markdown\|remark-frontmatter\|remark-gfm\|tailwind-merge\|tw-animate-css\|@google/genai)" code-oz-gui --glob '*.{ts,tsx,js,jsx,mjs}' --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**'` | repo root | 0 | +| `rg -n "from ['\\\"]yaml['\\\"]\|from ['\\\"]yaml/" src tests scripts npm-wrapper --glob '*.{ts,tsx,js,cjs,mjs}'` | repo root | 0 | +| `rg -n "(autoprefixer\|postcss\|tailwindcss\|@tailwindcss/postcss\|@tailwindcss/typography\|firebase-tools)" code-oz-gui --glob '*.{ts,tsx,js,jsx,mjs,json,css}' --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**'` | repo root | 0 | +| `rg -n "\\b(cn\|LogType\|SavedAction\|ProjectStatus\|ozBridge\|useIsMobile)\\b" code-oz-gui --glob '*.{ts,tsx}' --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**'` | repo root | 0 | +| `rg -n "\\b(validateArtifactSyncPath\|_validateArtifactSyncPath\|BUILD_READY_SIGNAL\|PLAN_READY_SIGNAL\|REVIEW_READY_SIGNAL)\\b" src tests --glob '*.{ts,tsx}'` | repo root | 0 | +| `rg -n "@hookform/resolvers\|class-variance-authority\|tw-animate-css" code-oz-gui --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**' --glob '!code-oz-gui/bun.lock'` | repo root | 0 | +| `rg -n "firebase" code-oz-gui --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**' --glob '!code-oz-gui/bun.lock'` | repo root | 0 | +| `rg -n "SavedAction\|ProjectStatus\|ozBridge\|useIsMobile" code-oz-gui --glob '*.{ts,tsx}' --glob '!code-oz-gui/.next/**' --glob '!code-oz-gui/node_modules/**'` | repo root | 0 | +| `bun run typecheck` | `code-oz-gui` | 1 | +| `./node_modules/.bin/tsc --noEmit --project tsconfig.json` | `code-oz-gui` | 0 | +| `bun run lint` | `code-oz-gui` | 1 | +| `git status --short -- code-oz-gui/.tmp` | repo root | 0 | +| `git check-ignore -v code-oz-gui/.tmp code-oz-gui/.tmp/bun-cache` | repo root | 0 | +| `date -u +%Y-%m-%dT%H:%M:%SZ` | repo root | 0 | diff --git a/docs/handoffs/codex-finalize/A2-gui-first-run.md b/docs/handoffs/codex-finalize/A2-gui-first-run.md new file mode 100644 index 0000000..3eead2f --- /dev/null +++ b/docs/handoffs/codex-finalize/A2-gui-first-run.md @@ -0,0 +1,238 @@ +# A2-gui-first-run findings + +Sub-task: A2 +Operator: codex-subtask-A2 +Started: 2026-05-13T22:13:00Z +Finished: 2026-05-13T22:35:04Z + +## Summary + +GUI fixture rendering is mostly intact under production start: the board, composer, drawer tabs, artifact/events/decisions path, and the existing Playwright happy path passed. The default first-run path is not clean, though. `bun run dev` hit `EMFILE` watcher errors and served `/` as a 404 in this audit environment, live fake-run start returned 503 because the GUI selected a stale local `dist/code-oz` binary, and live approval actions currently write to the fixture request directory rather than the active run. Severity mix: 3 block-ship, 4 fix-soon, 1 nit. + +## Commands run + +Commands were run from a temp copy at `/private/tmp/code-oz-a2-gui-Em5TI6/code-oz-gui` unless noted. + +| Command | Exit | Notes | +|---|---:|---| +| `pwd && git status --short --branch` | 0 | Branch confirmed: `finalize/v0.20.1-first-run-polish...origin/main`; other A-worker untracked files existed and were not touched. | +| `rsync -a --exclude node_modules --exclude .next --exclude test-results --exclude playwright-report code-oz-gui/ /private/tmp/code-oz-a2-gui-Em5TI6/code-oz-gui/` | 0 | Temp copy for install/build/test artifacts. | +| `bun install > /private/tmp/code-oz-a2-gui-Em5TI6/bun-install.log 2>&1` | 1 | Failed before install: `bun is unable to write files to tempdir: PermissionDenied`. | +| `env TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp bun install > /private/tmp/code-oz-a2-gui-Em5TI6/bun-install-tmpdir.log 2>&1` | 1 | Same tempdir permission failure. | +| `env HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home XDG_CACHE_HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home/.cache TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun install > /private/tmp/code-oz-a2-gui-Em5TI6/bun-install-isolated.log 2>&1` | 0 | Installed 1030 packages in 33.96s. No install warnings besides `.env` load. | +| `env HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home XDG_CACHE_HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home/.cache TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run dev > /private/tmp/code-oz-a2-gui-Em5TI6/dev.log 2>&1` | n/a | Server printed `Ready in 1109ms`, then repeated `EMFILE` watcher errors and served `GET / 404`. Killed after audit. | +| `env HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home XDG_CACHE_HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home/.cache TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run build > /private/tmp/code-oz-a2-gui-Em5TI6/build.log 2>&1` | 0 | Production build clean. Route table includes `/` and API routes. | +| `env HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home XDG_CACHE_HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home/.cache TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun test > /private/tmp/code-oz-a2-gui-Em5TI6/bun-test.log 2>&1` | 1 | `No tests found!` | +| `env HOME=/private/tmp/code-oz-a2-gui-Em5TI6/home ... bun run test:e2e > /private/tmp/code-oz-a2-gui-Em5TI6/e2e.log 2>&1` | 1 | Isolated HOME lacked Playwright browser cache. | +| `env TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run test:e2e > /private/tmp/code-oz-a2-gui-Em5TI6/e2e-default-home.log 2>&1` | 1 | Chromium launch blocked in sandbox: MachPort permission denied. | +| Same e2e command outside sandbox, while default dev server was running | 1 | Browser launched, but `/` was Next 404. | +| `env TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run start > /private/tmp/code-oz-a2-gui-Em5TI6/start.log 2>&1` | n/a | Production server ready in 253ms with `next start` standalone warning. Killed after e2e. | +| `curl -s -o /private/tmp/code-oz-a2-gui-Em5TI6/root.html -w '%{http_code}\n' http://localhost:3000/` | 0 | Returned `200` against production start. | +| `env TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run test:e2e > /private/tmp/code-oz-a2-gui-Em5TI6/e2e-prodstart-escalated.log 2>&1` | 0 | Existing Playwright happy path passed: `1 passed (7.6s)`. | +| `mv /private/tmp/code-oz-a2-gui-Em5TI6/code-oz-gui/.env /private/tmp/code-oz-a2-gui-Em5TI6/code-oz-gui/.env.audit-key-present` | 0 | Removed the copied local key file from the temp app before no-key AIHelper check. | +| `env NEXT_TELEMETRY_DISABLED=1 TMPDIR=/private/tmp/code-oz-a2-gui-Em5TI6/tmp BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a2-gui-Em5TI6/bun-cache bun run start -- -p 3012 > /private/tmp/code-oz-a2-gui-Em5TI6/start-no-key-3012.log 2>&1` | n/a | No-key production server printed `Ready in 257ms`. Still listening on PID 94334; kill requests were rejected. | +| `curl -s -w '\nHTTP_STATUS:%{http_code}\n' -H 'Content-Type: application/json' -d '{"runId":"r-2026-05-12-checkout-safari","cardId":"audit","currentTab":"artifact","prompt":"Explain this"}' http://localhost:3012/api/helper/ask > /private/tmp/code-oz-a2-gui-Em5TI6/helper-no-key-response.txt` | 0 | Returned 503 with `Gemini helper is not configured.` | +| `printf '{"name":"a2-smoke"}\n' > /private/tmp/code-oz-a2-live-repo-brown-HJKa9n/package.json` | 0 | Added a minimal marker file in the temp repo so `code-oz init` should detect brownfield. | +| `curl -s -w '\nHTTP_STATUS:%{http_code}\n' -H 'Content-Type: application/json' -d '{"description":"Audit this temp package repo for first-run GUI smoke","repoPath":"/private/tmp/code-oz-a2-live-repo-brown-HJKa9n","providerOverride":"fake"}' http://localhost:3012/api/run/start > /private/tmp/code-oz-a2-gui-Em5TI6/live-start-brown-response.txt` | 0 | API returned 503, while a run directory and active.json were still written. | +| `./dist/code-oz --version` from repo root | 0 | Printed `0.16.0-alpha.0`; current `package.json` is `0.20.0-alpha.0`. | + +## Checked coverage + +- Checked: `bun install`, default `bun run dev`, production `bun run build`, production start, root render, Board columns, composer typing via e2e path, drawer open/close, Artifact/Events/Decisions tab switching, Events filters, AIHelper expand/collapse, no-key AIHelper API response, live fake-run start API. +- Partially checked: Artifact frontmatter rendering and Events filters passed through the existing e2e path; provider-family provenance/accents were checked by source inspection. +- Gaps: live Gemini response with a real `GEMINI_API_KEY` was not exercised to avoid spending external provider tokens; live SSE transition mirroring could not be validated because `/api/run/start` returned 503; axe/a11y is A9 scope and was not run here; default dev render could not be inspected after it served 404. + +## Findings + +### F2.1 - GUI live runs resolve a stale hardcoded CLI binary + +- **Severity:** block-ship +- **Where:** `code-oz-gui/lib/code-oz-spawn.ts:73-118`, `/api/run/start` +- **Evidence:** + ```text + $ ./dist/code-oz --version + 0.16.0-alpha.0 + + code-oz-gui/lib/code-oz-spawn.ts: + 73 const execFileAsync = promisify(execFile); + 74 const CODE_OZ_SOURCE_DIR = join(homedir(), 'Projects', 'code-oz'); + 75 const CODE_OZ_DIST_BINARY = join(CODE_OZ_SOURCE_DIR, 'dist', 'code-oz'); + 99 export async function resolveCodeOzBinary(): Promise { + 100 if (await pathExists(CODE_OZ_DIST_BINARY)) { + 101 return { kind: 'binary', command: CODE_OZ_DIST_BINARY, args: [] }; + 102 } + + $ curl ... /api/run/start + {"error":"spawn-failed","detail":"code-oz exited before a runId was detected (exit 1)."} + HTTP_STATUS:503 + + /private/tmp/code-oz-a2-live-repo-brown-HJKa9n/.code-oz/config.yaml: + profile: brownfield + + /private/tmp/code-oz-a2-live-repo-brown-HJKa9n/.code-oz/state/runs/01KRHQGRFV24HHYJXRCH93WC5Y/events.jsonl: + {"type":"run_started","runId":"01KRHQGRFV24HHYJXRCH93WC5Y","profile":"greenfield",...} + ``` +- **Why it matters for first-run UX:** A user can install and open the GUI successfully, then the first real `COMPOSE` run silently uses an old CLI from `~/Projects/code-oz/dist/code-oz` and fails before the GUI can register or mirror the run. +- **Proposed fix:** Resolve the CLI relative to the monorepo checkout first, or require an explicit `CODE_OZ_CLI` path in dev. Before accepting a binary, run `--version` and compare it to the repo package version; if it mismatches or is absent, fall back to `bun --cwd run src/cli.ts`. Add a failing e2e/API test with a deliberately stale fake binary ahead of source in the resolution order, then verify `/api/run/start` registers the current run. +- **Effort estimate:** m + +### F2.2 - Live Approve/Revise actions write fixture requests, not run-specific requests + +- **Severity:** block-ship +- **Where:** `code-oz-gui/app/api/run/[runId]/approve/route.ts:93-121`, `code-oz-gui/lib/run-store.ts:423-440` +- **Evidence:** + ```text + code-oz-gui/app/api/run/[runId]/approve/route.ts: + 93 export async function POST(request: Request, context: RouteContext) { + 95 const runIdError = assertFixtureRunId(runId); + 115 const requestId = await writeApprovalRequest({ + 116 phase: body.phase, + 117 decision: body.action, + 118 revisionNotes: body.feedback, + 119 }); + + code-oz-gui/lib/run-store.ts: + 423 export async function writeApprovalRequest(input: { + 428 await mkdir(FIXTURE_REQUESTS_DIR, { recursive: true }); + 431 const requestPath = join(FIXTURE_REQUESTS_DIR, `${requestId}.json`); + 439 await writeFile(requestPath, `${JSON.stringify(body, null, 2)}\n`, 'utf8'); + ``` +- **Why it matters for first-run UX:** On a live run, clicking Approve or Ask for revisions appears to work but cannot reach the active CLI run directory, so the run can stall at a gate while the GUI shows no obvious control failure. +- **Proposed fix:** Make approval requests run-scoped: look up `getRunRecord(runId)`, write under that run's request/control directory, and include `runId`, `phase`, `taskId`, action, and feedback in the request file. If the intended control path is `code-oz approve`, call that subprocess explicitly and surface stdout/stderr. Add a test that registers a live run record, posts approve/revise, and asserts the request lands under the live run directory rather than `fixtures/sample-run/requests`. +- **Effort estimate:** m + +### F2.3 - Default `bun run dev` reached Ready but served the app as 404 + +- **Severity:** block-ship +- **Where:** `code-oz-gui/package.json:5-12`, default dev startup +- **Evidence:** + ```text + $ bun run dev + $ next dev + ▲ Next.js 15.5.18 + - Local: http://localhost:3000 + ✓ Starting... + Watchpack Error (watcher): Error: EMFILE: too many open files, watch + Watchpack Error (watcher): Error: EMFILE: too many open files, watch + ✓ Ready in 1109ms + ... + ○ Compiling /_not-found ... + ✓ Compiled /_not-found in 2.7s (605 modules) + GET / 404 in 3062ms + + $ bun run test:e2e + Error: expect(locator).toBeVisible() failed + Locator: getByText('Workspace: ./fixtures/sample-run') + Page snapshot: + - heading "404" + - heading "This page could not be found." + ``` +- **Why it matters for first-run UX:** The documented first run is `bun install && bun run dev`; printing a clean URL and then rendering a 404 is a hard stop for a new user. +- **Proposed fix:** Make the dev command resilient to watcher exhaustion. Options: set a dev-safe watcher mode by default, document and enforce `DISABLE_HMR=true` if that is the intended AI Studio path, or reduce watcher pressure in Next config. Add Playwright `webServer` coverage that starts `bun run dev` from a clean temp checkout and asserts `/` renders before tests begin. +- **Effort estimate:** s + +### F2.4 - AIHelper no-key UX omits the setup hint and logs a server stack + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/app/api/helper/ask/route.ts:149-154`, `code-oz-gui/lib/gemini-server.ts:7-12` +- **Evidence:** + ```text + $ curl ... /api/helper/ask + {"error":"helper-unavailable","detail":"Gemini helper is not configured."} + HTTP_STATUS:503 + + Server log: + AI helper request failed Error: GEMINI_API_KEY is not set. + at (.next/server/app/api/helper/ask/route.js:17:52420) + ``` +- **Why it matters for first-run UX:** The A2 contract asks for a one-line `set GEMINI_API_KEY to enable` hint; the current copy hides the exact env var from the UI and prints a stack for an expected configuration state. +- **Proposed fix:** Treat missing `GEMINI_API_KEY` as a typed expected state. Return `detail: "Set GEMINI_API_KEY to enable the Gemini helper."` and avoid `console.error` for that case. Add an API test for no-key and a UI test that opens the helper, sends a prompt, and sees the exact hint. +- **Effort estimate:** xs + +### F2.5 - Playwright e2e is not self-starting and can hit the wrong server + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/playwright.config.ts:3-18` +- **Evidence:** + ```text + code-oz-gui/playwright.config.ts: + 3 export default defineConfig({ + 10 use: { + 11 baseURL: 'http://localhost:3000', + 17 projects: [{ name: 'chromium', use: { browserName: 'chromium' } }], + 18 }); + + $ bun run test:e2e + 1 failed + Page snapshot: + - heading "404" + - heading "This page could not be found." + + $ bun run test:e2e # against production start + 1 passed (7.6s) + ``` +- **Why it matters for first-run UX:** A verification gate that depends on a pre-existing server can pass or fail based on whatever is already bound to port 3000, so it is weak evidence for the GUI first-run path. +- **Proposed fix:** Add `webServer` to the Playwright config using the intended command, URL, timeout, and `reuseExistingServer: false` in CI. If dev remains flaky, add a separate production-start e2e command and keep the first-run dev e2e as the release gate. +- **Effort estimate:** s + +### F2.6 - Drawer provenance and event accents do not show provider family + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/components/ArtifactView.tsx:79-106`, `code-oz-gui/components/ArtifactView.tsx:354-360`, `code-oz-gui/components/EventsView.tsx:121-130`, `code-oz-gui/components/EventsView.tsx:237-243` +- **Evidence:** + ```text + Artifact provenance shape: + 79 function findProvenance(input: { + 85 if (input.cardKind === 'audit') { + 88 return event && sha ? { artifactName: input.artifactName, sha, ts: event.ts } : null; + + Rendered chip: + 359 {provenance.artifactName} · sha: {shortSha(provenance.sha)} + + Event row coloring: + 121 function severityFor(eventType: string): Severity { + 126 if (eventType === 'intervention' || eventType === 'budget_warning') { + 239 className={cn( + 241 severity === 'warn' && 'border-l-2 border-l-amber-400/60 ...', + 242 severity === 'fail' && 'border-l-2 border-l-red-400/60 ...', + ``` +- **Why it matters for first-run UX:** The GUI's cross-family thesis is supposed to be visible in the first drawer interaction; the current artifact chip shows SHA only, and events color by severity rather than provider family. +- **Proposed fix:** Derive provider family from the relevant completion/invocation events and include it in the provenance chip, for example `Claude family · AUDIT.md · sha`. Add a provider-family color map for event rows while preserving warning/failure emphasis. Cover with a fixture assertion for audit, review, and task cards. +- **Effort estimate:** s + +### F2.7 - Fixture/demo does not render all five decision row kinds + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/fixtures/sample-run/events.jsonl:13-52`, `code-oz-gui/components/DecisionsView.tsx` +- **Evidence:** + ```text + $ rg 'question_added|gate_required|review_round_completed|budget_warning|debate_resolved' code-oz-gui/fixtures/sample-run/events.jsonl + 13: question_added + 17: gate_required + 25: gate_required + 41: review_round_completed + 52: budget_warning + + No `debate_resolved` event exists in the fixture. + The fixture `budget_warning` has no phase or taskId, while DecisionsView matches budget rows by taskId or phase. + ``` +- **Why it matters for first-run UX:** The Decisions tab claims five row kinds, but the first-run sample cannot demonstrate all five, and at least the unscoped budget warning is unlikely to render on any card. +- **Proposed fix:** Add fixture events for `debate_resolved` and a phase-scoped or task-scoped `budget_warning`, or teach `DecisionsView` to show run-scope budget warnings in the active phase/sidebar. Extend the e2e test to assert all five labels are visible across cards. +- **Effort estimate:** s + +### F2.8 - `bun test` has no GUI tests + +- **Severity:** nit +- **Where:** `code-oz-gui/package.json:5-13` +- **Evidence:** + ```text + $ bun test + bun test v1.3.9 (cf6cdbbb) + No tests found! + Tests need ".test", "_test_", ".spec" or "_spec_" in the filename + + package.json scripts include `test:e2e` but no `test`. + ``` +- **Why it matters for first-run UX:** The distribution playbook names `cd code-oz-gui && bun test` as a GUI gate; today that command fails because no unit/component tests exist. +- **Proposed fix:** Either add a `test` script that delegates to the intended GUI gate, or add focused component/API tests for `run-store`, `code-oz-spawn` resolution, AIHelper no-key handling, and Decisions row projection. +- **Effort estimate:** xs diff --git a/docs/handoffs/codex-finalize/A3-distribution.md b/docs/handoffs/codex-finalize/A3-distribution.md new file mode 100644 index 0000000..2726437 --- /dev/null +++ b/docs/handoffs/codex-finalize/A3-distribution.md @@ -0,0 +1,248 @@ +# A3-distribution findings + +Sub-task: A3 +Operator: codex-subtask-A3 +Started: 2026-05-13T22:05:00Z +Finished: 2026-05-13T22:24:00Z + +## Summary + +I found 3 findings: 1 block-ship and 2 fix-soon. The biggest risk is `npm-wrapper/index.cjs` trusting any existing cached `code-oz` binary by path alone, which violates the A3 tampered-cache requirement and can execute modified cache contents without a checksum check. The tagged `scripts/install.sh` path worked on macOS arm64, release asset SHAs matched `checksums.txt`, targeted distribution tests passed, and workflow source inspection matched the W3a contract. Linux host install and Homebrew strict online audit remain partially unverified due local Docker daemon and current Homebrew audit constraints. + +## Findings + +### F3.1 - npm wrapper executes a tampered cached binary without verification + +- **Severity:** block-ship +- **Where:** `npm-wrapper/index.cjs:125-128`, `npm-wrapper/index.cjs:163-168`, `tests/npm-wrapper.test.ts:34-46`, A3 checklist item in `docs/handoffs/2026-05-13-codex-finalize-distribution.md:138-140` +- **Evidence:** + + `npm-wrapper/index.cjs` returns the cached binary immediately when the file exists: + + ```text + 125 async function ensureBinary({ version, host, cacheRoot, baseUrl }) { + 126 const cacheDir = path.join(cacheRoot, version) + 127 const cachedBinary = path.join(cacheDir, 'code-oz') + 128 if (fs.existsSync(cachedBinary)) return cachedBinary + ``` + + The current tests prove cache reuse but do not cover a corrupted cache. A manual cache-tamper check executed the modified cached file even with the release URL unavailable: + + ```text + command: HOME=/private/tmp/code-oz-a3-npm-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-npm-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm NPM_CONFIG_USERCONFIG=/private/tmp/code-oz-a3-npm-home/.npmrc BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun bash -lc 'set -eu; printf "#!/bin/sh\necho cached-binary-tampered-live\n" > /private/tmp/code-oz-a3-wrapper-cache/0.20.0-alpha.0/code-oz; chmod +x /private/tmp/code-oz-a3-wrapper-cache/0.20.0-alpha.0/code-oz; CODE_OZ_NPM_CACHE_DIR=/private/tmp/code-oz-a3-wrapper-cache CODE_OZ_NPM_BASE_URL=file:///missing-after-tamper HOME=/private/tmp/code-oz-a3-npm-home node npm-wrapper/index.cjs --version' + exit: 0 + output: cached-binary-tampered-live + ``` + + A smaller temp-fixture repro produced the same behavior: + + ```text + command: HOME=/private/tmp/code-oz-a3-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun bash -lc 'set -eu; ROOT=$(mktemp -d /private/tmp/code-oz-a3-corrupt-cache.XXXXXX); mkdir -p "$ROOT/home" "$ROOT/cache/0.20.0-alpha.0"; printf "#!/bin/sh\necho tampered-cache-ran \"$@\"\n" > "$ROOT/cache/0.20.0-alpha.0/code-oz"; chmod +x "$ROOT/cache/0.20.0-alpha.0/code-oz"; CODE_OZ_NPM_CACHE_DIR="$ROOT/cache" CODE_OZ_NPM_BASE_URL="file:///definitely-missing-release" HOME="$ROOT/home" node npm-wrapper/index.cjs --probe' + exit: 0 + output: tampered-cache-ran + ``` + +- **Why it matters for first-run UX:** The npm channel claims a SHA-verified single-binary contract, but after first download the wrapper no longer proves the cached executable is the release binary. +- **Proposed fix:** After extracting the tarball, read `manifest.json`, verify the extracted binary SHA against the manifest row, and persist a sidecar such as `code-oz.sha256` or the manifest next to the cached binary. On cache hit, compute the cached binary SHA and compare it to the sidecar or manifest before executing. If the check fails, delete the cache entry and re-download; if re-download is unavailable, fail closed with a clear cache-corruption message. Add a failing test that creates a valid cache, mutates `cache//code-oz`, points `CODE_OZ_NPM_BASE_URL` at a missing URL, and asserts the tampered binary is not executed. +- **Effort estimate:** s + +### F3.2 - npm wrapper permits non-HTTPS download URLs and redirect downgrade + +- **Severity:** fix-soon +- **Where:** `npm-wrapper/index.cjs:65-80`, W3a risk lock in `docs/design/CODEX_SYNTHESIS_W3A.md` requires HTTPS-only release fetch behavior +- **Evidence:** + + ```text + 65 async function download(url, destination) { + 66 if (url.startsWith('file://')) { + ... + 74 const protocol = url.startsWith('https://') ? require('node:https') : require('node:http') + ... + 78 if (status >= 300 && status < 400 && response.headers.location) { + 79 response.resume() + 80 download(response.headers.location, destination).then(resolve, reject) + ``` + + The default base URL is HTTPS, but the downloader accepts any non-HTTPS URL through `node:http`, and redirects are followed without rejecting a protocol downgrade. + +- **Why it matters for first-run UX:** Without signed checksums, HTTPS is part of the integrity chain; if both tarball and `checksums.txt` are fetched over HTTP, a network attacker can replace both. +- **Proposed fix:** Parse URLs with `new URL()`. Allow only `https:` for production downloads and `file:` for explicit test fixtures. Reject `http:` and reject redirects whose resolved protocol is not `https:`. Add a unit test with a local redirect target that downgrades to `http:` and assert a clear fail-closed error. +- **Effort estimate:** xs + +### F3.3 - Homebrew audit command in the release recipe is not executable on this Homebrew setup + +- **Severity:** fix-soon +- **Where:** `docs/homebrew/code-oz.rb.template:15-16`, `docs/homebrew/README.md:56-58` +- **Evidence:** + + The rendered formula has valid Ruby syntax: + + ```text + command: HOME=/private/tmp/code-oz-a3-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun ruby -c docs/homebrew/code-oz.rb.template + exit: 0 + output: Syntax OK + ``` + + The documented path-based audit command is blocked by the installed Homebrew: + + ```text + command: HOME=/private/tmp/code-oz-a3-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun bash -lc 'set -eu; TMP=$(mktemp -d /private/tmp/code-oz-a3-homebrew.XXXXXX); sed -e "s/__VERSION__/0.20.0-alpha.0/g" -e "s/__SHA256_DARWIN_ARM64__/25f441e30a67b690db6e7b2fd6db5fd748e1738e6862e0c7f61f1fa59b8c6df6/" -e "s/__SHA256_DARWIN_X64__/dd6178dee576b9b1932fb1c7d584487b9416676625f49bd981639ed2f99b7cdf/" -e "s/__SHA256_LINUX_ARM64__/f97e2c99403cbf26010d282a0f6351eee38f5b6e5b3dfb1fc606ad00dc9fe1b1/" -e "s/__SHA256_LINUX_X64__/2ac24d187ea08d5ee17137a4e342a35caedbc009d68e1b21343554df892e6a46/" docs/homebrew/code-oz.rb.template > "$TMP/code-oz.rb"; HOMEBREW_CACHE=/private/tmp/code-oz-a3-brew-cache HOMEBREW_LOGS=/private/tmp/code-oz-a3-brew-logs brew audit --strict --online "$TMP/code-oz.rb"' + exit: 1 + output: Error: Calling `brew audit [path ...]` is disabled! Use `brew audit [name ...]` instead. + ``` + + `brew help audit` on this machine confirms the accepted shape is `brew audit [options] [formula|cask ...]`, with `--tap` available. + +- **Why it matters for first-run UX:** The release operator cannot produce the required Homebrew audit evidence using the documented local render command, so the Homebrew channel can look ready while its strict audit gate is unverified. +- **Proposed fix:** Update the Homebrew release recipe to audit the formula in a real tap checkout by name or tap, not by arbitrary path. For example, render into `Formula/code-oz.rb` inside `omerakben/homebrew-code-oz`, then run the current supported audit form for that tap before pushing. Re-run and capture the exact passing command in the handoff. +- **Effort estimate:** xs + +## Checks completed + +- `scripts/install.sh --version v0.20.0-alpha.0` on macOS arm64 downloaded from the tagged release, SHA-verified, installed to a clean temp install dir, and the installed binary printed `0.20.0-alpha.0`. +- `checksums.txt` from the GitHub release matched all four release tarballs with `shasum -a 256 -c checksums.txt`. +- `npm-wrapper/index.cjs` first invocation with a clean temp cache downloaded the current per-arch binary and printed `0.20.0-alpha.0`. +- `npm-wrapper/index.cjs` second invocation with `CODE_OZ_NPM_BASE_URL=file:///missing-after-cache` reused cache and printed `0.20.0-alpha.0`. +- `npm pack --dry-run --json` showed the package contains `LICENSE`, `README.md`, `npm-wrapper/index.cjs`, and `package.json`; unpacked size 13153 bytes. +- Targeted distribution tests passed: `51 pass`, `0 fail` across `tests/install-script.test.ts`, `tests/npm-wrapper.test.ts`, and `tests/homebrew-formula.test.ts`. +- `.github/workflows/release.yml` has `bun-version: 1.3.9`, an explicit `bun install --frozen-lockfile` before build, four build matrix targets, checksums assembly, `install.sh` upload, and all four tarball upload globs. +- `.github/workflows/test.yml` has `ubuntu-latest` and `macos-latest`, `bun-version: 1.3.9`, `bun install --frozen-lockfile`, typecheck, and `bun test`; no skipped jobs found by source inspection. + +## Gaps and unverified items + +- Real Linux install was not executed. `docker --version` succeeded, but `docker image ls` failed because the Docker daemon socket was unavailable. Synthetic Linux coverage exists in `tests/install-script.test.ts` and passed, including `linux-x64` network mode and SHA-tool fallbacks. +- `gh release view v0.20.0-alpha.0 --repo omerakben/code-oz --json tagName,assets` was attempted under the required clean `HOME` and failed with exit 4 because GitHub CLI auth was absent. I used unauthenticated GitHub API and direct release asset downloads as fallback evidence. +- `brew audit --strict --online` on the rendered formula remains unverified because current Homebrew rejected path-based audit. Ruby syntax and template structure tests passed, but a supported tap/name audit command still needs to be run. +- I did not run full GitHub Actions workflows locally; workflow findings are source inspection plus live release artifact evidence. + +## Commands run + +Common command prefix unless noted: `HOME=/private/tmp/code-oz-a3-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun` + +```text +command: mkdir -p /private/tmp/code-oz-a3-home /private/tmp/code-oz-a3-cache /private/tmp/code-oz-a3-npm /private/tmp/code-oz-a3-bun +exit: 0 + +command: git status --short --branch +exit: 0 +output: ## finalize/v0.20.1-first-run-polish...origin/main + +command: sed -n '1,240p' docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit: 0 + +command: sed -n '1,260p' CLAUDE.md +exit: 0 + +command: sed -n '1,260p' docs/design/CODEX_SYNTHESIS_W3A.md +exit: 0 + +command: sed -n '1,220p' README.md +exit: 0 + +command: sed -n '1,220p' docs/ABOUT.md +exit: 0 + +command: sed -n '260,520p' docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit: 0 + +command: rg -n "W3|v0\\.20|distribution|Homebrew|npm|install\\.sh|release\\.yml|test\\.yml|checksums|Scoop|Windows" docs/design/ROADMAP.md +exit: 0 + +command: rg --files -uu .github docs/handoffs/codex-finalize scripts npm-wrapper docs/homebrew +exit: 0 + +command: sed -n '1,260p' scripts/install.sh +exit: 0 + +command: sed -n '260,520p' scripts/install.sh +exit: 0 + +command: sed -n '1,300p' npm-wrapper/index.cjs +exit: 0 + +command: sed -n '1,240p' docs/homebrew/code-oz.rb.template +exit: 0 + +command: sed -n '1,320p' .github/workflows/release.yml +exit: 0 + +command: sed -n '1,260p' .github/workflows/test.yml +exit: 0 + +command: sed -n '1,280p' tests/install-script.test.ts +exit: 0 + +command: sed -n '280,640p' tests/install-script.test.ts +exit: 0 + +command: sed -n '1,320p' tests/npm-wrapper.test.ts +exit: 0 + +command: sed -n '1,260p' tests/homebrew-formula.test.ts +exit: 0 + +command: bun test tests/install-script.test.ts tests/npm-wrapper.test.ts tests/homebrew-formula.test.ts +exit: 0 +output: 51 pass, 0 fail + +command: bash -lc 'set -eu; ROOT=$(mktemp -d /private/tmp/code-oz-a3-corrupt-cache.XXXXXX); mkdir -p "$ROOT/home" "$ROOT/cache/0.20.0-alpha.0"; printf "#!/bin/sh\necho tampered-cache-ran \"$@\"\n" > "$ROOT/cache/0.20.0-alpha.0/code-oz"; chmod +x "$ROOT/cache/0.20.0-alpha.0/code-oz"; CODE_OZ_NPM_CACHE_DIR="$ROOT/cache" CODE_OZ_NPM_BASE_URL="file:///definitely-missing-release" HOME="$ROOT/home" node npm-wrapper/index.cjs --probe' +exit: 0 +output: tampered-cache-ran + +command: gh release view v0.20.0-alpha.0 --repo omerakben/code-oz --json tagName,assets +exit: 4 +output: gh auth login required + +command: curl -fsSL https://github.com/omerakben/code-oz/releases/download/v0.20.0-alpha.0/checksums.txt +exit: 0 + +command: HOME=/private/tmp/code-oz-a3-install-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-install-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun CODE_OZ_INSTALL_DIR=/private/tmp/code-oz-a3-install-bin sh scripts/install.sh --version v0.20.0-alpha.0 +exit: 0 +output: code-oz installed at /private/tmp/code-oz-a3-install-bin/code-oz (version 0.20.0-alpha.0) + +command: bash -lc 'set -eu; ROOT=$(mktemp -d /private/tmp/code-oz-a3-release-sha-ok.XXXXXX); cd "$ROOT"; curl -fsSLO https://github.com/omerakben/code-oz/releases/download/v0.20.0-alpha.0/checksums.txt; for asset in code-oz-v0.20.0-alpha.0-darwin-arm64.tar.gz code-oz-v0.20.0-alpha.0-darwin-x64.tar.gz code-oz-v0.20.0-alpha.0-linux-arm64.tar.gz code-oz-v0.20.0-alpha.0-linux-x64.tar.gz; do curl -fsSLO "https://github.com/omerakben/code-oz/releases/download/v0.20.0-alpha.0/$asset"; done; shasum -a 256 -c checksums.txt' +exit: 0 +output: all four tarballs OK + +command: HOME=/private/tmp/code-oz-a3-install-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-install-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun /private/tmp/code-oz-a3-install-bin/code-oz --version +exit: 0 +output: 0.20.0-alpha.0 + +command: command -v brew +exit: 0 +output: /opt/homebrew/bin/brew + +command: curl -fsSL https://api.github.com/repos/omerakben/code-oz/releases/tags/v0.20.0-alpha.0 +exit: 0 + +command: ruby -c docs/homebrew/code-oz.rb.template +exit: 0 +output: Syntax OK + +command: bash -lc 'set -eu; TMP=$(mktemp -d /private/tmp/code-oz-a3-homebrew.XXXXXX); sed -e "s/__VERSION__/0.20.0-alpha.0/g" -e "s/__SHA256_DARWIN_ARM64__/25f441e30a67b690db6e7b2fd6db5fd748e1738e6862e0c7f61f1fa59b8c6df6/" -e "s/__SHA256_DARWIN_X64__/dd6178dee576b9b1932fb1c7d584487b9416676625f49bd981639ed2f99b7cdf/" -e "s/__SHA256_LINUX_ARM64__/f97e2c99403cbf26010d282a0f6351eee38f5b6e5b3dfb1fc606ad00dc9fe1b1/" -e "s/__SHA256_LINUX_X64__/2ac24d187ea08d5ee17137a4e342a35caedbc009d68e1b21343554df892e6a46/" docs/homebrew/code-oz.rb.template > "$TMP/code-oz.rb"; HOMEBREW_CACHE=/private/tmp/code-oz-a3-brew-cache HOMEBREW_LOGS=/private/tmp/code-oz-a3-brew-logs brew audit --strict --online "$TMP/code-oz.rb"' +exit: 1 +output: brew audit path form disabled + +command: HOME=/private/tmp/code-oz-a3-npm-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-npm-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm NPM_CONFIG_USERCONFIG=/private/tmp/code-oz-a3-npm-home/.npmrc BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun CODE_OZ_NPM_CACHE_DIR=/private/tmp/code-oz-a3-wrapper-cache node npm-wrapper/index.cjs --version +exit: 0 +output: 0.20.0-alpha.0 + +command: npm pack --dry-run --json +exit: 0 + +command: HOME=/private/tmp/code-oz-a3-npm-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-npm-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm NPM_CONFIG_USERCONFIG=/private/tmp/code-oz-a3-npm-home/.npmrc BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun CODE_OZ_NPM_CACHE_DIR=/private/tmp/code-oz-a3-wrapper-cache CODE_OZ_NPM_BASE_URL=file:///missing-after-cache node npm-wrapper/index.cjs --version +exit: 0 +output: 0.20.0-alpha.0 + +command: HOME=/private/tmp/code-oz-a3-npm-home XDG_CACHE_HOME=/private/tmp/code-oz-a3-npm-home/.cache NPM_CONFIG_CACHE=/private/tmp/code-oz-a3-npm NPM_CONFIG_USERCONFIG=/private/tmp/code-oz-a3-npm-home/.npmrc BUN_INSTALL_CACHE_DIR=/private/tmp/code-oz-a3-bun bash -lc 'set -eu; printf "#!/bin/sh\necho cached-binary-tampered-live\n" > /private/tmp/code-oz-a3-wrapper-cache/0.20.0-alpha.0/code-oz; chmod +x /private/tmp/code-oz-a3-wrapper-cache/0.20.0-alpha.0/code-oz; CODE_OZ_NPM_CACHE_DIR=/private/tmp/code-oz-a3-wrapper-cache CODE_OZ_NPM_BASE_URL=file:///missing-after-tamper HOME=/private/tmp/code-oz-a3-npm-home node npm-wrapper/index.cjs --version' +exit: 0 +output: cached-binary-tampered-live + +command: docker --version +exit: 0 +output: Docker version 29.2.1, build a5c7197 + +command: docker image ls --format '{{.Repository}}:{{.Tag}}' +exit: 1 +output: failed to connect to the docker API at unix:///var/run/docker.sock +``` diff --git a/docs/handoffs/codex-finalize/A4-binaries.md b/docs/handoffs/codex-finalize/A4-binaries.md new file mode 100644 index 0000000..7beb856 --- /dev/null +++ b/docs/handoffs/codex-finalize/A4-binaries.md @@ -0,0 +1,234 @@ +# A4-binaries findings + +Sub-task: A4 +Operator: codex-subtask-4 +Started: 2026-05-13T22:04:00Z +Finished: 2026-05-13T22:19:59Z + +## Summary + +Four target binaries were produced locally after one sandboxed toolchain-download failure and one approved rerun: darwin-arm64, darwin-x64, linux-x64, and linux-arm64. macOS arm64 passed `--version`, `--help`, installer smoke, `init`, and `doctor tools`; macOS x64 ran under Rosetta with matching stdout but emitted Bun's AVX warning on stderr. Linux targets were synthetically checked only from this Darwin arm64 host. Findings: 1 block-ship, 2 fix-soon, 1 nit. + +## Findings + +### F4.1 - Release workflow uploads binaries without running the W3a smoke commands + +- **Severity:** block-ship +- **Where:** `.github/workflows/release.yml:56`, `.github/workflows/release.yml:113`, `docs/design/CODEX_SYNTHESIS_W3A.md:41` +- **Evidence:** + ```text + Static inspection: + .github/workflows/release.yml builds each matrix binary at lines 56-68, + stages a tarball at lines 70-122, uploads at lines 124-130, and releases at + lines 173-189. + + Command: + rg -n -- "--version|doctor|init|smoke|build:binaries|scripts/build-binaries" .github/workflows/release.yml .github/workflows/test.yml scripts/build-binaries.ts package.json + Exit: 0 + Output: + package.json:14: "build:binaries": "bun run scripts/build-binaries.ts", + package.json:15: "smoke": "bun run scripts/smoke-test.ts", + scripts/build-binaries.ts:53:code-oz --version + + W3a target matrix requires each asset to smoke: + darwin-arm64: code-oz --version + code-oz init + darwin-x64: same + linux-x64: same + linux-arm64: same + ``` +- **Why it matters for first-run UX:** The tag workflow can publish a tarball that was built and checksummed but never executed, so a wrong-version or non-starting binary can reach curl, npm, and Homebrew users. +- **Proposed fix:** Add a release-workflow smoke step before tarball upload. At minimum, run the built binary's `--version` and `init` on targets executable by the runner, then run synthetic `file`, executable-bit, manifest-version, and size/SHA checks for targets not executable on that runner. Prefer native runners or explicit emulation for linux-arm64 before treating it as fully covered. Keep the local `scripts/smoke-test.ts` command as a separate host smoke, but do not let it be the only binary execution gate for release assets. +- **Effort estimate:** m + +### F4.2 - Local multi-target build script emits one misleading `darwin` tarball instead of the four release asset names + +- **Severity:** fix-soon +- **Where:** `scripts/build-binaries.ts:443`, `scripts/build-binaries.ts:456`, `scripts/build-binaries.ts:625` +- **Evidence:** + ```text + Command: + bun run scripts/build-binaries.ts + Exit: 0 after approved rerun + Output: + code-oz binaries ready: 0.20.0-alpha.0 + - darwin-arm64 dist/handoff/darwin-arm64/code-oz ... 61645792 bytes + - darwin-x64 dist/handoff/darwin-x64/code-oz ... 66537712 bytes + - linux-x64 dist/handoff/linux-x64/code-oz ... 103723011 bytes + - linux-arm64 dist/handoff/linux-arm64/code-oz ... 101287771 bytes + manifest: dist/handoff/manifest.json + tarball: dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz + + Command: + tar -tzf dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz | sed -n '1,80p' + Exit: 0 + Output excerpt: + code-oz-v0.20.0-alpha.0-darwin/linux-arm64/ + code-oz-v0.20.0-alpha.0-darwin/darwin-x64/ + code-oz-v0.20.0-alpha.0-darwin/darwin-arm64/ + code-oz-v0.20.0-alpha.0-darwin/linux-x64/ + + Command: + ls -1 dist/code-oz-v0.20.0-alpha.0-*.tar.gz dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz 2>/dev/null + Exit: 0 + Output: + dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz + dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz + ``` +- **Why it matters for first-run UX:** The script name and output look like a Darwin release asset while containing Linux binaries too, which can cause a maintainer to upload or checksum the wrong local bundle when trying to reproduce the release contract. +- **Proposed fix:** Either make `scripts/build-binaries.ts` emit the same four tarballs as `.github/workflows/release.yml` (`code-oz-v-darwin-arm64.tar.gz`, `darwin-x64`, `linux-x64`, `linux-arm64`) or rename the aggregate output to an explicit non-release name such as `code-oz-v-all-platforms.tar.gz`. Update `scripts/smoke-test.ts` to validate whichever contract is chosen. +- **Effort estimate:** s + +### F4.3 - The literal A4 `doctor` smoke fails even though `doctor tools` passes + +- **Severity:** fix-soon +- **Where:** `docs/handoffs/2026-05-13-codex-finalize-distribution.md:153`, binary command `code-oz doctor` +- **Evidence:** + ```text + Command: + env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz init + Exit: 0 + Output: + code-oz: initialized greenfield project at /private/tmp/code-oz-a4-project-RB5wow/.code-oz + + Command: + env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz doctor + Exit: 1 + Output excerpt: + Usage: code-oz doctor [options] + + Command: + env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz doctor tools + Exit: 0 + Output: + TOOL AVAILABLE VERSION + rg yes ripgrep 15.1.0 + + All required tools available. + ``` +- **Why it matters for first-run UX:** The playbook asks A4 to smoke `init` and `doctor`; a user or release worker following that literally hits an exit-1 usage page after a successful init. +- **Proposed fix:** Choose one contract and make docs plus CLI match it. Either make bare `code-oz doctor` run a default read-only summary, or update the first-run playbook and README examples to use a specific subcommand such as `doctor tools` or `doctor providers`. +- **Effort estimate:** s + +### F4.4 - darwin-x64 local smoke under Rosetta has matching stdout but emits a Bun AVX warning on stderr + +- **Severity:** nit +- **Where:** `dist/handoff/darwin-x64/code-oz` +- **Evidence:** + ```text + Command: + dist/handoff/darwin-x64/code-oz --version + Exit: 0 + Output: + warn: CPU lacks AVX support, strange crashes may occur. Reinstall Bun or use *-baseline build: + https://github.com/oven-sh/bun/releases/download/bun-v1.3.9/bun-darwin-x64-baseline.zip + 0.20.0-alpha.0 + + Command: + diff -u /tmp/a4-arm64-help.txt /tmp/a4-x64-help.txt + Exit: 0 + + Command: + sed -n '1,4p' /tmp/a4-x64-help.stderr + Exit: 0 + Output: + warn: CPU lacks AVX support, strange crashes may occur. Reinstall Bun or use *-baseline build: + https://github.com/oven-sh/bun/releases/download/bun-v1.3.9/bun-darwin-x64-baseline.zip + ``` +- **Why it matters for first-run UX:** Help and version stdout are identical, but a user forced onto the x64 binary can see a runtime warning before every CLI response. +- **Proposed fix:** Treat darwin-x64 as synthetic unless it is run on a real Intel macOS host, or add an explicit CI/local note that Rosetta AVX warnings are expected and not a help-parity failure. If older Intel Macs without AVX are in scope, evaluate whether a Bun baseline target is needed before release. +- **Effort estimate:** xs + +## Completed checks + +```text +pwd && git status --short --branch +Exit: 0 +Output: /Users/ozzy-mac/Projects/code-oz; ## finalize/v0.20.1-first-run-polish...origin/main + +bun --version +Exit: 0 +Output: 1.3.9 + +uname -s && uname -m +Exit: 0 +Output: Darwin; arm64 + +bun run scripts/build-binaries.ts +Exit: 2 in sandbox +Output excerpt: TOOLCHAIN_FAIL: linux-x64 build failed with exit code 1; Failed to extract executable for 'bun-linux-x64-v1.3.9'. The download may be incomplete. + +bun run scripts/build-binaries.ts +Exit: 0 after approved rerun outside sandbox +Output: produced darwin-arm64, darwin-x64, linux-x64, linux-arm64 rows in dist/handoff/manifest.json + +jq -r '.version as $v | .targets[] | [.os,.arch,.bunTarget,.binaryRelativePath,.version,.sizeBytes] | @tsv' dist/handoff/manifest.json +Exit: 0 +Output: +darwin arm64 bun-darwin-arm64 darwin-arm64/code-oz 0.20.0-alpha.0 61645792 +darwin x64 bun-darwin-x64 darwin-x64/code-oz 0.20.0-alpha.0 66537712 +linux x64 bun-linux-x64 linux-x64/code-oz 0.20.0-alpha.0 103723011 +linux arm64 bun-linux-arm64 linux-arm64/code-oz 0.20.0-alpha.0 101287771 + +file dist/handoff/darwin-arm64/code-oz dist/handoff/darwin-x64/code-oz dist/handoff/linux-x64/code-oz dist/handoff/linux-arm64/code-oz +Exit: 0 +Output: Mach-O arm64, Mach-O x86_64, ELF x86-64, ELF ARM aarch64 + +du -h dist/handoff/darwin-arm64/code-oz dist/handoff/darwin-x64/code-oz dist/handoff/linux-x64/code-oz dist/handoff/linux-arm64/code-oz dist/code-oz-v0.20.0-alpha.0-darwin.tar.gz +Exit: 0 +Output: 59M, 63M, 99M, 97M, 128M + +dist/handoff/darwin-arm64/code-oz --version +Exit: 0 +Output: 0.20.0-alpha.0 + +dist/handoff/darwin-arm64/code-oz --help +Exit: 0 +Output: help printed cleanly + +dist/handoff/darwin-x64/code-oz --version +Exit: 0 +Output: 0.20.0-alpha.0 plus Bun AVX warning on stderr under Rosetta + +dist/handoff/darwin-x64/code-oz --help +Exit: 0 +Output: help stdout matches arm64; Bun AVX warning on stderr under Rosetta + +diff -u /tmp/a4-arm64-version.txt /tmp/a4-x64-version.txt +Exit: 0 + +diff -u /tmp/a4-arm64-help.txt /tmp/a4-x64-help.txt +Exit: 0 + +bun run scripts/smoke-test.ts +Exit: 0 +Output: layout ok, install ok, version ok, help ok, init ok for host target darwin-arm64; tarball validated + +env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz init +Exit: 0 + +env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz doctor +Exit: 1 + +env HOME=/tmp/code-oz-a4-home-jE1U3w /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-arm64/code-oz doctor tools +Exit: 0 + +env HOME=/tmp/code-oz-a4-x64-home-319DcC /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-x64/code-oz init +Exit: 0 + +env HOME=/tmp/code-oz-a4-x64-home-319DcC /Users/ozzy-mac/Projects/code-oz/dist/handoff/darwin-x64/code-oz doctor tools +Exit: 0 + +dist/handoff/linux-x64/code-oz --version +Exit: 126 +Output: exec format error on Darwin host + +dist/handoff/linux-arm64/code-oz --version +Exit: 126 +Output: exec format error on Darwin host +``` + +## Gaps + +- Linux `--version`, `--help`, `init`, and `doctor` were not runnable on this Darwin arm64 host. Synthetic checks covered manifest rows, file type, executable presence, size, and SHA via `scripts/smoke-test.ts`. +- darwin-x64 was runnable only through Rosetta on Apple Silicon, not on a native Intel Mac. +- No actual GitHub release artifact was generated in this audit; asset naming was checked statically in `.github/workflows/release.yml` and against local `scripts/build-binaries.ts` output. diff --git a/docs/handoffs/codex-finalize/A5-docs.md b/docs/handoffs/codex-finalize/A5-docs.md new file mode 100644 index 0000000..44cbad1 --- /dev/null +++ b/docs/handoffs/codex-finalize/A5-docs.md @@ -0,0 +1,195 @@ +# A5-docs findings + +Sub-task: A5 +Operator: codex-subtask-5 +Started: 2026-05-13T22:10:00Z +Finished: 2026-05-13T22:19:24Z + +## Summary + +Found 4 documentation findings: 1 block-ship and 3 fix-soon. Biggest first-run risk: the GUI README tells users to clone a GitHub repo that returns 404, so the documented GUI quick start cannot begin. Link/path checks were light: local markdown links resolved, README release asset resolved, npm package version resolved, Homebrew tap resolved, and banned-vocabulary scan found no hits in the three requested docs. Screenshot files exist, but live GUI screenshot drift was not verified in this 10-minute pass. + +## Findings + +### F5.1 - GUI quick start clones a missing repository + +- **Severity:** block-ship +- **Where:** code-oz-gui/README.md:79 +- **Evidence:** + ```text + code-oz-gui/README.md:79:git clone https://github.com/omerakben/code-oz-gui.git + + Command: + curl -fsSI -L https://github.com/omerakben/code-oz-gui + + Exit: 56 + Output: + curl: (56) The requested URL returned error: 404 + HTTP/2 404 + ``` +- **Why it matters for first-run UX:** A friend following the GUI README cannot clone the app, so `bun install && bun run dev` is unreachable from the documented path. +- **Proposed fix:** Update the quick start to clone the monorepo and enter the GUI subdirectory, for example `git clone https://github.com/omerakben/code-oz.git`, `cd code-oz/code-oz-gui`, `bun install`, `bun run dev`. If a split `code-oz-gui` repo is intended, create it before release and make the link resolve. +- **Effort estimate:** xs + +### F5.2 - README presents brownfield AUDIT as usable even though M17 is still the gap + +- **Severity:** fix-soon +- **Where:** README.md:38, docs/ABOUT.md:66, src/commands/run.ts:313-317 +- **Evidence:** + ```text + README.md:38:Phases (brownfield): `AUDIT -> PLAN -> BUILD -> VERIFY -> REVIEW -> SHIP`. Auto-detected on boot. + + docs/ABOUT.md:66:The next milestone is M17 (AUDIT runtime), shipping to v0.21 ... M17 closes the brownfield workflow gap so `code-oz` can audit existing codebases before proposing fixes. + + src/commands/run.ts:313-317: + M17's C2 will add the AUDIT dispatch branch ... until then the fresh-run path still calls runDefine below ... the real AUDIT dispatch gap. + + Command: + test -f src/phases/audit.ts + + Exit: 1 + ``` +- **Why it matters for first-run UX:** Users with an existing repo will expect the documented brownfield path to run, but the current codebase says AUDIT dispatch is not wired yet. +- **Proposed fix:** Reword README status around brownfield as "detected and represented in state, AUDIT runtime lands in M17/v0.21" and point users to the greenfield or fake-provider demo for v0.20.1. Keep docs/ABOUT as the deeper milestone note, but make the README first-run path honest. +- **Effort estimate:** s + +### F5.3 - Provider/auth setup is split and contradictory + +- **Severity:** fix-soon +- **Where:** README.md:36, code-oz-gui/README.md:83-87, docs/ABOUT.md:16, docs/references/provider-contract.md:14 +- **Evidence:** + ```text + README.md:36 says no API keys are required for supported families and describes Claude/Codex/Gemini CLI subscription auth. + + code-oz-gui/README.md:83-87 tells users to set provider keys for live modes: + GEMINI_API_KEY for the GUI helper, and ANTHROPIC_API_KEY / OPENAI_API_KEY / XAI_API_KEY for real providers. + + docs/ABOUT.md:16 documents XaiProvider reading XAI_API_KEY. + + docs/references/provider-contract.md:14 says v0.1 supports two auth shapes: + Claude/Codex subprocess delegation and xAI direct API-key transmission. + ``` +- **Why it matters for first-run UX:** The north star says the user sets one provider key, but the docs do not say which key is needed for CLI first-run, GUI helper, fake mode, xAI live mode, or Claude/Codex subscription mode. +- **Proposed fix:** Add one provider setup table and link to it from README, docs/ABOUT, and code-oz-gui/README. The table should distinguish: fake mode needs no key; Claude and Codex use `claude login` / `codex login status`; xAI uses `XAI_API_KEY`; GUI helper uses `GEMINI_API_KEY`; Anthropic/OpenAI env keys should not be documented for v0.20.1 unless direct HTTP adapters actually consume them. +- **Effort estimate:** s + +### F5.4 - Release/changelog docs still read like tag-prep and disagree on test count + +- **Severity:** fix-soon +- **Where:** CLAUDE.md:9, README.md:64, docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md:4,52,63,73, docs/handoffs/2026-05-13-codex-finalize-distribution.md:165 +- **Evidence:** + ```text + gh release view v0.20.0-alpha.0 --json tagName,isDraft,isPrerelease,publishedAt,url + Exit: 0 + {"isDraft":false,"isPrerelease":false,"publishedAt":"2026-05-12T03:24:58Z","tagName":"v0.20.0-alpha.0","url":"https://github.com/omerakben/code-oz/releases/tag/v0.20.0-alpha.0"} + + CLAUDE.md:9 says v0.19 shipped and v0.20 is preparing, with 3362 offline tests. + README.md:64 says v0.20.0-alpha.0 is the first release and 3366 tests pass. + docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md:4 says status: tag-prep. + docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md:52 says bun test runs 3362 offline tests. + docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md:63 and :73 still contain push/publish prerequisite sections. + + Command: + rg --files | rg 'CHANGELOG|RELEASE_NOTES|release_notes|Release|release' + + Exit: 0 + Output: + docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md + ``` +- **Why it matters for first-run UX:** The repo's orientation docs disagree on whether v0.20 is released and what the current verification baseline is, so release-readiness workers and users get mixed signals. +- **Proposed fix:** Update the v0.20 release notes from tag-prep to published state, make the test count match the verified branch baseline, and move old push/publish instructions into a historical/release-procedure note. Add a `v0.20.1-alpha.0` release-note stub for this first-run polish PR as requested by the playbook. +- **Effort estimate:** s + +## Checks with no findings + +- Local markdown and HTML image references in README.md, docs/ABOUT.md, code-oz-gui/README.md, and the v0.20 release notes resolved: `missing_local_links=0`. +- README release install asset resolved with HTTP 200 after redirect. +- `npm --cache /private/tmp/code-oz-npm-cache view @tuel/code-oz version` returned `0.20.0-alpha.0`. +- Homebrew tap URL returned HTTP 200. +- Banned-vocabulary scan across README.md, docs/ABOUT.md, and code-oz-gui/README.md returned no matches. +- GUI screenshot paths exist and are PNGs under `code-oz-gui/docs/screenshots/`. + +## Gaps / not verified + +- Did not execute the full README install commands end-to-end because this was the A5 lightweight pass and install would write outside the repo. +- Did not run `brew tap` or `brew install`; only checked the tap URL. +- Did not run the GUI or compare screenshots against a fresh render. Screenshot path/metadata only. +- `curl -fsSI -L https://www.npmjs.com/package/@tuel/code-oz` returned a Cloudflare 403 challenge, but `npm view` with an isolated cache verified the package version. +- Did not run a full HTTP link crawler. Checked the install asset, npm package via registry, Homebrew tap, GitHub Actions workflow links, and the GUI clone target. + +## Commands run + +- `rg -n "finalize|v0\\.20|0\\.20|first-run|changelog|provider|key|screenshot|banned|vocabulary|playbook" /Users/ozzy-mac/.codex/memories/MEMORY.md` -> exit 0 +- `git status --short --branch` -> exit 0 +- `pwd` -> exit 0 +- `rg --files -g 'CHANGELOG*' -g '*RELEASE*' -g '*release*' -g 'docs/handoffs/**' -g 'CLAUDE.md' -g 'README.md' -g 'docs/ABOUT.md' -g 'code-oz-gui/README.md'` -> exit 0 +- `wc -l docs/handoffs/2026-05-13-codex-finalize-distribution.md CLAUDE.md README.md docs/ABOUT.md code-oz-gui/README.md CHANGELOG.md` -> exit 1 +- `rg -n "" docs/handoffs/2026-05-13-codex-finalize-distribution.md CLAUDE.md README.md docs/ABOUT.md code-oz-gui/README.md CHANGELOG.md` -> exit 2 +- `sed -n '1,220p' docs/handoffs/2026-05-13-codex-finalize-distribution.md` -> exit 0 +- `sed -n '221,420p' docs/handoffs/2026-05-13-codex-finalize-distribution.md` -> exit 0 +- `sed -n '1,180p' CLAUDE.md` -> exit 0 +- `sed -n '1,180p' README.md` -> exit 0 +- `sed -n '1,180p' docs/ABOUT.md` -> exit 0 +- `sed -n '1,220p' code-oz-gui/README.md` -> exit 0 +- `sed -n '1,220p' docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md` -> exit 0 +- `find docs/handoffs/codex-finalize -maxdepth 1 -type f -print` -> exit 0 +- `rg -n "" README.md docs/ABOUT.md code-oz-gui/README.md docs docs/reference src package.json code-oz-gui/.env.example` -> exit 2 +- `rg -n "" README.md docs/ABOUT.md code-oz-gui/README.md` -> exit 1 +- `rg -n "\\[[^\\]]+\\]\\([^)]*\\)| exit 0 +- `rg --files docs/screenshots code-oz-gui/docs/screenshots` -> exit 2 +- `rg --files docs src tests code-oz-gui | rg 'provider|references|reference|env|example|screenshot|CLAUDE_DESIGN_BRIEF|ROADMAP|demo/01-todo-cli/README|homebrew/README'` -> exit 0 +- `sed -n '1,220p' docs/references/provider-contract.md` -> exit 0 +- `cat code-oz-gui/.env.example` -> exit 0 +- `cat package.json` -> exit 0 +- `sed -n '1,240p' src/providers/registry.ts` -> exit 0 +- `sed -n '1,220p' src/providers/gemini.ts` -> exit 0 +- `sed -n '1,220p' src/providers/xai.ts` -> exit 0 +- `sed -n '1,180p' src/commands/doctor.ts` -> exit 0 +- `sed -n '180,380p' src/commands/doctor.ts` -> exit 0 +- `sed -n '1,260p' src/cli/bootstrap.ts` -> exit 0 +- `sed -n '1,220p' src/providers/claude.ts` -> exit 0 +- `sed -n '1,220p' src/providers/codex.ts` -> exit 0 +- `sed -n '1,220p' scripts/demo/01-todo-cli/run-demo.ts` -> exit 0 +- `sed -n '1,220p' docs/demo/01-todo-cli/README.md` -> exit 0 +- `bun run dev --help` -> exit 0 +- `bun run dev doctor --help` -> exit 0 +- `rg -n "brownfield|AUDIT|Auto-detected|audit_completed|currentPhase|phase: 'audit'|phase.*audit|M17" README.md docs/ABOUT.md src tests code-oz-gui/README.md` -> exit 0 +- `sed -n '1,260p' src/commands/init.ts` -> exit 0 +- `sed -n '1,140p' src/state/schemas.ts` -> exit 0 +- `rg -n "runAskMe|currentPhase|audit|define" src/commands/run.ts src/phases/audit.ts src/phases/ask-me.ts src/phases/define.ts` -> exit 2 +- `node -e 'const fs=require("fs"),path=require("path"); const files=["README.md","docs/ABOUT.md","code-oz-gui/README.md","docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md"]; let bad=0; for (const f of files){const text=fs.readFileSync(f,"utf8"); const links=[...text.matchAll(/\\[[^\\]]+\\]\\(([^)\\s]+)(?:\\s+"[^"]*")?\\)/g)].map(m=>m[1]).concat([...text.matchAll(/]*src="([^"]+)"/g)].map(m=>m[1])); for (const raw of links){ if (/^(https?:|mailto:|#)/.test(raw)) continue; const clean=raw.split("#")[0]; if (!clean) continue; const p=path.resolve(path.dirname(f),clean); if (!fs.existsSync(p)){ console.log(`${f}: missing local link ${raw} -> ${path.relative(process.cwd(),p)}`); bad++; } } } console.log(`missing_local_links=${bad}`); process.exit(bad?1:0);'` -> exit 0 +- `test -f docs/planning/1000_STAR_PLAN.md` -> exit 0 +- `test -f CHANGELOG.md` -> exit 1 +- `test -f src/phases/audit.ts` -> exit 1 +- `curl -fsSI -L https://github.com/omerakben/code-oz/releases/download/v0.20.0-alpha.0/install.sh` -> exit 0 +- `npm view @tuel/code-oz version` -> exit 1 +- `curl -fsSI -L https://github.com/omerakben/homebrew-code-oz` -> exit 0 +- `curl -fsSI -L https://github.com/omerakben/code-oz-gui` -> exit 56 +- `npm --cache /private/tmp/code-oz-npm-cache view @tuel/code-oz version` -> exit 0 +- `curl -fsSI -L https://www.npmjs.com/package/@tuel/code-oz` -> exit 56 +- `curl -fsSI -L https://github.com/omerakben/code-oz/actions/workflows/test.yml` -> exit 0 +- `curl -fsSI -L https://github.com/omerakben/code-oz/actions/workflows/release.yml` -> exit 0 +- `cat code-oz-gui/package.json` -> exit 0 +- `test -f code-oz-gui/bun.lock` -> exit 0 +- `rg -n "code-oz-gui\\.git|bun dev|bun run dev|git clone|cd code-oz-gui|README|Quick start|install" code-oz-gui/README.md README.md docs/ABOUT.md` -> exit 0 +- `bun --version` -> exit 0 +- `ls -l code-oz-gui/docs/screenshots` -> exit 0 +- `file code-oz-gui/docs/screenshots/hero.png code-oz-gui/docs/screenshots/decisions-task.png code-oz-gui/docs/screenshots/events-errors.png code-oz-gui/docs/screenshots/workspace-form.png` -> exit 0 +- `rg -n "screenshots|hero\\.png|decisions-task|events-errors|workspace-form|capture-screenshots|67d77e4" README.md docs/ABOUT.md code-oz-gui/README.md code-oz-gui/scripts/capture-screenshots.ts docs/handoffs/2026-05-13-codex-finalize-distribution.md` -> exit 0 +- `git log --oneline -- code-oz-gui/docs/screenshots code-oz-gui/README.md | head -20` -> exit 0 +- `rg --files | rg 'CHANGELOG|RELEASE_NOTES|release_notes|Release|release'` -> exit 0 +- `rg -n "0\\.20\\.1|v0\\.20\\.1|0\\.20\\.0-alpha\\.0|v0\\.20\\.0-alpha\\.0|CHANGELOG" README.md docs/ABOUT.md code-oz-gui/README.md docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md docs/handoffs/2026-05-13-codex-finalize-distribution.md` -> exit 0 +- `rg -n "No API keys required|provider keys|GEMINI_API_KEY|ANTHROPIC_API_KEY|OPENAI_API_KEY|XAI_API_KEY|Sync policy|subscription-first|direct API-key|API-key transmission" README.md docs/ABOUT.md code-oz-gui/README.md docs/references/provider-contract.md code-oz-gui/.env.example` -> exit 0 +- `rg -n "git clone https://github.com/omerakben/code-oz-gui.git|cd code-oz-gui|bun install|bun dev|code-oz-gui" code-oz-gui/README.md` -> exit 0 +- `rg -n "Phases \\(brownfield\\)|Auto-detected|M17 closes the brownfield|AUDIT dispatch gap|currentPhase === 'audit'|no v0\\.1 default personas|src/phases/audit" README.md docs/ABOUT.md src/commands/run.ts tests/agents-defaults.test.ts tests/e2e/cli-fresh-profile-propagation.test.ts` -> exit 0 +- `rg -n "0\\.20\\.1|v0\\.20\\.1|RELEASE_NOTES|CHANGELOG|0\\.20\\.0-alpha\\.0" docs/handoffs/2026-05-13-codex-finalize-distribution.md docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md README.md docs/ABOUT.md` -> exit 0 +- `awk 'NR>=306&&NR<=318{print NR ":" $0}' src/commands/run.ts` -> exit 0 +- `awk 'NR>=45&&NR<=56{print NR ":" $0}' tests/agents-defaults.test.ts` -> exit 0 +- `awk 'NR>=8&&NR<=16{print NR ":" $0}' tests/e2e/cli-fresh-profile-propagation.test.ts` -> exit 0 +- `find src/phases -maxdepth 1 -type f -print` -> exit 0 +- `rg -n "OpenCode|Roo Code|Gemini|xAI|favorite coding agents|ProviderId|PROVIDER_IDS" README.md docs/ABOUT.md src/providers/types.ts src/providers/gemini.ts src/cli/bootstrap.ts` -> exit 0 +- `sed -n '1,80p' src/providers/types.ts` -> exit 0 +- `rg -n "v0\\.19\\.0-alpha\\.0 shipped|preparing v0\\.20\\.0-alpha\\.0|3362|3366|status: tag-prep|Push prerequisites|Publish sequence|first release|tag-prep|release:" CLAUDE.md README.md docs/design/RELEASE_NOTES_v0.20.0-alpha.0.md docs/handoffs/2026-05-13-codex-finalize-distribution.md` -> exit 0 +- `gh release view v0.20.0-alpha.0 --json tagName,isDraft,isPrerelease,publishedAt,url` -> exit 0 +- `git tag -l 'v0.20.0-alpha.0'` -> exit 0 +- `date -u +%Y-%m-%dT%H:%M:%SZ` -> exit 0 diff --git a/docs/handoffs/codex-finalize/A6-errors.md b/docs/handoffs/codex-finalize/A6-errors.md new file mode 100644 index 0000000..edc78a9 --- /dev/null +++ b/docs/handoffs/codex-finalize/A6-errors.md @@ -0,0 +1,284 @@ +# A6-errors findings + +Sub-task: A6 +Operator: codex-subtask-6 +Started: 2026-05-13T22:06:00Z +Finished: 2026-05-13T22:18:28Z + +## Summary + +Static audit found five findings: one block-ship, three fix-soon, and one nit. The biggest risk is that BUILD can write `NEEDS_INTERVENTION.json` with an empty `actionableSuggestions` array, and the gate validator accepts it. That violates the first-run recovery contract because the CLI formatter then has no concrete recovery step to show. I also found a playbook/schema drift around the required `event_pointer`, missing production PAUSE/STOP sequencing, distribution fail-closed messages without recovery hints, and a few top-level stderr exits that still rely on raw messages instead of a consistent hint. + +## Findings + +### F6.1 - BUILD can write NEEDS_INTERVENTION with no actionable recovery step + +- **Severity:** block-ship +- **Where:** `src/phases/build.ts:913`, `src/phases/build.ts:931`, `src/phases/build.ts:940`, `src/state/gates.ts:482`, `src/state/gates.ts:489` +- **Evidence:** + ```text + src/phases/build.ts:913-920 defines recordIntervention without any suggestion input. + src/phases/build.ts:931-941 writes NEEDS_INTERVENTION.json with actionableSuggestions: []. + src/state/gates.ts:482-490 checks Array.isArray and every string is non-empty, but does not check length > 0. + src/providers/errors.ts:57-61 rejects ProviderError issues with zero suggestions, so this is inconsistent with the provider error contract. + tests/build-phase.test.ts:461-466 only asserts the file exists, not that it contains suggestions. + ``` +- **Why it matters for first-run UX:** A user who hits a BUILD preflight, drift, persona, or patch failure can get a durable intervention file with no specific next action. +- **Proposed fix:** Make `validateNeedsIntervention` reject empty `actionableSuggestions`, then change BUILD's `recordIntervention` to accept suggestions or use a code-to-suggestions mapper like VERIFY/REVIEW. Failing-test sketch: first add a test that `writeNeedsInterventionGate` rejects `actionableSuggestions: []`, and a BUILD failure test that reads `NEEDS_INTERVENTION.json` and asserts `actionableSuggestions.length > 0` plus at least one concrete command or artifact path. +- **Effort estimate:** s + +### F6.2 - Playbook-required event_pointer is absent from the gate schema and every writer + +- **Severity:** fix-soon +- **Where:** `docs/handoffs/2026-05-13-codex-finalize-distribution.md:174`, `src/state/schemas.ts:1582`, `src/state/schemas.ts:1590`, `docs/references/file-based-gates.md:60`, `docs/references/file-based-gates.md:75` +- **Evidence:** + ```text + Playbook A6 requires every NEEDS_INTERVENTION writer to include phase, reason, suggestion, event_pointer. + NeedsInterventionGate only has version, runId, phase, agent, code, rule, detail?, actionableSuggestions, createdAt. + rg -n "event_pointer|eventPointer" found no implementation fields in src. + The current reference schema also omits event_pointer. + ``` +- **Why it matters for first-run UX:** When a run halts, the user has a phase/code but no exact event-log pointer to the triggering `agent_invoked`, `build_failed`, `verify_failed`, or provider failure row. +- **Proposed fix:** Decide the field name and shape once, then update the schema, validator, writers, formatter, and tests. A minimal shape could be `eventPointer: { file: "events.jsonl", line?: number, type?: string, code?: string }`, or a string form such as `events.jsonl:`. Failing-test sketch: create a provider failure and assert `NEEDS_INTERVENTION.json.eventPointer` points at the adjacent `intervention` event or the prior causal event. +- **Effort estimate:** m + +### F6.3 - PAUSE/STOP have schema writers but no production event-order path to verify + +- **Severity:** fix-soon +- **Where:** `src/state/gates.ts:296`, `src/state/gates.ts:304`, `tests/state-gates.test.ts:370`, `docs/references/file-based-gates.md:81`, `docs/references/file-based-gates.md:98`, `docs/references/file-based-gates.md:160` +- **Evidence:** + ```text + rg found writePauseGate/writeStopGate only in src/state/gates.ts and tests/state-gates.test.ts. + No production caller was found under src. + No SIGINT/SIGTERM handler was found under src. + docs/references/file-based-gates.md lists run_ended outcome "stopped" | "paused", but no current production helper sequences PAUSE.json/STOP.json with run_ended. + ``` +- **Why it matters for first-run UX:** Ctrl-C or user-requested stop behavior cannot be audited for the required gate/event order because the orchestrator path is not present. +- **Proposed fix:** Add an orchestrator-owned pause/stop helper in `src/state/run.ts` or the CLI boundary that writes `PAUSE.json`/`STOP.json`, appends the corresponding `run_ended` event, and rebuilds current state under one lock. Add tests that assert exact order for pause and stop. If stop/pause are intentionally out of scope for this release, document that in the A6 synthesis so A1 does not assume Ctrl-C durability exists. +- **Effort estimate:** m + +### F6.4 - Distribution fail-closed errors often omit recovery hints + +- **Severity:** fix-soon +- **Where:** `scripts/install.sh:3`, `scripts/install.sh:219`, `scripts/install.sh:231`, `scripts/install.sh:260`, `scripts/install.sh:265`, `npm-wrapper/index.cjs:27`, `npm-wrapper/index.cjs:139`, `npm-wrapper/index.cjs:150`, `npm-wrapper/index.cjs:154`, `npm-wrapper/index.cjs:160` +- **Evidence:** + ```text + scripts/install.sh uses fail() to print only "install.sh: " and exit 1. + Some failures have hints, for example no SHA tool or no downloader, but checksum mismatch, missing manifest row, missing binary, and download failures do not include a one-line recovery hint. + npm-wrapper/index.cjs die() prints only "code-oz launcher: " and exits 1. + npm wrapper download, checksum, and extracted-binary failures throw raw messages that are passed directly to die(). + ``` +- **Why it matters for first-run UX:** A friend installing from npm or curl can hit a network, tag, cache, or checksum failure and see what failed without seeing what to do next. +- **Proposed fix:** Give `fail`/`die` an optional hint argument and add hints for download, checksum, missing checksum entry, missing binary, unsupported platform, and cache corruption paths. Example hints: check the release tag, retry with a stable network, remove `~/.cache/code-oz//`, or set `CODE_OZ_NPM_BASE_URL` only for local testing. Failing-test sketch: tamper `checksums.txt` and assert stderr contains both "checksum mismatch" and a "try" or "remove cache/retry" line. +- **Effort estimate:** s + +### F6.5 - Top-level CLI stderr fallback hides stacks but does not add a recovery hint + +- **Severity:** nit +- **Where:** `src/cli.ts:79`, `src/cli.ts:81`, `src/cli.ts:82`, `src/commands/approve.ts:971`, `src/commands/approve.ts:974`, `src/commands/run.ts:270` +- **Evidence:** + ```text + src/cli.ts catches any thrown error, prints only `code-oz: ${msg}`, and exits 1. + approveCommand converts GateLoadError to a bare Error message, then the global catch prints it without command-specific recovery guidance. + code-oz run with no initial input prints "code-oz run: no initial user input provided." and exits 2 without suggesting --request, --request-file, or running from a TTY. + Static search found no `.stack` printing, so raw stack traces appear hidden in the audited paths. + ``` +- **Why it matters for first-run UX:** Unknown command and many phase-specific paths have hints, but the global fallback can still produce a dead-end message for parse, gate, and input failures. +- **Proposed fix:** Add a small fatal-error formatter that accepts `command`, `message`, `hint`, and `exitCode`, and use it for global catch and simple command exits. Keep stack traces hidden unless a future debug flag explicitly requests them. Failing-test sketch: spawn `code-oz run` without input in non-TTY mode and assert stderr includes a concrete next action such as `code-oz run --request "..."`. +- **Effort estimate:** s + +## Unverified gaps + +- I did not execute the CLI, installers, npm wrapper, or provider SDKs. This is a static source audit within the requested time box. +- I enumerated direct `writeNeedsInterventionGate` call sites with `rg` and inspected those snippets, but I did not read every line of every large phase file. +- Raw SDK stack trace behavior was checked by static search for `.stack` and error formatting. I did not force live Claude/Codex/Gemini/xAI failures. +- PAUSE/STOP event order is unverified because no production writer path was found. + +## Commands run + +All commands below were run from `/Users/ozzy-mac/Projects/code-oz` unless an absolute memory or skill path is shown. + +```text +rg -n "code-oz|finalize|v0\\.20\\.1|NEEDS_INTERVENTION|first-run" /Users/ozzy-mac/.codex/memories/MEMORY.md +exit 0 + +sed -n '1,220p' /Users/ozzy-mac/.agents/skills/code-review/SKILL.md +exit 0 + +git status --short --branch +exit 0 + +sed -n '1,260p' CLAUDE.md +exit 0 + +sed -n '1,240p' docs/design/ROADMAP.md +exit 0 + +sed -n '1,260p' docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit 0 + +rg -n "Finding template|severity|block-ship|fix-soon|A6|playbook" docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit 0 + +sed -n '250,380p' docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit 0 + +rg --files src/phases src/commands +exit 0 + +rg -n "NEEDS_INTERVENTION|writeNeedsIntervention|needs_intervention|writePauseGate|writeStopGate|PAUSE|STOP|console\\.error|process\\.exit|exitCode|throw new Error|stack|provider_failed|provider" src/state src/phases src/providers src/commands src/cli.ts scripts/install.sh npm-wrapper/index.cjs +exit 0 + +rg -n "fail|error|unsupported|checksum|sha|curl|download|exit 1|die\\(|fatal|recovery|hint|try|install" scripts/install.sh npm-wrapper/index.cjs +exit 0 + +rg -n "writeNeedsInterventionGate\\(|writePauseGate\\(|writeStopGate\\(|writeControlGate\\(" src scripts npm-wrapper tests docs +exit 0 + +nl -ba src/state/gates.ts | sed -n '1,360p' +exit 0 + +nl -ba src/state/run.ts | sed -n '1,360p' +exit 0 + +nl -ba src/providers/invoke.ts | sed -n '1,380p' +exit 0 + +nl -ba src/state/gates.ts | sed -n '312,620p' +exit 0 + +rg -n "interface NeedsInterventionGate|type NeedsInterventionGate|event_pointer|eventPointer|actionableSuggestions|PauseGate|StopGate|control" src/state/schemas.ts src/state/gates.ts docs/contracts docs/references +exit 0 + +nl -ba src/state/schemas.ts | sed -n '1,180p' +exit 0 + +nl -ba tests/state-gates.test.ts | sed -n '340,420p' +exit 0 + +nl -ba src/phases/define.ts | sed -n '90,160p' +exit 0 + +nl -ba src/phases/plan.ts | sed -n '110,160p'; nl -ba src/phases/plan.ts | sed -n '540,590p' +exit 0 + +nl -ba src/phases/build.ts | sed -n '900,970p' +exit 0 + +nl -ba src/phases/verify.ts | sed -n '150,230p' +exit 0 + +nl -ba src/phases/schedule-attempt.ts | sed -n '60,110p' +exit 0 + +nl -ba src/worktree/load-or-create-run-worktree.ts | sed -n '380,430p' +exit 0 + +rg -n "recordIntervention\\(|build_failed|return Object.freeze\\(\\{ status: 'intervention'|actionableSuggestions" src/phases/build.ts +exit 0 + +nl -ba src/phases/build.ts | sed -n '340,395p'; nl -ba src/phases/build.ts | sed -n '960,1005p' +exit 0 + +nl -ba src/phases/review.ts | sed -n '300,420p'; nl -ba src/phases/review.ts | sed -n '450,490p'; nl -ba src/phases/review.ts | sed -n '820,875p' +exit 0 + +nl -ba src/cli.ts | sed -n '1,120p' +exit 0 + +rg -n "console\\.error|process\\.exit|exitCode|throw new Error|catch \\(err|catch \\(error" src/commands src/cli.ts +exit 0 + +nl -ba src/commands/run.ts | sed -n '1,220p'; nl -ba src/commands/run.ts | sed -n '850,1040p' +exit 0 + +nl -ba src/commands/init.ts | sed -n '1,260p' +exit 0 + +rg -n "\.stack|stack trace|console\.error|stderr\.write\(`.*\$\{\(err as Error\)\.message|err\.message|String\(err\)" src/providers src/commands src/cli.ts +exit 1 + +rg -n '(\\.stack|stack trace|console\\.error|err\\.message|String\\(err\\)|\\(err as Error\\)\\.message)' src/providers src/commands src/cli.ts +exit 0 + +nl -ba src/providers/claude.ts | sed -n '80,135p'; nl -ba src/providers/claude.ts | sed -n '250,290p' +exit 0 + +nl -ba src/providers/codex.ts | sed -n '80,180p' +exit 0 + +nl -ba src/providers/gemini.ts | sed -n '1,80p' +exit 0 + +nl -ba src/providers/xai.ts | sed -n '280,370p' +exit 0 + +rg -n "writePauseGate|writeStopGate|PAUSE\\.json|STOP\\.json|pause|stop|SIGINT|SIGTERM|SIGINT|AbortSignal" src tests docs/contracts docs/references +exit 0 + +nl -ba src/commands/approve.ts | sed -n '1,160p'; nl -ba src/commands/approve.ts | sed -n '940,990p' +exit 0 + +nl -ba src/commands/doctor.ts | sed -n '120,210p'; nl -ba src/commands/doctor.ts | sed -n '220,380p' +exit 0 + +nl -ba src/commands/dispatch-build-helpers.ts | sed -n '590,635p' +exit 0 + +nl -ba src/cli/exit-codes.ts | sed -n '1,120p' +exit 0 + +nl -ba src/commands/run.ts | sed -n '220,390p' +exit 0 + +nl -ba src/commands/run.ts | sed -n '1040,1160p'; nl -ba src/commands/run.ts | sed -n '1160,1230p' +exit 0 + +nl -ba src/commands/run.ts | sed -n '1240,1410p'; nl -ba src/commands/run.ts | sed -n '1860,2035p'; nl -ba src/commands/run.ts | sed -n '2190,2285p' +exit 0 + +nl -ba src/phases/define.ts | sed -n '160,290p'; nl -ba src/phases/define.ts | sed -n '290,390p' +exit 0 + +ls -la docs/handoffs/codex-finalize +exit 0 + +rg -n "event_pointer|eventPointer|NEEDS_INTERVENTION\\.json|actionableSuggestions: \\[\\]|writeNeedsInterventionGate\\(" src tests docs/handoffs docs/contracts docs/references +exit 0 + +rg -n "process\\.on\\('SIGINT|process\\.on\\(\"SIGINT|SIGTERM|writeStopGate|writePauseGate|run_ended.*stopped|run_ended.*paused" src tests +exit 0 + +rg -n "A6-errors|A6" docs/handoffs/codex-finalize docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit 0 + +nl -ba tests/build-phase.test.ts | sed -n '440,475p'; nl -ba tests/providers-types.test.ts | sed -n '55,75p' +exit 0 + +nl -ba src/providers/errors.ts | sed -n '45,85p' +exit 0 + +nl -ba docs/references/file-based-gates.md | sed -n '52,105p'; nl -ba docs/references/provider-contract.md | sed -n '160,190p' +exit 0 + +nl -ba docs/references/file-based-gates.md | sed -n '104,170p' +exit 0 + +date -u +%Y-%m-%dT%H:%M:%SZ +exit 0 + +nl -ba src/state/schemas.ts | sed -n '1580,1608p' +exit 0 + +git status --short --branch +exit 0 + +LC_ALL=C rg -n $'\xE2\x80\x94' docs/handoffs/codex-finalize/A6-errors.md +exit 1 + +sed -n '1,260p' docs/handoffs/codex-finalize/A6-errors.md +exit 0 + +tail -n 90 docs/handoffs/codex-finalize/A6-errors.md +exit 0 +``` diff --git a/docs/handoffs/codex-finalize/A7-providers.md b/docs/handoffs/codex-finalize/A7-providers.md new file mode 100644 index 0000000..3aaf1cd --- /dev/null +++ b/docs/handoffs/codex-finalize/A7-providers.md @@ -0,0 +1,197 @@ +# A7-providers findings + +Sub-task: A7 +Operator: codex-subtask-7 +Started: 2026-05-13T22:10:00Z +Finished: 2026-05-13T22:26:37Z + +## Summary + +Filed 3 findings: 1 block-ship and 2 fix-soon. Biggest risk: the first-run provider contract says "set one provider key" and "no key falls back to FakeProvider", but the CLI source still routes default runs through Claude/Codex CLI OAuth unless `--provider fake` is passed explicitly. xAI redaction and mocked invalid-key coverage are strong; GUI Gemini no-key UX and subprocess expired-auth classification still need polish. + +## Findings + +### F7.1 - First-run provider-key contract does not match the implemented CLI surface + +- **Severity:** block-ship +- **Where:** `src/commands/run.ts:274`, `src/commands/run.ts:621`, `src/cli/bootstrap.ts:159`, `README.md:36`, `code-oz-gui/README.md:85` +- **Evidence:** + + ```text + $ rg -n "buildProviderRegistry\\(|parseProviderOverride|--provider only accepts|providerOverride === 'fake'|No API keys required|FakeProvider|Cost-free demo|GEMINI_API_KEY|Gemini helper is not configured|GEMINI_API_KEY is not set|console\\.error\\('AI helper request failed'|safeErrorDetail|ANTHROPIC_API_KEY|OPENAI_API_KEY|XAI_API_KEY" src/commands/run.ts src/cli/bootstrap.ts README.md docs/ABOUT.md docs/contracts/PROVIDERS.md docs/references/provider-contract.md code-oz-gui/README.md code-oz-gui/app/api/helper/ask/route.ts code-oz-gui/lib/gemini-server.ts code-oz-gui/components/AIHelper.tsx + src/commands/run.ts:274: const { registry: providerRegistry, fakeProvider } = buildProviderRegistry({ + src/commands/run.ts:621:function parseProviderOverride(value: string): { kind: 'ok'; value: ProviderOverride } | ParsedError { + src/commands/run.ts:625: message: `--provider only accepts 'fake' in v0.1 (got ${JSON.stringify(value)})`, + src/cli/bootstrap.ts:159:export function buildProviderRegistry( + src/cli/bootstrap.ts:162: if (opts.providerOverride === 'fake') { + README.md:36:... No API keys required for the supported families. + code-oz-gui/README.md:85:# Edit .env and add at least GEMINI_API_KEY for the in-GUI AI helper. + code-oz-gui/README.md:86:# ANTHROPIC_API_KEY / OPENAI_API_KEY / XAI_API_KEY are only required when + ``` + + ```text + $ rg -n "ANTHROPIC_API_KEY|OPENAI_API_KEY|XAI_API_KEY|GEMINI_API_KEY|process\\.env" src/providers src/commands code-oz-gui/lib code-oz-gui/app/api code-oz-gui/components + src/providers/xai.ts:132: const rawKey = (process.env.XAI_API_KEY ?? '').trim() + src/providers/xai.ts:211: const raw = (process.env.XAI_API_KEY ?? '').trim() + code-oz-gui/lib/gemini-server.ts:8: const apiKey = process.env.GEMINI_API_KEY; + ``` + + ```text + $ rg -n "^provider:|^model:|name:" src/agents/defaults/*.md + src/agents/defaults/ba.md:5:provider: claude + src/agents/defaults/builder.md:5:provider: claude + src/agents/defaults/lead.md:5:provider: claude + src/agents/defaults/reviewer.md:5:provider: codex + src/agents/defaults/scientist.md:5:provider: claude + src/agents/defaults/verifier.md:5:provider: claude + ``` + +- **Why it matters for first-run UX:** A user who follows the playbook and sets only `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`, or sets no key expecting FakeProvider, will not get the promised path; those env vars are ignored and bundled agents still invoke Claude/Codex CLI adapters. +- **Proposed fix:** Pick one product contract and make code/docs agree before release. If the A7 playbook is authoritative, add a RED test that a fresh run with no supported credential source routes through `buildProviderRegistry({ providerOverride: 'fake' })`, then implement a no-key fake fallback with the existing loud fake banner/event and update README. If subscription-first is authoritative, revise the first-run docs and GUI README to remove `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` for CLI runs and point users to `claude login`, `codex login`, `XAI_API_KEY`, and explicit `--provider fake`. +- **Effort estimate:** m + +### F7.2 - GUI Gemini no-key hint is generic and the route logs the raw provider error object + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/app/api/helper/ask/route.ts:149`, `code-oz-gui/app/api/helper/ask/route.ts:197`, `code-oz-gui/lib/gemini-server.ts:8` +- **Evidence:** + + ```text + $ rg -n "AIHelper|GEMINI_API_KEY|helper-unavailable|Gemini helper|askGemini|/api/helper/ask" code-oz-gui tests + code-oz-gui/lib/gemini-server.ts:8: const apiKey = process.env.GEMINI_API_KEY; + code-oz-gui/lib/gemini-server.ts:11: throw new Error('GEMINI_API_KEY is not set.'); + code-oz-gui/app/api/helper/ask/route.ts:149:function safeErrorDetail(error: unknown): string { + code-oz-gui/app/api/helper/ask/route.ts:150: if (error instanceof Error && error.message === 'GEMINI_API_KEY is not set.') { + code-oz-gui/app/api/helper/ask/route.ts:151: return 'Gemini helper is not configured.'; + code-oz-gui/app/api/helper/ask/route.ts:197: console.error('AI helper request failed', error); + code-oz-gui/components/AIHelper.tsx:57: const response = await fetch('/api/helper/ask', { + ``` + +- **Why it matters for first-run UX:** The GUI should tell a first-time user exactly how to enable the helper; "Gemini helper is not configured" does not name `GEMINI_API_KEY`, and raw SDK errors in server logs are the wrong default for the no-key/no-key-logging contract. +- **Proposed fix:** Change the no-key detail to a one-line setup hint such as `Set GEMINI_API_KEY to enable the Gemini helper.` Replace `console.error('AI helper request failed', error)` with sanitized structured logging that never includes provider SDK request/response objects or auth headers. Add a small route-level regression test by mocking `askGemini` to throw the no-key error and asserting the 503 detail contains `GEMINI_API_KEY` while logs stay sanitized. +- **Effort estimate:** s + +### F7.3 - Expired auth is a typed contract value but subprocess providers do not classify it + +- **Severity:** fix-soon +- **Where:** `src/providers/types.ts:175`, `src/providers/claude.ts:284`, `src/providers/codex.ts:156`, `docs/references/provider-contract.md:252` +- **Evidence:** + + ```text + $ rg -n "provider_auth_expired|expired|401|invalid or expired|provider_auth_missing|actionableSuggestions|redact|Bearer|Authorization|XAI_API_KEY" src/providers tests/providers-xai.test.ts tests/providers-xai-redaction.test.ts tests/commands-doctor.test.ts docs/references/provider-contract.md + src/providers/types.ts:175:export type AuthStatus = 'ok' | 'missing' | 'expired' | 'unsupported' | 'unknown' + src/providers/claude.ts:296: 'provider_auth_missing', + src/providers/codex.ts:156: code: 'provider_auth_missing', + src/providers/xai.ts:184: rule: 'xai /v1/models returned 401 (invalid or expired API key)', + src/providers/xai.ts:294: 'xai endpoint returned 401 (invalid or expired API key)', + docs/references/provider-contract.md:252: readonly authStatus: 'ok' | 'missing' | 'expired' | 'unsupported' | 'unknown' + ``` + + Targeted tests covered auth-missing and xAI 401 behavior but not expired Claude/Codex CLI auth text: + + ```text + $ bun test tests/cli-provider-override.test.ts tests/providers-xai.test.ts tests/providers-xai-redaction.test.ts tests/providers-claude.test.ts tests/providers-codex.test.ts tests/providers-gemini.test.ts tests/commands-doctor.test.ts + 102 pass + 0 fail + 241 expect() calls + Ran 102 tests across 7 files. [858.00ms] + ``` + +- **Why it matters for first-run UX:** If an upstream Claude/Codex CLI returns "token expired" or similar, the current keyword path can surface a generic provider I/O failure instead of the expected re-auth instruction. +- **Proposed fix:** Add RED tests for ClaudeProvider and CodexProvider mocked stderr such as `token expired`, `session expired`, and `invalid api key`. Map explicit expiry text to `provider_auth_expired` with `run login` remediation, and map invalid/missing login text to `provider_auth_missing`. Keep xAI 401 behavior as-is unless the HTTP body can be safely classified without reading raw upstream content. +- **Effort estimate:** s + +## Checked items + +- `FakeProvider` itself remains deterministic and offline; the override path aliases every provider id to one shared fake instance while preserving family identity. +- xAI missing key, 401, 403, 429, 5xx, malformed JSON, and wrapper redaction paths have good offline coverage. +- Source search found no runtime reads of `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`; CLI env-key isolation is strict by absence, but this conflicts with first-run docs that mention those keys. +- GUI helper reads only `GEMINI_API_KEY`; CLI xAI reads only `XAI_API_KEY`. + +## Gaps / unchecked + +- No live provider calls were made. I did not validate real Claude, Codex, Gemini, or xAI credentials. +- `ANTHROPIC_API_KEY only`, `OPENAI_API_KEY only`, `GEMINI_API_KEY only`, and `XAI_API_KEY only` end-to-end live paths were not executed; source/tests were used instead. +- I did not run the full `code-oz run` lifecycle without keys because the source shows it would use bundled Claude/Codex providers unless `--provider fake` is passed, and live provider calls were out of scope. +- I did not run the GUI helper with a real `GEMINI_API_KEY`; the no-key behavior was audited from source. +- I did not inspect or rely on other workers' untracked handoff docs beyond incidental `rg` output; A1/A3/A4/A5/A6 files were left untouched. + +## Commands run + +All commands ran from `/Users/ozzy-mac/Projects/code-oz` unless an absolute path is shown. + +```text +sed -n '1,220p' /Users/ozzy-mac/.agents/skills/code-review/SKILL.md -> exit 0 +rg -n "code-oz|finalize|provider|FakeProvider|GEMINI|ANTHROPIC|OPENAI|XAI|v0\\.20\\.1" /Users/ozzy-mac/.codex/memories/MEMORY.md -> exit 0 +git status --short --branch -> exit 0 +sed -n '1,260p' CLAUDE.md -> exit 0 +sed -n '1,240p' docs/design/ROADMAP.md -> exit 0 +sed -n '1,260p' docs/handoffs/2026-05-13-codex-finalize-distribution.md -> exit 0 +sed -n '1,220p' README.md -> exit 0 +sed -n '1,220p' docs/ABOUT.md -> exit 0 +rg -n "Finding template|severity|evidence|proposed fix|A7" docs/handoffs/2026-05-13-codex-finalize-distribution.md -> exit 0 +rg --files docs src code-oz-gui | rg -i "provider|providers|anthropic|openai|gemini|xai|aihelper|ai-helper|route|run\\.ts|doctor\\.ts|README|ABOUT|contract" -> exit 0 +rg -n "ANTHROPIC_API_KEY|OPENAI_API_KEY|GEMINI_API_KEY|XAI_API_KEY|FakeProvider|Provider|provider|invalid|expired|401|redact|secret|key" src docs code-oz-gui README.md -> exit 0 +sed -n '332,360p' docs/handoffs/2026-05-13-codex-finalize-distribution.md -> exit 0 +sed -n '1,220p' docs/contracts/PROVIDERS.md -> exit 0 +sed -n '1,220p' docs/references/provider-contract.md -> exit 0 +sed -n '1,180p' src/providers/types.ts -> exit 0 +sed -n '1,260p' src/providers/registry.ts -> exit 0 +sed -n '180,340p' src/providers/types.ts -> exit 0 +sed -n '1,260p' src/providers/errors.ts -> exit 0 +sed -n '1,260p' src/providers/claude.ts -> exit 0 +sed -n '1,280p' src/providers/codex.ts -> exit 0 +sed -n '1,260p' src/providers/gemini.ts -> exit 0 +sed -n '260,520p' src/providers/claude.ts -> exit 0 +sed -n '1,340p' src/providers/xai.ts -> exit 0 +sed -n '1,260p' src/providers/fake.ts -> exit 0 +sed -n '1,260p' src/providers/invoke.ts -> exit 0 +sed -n '1,260p' src/cli/fake-provider-warning.ts -> exit 0 +sed -n '260,560p' src/providers/invoke.ts -> exit 0 +sed -n '260,560p' src/providers/xai.ts -> exit 0 +sed -n '1,260p' src/commands/run.ts -> exit 0 +sed -n '1,320p' src/commands/doctor.ts -> exit 0 +rg -n "providerOverride|--provider|buildProviderRegistry|applyFakeScript|fake|ProviderOverride|parseRunArgs|initRun|runDefine|provider" src/commands/run.ts -> exit 0 +rg -n "providers|runDoctorProviders|formatProviders|Provider health|authStatus|actionable|suggest|login|XAI_API_KEY|GEMINI|ANTHROPIC|OPENAI" src/commands/doctor.ts -> exit 0 +sed -n '1,260p' src/cli/bootstrap.ts -> exit 0 +sed -n '1,220p' src/providers/families.ts -> exit 0 +sed -n '1,260p' src/providers/capabilities.ts -> exit 0 +sed -n '260,380p' src/commands/run.ts -> exit 0 +sed -n '427,640p' src/commands/run.ts -> exit 0 +sed -n '640,730p' src/commands/doctor.ts -> exit 0 +sed -n '340,380p' src/commands/doctor.ts -> exit 0 +sed -n '2360,2385p' src/commands/run.ts -> exit 0 +rg --files tests code-oz-gui | rg -i "provider|doctor|xai|gemini|fake|run|helper" -> exit 0 +rg -n "ANTHROPIC_API_KEY|OPENAI_API_KEY|GEMINI_API_KEY|XAI_API_KEY|provider_auth_missing|provider_auth_expired|expired|invalid|401|redact|fake provider|--provider fake|no key|missing key|doctor providers|AIHelper" tests code-oz-gui src/providers src/commands docs/references docs/contracts README.md -> exit 0 +sed -n '1,260p' code-oz-gui/app/api/helper/ask/route.ts -> exit 0 +sed -n '1,260p' code-oz-gui/lib/gemini-server.ts -> exit 0 +sed -n '1,260p' code-oz-gui/components/AIHelper.tsx -> exit 0 +sed -n '1,340p' tests/providers-xai.test.ts -> exit 0 +sed -n '340,520p' tests/providers-xai.test.ts -> exit 0 +sed -n '1,260p' tests/providers-xai-redaction.test.ts -> exit 0 +sed -n '1,300p' tests/commands-doctor.test.ts -> exit 0 +sed -n '1,220p' tests/cli-provider-override.test.ts -> exit 0 +sed -n '260,560p' tests/providers-xai-redaction.test.ts -> exit 0 +sed -n '1,280p' tests/providers-claude.test.ts -> exit 0 +sed -n '220,300p' tests/providers-codex.test.ts -> exit 0 +sed -n '1,120p' code-oz-gui/README.md -> exit 0 +rg -n "AIHelper|GEMINI_API_KEY|helper-unavailable|Gemini helper|askGemini|/api/helper/ask" code-oz-gui tests -> exit 0 +sed -n '1,260p' code-oz-gui/app/api/run/start/route.ts -> exit 0 +sed -n '1,260p' code-oz-gui/lib/code-oz-spawn.ts -> exit 0 +sed -n '120,220p' code-oz-gui/scripts/capture-screenshots.ts -> exit 0 +rg --files code-oz-gui | rg -i "test|spec|e2e" -> exit 0 +sed -n '260,560p' code-oz-gui/lib/code-oz-spawn.ts -> exit 0 +sed -n '1,260p' code-oz-gui/tests/e2e/happy-path.e2e.ts -> exit 0 +rg -n "^provider:|^model:|name:" src/agents/defaults/*.md -> exit 0 +rg -n "company:|provider:|xai|codex|claude|fake|GEMINI|ANTHROPIC|OPENAI|XAI" src/config docs contracts .code-oz README.md code-oz-gui/.env.example -> exit 2 (included a nonexistent path named contracts) +sed -n '245,360p' docs/references/provider-contract.md -> exit 0 +sed -n '500,560p' docs/references/provider-contract.md -> exit 0 +sed -n '280,345p' src/config/schema.ts -> exit 0 +sed -n '223,520p' src/config/load.ts -> exit 0 +bun test tests/cli-provider-override.test.ts tests/providers-xai.test.ts tests/providers-xai-redaction.test.ts tests/providers-claude.test.ts tests/providers-codex.test.ts tests/providers-gemini.test.ts tests/commands-doctor.test.ts -> exit 0 +rg -n "buildProviderRegistry\\(|parseProviderOverride|--provider only accepts|providerOverride === 'fake'|No API keys required|FakeProvider|Cost-free demo|GEMINI_API_KEY|Gemini helper is not configured|GEMINI_API_KEY is not set|console\\.error\\('AI helper request failed'|safeErrorDetail|ANTHROPIC_API_KEY|OPENAI_API_KEY|XAI_API_KEY" src/commands/run.ts src/cli/bootstrap.ts README.md docs/ABOUT.md docs/contracts/PROVIDERS.md docs/references/provider-contract.md code-oz-gui/README.md code-oz-gui/app/api/helper/ask/route.ts code-oz-gui/lib/gemini-server.ts code-oz-gui/components/AIHelper.tsx -> exit 0 +rg -n "ANTHROPIC_API_KEY|OPENAI_API_KEY|XAI_API_KEY|GEMINI_API_KEY|process\\.env" src/providers src/commands code-oz-gui/lib code-oz-gui/app/api code-oz-gui/components -> exit 0 +rg -n "provider_auth_expired|expired|401|invalid or expired|provider_auth_missing|actionableSuggestions|redact|Bearer|Authorization|XAI_API_KEY" src/providers tests/providers-xai.test.ts tests/providers-xai-redaction.test.ts tests/commands-doctor.test.ts docs/references/provider-contract.md -> exit 0 +git status --short --branch -> exit 0 +date -u +%Y-%m-%dT%H:%M:%SZ -> exit 0 +``` diff --git a/docs/handoffs/codex-finalize/A8-visual.md b/docs/handoffs/codex-finalize/A8-visual.md new file mode 100644 index 0000000..817ad2e --- /dev/null +++ b/docs/handoffs/codex-finalize/A8-visual.md @@ -0,0 +1,337 @@ +# A8-visual findings + +Sub-task: A8 +Operator: codex-subtask-8 +Started: 2026-05-13T22:19:00Z +Finished: 2026-05-13T22:33:30Z + +## Summary + +I found 3 findings: 1 block-ship and 2 fix-soon. The biggest risk is that the documented first-run GUI surface does not render under `bun run dev` in the current checkout: the dev server starts, but `/` returns the Next.js 404 page, so the README screenshots cannot be layout-faithful to the current GUI until that route is fixed. I did not reshoot screenshots. Screenshot files exist and are tracked, but live pixel comparison was blocked by the 404 render. + +## Screenshot reference inventory + +- `README.md` references remote badges only. No GUI screenshot paths. +- `docs/ABOUT.md` has no image references. +- `code-oz-gui/README.md:6` references `code-oz-gui/docs/screenshots/hero.png`. +- `code-oz-gui/README.md:27` references `code-oz-gui/docs/screenshots/hero.png`. +- `code-oz-gui/README.md:28` references `code-oz-gui/docs/screenshots/decisions-task.png`. +- `code-oz-gui/README.md:35` references `code-oz-gui/docs/screenshots/events-errors.png`. +- `code-oz-gui/README.md:36` references `code-oz-gui/docs/screenshots/workspace-form.png`. +- All four referenced PNG files exist and are tracked under `code-oz-gui/docs/screenshots/`. +- No root-level `docs/screenshots/` directory exists in this checkout. + +## Findings + +### F8.1 - GUI first screen renders Next.js 404 instead of the documented board + +- **Severity:** block-ship +- **Where:** `code-oz-gui` dev server and current render at `http://localhost:3000/` +- **Evidence:** + ```text + command: cd code-oz-gui && bun run dev + exit status: 0 after the dev server exited + relevant output: + Warning: Next.js inferred your workspace root, but it may not be correct. + We detected multiple lockfiles and selected the directory of /Users/ozzy-mac/Projects/code-oz/bun.lock as the root directory. + Watchpack Error (watcher): Error: EMFILE: too many open files, watch + Ready in 1095ms + GET / 404 in 2045ms + + command: cd code-oz-gui && env DISABLE_HMR=true bun run dev + exit status: 0 after Ctrl-C + relevant output: + Warning: Next.js inferred your workspace root, but it may not be correct. + Watchpack Error (watcher): Error: EMFILE: too many open files, watch + Ready in 965ms + GET / 404 in 1140ms + GET / 404 in 33ms + GET / 404 in 34ms + + command: curl -s -S -D - http://localhost:3000/ -o /dev/null + exit status: 0 + relevant output: + HTTP/1.1 404 Not Found + + Playwright MCP render snapshot at 1440x900: + Page URL: http://localhost:3000/ + Page Title: code OZ + Console: 1 errors, 0 warnings + Snapshot: + heading "404" + heading "This page could not be found." + ``` +- **Why it matters for first-run UX:** The README sells a ready Kanban board and drawer, but a first-run user opening the documented dev URL gets a framework 404 instead. +- **Proposed fix:** First make `cd code-oz-gui && bun run dev` render `/` with the board. Treat the root warning and the missing route registration as the primary repro, and do not paper over it by changing screenshot docs. Add a tiny smoke check that starts the GUI and fails unless `curl -f http://localhost:3000/` returns 200 and Playwright sees `UNDERSTAND`, `Composer`, and at least one fixture card. Then address the watcher issue separately: the existing `DISABLE_HMR` path in `code-oz-gui/next.config.ts:24-31` did not prevent `EMFILE`, so verify the fix under that mode too. +- **Effort estimate:** m + +### F8.2 - Hero alt text can claim a Gemini answer even when the capture script allows an error-state hero + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/README.md:6`, `code-oz-gui/README.md:31`, `code-oz-gui/scripts/capture-screenshots.ts:91-108` +- **Evidence:** + ```text + code-oz-gui/README.md:6: + alt="code-oz-gui hero ... and a Gemini Flash answer explaining the audit to a non-developer" + + code-oz-gui/README.md:31: + Board + Artifact tab + AI helper explaining the audit + + code-oz-gui/scripts/capture-screenshots.ts:99-107: + waits for either /Approving|checkout/i or + /Gemini helper is not configured|helper is unavailable|Helper unavailable/i, + then saves hero.png either way. + ``` +- **Why it matters for first-run UX:** If `hero.png` was captured without a valid `GEMINI_API_KEY`, the image can show a helper error while the alt text and caption say it shows an explanatory answer. +- **Proposed fix:** Make the hero screenshot deterministic. Either seed the helper route with a fixture answer for screenshot capture, or fail the screenshot task before writing `hero.png` when the helper is unavailable. If the intended documented state is "helper not configured", change the alt text and caption to say that plainly. Reuse the existing screenshot names after the fix, but reshoot only in the later image-refresh phase. +- **Effort estimate:** s + +### F8.3 - Card and drawer typography still hard-truncates title, subtitle, and status text + +- **Severity:** fix-soon +- **Where:** `code-oz-gui/components/Card.tsx:78-83`, `code-oz-gui/components/Drawer.tsx:147-150`, `.remember/today-2026-05-13.md:1-2` +- **Evidence:** + ```text + code-oz-gui/components/Card.tsx:78: +

{card.title}

+ + code-oz-gui/components/Card.tsx:79-80: +

{card.subtitle}

+

{stateDescriptor(card.state)}

+ + code-oz-gui/components/Card.tsx:83: + {stateDescriptor(card.state)} + + code-oz-gui/components/Drawer.tsx:150: +

{card.subtitle}

+ + .remember/today-2026-05-13.md:1-2: + prior visual review already called out title truncation, pill/status wrap, and subtitle issues. + ``` +- **Why it matters for first-run UX:** The A8 checklist explicitly asks for no title truncation, no pill wrap, no status wrap, and no subtitle drift; the source still encodes truncation for the exact text classes a user reads first. +- **Proposed fix:** Replace the hard truncation rules with stable wrapping rules. Suggested shape: allow card titles to wrap to the designed height with `break-words` and an explicit min-height, let subtitles/status wrap or clamp only with a visible `title` tooltip, and make the drawer subtitle wrap within the 520px drawer instead of truncating. Add a Playwright layout assertion for 1440x900 and 1280x800 that checks header, pill, status, and subtitle elements have `scrollWidth <= clientWidth` and no overlapped bounding boxes. +- **Effort estimate:** m + +## Checked items and gaps + +- Checked screenshot references in `README.md`, `docs/ABOUT.md`, and `code-oz-gui/README.md`. +- Checked that all referenced PNG files exist and are tracked. +- Checked current GUI render as far as feasible. It is blocked by F8.1. +- Checked dark/light parity. The design brief says dark only, and `code-oz-gui/app/layout.tsx:26` forces the `dark` class, so light-mode parity is not supported in this version. +- Did not reshoot images, per phase constraint. +- Did not perform pixel comparison against live board/drawer states because `/` rendered a Next.js 404. +- Did not audit root `docs/screenshots/` contents because that directory does not exist. + +## Commands run + +```text +rg -n "finalize/v0\\.20\\.1|codex-finalize|visual|screenshot|GUI|README|ABOUT" /Users/ozzy-mac/.codex/memories/MEMORY.md +exit status: 0 + +pwd && git status --short --branch +exit status: 0 + +sed -n '1,220p' /Users/ozzy-mac/.agents/skills/code-review/SKILL.md +exit status: 0 + +sed -n '1,260p' docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit status: 0 + +sed -n '1,260p' CLAUDE.md +exit status: 0 + +sed -n '1,260p' README.md +exit status: 0 + +sed -n '1,260p' docs/ABOUT.md +exit status: 0 + +sed -n '1,260p' code-oz-gui/README.md +exit status: 0 + +rg -n "Finding template|severity|block-ship|fix-soon|nit|## Finding|### Finding" docs/handoffs/2026-05-13-codex-finalize-distribution.md +exit status: 0 + +find code-oz-gui -maxdepth 3 -type f | sort | sed -n '1,220p' +exit status: 0 + +find docs -path '*screenshots*' -o -path '*screenshot*' -maxdepth 4 -type f | sort +exit status: 0 + +rg -n "{ const browser=await chromium.launch({headless:true}); const page=await browser.newPage({viewport:{width:1440,height:900}}); const errors=[]; page.on('console',msg=>{ if(['error','warning'].includes(msg.type())) errors.push(msg.type()+': '+msg.text()); }); await page.goto('http://localhost:3000',{waitUntil:'networkidle',timeout:15000}); console.log('title='+await page.title()); console.log('columns='+await page.locator('section header h2').evaluateAll(nodes=>nodes.map(n=>n.textContent).join('|'))); console.log('console='+JSON.stringify(errors)); await browser.close(); })().catch(e=>{ console.error(e.stack||e); process.exit(1); });" +exit status: 1 in sandbox, blocked by Chromium MachPort permission + +same node -e command with escalation +exit status: 1, net::ERR_CONNECTION_REFUSED because the prior server was gone + +bun run dev +exit status: 0 after dev server exited + +Playwright MCP browser_resize 1440x900 +exit status: n/a + +Playwright MCP browser_navigate http://localhost:3000 +exit status: n/a, page rendered 404 + +Playwright MCP browser_snapshot with boxes +exit status: n/a, snapshot showed only the Next.js 404 headings + +sed -n '1,200p' code-oz-gui/next.config.ts +exit status: 0 + +curl -s http://localhost:3000/ | sed -n '1,40p' +exit status: 0, but pipeline hid connection state + +find . -maxdepth 2 -type d -name app -o -name pages | sort +exit status: 0 + +env DISABLE_HMR=true bun run dev +exit status: 0 after Ctrl-C + +curl -s -S -D - http://localhost:3000/ -o /dev/null +exit status: 0, HTTP 404 + +curl -s -S http://localhost:3000/ | rg -n "404|This page|__next|code OZ|Composer|UNDERSTAND" +exit status: 0 + +find code-oz-gui/app -maxdepth 3 -type f -print | sort +exit status: 0 + +find code-oz-gui/app -maxdepth 1 -type f -print -exec ls -l {} \\; +exit status: 0 + +sed -n '1,80p' .playwright-mcp/console-2026-05-13T22-30-39-694Z.log +exit status: 0 + +sed -n '1,80p' .playwright-mcp/console-2026-05-13T22-29-52-090Z.log +exit status: 0 + +rg -n "step-4|visual review|title truncation|pill wrap|status wrap|subtitle drift|AI Studio|screenshot|wrap|trunc" .remember/today-2026-05-13.md .remember/today-2026-05-13.done.md +exit status: 0 + +sed -n '1,220p' .remember/today-2026-05-13.md +exit status: 0 + +sed -n '1,260p' .remember/today-2026-05-13.done.md +exit status: 0 + +nl -ba code-oz-gui/README.md | sed -n '1,45p' +exit status: 0 + +nl -ba code-oz-gui/components/Card.tsx | sed -n '60,90p' +exit status: 0 + +nl -ba code-oz-gui/components/Drawer.tsx | sed -n '140,155p' +exit status: 0 + +nl -ba code-oz-gui/scripts/capture-screenshots.ts | sed -n '88,110p' +exit status: 0 + +nl -ba code-oz-gui/next.config.ts | sed -n '1,32p' +exit status: 0 + +nl -ba .remember/today-2026-05-13.md | sed -n '1,12p' +exit status: 0 + +rg -n "('button:not([disabled])') + + Focusable controls inside the drawer that are not buttons: + code-oz-gui/components/AIHelper.tsx:159-165 +