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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
675 lines · 61.6 KB Raw History
# Changelog

What changed in each version of knotless for those who use it, from the
change files in `.changeset/` (see `knope.toml`).

## 0.12.0 (2026-11-08)

### Features

#### A repository's crates can be published and used with Cargo

knotless-serve answers Cargo's registry protocol for each repository, so `cargo publish`, `cargo yank` and `cargo search` work with `index = "sparse+https://knotless.dev/cargo/<did>/<repo key>/"`, and the token is `<handle>:<app password>`.

#### Pulls, issues, diffs and files are MCP resources

`knotless-mcp` and `/mcp` offer resource templates, so a client that reads MCP resources opens a pull, an issue, a pull's diff or a file by a URI like its page's, as `knotless://alice.test/proj/pull/<did>/<rkey>/diff`, answered as the matching tool answers.

#### Merge a pull by itself once its checks pass

`knotless pull merge <url> <pull> --auto` sets a pull to merge by itself once its checks pass, on a branch with no merge queue. The runner that lands the repository's merge queues merges it then. A push by anyone who may not merge it, or retargeting it, cancels it, and `--auto --undo` cancels it, as the pull's author may too. `pull show` says where it stands. The MCP tool is `pull_merge` with `auto`, and `undo`. Logging in again gives an existing OAuth login the permission to do it.

#### The pages set a pull to merge when its checks pass

A pull's merge box offers Merge when checks pass while its checks run or fail, says who set it, and cancels it.

#### The merge queue is off until a branch's rule turns it on

`knotless pull merge --queue` and the `pull_merge` tool's `queue` now refuse a pull unless the branch it lands on is protected with `--queue`. Turn it on with `knotless branch protect <url> <branch> --queue`, and every pull into that branch then lands through the queue. A pull already queued keeps its place.

#### Withdraw a bad release with `knotless release yank`

`knotless release yank <url> <package> <version>` withdraws a version without breaking anyone who pinned it: its files can still be read by version, but it is no longer the latest, and the releases page marks it Yanked. `--undo` takes that back. The MCP tool is `release_yank`. Logging in again gives an existing OAuth login the permission to do it.

#### A review agent's rounds count only its requests for changes

`knotless-agent review --rounds` now limits how many times it asks for changes on a pull, not how many times it reviews it. A pull it approved is reviewed again at each later push, and the command is told the commit it approved (`approved`), and how many times it has asked for changes (`changes`). A request for changes is no longer carried to a head that only merges the base in when the pull's title or body was edited after it.

#### An agent watching an owner finds their private repositories shared with it

`knotless repo share` leaves the account shared with an invite at the appview, and `knotless-agent --owner <handle>` watches, besides that account's public repositories, every private one it shared with the agent's account, so a reviewer need not be told each private repository by name.

### Fixes

#### Commands reading one repository at once no longer fail on its history

Two commands fetching the same repository at once could fail with `git commit-graph: Could not read`, when one found a commit whose parents the other had not fetched yet. Fetches now take turns.

#### A connector whose login has ended asks you to sign in again

When your PDS ended the login a knotless server keeps for your connector, its tools carried on as no one: private repositories read as not there, and writes said to run `knotless login`. The server now answers so that your assistant has you sign in again.

#### A git killed part way no longer leaves a repository unreadable

A server or command stopped while it set a mirror's refs could leave git's lock file behind, and every later read of that repository then failed with `cannot lock ref`. The lock is now cleared the next time the refs move.

#### An OAuth login is kept when refreshing it fails

When an OAuth login's refresh failed for any reason, knotless logged in with `KNOTLESS_PASSWORD` instead and saved that over the OAuth login, which can't read private repositories. Now the OAuth login is kept and tried again, and the failure is logged. The password stands in only once the server turns the refresh down for good.

#### A merge queue's next batch goes first once the batch before it lands

A batch built ahead of one still being checked waits for a job behind pulls' runs. Once the batch under it lands, a runner now ranks what is left of it with pushes to the default branch, so it lands next instead of waiting behind every pre-flight queued meanwhile.

#### A merge queue batches a pull as soon as its pre-flights pass

A runner landing a merge queue now looks again the moment a pull's run ends, on it or on another runner the repository names. A pull whose last pre-flight passed used to wait for the runner's next sweep, up to 10 minutes, before going into a batch.

#### A release that fails to publish says why

Publishing a version now says it is published already only when it is. Any other failure, such as a login that was refused, gives its own reason.

## 0.11.0 (2026-11-05)

### Features

#### Pulls based on a pull's branch move when it lands

When a pull lands and its branch goes, open pulls based on that branch now target what it landed on, and show only their own changes. Before, they stayed on the old branch, which was kept for them.

#### A stack queued lands in turn

Queue each pull of a stack and each waits for the one below it, then moves to what that one landed on and lands in turn. `knotless queue list` lists one waiting as `stacked`. Leaving the queue takes those waiting on the pull with it.

### Fixes

#### A moved pull lands with an older git

Merging or queueing a pull moved off a landed pull no longer needs git 2.40, and git failing during a merge is no longer reported as a conflict.

#### Waiting pulls list as they land

`knotless queue list` lists pulls waiting for the pull below them lowest first, the order they land in.

## 0.10.0 (2026-11-05)

### Features

#### Commands are grouped

Every `knotless` command is now `knotless <group> <action>`: `knotless pull create`, `knotless repo star`, `knotless run list`, `knotless branch protect`. An opposite is `--undo` on the action, so `unstar` is `repo star --undo`. Only `login`, `logout`, `upgrade` and `status` stand alone. The old names, such as `knotless pull` and `knotless checks`, work for one more release and then go. `knotless --help` lists the new ones. The MCP tools are renamed to match, `<group>_<action>`: `repo_star`, `pull_merge`, `run_list`. A tool is now the twin of the command of its name, so what the CLI could do and the tools could not, and the other way round, is added: `repo tree`, `repo read`, `repo log`, `pull update-branch`, `pull dismiss-review` and `release read` on the command line, and the commands that lacked tools, in the tools. Update any MCP client config that names a tool.

#### Private repositories have sites

`knotless repo site` now deploys a private repository's site. It is kept in the repository's space and read only by its members, at a host of its own, `https://<repo key>-<plc id>.private.knotless.page/`. A member who opens it logs in at knotless.dev if they are not logged in already. Someone who is not a member gets a 404, and a member you remove stops reading it within a minute.

#### The owner cancels runs

`knotless run cancel <url> --ref <name> | --pull <at-uri> [--workflow <name>]`, and the MCP `run_cancel` tool, ask a repository's runners to cancel a ref's or a pull's runs going or waiting, a deploy's too, or only one workflow's.

