lib/ — Hare-shaped standard library. Scope: every `lib/*` directory except `lib/ww/`, which is the compiler frontend port and has its own rules (see `lib/ww/CLAUDE.md`). Names mirror Hare. Before adding a function, find its counterpart in `ref/hare//` and copy the name — drop Hare's underscores per plan 9 style (`trim_prefix` → `trimprefix`, `next_token` → `nexttoken`). Don't invent. Don't shorten further. Don't reorder parameters. Signatures mirror Hare too, modulo: - Tagged-union returns are spelled with the ww `!` error tag where Hare uses `!void` / `!T`, and indices use the underlying length type (`i32` under root CLAUDE.md rule 9). Example: `strconv.stoi64(s: str, b: strconv.base) (i64 | invalid | overflow)` — same shape as Hare's. The base parameter is the named enum `strconv.base` (Hare uses `enum uint`; we pick `enum i32` to match the index type). - Static-buffer `str` returns where Hare uses them. `strconv.*tos` returns a `const str` view into a module-level buffer that is overwritten on the next call to the same function. Callers that need the bytes to outlive the next call duplicate via [[strings.dup]]. Functions that genuinely allocate a fresh buffer (`strings.dup`, `strings.concat`) still return an owned `str` that callers free via `os.free(r.ptr, r.len: u64)`. - Call-site variadic sugar matches Hare. `fn f(args: T...)` declares a Hare-style variadic; call sites either gather N args into a fresh `[]T` (`fmt.println(42, "hi", true)`) or forward an existing slice with `xs...` (`fprintln(h, args...)`). The bare `T...` form in tagged unions still means spread-flatten (`(...inner | E)`); the two uses don't overlap because `T...` only attaches to a *param* decl. `lib/fmt` ships both the print family (`print` / `println` / `fprint` / `fprintln`) and the {n}-placeholder family (`printf` / `fprintf` / `bsprintf` / `fatalf`); the `%`-parametric modifier form is parsed but its `*mods` arg slot aborts on dispatch. - `(T | U)` sum-typed parameters dispatch via `match` inside the callee. `strings.byteindex(haystack: str, needle: (str | rune))`, `bytes.index(s: []u8, needle: (u8 | []u8))`, and `rbyteindex`/ `rindex` follow Hare's shape directly. The rune-indexed `strings.index` (rune-wise position) isn't shipped yet — we don't have UTF-8 rune iteration in the language stack. Don't ship a richer surface than Hare has. A documented subset is fine; an extension, rename, or convenience-wrapper is not — callers should not bake the current subset shape into themselves. Modules with intentional divergence: - `lib/os` and `lib/net` stay below the Hare abstraction — they are syscall wrappers, not the high-level `io::handle` / `net::socket` API. Use them as the foundation that `lib/io` and the buffered layers build on. `lib/os` also plays Hare's `sys` role (ww folds `sys` into `os`), so it is the import floor: lib/os must never import io OR errors — everything points down to os; os's only edge is the import-free time leaf. This is what lets `errors` import `os` (for `os.errno` / `os.strerror`) without a cycle, mirroring Hare's `sys ← errors`, `sys ← io`. - `lib/io` keeps the ww-specific `stream` struct (vtable of fn pointers, no closures, no methods). The Hare `io::handle` family needs language features we don't have yet. - `lib/bufio` and `lib/sort` will be redesigned to Hare's `scanner` / `cmpfunc` shapes; the present minimal forms are placeholders until then. - `lib/time` and `lib/math` ship only what callers need today; they're not aiming for parity yet. When the compiler can express a Hare signature that's still in the ww-specific shape (e.g., once a module-level `*u8` is mutable, the strconv `*tos` family graduates to static-buffer returns), graduate the module in one go — replace the current shape with the Hare shape and fix the callers. Don't keep both around.