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
92 changes: 64 additions & 28 deletions docs/release/upgrading-from-bash.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ Use this guide if all of these are true:

- You installed Bash docket from the `v0.9.2` or `v0.9.3` tag.
- Your repositories keep their docket records on a separate `docket` branch.
- You use docket with Claude Code.
- You use docket with Claude Code, Cursor, or both.

Repositories that keep their records on the default branch (the single-branch layout) are not
covered. The guide covers Claude Code only. Sections for Cursor and OpenCode arrive in later
pre-releases. Codex is not supported.
covered. The guide covers Claude Code and Cursor. OpenCode follows in a later pre-release. Codex is
not supported.

## 2. Before you start

Expand All @@ -28,16 +28,17 @@ pre-releases. Codex is not supported.
## 3. Install the docket binary

Run these commands outside any repository. They download the installer and the checksum file into
a new folder, check the installer against the checksum, then run it for Claude Code.
a new folder, check the installer against the checksum, then run it for Claude Code and Cursor. If
you use only one of them, drop the other one's `--harness` flag, here and in section 4.

<!-- upgrade-step: install-binary -->
```sh
mkdir -p ~/docket-download && cd ~/docket-download
VERSION=v1.0.0-alpha.1
VERSION=v1.0.0-alpha.2
curl -fsSLO "https://github.com/danielhanold/docket/releases/download/$VERSION/install.sh"
curl -fsSLO "https://github.com/danielhanold/docket/releases/download/$VERSION/checksums.txt"
grep ' install.sh$' checksums.txt | shasum -a 256 -c -
sh install.sh --harness claude
sh install.sh --harness claude --harness cursor
```

The `shasum` line must print `install.sh: OK`. If it prints anything else, stop and do not run
Expand All @@ -53,7 +54,7 @@ clears the conflicts.
If you build docket from a source checkout instead, see [Installing docket](../install/install.md).
That route needs Go and is not part of this guide.

## 4. Take over the old Claude Code install
## 4. Take over the old Claude Code and Cursor install

The installer takes over the agent files Bash docket wrote under `~/.claude/agents/` by itself, as
long as they still hold exactly what the Bash installer wrote.
Expand All @@ -65,14 +66,31 @@ you to move or delete it. For a Bash `v0.9.2` or `v0.9.3` install, that list is:
Deleting a link leaves the checkout alone.
- on `v0.9.3` only, `~/.claude/agents/docket-plan-writer.md`.

Delete them, then run the installer again from the same folder. The failed run left no `docket`
command, so run `sh install.sh` again, not `docket install`.
For Cursor, the installer takes over Bash's agent files under `~/.cursor/agents/` by itself in the
same way. It reports these Cursor paths as conflicts:

- every `docket-*` link under `~/.cursor/skills/`. These also point into your old Bash checkout.
- on `v0.9.3` only, `~/.cursor/agents/docket-plan-writer.md` and Bash's user-level rule
`~/.cursor/rules/docket-dispatch.mdc`. On `v0.9.2` it is not reported as a conflict; the block
below deletes it either way.

Delete the Cursor paths first. If you don't use Cursor, skip this block.

<!-- upgrade-step: cursor-takeover-remedy -->
```sh
rm ~/.cursor/skills/docket-*
rm -f ~/.cursor/agents/docket-plan-writer.md ~/.cursor/rules/docket-dispatch.mdc
```

Then delete the Claude Code paths and run the installer again from the same folder, with the same
`--harness` flags as in section 3. If you don't use Claude Code, leave out the two `rm` lines. The
failed run left no `docket` command, so run `sh install.sh` again, not `docket install`.

<!-- upgrade-step: takeover-remedy -->
```sh
rm ~/.claude/skills/docket-*
rm -f ~/.claude/agents/docket-plan-writer.md
sh install.sh --harness claude
sh install.sh --harness claude --harness cursor
```

This time the installer reports `install: applied`.
Expand Down Expand Up @@ -177,9 +195,8 @@ docket repository configure-tests

Next, choose the coding agents docket writes instructions for. On a terminal,
`docket repository configure-harnesses` shows a checklist. The command below records "none yet",
because Bash docket's `CLAUDE.md` block would get in the way. After you remove that block (the last
step), run `docket repository configure-harnesses --harnesses claude` (or your agents) and commit
the result.
because Bash docket's `CLAUDE.md` block would get in the way. The last step of this section, after
you remove that block, chooses your agents.

<!-- upgrade-step: configure-harnesses -->
```sh
Expand Down Expand Up @@ -236,9 +253,19 @@ git commit -am "Remove the Bash docket block from CLAUDE.md"
git push
```