## 0.9.0 (2026-11-04)

### Features

#### A keeper for the merge queue

`knotless-agent keep` is an account that keeps the pulls the merge queue turned away moving. One turned away for a conflict that is only lines both sides added it brings up to date and queues again. Where both sides changed the same lines, it tells the author on the pull which files. `Keeper: off` on a line of a pull's body has it left alone.

#### Deploy a repository's website

`knotless site <url> <dir>` deploys a directory as the repository's website, served at `https://<owner>.knotless.page/<repo>/`, uploading only the files the live site lacks. `--fallback` serves a single-page app, `--not-found` a 404 page, and `--down` takes the site down.

### Fixes

#### A pull held for a review no longer tells its author to approve it

The message for a pull that lacks a review now says that anyone but its
author who may write can approve it, and that the owner merges past it
with `--force`, their own pull too.

#### Pushes and fetches no longer wait on a listing of the packs

Each ref now names the pack holding its commit, so `git fetch` reads just those packs, and `git push`, `git ls-remote` and a fetch with nothing new no longer list the packs at all. On a repository with many recent small pushes that listing ran to megabytes, and about half of each call's time. The owner's `git push` now starts `knotless compact --due` after every push, which counts the packs itself and compacts only at `KNOTLESS_COMPACT_THRESHOLD`.

## 0.8.0 (2026-11-04)

### Features

#### Code search loads more results

The code search page shows its first 20 files, then loads more below them, 200 files at most. `dev.knotless.searchCode` takes a `cursor` for the page after.

#### Code search

Search finds code: pick Code beside Repositories, or the palette's code row, to search the default branch of every public repository, with `repo:owner/name` for one and `lang:` for a language. Each match is shown in its lines, highlighted as its file is. `knotless status` shows the server's code search index: whether it answers, and the repositories, shards and size it holds.

#### Dismiss a review

`knotless dismiss <url> <pull> <reviewer> <reason>` dismisses a review as the owner or a collaborator, and so does MCP's `dismiss_review`. The review counts for nothing until its reviewer reviews again, so a gate's request for changes no longer needs `--force`, even on your own pull. `show` marks a dismissed review.

#### Go to file

A repository's code page has Go to file beside Code, and `t` opens it: type part of a path to find any folder or file at the branch shown, and Enter goes to it.

#### The install script brings the runner, labeller and reviewer

`curl -fsSL https://knotless.dev/install.sh | sh` now installs `knotless-run`, `knotless-triage`, `knotless-agent` and its `knotless-claude-review` script beside `knotless`, `git-remote-knotless` and `knotless-mcp`. A runner, labeller or reviewer no longer needs a clone of knotless and Rust to build it. Pinning `KNOTLESS_VERSION` to a release from before these shipped still installs the first three.

#### Merges always name their pull, and a push that lands a pull merges it

`knotless merge` into a branch that is not protected as linear now always makes a merge commit naming the pull, even where it could have fast-forwarded, so the branch's history says which pull each commit came from. Its method is `merge` or `squash`; `fastForward` is no longer made. A pull whose head is already on its base is marked merged without moving the branch.

A `git push` that puts an open pull's head on the branch it targets now marks that pull merged, and closes the issues it names, as `knotless merge` would, saying so on git's output. The pages link each commit to the pull that brought it in, whether it was squashed, merged or pushed.

#### A branch's gates are its required reviewers

Every pull into a protected branch now asks the gates its rule names to review it, so they show among its reviewers before they review, marked "Required reviewer for main" on its page. `unask-review` refuses to stop asking one: change the rule instead.

#### A stopping runner hands over to the next one

On SIGTERM, knotless-run finishes the runs it has started, along with a merge-queue batch's runs and those at a running deploy's commit. It writes a note beside its state file, so a runner started beside it takes up everything else at once. The new runner leaves the old one's checks and caches alone, and takes no job the old one's runs hold.

#### Update a pull's branch

`knotless update-branch <url> <pull>` and the MCP `update_branch` tool merge a pull's base into its branch and push it, with no checkout, as GitHub's Update branch button does. Where both sides only added lines at the same place, as two pulls each adding a section to one docs page, the base's lines come first and the pull's after; any other conflict is refused, naming its paths.

#### Workflows run after others

A workflow can name others in `after`: the runner starts it only once their checks at the same commit have passed, and it holds no job while it waits. If one fails or is cancelled, it does not run.

### Fixes

#### `knotless login` works in Chrome and Firefox again

The login page's address had grown past what Chrome and Firefox send back to it, so approving a login on bsky.social and other PDSes failed with "Invalid referer". `knotless login` now asks for the same access in a shorter form. A login made before stays current.

#### The MCP `pull_diff` tool gives a long diff a few files at a time

A pull's diff too long for one answer used to be cut off by the MCP client, so an agent read only its first files. Now `pull_diff` lists the files of such a diff instead, with how many lines each adds and removes, and takes `paths` to give those files' parts of it.

#### The queue says when a pull's pre-flight failed

`knotless queue` lists a queued pull whose pre-flight failed as `failed
<workflows>`, and `show` says which failed, rather than `queued` and
"running its checks again" while nothing runs, until the runner ejects it
or runs it again.

#### A review agent reads a pull again when its body answers a request for changes

After `knotless-agent review` asks for changes, editing the pull's title or body has it review the pull again at the same head, as one of its rounds, so a request about what the pull says no longer needs a push to clear. `knotless-claude-review` also no longer asks for changes over what a required check already fails the pull for.

## 0.7.0 (2026-11-03)

### Features

#### Pulls and issues are assigned, and maintainers ask for reviews

The owner and collaborators assign a pull or an issue to accounts with `knotless assign` and take them off with `unassign`, and ask anyone to review a pull, not only its author, with `knotless ask-review` and `unask-review`. Each stops asking only those it asked. `knotless show` lists the assignees, `knotless list -q` and the MCP's `list_items` take `assignee:<handle, did or @me>` and `no:assignee`, and the MCP's `assign_item` does either. Those assigned hear of it as an `assigned` notification, and those asked as a `review-requested` one. The pages do the same under an item's title.

#### An organization's admins change its repositories' settings from the command line

`knotless describe`, `archive`, `compact`, `protect`, `unprotect`, `labels`, `triagers`, `agents`, `share`, `unshare` and `runners`, and the MCP tools that do the same, now work on an organization's repository from an admin's own login. Not logged in as the owner, they ask the appview, which makes the change as the organization. Log in again (`knotless login`) if your login is older than this, so it may ask. Renaming, deleting and transferring a repository still take the organization's own login, on the pages too.

