Files
windmill/AGENTS.md
hugocasa c3b2275864 docs(agents): rework agent context, fix dev-env docs, vendor skills (#10667)
* docs(agents): scope agent guidance to where it loads

AGENTS.md loads in every session. Three of its sections only ever applied to
one directory, and docs/autonomous-mode.md was unreferenced by anything in the
repo, so none of its content was in effect.

- Move "Verifying Backend Changes" to backend/CLAUDE.md, "Verifying Frontend
  Changes" and "Banned Patterns" to frontend/CLAUDE.md. They now load when
  working under those directories, which is when they apply.
- Update the two cross-references that pointed at the moved sections (pr and
  svelte-frontend skills).
- Delete docs/autonomous-mode.md. Its "don't stop early" half is already in
  .webmux.yaml's oneshot system prompt, which actually loads; its trigger was
  bypassPermissions, which does not imply an absent user; and it restated
  AGENTS.md and the pr skill with copies that had drifted (hardcoded ports,
  relative screenshot paths). Salvaged the UI traps it uniquely documented
  into frontend/CLAUDE.md and dropped the three stale profile references.

AGENTS.md drops ~3.6k characters with no guidance lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(agents): guidance for building a feature — reuse, telemetry, live verification

Three recurring gaps, all cases where a pointer existed but nothing triggered
on it.

Component reuse. The svelte-frontend skill documented three components with
props, which reads as the whole catalog; the barrel exports 23 and common/ has
34 subdirectories against those 23. So "never use raw HTML elements" was an
instruction agents could not follow. Added a mandatory discovery step: read
the barrel, grep the tree, and treat the documented three as examples.

Brand guidelines. frontend/brand-guidelines.md is 34k characters referenced by
bare path, which nothing opens speculatively. Added a table mapping what you
are building to the section that governs it, entered with grep rather than a
full read.

Product telemetry. feature_usage has 14 registered actions across three
features, and an unregistered (feature, kind) pair is dropped by
valid_feature_usage_event with a bare continue — no error, still a 204 — so
frontend-only instrumentation silently records nothing. New
docs/feature-telemetry.md carries the criteria for when to instrument, the
four-step recipe including the allowlist and the InstanceSettings disclosure,
and the privacy rules. Raised in the plan for user-facing work, not as a
separate question, and not at all for bugfixes or refactors.

Also: validation now ends at exercising the change on the running instance,
with standing permission to spin up whatever that takes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(dev): correct the worktree dev-environment guidance

Several things agents were told to do did not match what the machine does.

- Env discovery pointed at .env / .env.local / backend/.env. In a webmux
  worktree the real values are in $(git rev-parse --git-dir)/webmux/runtime.env
  (BACKEND_PORT, FRONTEND_PORT, DATABASE_URL, CARGO_FEATURES, WM_DB_NAME),
  sourced by every pane and undocumented. Reading it is also not blocked by the
  Read(**/.env) deny rules, which the old instruction walked straight into.
- The database name rule said branch-with-underscores. worktree-common.sh uses
  the worktree directory basename, and Postgres truncates at 63 characters, so
  branch hugo/win-2340-… resolves to windmill_win_2340_…_and_eval with no hugo_
  prefix and the tail chopped. A wrong DATABASE_URL guts the sqlx cache.
- The restart procedure said "tmux pane 1" and sent keys to an undefined
  <pane1>. Pane 1 is the backend under the full profile and the frontend under
  frontendOnly. Replaced with finding the pane by pane_current_command,
  recovering the live feature set from the running process (CARGO_FEATURES in
  runtime.env only records what the pane started with), and restarting in place.
- Added recovery for an orphaned backend holding the port: it reparents to
  systemd when its shell dies, so it survives anything that looks like cleanup.
  Three checks before killing a single pid, because pkill -f windmill takes out
  every sibling worktree.
- Agents spawned their own servers because AGENTS.md opened by telling them to.
  Now it checks for the existing panes first; the spawn commands are scoped to
  a plain checkout.
- New EE worktrees branched from the EE repo's local main, which nothing
  fast-forwards, so they started behind the commit pinned in
  backend/ee-repo-ref.txt — the one CI builds against. They now base on the pin,
  falling back to main only when it is unreadable.
- Enabled webmux autoPull so local main stays current; new worktrees are
  branched from it. Documented what WM_CLONE_DB does, including that it
  terminates every connection to the base windmill database.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(skills): vendor grilling/architecture skills; tighten PR ready and review rounds

Vendors five skills from https://github.com/mattpocock/skills (MIT, pinned at
84fdeffd12f2ee307994d1eb6feb48173b6e0502). They are one dependency closure:
grill-me is a stub that runs grilling, and improve-codebase-architecture draws
its vocabulary from codebase-design and its CONTEXT.md upkeep from
domain-modeling. .agents/skills/UPSTREAM.md records the license, the pin, and
the four local deltas so a refresh stays a diff:

