~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
339 lines · 19.5 KB Raw History
# Working on knotless

For every agent working here: CLAUDE.md is this file, imported.

Use `rustup run stable cargo` (the Justfile's `cargo`), not a system cargo.
Node is the version `.node-version` names, CI's: the harness's PDS loads a
better-sqlite3 built for it, and refuses another major.
Tests run against the npm reference PDS through `harness/`, never pds.js.

## Running tests

Build and test on the runner, not on this machine: several agents
building here at once have brought it down. Open the pull early, push to
its branch as you go, and read what its checks say with `knotless run list
<repo> --pull <uri> --wait <secs> --log`. Each push runs warm from
`main`'s caches (see What CI checks): `check` (fmt and clippy) in a minute
or so, `test` in a few, running only the test binaries the push changed.
`just fmt` (rustfmt, and oxfmt for the pages, the harness and the docs
site) costs nothing, so run it before pushing; Claude Code runs it on
each file it writes (`.claude/settings.json`).

Build here only for a loop the runner is too slow for, such as one test
being fixed, and then in one checkout at a time, with
`CARGO_BUILD_JOBS=2`, and `just pds` stopped once done. Never run `just
cov`, `just lint`'s clippy over the workspace, `shots-linux`,
`shots-check` or `shots-live` here: their checks run them. What follows
is for that loop.

- `just pds` in the background first: test binaries then share that PDS
  instead of each starting one.
- `just changed` names the files changed since tests last passed and the
  tests that cover them; `just t-changed` runs those and marks the pass.
  `just t <cargo test args>` runs any selection on the shared PDS.
- Working on the web pages (`web/`): `web/AGENTS.md`, loaded with
  them, says how they are built, shot and checked, and to load the `ui`
  skill before changing one.
- Done: all seven of the pull's checks pass. When one fails in a way its
  log and pictures do not explain, run that one thing here, alone
  (`COV_CLEAN=1` rebuilds everything first, for a cov miss that makes no
  sense).

`cargo fmt` can put a `?` on a line of its own, which coverage then counts
as a line never run: bind the value first, or use a named function for a
row mapper, rather than lowering the gate.

## Commands and tools

A command is `knotless <group> <action> [<url>] ...`: the group a singular
noun (`repo`, `pull`, `branch`), then the action. Only `login`, `logout`,
`upgrade` and `status` stand alone. An opposite is `--undo` on the action
(`repo star --undo`), never an `un-` command. A verb that works on any pull
or issue by `at://` URI exists under both `pull` and `issue`, on one
handler.

A command and its MCP twin land together, in one pull. The tool is
`<group>_<action>` (`-` as `_`), and takes the command's arguments: a
parameter is the flag's name (`--keep-branch`, `keep_branch`), positionals
as the usage line names them, `--undo` as `undo`. One tool per action: a
tool that folds several commands is split to match. Every command has a
tool and every tool a command, but those that act on this machine, which
are CLI-only: `login`, `logout`, `upgrade`, `repo mirror`, `workflow pin`
and `release download`.

Renaming a command keeps its old name working, hidden from the usage, for
one release: the deploy host and shared steps run an installed binary.
`docs/reference/cli.md` and `docs/reference/mcp.md` are laid out by group,
one section each, and `docs/reference/actions.md` lines the two up: a new
command or tool goes in its group's section.

## atproto's primitives

Keys, JWTs, base32, TIDs, service auth, DID documents' keys and services,
handle syntax, and the spaces alpha's set hash, MACs, STAR archives and
tokens come from the `atproto` crate, in its own repository
(knotless.dev/chadtmiller.com/atproto), published to that repository's
Cargo registry, which `.cargo/config.toml` names. A primitive
knotless needs that the crate lacks, or a fix to one, goes there, as a
change to that repository and a new version, before knotless moves to it.

## Lexicons

A new or changed schema under `lexicons/` follows `lexicons/AGENTS.md`
and passes `just lex`.

## Queries

A query of the index (`crates/knotless-serve/src/index.rs`) is planned
at prod's size as the tests run it, and `test` fails on one that scans
or sorts a table that grows with the network:
`crates/knotless-serve/AGENTS.md` says how to fix or mark one.

## Docs

`docs/` is a small wiki: design pages explain why, the guide shows how,
the reference lists what. A change that alters what a page says updates
that page in the same pull:

- storage records, push, claims, compaction: `docs/design/storage.md`
- gateway, delegation, `pushRefs`, `createCommit`: `docs/design/gateway.md`
- pulls, issues, states, merging, moderation: `docs/design/collaboration.md`
- the merge queue: `docs/design/merge-queue.md`
- knotless-run: `docs/design/runner.md`, `docs/reference/runner-internals.md`
- knotless-triage and `triage.rs`: `docs/design/collaboration.md` (Labels),
  `docs/reference/cli.md`
- knotless-serve, its cache, appview and pages: `docs/design/serve.md`,
  and its methods in `docs/reference/api.md`
- names, redirects, transfers, commit authors: `docs/design/identity.md`
- spaces and private repositories: `docs/design/access.md`
- releases, `publish`, versioning: `docs/design/packages.md`
- a flag, environment variable or command's output: `docs/reference/cli.md`
- an MCP tool: `docs/reference/mcp.md`
- a workflow key: `docs/reference/workflows.md`
- who can do what: `docs/design/security.md`

To write or rewrite a page, or to check one against the code, use the
`docs` skill (`.claude/skills/docs/SKILL.md`): facts from the code first,
then the page from the reader's questions. `just docs` checks links,
anchors and diagrams (`npm ci` in `web/` first, for Pele).

The pages also serve the docs as a site at `/docs`: `docs-site/`, a
Starlight site that reads `docs/` as it is, each page's `#` heading its
title, its links made the site's, and its sidebar in `astro.config.mjs`.
A page added to `docs/` goes in that sidebar too. `just docs-site` shows
it as you write.

## Versioning

A pull that changes what those who use knotless see (a command, a flag,
what the client does) adds a change file under `.changeset/`, as
`knope document-change` writes one (https://knope.tech): `default:
patch`, `minor` or `major` in its front matter, then a heading and a
line for them. `knope release` turns them into the version and
CHANGELOG.md. Commit titles stay prose: no conventional commits. The
`check` check fails a pull that changes `crates/knotless/src` or
`crates/knotless-mcp/src` and adds no change file, unless its body says,
on a line of its own, `Changeset: none`: for a change no one using
knotless sees.

## What CI checks

The workflows in `.knotless/workflows/` run on pushes to `main` and on
pulls into it or into a `feature/` branch (see Landing a change), each
pull at its head merged with its base as it is now, and again whenever
either moves; a push to any other branch runs nothing.
On a pull, `web`, `lexicons`, `shots`, `docs` and `e2e` run only when it
changes what they read (each file's `paths`); otherwise each passes at
once, noted as skipped. A push to `main` runs all seven, and `check` and `test` run
on every pull. `main` deploys only once they all pass:

- `check`: `cargo fmt --check` and clippy, warnings denied; on a pull,
  a change file for a change to the client (`harness/changes.sh`).
- `test`: `just cov`'s 100% gate, over the spaces alpha, and the stock
  PDS for the few test binaries that reach what a PDS without spaces does
  (`COV_STOCK` in harness/cov.mjs),
  and that every XRPC answer the serve tests received, kept with a
  stored run or fresh, is as its lexicon says (`harness/answers.mjs`
  over what `harness/cov.mjs` gathers), and that no query the index
  ran scans a table that grows with the network, as SQLite would plan
  it at prod's size, or sorts many of its rows (`harness/plans.mjs`; see Queries). A test binary that
  passed before is not run again: when it, the workspace binaries it ran
  and the harness are the same, its stored profile counts instead
  (`COV_STORE` in harness/cov.mjs), so a change runs only the binaries it
  changes. A pull's run, its pre-flight, goes against both nets and
  holds the gate, as a batch's does, so a line nothing reaches fails
  the pull rather than a batch. A binary that fails is run once more; one that then passes is
  logged `FLAKY: <net> <binary> <test>` with its first failure: fix it.
  One that fails again only in tests `harness/quarantine.json` names,
  until the day it gives, passes too, logged `QUARANTINED: <net> <binary>
  <test>`, and is run alone until it passes, three times at most, so
  the lines only it reaches still count: a test that fails now and then
  is put there while it is fixed, and taken out with the fix.
- `web`: oxfmt's format, the component layers and svelte-check.
- `lexicons`: `just lex`, against main's schemas where the branch left main.
- `shots`: `just shots-check` in Playwright's Linux image: each page whose
  HTML differs from the base's, shot as the base draws it and as the
  change does. Its pictures (before, after, and where they differ) of
  each that moved, or is new, are the check's outputs, and one the pull's
  body does not name on a `Shots-moved:` line fails it.
- `docs`: `just docs`: every relative link and anchor in the docs lands,
  and every Mermaid block draws with Pele; and the site at `/docs`
  builds.
- `e2e`: `just e2e`: the pages used in Chromium over a world seeded for
  the run as the shots' is, served as a gateway behind the harness's
  https front: logging in through the PDS's approval page, then every
  write the pages make, from starring and commenting to merging,
  settings, organizations, transfers and deleting an account's data,
  as two accounts, each read back on the page and from the PDS
  (`harness/e2e/flows.spec.mjs`); and beside it a private repository's, over
  the spaces alpha, each read back from its space and as not there to
  anyone else (`private.spec.mjs`, `just e2e-private`). A flow that
  fails leaves its page among the check's outputs. A new control that
  writes gets a flow there, and in the private flows too when a private
  repository reads it back differently.

A push to `main` at the same tree as a pull's run that passed, as
`knotless pull merge` leaves it, does not run these again: the runner files
them as passed at once, each log naming the pull's run. deploy.yml still
runs, and waits for them as ever.

`ios.yml` is not one of the seven either: on a pull that changes `ios/`, the
lexicons or the recorded answers, it reads those answers with the iOS app's
models, checks what its merge section says, and holds its writes to their
lexicons. Xcode needs macOS, so the app itself is built by hand
(`ios/README.md`), and no deploy waits for this. `just ios-look <screen>` shows
a screen in any state, as `just look` does a page: run it before pushing a
change to one.

`scenarios.yml` is not one of the seven: it runs the scenario tracks on
main only when the owner asks for it, as `knotless run rerun <repo> --ref
refs/heads/main --workflow scenarios -e TRACKS="team"`, and costs model
usage, so ask before starting one. Its report is the end of its log.
`look.yml` is too, at any branch: `just look` for an agent with no
checkout, its pictures the check's outputs (the `ui` skill says how).
`docs-audit.yml` is the same kind: Claude Code sessions checking each
docs page against the code (`-e PAGES="design/storage.md"` for some),
whose report says what each page gets wrong or leaves out.

What `just lint`, `just cov` and `just lex` pass here passes there. A
check's result and log: `knotless run list <repo> --pull <pull uri>`
(`--wait <seconds>` waits for it; `--log` adds each log's end; `--json`
gives each step), or the `run_list` tool over MCP. Its pictures:
`--images <dir>` saves them to open, and over MCP `images: true` returns
them to look at. A pull that moves pages on purpose names them in its
body, on a line of their own, `Shots-moved: home, pull-open` (the names
its check gives; `all` for every page), then runs that check again
(`knotless run rerun <repo> --pull <pull uri> --workflow shots`): the body is read as the check
runs, and editing it moves nothing. A batch reads the line from the
squashed pulls' bodies. One moved by mistake is a fix to make.
A change to a page in a state the check does not draw, as a menu or
dialog open, an edit box, a filter that empties a list or a button
busy, shows that state in the pull: `just look`'s pictures of it,
desktop light and phone dark, each given to `knotless pull create` (or
`knotless pull edit`) as an `--image`. Only the states the change touches:
the check already shows what moved of the pages it records.

Prod's health: `knotless status https://knotless.dev --repo
chadtmiller.com/knotless` (`--json` for all of it): its feeds, index and a
mirror, from `/_status`; deploy/README.md says what each means.

## Landing a change

Open a pull rather than committing to `main`: push the work to a branch
named for it (`web/mermaid`, `serve/jetstream`), then `knotless pull create
knotless://chadtmiller.com/knotless main knotless://chadtmiller.com/knotless
<branch> "<title>" -m - < body.md`, the title and body written from
`.knotless/pull.md` (`knotless pull template <repo>`); `-m -` keeps the
shell off the Markdown. The body opens with a short paragraph a person
would write, what the pull is for and whether it needs a reader's
attention, and folds the full write-up below it in `<details>`, under a
`<summary>` naming the model and effort that wrote it, as the template
lays out. It is merged into `main` and deployed from there. A body saying `Fixes <issue>` (or Closes, Resolves), the issue by
its knotless.dev page or `at://` URI, closes that issue as it merges. Once its own checks pass, land it with `knotless pull merge <repo>
<pull uri> --queue`, which returns at once: the runner lands queued
pulls in batches, main with each squashed onto it as a commit of its
own, checked once at the batch's tip, so many landing together cost one
run. It takes a pull queued in within half a minute, and checks the next
batch on top of the one being checked, so a pull queued behind a batch
does not wait for that one to land. `knotless queue list <repo>` lists the queue and what each is doing, and
`--leave <pull uri>` takes one out. A pull that conflicts with those
ahead of it, or breaks the batch (found by halving it), is ejected, and
`knotless pull show` says why: fix it and queue it again. Plain `knotless
pull merge` merges at once, and only once all seven checks have passed at its
head merged with `main` as it is now, and otherwise says which failed,
are running, or are of an older head or base: `--wait <secs>` waits for
them, and `--force` is the owner's alone. A pull that conflicts with
`main` fails each check without running: merge `main` into it, or
rebase it onto `main`. The runner checks a pull as it moves only when its
author and its head repository's owner are the owner or a collaborator;
anyone else's is not checked until the owner runs it with `knotless run rerun
<repo> --pull <pull uri>`. Every run starts warm from the caches `main`'s
last passing run left, each over a copy-on-write layer of its own, so runs
of one workflow go side by side, and only pushes keep what they wrote:
a pull's writes go when it ends. A branch whose pushes and pulls both run
a workflow, as a feature branch's can, keeps caches of its own, from its
pushes, which the pulls into it start from (`docs/design/runner.md`). (Anyone else's pull of a workflow that
asks for secrets starts from empty caches instead.)
While the runner lands the queue, a pull not in it is checked again only
as its head moves, not as `main` does, and after the queue's batches:
the queue checks it against `main` once it is queued.

Queue a pull at once, once pushed: the queue holds it until its
pre-flights, `check`, `test` and `web` (`queue: first` in their
workflows), pass at its head, and ejects it if one fails, so one that does
not build, fails a test, misses a line or is left unformatted never fails
a batch. A pass against an older `main` counts. Pulls queued behind it
that are ready go ahead meanwhile, and `knotless queue list` says
`pre-flight check,test` or the like while it waits. Queueing turns away a pull that conflicts with `main` or whose
checks failed, saying why, so merge `main` into the branch just before
queueing. One ejected: read why with `knotless pull show`, and the failed
check's log, then fix it and queue it again. Queue the same head again
only when the log shows the failure is not the pull's, such as a test or
a line it does not touch that fails other pulls too, and then fix or
quarantine what failed rather than leave it for the next pull. A pull
that conflicts with one queued ahead of it waits for that one to land or
leave; ejected then, it conflicts with `main`, so merge `main` into it.
Ejected three times for the same reason, stop and say so rather
than queue it again. A branch whose rule names a gate, as `main` may
name review.knotless.dev (`knotless branch rules`), waits for its review of the
pull's head: `knotless pull show` says `waiting for its review` meanwhile.
Ejected with `changes asked by @review.knotless.dev`, read the review
with `knotless pull show`, push the fix, and queue it again: the agent reads
the new head. Ejected for `not reviewed as main asks` when it approved
an earlier head and has stopped reviewing (it reviews a pull `--rounds`
times), and nothing since is more than `main` merged in or its own notes
answered, dismiss its review, saying so (`knotless pull dismiss-review
<repo> <pull uri> review.knotless.dev <reason>`), and queue it again: a
dismissal stands in for a gate's review, on the owner's own pull too,
where an approval never counts. Where it asked for changes and is wrong,
say why in a comment and ask the owner whether to dismiss it. Never
`--force` past it: that skips the checks at the head too. An approving
review can still carry notes it does not hold the pull for: once the
pull lands, read its review with `knotless pull show` and judge each note,
checking first that it is right, as a reviewer can be wrong. Fix it in
a follow-up pull, open an issue for one worth doing but not now, or
drop it, and say which, and why, in your report to the user: the
reviewer reads no reply. When several agents land pulls at once, one of them
lands them all, and the rest hand it their pulls' URIs rather than each
watching its own. A restart of the runner host's runner, as its deploy does, pauses the queue. A batch whose checks
pass while it stops still lands; one it cuts off is built and checked
again, with nothing to do.

Most conflicts are in the docs pages nearly every change updates,
`docs/design/serve.md` and `docs/reference/api.md` above all: add to
them where the change belongs, and leave the lines around it as they
are. Agents working at once take different parts of the code, and of
those pages, where they can.

Work that should not reach `main` yet goes on a feature branch,
`feature/<name>`, cut from `main`. Its parts are pulls into it, made as
pulls into `main` are but with `feature/<name>` as the base: the seven
checks run on them at their heads merged with the feature branch, and
`knotless pull merge` lands them there, through the queue only if that
branch's rule turns it on (`knotless branch protect <url> <branch>
--queue`). Nothing deploys from
a feature branch, and a push to one runs nothing. Keep one pull open
from it into `main`, unqueued, as the check that the whole of it still
passes on `main` as it is now; merge `main` into the feature branch as
`main` moves, since a pull into `main` that conflicts fails without
running. When it is ready, that pull is queued as any other.