Last, choose your coding agents, which the configure-harnesses step left as "none yet": run
`docket repository configure-harnesses --harnesses <agents>` (for example `claude`) and commit the
paths it lists.
Last, choose your coding agents, which the configure-harnesses step left as "none yet". The command
lists the paths to review and commit. For Claude Code, it writes docket's own block into `CLAUDE.md`.
For Cursor, it writes `.cursor/rules/docket-dispatch.mdc` into the repository, which the
`.gitignore` block keeps out of commits. Drop the agent you don't use from `--harnesses`, and if you
don't use Claude Code, leave `CLAUDE.md` off the `git add` line.

<!-- upgrade-step: choose-harnesses -->
```sh
docket repository configure-harnesses --harnesses claude,cursor
git add .docket.yml CLAUDE.md
git commit -m "Choose docket's agents"
git push
```

## 6. Settings that changed

Expand Down Expand Up @@ -277,22 +304,31 @@ does not mention them. Delete them:
The test covers zsh, where those lines are in `~/.zshenv`. Other shells keep them in a different
startup file, which the test does not cover.

Your old Bash checkout, usually `~/dev/docket`, is no longer used by Claude Code: nothing under
`~/.claude` points into it after the upgrade. The links Bash docket made for other tools, under
`~/.cursor`, `~/.codex` and `~/.agents`, still do. The guide does not cover those tools. Using
docket from Cursor or OpenCode on an upgraded repository is not supported until their sections
arrive, and Codex is not supported at all (see section 1).
Your old Bash checkout, usually `~/dev/docket`, is no longer used by Claude Code or Cursor: nothing
under `~/.claude` or `~/.cursor` points into it after the upgrade. The links Bash docket made for
other tools, under `~/.codex` and `~/.agents`, still do. The guide does not cover those tools.
Using docket from OpenCode on an upgraded repository is not supported until its section arrives,
and Codex is not supported at all (see section 1).

If you skipped a harness in section 4, its Bash links under `~/.claude` or `~/.cursor` still point
into the checkout; delete them with that harness's `rm` lines from section 4.

In Cursor, docket must run outside Cursor's sandbox. [Running docket under Cursor](../install/cursor.md)
shows the permission setup that allows it.

Bash docket also wrote agent files into a repository when its `.docket.yml` has an `agents:`
setting: one `docket-*.md` file per agent under the repository's `.claude/agents/` folder. Claude
Code uses an agent file in the repository instead of the one with the same name in
`~/.claude/agents/`, so these old files hide the agents the installer just wrote. Delete them in
every repository. If a repository has none, the command does nothing.
setting: one `docket-*.md` file per agent under the repository's `.claude/agents/` folder, and for
Cursor under `.cursor/agents/`. Claude Code uses an agent file in the repository instead of the one
with the same name in `~/.claude/agents/`, so these old files hide the agents the installer just
wrote. Delete them in every repository. If a repository has none, the command does nothing. The
saved installs the test runs carry no repository Cursor agent files, so the test does not prove the
`.cursor/agents` line.

<!-- upgrade-step: repo-agent-files -->
```sh
cd <repo>
rm -f .claude/agents/docket-*.md
rm -f .cursor/agents/docket-*.md
```

The `.gitignore` block from section 5 keeps these files out of commits, so there is nothing to
Expand All @@ -304,10 +340,10 @@ default branch, such as `Bash(git -C * push origin HEAD:main)`, and it can also
your own. This guide does not cover that file: the test leaves it in place and does not show
whether deleting it is safe.

## 8. Restart Claude Code
## 8. Restart Claude Code and Cursor

Quit Claude Code and start it again. Claude Code loads agents and skills when it starts, so
clearing a conversation is not enough.
Quit Claude Code and Cursor and start each one again. Both load agents and skills when they start,
so clearing a conversation is not enough.

## 9. If something goes wrong

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Cursor section of the Bash upgrade guide, proven on the saved cases

Change 0543 — Cursor section of the Bash upgrade guide, proven on the saved cases

**Change:** 0543 · **Type:** docs · **Priority:** high · **Groomed:** 2026-10-08 · **Status:** Approved design

## Purpose

`docs/release/upgrading-from-bash.md` (change 0511) moves a Bash docket install to the Go binary
for Claude Code only. This change extends it to Cursor, proven by the same
`internal/bashupgrade` integration test against the saved `v0.9.2` and `v0.9.3` cases, which
already hold the Bash Cursor files. It must merge before the `v1.0.0-alpha.2` candidate is cut
(change 0512), so the test runs inside that candidate's source gate.

No product code changes. If the test exposes something the binary does wrong, rather than
something the guide can explain, the build halts and that bug gets its own fix change, as in 0511.

## What the saved cases show (traced 2026-10-08)

