Files
claude-mode/docs/development.md
T
smoidoandClaude Opus 5 b514e00745 README down to what a user needs; the rest into docs/
The README had grown to 1,010 lines of user docs and design notes in one file,
and had gone stale: it still showed the old numbered menu, set up only three
providers, and listed preflight checks and a file layout that predate the last
three releases. It now carries install, first run, the full command
reference, the providers at a glance, troubleshooting and a docs index.

docs/:
- providers.md    presets, defaults, the model cache, context windows, each
                  provider (OpenRouter's cost guard and guardrail check are
                  written up for the first time), adding a provider
- live-sessions.md  what a switch does to running sessions, and repair
- design.md       why settings.json, why keys stay out of it (and the vault
                  per platform), the preflight checks as they are now
- bar-widget.md   the widget as it is now: providers from health.json, every
                  server provider's settings, the restart after an upgrade
- architecture.md the pieces, every file on disk and who writes it, the
                  contracts between them, where to change what
- development.md  running the tests, the conventions the code follows,
                  working on the widget, releasing
CONTRIBUTING.md points at it.

Also:
- The per-project session listing used awk, which the CLI avoids because it
  is missing from minimal images; it uses the script's own TSV helpers now,
  and tests/static.sh fails on any awk in the CLI.
- tests/static.sh checks every relative Markdown link and #anchor.
- A unit test pins the managed env keys between cm-json.py and
  claude-mode.ps1, which only a comment kept in step before.
- test_sessions covers the per-project listing, which nothing ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 01:52:03 +03:00

6.2 KiB

Development

How to change claude-mode without breaking it. How the pieces fit is in architecture.md; why they are shaped that way, in design.md.

Running the tests

scripts/test.sh                  # static checks, Python unit tests, CLI tests (~30s)
scripts/test.sh --windows        # ...and the Windows suite on winbox over SSH
scripts/test.sh --windows host   # ...on another Windows host
make test                        # the same, if make is installed

Nothing needs installing beyond bash, python3 and curl. qmllint and shellcheck are used when present and skipped, out loud, when not.

suite what it covers
tests/static.sh every script parses; JSON is valid; VERSION matches the widget manifest and has a CHANGELOG.md entry; providers.json uses only the kinds, probes and checks the code implements, every default preset exists, no id or alias shadows a command; Markdown links resolve; QML has no syntax errors
tests/python/ linux/cm-json.py, function by function: providers and the column contract the shell depends on, the original three scaffolds byte for byte, every catalogue parser, the cache, session scan/dismiss/repair, rename, defaults, health, the cost guard
tests/cli/ the POSIX CLI end to end, in a sandbox, against fake servers: providers, preflight, presets, models, doctor, a real switch, sessions, and the key helper under a rename race
tests/windows/ claude-mode.ps1 on a real Windows PowerShell 5.1, in a sandbox, against the same fake servers

The tests never touch a real install. Each CLI test gets its own HOME, CM_ROOT and CLAUDE_CONFIG_DIR with the repository's scripts installed into it, and the vault forced to the plain-file backend (CLAUDE_MODE_VAULT=file), so your keyring, ~/.claude-mode and ~/.claude/settings.json are never read or written; a switch a test performs rewrites the sandbox's settings.json. The Windows suite points USERPROFILE at a temp folder for its process, which moves every path the script uses. tests/fake_server.py stands in for Ollama, LM Studio, a keyed gateway and a listing-less proxy, on ports the OS picks.

The Windows suite needs key-based SSH to the host and python on the host (for the fake servers). It copies a payload to the host's %TEMP%, runs it, and removes it.

Do not test by switching your own machine. A switch rewrites the live settings.json that every Claude Code session — including one you may be working in — depends on. Test in the sandbox; tests/lib.sh shows how to get one by hand.

Writing a test

A CLI test sources tests/lib.sh, which provides the sandbox, run_cm (output in $OUT, colour stripped; status in $RC), expect_rc / expect_out / expect_eq / expect_file, start_fake (sets OLLAMA_URL, LMSTUDIO_URL, KEYED_URL, PROXY_URL), ready_preset, set_state and preflight_code, and ends with finish. Assertions count rather than stop, so one run reports every failure. A test earns its place by failing when the thing it describes breaks — check that it does, by breaking it.

Conventions

The code follows these already; they are written down so it keeps doing so.

Portability of the POSIX port. It runs on the bash 3.2 macOS ships: no associative arrays, no mapfile, no fractional read -t, no GNU-only stat or find flags. linux/claude-mode uses no awk (absent from minimal images). python3 is the only dependency, and handles every piece of JSON — bash only ever sees flat lines of text.

Never read TSV with IFS=$'\t' read when a column can be empty: tab counts as whitespace to read, so empty fields merge and later columns shift. Use cut (tsv_field), or have the producer print - for empty.

Windows PowerShell 5.1. No ?: or ??; "$id:latest" parses as a drive-qualified variable, so write "${id}:latest". The .ps1 files are ASCII only, because 5.1 reads a file without a BOM in the ANSI codepage.

Files are LF everywhere, .ps1 and .cmd included (.gitattributes, .editorconfig).

Comments say why, and name the thing that went wrong when a line exists because of it. What the code does is left to the code.

Keys never leave the vault: not into settings.json, health.json, the model cache, logs, or test output.

Providers

Adding a provider that reuses existing behaviour is an entry in providers.json and a preset in presets/ — see providers.md. The static check enforces the entry's shape; add a CLI test if the provider does anything the others do not. A new kind of behaviour needs a parser in cm-json.py, a branch in the bash CLI and in claude-mode.ps1, and its value added to the check in tests/static.sh.

cm-json.py's PROVIDER_TSV is a contract: the shell addresses its columns by number. Append only, and update the column list in linux/claude-mode and the unit test that pins it.

The bar widget

  • Install with bash omarchy/install.sh, then omarchy restart shell: an edited QML file does not reliably reload in a running shell, and Modes.js, a .pragma library, never does.
  • Anything with a text field must live in a KeyboardPanel. A PopupCard is an xdg-popup, which never receives the keyboard, so its fields look fine and take no typing.
  • The panel's height clamps to the screen rather than scrolling: long content needs its own bounded, scrolling area, or it is cut off.
  • Provider presentation comes from health.json, not from Modes.js — new providers need no QML change.
  • There are no automated UI tests. tests/static.sh catches QML syntax errors; everything else is checked by eye, on the real bar.

Releasing

  1. scripts/test.sh --windows — everything green.
  2. scripts/bump-version.sh X.Y.Z, and a CHANGELOG.md entry for it (the static check fails without one).
  3. Commit.
  4. Install on your own machine: bash linux/install.sh, bash omarchy/install.sh, omarchy restart shell, and look at the bar.
  5. Push. The Windows package is built on a Windows machine with scripts/build-package.ps1, which refuses to package a script that does not parse.

Semver: a minor version adds behaviour, a patch fixes it.