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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ name: Check
on:
push:
branches: [main]
# Drafts are claims with local checks; CI starts once a PR is ready for review.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

permissions:
Expand All @@ -15,6 +17,7 @@ concurrency:

jobs:
check:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
Expand All @@ -35,6 +38,7 @@ jobs:

lifecycle:
name: lifecycle (${{ matrix.runner }})
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
Expand All @@ -56,6 +60,7 @@ jobs:

windows:
name: native Windows (${{ matrix.runner }})
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
Expand Down
73 changes: 54 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ finished plan stays in Git history, beside the code that completed it.
Work is taken through a live runner lock or an open change on the forge.
kix.quest records each active job in its database and reassigns a job whose
runner goes offline. On GitHub, a draft PR
opened from the quest's branch, including from a fork, claims the work. These
states stay out of the plan file.
opened from the quest's branch, including from a fork, claims the work;
`quest start` opens one. These states stay out of the plan file.

## Try it

Expand All @@ -89,17 +89,16 @@ quest tree. From this checkout:
$ quest --root examples/export check
The quest log is in order: 4 documents checked.

$ quest --root examples/export ready
1 quest ready
! [S] Add a CSV export endpoint quest/m0/export.md
$ quest --root examples/export unblocked
1 quest/m0/export.md

$ quest --root examples/export ready quest/m0/download.md
$ quest --root examples/export unblocked quest/m0/download.md
! [S] Add a download button quest/m0/download.md
Required:
[S] Add a CSV export endpoint quest/m0/export.md
```

With no path, `ready` lists every ready quest. Given a path, it shows what
With no path, `unblocked` ranks every ready quest. Given a path, it shows what
blocks that quest. On a terminal, a yellow `!` marks a ready quest and a grey
`!` a blocked one, and each size label takes its colour, like loot rarity: XS
grey, S green, M blue, L purple, and XL orange (your terminal's yellow). Set
Expand All @@ -108,19 +107,13 @@ grey, S green, M blue, L purple, and XL orange (your terminal's yellow). Set
Scripts and agents read the piped form, which is plain text:

```console
$ quest --root examples/export ready | cat
quest/m0/export.md

$ quest --root examples/export ready quest/m0/download.md | cat
$ quest --root examples/export unblocked quest/m0/download.md | cat
quest/m0/export.md
```

Piped, `ready` prints one path per line. Given a path, it prints that quest's
blockers, with an explanation on stderr; no blockers means no stdout.
It exits zero for both ready and blocked quests; a nonzero exit means the
command failed. Run `check` first to catch malformed plans. `ready` reads
dependencies in the quest tree; it does not inspect live runner locks, open changes, or GitHub branch
claims. Check those before you start work someone else may already be on.
Piped, a path prints that quest's blockers, with an explanation on stderr; no
blockers means no stdout. It exits zero for both ready and blocked quests; a
nonzero exit means the command failed.

## Prioritize ready work

Expand All @@ -142,10 +135,52 @@ $ quest --root examples/export unblocked --json --limit 1
Each plain output line contains the count, a tab, and the path. On a terminal,
a heading explains the count. `--json` prints an ordered array, including `[]`
when no quests are ready; `--limit <count>` requires a positive integer.
Invalid trees fail with diagnostics and no ranking. This command uses the same
local readiness as `ready`; verify claims and outside conditions before starting.
Invalid trees fail with diagnostics and no ranking.
`quest-spawn` uses the ranking unless you explicitly select work in another order.

`--unclaimed` drops quests someone already holds: the quest's branch on the
upstream repository, or an open PR from that branch in any repository, forks
included. It lists each one it skips on stderr with its PR and how long ago
that PR was last active, so you can decide whether a stale claim should be
taken over. In a fork's clone, the upstream is the fork's parent, and the
fork's own branches and PRs count too; `--remote` picks a remote other than
`origin`. Off GitHub, only branches are checked.
Neither form inspects kix.quest runner locks or outside conditions.

## Claim a quest

`quest start` claims a ready quest so every claim is visible on GitHub:

```sh
quest start quest/m0/export.md
quest start --release quest/m0/export.md
```

It pushes an empty claim commit, built on the upstream default branch, to the
quest's branch (its path without `.md`) without forcing, then opens a draft PR
from it right away and prints the PR's URL. It switches your checkout to the
claim on a new local branch, or fast-forwards that branch if it is already
checked out. A fork pushes to itself and opens the PR on its parent. It fails
on a tree `quest check` rejects, which can hide a blocker, and refuses a
blocked quest and one someone else holds; a rejected push means
another claimer got there first, and when two PRs race, the earliest open one
keeps the claim and the later one is closed. Off GitHub, it pushes the branch
without a PR.

`--release` deletes the branch, with a lease on its tip, and closes its PR, but
only while the tip is still an empty commit. Once work is on the branch, hand
the draft back instead: a `## Decisions needed` section in its body marks it as
waiting on the maintainer.