#### A branch waits for a review agent's verdict with `knotless protect --gate`

`knotless protect <url> <branch> --gate <handle>` names an account, such as a review agent, whose review of a pull's head the branch waits for, though it may not write to the repository. The merge queue holds a pull until the gate has reviewed its head, and a request for changes from it blocks merging and queueing until it reviews again. An approval of the head from the owner or a collaborator, given after the gate's review, stands in for it. `knotless rules` lists each gate, `merge --check` says `a review of its head from @<gate>` while one is awaited, and MCP's `protect_branch` takes `gates`. knotless-run's `--review-wait <secs>` (30 minutes) is how long the queue waits before ejecting a pull no gate has reviewed.

#### A branch's gates are named on its settings page

A protected branch's settings on the pages now have "Wait for a review from", which names an account, such as a review agent, as a gate of the branch by its handle. The page lists each gate, with Remove to take it out, as `knotless protect --gate` does.

### Fixes

#### `knotless checks --images` keeps the newest run's pictures

When the checks listed hold more than one run of a workflow, as a branch's reruns or a pull's heads do, an older run's picture no longer overwrites the newest's of the same name. The older one is saved beside it, named for its commit too: `look notifications-desktop-light 450b27ae11f7.webp`.

#### Exclusive labels with no scope stand together, and a failed retarget changes nothing

An exclusive label with no `/` in its name no longer takes other such labels off: only labels in the same scope, such as `priority/high` and `priority/low`, rival each other. `knotless retarget` with both `--base` and `--head` checks the head first, so one refused for its head no longer moves the pull's base. Several workflow files with names that are not valid each give their own error in the one `invalid` check, rather than a duplicate-name error. `knotless queue` and `knotless uses update` no longer panic on a commit ID shorter than 12 characters, and the gateway takes SHA-256 commit IDs as pushes do.

#### The page after logging in is easier to read

The page the browser shows once you have approved a login now has a heading and the look of knotless's own pages, in light and dark, and says so when the server refused the login rather than that you are logged in.

#### A merge no longer leaves a pull open when its head branch cannot be deleted

Merging a pull whose head branch is protected keeps that branch, and says why, instead of trying to delete it. A delete that is refused for another reason no longer fails the merge after the target has moved. A `git push` also no longer fails when the compaction after it does, and `KNOTLESS_INLINE_MAX` is held to 262144 bytes, the most a pack record takes.

## 0.6.0 (2026-11-02)

### Features

#### `knotless admin logs` reads the lines the appview logged

An appview's operator can read the lines it logged, without SSH: `knotless admin logs --trace <id>` shows one request with the calls it made and how long each took, and `--message`, `--level` and `--min-ms` show the last of the rest. The MCP `logs` tool does the same. The appview keeps them across deploys, for 30 days at most, and logs each read with who asked.

#### An organization's admins change its repositories' settings on the pages

An admin who joined an organization now sees the Settings tab on its repositories, and changes everything there as the organization: description, branches and their rules, labels, collaborators, triagers, runners and secrets, and archiving. Transferring and deleting a repository stay with the organization's own login.

#### Deleting a branch deletes its record

Deleting a branch or tag now deletes its ref record from your PDS, rather than leaving an empty one so that its count of moves never repeats. A new ref starts its count at the time instead. Compaction removes the empty records older clients left once they are a day old, so a repository that has had hundreds of branches reads only the ones it has now on every push, fetch and page.

Upgrade every client that pushes: an older one making a branch again under a name deleted less than a day before can have its pushes to it refused until the next compaction.

#### A shared step can be passed a secret under the name it asks for

A `uses` step's `secrets` can be a map from the name the step file asks for to the workflow's own, as `secrets: {SSH_KEY: DEPLOY_SSH_KEY}`: its steps see the value as `SSH_KEY`, and nothing as `DEPLOY_SSH_KEY`. A list still passes each secret under its own name.

#### A private repository's pulls, issues, comments and checks are named by their URI in its space

knotless names a record in a private repository's space as atproto's permissioned data proposal does, `at://<owner>/space/dev.knotless.space/<name>/<author>/<collection>/<rkey>`, wherever it gives one: what `knotless pull`, `issue`, `comment` and `checks` print, what `show` and `list` name, and what new records written into the space refer to. A URI in the public form, `at://<author>/<collection>/<rkey>`, is still taken for one in the repository being asked about, and records written before are read as they were. A knotless older than the one that reads both forms does not find a record named in full: upgrade with `knotless upgrade`.

#### Watch a repository, and subscribe to or mute a pull or issue

`knotless notifications watch <url> [all | releases | ignore | participating]` chooses what you hear of a repository: all it does, its releases alone, or nothing. `knotless notifications subscribe <at-uri>` hears of every comment on a pull or issue, `mute` hears nothing of it but a review asked of you, and `unsubscribe` goes back to hearing of it as your part in it says. `knotless notifications subscriptions` lists what you chose. The MCP `subscribe` tool does the same. Notifications gain the reasons `subscribed` and `watching`. They call two new appview methods, so a login made before this needs `knotless login` again.

#### Workflows run on a schedule

A workflow's `on: {schedule: [{cron: '30 3 * * *'}]}` runs it at the default branch's tip at the minutes its cron expressions name, in UTC, at most every five minutes. Its steps see `KNOTLESS_SCHEDULE`, the expression that ran it, and its log says `==> on its schedule`. The runner reads schedules from the default branch's tip as it starts and whenever that tip moves. A time that comes while the last run is still going is skipped. `knotless workflows` lists each workflow's schedules at the default branch.

### Fixes

#### A feature branch's pulls start from its own warm caches

A branch whose pushes and pulls both run a workflow, as `on.push.refs` and `on.pull.base` both naming `refs/heads/feature/*` do, keeps that workflow's caches for itself: each passing push to it leaves what the pulls into it start from, and a pull landed there at the tree it was checked at is taken as the push's run, caches and all. A branch with none yet starts from the default branch's, and its caches go when it is deleted.

#### `knotless checks --ref` takes a branch named bare

`knotless checks --ref main` lists the checks of `refs/heads/main`, as `rerun` and `workflows` read a bare name, rather than none; a tag of that name, when there is no such branch. A branch deleted since, as a merged pull's, still names its checks. The MCP `checks` tool's `ref` takes one the same way.

#### `knotless admin logs --trace` reads up to 500 lines of the request

A read by trace now answers up to 500 lines unless `--limit` says fewer, instead of the last 100, so a long request's first lines are not cut off. Lines serve fails to write to its log store, as when another serve holds the file during a deploy, are kept and written with the next batch instead of lost.

