Add API and web application design documents

This commit is contained in:
Jan Doubravský
2026-09-24 10:15:24 +02:00
parent 6737a27fa0
commit 91d480595d
6 changed files with 553 additions and 16 deletions
+227
View File
@@ -0,0 +1,227 @@
# API Design Guidelines
**Document Version:** v2
> **Note on Versioning:**
> - This document version is independent — reused across projects
> - **Project version** source of truth: the project manifest (`pyproject.toml`, `Cargo.toml`)
> - The **URL version** (`/v0`, `/v1`) is a separate axis and moves independently — see section 1
> - `CHANGELOG.md` uses the project version, never the URL version
## Related Documents
- **README.md** — Project overview, tool descriptions, build instructions
- **AGENTS.md** — Rules for AI assistants
- **PROJECT.md** — Project goals and current state
- **CHANGELOG.md** — Version history
- **DESIGN_DOCUMENT_LANGUAGE.md** — everything language-specific (`DESIGN_DOCUMENT_PYTHON.md`, …)
This document applies to any project that exposes an HTTP API, in addition to the design document
for the language it is written in. Where the two disagree, the language document wins on language
matters and this one wins on API surface.
---
## 1. Two Version Axes
An API carries two versions that must never be conflated.
| Axis | Where it lives | What it means | How often it moves |
|------|----------------|---------------|--------------------|
| **URL version** | the path — `/v0/...` | the contract with the consumer | almost never |
| **Product version** | project manifest, job manifest | the release of the software | every release |
The product version follows semantic versioning and the rules in `AGENTS.md`. It is what the
OpenAPI/Swagger `version` field reports, and what `CHANGELOG.md` is organised by.
The URL version is **not** semantic. It is a single integer identifying a frozen contract. A
consumer pinned to `/v0` keeps working across every product release until `/v0` is formally retired.
### 1.1 When the URL version is bumped
Bump `/v0` to `/v1` **only** on a breaking change to an existing endpoint:
- a field is removed or renamed
- a field changes type or meaning
- a default changes in a way that alters the response
- an endpoint is removed or its path changes
- a previously optional parameter becomes required
The following are **not** breaking and never justify a bump:
- adding a new endpoint
- adding a new optional query parameter
- adding a new field to a response
- performance, caching or refresh-strategy changes that leave the payload identical
### 1.2 Coexistence and retirement
When the version is bumped, the old version keeps serving until every consumer has moved:
- both versions are routed at the same time; the old one is not silently redirected
- the old version is marked `deprecated` in the OpenAPI schema
- retirement is announced in `CHANGELOG.md` at least one release before removal
- removal is a MAJOR bump of the product version
A new URL version is a cost paid by every consumer. Prefer an additive change, a new endpoint, or
an opt-in query parameter over a bump.
---
## 2. Path Naming
```
/v<n>/<CONSUMER>/<resource>
```
| Segment | Rule | Example |
|---------|------|---------|
| `v<n>` | lowercase `v` + integer, no dots | `/v0`, `/v1` |
| `<CONSUMER>` | the consuming system, as its own abbreviation | `WEXT`, `PDH`, `SALESOFFER` |
| `<resource>` | lowercase `kebab-case` | `insert-relation`, `attribute-defs` |
- **Collections are plural**, single lookups append the identifier:
`/v0/WEXT/products` and `/v0/WEXT/products/{productnr}`
- **Sub-resources nest**, they are not hyphenated into the parent:
`/v0/WEXT/assets/general`, not `/v0/WEXT/assets-general`
- **Never PascalCase or SCREAMING_CASE a resource.** The consumer segment is the only place an
uppercase abbreviation is allowed, because it is a proper name rather than a word.
- **Spell resources out.** `number-of-inserts` over `noi` — the URL is read by people who do not
know the internal acronym.
### 2.1 Endpoints outside the versioned space
Two different kinds of endpoint carry no version prefix, and the same rule does not cover both.
**Operational endpoints** — `/health`, `/cache/stats`, `/cache/reset` — are called by operators and
monitoring, never by a consumer. They change with the product version, no ceremony.
**Unversioned consumer endpoints** — the token endpoints (`/subscribe`) and any feed that predates
the versioning scheme — carry no prefix but are still contracts, and the token endpoints are the
widest of them: every delta consumer holds a token one of them issued. A breaking change to any of
these follows section 1.1 exactly. Having no version to bump is not permission to break. The
endpoint is either extended additively, or it enters the versioned space as a new path while the
old one keeps serving and is marked deprecated.
Unversioned is not unprotected. What decides is who calls the endpoint, not whether the path has a
number in it.
---
## 3. Parameter Naming
| Kind | Convention | Example |
|------|------------|---------|
| Path parameter | `snake_case` | `/products/{productnr}` |
| Query parameter | `snake_case` | `?web_only=true`, `?dict_id=42` |
| Header | `X-` prefix, `Kebab-Case` | `X-Token` |
Reserved query parameter names, to be used for nothing else:
| Name | Meaning |
|------|---------|
| `limit` | rows per page |
| `offset` | row offset |
| `since` | ISO-8601 low-water mark for a delta pull |
| `format` | response format override, where an endpoint serves more than one |
---
## 4. Code Naming
The path is the contract; the code that serves it mirrors the path mechanically, so the two cannot
drift apart.
| Element | Convention | Example |
|---------|------------|---------|
| Router variable | `<consumer>_router` | `wext_router` |
| Handler function | `<consumer>_<resource>`, `snake_case` | `wext_attribute_defs` |
| Single-item handler | `<consumer>_<resource>`, singular | `wext_product` |
| OpenAPI tag | `<Consumer> feed` | `"WEXT feed"` |
One router per consumer, declared with its own prefix and tag and included on the application in a
single place. A handler never hardcodes its own prefix.
---
## 5. Responses
### 5.1 Envelope
A list endpoint returns its rows inside an envelope carrying the paging state, never a bare array:
```json
{"table": "wext-products", "since": null, "count": 3,
"limit": 5000, "offset": 0, "has_more": false, "rows": []}
```
For XML, the same metadata goes on the root element as attributes (`limit`, `offset`, `returned`,
`hasMore`).
Where the response body is a message defined by the **consumer** rather than by us, the paging
metadata stays outside that message — the body must remain exactly what the consumer schema expects.
### 5.2 Paging
**Every list endpoint is paged.** An endpoint with no cap is a denial of service waiting for the
first client that omits a filter.
- `limit` has a documented default and a hard maximum, both configurable by environment variable
- the maximum is chosen by measurement — response size, and the time a request holds any shared
lock — not by guess
- `has_more` is reported so the client knows to continue
- deep offsets over a very large table are not the intended access path; document the alternative
(walking `since` windows) instead
### 5.3 Delta feeds
An endpoint whose source has a reliable change column may support incremental pulls:
- `?since=<ISO-8601>` returns only rows changed strictly after that instant
- a subscription token in `X-Token` supplies, and then advances, a server-tracked watermark
- an explicit `?since` always wins over the token value
- a malformed `?since` is rejected with **422** before it reaches the query
**A snapshot message is never a delta.** If a response states a complete set as of a timestamp,
answering with only the changed rows tells the consumer that the set shrank to those rows. Such an
endpoint is a full snapshot and says so in its description.
### 5.4 Status codes
| Code | When |
|------|------|
| `200` | success, including a legitimately empty collection |
| `302` | redirect to an external asset |
| `400` | the request is missing something it needs |
| `403` | unknown or revoked token |
| `404` | a named single entity does not exist |
| `422` | a parameter is present but malformed |
| `500` | internal failure |
An empty collection is `200` with an empty list, not `404`. `404` means *this identifier does not
exist*, which only a single-entity lookup can determine.
Error bodies carry a short, generic `detail`. **Internal detail — driver strings, connection
parameters, SQL, stack traces — goes to the log only, never to the client.**
---
## 6. Documentation
- Every endpoint declares a `summary` and a `description`. The description states where the data
comes from, whether the endpoint is a delta or a snapshot, and any non-obvious default.
- The OpenAPI `version` field is read from the project manifest, never hand-maintained.
- The interactive schema (`/docs`, `/openapi.json`) is the reference for consumers. Prose that only
restates it belongs nowhere.
- A new consumer feed is announced in `CHANGELOG.md` under the product version that ships it.
---
## 7. Testing
In addition to the testing rules in the language design document:
- every endpoint has at least one test asserting the **shape** of its response
- the query an endpoint builds is asserted, so a changed filter cannot pass unnoticed
- tests stub the data-access layer; they never require a live database
- the empty-result path is tested explicitly — it is the one most often wrong
+37
View File
@@ -7,6 +7,43 @@ ships documents, so it uses the same `v#` scheme as the documents themselves.
Individual guideline documents carry their own `Document Version` header, and the skills carry Individual guideline documents carry their own `Document Version` header, and the skills carry
`metadata.version`; both are independent of the revisions below. `metadata.version`; both are independent of the revisions below.
## v7 — 2026-09-24
### Added
- **`API/DESIGN_DOCUMENT_API.md` v2** — guidelines for any project that exposes an HTTP API,
carried in addition to the language design document. It settles what was previously left to
precedent: the URL version (`/v0`) and the product version are separate axes, the first naming a
frozen consumer contract that moves only on a breaking change, the second following semantic
versioning per release. Also fixes path naming
(`/v<n>/<CONSUMER>/<resource-in-kebab-case>`, resources spelled out and never PascalCase),
parameter and handler naming, the response envelope and its reserved paging keys, when an
endpoint may be a delta and when it must stay a full snapshot, status-code meanings (an empty
collection is `200`, not `404`), and the endpoint-level testing floor.
- `API/DESIGN_DOCUMENT_API.md` registered in `check_versions.py` and in the project-type table, so
`--sync` propagates it like every other guideline document.
- Section 2.1 was rewritten before release (v1 → v2). The v1 draft filed `/subscribe` under
endpoints that are "not part of any consumer contract" and declared the whole group free to
change with the product version. It is the opposite: the token endpoints are the widest
contract in the API, every delta consumer holds a token they issued, and the unversioned
legacy feeds are contracts too. The section now splits operational endpoints from unversioned
consumer endpoints and subjects the second group to section 1.1 — having no version to bump
is not permission to break.
- **`WEB/DESIGN_DOCUMENT_WEBAPP.md` v1** — guidelines for a server-rendered web interface driven
over a websocket (NiceGUI on FastAPI), carried in addition to `DESIGN_DOCUMENT_PYTHON.md`. Covers
layers, one connection and one lock per service, page and dialog structure, where the language
belongs, tables as the main idiom (including the slot trap), hand-written canvases, theming,
performance, errors and notifications, authentication, testing, and a checklist for a new screen.
### Changed
- **`AGENTS.md` v8 → v9** — the table mapping project types to design documents is gone. Every
`DESIGN_DOCUMENT*.md` in the project root now applies, with the suffix saying what it covers (a
language, application vs. library, or a part of the system such as `_WEBAPP`), so the list can no
longer go stale. Where two design documents disagree, the more specific one wins for what it covers.
- The `MASTERS` comment in `check_versions.py` no longer claims a design document is always named
after a language — it is named after what it scopes, which may be a cross-cutting concern.
---
## v6 — 2026-08-18 ## v6 — 2026-08-18
### Added ### Added
+11 -14
View File
@@ -1,24 +1,21 @@
# AI Agents - Project Rules # AI Agents - Project Rules
**Document Version:** v8 (independent, incremented on structural changes) **Document Version:** v9 (independent, incremented on structural changes)
Language-agnostic rules for AI assistants (Claude Code, Cursor, Copilot, etc.). Language-agnostic rules for AI assistants (Claude Code, Cursor, Copilot, etc.).
Everything language-specific — package manager, formatter, linter, test framework, Everything specific — package manager, formatter, linter, test framework, logging library,
logging library, line length, project layout — lives in the matching design document: line length, project layout, interface conventions — lives in the design documents.
| Project type | Design document | **Find them, do not look them up in a list:** every file matching `DESIGN_DOCUMENT*.md` in the
|--------------------|------------------------------------| project root applies to this project. The suffix says what it covers — the language
| Python application | `DESIGN_DOCUMENT_PYTHON.md` | (`DESIGN_DOCUMENT_PYTHON.md`), whether it is an application or a library
| Python library | `DESIGN_DOCUMENT_PYTHON_MODULE.md` | (`..._PYTHON_MODULE.md`), or a part of the system that has conventions of its own
| Rust application | `DESIGN_DOCUMENT_RUST.md` | (`..._WEBAPP.md`). A project carries as many as it needs and nothing collides, so no list here
| Rust library | `DESIGN_DOCUMENT_RUST_LIB.md` | needs maintaining and none can go stale.
| Godot | `DESIGN_DOCUMENT_GODOT.md` |
The language is part of the file name, so a project that mixes languages carries one design document Where this file and a design document disagree, **the design document wins**. Where two design
per language and nothing collides. documents disagree, the more specific one wins for what it covers.
Where this file and a design document disagree, **the design document wins**.
## First-time setup ## First-time setup
+4
View File
@@ -38,6 +38,9 @@ Dokumentace/
│ ├── DESIGN_DOCUMENT_GODOT.md # Guidelines for Godot projects │ ├── DESIGN_DOCUMENT_GODOT.md # Guidelines for Godot projects
│ └── .gitignore # Godot-specific ignore rules │ └── .gitignore # Godot-specific ignore rules
│ │
├── API/ # API design guidelines
│ └── DESIGN_DOCUMENT_API.md # Guidelines for projects exposing an HTTP API
│
├── Project template/ # Reusable files for new projects ├── Project template/ # Reusable files for new projects
│ ├── CHANGELOG.md # Changelog template │ ├── CHANGELOG.md # Changelog template
│ ├── PROJECT.md # Project documentation template │ ├── PROJECT.md # Project documentation template
@@ -67,6 +70,7 @@ Where the two disagree, **the design document wins**.
| Rust (application) | `Rust/DESIGN_DOCUMENT_RUST.md` | | Rust (application) | `Rust/DESIGN_DOCUMENT_RUST.md` |
| Rust (library) | `Rust/DESIGN_DOCUMENT_RUST_LIB.md` | | Rust (library) | `Rust/DESIGN_DOCUMENT_RUST_LIB.md` |
| Godot | `Godot/DESIGN_DOCUMENT_GODOT.md` | | Godot | `Godot/DESIGN_DOCUMENT_GODOT.md` |
| Any project exposing an HTTP API | `API/DESIGN_DOCUMENT_API.md` (in addition to the language document) |
### Starting a new project ### Starting a new project
+270
View File
@@ -0,0 +1,270 @@
# Web Application Guidelines
**Document Version:** v1
> **Note on Versioning:**
> - This document version is independent — reused across projects
> - It describes a **server-rendered web interface driven over a websocket** (NiceGUI on FastAPI); the language rules stay in `DESIGN_DOCUMENT_PYTHON.md`
> - Where this document and the language document disagree about the same thing, the language document wins for language matters, this one for interface matters
## Related Documents
- **README.md** — Project overview, tool descriptions, build instructions
- **AGENTS.md** — Rules for AI assistants
- **DESIGN_DOCUMENT_PYTHON.md** — Language rules: tooling, typing, logging, layout
- **PROJECT.md** — Project goals and current state
---
## 1. What This Stack Is
One Python process serves the pages, holds the session state and talks to the database. The browser
renders components and sends events back over a websocket; there is no separate front-end build, no
REST layer between the screen and the data, and no client-side router.
That buys a great deal — a screen is a function, a test can call it, a value is one attribute access
away — and it costs three things that shape every rule below:
| Property | Consequence |
|---|---|
| The server holds the UI state | A slow call blocks the screen, and often every screen |
| Events cross a websocket | Anything the browser does must reach the server to have happened |
| Components are a third-party library | Its escape hatches (slots, CSS layers) follow its rules, not yours |
---
## 2. Layers
```
core/ decisions: models, rules, calculations — no UI, no database
repository/ storage: one class per table or aggregate — no UI
services/ one wired-up object: connection + repositories + shared services
web/ screens: build components, read services, write back
api/ HTTP routes for machines
```
Dependencies point one way: `web → services → repository → core`. A core module never imports the
web layer, a repository never renders, a screen never opens a connection.
**Every screen has a pure counterpart.** What a screen *decides* — which rows, in what order, which
cells may be edited, what a value reads as — belongs in a core module that takes data and returns
plain structures. The screen is then the part that cannot be unit tested but also barely has logic
in it. This is the single most useful rule in this document: it is what makes a complicated screen
testable without a browser.
---
## 3. Services: One Connection, One Lock
The whole point of a server is that the database connection exists once, not per user:
- One process-wide `Services` object, built lazily on first use, holding the connection and every
repository built on it
- Database drivers are usually **not** safe for concurrent use, so every call goes through one lock:
`svc.run(fn, *args)`
- A screen never touches the driver, only `svc.run(...)`
The lock has a consequence worth writing on the wall: **a slow query freezes the application for
everybody**, not just for the person who triggered it. See §9.
Replace the lock with a connection pool before the user count makes this untrue — the call sites do
not change.
---
## 4. Page and Screen Structure
- **Entry point** starts the server, installs logging before anything that can fail, and prints every
address it can be reached at
- **One page per URL**: a sign-in page and a main page are usually enough; everything else is a
dialog opened from the main page
- **A screen is a function**: `open_<thing>(svc, ...)` builds its components and returns. No global
state, no singletons per screen, nothing left registered when it closes
- **State lives in one dict per page**, not in module globals: which object is selected, which tab,
which ticks. A dict, because it is shared with the nested functions that redraw parts of the page
- **Redraw in units, not wholesale.** Mark the parts that change as refreshable and refresh those;
rebuilding the page loses scroll position, focus and any transient state the browser holds
### Dialogs
Dialogs are for decisions and rare edits — a confirmation, a picker, managing relations. They are not
where everyday data is edited: a value a person changes ten times a minute belongs in the table they
are already looking at (§6).
---
## 5. Where the Language Belongs
Pick one language for the interface and one for the code, and keep them apart:
- Code, comments, identifiers, log messages, commit messages: **English**
- Labels, notifications, validation messages the user reads: **the user's language**
Mixing them inside one screen is what happens by accident when a feature is written in a hurry;
mixing them across screens is what the user notices.
---
## 6. Tables Are the Main Idiom
For a data application, the table is the interface. Two rules:
**Build rows in core, render in web.** A core function turns records into rows and cells — text,
whether it can be edited, where it came from, whether it is held by hand. The screen turns that into
component properties. Sorting, grouping and "show only what differs" then have unit tests.
**Edit in the cell, not in a dialog.** An input in the cell, written when the field is left or Enter
is pressed — never per keystroke, which turns one edit into a dozen writes.
### The slot trap
Component libraries let you replace a cell with your own markup (a "slot"). Framework event bindings
written inside such a slot **may never fire** — the slot is compiled in a scope that is not the one
the binding expects. Verified in a browser: the handler was never reached, with no error anywhere.
The reliable pattern is to bypass the framework's event system for that one case:
- A plain `<input>` with a **native** attribute handler (`onblur`, `onkeydown`) — compiled by the
browser itself
- The handler calls the framework's own "send an event to the server" function with a small payload:
which panel, which record, what value
### Addressing the panel
An event that carries an id, and a server-side registry of live panels, is the general shape for
anything the browser drives:
```
mount(panel_id, handler) # once per page build, outside any refreshable
emit → {panel: <id>, …} # from the browser
dispatch → registry[id] # server routes it, ignores unknown ids
release on disconnect # a browser that goes away stops answering
```
Two rules that are easy to get wrong:
- **Subscribe once per page, never inside the refreshable** that redraws the table. A subscription per
redraw saves a typed value once per redraw that has ever happened
- **Put the client id in the panel id.** The registry is process-wide; without it, a second person's
typing is handed to the first person's screen, written as them
---
## 7. Custom Canvases and Other Hand-Written Markup
When a component library has nothing for what you need — a node editor, a diagram — hand-write the
markup and keep the browser side thin:
- **The browser reports, the server decides.** A gesture (a wire dropped, a box let go) is sent as an
event; the state changes on the server, where a test can reach it. Only what is impossible to do
server-side — the box following the pointer — stays in the browser
- **One id per canvas**, as in §6, so two open canvases cannot answer for each other
- **Attach with a retry**: markup and script arrive in separate messages, so the script's entry point
polls a few times for its own element instead of assuming it is there
- **Sanitisers strip `<style>` and `<script>`.** If the framework sanitises raw HTML by default, an
opt-out is required for hand-written markup — and then **every value interpolated into that markup
must be escaped where it is built**. Opting out without escaping is how a catalogue name becomes an
injection
- **Pan and zoom belong to the browser** and must survive a redraw; recomputing a fit on every edit
throws away what the person set up
---
## 8. Theming
- **One module holds the palette** and applies it once per page: the framework's colour variables plus
a stylesheet. Screens use the names, never the hex codes
- **One selection style** for every pick-one list in the application. Five copies of "the selected row
is light blue" drift apart within a month
- **A brand colour is a fill, not small text.** A mid-tone brand colour on white fails contrast at
11 px. Filled buttons get it with white on top; flat and outlined buttons keep near-black labels
- **Component libraries fight back with `!important`.** When an override does not take, the cause is
usually CSS layers: for important declarations, a layered rule beats an unlayered one, and the
earlier layer wins. Put overrides in a layer the framework declares *before* its own important
layer, rather than escalating specificity
- **Status colours are not brand colours.** Error, warning and success carry meaning and stay
recognisable even when the palette changes
- **Static assets** (logo, images, icon) are served from one directory at a fixed URL and bundled by
the packaging step. Check that the packaging spec includes them; a page that works from source and
is bare in the executable is the default failure
---
## 9. Performance
The lock in §3 turns any slow call into a global freeze, so the interface layer owns performance:
- **Never walk a tree node by node.** One recursive query instead of one query per node. A real case
from this project: a depth lookup cost 8 491 round trips and 22.5 s, ran on every click, and made
the whole application unusable while it did
- **One round trip per screen refresh** is the target; a handful is acceptable; per-row queries are a
defect
- **Measure before and after with a query counter**, not with impressions. Wrap the database's execute
method, count calls, print the time. It takes ten minutes and it is the only way to know
- **Page long lists** and cap what a screen renders (rows, columns, hits) with an explicit, visible
limit — "showing the first 200 of 7 890" — rather than a silent truncation
- **Cache what is recomputed per redraw**, such as compiled patterns, at module level. Anything
derived from code, not from data, can be computed once per process
---
## 10. Errors, Refusals and Notifications
- **Refuse rather than guess.** A value that cannot be parsed, a move that would create a cycle, a
rename that would orphan something: say what is wrong, change nothing
- **Every write says what it did**, as a short notification: what was saved, how many objects it
reached, what it left alone
- **One failure must not cost the whole screen.** A record that will not load is dropped with a
logged warning; the other sixty still render
- **Log for the operator, notify for the user.** They are different messages: the log has ids and
counts, the notification has the sentence
---
## 11. Authentication and the Machine Interface
- Every page checks the session and redirects to sign-in; every role check happens **on the server**,
never by hiding a menu item
- HTTP routes for machines live beside the pages in the same process and authenticate per request
(HTTP Basic is enough behind a firewall); they never rely on the browser session
- **A health endpoint** is worth more than it costs: unauthenticated it answers "is the server up",
authenticated it answers "is the data in the state it should be" — schema version, drift, coverage.
It is where a broken deployment announces itself
- Turn on the framework's API documentation and make the addresses visible in the interface, showing
**the address the browser used**, not `localhost`, which on anybody else's machine is their own
computer
- Secrets come from the environment; nothing secret is ever logged
---
## 12. Testing
Three levels, each for what only it can see:
| Level | Covers | Cost |
|---|---|---|
| Core functions | Rows, ordering, what is editable, every rule | Milliseconds |
| Simulated client | The screen assembles, a click reaches its handler, a dialog opens | Fast, no browser |
| Real browser | Anything that only exists in a browser: gestures, styles, native events | Slow, opt-in |
- **The simulated client cannot see into component properties.** A table's contents live in its props,
not in the page text, so assert on the element's `columns` and `rows`, not on visible text
- **A real-browser test is worth writing for the parts that burned you.** Drive it over the browser's
debugging protocol: sign in, click, type real keystrokes, read the DOM back. Two bugs in this
project — an unstyled canvas and a dead event binding — were invisible to every other level
- **Skip, do not fail, when no browser is present** (`CDE_BROWSER=none`), so the suite still runs on a
machine that has none
- **Never verify against production data without restoring it.** If a check must write, write one
field, read it back through the database, restore it, and say so in the report
---
## 13. Checklist for a New Screen
1. What does it decide? Put that in a core module with tests
2. What does it show? Build it from the core structures; one refreshable per part that changes
3. What does it write? One function per write, each reporting what it did
4. Does the browser drive anything? Give it an id, subscribe once, release on disconnect
5. How many queries does one refresh cost? Count them
6. Does it use the palette, or does it invent a colour?
7. Which of the three test levels does it need? At least the first
+4 -2
View File
@@ -34,8 +34,9 @@ import sys
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
# Guideline documents owned by this repository, mapped to their master copy. The language is part of # Guideline documents owned by this repository, mapped to their master copy. Design documents are named
# every design document name, so a project may carry several of them without any collision. # after what they scope - a language, or a cross-cutting concern such as the API surface - so a project
# may carry several of them without any collision.
MASTERS: dict[str, str] = { MASTERS: dict[str, str] = {
"CLAUDE.md": "Claude/CLAUDE.md", "CLAUDE.md": "Claude/CLAUDE.md",
"AGENTS.md": "Claude/AGENTS.md", "AGENTS.md": "Claude/AGENTS.md",
@@ -44,6 +45,7 @@ MASTERS: dict[str, str] = {
"DESIGN_DOCUMENT_RUST.md": "Rust/DESIGN_DOCUMENT_RUST.md", "DESIGN_DOCUMENT_RUST.md": "Rust/DESIGN_DOCUMENT_RUST.md",
"DESIGN_DOCUMENT_RUST_LIB.md": "Rust/DESIGN_DOCUMENT_RUST_LIB.md", "DESIGN_DOCUMENT_RUST_LIB.md": "Rust/DESIGN_DOCUMENT_RUST_LIB.md",
"DESIGN_DOCUMENT_GODOT.md": "Godot/DESIGN_DOCUMENT_GODOT.md", "DESIGN_DOCUMENT_GODOT.md": "Godot/DESIGN_DOCUMENT_GODOT.md",
"DESIGN_DOCUMENT_API.md": "API/DESIGN_DOCUMENT_API.md",
} }
# Names used before the language became part of the file name. A project still carrying one of these # Names used before the language became part of the file name. A project still carrying one of these