- flattened the upstream engineering/ and productivity/ split
- rewrote bundled-file links to repo-root paths, since relative links break
  when read through the .claude/skills symlink
- dropped the upstream agents/openai.yaml packaging metadata
- removed every ADR path. This repo has not adopted ADRs, and a skill that
  offers to create them is how the practice arrives by side effect rather than
  by decision.

PR workflow changes, all in the pr skill:

- A round that never starts is usually a conflict with main, not a CI outage.
  Resolve by merging, not rebasing — a rebase rewrites the head SHA that round
  verdicts and the clean-round marker are keyed to. If the merge advances
  backend/ee-repo-ref.txt, the EE worktree has to follow or
  cargo check --features private compiles a tree neither the author nor CI
  intends.
- A clean round no longer means an automatic flip to ready. Wide blast radius
  (*_ee.rs, migrations, OpenAPI or the generated client, auth paths, shared
  worker infrastructure, a new public surface) asks first; self-contained
  changes flip. Unattended, the judgement holds and the action degrades: flip
  the small ones, leave the rest at a clean draft with the reason in the PR
  body.
- Rounds that never converge are usually structural. After three without
  convergence, stop, name the module the findings cluster around, and suggest
  improve-codebase-architecture rather than burning more CI.

AGENTS.local.md (gitignored, with CLAUDE.local.md importing it) holds the
ready/ask calibration, recorded as dated observations rather than a rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(dev): state that each worktree gets its own fresh database

The per-worktree section warned which DATABASE_URL to use but never said where
the database comes from: the post-create hook creates and migrates a new one
per worktree, so it starts with none of the main instance's workspaces, scripts
or flows. WM_CLONE_DB was documented only as a comment in .webmux.yaml, which
reads as how things work rather than as a per-project opt-in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(sqlx): script the cache backup/restore instead of documenting it

The update-sqlx skill spelled out a cp/comm/rm dance around `cargo sqlx
prepare`, which empties backend/.sqlx before regenerating — a failed run leaves
the cache gutted (observed: 2350 -> 142 entries), and a --all-targets run in a
CE checkout fails that way every time. Three problems with documenting it:

- The backup path was the literal /tmp/sqlx_backup, shared by every worktree.
  Two concurrent runs overwrite each other's backup, which is the only thing
  standing between a failed prepare and a gutted cache.
- The restore was a copy-pasted `rm -rf .sqlx && cp -r ... && cp ...` chain.
- Skipping the backup is what turns a routine failure into a lost cache, and a
  convention is easier to skip than a command.

sqlx-cache.sh has backup / newq / restore, keeps state in a per-worktree
directory, and leaves the judgement call where it belongs: `newq` prints each
added entry's query field for review, and only `restore` writes them in.

Also adds the general rule that scratch files belong outside the checkout —
anything written into the tree has to be deleted again, and rm prompts each
time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(agents): state why a routine cleanup prompts, and where scratch goes

The guard hook already auto-allows a plain rm whose operands are under /tmp or
inside a git checkout in $HOME, so deleting a temp dir or a stale .sqlx entry
costs nothing. What prompts is the command shape: the hook's tokenizer defers on
&&, ;, redirects, quotes and $VAR, so a chained cleanup falls through to the
Bash(rm:*) ask rule.