#### An organization's admins set its repositories' secrets

`knotless secret <url> set`, `ls` and `rm`, and the MCP `secret` tool, now work on a repository an organization owns when you are logged in as an admin who joined it, not only as the organization's own account. The values are sealed as the organization's, so its runners open them as before.

#### Queueing says to read an ejection's logs before queueing again

`knotless merge --queue` ends by saying to read an ejected pull's check logs before queueing it again. The MCP `merge` tool's description says the same, says to merge the target branch in just before queueing, and says that a pull conflicting only with one ahead of it waits for that one rather than merging its branch in.

#### A queued pull's record names the head it was queued at

`knotless merge --queue`, and the MCP `merge` tool with `queue`, write the pull's head into its `dev.knotless.queue` record, as an ejection already does, and `knotless show --json` gives it. The queue's history then says whether a pull queued again after an ejection had changed.

#### Queueing a pull again at the head it was ejected at says so

`knotless merge --queue` says when the pull is going back into the queue at the very head the queue last ejected, and why it was ejected: unless that was not the pull's doing, it will be ejected again. The MCP `merge` tool answers it as `queued.ejectedBefore`, and `dev.knotless.queuePull` as `ejectedBefore`.

#### A queued pull says what it waits for

`knotless show`, the MCP `show_item` tool and `knotless queue --json` say what a queued pull in no batch is waiting for: `Queued, 2nd of 3, running its checks`, `running its checks again on main as it is now` when one failed on an older tip, or `ready for the next batch`. Before, each just said `Queued`, except while a pre-flight was running. `--json` also gives each one's `stage`, its pre-flights' newest runs (`preflights`), and when the batch ahead of it started (`ahead`).

#### Reading a repository with many deleted branches is faster

A repository keeps a record for every branch it ever had. Past the first 100, knotless now reads those records in ranges side by side rather than one page after another. Pushing to, cloning and listing a repository that has had hundreds of branches, as a busy one does, takes a fraction of the time, for example 0.37 seconds rather than 1.7 for 590.

#### A private repository read for longer than a space credential lasts goes on reading

A space credential lasts minutes. What reads a private repository for longer, as `knotless-run` watching one does, renews it before it runs out rather than failing every read after with `JwtExpired`, and a session read with renews the same way.

#### A record in a private repository's space can be named by its full URI

Wherever knotless takes a pull's, an issue's or a comment's `at://` URI, as `knotless show`, `comment`, `merge`, `checks --pull` and a pull's `Fixes`, it reads a private repository's record by its URI in the space too, `at://<owner>/space/dev.knotless.space/<name>/<author>/<collection>/<rkey>`, as atproto's permissioned data proposal names it, as well as by `at://<author>/<collection>/<rkey>`.

#### `knotless admin stats` says which queries were slow, why actions failed, and which crawlers came

An operator's `knotless admin stats`, and the MCP `stats` tool, now list the index's slow statements with how often each ran slowly, their mean and their longest; why MCP tool calls failed, with like errors counted together; and the requests each crawler or script made, by its user agent. Each is kept for 30 days.

## 0.5.0 (2026-11-01)

### Features

#### A workflow step can use a step another repository shares

A step with `uses: <owner>/<repo>/<step>@<version>` runs the steps of `.knotless/steps/<step>.yml` in that repository, at the commit `.knotless/uses.lock` pins it to, with the inputs `with` gives, only the secrets the step passes it, and its own repository checked out beside the checkout as `KNOTLESS_STEP_DIR`. A step file can use shared steps of its own. Release a step with `knotless publish <url> @step/<step> <version> .knotless/steps/<step>.yml --commit <sha>`, and pin the ones a checkout uses with `knotless uses update`. Each step is now given its secrets as it starts, rather than the container holding them all.

#### `show` lists the pulls that mention an issue

`knotless show` on an issue prints `mentioned in <pull>` for each pull on the repository whose title or body names it, oldest first, ending `, to close it` where the pull names it after a closing keyword. MCP's `show_item` gives the same as `mentionedBy`.

#### A login and a gateway's grant name knotless's own space types

`knotless login` asks for `space:dev.knotless.space` and `space:dev.knotless.orgSpace`, each with the collections knotless writes there, in place of every space of every app (`space:*`); `knotless delegate` asks the same of a gateway's grant. A login or grant from before still works, and is asked for again as any older one is.

#### A workflow step can run only at some refs

A step with `on: {refs: [refs/heads/main, refs/tags/*]}` runs only at a push to, or a run asked for at, a ref those patterns match, as `on.push.refs` are matched; anywhere else it passes unrun, listed `skipped`, its log saying why. A pull's run and a merge-queue batch's run are at no ref, so such a step runs in neither. On a step with `uses`, its `on` holds for every step the shared step comes to, beside their own.

#### Themes of your own

The pages' Appearance settings make a theme of your own colours, checked as you choose them for text that reads on what it is drawn on, and publish it as a `dev.knotless.theme` record for anyone to choose from Appearance. Choosing one copies its colours into your preferences, so its author changing or deleting it later leaves your pages as they are. Logging in again asks to write `dev.knotless.theme` records.

The command line does the same: `knotless theme check`, `publish`, `use` and `delete`, and `knotless themes` to find them; and over MCP, the `theme` and `list_themes` tools. A palette is held to the same contrast wherever it is made.

### Fixes

#### `knotless show` names the command that reads an ejected pull's logs

A pull the merge queue ejected as its checks failed shows `logs: knotless checks <url> --pull <pull> --log`, or `--queue <branch>` for a batch, after where it stands. The runner no longer writes that command into the reason it records, which the web pages show beside links to the checks that failed.

#### An organization's space has a type of its own, `dev.knotless.orgSpace`

An organization keeps its invitations and hidden members in a space of the type `dev.knotless.orgSpace`, which has its own lexicon, so that a login can name it. Its gateway moves an organization out of its old space, of the type `dev.knotless.org`, as it starts and as the organization next changes, keeping each member where they stood. A hidden member's record of joining stays in the old space, and counts, until they join again: `knotless org join <org> --hidden` moves it.

#### `knotless status` shows the server's disk

`knotless status` adds a `disk` line: how much room is left on the disk a knotless-serve keeps its mirrors on. Under 15% free the disk shows as `low` among the problems, and under 5% the server counts as unhealthy.

## 0.4.0 (2026-10-30)

### Features

#### Say an account is a bot