A scratch reproduction ran the current `docket install --harness cursor` against each restored
saved home:

| Path | v0.9.2 | v0.9.3 |
|---|---|---|
| `~/.cursor/agents/docket-*.md` | taken over by the installer (legacy byte match) | taken over, except `docket-plan-writer.md`: conflict |
| `~/.cursor/skills/docket-*` (links into the old checkout) | conflict | conflict |
| `~/.cursor/rules/docket-dispatch.mdc` (Bash's user-level dispatch rule) | removed by the installer (matches the frozen v0.9.2 rule) | conflict (v0.9.3's bytes differ from the frozen floor) |

After `rm ~/.cursor/skills/docket-*` and
`rm -f ~/.cursor/agents/docket-plan-writer.md ~/.cursor/rules/docket-dispatch.mdc`, the second
install reports `applied` on both cases. `install check` is then clean except the `runtime.bash`
`obsolete-setting` warning, which the guide's existing global-config step clears. Nothing under
`~/.cursor` links into the old checkout. The installer also writes `~/.cursor/hooks.json`.

The v0.9.3 rule conflict is by design (ADR-0096: the legacy reproducer is a frozen v0.9.2
floor), so it is a guide remedy, the same way `docket-plan-writer.md` already is for Claude Code.

There is no Cursor counterpart of the repository `CLAUDE.md` dispatch block: Bash wrote its Cursor
rule at user level. The Go binary writes `.cursor/rules/docket-dispatch.mdc` per repository when
`agent_harnesses` lists `cursor`, and the guide's `.gitignore` block already ignores it.

## Design (human, 2026-10-08)

**One combined path.** The guide has a single flow for Claude Code and Cursor. Each harness's
lines are labelled, and the reader drops the harness they don't use. The test runs each saved case
once, through both harnesses. A separate Claude-only run is not kept: it would differ by one
`--harness` flag, and each harness's install targets are separate. Every existing Claude Code step
and assertion stays, because the test is what keeps the guide's Claude Code half true against each
new binary.

### Guide changes

- **§1 Who this is for.** "You use docket with Claude Code, Cursor, or both." Cursor is covered;
OpenCode follows in a later pre-release; Codex is not supported.
- **§3 Install the docket binary.** `VERSION=v1.0.0-alpha.2`. The installer line becomes
`sh install.sh --harness claude --harness cursor`, with a sentence to drop the `--harness` you
don't use. The text about the first run stopping with `conflict` lines is unchanged.
- **§4** becomes "Take over the old Claude Code and Cursor install".
- The existing Claude Code text and its marked `takeover-remedy` step stay. Its re-run line
becomes the same two-harness `sh install.sh` line as §3.
- A Cursor paragraph: the installer takes over Bash's agent files under `~/.cursor/agents/` by
itself. It reports as conflicts every `docket-*` link under `~/.cursor/skills/`, and on `v0.9.3`
`~/.cursor/agents/docket-plan-writer.md` and Bash's user-level rule
`~/.cursor/rules/docket-dispatch.mdc`. On `v0.9.2` the installer removes that rule itself.
- A new marked step, `cursor-takeover-remedy`, run before the re-run:

```sh
rm ~/.cursor/skills/docket-*
rm -f ~/.cursor/agents/docket-plan-writer.md ~/.cursor/rules/docket-dispatch.mdc
```

- The global-config cleanup and `confirm-install` steps are unchanged.
- **§5 Upgrade each repository.** The final `configure-harnesses` example becomes
`--harnesses claude,cursor`. For Cursor, it writes `.cursor/rules/docket-dispatch.mdc` into the
repository, which the `.gitignore` block keeps out of commits.
- **§7 Leftovers you can delete.**
- Nothing under `~/.claude` or `~/.cursor` points into the old checkout after the upgrade. The
links Bash made under `~/.codex` and `~/.agents` still do. Using docket from OpenCode on an
upgraded repository is not supported until its section arrives; Codex is not supported.
- The marked `repo-agent-files` step also runs `rm -f .cursor/agents/docket-*.md`. The saved
cases carry no repository Cursor agent files, so the guide says the test does not prove that
line.
- A sentence that docket must run outside Cursor's sandbox, linking `docs/install/cursor.md`.
- **§8** becomes "Restart Claude Code and Cursor": quit each and start it again; both load agents
and skills at startup.

### Test changes (`internal/bashupgrade`)

The guide-driven run (`TestIntegrationBashUpgradeGuide`) executes the new and changed marked steps
like every other. New assertions:

