chore: symlink AGENTS.md + GEMINI.md + CLAUDE.md -> LLM.md

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-02 02:46:00 -07:00
co-authored by Hanzo Dev
parent c23c1ad33c
commit d1b6ea9ecb
2 changed files with 9 additions and 197 deletions
Symlink
+1
View File
@@ -0,0 +1 @@
LLM.md
+8 -197
View File
@@ -9,11 +9,9 @@ In the codex-rs folder where the rust code lives:
Completion/build step
- Always validate using `./build-fast.sh` from the repo root. This is the single required check and must pass cleanly.
- `./build-fast.sh` can take 20+min to run from a cold cache!!! Please use long timeout when running `./build-fast.sh` or waiting for it to complete.
- Policy: All errors AND all warnings must be fixed before you’re done. Treat any compiler warning as a failure and address it (rename unused vars with `_`, remove `mut`, delete dead code, etc.).
- Do not run additional format/lint/test commands on completion (e.g., `just fmt`, `just fix`, `cargo test`) unless explicitly requested for a specific task.
- ***NEVER run rustfmt***
- Before pushing to `main`, run `./pre-release.sh` to mirror the release preflight (dev-fast build, CLI smokes, workspace nextest).
Optional regression checks (recommended when touching the Rust workspace):
@@ -22,143 +20,6 @@ Optional regression checks (recommended when touching the Rust workspace):
When debugging regressions or bugs, write a failing test (or targeted reproduction script) first and confirm it captures the issue before touching code—if it can’t fail, you can’t be confident the fix works.
## Product identity — do not let a merge take it back
`dev` is the Hanzo coding agent. `hanzo code` launches a bare `dev` off PATH, so
these are contract, not preference:
| Property | Value | Where |
|---|---|---|
| Default provider | `hanzo` — api.hanzo.ai/v1, Responses wire, bearer `HANZO_USER_KEY` | `code-rs/core/src/model_provider_info.rs` |
| Default model | `zen5-coder` (Zen family) | `code-rs/core/src/config.rs` |
| Login command | `hanzo auth login` | `code-rs/core/src/auth.rs` `LOGIN_COMMAND` |
| Canonical binary | `dev` (declared first; `code` is an alias to the same program) | `code-rs/cli/Cargo.toml` |
| npm | `@hanzo/dev` + `@hanzo/dev-<target>` | `codex-cli/package.json`, `.hanzo/workflows/release.yml` |
| Homebrew | `hanzoai/homebrew-tap`, `Formula/hanzo-dev.rb`, assets `dev-<triple>` | `scripts/generate-homebrew-formula.sh` |
| Release repo | `hanzoai/dev` | `code-rs/tui/src/updates.rs`, `codex-cli/postinstall.js` |
**`code-rs/` is the workspace that SHIPS.** `vendor/codex/` is the vendored upstream
copy. A change that only edits `vendor/codex/` changes nothing a user runs — this is
the single easiest mistake to make here, and `.github/merge-policy.json` made it
for a long time (every glob pointed at `vendor/codex/**`).
That sentence only became true with the v0.6.169 merge. Before it, building
`dev` compiled ten crates out of `vendor/codex/` — they were real path
dependencies, so the "read-only mirror" was load-bearing. Upstream finished
the migration and the two workspaces are now separate. One thread remains:
`vendor/codex/models-manager/models.json` is `include_str!`'d by
`code-version/src/lib.rs`, `core/src/model_family.rs` and
`core/src/agent_defaults.rs`. Deleting `vendor/codex/` would not build.
**Why there is a test for all of this.** A one-time edit to a default is a
suggestion that lasts until the next merge. `code-rs/core/tests/hanzo_identity.rs`
asserts every row above and fails the build if any regresses. Upstream has no
file at that path, so a merge can neither conflict with it nor carry it away
along with the thing it guards. If it goes red after a merge: re-apply the
identity, do not delete the test. (It is not decorative — it caught the
un-retargeted release pipeline and only went green once the pipeline was
genuinely fixed.)
**Model ids are not guessable.** `GET https://api.hanzo.ai/v1/models` needs a
bearer and is the only authority. The previous default, `gpt-5-codex`, is not in
the catalog and returns `400 model is not available` from our own gateway —
which is why `hanzo`'s CLI passes `-c model=` on every launch. Verify a model id
against `/v1/responses` (the wire `dev` speaks) before making it a default; some
ids answer on chat/completions but return system-prompt text on responses.
**Packaging is a supply chain.** `codex-cli/postinstall.js` runs on every
`npm install` and downloads and executes a binary. It, and the TUI auto-updater
(`code-rs/tui/src/updates.rs`), must point at `hanzoai/dev`. `codex-cli/.pack/`
is `npm pack` output that CI regenerates — it is gitignored on purpose; when it
was committed it was a second, stale home for that download URL.
### Moving releases to git.hanzo.ai — the precondition
Releases are served by GitHub today. Nothing in this repo points at
`git.hanzo.ai`, and retargeting it blind breaks every installed copy, because
the updater and the installer both fetch a URL the user already has baked in.
There are exactly four places, and they must move together:
| What | Where | Currently |
|---|---|---|
| Update check | `code-rs/tui/src/updates.rs` `LATEST_RELEASE_URL` | `api.github.com/repos/hanzoai/dev/releases/latest` |
| Release page | `code-rs/tui/src/updates.rs` `CODE_RELEASE_URL` | `github.com/hanzoai/dev/releases/latest` |
| Binary download | `codex-cli/postinstall.js` `downloadUrl` | `github.com/hanzoai/dev/releases/download/v${version}/…` |
| Asset upload | `.hanzo/workflows/release.yml` | GitHub release assets, and the same files on the forge release |
The forge serves its API at `/v1/`, **not** `/api/v1/` — so the update check
becomes `git.hanzo.ai/v1/repos/hanzoai/dev/releases/latest`, and the JSON it
returns must keep the `tag_name` and `assets[].name` fields `updates.rs` reads.
**The flip is safe only once the forge is serving release assets for a tag that
already exists on GitHub**, so an old client fetching the GitHub URL and a new
client fetching the forge URL get the same bytes. Publish to both for one full
release cycle, then move the updater, then stop publishing to GitHub. Moving the
updater first strands everyone who has not yet updated — they poll a host that
has no releases, and the CLI silently stops offering upgrades.
## Upstream lineage — where the code actually comes from
Read this before attempting a sync. The directory names lie about the topology.
- **Proximate upstream is `just-every/code`**, not `openai/codex`. Our history
is just-every's history with every author email rewritten to `[email protected]`,
which is why `git merge-base` against just-every returns nothing: identical
trees, different SHAs, disjoint graph.
- **`vendor/codex/` is not ours to track.** just-every carries it as a read-only
mirror of `openai/codex:main` and refreshes it with "Refresh codex-rs mirror
to upstream/main" commits. It reaches us through them. We never merge
openai/codex directly. (The `upstream` git remote pointing at openai/codex is
a leftover — it is not the sync path.)
- **`code-rs/` is the product.** It is what builds and what ships.
### Syncing
The graphs are disjoint, so there is no merge base to discover — the just-every
commit our tree currently matches is recorded here, and nowhere else:
upstream just-every/code
synced-to 7d59424890 ("fix(deps): update js-yaml to 4.3.1")
method 3-way apply of the delta (see below)
Generate the delta with `git diff --binary`. Two schema fixtures under
`vendor/codex/app-server-protocol/schema/precomputed/` are `.zst` blobs, and
`git apply` is atomic: without full index lines it rejects those two hunks and
silently rolls back all 371 files, reporting "Applied patch … cleanly" for
every other file on the way. A clean worktree after an apply that printed
success means it failed.
To sync: fetch just-every, then apply the delta `<synced-to>..<their-new-head>`
onto our tree and update the SHA above. Conflicts concentrate in the TUI
chatwidget, `core/src/config.rs`, and every file carrying our name.
The graft-and-merge recipe this section used to give (`git replace --graft`,
`git merge`, `git replace -d`) is not what the last sync actually did, and the
base it named — `e4fe007150` / `e92d81b496` — is now stale. Applying the delta
directly is simpler and has the same effect, because a graft merge against a
disjoint history degenerates to exactly that. The v0.6.170 sync (`2ebbda854e`)
recorded the numbers: the upstream delta touched 249 files, our fork diverges
in 140, and the intersection was 2 files — both pure additions.
**Do not merge `openai/codex` directly**, whatever the `upstream` remote
suggests. Measured 2026-08-06: `origin/main` and `openai/codex` share no merge
base at all (different root commits), our `vendor/codex/` mirror differs from
upstream by 341 shadowed files and ~175k lines, and 72 upstream crates have no
counterpart here. The subsystems people ask for by name — `tool_search`,
`thread_manager`, `multi_agents_v2`, MCP `connection_manager`, `resize_reflow` —
do not exist in `code-rs` at all; upstream refactored `core` into ~15 crates
while this fork kept the 2025-era monolith. Take upstream through just-every,
who already carry the mirror, or port one subsystem deliberately. A direct merge
is a re-fork, not a sync.
## Documentation hygiene
- Keep docs clean, clear, and current; prune stale instructions instead of piling on caveats.
- Avoid excessive verbosity; prioritize concise guidance over long narratives.
- Do not document minor or non-core features; focus on system-critical flows and expectations.
- Never commit temporary "working" docs, plans, or scratch notes.
## Strict Ordering In The TUI History
The TUI enforces strict, per‑turn ordering for all streamed content. Every
@@ -211,32 +72,6 @@ Quick procedure (merge-only):
- `git add -A && git commit -m "Merge origin/main: adopt remote version bumps; keep ours elsewhere (<areas>)"`
- Run `./build-fast.sh` and then `git push`
## The one thing `dev` sends to cloud besides inference
`code-rs/core/src/shot/` publishes a screenshot of the UI a patch just changed:
after each `TurnDiff`, a rendered file in the diff earns a headless capture of
the route it renders at, uploaded to the `sessions` bucket and named by one
`tool-call` event on `POST /v1/agents/sessions/{id}/events`, so a console
watching the live stream shows the picture beside the diff.
Four things about it are load-bearing and easy to undo by accident:
- **It is off unless `HANZO_SESSION` names a session.** Without one there is
nowhere to publish, so nothing dials and nothing launches. That variable, not a
config key, is the switch — and it is validated before it reaches a URL path.
- **It never starts a dev server.** It reads `package.json` for a port and dials
it. Starting one would run repo-supplied scripts outside the approval and
sandbox path every other command here goes through.
- **Base URL and bearer come from `create_hanzo_provider()`.** Same door, same
credential as inference. Do not grow a second notion of where cloud is.
- **The event names bucket and key, never a URL.** The only URL for a private
object is a signed, expiring one; writing that into a transcript leaks a
credential that outlives the session.
It fails soft everywhere — evidence is not the work — so a broken capture is a
`debug!` line, never a failed turn. Most of it arrived in `bc20e6af40`, whose
subject is about the Hanzo provider's machine credential and does not mention it.
## Command Execution Architecture
The command execution flow in Codex follows an event-driven pattern:
@@ -257,20 +92,6 @@ The command execution flow in Codex follows an event-driven pattern:
This architecture separates concerns between execution logic (core), UI state management (chatwidget), and rendering (history_cell).
### Auto Drive Escape Handling
- All Auto Drive escape routing lives in `code-rs/tui/src/chatwidget.rs`. The
`ChatWidget::auto_should_handle_global_esc` helper decides whether the global
Esc handler in `app.rs` should defer to Auto Drive, and
`ChatWidget::handle_key_event` owns the actual stop / pause behaviour. When
you need to tweak Esc semantics, update those two locations together.
- The approval pane must *never* swallow Esc. `code-rs/tui/src/bottom_pane/auto_coordinator_view.rs`
intentionally lets Esc (and the other approval shortcuts) bubble back to the
chat widget; keep this contract intact when editing the view layer.
- Avoid adding additional Esc handlers elsewhere for Auto Drive flows. Doing
so breaks the modal-first ordering in `app.rs` and prevents users from
reliably stopping a run.
## Writing New UI Regression Tests
- Start with `make_chatwidget_manual()` (or `make_chatwidget_manual_with_sender()`) to build a `ChatWidget` in isolation with in-memory channels.
@@ -284,27 +105,17 @@ This architecture separates concerns between execution logic (core), UI state ma
- The VT100 harness lives under `code-rs/tui/tests/vt100_chatwidget_snapshot.rs`. It renders the live `ChatWidget` UI into a `Terminal<VT100Backend>` so snapshots capture the exact PTY output the user sees (including frame chrome, composer rows, and streaming inserts).
- Use `ChatWidgetHarness` helpers from `code_tui::test_helpers` to seed history events and drain `AppEvent`s. Call `render_chat_widget_to_vt100(width, height)` for a single frame, or `render_chat_widget_frames_to_vt100(&[(w,h), ...])` to simulate successive draws while streaming.
- The harness now exports `layout_metrics()` so tests can assert scroll offsets and viewport heights without spelunking through private fields.
- Snapshots are deterministic: the greeting is fixed text, the free-pool notice is left out of test renders, and the composer's running spinner holds its first frame in test mode.
- Snapshots are deterministic: tests set `CODEX_TUI_FAKE_HOUR=12` automatically so greeting text (“What can I code for you today?”) doesn’t oscillate. If you need a different hour in a test, override the env var before constructing the harness.
- To add a new scenario, push history/events onto the harness, call `render_*_to_vt100`, and either `insta::assert_snapshot!` the frame(s) or manually assert string contents. For multi-frame streaming, push deltas/events first, then capture frames in the order the UI would display them.
- Run all VT100 snapshots via:
- `cargo test -p code-tui --test vt100_chatwidget_snapshot --features test-helpers -- --nocapture`
- When you intentionally change rendering, review the `.snap.new` files that appear in `code-rs/tui/tests/snapshots/` and accept them with `cargo insta review` / `cargo insta accept` (limit to this test where possible).
### The release lane
### Monitor Release Workflows After Pushing
CI runs on the forge (`git.hanzo.ai/hanzoai/dev`), from `.hanzo/workflows/`;
`.github/workflows/` no longer exists. `cicd.yml` is the compile gate on the
linux pool. `release.yml` is the release: one job on the macOS host runner
(`runs-on: [self-hosted, macos, arm64]` — dbc-metal), every push to `main`,
building all five targets on that one host — darwin natively, linux-musl via
`cargo zigbuild`, windows-msvc via `cargo xwin` — then GitHub Release, forge
release, npm (`@hanzo/dev` + five platform packages), Homebrew tap, and last the
`chore(release): X.Y.Z [skip ci]` bump + `vX.Y.Z` tag pushed to both remotes.
The version is the next patch above the newest tag and the newest npm package,
so a run that dies half-published never reuses a number.
Push `main` to both remotes (`origin` = GitHub, the forge = what runs CI); the
release job pushes its bump commit to both as well.
Every push to `main` on GitHub is not a release — only the forge runs the lane.
Follow a run in the forge UI, or read `action_run` on the git database.
- Use `scripts/wait-for-gh-run.sh` to follow GitHub Actions releases without spamming manual `gh` commands.
- Typical release check right after a push: `scripts/wait-for-gh-run.sh --workflow Release --branch main`.
- If you already know the run ID (e.g., from webhook output), run `scripts/wait-for-gh-run.sh --run <run-id>`.
- Adjust the poll cadence via `--interval <seconds>` (defaults to 8). The script exits 0 on success and 1 on failure, so it can gate local automation.
- Pass `--failure-logs` to automatically dump logs for any job that does not finish successfully.
- Dependencies: GitHub CLI (`gh`) and `jq` must be available in `PATH`.