`knotless profile --bot` says your account is a bot, such as a runner, a triager or a reviewer, and `--no-bot` takes that back. The MCP server's `set_profile` takes `bot`, and the profile settings page has a box for it. A bot's profile shows a "Bot" tag, and the front page's feed leaves out what it says and pushes.

### Fixes

#### The feed leaves out bots

The front page's feed no longer shows what a repository's runners and triagers say or push on it, which repeated what people did and crowded them out. A runner that is the repository's owner or a collaborator still shows. A pull the merge queue landed shows as merged by whoever queued it. A bot's own profile still shows everything it did.

#### The appview's gateway is the one its DID document names

`knotless share --write`, `knotless delegate` with no gateway named, and a runner landing a merge queue through the gateway now ask the appview for its DID instead of making one from `KNOTLESS_APPVIEW`'s address. A runner that reaches its appview at an internal address, as one beside it does, lands its queue again rather than failing to reach a gateway that does not exist.

#### Private repositories work on the October 29 spaces alpha

The spaces alpha of October 29 moved spaces from `com.atproto.simplespace` to a policy on `com.atproto.space`, and creating a private repository or sharing one failed against a PDS running it. knotless now creates each private repository's space under the memberspace policy and manages its members with `com.atproto.memberspace`. A PDS on an older alpha can't create private repositories with this version.

## 0.3.0 (2026-10-30)

### Features

#### Archive a repository

`knotless archive <url>` makes a repository you own read only, and `knotless archive <url> --undo` makes it writable again. While it is archived, pushes and commits are refused, nobody can open pulls or issues or comment on it, and its workflows stop running. Its pages show it as archived. You can also archive and unarchive it from its settings, in the Danger zone, or with the MCP server's `archive_repo` tool.

#### Hear of your pulls' failed checks and merge queue ejections

`knotless notifications`, the MCP `notifications` tool and the pages now say when checks fail at your pull's latest run (`checks-failed`) and when the merge queue takes your pull out (`ejected`), each naming the checks that failed. Either can be turned off with `knotless notifications settings --off <reason>`. The pages' notification settings can also show new notifications on your screen while knotless is open in a tab.

#### Change part of a long file without sending all of it

`knotless commit --edit <path> <old> <new>` and the MCP `commit` tool's `edits` replace text in a file as the branch holds it, so an agent changing one line of a long file sends that line, not the file. On a branch that moved meanwhile, edits are made in the file as it is there. `knotless search` and the `search` tool take `--path`/`path` to look only under a directory or glob, and `--context`/`context` to show the lines around each match, enough to write an edit from. The `read_file` tool takes `start` and `end` to read only some lines, and `blame` (`--lines <first>-<last>` on the command line) blames only some.

#### Notifications of one kind

`knotless notifications --kind <kind>` lists only mentions, reviews, comments, pulls-and-issues, checks or stars-and-follows, and the MCP server's `notifications` tool takes the same `kind`. A listing of one kind marks nothing seen, so the others stay new.

#### Owners give their runners secrets

`knotless secret <url> set <name> [--ref <pattern>]...` gives a repository's runners a secret, read from stdin, sealed to the key each runner publishes so only it can read it, and left at the appview. `knotless secret <url> ls` lists them, never a value, and `rm` takes one back. The MCP server has a `secret` tool to match. A runner reads them for each run whose workflow lists a name its secrets file does not grant. `knotless runners <url> remove` deletes the secrets sealed to that runner, and `add` says which secrets must be set again to reach it.

#### A read-only knotless-mcp, and reviews of private repositories

`knotless-mcp --read-only` offers only the tools that read, and runs no other, whatever account it is logged in as. The review agent reads through it with its own login, so it reviews the private repositories shared with it, and a repository says what it wants of its reviews in `.knotless/review.md` on its base branch.

#### Topics and collaborators on a repository

`knotless describe <url> --topic rust --topic cli` names what a repository is about, and `--topic ''` drops its topics. They show under its description, each linked to the other repositories about it, and the MCP server's `update_repo` tool and the repository's settings change them too. A repository's page also shows who may push to it besides its owner.

#### Review agents

`knotless-agent review` runs an account of your own that reviews the pulls asking it for a review, as GitHub's Claude review bot does. Ask it with `knotless pull --reviewer <its handle>` or `knotless edit --reviewer`; it reads the pull with Claude Code, through `knotless-mcp`'s read tools, and writes a review with a verdict and comments on lines, then reviews again as you push, up to `--rounds` times. It uses your Claude subscription (`claude setup-token`) or an API key, and `--command` takes any other reviewer that reads and writes its JSON. The guide is docs/guide/reviews.md.

#### The reviewer can watch every repository an account holds

`knotless-agent review --owner <handle>` reviews the pulls of every public repository that account holds, listed again at each read, so a repository made later is reviewed without restarting the agent. Repeat `--owner` for more accounts, and name repositories besides as before.

#### knotless-run keeps a key for secrets

`knotless-run` now makes a P-256 key on its first start, kept in `--secrets-key <file>` (`secrets-key.json` beside its state file by default), and publishes its public half as its `dev.knotless.runnerKey` record. Owners will seal the secrets they give a runner to it, so only the runner can read them.

#### The appview keeps secrets sealed to a runner

knotless-serve answers `dev.knotless.putSecret`, `deleteSecret` and `listSecrets`: an owner leaves a repository's runners secrets, each sealed to one runner's key, and the appview holds only ciphertext. knotless's login asks for these methods, so a session from before asks again at its next login.

#### Owners manage secrets on the repository's settings

The repository's Settings, under Actions, list its secrets by name and add, replace and remove them, signed in as its owner. Each value is sealed in the browser to the keys the runners publish, as `knotless secret` seals it, so the appview still holds only ciphertext.

#### Hear of new versions, and upgrade

Once a day, knotless asks its appview for the newest release. When one is out, a command ends by saying so on stderr. `knotless upgrade` installs it over the copy you run. When the appview asks for a newer version than yours, because yours is known to harm repositories, knotless and knotless-mcp refuse to run until you upgrade. `KNOTLESS_NO_UPDATE_CHECK` turns the check off, and `KNOTLESS_IGNORE_MINIMUM` runs anyway.

#### A workflow can name the runners that run it

A workflow's `runners` key names, by handle or DID, which of the repository's runners run it; the others leave it be and file no check of it. `knotless secret <url> set <name> --runner <handle>` gives a secret to those runners alone and takes back the copy any other had, as the MCP `secret` tool's `runners` does. Together they keep a deploy and its secrets on a runner of your own while the hosted runner checks everything else.

### Fixes

#### Clones no longer break after a branch is deleted and compacted