- The first install's conflicts include every `~/.cursor/skills/docket-*` link on both cases, and on
`v0.9.3` also `~/.cursor/agents/docket-plan-writer.md` and `~/.cursor/rules/docket-dispatch.mdc`.
- After the guide, `~/.cursor/rules/docket-dispatch.mdc` does not exist on either case.
- The clean end state covers both harnesses: `docket install check --json` reports `cursor` among
its harnesses with no error or warning finding.
- Nothing under `~/.cursor` links into the old checkout. The existing assertion that `~/.cursor`
still has such links flips to require zero; `~/.codex` and `~/.agents` keep requiring at least one.
- The clone has `.cursor/rules/docket-dispatch.mdc` after the final `configure-harnesses` step,
with any guide-shape check the test already applies to marked steps.

Existing assertions stay, including the guide-shape and finding/settings table checks. The test
stays in the integration build tag and inside the whole-suite gate.

## Out of scope

- OpenCode (alpha.3, change 0513) and Codex.
- The release itself (change 0512).
- Retiring the saved cases and the test (change 0514).
- A Claude-only test run, and new saved cases (for example repository `.cursor/agents` files).
- Any change to the binary's takeover rules, including teaching the frozen floor v0.9.3's rule.

## Acceptance

- The guide reads as above, and every marked step runs in the test.
- `TestBashUpgrade` passes on both saved cases with the new Cursor assertions, inside the whole
suite.
- Mutation check: deleting the `cursor-takeover-remedy` step from the guide makes the test fail.
32 changes: 29 additions & 3 deletions internal/bashupgrade/bashupgrade_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package bashupgrade

import (
"encoding/json"
"errors"
"io/fs"
"os"
"path/filepath"
"reflect"
Expand Down Expand Up @@ -106,9 +108,15 @@ func TestIntegrationBashUpgradeGuide(t *testing.T) {
t.Fatalf("the remedy did not lead to a second install run")
}
assertCleanEndState(t, c)
if _, err := os.Lstat(filepath.Join(c.Home, ".cursor", "rules", "docket-dispatch.mdc")); !errors.Is(err, fs.ErrNotExist) {
t.Errorf("Bash's user-level Cursor rule ~/.cursor/rules/docket-dispatch.mdc is still there after the guide (%v)", err)
}
assertRecordsSurvive(t, c, before)
if after, _ := os.ReadFile(filepath.Join(c.Clone, "CLAUDE.md")); strings.Contains(string(after), "docket:dispatch:") {
t.Errorf("the repository CLAUDE.md still carries the Bash dispatch block after the guide")
if after, _ := os.ReadFile(filepath.Join(c.Clone, "CLAUDE.md")); strings.Contains(string(after), bashDispatchSignature) {
t.Errorf("the repository CLAUDE.md still carries Bash docket's dispatch block (it runs docket.sh) after the guide")
}
if fi, err := os.Lstat(filepath.Join(c.Clone, ".cursor", "rules", "docket-dispatch.mdc")); err != nil || !fi.Mode().IsRegular() {
t.Errorf("the repository has no .cursor/rules/docket-dispatch.mdc after the guide (%v)", err)
}
dispatchRemoved = dispatchRemoved || st.DispatchBlockRemoved
repoAgentShadow = repoAgentShadow || st.RepoAgentFilesShadow > 0
Expand Down Expand Up @@ -148,7 +156,8 @@ func TestIntegrationBashUpgradeGuide(t *testing.T) {
}
}

// assertCleanEndState: install check, repository check and status all clean.
// assertCleanEndState: install check (for every harness the guide installs),
// repository check and status all clean.
func assertCleanEndState(t *testing.T, c *upgradeCase) {
t.Helper()
for _, args := range [][]string{{"--json", "install", "check"}, {"--json", "repository", "check"}} {
Expand All @@ -157,6 +166,23 @@ func assertCleanEndState(t *testing.T, c *upgradeCase) {
t.Errorf("docket %s exited %d\nstdout:\n%s\nstderr:\n%s", strings.Join(args, " "), r.Code, r.Stdout, r.Stderr)
continue
}
if args[1] == "install" {
var doc struct {
Harnesses []string `json:"harnesses"`
}
if err := json.Unmarshal([]byte(r.Stdout), &doc); err != nil {
t.Errorf("decode install check JSON: %v\n%s", err, r.Stdout)
}
have := map[string]bool{}
for _, h := range doc.Harnesses {
have[h] = true
}
for _, h := range guideHarnesses {
if !have[h] {
t.Errorf("docket install check reports harnesses %v; the guide installs %s", doc.Harnesses, h)
}
}
}
for _, f := range findingCodes(t, r.Stdout) {
if f.Severity == "error" || f.Severity == "warning" {
t.Errorf("docket %s still reports %s (%s)", strings.Join(args, " "), f.Code, f.Severity)
Expand Down
Loading
Loading