`quest start` exits 0 when done, 3 when it refuses (blocked, held, or work on
the branch), 2 for invalid arguments, and 1 for other failures. On GitHub it
uses `GH_TOKEN`, `GITHUB_TOKEN`, or `gh auth token`.

Draft claims are placeholders, so CI can skip them. This repository runs its
checks only on PRs ready for review: the `pull_request` trigger lists
`ready_for_review`, and each job has the condition
`github.event_name != 'pull_request' || github.event.pull_request.draft == false`.

## Move and delete quests

`quest mv` and `quest rm` move or delete a quest or an epic with `git mv` and
Expand Down
9 changes: 9 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ Read the root instructions with the new line in place:
- If `quest/README.md` was just created, ask the user what the project is
working toward and write that as its Goal.

`quest start` claims each quest with a draft PR that holds only an empty
commit. If the repository's CI runs on pull requests, recommend skipping
drafts, since agents run the checks locally until a PR is ready for review. In
GitHub Actions, add `ready_for_review` to the `pull_request` trigger's `types`
(alongside the defaults `opened`, `synchronize`, and `reopened`) and give each
job the condition
`github.event_name != 'pull_request' || github.event.pull_request.draft == false`.
Change workflows only if the user agrees.

Run `quest check`, then suggest next steps: restart the agent session so it
loads the new skills, then `/quest-plan` (`$quest-plan` in Codex) to plan
work or `/quest-import` to bring in open GitHub issues.
Expand Down
31 changes: 17 additions & 14 deletions assets/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,35 +98,38 @@ Unlike a blocked quest it stays ready, so it resurfaces every time ready work is
## Execution

Start only ready quests.
`quest ready [<path>]` prints what blocks a quest, or every ready quest.
`quest unblocked` ranks the ready quests, and `quest unblocked <path>` prints what blocks one.
With `--unclaimed`, it skips quests someone holds and lists them on stderr.

Work is taken when it has a live runner lock or an open change on the forge.
kix.quest records each active job in its database and reassigns a job whose
runner goes offline. Check those locks and open changes before
starting. These states stay out of the quest file.

On GitHub, a quest is claimed by an open PR whose head branch is the quest's
branch, from the upstream repository or a fork
(`gh pr list --repo <upstream> --head <quest-branch>`), or by that branch
upstream while its PR is being opened.
Check both against the upstream repository; in a fork's clone, `origin` is
usually the fork.
To claim a quest, push an empty commit to its branch, upstream if you have push
access and otherwise on your fork, then open a draft PR from it right away.
branch, from the upstream repository or a fork, or by that branch upstream
while its PR is being opened.
The earliest open PR holds the claim.
Claim a quest with `quest start <quest>`: it checks both, pushes an empty
claim commit to the quest's branch, and opens a draft PR from it right away.
That PR becomes the quest's PR.
Then recheck: the earliest open PR holds the claim, and a later one closes and
stops.
If an upstream branch looks stale (old, with no open PR), offer the user to take
it over.
In a fork's clone, it pushes to the fork and opens the PR upstream.
It exits 3 when the quest is blocked or someone else holds it; pick other work.
`quest start --release <quest>` gives back a claim before any work is pushed.
If a claim looks stale, such as a draft without `## Decisions needed` or a
branch with no open PR that has been quiet a long time, offer the user to take
it over, saying how long ago it was last active.

