Skip to content

The Build Loop

Every stk command lives under stk. Run stk --help to see them grouped by purpose.

Terminal window
uv run stk --help

Prefer bare stk? Activate the venv once per shell:

Terminal window
source .venv/bin/activate
stk run
Terminal window
uv run stk doctor
✓ env file .env
✓ app myapp.app:app
✓ secrets SECRET_KEY and password salt look random
✓ database sqlite stk.db (124 KiB)
! migrations database is behind: stk_0002 not applied
→ uv run stk db upgrade
✓ admin user 1 admin(s)
✓ assets vue 3.5.40 vuetify 4.1.6
✓ agent login disabled
✓ browser chromium ready for stk smoke
✓ shell ptpython: highlighting and completion
Usable, with the warnings above.

Every line that is not ✓ tells you the command that fixes it. A ! is a warning, a ✗ is a failure, and the exit code is 1 only on a failure, so it works in scripts. --json for tooling.

Terminal window
uv run stk new invoice

NAME is a lowercase snake_case identifier. This writes an extension package into your app (myapp/invoice/: model, views, template, and an init_app that registers the blueprint and its sidebar entry), adds it to extensions in myapp/app.py, generates the migration for the new model in the app’s lineage, applies it, and then tells you where to look. It never touches stk’s files:

Open: http://localhost:5000/invoices/ (start it with: stk run)

Pass --no-migrate if you would rather write the migration yourself, and --port if your dev server is not on 5000.

Terminal window
uv run stk verify --watch

It checks everything once, then polls for saved files and re-runs only what the change can break. A .py edit re-runs ruff, the sanity checks, and migration drift; a template, JS, or CSS edit re-runs only the sanity checks.

changed: myapp/invoice/models.py
✗ migration-drift
add_column: invoice due_date
→ uv run stk db revision -m "describe change" && uv run stk db upgrade
✓ ruff
✓ checks

Leave it in a split pane. You find out the moment you break something, not ten minutes later, and the fix is on screen.

Terminal window
uv run stk smoke

Boots a throwaway app on a free port against a temporary database, drives Chromium through the home page, login, dashboard, and admin users page, then visits every other GET page that takes no URL arguments. It fails on any of: an HTTP status of 400 or above, a console or uncaught error, a failed request, or button, chip, and alert text below 3:1 contrast with its background. That last one catches the class of bug no console error ever reports. The dashboard screenshot lands in .stk/smoke/dashboard.png; --json emits the per-page report.

Smoke needs Playwright’s Chromium. stk doctor tells you if it is missing.

Terminal window
uv run stk verify && uv run stk smoke
uv run stk report # docs/stk-report.html: routes, auth, gate results

stk verify runs ruff, the sanity checks in stk/checks.py against your app, and migration drift, and exits 1 if any fail. CI runs both stk verify --json and stk smoke --json (after the unit tests), so the pair above is the same gate locally. stk report --json prints the routes and verify results instead of writing HTML, and -o picks another output path.

stk verify and stk smoke are the gate for everything. Some changes need one more look:

You changed Also run
Templates, JS, or CSS uv run stk smoke, then look at the screenshot with real rows
Models uv run stk db check and uv run stk db current
Routes or auth uv run stk inspect routes --json, then uv run stk report
Anything with logic uv run python -m unittest discover -s tests

A 200 response and green tests do not prove that columns, labels, or buttons render. Look at the page.

Test configs subclass TestingConfig from stk/settings.py, not Config: .env loads at import, and TestingConfig pins the database, sessions, mail, OAuth, rate limits, and agent login so a developer’s settings never reach a test. Pin anything else your test depends on. Never name a test method run: on IsolatedAsyncioTestCase it replaces TestCase.run and the class silently runs nothing. Run a new test once by its full name and see it reported.

To stop a dev server, stop the process on its port (lsof -tiTCP:5000 -sTCP:LISTEN | xargs kill), not a process name pattern that can match another checkout’s server.

Agents and scripts that drive a browser can skip the login form with a short-lived token:

Terminal window
STK_ENV=development STK_ENABLE_AGENT_LOGIN=1 uv run stk browser-token create --user admin@example.com --ttl 60 --next /dashboard/

Open the printed /_test/login?token=... path: it creates a normal session and redirects to --next. The route exists only when all of these hold, and startup fails if it is enabled anywhere else:

  1. STK_ENABLE_AGENT_LOGIN=1
  2. STK_ENV=development, or the app is in testing mode
  3. the token is signed and younger than STK_AGENT_LOGIN_MAX_TTL_SECONDS
  4. the user’s email ends in @example.com

stk smoke uses the same route with a throwaway smoke@example.com admin.

Terminal window
uv run stk doctor # am I ready?
uv run stk new invoice # scaffold to a clickable URL
uv run stk verify --watch # instant feedback while editing
uv run stk smoke # real browser before shipping