Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions .agents/skills/backend/kernel-architecture/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,31 @@ src/visionset/
Every surface (UI, CLI, MCP, REST) is a **thin client of the same SDK**. If a behavior exists
in only one of them, it is in the wrong place.

## Two rules the machine enforces
## The rules the machine enforces

1. **Kernel purity** — `visionset.kernel` must never import `visionset.server`,
`visionset.cli`, `visionset.mcp`, `visionset.formats`, nor `fastapi` / `typer` / `mcp` /
`uvicorn`. Enforced by import-linter contracts in `pyproject.toml` **and** a fresh-process
test under `tests/architecture/`.
Four import-linter contracts in `pyproject.toml`, plus a fresh-process test under
`tests/architecture/`:

1. **Kernel purity** — `visionset.kernel` never imports `visionset.server`, `visionset.cli`,
`visionset.mcp`, `visionset.formats`, `visionset.wire`, `visionset.jobs`,
`visionset.inference`, nor `fastapi` / `typer` / `mcp` / `uvicorn`. The kernel decides what
exists; publication shapes, background handlers and the inference composition root all sit on
the other side of that line.
2. **Delivery clients are siblings** — `server`, `cli`, and `mcp` never import each other.
Shared logic moves down into the kernel, never sideways.
3. **Job handlers are below the surfaces** — `visionset.jobs` never imports `server`, `cli`,
`mcp`, `fastapi`, `typer` or `uvicorn`. Load-bearing under `spawn`: a worker importing
`visionset.server` would re-execute its module-level `app = create_app()`.
3. **Job handlers are below the surfaces** — `visionset.jobs` imports no delivery package.
Load-bearing under `spawn`: a worker importing `visionset.server` would re-execute its
module-level `app = create_app()`.
4. **Inference is below the surfaces and below jobs** — `visionset.inference` imports no
delivery package and not `visionset.jobs` (the download handler imports it, so the reverse
would close a cycle and put the optional runtime on the worker's spawn path).

```bash
uv run lint-imports # both contracts
uv run lint-imports # all four contracts
uv run pytest tests/architecture
```

**If a change fights either boundary, the change is wrong — not the boundary.** Never relax a
**If a change fights a boundary, the change is wrong — not the boundary.** Never relax a
contract in `pyproject.toml` to make a build pass; restructure instead.

## Where does this code go?
Expand Down
97 changes: 23 additions & 74 deletions .agents/skills/backend/python-reviewer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,97 +14,46 @@ model: opus
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, Task
---

You are an expert Python reviewer for a library-first, hexagonal codebase. Your job is to make
recently modified code clearer, more consistent, and architecturally correct **without changing
what it does**. You prefer explicit, readable code over clever compression.
You are an expert Python reviewer for a library-first, hexagonal codebase. Make recently
modified code clearer, more consistent, and architecturally correct **without changing what it
does**. Prefer explicit, readable code over clever compression.

## 1. Preserve functionality

Never change behavior — only how it is expressed. Same outputs, same errors, same side effects.
Non-negotiable. If you believe behavior is wrong, report it separately; do not silently "fix" it
inside a refactor.
If you believe behavior is wrong, report it separately; do not silently "fix" it inside a
refactor.

## 2. Enforce the architecture first
## 2. Architecture outranks style

Architecture violations outrank style. Check, in order:
Check boundaries first, against the `kernel-architecture` skill: kernel imports, sibling
delivery modules importing each other, business logic in a route/command/tool body, concrete
dependencies bypassing a port `Protocol`, formats handled by hardcoded branches instead of the
entry-point group. Project mechanics (ruff, mypy strictness, naming, import placement) are in
`python-setup`; enforce both rather than restating them here.

- Does anything in `visionset/kernel/` import `fastapi`, `typer`, `mcp`, `uvicorn`, or a
sibling delivery module? → must move.
- Do `server`, `cli`, or `mcp` import each other? → the shared part moves down into the kernel.
- Is there business logic inside a route handler, CLI command, or MCP tool? → it belongs in a
kernel service so every surface gets it.
- Is a concrete dependency (filesystem, DB, clock, uuid) used directly instead of through a
port `Protocol`? → introduce/use the port.
- Is a format handled by a hardcoded branch instead of the `visionset.formats` entry-point
group? → use discovery.

See the `kernel-architecture` skill for where each kind of code belongs.

## 3. Apply project standards

- **ruff** owns formatting and import order (line length 100, rules `E, F, I, UP, B, SIM`).
Never introduce black/isort/flake8. Remove `# noqa` that hides a real fix.
- Kernel code is fully typed under strict mypy: no untyped defs, no bare `Any`, no
`disallow_any_generics` escapes. Annotate new public functions outside the kernel too.
- Naming: `snake_case` functions/variables, `PascalCase` classes, `UPPER_CASE` constants.
Name for the domain (`ImageStore`), not the technology (`S3Client`).
- Imports at the top of the file; a function-local import needs a one-line reason.
- f-strings over `%`/`format()`. `pathlib` over `os.path`. `enum.StrEnum` over string literals
scattered across modules.

## 4. pydantic v2 and domain modelling
## 3. pydantic v2 and domain modelling

- Invariants live in the model (`Field` constraints, `@field_validator`, `@model_validator`),
not re-checked at every call site.
- Prefer `model_validate` / `model_dump` (v2 API); flag leftover v1 idioms (`parse_obj`,
`.dict()`, `class Config`, `@validator`).
- Domain models are immutable where it costs nothing (`model_config = ConfigDict(frozen=True)`)
and never carry transport concerns (HTTP status, CLI flags, MCP schemas).
- Prefer `model_validate` / `model_dump`; flag leftover v1 idioms (`parse_obj`, `.dict()`,
`class Config`, `@validator`).
- Domain models are immutable where it costs nothing (`ConfigDict(frozen=True)`) and never carry
transport concerns (HTTP status, CLI flags, MCP schemas).
- Value objects over primitive soup: an id type beats a bare `str` threaded through ten calls.

## 5. Delivery layers

**FastAPI (`server/`)**
- Handlers stay thin: validate → one SDK call → shape response. Explicit `response_model` and
status codes.
- Dependencies via `Depends`, not module-level globals.
- Domain errors are translated to HTTP at the boundary; the kernel never raises
`HTTPException`.
- Any route or response-model change requires re-exporting `openapi.json`
(`uv run python scripts/export_openapi.py`) — it is a committed contract.

**Typer (`cli/`)**
- One command = one SDK call plus presentation. Exit codes are meaningful.
- Human-readable output by default; keep machine-readable output an explicit flag.

**MCP (`mcp/`)**
- A tool is a typed mapping onto an SDK function. Tool descriptions state what the tool does
and when to use it — they are the model's only documentation.

## 6. Enhance clarity
## 4. Enhance clarity

- Early returns over deep nesting; delete dead code and premature abstractions.
- Remove comments that restate the code; keep comments that record *why* (a constraint, a
boundary, a decision).
- Consolidate related logic; extract a well-named function instead of a comment-delimited block.
- Narrow exception handling — no bare `except:`, no `except Exception` that swallows.
- Remove comments that restate the code; keep comments that record *why*.
- Extract a well-named function instead of a comment-delimited block.
- Narrow exception handling — no bare `except:`, no swallowing `except Exception`.
- Prefer the stdlib over a new dependency; a new runtime dependency ships to every wheel user.

## 7. Tests

- New kernel behavior ships with tests under `tests/kernel/`; architecture rules under
`tests/architecture/`; plugin-surface changes prove discoverability via `importlib.metadata`.
- Test behavior through public entry points, not private internals.
- Ports are tested with fakes; each default adapter has its own test.
- Never commit fixture media — `**/workspace-data/` stays ignored.

## Output format

Report as:

1. **Architecture** — boundary violations (blocking).
2. **Correctness risks** — behavior you suspect is wrong, stated as a question, not silently
changed.
2. **Correctness risks** — behavior you suspect is wrong, stated as a question.
3. **Refinements applied** — file:line, one line each.
4. **Checks run** — the actual commands and their result (`ruff`, `pytest`, `lint-imports`,
`mypy` when the kernel changed). Report failures verbatim; never claim green without running.
4. **Checks run** — the actual commands and results. Report failures verbatim; never claim green
without running.
31 changes: 11 additions & 20 deletions .agents/skills/backend/python-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,17 @@ of what a sync is for; CI uses `uv sync --locked` so it cannot happen there. The
what gets *into* uv.lock, and the lockfile governs everything after. Full rules and the escape
hatches are in CONTRIBUTING.md.

## Checks that must stay green

| Check | Command |
| --- | --- |
| Tests | `uv run pytest` |
| Import contracts (architecture) | `uv run lint-imports` |
| Kernel type-safety (strict) | `uv run mypy src/visionset/kernel` |
| Lint | `uv run ruff check .` |
| Format | `uv run ruff format .` |
| OpenAPI contract | `uv run python scripts/export_openapi.py` (commit the diff) |

Run at minimum `ruff check`, `ruff format`, `pytest`, and `lint-imports` after any Python
change. If you touched the kernel, add `mypy`. If you touched FastAPI routes or response
models, re-export `openapi.json` — it is a committed contract, a stale one is a bug.

**Never pass `-q` to pytest.** `pyproject.toml` already sets `addopts = "-q"`, and verbosity is a
counter, so a second one stacks to `-qq` — which drops the test count and the summary line and
leaves the exit code as the only signal, on a log that ends mid-progress and reads as truncated.
Plain `uv run pytest` already prints the count; where a wrapper you cannot edit has added a `-q`,
one `-v` cancels it.
## Checks

The check policy — pertinent tests while iterating, the gate once before the PR — is in
AGENTS.md `## Checks`, commands included. What is specific to Python work:

- If you touched FastAPI routes or response models, re-export `openapi.json`
(`uv run python scripts/export_openapi.py`) and commit the diff — it is a committed contract,
and a stale one is a bug.
- **Never pass `-q` to pytest.** `pyproject.toml` already sets `addopts = "-q"`, and verbosity
is a counter: a second one stacks to `-qq`, which drops the count and summary line and leaves
a log that reads as truncated. Where a wrapper you cannot edit adds one, `-v` cancels it.

## Formatting and lint

Expand Down
44 changes: 28 additions & 16 deletions .agents/skills/frontend/nodejs-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task

## Environment

- **The Node major is pinned by `.nvmrc`**, which is the single source of truth — CI's
`actions/setup-node` steps read it through `node-version-file`, and `scripts/check.sh`
refuses to run any group that needs Node under a different major. With nvm on the host,
`nvm use` reads it; there is no version to type. The refusal exists because the failure
otherwise arrives disguised: Node 26 declares `localStorage` on the global object and
leaves it `undefined` unless `--localstorage-file` is passed, so jsdom's never arrives
and eight `ui-core` tests fail with a `TypeError` on storage they never touched. The
suite passing under 26 is tracked separately (#607) — the product code is not affected,
because every storage read there goes through a guard that already answers with the
default when the access throws.
- **The Node major is pinned by `.nvmrc`**, the single source of truth — CI reads it through
`node-version-file`, `scripts/check.sh` refuses to run under a different major, and `nvm use`
reads it on the host. The refusal exists because the failure otherwise arrives disguised: a
newer Node declares `localStorage` on the global object as `undefined`, jsdom's never
arrives, and `ui-core` tests fail with a `TypeError` on storage they never touched.
- **pnpm** only (never npm, never yarn, never `npx`). Pinned via `packageManager` in
the root `package.json` — enable it with `corepack enable`. To run a binary the
workspace already has, `pnpm exec <bin>`; `npx` would *fetch and run* one it does
Expand Down Expand Up @@ -78,12 +73,29 @@ pnpm add -w -D <pkg> # root tooling only
Never hand-edit a `version` field. The repo-root `VERSION` file is the source of truth;
`pnpm version:sync` converts it to npm semver (`0.1.0.dev0` → `0.1.0-dev.0`).

## TypeScript and React conventions

- The typed API client under `frontend/ui-core/src/generated/` is **generated from the repo-root
`openapi.json`** — never hand-edit it, never redeclare a server type by hand; regenerate
(`pnpm generate:client`).
- Packages are ESM (`"type": "module"`) publishing `dist/` with declarations; public entry
points are exported from `src/index.ts`.
- React 19 with the React Compiler: no manual memoization (`useMemo`/`useCallback` for
performance), no `forwardRef` (ref is a prop), named imports only — the compiler and eslint
enforce these. `react-hooks/exhaustive-deps` is an `error` in `frontend/annotator`.
- Annotation behavior (hit-testing, geometry, undo/redo, interaction state) belongs in
`@visionset/annotator` core, never in a component — if deleting React would delete the logic,
the logic is in the wrong package. See the `annotator-core` skill for the boundary.

## Before you say it works

`pnpm -r build && pnpm -r test && pnpm -r lint` from the root. Report failures verbatim.
While iterating, run the package you touched — `pnpm --filter @visionset/<pkg> test`, and `lint`
on the same filter. The exhaustive run belongs to the gate — AGENTS.md `## Checks`. Two blind
spots worth naming:

**A green build is not a green typecheck.** Each package builds through
`tsconfig.build.json`, which *excludes test files*, while `lint` runs the full
`tsconfig.json` over everything. So `pnpm -r build` can pass while a type error sits in a test
or a test helper — which is where fixtures live, and fixtures are what a new required wire field
breaks. Run `lint` before calling TypeScript green.
- **None of the workspace commands runs a browser.** Both Playwright suites sit outside them, so
a change only chromium can observe passes everything above.
- **A green build is not a green typecheck.** Packages build through `tsconfig.build.json`,
which *excludes test files*, while `lint` runs the full `tsconfig.json`. `pnpm -r build` can
pass while a type error sits in a test helper — where fixtures live, and fixtures are what a
new required wire field breaks. Run `lint` before calling TypeScript green.
93 changes: 0 additions & 93 deletions .agents/skills/frontend/react-19/SKILL.md

This file was deleted.

Loading
Loading