A quest's branch is its path without `.md`.
For example, `quest/m1/foo/bar.md` is branch `quest/m1/foo/bar`.

Push claims and updates with an explicit refspec: `git push <remote> refs/heads/<quest-branch>:refs/heads/<quest-branch>`.
Push updates with an explicit refspec: `git push <remote> refs/heads/<quest-branch>:refs/heads/<quest-branch>`.
A bare branch name can push to the base branch instead, for example under `push.default=upstream` when the quest branch tracks it.

Run the repository's checks locally while the PR is a draft; CI may run only on PRs ready for review.
When a quest is complete with no open decisions, mark its PR ready for review.
Otherwise keep it a draft and list its open decisions, or what remains unfinished, in the body.
Otherwise keep it a draft and hand it back: add a `## Decisions needed` section to the body listing its open decisions, or what remains unfinished.
Tools read only whether that heading is present; a draft without it still belongs to its starter.
A draft suggests `/quest-iterate`; a PR ready for review suggests `/quest-merge`.
Include a summary of the changes made and suggest follow-up quests based on issues encountered.
Add a closing keyword for every issue under `Closes`.
Expand Down
5 changes: 3 additions & 2 deletions assets/skills/spawn.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ Before you begin, run `quest guide` and read its output completely.

Your goal is to execute many quests in parallel.
The scope consists of all ready quests that are not claimed.
Run `quest unblocked --json` for ready candidates in milestone priority, descending downstream impact, then tree order.
Run `quest unblocked --unclaimed --json` for unclaimed ready candidates in milestone priority, descending downstream impact, then tree order.
The count is distinct transitive dependents; other prerequisites may still remain.
Use that order when dispatching, and recompute after the quest tree changes.
An explicit argument takes precedence: filter to the selected quests/epics and honor their requested order.
Ranking does not establish that work is unclaimed: verify live locks, open changes (including PRs from forks), and remote branch claims before starting.
It lists each skipped claim on stderr with how long ago it was last active; offer the user any that look stale, as the guide describes.
Each agent claims its quest with `quest start`, which refuses one claimed since; a refusal is a skip, not a failure.
For a quest waiting on an outside condition, check whether it has cleared before recommending.

Start every quest you would recommend starting, without asking: well planned, with no blockers or conflicts with quests already started.
Expand Down
7 changes: 3 additions & 4 deletions assets/skills/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ The argument is the quest to work on.

If you are unsure on the best course of action, ask the user for direction.

Update your checkout, run `quest ready`, then confirm the quest is still ready and unclaimed.
Check the forge for a live runner lock or an open change; on GitHub, check the quest's upstream branch and open PRs from any fork.
Confirm you can write in your worktree; if you cannot, stop and report without claiming.
Claim it as `quest guide` describes.
Release work you cannot finish; on GitHub, close its claim PR, delete its branch, and remove your isolated worktree.
Update your checkout and check the forge for a live runner lock, then claim the quest with `quest start <quest>`.
If it refuses, the quest is blocked or someone else holds it: report what it printed and stop.
If you cannot pursue a claim before pushing work, release it with `quest start --release <quest>` and remove your isolated worktree; once work is pushed, hand it back as the guide describes.

Implement the quest until it is complete, or some blocker is hit, then push to its PR and update the description.
Never write to or clean up a directory other agents share, such as a session scratchpad.
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ its epics and quests with their statuses, filtered by status with
`?show=available` and the like. Each quest has a page at its path without
`.md`, such as `/repos/quest/quest/m0/kix/prompts`: its Markdown rendered
with raw HTML escaped, its status, and what to do next. Readiness comes from
`quest/core`, so the board agrees with `quest ready`. Progress counts finished
`quest/core`, so the board agrees with `quest unblocked`. Progress counts finished
quests, which merges delete, from Git history (`scripts/history.ts`), weighted
by size. A deletion counts only when its commit changed something outside
`quest/`, since deleting only plans abandons a quest.
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/src/board/model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The board's reading of a project: milestones, epics, and quests, each with its
// status and progress. Readiness comes from the shared core, so the board
// always agrees with `quest ready`.
// always agrees with `quest unblocked`.