A clone or fetch could fail with "the remote has no pack holding" a commit of a deleted branch, such as a landed pull's. A newer pack still named that commit as a base, and compaction had dropped it. Compaction now keeps every base that the packs it leaves name.

#### Compaction stops rather than lose a commit

If compaction can't fetch a commit that a newer push still needs, it now stops and deletes nothing. Before, it left that commit out and deleted the packs anyway, which broke clones with "the remote has no pack holding" that commit.

#### A conflict is named a conflict

A 409 from knotless-serve, such as a gateway commit whose edit no longer finds its text, now says `Conflict (409)` rather than `InternalServerError (409)`, so it no longer reads as the server failing.

#### A pull the merge queue landed shows the checks it landed on

A merged pull's page now shows the checks of the batch the merge queue landed it in, not older runs of its own heads. Over MCP, `list_items` and `show_item` name that batch's tip as `merged_batch`.

#### Joining an organization reads both places at once

`knotless org join` reads whether you joined publicly and in the organization's space at the same time. Switching between them no longer reads again after the organization takes you in.

#### Pages left open catch up when you come back

A page left in a background tab for a minute or more loads fresh data when you look at it again, and any page does the same when your connection comes back. A repository's page that loses its live connection now catches up on what it missed once it reconnects.

#### Pictures show at the size they were taken

A picture attached with `--image`, or `images` over MCP, keeps the size it is drawn at: a PNG saying it was made at 2x, as a Mac's screenshot does, shows at half its pixels, so a phone's screenshot is phone-sized on a pull or issue.

#### A run says which secrets it was not granted

A run whose workflow lists a secret the runner does not grant it now says so at the top of its log, `==> NAME is not granted to <repo> at <ref>`, rather than leaving the secret silently absent. A pull's or merge-queue batch's run says the same of each, as it gets none.

#### An edit or reply in progress survives the page updating

A comment you are editing, or a line comment you are replying to, on a pull's or issue's page no longer loses what you typed when the page refreshes and an earlier event shows up.

## 0.2.0 (2026-10-29)

### Features

#### Blame a file without a clone

`knotless blame <url> <path> [--rev <rev>]` prints who last changed each run of a file's lines, as `git blame` finds it, read from the cache mirror: the lines, the commit, its author, when it was written and its subject, tab-separated. The MCP server's `blame` tool answers the same as JSON, with the commit's parent and the file's path there, to blame again before it.

#### Reviews and merges say what they mean

`merge --check` lists each approval that does not count, and why, as an `uncounted` line (the MCP `merge` check as `reviewsUncounted`), and a merge refused for its reviews says so too. It says when the asker may not merge the pull, and counts a review written a moment ago. Over MCP, a refusal for reviews names the `comment` tool and `merge` with `force` rather than CLI commands; `merge` answers how it merged (`method`) and why it kept the head branch (`kept`); `show_item` gives handles beside reviewers' DIDs, marks reviews `outdated`, `maintainer` and `uncounted`, gives comments' mention `facets`, marks every comment of a resolved thread `resolved`, has a review's own comment name itself as `review`, and says `draft: false`; `comment` notes an approval that will not count; `notifications` says `markedSeen`.

#### Comment on a pull's lines, in threads you can resolve

`knotless comment` takes `--line <path>:<n>[-<m>][:old] <text>`, as many as you like, to comment on lines of a pull's diff, alone or as one review with `--approve`, `--changes` or a body, and prints the URI of each comment it wrote. Given a comment's URI, it answers that comment in its thread. `knotless resolve` and `unresolve` resolve a thread or open it again, and `knotless show` says which lines each comment is on and which answer which. Over MCP, `comment` takes `lines` and answers `written` too, `set_state` resolves a thread, and `show_item` gives each comment's `line`, `reply`, `review` and `resolved`. On the pages, the `+` beside a line of a pull's diff comments on it, or starts a review sent from the "Review changes" menu.

#### Delete all your knotless data

`knotless delete-data <handle>` lists what knotless keeps in your atproto account (your repositories, those others push to or read named, and the records of each kind), then, once you type your handle again (or pass `--yes`), deletes all of it: your private repositories' spaces, each public repository as `knotless delete` deletes one, then your pulls, issues, comments, stars, follows and the rest. knotless.dev then forgets you and logs you out. A run cut short carries on where it stopped when run again. Your atproto account, and other apps' records in it, stay. The `delete_account_data` MCP tool does the same, and the pages' account settings have a "Delete your knotless data" section. Logging in again asks to call `dev.knotless.deleteAccountData`.

#### Draft pulls and requested reviewers

`knotless pull --draft` opens a pull that neither merges nor queues until you mark it ready with `knotless edit <url> <at-uri> --ready` (`--draft` makes it one again); `merge --check` says `draft` and why. `--reviewer <handle>`, on `pull` and `edit`, asks someone to review your pull, and `edit --no-reviewer` stops asking: they hear of it as a `review-requested` notification once the pull is not a draft. `knotless show` lists both, and `list -q` takes `is:draft`, `-is:draft` and `review-requested:<handle or @me>`. MCP's `open_pull` and `edit_item` take `draft` and reviewers too. The pages mark drafts in the pulls list, show requested reviewers in the merge box, and let the author mark a draft ready there.

#### The gateway holds collaborators to required reviews and checks

Through the gateway, a collaborator moves a branch whose rule asks for reviews only by merging a pull reviewed as it asks, with its checks passed: a plain push or `knotless commit` to it is refused, saying to open a pull. In a repository that names runners, a collaborator's merge of a pull, pushed by any client, is refused until the pull's checks pass, as `knotless merge` refuses it.

#### Mentions

`@handle` in a pull's or an issue's title or body, or in a comment, now mentions that account: a full handle, with a dot, not in code or a link. Writing the record, from the command line, MCP or the pages, resolves each handle and keeps the account's DID in it as facets, so the mention still names the account after its handle changes. The pages link each mention to the account's profile, and the account hears of it with a new notification reason, `mention`, on unless turned off with `knotless notifications settings --off mention`, MCP's `notification_settings` or the settings page. On a private repository only those who can read it hear of a mention.

#### Organizations

