diff --git a/README.md b/README.md index 3cf4c75c..9de74eeb 100644 --- a/README.md +++ b/README.md @@ -1335,7 +1335,7 @@ The experimental `quest worktree` commands share one disk budget across this user's repositories and release a checkout's disk by deleting its declared scratch in place. They only operate on Git worktrees they create; use the owning application for Codex or other managed checkouts. Claude Code's -checkouts are Quest's own once `quest init` installs its hooks (see +checkouts are Quest's own once you opt into its hooks (see [Claude Code checkouts](#claude-code-checkouts)). Set a shared capacity and an explicit reservation in bytes, covering the @@ -1436,9 +1436,24 @@ stolen. This initial interface refuses unknown or changed checkout ownership. ### Claude Code checkouts -`quest init` adds hook entries to the repository's `.claude/settings.json` that -run `quest worktree hook` for Claude Code's `WorktreeCreate`, `WorktreeRemove`, -`SubagentStart`, and `SubagentStop` events, so `quest` must be on the `PATH` +Quest's hooks for Claude Code are an opt-in each user adds to their own +settings, `~/.claude/settings.json`, so they apply in every repository that +user opens and no repository forces them on its contributors. Merge these +entries into the file's `hooks`: + +```json +{ + "hooks": { + "WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }], + "WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }], + "SubagentStart": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }], + "SubagentStop": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }] + } +} +``` + +Only one hook can manage Claude's worktrees, so remove any other +`WorktreeCreate` or `WorktreeRemove` hook first. `quest` must be on the `PATH` Claude Code runs hooks with. For `claude --bg` and agent view, that is the environment of the shell that started the session. The hook then creates every worktree Claude makes in the repository: for `--worktree`, `EnterWorktree`, isolated subagents and diff --git a/SETUP.md b/SETUP.md index cdab05f7..5c7326ad 100644 --- a/SETUP.md +++ b/SETUP.md @@ -48,10 +48,7 @@ without creating symbolic links (existing linked layouts continue to work), crea starting with `Quests: ` to the root `AGENTS.md`, or `CLAUDE.md` when only that exists. It also appends `quest/** merge=quest` to `.gitattributes` and sets `merge.quest.driver` in the clone's git config, so local merges resolve -concurrent edits to quest link lists. It adds hook entries running -`quest worktree hook` to `.claude/settings.json`, keeping the file's other -settings, so Quest creates and reclaims Claude Code's worktrees; Claude Code -then needs `quest` on its `PATH` to create any worktree in the repository. It +concurrent edits to quest link lists. It writes no Claude Code hooks. It prints each path it changed and is safe to rerun. Rerun after an upgrade, it also rewrites the unmodified stubs whose description or setup link changed and removes the unmodified stubs of skills the new version no longer ships. @@ -59,6 +56,18 @@ removes the unmodified stubs of skills the new version no longer ships. If init refuses, it names the conflicting path. Report it and ask the user how to proceed; never delete or rename their files to get past it. +## Opt into worktree hooks + +Quest can create and reclaim Claude Code's worktrees, sharing one disk budget +across the user's repositories, through hooks in the user's own Claude Code +settings rather than the repository's, since repository settings apply to +every contributor. Offer them, explaining that they then apply in every +repository the user opens in Claude Code and need `quest` on the `PATH` +Claude Code runs hooks with. With consent, merge the entries in +[Claude Code checkouts](README.md#claude-code-checkouts) into +`~/.claude/settings.json`, keeping its other settings. If it already has a +`WorktreeCreate` or `WorktreeRemove` hook, ask the user which to keep. + ## Adapt the instructions Read the root instructions with the new line in place: @@ -102,11 +111,13 @@ work or `/quest-import` to bring in open GitHub issues. 1. Run `quest skill export` and follow it, so active quests become GitHub issues. Skip this only if the user says so. 2. Run `quest uninstall`. It removes only unmodified stubs of any version, the - `Quests: ` line, Quest's hook entries, and the merge driver, and never - deletes the quest tree. + `Quests: ` line, and the merge driver, and never deletes the quest tree. 3. Remove the pin added above: the mise entry, or the flake input and dev shell package. For an unpinned install, delete the `quest` binary the installer placed (`~/.local/bin/quest` by default, or `quest.exe` in the - chosen Windows install directory). Remove any PATH entry you added. + chosen Windows install directory). Remove any PATH entry you added. If the + user opted into Quest's worktree hooks and Claude Code no longer finds + `quest`, remove those entries from `~/.claude/settings.json`, since without + it they stop Claude Code making worktrees. 4. Ask the user whether to delete `quest/`. Git history keeps the plans either way. diff --git a/cloudflare/GITHUB.md b/cloudflare/GITHUB.md index f0a8ef27..7f53d50a 100644 --- a/cloudflare/GITHUB.md +++ b/cloudflare/GITHUB.md @@ -607,12 +607,12 @@ Submitting it opens the App's setup pull request, with no runner involved `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 +Goal, the `Quests: ` line in the root agent instructions, and the +`.gitattributes` line. 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 +directory that is not a Quest stub, 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 diff --git a/cloudflare/src/github/service.ts b/cloudflare/src/github/service.ts index 31db0831..d9d6c1a8 100644 --- a/cloudflare/src/github/service.ts +++ b/cloudflare/src/github/service.ts @@ -1462,7 +1462,7 @@ export class GitHubService { mode: "100644" as const, })); const message = - "chore: set up Quest\n\nWrites what `quest init` installs: skill stubs, quest/README.md, the agent\ninstructions' Quests line, the merge driver's .gitattributes line, and\nClaude Code's worktree hooks.\n"; + "chore: set up Quest\n\nWrites what `quest init` installs: skill stubs, quest/README.md, the agent\ninstructions' Quests line, and the merge driver's .gitattributes line.\n"; // A retry builds on the default branch's files, but keeps kix's earlier commit as a parent, // so the branch only fast-forwards: a push since the check above makes it fail rather than vanish. const commit = diff --git a/cloudflare/src/github/setup.ts b/cloudflare/src/github/setup.ts index 0938c722..8d860e23 100644 --- a/cloudflare/src/github/setup.ts +++ b/cloudflare/src/github/setup.ts @@ -27,7 +27,7 @@ export type TreeEntry = { path: string; type: string; mode: string; sha: string /** Whether the plan may read the file at `path`, so the service fetches it with the tree. */ export function planned(path: string): boolean { - if (["AGENTS.md", "CLAUDE.md", ".gitattributes", ".claude/settings.json"].includes(path)) return true; + if (["AGENTS.md", "CLAUDE.md", ".gitattributes"].includes(path)) return true; return SKILL_DIRS.some((dir) => { const rest = path.startsWith(`${dir}/`) ? path.slice(dir.length + 1).split("/") : []; return rest.length === 2 && rest[0]!.startsWith("quest-") && rest[1] === "SKILL.md"; @@ -124,7 +124,6 @@ function purpose(change: InitChange): string { if (path.endsWith("/SKILL.md")) return "a skill stub"; if (path === "quest/README.md") return "the quest tree's root, with the project's goal"; if (path === ".gitattributes") return "routes `quest/**` through the `quest` merge driver"; - if (path === ".claude/settings.json") return "Claude Code's worktree hooks, which run `quest worktree hook`"; return "points agents at `quest context`"; } diff --git a/cloudflare/src/projects.tsx b/cloudflare/src/projects.tsx index 3a8d9b69..8b2ff71d 100644 --- a/cloudflare/src/projects.tsx +++ b/cloudflare/src/projects.tsx @@ -259,8 +259,7 @@ export function QuestSetupPage(props: {
This repository has no Quest files on its default branch yet. The App opens a pull request with what{" "}
quest init writes: skill stubs, quest/README.md, one line in the agent
- instructions, a .gitattributes line, and Claude Code's worktree hooks. Contributions
- are enabled when you merge it.
+ instructions, and a .gitattributes line. Contributions are enabled when you merge it.