import { type Doc, ROOT, blockers, children, comparePaths, entries, isEpic, parse, permanent, ready } from "quest/core";
import type { Size } from "../layout";
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/src/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function Home(props: { setup: string; board: Board }) {
Available
</p>
<p>
<code>quest ready</code> lists the quests with nothing standing in their way.
<code>quest unblocked</code> lists the quests with nothing standing in their way.
</p>
</li>
<li>
Expand Down
12 changes: 6 additions & 6 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ From your repository root, validate the result and list the ready quests:

```sh
quest check
quest ready
quest unblocked
```

Review the plan before setting out. `/quest-start` claims one quest and opens a
pull request; `/quest-spawn` sends several agents out in parallel when
your session supports them. Use `$quest-start` and `$quest-spawn` in Codex.
Check the forge for a live runner lock or an open change before starting.
kix.quest records each active job in its database and reassigns a job whose
runner goes offline. On GitHub, check the upstream repository for the quest's
branch and for open PRs from it, including PRs from forks. `quest ready`
reads dependencies in the quest tree; it does not look for these live states.
Both claim through `quest start`, which pushes the quest's branch and opens a
draft PR right away, and refuses a quest whose branch or PR, including one from
a fork, already exists. `quest unblocked --unclaimed` lists the ready quests
nobody holds. kix.quest records each active job in its database and reassigns a
job whose runner goes offline; check it for a live runner lock before starting.

You review each change: `/quest-ready` takes a draft to review, and
`/quest-merge` (`$quest-merge` in Codex) merges it once checks and reviews pass.
Expand Down
1 change: 0 additions & 1 deletion quest/m0/helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ commands from Sep-Oct. Session counts in each child come from that window.
## Required

- [CI checks in wait](/quest/m0/helpers/wait-checks.md) - `quest wait` wakes on required check results for the head
- [quest start](/quest/m0/helpers/start.md) - claim a quest with a draft PR, the `## Decisions needed` hand-back, `quest unblocked --unclaimed`, and CI on ready PRs only
- [PR state commands](/quest/m0/helpers/pr-states.md) - `quest iterate`, `ready`, `merge`, and `release` label the one owner of a PR
- [Headless agents](/quest/m0/helpers/headless-agents.md) - `/quest-spawn api` launches, resumes, and stops billed agents through one command auto mode allows

Expand Down
8 changes: 2 additions & 6 deletions quest/m0/helpers/pr-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ way in shows `quest:merge` on GitHub.
## Plan

Decided 2026-10-20 (user) from the transcript audit recorded in
[quest start](/quest/m0/helpers/start.md). Overlapping merge sessions in one
[quest start](https://github.com/kixelated/quest/blob/d265755/quest/m0/helpers/start.md). Overlapping merge sessions in one
repository drew "another agent was merging, I told them to stop, can you take
over anything needed?", and the same-machine guard flagged sessions in other
repositories ("those are in another repo?").
Expand All @@ -23,7 +23,7 @@ repositories ("those are in another repo?").
- Decided (user): a label per state, with no owner comment:
`quest:iterate`, `quest:ready`, and `quest:merge`. At most one is on a
PR. Planning PRs get none. A draft that its starter still holds, one with no
`## Decisions needed` section (see [quest start](/quest/m0/helpers/start.md)),
`## Decisions needed` section (the guide's Execution section),
counts as owned too, so every command refuses it until the starter hands
back; taking it over from a stale starter is the user's choice. Decided
2026-10-21 (user), replacing a `quest:start` label.
Expand Down Expand Up @@ -69,7 +69,3 @@ repositories ("those are in another repo?").
with a stale token, a refused swap, landing on a moved head, a PR
leaving the queue, and a repository whose root instructions override
landing.

## Required

- [quest start](/quest/m0/helpers/start.md) - the hand-back marker and the freed `ready` name
Loading
Loading