Any account can be an organization. As that account, after delegating to a gateway (`knotless delegate`), `knotless org add <handle>` invites another account, and `knotless org join <org> --public` or `--hidden` accepts: from then on they push to each of the organization's repositories, made before or after, and read the private ones, until `knotless org leave` or `knotless org remove`. A public membership shows to anyone; a hidden one only to the organization's members, and on its public repositories a hidden member pushes and opens pulls while merging, closing and labelling take a public membership. Invitations lapse after 7 days. `--admin` makes an admin, who invites, removes and makes repositories as the organization from their own login (`--org <org>`, or `knotless create knotless://<org>/<name>`). `knotless org members` lists who is in (`--public` as anyone outside sees it), `knotless org list` which organizations an account is in, and `knotless org delete --yes` ends it. `knotless members` names an organization's members on each repository. The `org` MCP tool does the same, and so do the pages: Start an organization in your account menu, then a Members tab to invite, change roles, join, hide, leave and remove, and Delete organization in settings. An invitation shows in `knotless notifications`, MCP's `notifications` and the pages' notifications until it is answered or lapses, and the pages answer it there. Logging in again, and delegating again, asks to write `dev.knotless.org`, `dev.knotless.orgMember` and `dev.knotless.orgAcceptance` records and to call the organization methods.

`knotless merge --check`, and MCP's `merge` with `check`, tell a hidden member why they may not merge on an organization's public repositories: that it takes a public membership.

#### Pictures in pulls, issues and comments

Paste or drop a picture into a pull's, an issue's or a comment's box on the pages and it is uploaded to your account and linked where you put it, as GitHub does. From the command line, `--image <file>` on `pull`, `issue`, `comment` and `edit` does the same, and over MCP `images` on `open_pull`, `open_issue`, `comment` and `edit_item`. The record attaches each picture its body shows, so your PDS keeps it, and lets it go when an edit takes it out. A picture over 1 MB is made smaller rather than refused, and so is a profile picture, which may now be a WebP or GIF too.

#### A pull's diff over MCP numbers its lines

The MCP server's `pull_diff` tool now leads each line of a hunk with its line number in the old file and in the new, as `comment`'s `lines` takes them: a `+` line is `side: new` at its new number, a `-` line `side: old` at its old number. An agent no longer counts lines from the `@@` headers to comment on one.

#### Branches that take pulls only through the merge queue

`knotless protect <url> <branch> --queue` makes a branch take pulls only through the merge queue, as GitHub's "Require merge queue" does: `knotless merge` (and MCP `merge`) queues a pull into it instead of merging, saying so, and only the owner's `--force` merges at once. The gateway moves such a branch for no collaborator but the runner that lands the queue. `knotless rules` shows `queue`, MCP `protect_branch` takes it, and the repository settings' Branches page has a "Require the merge queue" checkbox for each protected branch, whose pulls' pages then offer only the queue.

#### Repositories show their releases

A repository's pages show what it publishes: its latest release and its packages under its name, and a Releases tab listing every version, with its notes, files and, for an image, the `docker pull` command. knotless-serve indexes `dev.knotless.release` records and answers `dev.knotless.listReleases`.

#### Repositories show their links

A repository can name up to five links, its website first, as a profile names its links: `knotless create <url> --link <url> [--title <text>]`, and `knotless describe <url> --link …` to replace them, or `--link ''` to drop them. The MCP `create_repo` and `update_repo` tools take `links`, and its settings page has a Links field. Its pages show the first link under the description, before its latest release, with a count of any others that opens the list of them all. `knotless describe` prints a `link:` line for each.

#### Protected branches can require reviews, from code owners too

`knotless protect <url> <branch> --reviews <n>` makes a pull merge into the branch only once `<n>` of the owner and collaborators, other than its author, approve it, and while none of them has asked for changes. `--code-owners` also asks, for each path it changes that the branch's CODEOWNERS file (`.knotless/CODEOWNERS`, `CODEOWNERS` or `.github/CODEOWNERS`) gives owners, for one of them to approve it; `--dismiss-stale` counts only approvals of the pull's head as it is now. `merge`, `merge --check` (which lists what is missing, and CODEOWNERS lines it skipped), the merge queue, MCP `protect_branch` and `merge`, and the pages' branch settings and merge box all follow it. The owner's `--force` merges past it.

#### Revert a merged pull

`knotless revert <url> <pull at-uri> [-m <reason>]` undoes a merged pull as GitHub's Revert button does: one commit that undoes everything its merge brought in, on a new branch `revert/<rkey>` cut from its target branch, and a pull of that branch into it, titled `Revert "<title>"`, its body linking the merged pull and saying why. It prints the new pull's URI; check and merge it, or queue it, like any pull. It stops on a conflict, naming the paths. The owner and collaborators may. The `revert` MCP tool does the same, and a merged pull's page has a Revert button.

#### Reviews stand as GitHub's do

Each reviewer's latest verdict is where they stand: a later `--approve` or `--changes` replaces an earlier one, and a comment without a verdict changes nothing. `knotless show` lists each reviewer as a `review:` line with the head they looked at, and the MCP `list_items` and `show_item` give an item's `reviews` in place of `approvals`. You can no longer review your own pull, or an issue. The pages show reviewers in a pull's merge box, and a review of a head the pull has since moved past as one of an earlier version.

#### Sharing for writing delegates first, and reviews say more

`knotless share --write` and MCP's `share` delegate your writes to the appview's gateway first when you have not, approved in the browser as `delegate` is, and say they did. Notifications name what each is about (the pull or issue, or for a star the repository) and its repository as `handle/name`; a review says how many line comments it holds and whether it mentions you. `show_item` gives each line comment its `side` and `startLine`, `resolved` only on threads, `superseded` on a review a later one replaced, and whether each requested review is still `pending`; `list_items` says `draft: false` too. A pull whose only code owner is its author says why it can never have that approval, and `protect_branch` and `list_rules` warn when you alone own a path.

#### Tag a commit without a clone

`knotless tag <url> <name> [<from>]` tags a commit, the default branch's or that of a branch, tag or commit, and prints it; `--delete` deletes a tag. The MCP server's `create_tag` and `delete_tag` tools do the same. Each release of knotless is now tagged `v<version>` at the commit it was built from.

#### The branch menu switches to tags too

On a repository's code, commits, file and blame pages, the menu that switches branches has a Tags tab beside Branches, as GitHub's does, to see the same page at a tag. Viewing a tag, the menu's button shows the tag icon and opens on Tags.

### Fixes

#### Buttons show their action under way

On the pages, the button you pressed shows a spinner and says what it is doing, such as "Inviting…" or "Joining…", until the server answers. The rest wait, dimmed. This covers organizations and their invitations, notifications, merging, settings, labels, reactions and the rest.

#### Commands and MCP tools say when your login is out of date

While your login is older than what knotless asks for now, every command ends by saying so and naming the `knotless login` to run. An MCP tool a PDS refuses for want of scope tells the agent to have you sign in again, by reconnecting knotless in your assistant or running `knotless login`.

#### Deleting a repository leaves nothing of it