That was recorded only inside a paragraph about screenshot file paths in
frontend/CLAUDE.md, where nobody looking for it would find it. Stated in Core
Principles instead, alongside the rule that scratch belongs outside the tree —
for the reason that actually applies, which is not committing junk rather than
avoiding prompts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(security): deny agent edits to the permission hooks and project settings

.claude/hooks/guard-rm-outside-tmp.sh and guard-main-branch.sh are the
enforcement points for everything the permission rules are meant to catch, and
nothing stopped an agent editing them. One sed -i disables the guard for every
later command, silently, and the deny list in .claude/settings.json has the same
exposure.

Defence in depth rather than a boundary: an agent with arbitrary bash can still
delete, and this may only close the Edit-tool path if Bash writes are not
covered by Edit deny rules. It costs nothing and removes the cheapest way to
turn the guards off. Changing them now means editing the files by hand, which is
the intent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address review round findings on head 3f47dc1

- backend/ and frontend/ guidance was Claude-only. Codex and Pi read AGENTS.md,
  not CLAUDE.md, so moving "Verifying Backend/Frontend Changes" and the
  $bindable ban out of the root AGENTS.md made them invisible to two of the
  three CLIs this repo supports. Renamed both to AGENTS.md with a one-line
  @AGENTS.md CLAUDE.md beside them, matching what the repo already does at the
  root and in ai_evals/, and retargeted the four references.

- sqlx-cache.sh aborted with exit 2 and no output when .sqlx was empty:
  list_entries ran `ls -1 ./*.json`, and an unmatched glob under
  `set -euo pipefail` killed the script. An empty cache is precisely what a
  failed prepare leaves behind, so it broke in the one case it exists for.
  Replaced with a glob loop; reproduced the failure and verified the fix.

- The oneshot prompt ("never leave the PR sitting in draft") contradicted the
  "Flip, or ask first" rule added in the same PR, which tells unattended runs to
  leave wide-blast-radius changes as clean drafts. The prompt now defers to the
  skill for the flip decision and keeps only "never stop at an unreviewed
  draft".

- Bundled-resource references in the vendored skills were markdown links to
  `.agents/skills/...`, which resolve relative to the file, not the repo root.
  Replaced with inline paths stating they are repo-root relative.

- The PR-ready calibration file was write-only: the skill said to record
  answers there but never to read it. It is now consulted before deciding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Revert "chore(security): deny agent edits to the permission hooks and project settings"

This reverts commit 3f47dc1692.

* fix: address round 2 nits

- backend/AGENTS.md told agents to persist CARGO_FEATURES in runtime.env, but
  webmux regenerates that file from metadata and .env.local every time the
  worktree is opened, so the setting is lost on the next reopen. The persistent
  source is .env.local, which scripts/post-create.sh already writes.

- UPSTREAM.md still described the vendoring delta as rewriting bundled-file
  *links* to repo-root paths. 555f063 replaced them with plain paths in prose,
  because a markdown target resolves relative to the file — a repo-root link is
  just as broken as a sibling-relative one through the symlink. Replaying the
  old wording on a refresh would reintroduce the bug UPSTREAM.md exists to
  prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(skills): correct the UPSTREAM.md link-rewrite delta

The delta note still described rewriting bundled-file *links* to repo-root
paths. 555f063 replaced them with plain paths in prose, because a markdown
target resolves relative to the file containing it — a repo-root link is as
broken as a sibling-relative one read through the symlink. Replaying the old
wording on a refresh would reintroduce exactly the bug UPSTREAM.md exists to
prevent.

The preceding commit's message claimed this fix; the edit had failed on a
stale anchor and only the backend/AGENTS.md half landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(dev): describe what a fresh worktree database actually contains

