mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-14 08:02:58 +00:00
* feat: let operators compose full-code apps when the workspace grants it Adds operator_settings.builder_apps, the second builder right, granted independently of flows because the two are not equally verifiable: a composed flow is checked in full server-side, while a full-code app is a browser-built bundle no check can read. The app right rests instead on forced sandbox isolation, a refusal of Viewer execution mode, and a deploy-time check of every runnable the app can reach. BuilderKind arrives with the second kind and is a mandatory argument, so no gate can mean "either" by accident: one that did would authorize an app write on a flows-only workspace. The only surfaces reading .any() are the ones that are genuinely not per-kind, the seat a membership costs and listing drafts. validate_operator_composed_app checks two surfaces rather than one, because they are not the same list: the policy's triggerables and the runnableByPath entries in the app value, which is what the deployed bundle actually resolves against. Every colon in a triggerable key is a possible split, since execute_component looks up "<component>:<path>" with an unrestricted component string. create_app_raw_source stays closed: it compiles caller-supplied sources with a bundler job on a worker, which is arbitrary code execution. Builders lose nothing, since the browser and the CLI both bundle locally and deploy through the multipart endpoints. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dsf6VC4MVLisiEoeQkgbr4 * fix: list a builder's app drafts and refuse inline code in them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to 6d15ff957df4dcd5129e1ab217eab1a13af62fbb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check a builder app as it will be stored Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: show the home list edit button to builder operators Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: pause the raw app preview for an app an operator deployed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let app builders save a full-code app's deploy settings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: record the builder app settings path, public apps and the editor preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: cache the merged EE seat usage query The EE query now reads audit_partitioned and skips service accounts, so the cached entry for its builder_apps predicate no longer matched and offline EE builds failed with no cached data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: pause the raw app preview for loaded drafts and unknown authors A draft loaded from an operator ran in the viewer's preview because the pause only checked the app's last deployer, and an author no longer in the workspace read as a developer. The pause now checks a loaded draft's owner, records it in the viewer's draft until the preview is run or deployed, and holds when the author cannot be found. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: name guest apps among the modes a builder may deploy Guest runs as the publisher with no Viewer fallback, like Publisher and Anonymous, and RestrictGuestAppDeployment refuses it per workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep a loaded draft's preview paused in AI sessions and chat edits The session draft codec and the chat's draft save dropped loaded_from, so opening a paused operator draft in an AI session, or editing it through the chat, ran it with the viewer's session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: pin builder refusals for low-code apps and unreviewed previews Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the operator mark when loading a developer's copy of a draft Loading another user's draft recorded only who saved it, so a developer's copy of an operator draft they never previewed opened unpaused for the next person who loaded it, running the operator's code with their session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let builders restore a full-code app from deployment history Restore always sends allow_kind_change, which the builder check refused even for a full-code version restored over a full-code app. Builders now drop the flag, so the locked kind check still refuses an actual conversion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: rustfmt the builder checks in jobs.rs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: record the builder kind-check race as an accepted risk Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: never trust the owner name a loaded draft carries A loaded draft's loaded_from is written by its owner, so an operator could name a developer, or leave it empty, and the next person to load the draft previewed it unpaused. A loaded draft that carries any mark now stays paused as unattributable; an unmarked one is still checked against its real owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: record viewer-supplied resources in builder apps as an accepted risk Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: say that the app right lets operators write frontend code The settings section and the docs told admins builder operators "still cannot write code", but the app right has them write the app's frontend bundle. Only backend code stays out of reach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: state the raw-source closure once and drop an unreachable preview case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf: look up only the author when deciding whether to pause the preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: pin that the app right alone bills operators as developers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: point ee-repo-ref at the EE seat test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: drop the editor preview pause now that sandboxed apps preview isolated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse a builder operator's app draft without the sandbox Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: turn the sandbox on when a builder operator opens the app editor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse builder app drafts naming runnables they cannot read Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: note the editor preview asks before minting a builder app token Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: trim the builder app check comments to the constraints Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse an app runnable whose runType is not script, flow or hub Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check builder app draft policy keys like a deploy does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: count main's half-seat service accounts in the builder billing test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: name the builder policy check for drafts as well as deploys Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let a builder app draft autosave with a stale triggerables_v2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: check the script version a builder app would run, not any version Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse viewer mode in a builder app draft as on deploy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: hold builder app drafts and deploys to one check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: say a builder's script may not have finished deploying Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: cap builder app triggerable keys before reading every colon split Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse a builder app draft that runs as someone else Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: conform builder app drafts saved by the AI chat as the editor does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: record that a deployed builder draft runs as its deployer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse builder app runnables not written as plain text Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: a builder draft may carry its builder's own run identity Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: offer builders Start with AI only when it opens a session Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name full-code apps in the builder rights confirmation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name only the granted builder rights in the confirmation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: note a wm_deployers builder may keep another run identity Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: give builders a starter app that calls no missing runnables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: seed the builder starter under the picker for builders Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: qualify the builder run identity comment for wm_deployers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: resolve builder app script hashes before opening the RLS transaction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: run the builder app tests serially to share the rights cache safely Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: give builders the builder starter on a fresh editor load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: drop a new-app load overtaken while waiting for the workspace list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep main's 401 for operators previewing without the app right Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to 5d7726c4979070ee8db4fb9e8014152872a641ba This commit updates the EE repository reference after PR #816 was merged in windmill-ee-private. Previous ee-repo-ref: 7151060bfc02908e97d9344b3820ee33a4951322 New ee-repo-ref: 5d7726c4979070ee8db4fb9e8014152872a641ba Automated by sync-ee-ref workflow. * refactor: let the app row's edit button trust its write flag Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: point ee-repo-ref at the merged EE companion Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: note the session raw-app editor does not conform builder drafts yet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
224 lines
20 KiB
Markdown
224 lines
20 KiB
Markdown
# 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.
|
|
- **External instance data tables**: `docs/external-instance-datatables.md` — the cluster Windmill
|
|
administers behind `external_instance` data tables and Ducklake catalogs: its invariants (one
|
|
lifecycle lock, managed-object markers, the setup gate, per-cluster roles, fork copy ownership)
|
|
and how to run one locally
|
|
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
|
|
- **Operator write rights**: `docs/operator-write-rights.md` — which `operator_settings` flags are
|
|
enforced rather than cosmetic, and why a right that is granted-unless-withdrawn needs `Option`
|
|
fields and a jsonb merge rather than a serde default
|
|
- **Operator builder rights**: `docs/operator-builder-rights.md` — the workspace settings that let
|
|
operators compose flows and full-code apps, what the composition check must cover, which
|
|
raw-app endpoint stays closed on purpose, and why either right costs a full seat
|
|
- **Auth surface**: `docs/auth-surface.md` — credential precedence, session/cache invalidation
|
|
scope, which token labels email their owner at expiry, how OAuth login matches `login_type`, and
|
|
that every superadmin route refuses `$WM_TOKEN`. Read before designing anything that creates
|
|
users, tokens or sessions.
|
|
- **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-6.1-sol` model and `xhigh` reasoning as the action; requires the `codex` CLI >= 0.159.3.
|
|
- **Domain guides**: `.claude/skills/native-trigger/`
|
|
- **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.
|
|
- **AI guidance** (chat prompts and CLI skills): what the AI knows about Windmill itself — how flows, scripts, apps, pipelines and resources work — is written once, in `system_prompts/base/` and `system_prompts/languages/`, and `system_prompts/generate.py` builds both the chat's `$system_prompts` exports and the CLI's skills from it. Add or change such guidance there, never inline in one consumer: a sentence only one side should see goes in a `<!-- cli-only -->` / `<!-- chat-only -->` block, and a new file reaches both through the `TOPICS` table. The chat prompt builders under `frontend/src/lib/components/copilot/chat/` add only tool plumbing and runtime values, and shared markdown names no chat tool, since it reaches every chat mode. Rerun `generate.py` after editing; `system_prompts/README.md` has the details.
|
|
- **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).
|
|
- **Raw-app policy**: `frontend/src/lib/components/raw_apps/rawAppPolicy.ts` also derives the policy the server's raw-app deploy stores, vendored into the bundle job as `backend/windmill-api/src/apps_raw_policy.gen.js`. After changing it or anything it imports, run `bun run gen:app-policy` from `cli/` (`cli/test/app_policy_bundle_unit.test.ts` fails otherwise). It rides in the job rather than being read from the CLI the job runs because the images install `windmill-cli` unpinned, so an image can carry one older than its server.
|
|
|
|
## 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 managed worktree the
|
|
backend and frontend are already up in sibling panes — use those, don't spawn your own. A second
|
|
server started in your own shell fights the first one for the port.
|
|
|
|
**herdr** is the worktree manager; `$HERDR_ENV=1` marks one of its panes. `herdr pane list
|
|
--workspace "$HERDR_WORKSPACE_ID"` lists this worktree's panes — match on `cwd`, since the servers
|
|
run from `backend/` and `frontend/` — and `herdr pane read <pane_id> --source recent-unwrapped
|
|
--lines 50` reads one's log; `herdr --skill` prints the full reference for
|
|
inspecting and driving panes and agents. The plugins that provision these worktrees live in the
|
|
private `windmill-labs/windmill-herdr` — clone it and run `./setup.sh` to install them and the
|
|
keybindings they need.
|
|
|
|
A worktree from the older **webmux** setup sets `$WEBMUX_WORKTREE_PATH` instead and is driven
|
|
through tmux: `tmux list-panes -t "$(tmux display-message -p -t "$TMUX_PANE" '#{window_id}')" -F
|
|
'#{pane_index} #{pane_current_command}'` for what is running, `tmux capture-pane` for a pane's log.
|
|
|
|
See `backend/AGENTS.md` to restart the backend with different cargo features. 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)
|
|
- **Resource types ship through the hub, not migrations.** A new type, a provider's credential
|
|
included, is a `hub/<app>/<app>.resource_type.json` in `windmill-labs/windmill-integrations`
|
|
and reaches instances by hub sync; a product name its identifier misses (`typesafe` →
|
|
"TypeSafe") is that file's `display_name`, not a word in `resourceTypeDisplay.ts`'s map. Only a
|
|
type Windmill itself defines and reads as a product object (`ai_skill`, `ai_instruction`) is
|
|
also seeded by a migration, so the feature works before a sync. For local testing, insert the row.
|
|
|
|
### Per-worktree ports and database
|
|
|
|
**`.env.local` in the worktree root holds the real values** — `BACKEND_PORT`, `FRONTEND_PORT`,
|
|
`REMOTE`, `DATABASE_URL`, `CARGO_FEATURES`, `WM_DB_NAME`. Every manager writes those fields, so it
|
|
is correct whichever one you are in. Read it with `cat .env.local` from a shell: the file-read
|
|
tool denies every `.env.*` path, and `DATABASE_URL` is written with an `export` prefix that a
|
|
`^DATABASE_URL=` grep misses.
|
|
|
|
herdr keeps nothing besides that file; its hooks write it and read it back. A webmux worktree
|
|
additionally has `$(git rev-parse --git-dir)/webmux/runtime.env`, which every pane sources at
|
|
startup and which carries the extras `.env.local` lacks: `WEBMUX_*`, `WM_CLONE_DB`,
|
|
`USE_RUST_PLUGIN`.
|
|
|
|
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 via `WM_CLONE_DB` (the `windmill.worktree` plugin's `config.env` under herdr, or
|
|
`.webmux.yaml` under webmux); read the note in `.webmux.yaml` 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. herdr creates the directory under `~/.herdr/worktrees/<repo>/` from the branch name
|
|
with each `/` turned into `-`, so branch
|
|
`hugo/win-2544-add-options-field-to-the-postgresql-resource-type` gets the database
|
|
`windmill_hugo_win_2544_add_options_field_to_the_postgresql_reso` — the whole branch name, cut
|
|
mid-word at the limit. The two drift apart as soon as the branch is renamed, and webmux named its
|
|
directories after the ticket alone, so reconstructing the name from the branch you are on is
|
|
wrong in both. Take `WM_DB_NAME` from `.env.local` instead. Read that, or discover from what is
|
|
already running:
|
|
|
|
```bash
|
|
# match on the worktree directory, truncated the way Postgres truncates it (63 - len('windmill_')):
|
|
psql postgres://postgres:changeme@localhost:5432/postgres -tAc \
|
|
"select datname from pg_database where datname like 'windmill%'" \
|
|
| grep -F "$(basename "$(git rev-parse --show-toplevel)" | tr '/-' '_' | cut -c1-54)"
|
|
# 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.
|
|
|
|
```bash
|
|
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 the paths in `rm`/`mv`/`cp` out literally: a PreToolUse hook proves each
|
|
operand, and auto-allows deletes, moves, copies and mode changes under `/tmp`, inside a git
|
|
checkout under `$HOME`, or in the Playwright MCP browser caches (`~/Library/Caches/ms-playwright`
|
|
and `ms-playwright-mcp`, `~/.cache/…` on Linux), as long as one operation stays within a single
|
|
root — a sibling checkout is a root of its own (`tar` and `unzip` stay `/tmp`-only). Chain
|
|
deletes freely, each proved on its own operands, but keep writes to one per line, name the
|
|
destination rather than a directory to drop it in, and put anything else on its own line: a
|
|
command the hook does not prove drops the whole line back to the normal permission flow. A
|
|
leading `~/` or `$HOME/` is expanded and proved; a quoted operand, any other `$VAR`, a redirect,
|
|
a `$(…)`, a relative `cd`, or a wrapper like `xargs rm` cannot be, and that deferral is what
|
|
turns a cleanup into a prompt.
|
|
- **Change files with Edit/Write, not the shell.** `sed -i`, `cat > file <<'EOF'` and inline
|
|
`python3 - <<'PY'` scripts put an edit through the PreToolUse guards and the permission
|
|
classifier, which match `Bash` and nothing else, so a routine edit arrives as a prompt. Bash
|
|
stays right for running things — tests, builds, git, one-off queries.
|
|
- 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
|
|
- **A simpler design found late is still the design.** Work already spent is not an argument
|
|
for a shape, and neither is a clean review round, a passing suite, or a long PR thread. The
|
|
signal to stop and re-derive rather than patch again is a change that keeps growing to defend
|
|
its own structure: each review finding fixing an assumption the previous fix broke, the same
|
|
class of bug reappearing somewhere new, or most of the diff being consequences of one early
|
|
choice rather than the thing you set out to do. When that happens, say plainly what the
|
|
simpler design is and what switching costs — a migration, a review cycle restarted from zero,
|
|
work discarded — and let the user decide. Do not keep paying down the harder one because it
|
|
is nearly finished, and do not present the accumulated cost as a reason to continue.
|
|
- **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.
|