`knotless delete`, the `delete_repo` tool and the pages' Delete now also delete the repository's releases and reruns, and what you wrote about it yourself: your pulls, issues, comments, states, labels, hides, queue entries and blocks naming it, your star of it, and your reactions to those. The repository's own record goes last, so a delete cut short carries on when run again. knotless.dev drops what it kept of a deleted repository, its mirror included, and a runner drops its checks, caches and secrets grants. Logging in again asks to write `dev.knotless.release` records, which `knotless publish` needs with OAuth.

#### Organization changes take fewer round trips

Inviting, removing and leaving wait on fewer calls to a PDS. The gateway reads what it needs at once rather than one after another, and no longer tries to make the organization's space each time. On the pages, a change this server's gateway made is no longer indexed a second time.

#### Handle suggestions when inviting and sharing

On the pages, the fields that take someone else's handle suggest accounts as you type, as the login dialog does: inviting to an organization, adding a collaborator or a reader, and offering a repository to another account.

#### Say how to grant what a login lacks

A command a PDS refuses because your login is older than what knotless asks for now says so, and to run `knotless login` again. Logins now keep the scope they asked for, and the pages offer to log in again, or to delegate again, when knotless asks for more.

#### Collaborators and organization members hear of new pulls and issues

A pull or an issue opened on a repository is now a notification for its collaborators, and for the members of the organization that owns it, hidden members too, as it was for its owner.

#### Suggestions for @ while writing

Typing `@` in a comment, a review, an edit or a new pull or issue on the pages suggests whom to mention: those in the thread, then whom you follow, then accounts whose handles match. Arrows choose, Enter or Tab picks, Escape closes. A mention an edit adds is heard of as of the edit, not as of when the record was first written.

#### A new repository never replaces one of the same name

`create`, `fork`, `accept` and `rename` refuse a name a repository of yours already has, public or private, with `<url> already exists`, before writing anything. On a PDS that took a create over an existing record as an update, creating a repository under a taken name replaced the old one's record and orphaned its history. The record is now written with a compare and swap that the PDS refuses when any record is there. On the web, the new repository form, the fork menu and the rename setting say as you type whether the name is free, and hold their button while it is taken.

#### Organization changes read at once

Joining, inviting, removing and ending an organization read what they need at the same time instead of one after another. They also read the organization's space once instead of two or three times.

#### Pasted pictures show in the preview

A picture pasted or dropped into a pull's, an issue's or a comment's box shows in its preview, and a profile picture picked shows before it is saved: the pages let a picture be drawn from what was pasted or picked.

#### Notification settings live with your other preferences

`knotless notifications settings` and the `notification_settings` MCP tool read and change which notifications you hear of through the appview's preferences (`dev.knotless.getPreferences` and `putPreferences`), which also keep the theme you choose on the pages. They behave as before, but need an appview at this version or later. Notification settings made before this were not carried over: everyone starts from hearing every reason, from anyone.

#### Say why a private repository can't be made yet

`knotless create --private`, and the `create_repo` MCP tool with `private`, on a PDS without spaces now say that your PDS can't hold private repositories yet, rather than passing on its `UpstreamFailure` or `MethodNotImplemented`. Nothing is made.

#### Notifications from private repositories reach members who never logged in to the appview

Members of a private repository who use only the command line or MCP now hear of what is said there: a mention, a comment on their issue, a reply. Writing a pull, an issue, a comment, a review or a state to a private repository, or sharing one, now shows the appview the repository with a delegation token, so its index has what was written. Sharing a private repository to read now names the account in the record's new `readers`, which the appview takes as the owner's word that they may hear of it. Someone mentioned there who is not a member still hears nothing.

#### A merged pull's diff once its branch is gone

The MCP `pull_diff` tool on a merged pull answers what its merge brought in, from the commits its merge recorded, rather than failing once merging deleted its branch.

#### Pushes through a gateway are credited to whoever made them

A push or commit a collaborator or runner makes through a gateway names them as `pusher` on the claims and refs it writes, and the feed and activity pages credit it to them rather than to the repository's owner, while the repository lists them as a collaborator or runner.

#### A reverted pull names its revert

Once a revert pull merges, the pull it undid names it: `knotless show` says `reverted by`, as do the MCP `show_item` (`item.revertedBy`) and the pull's page. A merged pull's `compare` in `show_item` is what its merge brought in, even once its branch is deleted, and the MCP `diff` tool says when there are no differences rather than answering nothing.

#### Usage keeps who did what for 30 days

knotless-serve's usage counts (`--usage`) delete the accounts they noted for MCP, the gateway and the pages' logins once 30 days have passed, keeping only counts. In `knotless admin stats`, an event's `accounts` are of the days asked, at most the last 30, and a funnel starts with an account that had not done its first step in the 30 days before.

## 0.1.1 (2026-10-28)

### Features

#### Container images as releases

`knotless publish --oci <dir>` publishes an OCI image layout as a release, each blob named for its digest, and knotless-serve answers `docker pull <host>/<owner>/<repo>/<package>:<version>` from those releases at `/v2/`.

#### Agents publish and read releases

The MCP tools `publish`, `releases` and `read_release` publish a version of a package, list a repository's releases, and read one file of a release, as `knotless publish`, `releases` and `download` do.

#### Images can be pushed to knotless-serve's registry, which lists their tags

`docker login` with the owner's handle and an app password, then `docker push` to a version's tag, publishes the image as that version's release; `tags/list` lists an image's versions.

#### Releases as records

`knotless publish` publishes a version of a package from a repository, as `dev.knotless.release` records in its owner's account, never changed once made; `knotless releases` lists them and `knotless download` fetches and checks a version's files.

#### knotless-serve installs from a repository's releases

`--releases knotless://<owner>/<name>` serves `/install.sh` and `/releases/` from that repository's releases of the `knotless` package, each file checked against its record and kept, rather than from a directory.

### Fixes

#### Releases stream

`knotless publish` reads each file a part at a time as it uploads it, and `knotless download` writes each part as it reads it, beside the file until it is whole and checked, so a file of any size takes only a few parts' memory.

#### A runner's pass that runs long says where it is

`knotless-run` logs each pass over a repository as `pass ended`, with how long it took, and one still going after 5 minutes as `pass running long`, with the stage it is at, every 5 minutes. A git command slower than `KNOTLESS_SLOW_MS` is logged at warn, as `git`. The same failure of a repository is logged once a minute while it goes on, with a count, rather than every time.

#### knotless-serve's image is published as a release

Each new version's image is the release of the `knotless-serve` package: `docker pull knotless.dev/chadtmiller.com/knotless/knotless-serve`.