# AGENTS.md This file provides guidance to coding agents working with code in this repository. ## Check the branch is on top of `origin/main` before starting Run `mise run branch-up-to-date-check` as the first action of any task, and again before diagnosing any check or test failure. A branch that is behind `origin/main` runs its lints and tests against code the rest of the repo has already moved past, so failures surface in files the branch never touched -- and they read as though the branch caused them. If the check reports the branch is behind, say so and get it rebased before doing anything else; `git pull --rebase origin main` is not the agent's call to make unprompted, so ask. Do not theorise about why an unrelated crate is suddenly failing until this check has passed. A worked example from this repo: a branch sitting one commit behind `origin/main` produced 10 clippy errors across `libs/ws-runner-common`, `services/ws-web-runner` and `services/ws-wasi-runner` -- three crates the branch had not touched and which did not depend on anything it changed. Every one of those lints had already been fixed by the single missing commit. The wrong diagnosis reached for first was a toolchain change, which the dates flatly contradicted; `git rev-list --left-right --count origin/main...HEAD` would have settled it immediately. ## Install the mise tools at the start of every session Run `mise install` right after the branch check, once per session, before touching anything else. It is idempotent and near-instant when everything is already present, so the cost of running it is far below the cost of not running it. `mise install` covers only the envs `MISE_ENV` currently loads -- reach for `mise run install-all` when the work spans guest languages, since a tool from an unloaded env is missing in exactly the same way. A tool that is absent does not announce itself up front. It surfaces much later as a bare `: not found` in the middle of a check, at which point the failure looks like a bug in the thing being checked rather than a gap in the environment, and the tempting next move -- calling the host's copy, or hardcoding an absolute path into an install dir -- quietly violates the mise-managed-tools rules further down and produces results that do not match the canonical pipeline. Install the tool instead; a missing mise tool is a misconfigured environment, never a reason to route around it. The same applies the moment a `not found` does appear mid-task: stop and install, rather than reaching for a fallback. Treat it as a signal that this session skipped the step above, or that a config bump added a tool the existing install predates -- a warm checkout that already has an older tool set still needs `mise install` re-run after pulling, because a tool the current mise cannot resolve can still report as installed and nothing will refetch it on its own. ## Work through the session's tasks in the order they were given Finish the tasks the user has asked for in the order they asked for them. A later request is a task added to the end of the queue, not an instruction to abandon the one in flight -- switching to it first leaves the earlier work half-done, and half-done work is the kind that gets forgotten and rediscovered as a regression. Reorder only for a strong reason, and say so: a later task is a prerequisite of an earlier one, the earlier one is blocked waiting on something, or the later one is a live breakage (a red CI lane, a broken build) that makes the rest moot until it is fixed. ## Don't stop to ask; decide, act, and report Questions cost a round trip each, and a session that stops at every fork gets nothing finished. Default to making the call yourself and reporting what you chose and why. Ask only when proceeding either way would be unsafe or would waste substantial work that the answer might invalidate -- and when the answer is a fact rather than a preference, find the fact instead of asking for it. Two specific calls that have come up repeatedly and never need asking: - **Prefer upgrading to downgrading.** When a version bump breaks something, fix the code against the new version. Downgrading is a last resort, reserved for when upstream genuinely cannot support the new version yet -- and then say which upstream release is the blocker, so the note survives as evidence. - **Never propose forking or patching a published crate or tool.** Carrying a fork of someone else's release -- a `[patch.crates-io]` entry, a vendored copy, a local patch to a mise tool -- is almost never acceptable here: it is invisible to anything consuming our published crates, and it becomes a maintenance burden nobody signed up for. If the only way past a problem looks like a fork, that is a signal the approach is wrong. Find another route, or accept the constraint and record it, rather than raising the fork as an option. ## Scratch work stays inside this repo Any throwaway file the agent needs while working -- backup copies of files before destructive edits, generated probe scripts, captured tool output, intermediate diff materials, anything -- must live under this repo's `target/` directory (which is already gitignored). Do **not** write to `/tmp`, `/var/tmp`, `~/Desktop`, `~/Downloads`, `~/scratch`, or any other path outside this working directory. `target/scratch/` is fine; create subdirectories under it freely and clean up when done. ### NEVER reference a file that isn't in this repo Do not mention, link to, or write a path to any file that is not tracked in this repo -- not from a tracked file (source, `*.md`, config, a comment) and not in a commit message, PR body, or code-review note. A relative path that escapes the repo root, an absolute filesystem path, or prose like "see the write-up in the parent directory" is meaningless to everyone who clones the repo and reads as a dangling pointer. If the thing being referenced matters to the repo, it must live **inside** the repo; if it deliberately lives elsewhere (the operator keeps it outside the tree on purpose), then simply never point at it from inside. Creating or editing files outside the repo is fine when the operator wants that -- this rule is **only** about never referring to out-of-repo files from within the repo. ## Misc Markdown files are not committed Never create a `docs/` directory. When asked for a miscellaneous `.md` file related to this repo -- an email draft, notes, a write-up -- write it at the top level of the repo as `.local.md`. `.gitignore` covers that suffix, so it is never committed and no linter reads it. It needs no formatting either, and the line-length limit does not apply. Write it for its reader instead: text meant to be pasted elsewhere, such as an email, keeps each paragraph on one line so it pastes without hard breaks. Once it is written, offer to open it in a Markdown editor; on macOS, `open` on the file launches the default app for `.md`. ## Finding a Curtin staff member's details A Curtin staff member's email address is `firstname.lastname@curtin.edu.au`, so build it from their name rather than searching for it. Their honorific, position and school are public in Curtin Profiles, which takes a surname at `https://profiles.curtin.edu.au/search?by=text&type=user&v=`; it renders client-side, so read it in the browser pane rather than fetching it. Web search finds neither, so do not spend searches on a name. The older `https://staffportal.curtin.edu.au/staff/profile/view/` profile URLs redirect to Curtin Profiles. ## On macOS: use homebrew bash for ad-hoc agent commands On macOS, every ad-hoc command the agent runs that is **not** a `mise run ` invocation -- investigations, scratch-area probes, multi-line shell loops, anything with command substitution or arrays -- must execute under homebrew's `bash` (typically `/usr/local/bin/bash` on Intel, `/opt/homebrew/bin/bash` on Apple Silicon), **not** the session's default zsh and **not** macOS's bundled `/bin/bash` (which is GNU bash 3.2 from 2007 and predates many modern features). Invoke it explicitly per command: `/usr/local/bin/bash -c '