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
52 changes: 49 additions & 3 deletions cloudflare/GITHUB.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ and decision inbox. It links the registered App's installation URL and lists
the repositories to pick from: those the App's installations reach and the
user administers or maintains, each with its contribution state, an Enable
button, or why it cannot be enabled yet (archived, private, no Quest files,
or no OSI-approved license). A repository without Quest files links `/setup`.
or no OSI-approved license). A repository without Quest files enables through
its [setup pull request](#setup-pull-request).

The picker is navigation, built from the user's own GitHub App token so its
cost does not grow with an installation's size: one read of the user's
Expand Down Expand Up @@ -445,8 +446,9 @@ external request per file. Blob IDs come from the pinned Git tree; missing,
binary, truncated, partial, or hash-mismatched content fails closed. This keeps
enrollment's external request count independent of the number of quest files.
It rejects private,
unavailable, incomplete, invalid, and symlinked quest snapshots. Repositories
without Quest files follow `/setup`; no repository writes occur during setup.
unavailable, incomplete, invalid, and symlinked quest snapshots. A repository
without Quest files gets the App's [setup pull request](#setup-pull-request)
instead; nothing else in enrollment writes to the repository.

The D1 enrollment records a project-wide enabled/paused state, distinct from
installation permissions. Every opt-in/resume, pause, and disconnect rechecks
Expand Down Expand Up @@ -496,6 +498,50 @@ See the official [GitHub license API](https://docs.github.com/en/rest/licenses/l
[GraphQL blobs](https://docs.github.com/en/graphql/reference/git#blob), and
[App metadata API](https://docs.github.com/en/rest/apps/apps#get-the-authenticated-app).

### Setup pull request

On a repository whose default branch has no Quest files, **Enable** opens a
short form holding one optional question: what the project is working toward.
Submitting it opens the App's setup pull request, with no runner involved
(`src/github/setup.ts`). The core's `planInit`, the same file plan
`quest init` applies to a checkout, plans it from the default branch's tree:
the skill stubs this deployment's `quest` ships (rendered into
`build/stubs.json` at build time), `quest/README.md` with the answer as its
Goal, the `Quests: ` line in the root agent instructions, the `.gitattributes`
line, and Claude Code's hook entries in `.claude/settings.json`. Heading
syntax in the answer is escaped, and the planned `quest/README.md` must pass
`quest check`, so a goal that would fail it, such as one linking a missing
quest, is refused before anything is written instead of when enabling. A path init would refuse, such as a skill
directory that is not a Quest stub or invalid settings JSON, is named on the
page and nothing is written; kix never deletes or renames a repository's
files. The plan does not follow symlinks, since a Git tree stores their targets
as data: one on a planned path is refused the same way, pointing at running
`quest init` in a clone.

The App publishes the plan as for job branches: blobs, a tree, and a commit
through the Git Data API on `kix/setup`, outside the quest claim namespace,
then a ready pull request against the default branch. Its body lists each
change and what it leaves out, because those steps need judgement about the
repository: pinning `quest` in its tool manager and adapting existing
instructions. kix's jobs need neither, since runners bring their own `quest`.
Without a `flake.nix`, the body says the `bootstrap` job adds one after the
merge.

Until the pull request merges the project is enrolled, setting up
(`github_project_setups`), which the project page and picker show with the pull
request's link. Every open, retry, and enable rechecks the maintainer's
authority, the repository's visibility, and its license live. A `pull_request`
webhook that closes or reopens it only asks the service to look: it rereads the
pull request and, once merged, enables contributions through the same live
checks as **Enable**, including `quest check` on the merged tree, with the
enroller's authority, so no second visit is needed. The project page reconciles
the same way when a webhook was missed, and keeps the reason when enabling
fails. A pull request closed without merging leaves the project setting up,
offering to open it again from the current default branch or to disconnect.
Opening again replaces `kix/setup` only while it is still at a commit the App
recorded writing there; a branch with anyone else's commits is refused, naming
it. Disconnecting leaves the pull request and its branch on GitHub.

The setup preview does not reserve or launch ready quests.
The jobs/funding quests own live eligibility checks and contribution execution.

Expand Down
5 changes: 4 additions & 1 deletion cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ renders the repository's `docs/*.md` into `build/docs.json`, so the Markdown
stays the single source and its relative links keep working on GitHub: links to
other docs become `/docs/<name>` and other repository paths point at GitHub. It
also snapshots this repository's `quest/` tree and finished quests into
`build/board.json`, which supplies the public board. Enrolled repositories use authenticated
`build/board.json`, which supplies the public board. It renders the skill
stubs `quest init` installs into `build/stubs.json`, for the setup pull request
a repository without Quest files gets when enrolling
([GITHUB.md](GITHUB.md#setup-pull-request)). Enrolled repositories use authenticated
GitHub snapshots through the same board model. A shallow clone has no
history, so its board shows no finished work. It also copies `design/theme/`,
`src/site.css`, the scripts, and the web app manifest into `build/public/`,
Expand Down
4 changes: 4 additions & 0 deletions cloudflare/bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# The bundled skills import as text, as in the root bunfig.toml, so the build can render their stubs.
[loader]
".md" = "text"

# Scripts run on Bun even when a tool declares a Node shebang, as in the root bunfig.toml.
[run]
bun = true
23 changes: 23 additions & 0 deletions cloudflare/migrations/0021_project_setups.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- A repository without Quest files that a maintainer chose to enroll: the
-- App's setup pull request, which writes what `quest init` would. The project
-- is enrolled, setting up, until that pull request merges, when the project
-- moves to github_projects as enabled and this row goes. A closed pull request
-- leaves the row, offering a retry or a disconnect. It never grants authority:
-- opening, retrying, and enabling each recheck GitHub live.
CREATE TABLE github_project_setups (
id INTEGER PRIMARY KEY NOT NULL,
owner TEXT NOT NULL,
name TEXT NOT NULL,
installation INTEGER NOT NULL,
-- The branch's commit kix last published, and one it is moving the branch to,
-- so a retry recognizes kix's own branch after an interrupted update.
head TEXT,
pending TEXT,
pull INTEGER,
state TEXT NOT NULL CHECK(state IN ('open','closed')),
-- Why the merged setup could not enable contributions, until a retry succeeds.
failure TEXT,
enrolledBy TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE,
updatedAt INTEGER NOT NULL
);
CREATE INDEX github_project_setups_user ON github_project_setups(enrolledBy);
3 changes: 3 additions & 0 deletions cloudflare/scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Build-time inputs for the Worker, written to build/ (ignored by Git):
// - build/docs.json: the repository's docs/*.md rendered to HTML.
// - build/board.json: this repository's quest tree and finished quests, for the board.
// - build/stubs.json: the skill stubs `quest init` installs, for the setup pull request.
// - build/public/: static assets: the theme from design/theme/, src/site.css, the
// scripts src/passkey.js, src/app.js, and the service worker src/sw.js, and the
// web app manifest.
Expand All @@ -10,6 +11,7 @@ import { cpSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } f
import { join, posix } from "node:path";
import { Lexer, Marked, type Token, type Tokens } from "marked";
import type { Snapshot } from "../src/board/project";
import { SKILLS } from "../../src/cli/skills.ts";
import { LOG, finished, isQuest } from "./history.ts";

const repo = "https://github.com/moq-dev/quest";
Expand Down Expand Up @@ -113,6 +115,7 @@ rmSync(build, { recursive: true, force: true });
mkdirSync(join(build, "public"), { recursive: true });
writeFileSync(join(build, "docs.json"), JSON.stringify(rendered));
writeFileSync(join(build, "board.json"), JSON.stringify(snapshot));
writeFileSync(join(build, "stubs.json"), JSON.stringify(SKILLS.map(({ installed, stub }) => ({ installed, stub }))));
cpSync(join(design, "theme"), join(build, "public", "theme"), { recursive: true });
cpSync(join(worker, "src", "site.css"), join(build, "public", "site.css"));
for (const file of ["passkey.js", "app.js", "sw.js", "manifest.webmanifest"])
Expand Down
Loading
Loading