runcastle 1.2.7 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/runcastle.js +6939 -5323
- package/drizzle/0027_dashing_texas_twister.sql +1 -0
- package/drizzle/0028_tired_deadpool.sql +2 -0
- package/drizzle/0029_damp_stellaris.sql +1 -0
- package/drizzle/meta/0027_snapshot.json +928 -0
- package/drizzle/meta/0028_snapshot.json +942 -0
- package/drizzle/meta/0029_snapshot.json +949 -0
- package/drizzle/meta/_journal.json +22 -1
- package/index.js +8329 -6724
- package/package.json +1 -1
- package/sandcastle-template/Containerfile +9 -1
- package/sandcastle-template/Dockerfile +9 -1
- package/skills/burner/implement-ticket.md +32 -47
- package/skills/burner/resolve-conflict.md +3 -3
- package/skills/burner/review-ticket.md +24 -25
- package/skills/packs/README.md +95 -5
- package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
- package/skills/packs/runcastle/skills/converge/SKILL.md +26 -21
- package/skills/packs/runcastle/skills/ideate/SKILL.md +14 -8
- package/skills/packs/runcastle/skills/prepare/SKILL.md +96 -0
- package/skills/packs/runcastle/skills/prepare/references/recipes.md +72 -0
- package/skills/packs/runcastle/skills/project/SKILL.md +22 -48
- package/skills/packs/runcastle/skills/project/references/charter.md +79 -0
- package/skills/packs/runcastle/skills/project/references/health-sweeps.md +44 -0
- package/skills/packs/runcastle/skills/qa/SKILL.md +29 -13
- package/skills/packs/runcastle/skills/revisit/SKILL.md +8 -8
- package/skills/packs/runcastle/skills/spec/SKILL.md +3 -3
- package/skills/packs/runcastle/skills/tickets/SKILL.md +13 -2
- package/skills/packs/runcastle/skills/waypoint/SKILL.md +14 -9
- package/web/assets/index-BRjcVBgj.js +142 -0
- package/web/assets/{index-B3xwZ1FR.css → index-CNSn8X1O.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-C0S4i8wY.js +0 -142
package/package.json
CHANGED
|
@@ -15,6 +15,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
ENV BUN_INSTALL=/usr/local
|
|
16
16
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
17
17
|
|
|
18
|
+
# Codex CLI (the second agent runtime) — authenticated at runtime via CODEX_API_KEY.
|
|
19
|
+
# Installed as root, before the user switch, so it lands in the system-wide npm
|
|
20
|
+
# prefix: after `USER` a global install has nowhere writable to go.
|
|
21
|
+
RUN npm install -g @openai/codex
|
|
22
|
+
|
|
18
23
|
# Rootless-podman invariant (research #32): the in-container agent user MUST be at
|
|
19
24
|
# UID/GID 1000 so bind-mounted host files share an owner. Sandcastle passes the host
|
|
20
25
|
# uid/gid as build args (defaulting here to 1000); rename the stock `node` user to match.
|
|
@@ -24,7 +29,10 @@ RUN groupmod -o -g "$AGENT_GID" node \
|
|
|
24
29
|
&& usermod -o -u "$AGENT_UID" -g "$AGENT_GID" -d /home/agent -m -l agent node
|
|
25
30
|
USER ${AGENT_UID}:${AGENT_GID}
|
|
26
31
|
|
|
27
|
-
# Claude Code CLI (the agent runtime) — authenticated at runtime via
|
|
32
|
+
# Claude Code CLI (the other agent runtime) — authenticated at runtime via
|
|
33
|
+
# CLAUDE_CODE_OAUTH_TOKEN. Installed per-user, so it must follow the switch.
|
|
34
|
+
# One image serves both runtimes: which CLI a burn invokes is decided by the
|
|
35
|
+
# resolved model's runtime, not by the image.
|
|
28
36
|
RUN curl -fsSL https://claude.ai/install.sh | bash
|
|
29
37
|
ENV PATH="/home/agent/.local/bin:$PATH"
|
|
30
38
|
|
|
@@ -15,6 +15,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
ENV BUN_INSTALL=/usr/local
|
|
16
16
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
17
17
|
|
|
18
|
+
# Codex CLI (the second agent runtime) — authenticated at runtime via CODEX_API_KEY.
|
|
19
|
+
# Installed as root, before the user switch, so it lands in the system-wide npm
|
|
20
|
+
# prefix: after `USER` a global install has nowhere writable to go.
|
|
21
|
+
RUN npm install -g @openai/codex
|
|
22
|
+
|
|
18
23
|
# Rootless-podman invariant (research #32): the in-container agent user MUST be at
|
|
19
24
|
# UID/GID 1000 so bind-mounted host files share an owner. Sandcastle passes the host
|
|
20
25
|
# uid/gid as build args (defaulting here to 1000); rename the stock `node` user to match.
|
|
@@ -24,7 +29,10 @@ RUN groupmod -o -g "$AGENT_GID" node \
|
|
|
24
29
|
&& usermod -o -u "$AGENT_UID" -g "$AGENT_GID" -d /home/agent -m -l agent node
|
|
25
30
|
USER ${AGENT_UID}:${AGENT_GID}
|
|
26
31
|
|
|
27
|
-
# Claude Code CLI (the agent runtime) — authenticated at runtime via
|
|
32
|
+
# Claude Code CLI (the other agent runtime) — authenticated at runtime via
|
|
33
|
+
# CLAUDE_CODE_OAUTH_TOKEN. Installed per-user, so it must follow the switch.
|
|
34
|
+
# One image serves both runtimes: which CLI a burn invokes is decided by the
|
|
35
|
+
# resolved model's runtime, not by the image.
|
|
28
36
|
RUN curl -fsSL https://claude.ai/install.sh | bash
|
|
29
37
|
ENV PATH="/home/agent/.local/bin:$PATH"
|
|
30
38
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
<!-- Forked from Matt Pocock's implement + tdd + code-review skills, 2026-07-14, adapted for runcastle's unattended burner. Rendered per ticket
|
|
1
|
+
<!-- Forked from Matt Pocock's implement + tdd + code-review skills, 2026-07-14, adapted for runcastle's unattended burner. Rendered per ticket by the ticket-burner workflow. ORDERING IS LOAD-BEARING: everything above "## Your ticket" is byte-identical for every ticket in a burn and across a ticket's own retry iterations, so concurrent agents share a long cacheable prefix. The two ticket-specific placeholders live at the END, under "Your ticket", and must stay there — moving one up collapses the shared prefix to whatever precedes it. -->
|
|
2
2
|
|
|
3
3
|
# Implement this ticket — unattended
|
|
4
4
|
|
|
5
|
-
You are a single agent in a sandbox on **your ticket's own temp branch**, `runcastle/ticket/<slug>/<seq>-<unique>`, forked from the feature branch
|
|
5
|
+
You are a single agent in a sandbox on **your ticket's own temp branch**, `runcastle/ticket/<slug>/<seq>-<unique>`, forked from the feature's integration branch. Commit there and only there — the run merges your branch back into the integration branch once you land, so never check out or commit to the integration branch yourself. You have **one ticket**, stated at the end of this prompt. There is **no human to ask** — no follow-up questions are possible. Everything you need is in this prompt and in the repo. Work carefully, commit only green work, and stop when the ticket is done.
|
|
6
6
|
|
|
7
7
|
## How you run
|
|
8
8
|
|
|
9
|
-
You run **non-interactively**
|
|
9
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — for up to a few fresh iterations:
|
|
10
10
|
|
|
11
11
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode — a "the notification will re-invoke me" plan never fires. Never end your turn to wait on a background command; run long commands (dependency installs, full test suites) in the foreground with a generous timeout, or poll a backgrounded command to completion *within* the same turn. **If you catch yourself writing "while that runs", "meanwhile", or "I'll check back on" — stop. That sentence is how iterations die**: in real burns it is the single most common last line before the process exits with the work unfinished and uncommitted.
|
|
12
12
|
- **A next iteration is not a free retry.** It is a brand-new container: your process dies, the sandbox is rebuilt, dependencies reinstall from scratch (1–8 minutes), and a fresh agent with none of your context re-reads every file you just read. Budget roughly ten wasted minutes per iteration you burn. Finishing in one is worth real effort.
|
|
13
|
-
- **You may not be the first iteration.**
|
|
13
|
+
- **You may not be the first iteration.** Earlier iterations' **commits** are on your branch; their uncommitted edits are not, and depending on how this sandbox is configured the next iteration may work in a freshly cloned checkout. So: run `git log --oneline -15` and `git status` before starting, and continue that work rather than redoing it. Only commits carry across.
|
|
14
14
|
- **Signal completion.** When the ticket is fully done — every acceptance criterion verified, self-review finished, all work committed — print exactly `<promise>COMPLETE</promise>` as the last line of your final message. Do the same after writing `BLOCKED.md` (see hard rules). Without the signal, the harness assumes you were cut off and spends another iteration.
|
|
15
15
|
- **Run the repo's test command as the repo defines it.** Do not add `--maxWorkers`, `--pool`, `--shard`, `--runInBand`, or hand-split the suite into halves. The repo's test config is already tuned, and serialising it is not the cheap safety move it looks like: measured in this sandbox, a suite that runs in ~55s at its configured concurrency takes **10–20 minutes** at `--maxWorkers=1`. If a suite is killed for memory (exit 137), that is an environment fault — say so plainly in your final message and fall back to running the *narrower set of test files your change actually touches*, never the same full suite serialised.
|
|
16
16
|
|
|
@@ -29,11 +29,9 @@ This is not a style preference — it is one of the largest measured costs in re
|
|
|
29
29
|
|
|
30
30
|
If you catch yourself writing a heredoc to edit a file, stop and use `Edit`.
|
|
31
31
|
|
|
32
|
-
## The
|
|
32
|
+
## The standards you are held to
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
{{TICKET_JSON}}
|
|
36
|
-
```
|
|
34
|
+
{{PROJECT_STANDARDS}}
|
|
37
35
|
|
|
38
36
|
## Feature context
|
|
39
37
|
|
|
@@ -55,7 +53,7 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
55
53
|
|
|
56
54
|
## How to work
|
|
57
55
|
|
|
58
|
-
1. **Orient.** Read
|
|
56
|
+
1. **Orient.** Read your ticket's `goal`, `context`, `acceptanceCriteria`, and `seams` (they are at the end of this prompt). Read the files its `context` names and the existing patterns it points to. Match the conventions of the surrounding code — you are extending this codebase, not starting a new one.
|
|
59
57
|
|
|
60
58
|
2. **Test at the seams (forked tdd).** The ticket's `seams` are the public interfaces to test at — test *there*, at the boundary where behaviour is observable, never against internals or private helpers. A good test reads like a spec ("user can X"), uses the public API only, and survives refactors. Mock only true system boundaries (network, clock, external services); never mock your own modules.
|
|
61
59
|
|
|
@@ -63,64 +61,51 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
63
61
|
|
|
64
62
|
4. **Commit early, commit often — an uncommitted slice is a slice you can lose.** A commit is the *only* thing that survives your process ending. Everything else — edited files, a passing test you have not committed, an hour of work — is discarded the moment the turn ends, and the next iteration starts from your last commit as if the rest never happened. Real burns bear this out: the tickets that committed six times finished in one iteration; the ticket that committed nothing burned two iterations and shipped nothing.
|
|
65
63
|
|
|
66
|
-
So: **the moment a slice is green, commit it.** Do not save commits up for a tidy end-of-ticket batch, do not wait for the next criterion, do not let more than about ten minutes of work sit uncommitted. Before each commit run typecheck and the *relevant* tests — **never commit red** — then commit
|
|
64
|
+
So: **the moment a slice is green, commit it.** Do not save commits up for a tidy end-of-ticket batch, do not wait for the next criterion, do not let more than about ten minutes of work sit uncommitted. Before each commit run typecheck and the *relevant* tests — **never commit red** — then commit with a subject line of the form:
|
|
67
65
|
|
|
68
|
-
`
|
|
66
|
+
`ticket(<seq>): <summary>`
|
|
67
|
+
|
|
68
|
+
where `<seq>` is the `seq` field of the ticket JSON at the end of this prompt (so ticket 4 commits `ticket(4): add the login form`). Use that prefix on every commit you make.
|
|
69
69
|
|
|
70
70
|
A half-done ticket with four green commits is a good outcome the next iteration can finish. A nearly-done ticket with zero commits is a total loss.
|
|
71
71
|
|
|
72
72
|
5. **Self-review before you finish (forked code-review — two axes).** When all acceptance criteria pass, review your own diff along both axes, then fix what you find and re-run typecheck + tests:
|
|
73
|
-
- **Standards** — does the diff follow the conventions of the surrounding code? Watch for the smells: duplicated logic (extract it), mysterious names (rename), primitive obsession (give the concept a type), speculative generality (delete anything the ticket did not ask for), feature envy, data clumps.
|
|
73
|
+
- **Standards** — does the diff follow the documented standards above and the conventions of the surrounding code? Watch for the smells: duplicated logic (extract it), mysterious names (rename), primitive obsession (give the concept a type), speculative generality (delete anything the ticket did not ask for), feature envy, data clumps.
|
|
74
74
|
- **Spec = this ticket** — is every acceptance criterion actually met, and is there **nothing in the diff the ticket did not ask for**? Missing and extra both count.
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
A review agent runs against your diff at the end of this burn, along exactly these two axes and against exactly those standards files. Everything you catch here is a finding that never becomes someone's fix ticket. Commit the fixes.
|
|
76
77
|
|
|
77
78
|
6. **Write your digest — the last thing you do.** Once every acceptance criterion passes and the self-review fixes are committed, write `DIGEST.md` (see "Where to work" for exactly where) as your final act before printing `<promise>COMPLETE</promise>`. Roughly 10–15 lines, three parts:
|
|
78
79
|
- **What was done** — past tense, what you actually built, *including where it deviated* from the approach the ticket described.
|
|
79
80
|
- **Surprises** — what the ticket or the spec did not anticipate: the coupling nobody mentioned, the test that was already red, the API that did not behave as documented.
|
|
80
81
|
- **Left undone** — adjacent work you noticed and deliberately did not do, so the next agent inherits the observation instead of re-finding it.
|
|
81
82
|
|
|
82
|
-
Write it in plain prose, for a reader who does not have your context. **Never commit `DIGEST.md`** — it is harvested from the workspace, not from the repo, and a committed one is diff noise. It is a **success artifact only**: if you are blocked and writing `BLOCKED.md`, write no digest — `BLOCKED.md` is your record.
|
|
83
|
+
Write it in plain prose, for a reader who does not have your context. It is read: the tickets that depend on yours are handed it verbatim, and so is the agent that reviews this whole lap. **Never commit `DIGEST.md`** — it is harvested from the workspace, not from the repo, and a committed one is diff noise. It is a **success artifact only**: if you are blocked and writing `BLOCKED.md`, write no digest — `BLOCKED.md` is your record.
|
|
83
84
|
|
|
84
|
-
## Keep the drive
|
|
85
|
+
## Keep the drive machinery true
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
{{DRIVE_NOTES}}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- a **service** the app now needs — a database, a queue, a container in the compose file;
|
|
91
|
-
- a **required env var** the app reads at boot and fails without;
|
|
92
|
-
- a **seed** requirement — data that has to exist before the app is usable;
|
|
93
|
-
- a **process** the dev environment must run alongside the app — a worker, a watcher.
|
|
94
|
-
|
|
95
|
-
Anything short of those needs no edit. The steps are idempotent by design — install, migrate, compose up all run unconditionally — so a branch that merely adds a package or a migration is already covered. Only structural changes need you, and you are the agent who knows about them. This is part of your ticket, not adjacent work: a branch whose drive cannot boot is not done.
|
|
96
|
-
|
|
97
|
-
The contract facts you need:
|
|
98
|
-
|
|
99
|
-
- The scripts are **committed source** — edit them like any other file, alongside the change that made them necessary.
|
|
100
|
-
- **Every step stays idempotent** — safe to re-run against an already-prepared world, correct against a fresh one.
|
|
101
|
-
- **`drive-setup` writes `.runcastle/drive.env`** (gitignored, plain `KEY=VALUE`). That file is the only way a value the script computes — a port, a database name, a URL — reaches the app. If your ticket makes the app require an env var, the setup script must write it there.
|
|
102
|
-
- **`RUNCASTLE_SLUG`, `RUNCASTLE_BRANCH`, and `RUNCASTLE_ID` are provided by the server** to every drive hook. Derive per-drive identity from those; never from git inside the script.
|
|
103
|
-
- **Exit 0 means ready** — if you add a service, add the wait for it too.
|
|
89
|
+
## Hard rules
|
|
104
90
|
|
|
105
|
-
|
|
91
|
+
{{GUARD_NOTES}}
|
|
106
92
|
|
|
107
|
-
|
|
93
|
+
- **Never end your turn with uncommitted green work.** If you are about to stop for any reason — done, blocked, unsure, out of room — commit what is green first. This rule outranks tidiness: a scrappy commit beats losing the work.
|
|
94
|
+
- **Never `git stash`.** See "How to verify" — stashed work is invisible to the orchestrator and unrecoverable if your process dies.
|
|
95
|
+
- **Never expand scope beyond the ticket.** If you notice adjacent work, worthwhile refactors, or another ticket's territory, leave it. Note it in your final commit body or your digest if it matters; do not do it.
|
|
96
|
+
- **No questions, no guessing into the void.** Resolve ambiguity from the ticket context and the code. If two readings both satisfy the acceptance criteria, take the smaller one.
|
|
97
|
+
- **If genuinely blocked** (a dependency ticket's output is not there, the environment fails, or a requirement truly cannot be resolved from context + code): **commit nothing for the blocked part**, write `BLOCKED.md` — **at the path given in "Where to work", and nowhere else** — stating precisely what blocked you and what is needed to unblock, print `<promise>COMPLETE</promise>`, and stop. Green, complete parts may still be committed; the blocked part must not be. The orchestrator reads your exit state from the commits — a part with no commits is read as not done.
|
|
108
98
|
|
|
109
|
-
|
|
99
|
+
## Your ticket
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
- **Every referenced file exists.** Read the script and check that each path it names — compose file, seed file, env sample, sourced helper — is present in the repo at that path.
|
|
113
|
-
- **The compose file parses**, if you changed one: `docker compose config -q` when the CLI happens to be installed, otherwise parse it as YAML.
|
|
114
|
-
- **New env vars appear in the output.** If your change made the app require an env var, grep the setup script and confirm it actually writes that key into `.runcastle/drive.env`.
|
|
101
|
+
Everything above is the standing brief for this burn. This is the part that is yours alone.
|
|
115
102
|
|
|
116
|
-
|
|
103
|
+
```json
|
|
104
|
+
{{TICKET_JSON}}
|
|
105
|
+
```
|
|
117
106
|
|
|
118
|
-
|
|
107
|
+
### What landed before you
|
|
119
108
|
|
|
120
|
-
|
|
109
|
+
{{BLOCKERS}}
|
|
121
110
|
|
|
122
|
-
|
|
123
|
-
- **Never `git stash`.** See "How to verify" — stashed work is invisible to the orchestrator and unrecoverable if your process dies.
|
|
124
|
-
- **Never expand scope beyond the ticket.** If you notice adjacent work, worthwhile refactors, or another ticket's territory, leave it. Note it in your final commit body if it matters; do not do it.
|
|
125
|
-
- **No questions, no guessing into the void.** Resolve ambiguity from the ticket context and the code. If two readings both satisfy the acceptance criteria, take the smaller one.
|
|
126
|
-
- **If genuinely blocked** (a dependency ticket's output is not there, the environment fails, or a requirement truly cannot be resolved from context + code): **commit nothing for the blocked part**, write `BLOCKED.md` at the repo root stating precisely what blocked you and what is needed to unblock, print `<promise>COMPLETE</promise>`, and stop. Green, complete parts may still be committed; the blocked part must not be. The orchestrator reads your exit state from the commits — a part with no commits is read as not done.
|
|
111
|
+
Now do it.
|
|
@@ -10,7 +10,7 @@ There is **no human to ask** — everything you need is in this prompt and in th
|
|
|
10
10
|
|
|
11
11
|
## How you run
|
|
12
12
|
|
|
13
|
-
You run **non-interactively**
|
|
13
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — for up to a few fresh iterations. Only committed work carries from one iteration to the next — an iteration may start from a freshly cloned checkout of your branch:
|
|
14
14
|
|
|
15
15
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode — a "the notification will re-invoke me" plan never fires. Run long commands (test suites) in the foreground with a generous timeout. If you catch yourself writing "while that runs" or "meanwhile", stop — that sentence is how an iteration dies mid-merge.
|
|
16
16
|
- **An unfinished merge does not survive you.** Resolved-but-uncommitted files are discarded when your process ends, and the next iteration restarts the merge from scratch. Once the conflicts are resolved and the tree is sane, land the merge commit — then verify and fix forward on top of it.
|
|
@@ -70,9 +70,9 @@ These commits landed on `{{FEATURE_BRANCH}}` while you were working — this is
|
|
|
70
70
|
|
|
71
71
|
## Hard rules
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
{{GUARD_NOTES}}
|
|
74
74
|
|
|
75
75
|
- **Never expand scope.** Resolving the conflict is the whole job. No refactors, no improvements, no adjacent fixes, not even tempting ones in the files you are already editing.
|
|
76
76
|
- **Never lose committed work.** Not yours, not theirs. `git merge --abort` + "start over from one side" is not a resolution.
|
|
77
77
|
- **Never push, and never touch the remote.** The orchestrator lands your branch itself once the merge commit exists.
|
|
78
|
-
- **If the two sides genuinely cannot be reconciled without a product decision** (they implement contradictory behaviour, not merely overlapping code): `git merge --abort`, write `BLOCKED.md` at the
|
|
78
|
+
- **If the two sides genuinely cannot be reconciled without a product decision** (they implement contradictory behaviour, not merely overlapping code): `git merge --abort`, write `BLOCKED.md` — at the path given in "Where to work", and nowhere else — naming the exact contradiction and the decision needed, print `<promise>COMPLETE</promise>`, and stop. A human will decide. Do not guess.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Review this feature — unattended
|
|
4
4
|
|
|
5
|
-
Every implementation ticket in this burn has landed on `{{FEATURE_BRANCH}}`. Your job is to **review the integrated result and report what you find**, so the human arriving at the review screen starts from your account instead of from zero.
|
|
5
|
+
Every implementation ticket in this burn has landed on `{{FEATURE_BRANCH}}`, which forked from `{{BASE_BRANCH}}`. Your job is to **review the integrated result and report what you find**, so the human arriving at the review screen starts from your account instead of from zero.
|
|
6
6
|
|
|
7
7
|
You do two things, in this order:
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ There is **no human to ask** — everything you need is in this prompt and in th
|
|
|
17
17
|
|
|
18
18
|
## How you run
|
|
19
19
|
|
|
20
|
-
You run **non-interactively**
|
|
20
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — on the **host**, in the project's real checkout: not a container, not a worktree. So:
|
|
21
21
|
|
|
22
22
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode. Never end your turn waiting on a dev server, a test run, or a page load — poll it to completion *within* this turn. If you catch yourself writing "meanwhile" or "I'll check back on", stop.
|
|
23
23
|
- **You are on someone's machine.** The drive you start switches their checkout and runs their dev server. Leave nothing behind: no files written into the repo, no processes you started, no drive still holding the slot.
|
|
@@ -41,40 +41,39 @@ Its `goal` says what to verify and its `acceptanceCriteria` say how you will kno
|
|
|
41
41
|
|
|
42
42
|
{{DOCS_DIGEST}}
|
|
43
43
|
|
|
44
|
+
## What the implementers say they did
|
|
45
|
+
|
|
46
|
+
Each ticket in this burn wrote its own account of its work before finishing. They are reproduced here in full, and they are **evidence, not truth** — an implementer's claim is a hypothesis you confirm against the diff, exactly like a sub-agent's finding.
|
|
47
|
+
|
|
48
|
+
Read them for the two things a diff cannot express at any price: what **surprised** each implementer (the coupling nobody specified, the test that was already red, the API that misbehaved), and what each one **left undone** on purpose. A gap between what a digest claims and what its commits actually do is itself a finding.
|
|
49
|
+
|
|
50
|
+
{{LAP_DIGESTS}}
|
|
51
|
+
|
|
44
52
|
## How to work
|
|
45
53
|
|
|
46
54
|
### 1. Code review — always, and first
|
|
47
55
|
|
|
48
56
|
First, because it is the part that always runs and the part that needs nothing booted. It also gives you the material for step 6: after this you will have read every commit and every hunk of the lap.
|
|
49
57
|
|
|
50
|
-
**Pin the fixed point.**
|
|
58
|
+
**Pin the fixed point.** Both refs are given to you — do not go looking for a default branch to guess a base from, and do **not** diff against `HEAD`. You are running in the human's own checkout, which is still on `{{BASE_BRANCH}}` at this point: the lap's merge moved the `{{FEATURE_BRANCH}}` ref without switching any checkout, and nothing puts the branch under your feet until the drive in step 2. Diffing `HEAD` here reads an empty diff on a perfectly healthy lap.
|
|
59
|
+
|
|
60
|
+
Three-dot, so you see the branch's own work and not everything that landed on the base meanwhile:
|
|
51
61
|
|
|
52
62
|
```
|
|
53
|
-
git log
|
|
54
|
-
git diff
|
|
63
|
+
git log {{BASE_BRANCH}}...{{FEATURE_BRANCH}} --oneline # the commits under review
|
|
64
|
+
git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}} # the diff both axes read
|
|
55
65
|
```
|
|
56
66
|
|
|
57
|
-
Confirm
|
|
67
|
+
Confirm both refs resolve and the diff is non-empty **before** spawning anything. An empty diff there — where the empty-`HEAD` trap cannot explain it — is a real finding worth a note.
|
|
58
68
|
|
|
59
|
-
**Gather the standards.** `CLAUDE.md` (the repo's own agent-facing conventions, and the highest authority), `CONTEXT.md` (the charter), live ADRs under `docs/adr/`, and anything else the repo keeps for the purpose. On top of
|
|
69
|
+
**Gather the standards.** `CLAUDE.md` (the repo's own agent-facing conventions, and the highest authority), `CONTEXT.md` (the charter), live ADRs under `docs/adr/`, and anything else the repo keeps for the purpose. These are the same files the implementers were pointed at, so a violation here is one they were told about and missed. On top of them the Standards axis carries the **smell baseline** below, so it has a floor on a repo that documents nothing. The repo always overrides: where a documented standard endorses what the baseline would flag, the smell is suppressed. Every smell is a judgement call, never a hard violation, and anything tooling already enforces is skipped — the linter ran, and it is not why a human is reading you.
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
- **Duplicated Code** — the same logic shape in more than one hunk or file. → extract it, call it from both.
|
|
63
|
-
- **Feature Envy** — a method that reaches into another object's data more than its own. → move it onto the data it envies.
|
|
64
|
-
- **Data Clumps** — the same few fields or params keep travelling together. → bundle them into one type.
|
|
65
|
-
- **Primitive Obsession** — a primitive standing in for a domain concept. → give the concept its own small type.
|
|
66
|
-
- **Repeated Switches** — the same switch/if-cascade on the same type recurs. → polymorphism, or one shared map.
|
|
67
|
-
- **Shotgun Surgery** — one logical change forces scattered edits across many files. → gather what changes together.
|
|
68
|
-
- **Divergent Change** — one file edited for several unrelated reasons. → split so each module changes for one reason.
|
|
69
|
-
- **Speculative Generality** — abstraction or hooks for needs the spec doesn't have. → delete it.
|
|
70
|
-
- **Message Chains** — long `a.b().c().d()` navigation. → hide the walk behind one method.
|
|
71
|
-
- **Middle Man** — a function that mostly just delegates onward. → cut it, call the real target.
|
|
72
|
-
- **Refused Bequest** — an implementer that ignores most of what it inherits. → composition, not inheritance.
|
|
71
|
+
Mysterious Name · Duplicated Code · Feature Envy · Data Clumps · Primitive Obsession · Repeated Switches · Shotgun Surgery · Divergent Change · Speculative Generality · Message Chains · Middle Man · Refused Bequest.
|
|
73
72
|
|
|
74
|
-
**Spawn both axes in parallel**, in one dispatch, so neither pollutes the other's context
|
|
73
|
+
**Spawn both axes in parallel**, in one dispatch, so neither pollutes the other's context. Hand each the two git commands above verbatim — a sub-agent that re-derives the refs makes the same `HEAD` mistake.
|
|
75
74
|
|
|
76
|
-
- **Standards** —
|
|
77
|
-
- **Spec** —
|
|
75
|
+
- **Standards** — plus the standards files **by path** (it can read them itself; do not paste their contents) and the smell list above. Brief: *"Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + rule); (b) any smell from this list: name it and quote the hunk. Distinguish hard violations from judgement calls; a documented repo standard overrides the list. Skip what tooling enforces. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
76
|
+
- **Spec** — plus the acceptance criteria of the review ticket and the paths `docs/features/<slug>/spec.md` and `decisions.md`, which it reads itself. Brief: *"Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. Quote the spec line for each finding. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
78
77
|
|
|
79
78
|
That last sentence in each brief is load-bearing: without it a sub-agent can rediscover this review and fan out again. Two sub-agents, one level deep, and that is the whole tree.
|
|
80
79
|
|
|
@@ -140,7 +139,7 @@ At exactly:
|
|
|
140
139
|
|
|
141
140
|
`{{DIGEST_PATH}}`
|
|
142
141
|
|
|
143
|
-
**This is not a review log. It is the lap's summary, written for a human, and the review page renders it verbatim as the first thing they read.** They arrive at the review screen wanting one question answered — *what did this lap actually do?* — and you are the
|
|
142
|
+
**This is not a review log. It is the lap's summary, written for a human, and the review page renders it verbatim as the first thing they read.** They arrive at the review screen wanting one question answered — *what did this lap actually do?* — and you are the agent best placed to answer it: you ran last, you hold the spec, you have every implementer's own account above, and you have just read every commit and every hunk on the branch. Synthesizing those into one honest paragraph is work only you can do; none of the implementers could see past its own ticket.
|
|
144
143
|
|
|
145
144
|
So write **prose**, roughly 10–15 lines, for a reader who has none of your context:
|
|
146
145
|
|
|
@@ -159,8 +158,8 @@ The bar is now high, because the code review needs almost nothing: no app, no br
|
|
|
159
158
|
|
|
160
159
|
The review **failed** only when the code review itself could not run:
|
|
161
160
|
|
|
162
|
-
- the repository is unreadable, or
|
|
163
|
-
-
|
|
161
|
+
- the repository is unreadable, or `{{BASE_BRANCH}}` / `{{FEATURE_BRANCH}}` cannot be resolved at all,
|
|
162
|
+
- `git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}}` is empty *and* `{{FEATURE_BRANCH}}` does not exist. An empty diff on a branch that does exist is a **finding**, not a failed review — write the note and carry on.
|
|
164
163
|
|
|
165
164
|
When that happens: run the step 5 cleanup for whatever you got as far as starting — recorder, browser, drive — write **`{{BLOCKED_PATH}}`** — that path, not the repo — saying in one or two sentences precisely what stopped you, and print `<promise>COMPLETE</promise>`. Write no digest; the blocked file is your record. Do not write notes speculating about a feature you never saw.
|
|
166
165
|
|
package/skills/packs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Skill packs
|
|
2
2
|
|
|
3
|
-
**Packs** are runcastle-owned Claude Code *plugin directories* wired to runcastle's MCP contracts. They are injected into each launched terminal via `--plugin-dir`, so the human's Claude Code needs nothing preinstalled, and upstream changes to Matt Pocock's skills can never break us.
|
|
3
|
+
**Packs** are runcastle-owned Claude Code *plugin directories* wired to runcastle's MCP contracts. They are injected into each launched terminal via `--plugin-dir`, so the human's Claude Code needs nothing preinstalled, and upstream changes to Matt Pocock's skills can never break us. Most of the pack's skills are adapted forks of his methodology skills and each keeps a provenance header crediting the original; `revisit` and `waypoint` are original runcastle work.
|
|
4
4
|
|
|
5
5
|
## The `runcastle` pack
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ Scope-specific skills, each namespaced `/runcastle:<skill>`:
|
|
|
14
14
|
| `/runcastle:qa` | entry for `kind=qa` | read-only Q&A over an existing feature; never advances phases |
|
|
15
15
|
| `/runcastle:project` | entry for `kind=project` | project scope, not feature scope: consults the portfolio, advises on how a lump of intent should be cut into N features and creates them, routes, answers portfolio questions, curates advisory-only, and owns `CONTEXT.md` |
|
|
16
16
|
| `/runcastle:waypoint` | entry for `kind=waypoint` | *original* — works ONE waypoint on a mapped feature, writes its decision prose, resolves the waypoint |
|
|
17
|
-
| `/runcastle:converge` | entry for `kind=converge` | closes a mapped feature: reads
|
|
17
|
+
| `/runcastle:converge` | entry for `kind=converge` | closes a mapped feature: reads the compressed knowledge — `map.md`, `decisions.md`, and the `research/*.md` deliverables the research waypoints produced — then drives spec + tickets from it |
|
|
18
18
|
| `/runcastle:revisit` | entry for `kind=revisit` | *original* — folds late information into a finished feature; on a Rethink, runs the whole front half of a lap |
|
|
19
19
|
| `/runcastle:code-review` | by description, or by name | two-axis review (Standards + Spec) of a feature branch's diff against its base, run as parallel sub-agents and reported unmerged; never edits |
|
|
20
20
|
|
|
@@ -31,13 +31,38 @@ packs/
|
|
|
31
31
|
├── spec/SKILL.md
|
|
32
32
|
├── tickets/SKILL.md
|
|
33
33
|
├── qa/SKILL.md
|
|
34
|
-
├── project/
|
|
34
|
+
├── project/
|
|
35
|
+
│ ├── SKILL.md
|
|
36
|
+
│ └── references/ # loaded on demand by SKILL.md, never up front
|
|
37
|
+
│ ├── charter.md
|
|
38
|
+
│ └── health-sweeps.md
|
|
35
39
|
├── waypoint/SKILL.md
|
|
36
40
|
├── converge/SKILL.md
|
|
37
41
|
├── revisit/SKILL.md
|
|
38
42
|
└── code-review/SKILL.md
|
|
39
43
|
```
|
|
40
44
|
|
|
45
|
+
### `references/` — progressive disclosure
|
|
46
|
+
|
|
47
|
+
A `SKILL.md` is loaded **in full, unconditionally**, the moment its session
|
|
48
|
+
starts. So a section that only one conversation in five will ever need is a tax
|
|
49
|
+
on the other four. The convention (the same one upstream skills use) is a
|
|
50
|
+
`references/` directory beside `SKILL.md`, listed in the body by relative path
|
|
51
|
+
with a one-line description of when to read it; the agent has file tools in its
|
|
52
|
+
worktree and reads one only when that job actually arrives.
|
|
53
|
+
|
|
54
|
+
`project` is the case that earned it: the charter format (a full verbatim
|
|
55
|
+
markdown template) and the health-sweep procedure were ~18% of the largest skill
|
|
56
|
+
in the pack, loaded on every intake conversation that never touches either. Both
|
|
57
|
+
moved to `references/`, and `SKILL.md` keeps only the rules that must fire the
|
|
58
|
+
moment the subject comes up — "never scaffold an empty charter", "never run a
|
|
59
|
+
sweep unprompted" — plus the pointer.
|
|
60
|
+
|
|
61
|
+
Use it when a section is (a) long, (b) needed by a minority of the skill's
|
|
62
|
+
conversations, and (c) safe to be absent until then. Do **not** use it for
|
|
63
|
+
prohibitions or contracts: a rule the agent must not break has to be in the
|
|
64
|
+
always-loaded file, because a rule it has not read is not a rule.
|
|
65
|
+
|
|
41
66
|
## How the launcher consumes a pack
|
|
42
67
|
|
|
43
68
|
The session launcher spawns Claude Code with the pack's **root** directory (the folder that directly contains `.claude-plugin/`) passed to `--plugin-dir`:
|
|
@@ -46,12 +71,77 @@ The session launcher spawns Claude Code with the pack's **root** directory (the
|
|
|
46
71
|
claude ... --plugin-dir "<abs path>/packages/skills/packs/runcastle" ...
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
The `name` field in `plugin.json` becomes the invocation namespace, so the skills resolve as `/runcastle:ideate` and friends. The injected system prompt tells each session which entry skill to invoke for its kind (`/runcastle:ideate` for ideation, `/runcastle:qa` for Q&A).
|
|
74
|
+
The `name` field in `plugin.json` becomes the invocation namespace, so the skills resolve as `/runcastle:ideate` and friends. The injected system prompt tells each session which entry skill to invoke for its kind (`/runcastle:ideate` for ideation, `/runcastle:qa` for Q&A).
|
|
75
|
+
|
|
76
|
+
### `disable-model-invocation` — session entries off, chained and reached ones on
|
|
77
|
+
|
|
78
|
+
Every skill's `description` is loaded into every session in the pack, whether or
|
|
79
|
+
not that session can use it. That is cheap for a skill the model might genuinely
|
|
80
|
+
need and actively harmful for one it must not run: a `qa` session was carrying
|
|
81
|
+
the descriptions of `ideate`, `spec`, `tickets`, `converge`, `revisit`,
|
|
82
|
+
`waypoint` and `project` (~582 tokens), every one of them advertising a procedure
|
|
83
|
+
`qa/SKILL.md` forbids. Sharper still, `project/SKILL.md` says "**Never run an
|
|
84
|
+
ideation grilling**" while `ideate`'s description sat in the same list offering
|
|
85
|
+
exactly that.
|
|
86
|
+
|
|
87
|
+
So the six **session-entry** skills — `ideate`, `qa`, `converge`, `revisit`,
|
|
88
|
+
`waypoint`, `project` — carry `disable-model-invocation: true`. They are never
|
|
89
|
+
model-chosen: the kickoff line the launcher injects names the one skill for that
|
|
90
|
+
kind explicitly, by name, and explicit invocation works fine for a skill with
|
|
91
|
+
model invocation disabled. Nothing else should ever reach them, because reaching
|
|
92
|
+
one means a session running another session's procedure.
|
|
93
|
+
|
|
94
|
+
The remaining three stay `false`, each for a reason:
|
|
95
|
+
|
|
96
|
+
- `spec` and `tickets` are **chained**, invoked by name from inside `ideate`
|
|
97
|
+
and `converge` mid-conversation.
|
|
98
|
+
- `code-review` is the one skill that is not a session entry point at all — being
|
|
99
|
+
reached by description is the only way it is reached.
|
|
100
|
+
|
|
101
|
+
The rule, for anything added later: **a session-entry skill disables model
|
|
102
|
+
invocation; a chained or genuinely description-reached one does not.** The
|
|
103
|
+
question is not "might this be useful?" but "is there a session whose *whole job*
|
|
104
|
+
is something else that could pick this up by mistake?" If yes, disable it and
|
|
105
|
+
invoke it by name.
|
|
106
|
+
|
|
107
|
+
## Layering: prompt, skill, hook — one home each
|
|
108
|
+
|
|
109
|
+
A launched session reads the same fact from up to four places: the injected
|
|
110
|
+
system prompt, the entry skill, a skill it chains into, and the deny message of
|
|
111
|
+
a hook. Restating a rule in all of them does not make it stick harder — it makes
|
|
112
|
+
each copy free to drift, and the drifted copy is the one that gets believed.
|
|
113
|
+
|
|
114
|
+
The standing convention across this pack:
|
|
115
|
+
|
|
116
|
+
- **Per-session facts belong to the prompt** — the slug, the paths, the kind,
|
|
117
|
+
the kickoff line, and the standing prohibitions the hooks enforce (the no-code
|
|
118
|
+
rule lives there, backed by `launcher/edit-guard.ts`, and is *not* restated in
|
|
119
|
+
any skill).
|
|
120
|
+
- **Procedures belong to the skill.** One skill owns each procedure in full;
|
|
121
|
+
`revisit/SKILL.md` §Lap mode is the whole lap procedure, `ideate/SKILL.md` §3
|
|
122
|
+
is the whole escalation procedure.
|
|
123
|
+
- **Where a skill names a fact the prompt also names, it points instead of
|
|
124
|
+
restating** — the shape is `ideate/SKILL.md`'s "The injected system prompt
|
|
125
|
+
carries the slug and paths; trust `get_feature_context` for the live state."
|
|
126
|
+
- **Where two skills need the same procedure, the second delegates by
|
|
127
|
+
reference** — `revisit`'s "escalate the way ideation would
|
|
128
|
+
(`/runcastle:ideate` §3, the map)" rather than a second copy that ages
|
|
129
|
+
differently.
|
|
130
|
+
- **A rule the hook enforces is stated once, as enforced, not as advice.** The
|
|
131
|
+
`qa` kind's write refusals are server-side; `qa/SKILL.md` says so rather than
|
|
132
|
+
asking nicely.
|
|
133
|
+
|
|
134
|
+
A corollary that cost real sessions: **never tell a session to do something a
|
|
135
|
+
hook will deny.** An agent that believes a skill and hits a hard deny has no path
|
|
136
|
+
forward and improvises — ADR-0007 §6 documents one aborting and emitting a
|
|
137
|
+
ticket instead. `prototype` waypoints were in exactly that trap until the skill
|
|
138
|
+
was pointed at `docs/features/<slug>/prototypes/`, the one path the edit guard
|
|
139
|
+
lets a talk session write code into.
|
|
50
140
|
|
|
51
141
|
## Adding a pack
|
|
52
142
|
|
|
53
143
|
1. Create `packs/<pack-name>/.claude-plugin/plugin.json` with at least `{ "name": "<pack-name>" }` — `name` sets the `/<pack-name>:` namespace.
|
|
54
|
-
2. Add skills at `packs/<pack-name>/skills/<skill>/SKILL.md` (folder name = skill name; frontmatter needs `
|
|
144
|
+
2. Add skills at `packs/<pack-name>/skills/<skill>/SKILL.md` (folder name = skill name; frontmatter needs `name` matching the folder, `description`, and `disable-model-invocation` — see the split above). Long, rarely-needed sections go in a sibling `references/` dir.
|
|
55
145
|
3. If you are forking an upstream skill, keep the provenance header and rewire its steps to runcastle's MCP tools (`get_feature_context`, `emit_tickets`, `record_event`, `complete_phase`).
|
|
56
146
|
4. Pass another `--plugin-dir` for it at launch.
|
|
57
147
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runcastle",
|
|
3
|
-
"description": "Runcastle phase-scoped skills for the MCP feature pipeline: ideate, spec, tickets, qa, project, converge, code-review (forks of Matt Pocock's grilling, to-spec, to-tickets and code-review, adapted) plus revisit and
|
|
3
|
+
"description": "Runcastle phase-scoped skills for the MCP feature pipeline: ideate, spec, tickets, qa, project, converge, code-review (forks of Matt Pocock's grilling, to-spec, to-tickets and code-review, adapted) plus revisit, waypoint and prepare (original).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": { "name": "runcastle" }
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: converge
|
|
3
|
-
description: Close a mapped feature. Read
|
|
4
|
-
disable-model-invocation:
|
|
3
|
+
description: Close a mapped feature. Read the compressed knowledge — map.md, decisions.md and the research deliverables — never the waypoint transcripts, then drive spec and tickets out of that one unbroken window. Entry skill for kind=converge sessions.
|
|
4
|
+
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
<!-- Forked from Matt Pocock's grilling/to-spec/to-tickets discipline, via https://github.com/mattpocock/skills, 2026-07-15, adapted for runcastle mapped ideation -->
|
|
7
7
|
|
|
@@ -11,37 +11,42 @@ A mapped feature was too big for one window, so it was charted into a map and it
|
|
|
11
11
|
|
|
12
12
|
## Context hygiene is the whole game
|
|
13
13
|
|
|
14
|
-
Everything happens in this one window. **Never compact. Never `/clear`. Never suggest either.** The map plus the decisions ARE the compression — that is the whole point of charting a map. Read them once, hold them, and let spec and tickets fall out of the same conversation.
|
|
14
|
+
Everything happens in this one window. **Never compact. Never `/clear`. Never suggest either.** The map plus the decisions plus the research ARE the compression — that is the whole point of charting a map. Read them once, hold them, and let spec and tickets fall out of the same conversation.
|
|
15
15
|
|
|
16
|
-
## 0. Read
|
|
16
|
+
## 0. Read the compressed knowledge — and only that
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
Call `mcp__runcastle__get_feature_context`. The injected system prompt carries the slug and the paths; trust `get_feature_context` for the live state. It returns the feature, the current `phase`, the map, the tickets, and its docs in two parts — `docs[]`, inlined in full, and `moreDocs[]`, an index of everything else with a `relPath` and a byte count. Read, in this order:
|
|
19
|
+
|
|
20
|
+
1. **`map.md`** (inlined in `docs[]`) — the destination, the notes, and what was ruled out of scope.
|
|
21
|
+
2. **`decisions.md`** (inlined in `docs[]`) — every decision the waypoint sessions locked. This is the spine of the spec.
|
|
22
|
+
3. **`research/*.md`** — the deliverables the `research` waypoints wrote, one file each, under `docs/features/<slug>/research/`. **These are not inlined.** They appear in `moreDocs[]` with their paths and sizes, and each resolved research waypoint's `summary` in `waypoints[]` names its file (`researched: <title> — see <path>`). Read them with `mcp__runcastle__read_feature_doc({ relPath })`, or straight off disk in this worktree.
|
|
23
|
+
|
|
24
|
+
**Do not skip step 3.** A research waypoint is forbidden to write `decisions.md` — its findings live *only* in its own file. If a whole AFK run went into researching this feature and you converge without opening the file it produced, that run is simply lost, and the spec you write will contradict it. Read every research deliverable the map produced, or say out loud which one you skipped and why.
|
|
25
|
+
|
|
26
|
+
**Do NOT read the waypoint session transcripts.** The map, the decisions and the research deliverables are the compression; trust them. If something is genuinely missing you may check the codebase for a *fact*, but never re-grill the human and never reopen a resolved waypoint — converge, do not re-ideate.
|
|
27
|
+
|
|
28
|
+
If a `map.md` gap is big enough that no spec can be written over it, do not paper over it and do not grill it out here: say so plainly and tell the human to work one more waypoint before converging.
|
|
24
29
|
|
|
25
30
|
## 1. Run the pipeline in this window
|
|
26
31
|
|
|
27
32
|
Invoke `/runcastle:spec` (it writes `spec.md` and completes the `spec` phase); when it returns, invoke `/runcastle:tickets`.
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
or was closed mid-way (the feature is past G1 but has no tickets). Pick up from
|
|
31
|
-
whatever already exists on disk:
|
|
34
|
+
Do not open a new session for these. They run here, on top of the compressed knowledge you just read.
|
|
32
35
|
|
|
33
|
-
-
|
|
34
|
-
rewrite it, and proceed straight to `/runcastle:tickets`. (If the current
|
|
35
|
-
`phase` is still `spec`, call `complete_phase` for spec first — the spec work
|
|
36
|
-
is already done.)
|
|
37
|
-
- If it does not exist, run `/runcastle:spec` then `/runcastle:tickets` as normal.
|
|
36
|
+
**Re-convergence.** You may be a fresh session continuing a converge that crashed or was closed mid-way — the feature is past G1 but has no tickets. Do not start over; pick up from whatever is already on disk:
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
- **`spec.md` already exists** (it is in `docs[]`): read it, do **not** rewrite it, and go straight to `/runcastle:tickets`. **If the current `phase` is still `spec`, call `mcp__runcastle__complete_phase({ phase: "spec" })` first** — the spec work is done, the phase just never got closed, and `/runcastle:tickets` cannot complete a phase it is not standing in.
|
|
39
|
+
- **`spec.md` does not exist**: run `/runcastle:spec` then `/runcastle:tickets` as normal.
|
|
40
|
+
- **Tickets already exist** for this lap: reconcile rather than duplicate — this is the one case where `update_ticket` / `cancel_ticket` beat a second `emit_tickets`.
|
|
41
|
+
|
|
42
|
+
## 2. Parking, not swallowing
|
|
43
|
+
|
|
44
|
+
If something surfaces that is plainly its own feature rather than part of this one, park it: `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` creates a parked draft — no branch, nothing written — with the `brief` carrying why you deferred it and what it must not swallow. Then get back to converging. Drafts are the only feature you may create here; a full create belongs to the project session.
|
|
40
45
|
|
|
41
|
-
##
|
|
46
|
+
## 3. Close — hand off to the human
|
|
42
47
|
|
|
43
48
|
`/runcastle:tickets` ends by emitting tickets to the runcastle store. When it returns, your job is **done**. Tell the human plainly:
|
|
44
49
|
|
|
45
50
|
> Converged. Tickets are in the runcastle UI — review the ticket cards and click **Burn** to start the AFK agents. I'll stop here.
|
|
46
51
|
|
|
47
|
-
Do **not**
|
|
52
|
+
Do **not** start burning. The two human clicks (Burn, then Merge after test-drive) are the only gates left, and they belong to the human. (You do not write code here either — that rule is in your injected prompt and enforced by the edit guard, not restated as advice.)
|