Exercising a real worktree creation showed the previous wording ("none of your
workspaces, scripts or flows") reads as an empty database. It is a bootstrap
instance: the admins workspace, the admin@windmill.dev superadmin, the license
key copied from the base database, and the migration seeds — observed as
u/admin/hub_sync and the default app theme resource.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:12:20 +00:00

14 KiB

Windmill

Open-source platform for internal tools, workflows, API integrations, background jobs, and UIs. Rust backend + Svelte 5 frontend.

Workflow

  1. Understand: Before coding, explore the codebase (see Code Navigation below). Use outline to understand file structure, body to read specific symbols, def/callers/callees to trace code, Grep to find usages. Read docs/ for domain context.
  2. Plan: For non-trivial changes, use plan mode. For large features, break into reviewable stages. For a new user-facing feature, put the feature_usage telemetry in the plan as a proposed item (see docs/feature-telemetry.md) so the user can keep or drop it — don't ask separately, and don't instrument bugfixes or refactors.
  3. Execute: Follow coding patterns from skills (rust-backend, svelte-frontend)
  4. Validate: After every change, run the appropriate checks per docs/validation.md, then exercise the change on the running instance. Type-checks are not verification. Whatever the change touches, get that path actually running, and stand up whatever that takes — this is expected, not a last resort. A few examples, not a closed list: drive the UI with the Playwright MCP, run a real job of the kind you touched, restart the backend with the cargo features the path needs (backend/AGENTS.md), put a stub in front of an upstream, start MinIO for an S3 path, plant state with SQL, exercise it through the wmill CLI. If the path you need has no obvious way in, invent one rather than skipping it; docs/ carries recipes for several areas. If it needs a credential or a third-party account, ask for one rather than skipping the test or inventing a value. If you genuinely cannot exercise it, say which path went unexercised instead of implying it was verified.

Documentation

  • Validation: docs/validation.md — what checks to run based on what you changed
  • Unreleased SDK changes: docs/wac-sdk-e2e.md — exercising a client change on a real worker
  • Agent workers: docs/agent-worker-e2e.md — building and running one locally. An agent reaches the DB only through the API, so Connection::Http paths are never taken by a plain cargo run; a normal build cannot start one at all.
  • Enterprise: docs/enterprise.md — EE file conventions and PR workflow
  • Product telemetry: docs/feature-telemetry.md — when to instrument a new feature with feature_usage, and the four-step recipe. An unregistered (feature, kind) pair is dropped silently, so frontend-only instrumentation records nothing.
  • Backend patterns: use the rust-backend skill when writing Rust code
  • Frontend patterns: use the svelte-frontend skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
  • Frontend UUIDs: do not call crypto.randomUUID() in frontend code. Import randomUUID from $lib/utils/uuid instead.
  • Code review: review the current PR or branch against the shared review policy in REVIEW.md (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at .agents/skills/local-review/SKILL.md orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads .claude/skills/ (symlinked to the canonical .agents/skills/ file), Codex and Pi read .agents/skills/ directly. Invoke with /local-review in Claude Code, $local-review (or /skills selector) in Codex, or pi --skill local-review / /skill:local-review in Pi. For a Codex-driven pass that mirrors the codex-pr-review GitHub action against your unpushed work (committed + uncommitted) before you push, use /local-review-codex (.agents/skills/local-review-codex/) — same REVIEW.md policy, gpt-5.6-sol, xhigh reasoning; requires the codex CLI >= 0.144.1.
  • Domain guides: .claude/skills/native-trigger/ and frontend/tutorial-system-guide.mdc
  • Brand/UI guidelines: frontend/brand-guidelines.md
  • Domain vocabulary: CONTEXT.md — the words this codebase uses for its own concepts (step, step setting, trigger step, …). Name things the way it does.
  • CLI commands: when adding/modifying/removing a command, subcommand, option, or description in cli/src/commands/, run python system_prompts/generate.py to refresh system_prompts/auto-generated/ and cli/src/guidance/skills.gen.ts. The CLI docs the agents use to operate wmill are derived from the source — stale generated files give agents the wrong flags.
  • Session recorder: frontend/src/lib/components/recording/ is also the recorder wmill app dev --recording serves, vendored into the CLI as cli/src/commands/app/devRecorderBundle.gen.ts. After changing rawAppSnapshot.ts or rawAppRecording.svelte.ts, run bun run gen:dev-recorder from cli/ (cli/test/dev_recorder_bundle_unit.test.ts fails otherwise).

Dev Environment

In a git worktree, the ports and database below are NOT the ones to use. Each worktree gets its own backend port, frontend port and Postgres database, so the defaults in this section apply only to a plain single checkout. Discover the real values before running anything — see "Per-worktree ports and database" below.

Check whether they are already running before starting anything. In a webmux worktree ($WEBMUX_WORKTREE_PATH is set) the backend and frontend are already up in sibling tmux panes — use those, don't spawn your own. tmux list-panes -t "$(tmux display-message -p -t "$TMUX_PANE" '#{window_id}')" -F '#{pane_index} #{pane_current_command}' shows what is running; read its log with tmux capture-pane, and see backend/AGENTS.md to restart it with different cargo features. A second server started in your own shell fights the first one for the port. The commands below are for a plain checkout with nothing running.

  • Backend: cargo run from backend/ (API at http://localhost:8000)
  • Frontend: REMOTE=http://localhost:8000 npm run dev from frontend/ (port 3000+)
  • DB: psql postgres://postgres:changeme@localhost:5432/windmill
  • Login: admin@windmill.dev / changeme
  • Instance settings: navigate to /#superadmin-settings
  • Migrations: use cargo sqlx migrate add -r <name> from backend/ to create new migrations (never generate timestamps manually)

Per-worktree ports and database

In a webmux worktree the authoritative values live in $(git rev-parse --git-dir)/webmux/runtime.envBACKEND_PORT, FRONTEND_PORT, DATABASE_URL, CARGO_FEATURES, WM_DB_NAME. Every pane sources it at startup. Read that first: it is not a .env* file, so the repo's secret-file read rules don't stand in the way.

In a plain checkout, fall back to .env / .env.local (repo root) and backend/.env.

Each worktree gets a brand-new database, created and migrated from scratch by the post-create hook. It is not a copy of the main dev instance: you get the admins workspace, the admin@windmill.dev superadmin, the license key copied from the base database, and whatever the migrations seed — and none of your own workspaces, scripts, flows or apps. Create whatever a test needs. Cloning the base windmill database instead is opt-in per project via WM_CLONE_DB in .webmux.yaml; read the note there before turning it on.

The database is named after the worktree directory, not the branch (scripts/worktree-common.sh): windmill_ + the directory basename with -_, which Postgres then truncates at 63 characters. Branch hugo/win-2340-ai-agent-evals-standalone-agent-runs-and-eval-datasets sits in a worktree directory named win-2340-…, so its database is windmill_win_2340_ai_agent_evals_standalone_agent_runs_and_eval — no hugo_, and the tail chopped. Take WM_DB_NAME from runtime.env instead of reconstructing the name. Read those, or discover from what is already running:

psql postgres://postgres:changeme@localhost:5432/postgres -tAc \
  "select datname from pg_database where datname like 'windmill%'" | grep "$(git branch --show-current | tr - _)"
# the port the frontend actually proxies to (REMOTE of this worktree's vite):
for p in $(pgrep -f vite); do case "$(readlink /proc/$p/cwd)" in *"$(basename "$(git rev-parse --show-toplevel)")"*)
  tr '\0' '\n' < /proc/$p/environ | grep -E '^REMOTE=|^PORT=';; esac; done

Getting these wrong is not a cheap mistake:

  • DATABASE_URL pointed at another worktree's database silently destroys the sqlx cache. cargo run and cargo sqlx prepare both compile sqlx::query! against the live database, so the wrong one fails with relation "<your_new_table>" does not exist — and prepare deletes the whole .sqlx/ directory before it fails, leaving it gutted. Always cp -r backend/.sqlx <tmp>/sqlx_backup first (see the update-sqlx skill).
  • The frontend proxies to its own worktree's backend port, not 8000. Starting a backend on the wrong port leaves the UI up but every API call 502s, which reads like an application bug rather than a misconfiguration.
  • Kill backends by pid scoped to this worktree's cwd (readlink /proc/<pid>/cwd), never pkill -f target/debug/windmill — that kills every sibling worktree's backend. Beware that a pgrep -f "<pattern>" in a shell whose own command line contains <pattern> matches the shell itself.

Code Navigation

wm-ts-nav is an AST-aware code navigator. Use wm-ts-nav for structural queries — it skips comments/strings and understands symbol boundaries.

MUST use outline before Read on unfamiliar files — a 500-line file costs ~500 lines of context, while outline costs ~20. Then MUST use body "X" instead of reading a full file to see one function/struct. Use Read with offset/limit only when you need surrounding context that body doesn't capture.

  • refs "X" --caller instead of reading files to find which function contains each reference
  • callers "X" / callees "X" for call-graph questions

EE files (*_ee.rs, *_ee.ts, *_ee.svelte) are indexed — you can outline, def, body, refs etc. on them just like regular files.

NAV="sh wm-ts-nav/nav"
# Use --root backend for Rust, --root frontend/src for TS/Svelte
$NAV --root backend outline backend/path/to/file.rs      # file structure
$NAV --root backend def "ServiceName"                     # find definition
$NAV --root backend body "decrypt_oauth_data"             # extract source code
$NAV --root backend search "%" --parent ServiceName       # methods on a type
$NAV --root backend search "Trigger" --kind struct        # find by kind
$NAV --root backend refs "X" --file handler.rs --caller   # scoped refs with caller
$NAV --root backend callers "X"                           # who calls X?
$NAV --root backend callees "X"                           # what does X call?

Limitations — syntax-level analysis, no type inference. Use Grep instead when completeness matters (finding all usages, exhaustiveness checks):

  • refs/callers/callees can't follow re-exports, glob imports, or different import paths to the same symbol
  • Trait impls, macro-generated symbols (sqlx::FromRow), and namespace member access (ns.X) are invisible
  • callees shows all identifiers in a function body, not just actual calls

Core Principles

  • MUST outline before Read on unfamiliar files — then body or Read with offset/limit for specifics
  • Scratch stays outside the checkout. Temp scripts, data dumps, cache backups and screenshots go in the session scratch directory or /tmp, so nothing temporary can end up committed. Write rm/mv/cp as one plain unchained command: a PreToolUse hook auto-allows those when every operand is under /tmp or inside this checkout, but it defers on &&, ;, redirects, quotes and $VAR — that deferral, not the delete itself, is what turns a routine cleanup into a permission prompt.
  • Search for existing code to reuse before writing new code
  • Follow established patterns in the codebase
  • Keep changes focused — don't refactor beyond what's asked
  • Ship only the tests the PR needs. A committed test must pin behavior a future change could plausibly break, and be the smallest setup that exercises the new logic. While developing, write as many exhaustive tests and do as much manual testing as you need to convince yourself the change works — then remove that scaffolding before marking the PR ready, keeping only the essential regression guard(s). A test that merely re-exercises pre-existing behavior, or needs elaborate fixtures to assert something trivial, is scaffolding: delete it. If nothing meaningful is left to guard, ship no test rather than a ceremonial one.
  • Comments record constraints, not narration. Write a comment only for what the code can't show: why a non-obvious approach is required, what breaks if it's "simplified" away. State each invariant once, at the place where someone would break it, in ≤4 lines. Don't describe what the next line does, don't repeat the same rationale at multiple sites, and don't address the PR reviewer (justifying a change belongs in the PR description, not the code). Reference nothing ephemeral — no numbered steps from your dev flow, no "the poller / the test does X" scaffolding, no transient state that won't exist for the next reader; keep only the essential, durable rationale. Describe the code as it is, never its drafting history: "we no longer do X", "unchanged behavior", "instead of the previous approach" are meaningless to a reader who never saw the earlier iteration — before finishing, reread your comments as if the current state is the only state that ever existed.
  • Never attribute work to a specific customer, account, or "requested by a customer" in repo-tracked content (PR descriptions, commit messages, code comments, docs). Describe changes by their technical motivation instead.