runcastle 1.2.6 → 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.
Files changed (37) hide show
  1. package/bin/runcastle.js +8467 -6620
  2. package/drizzle/0026_polite_vertigo.sql +2 -0
  3. package/drizzle/0027_dashing_texas_twister.sql +1 -0
  4. package/drizzle/0028_tired_deadpool.sql +2 -0
  5. package/drizzle/0029_damp_stellaris.sql +1 -0
  6. package/drizzle/meta/0026_snapshot.json +921 -0
  7. package/drizzle/meta/0027_snapshot.json +928 -0
  8. package/drizzle/meta/0028_snapshot.json +942 -0
  9. package/drizzle/meta/0029_snapshot.json +949 -0
  10. package/drizzle/meta/_journal.json +29 -1
  11. package/index.js +8424 -6588
  12. package/package.json +1 -1
  13. package/sandcastle-template/Containerfile +9 -1
  14. package/sandcastle-template/Dockerfile +9 -1
  15. package/skills/burner/implement-ticket.md +32 -47
  16. package/skills/burner/resolve-conflict.md +3 -3
  17. package/skills/burner/review-ticket.md +118 -37
  18. package/skills/packs/README.md +99 -7
  19. package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
  20. package/skills/packs/runcastle/skills/code-review/SKILL.md +131 -0
  21. package/skills/packs/runcastle/skills/converge/SKILL.md +26 -21
  22. package/skills/packs/runcastle/skills/ideate/SKILL.md +14 -8
  23. package/skills/packs/runcastle/skills/prepare/SKILL.md +96 -0
  24. package/skills/packs/runcastle/skills/prepare/references/recipes.md +72 -0
  25. package/skills/packs/runcastle/skills/project/SKILL.md +54 -55
  26. package/skills/packs/runcastle/skills/project/references/charter.md +79 -0
  27. package/skills/packs/runcastle/skills/project/references/health-sweeps.md +44 -0
  28. package/skills/packs/runcastle/skills/qa/SKILL.md +29 -13
  29. package/skills/packs/runcastle/skills/revisit/SKILL.md +8 -8
  30. package/skills/packs/runcastle/skills/spec/SKILL.md +3 -3
  31. package/skills/packs/runcastle/skills/tickets/SKILL.md +19 -3
  32. package/skills/packs/runcastle/skills/waypoint/SKILL.md +14 -9
  33. package/web/assets/index-BRjcVBgj.js +142 -0
  34. package/web/assets/index-CNSn8X1O.css +1 -0
  35. package/web/index.html +2 -2
  36. package/web/assets/index-bof4-dhY.css +0 -1
  37. package/web/assets/index-qHtxEVWq.js +0 -142
@@ -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. Six of the eight skills are adapted forks of his methodology skills and each keeps a provenance header crediting the original; `revisit` and `waypoint` are original runcastle work.
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
 
@@ -12,10 +12,11 @@ Scope-specific skills, each namespaced `/runcastle:<skill>`:
12
12
  | `/runcastle:spec` | by ideate | synthesizes `spec.md`, completes the `spec` phase |
13
13
  | `/runcastle:tickets` | by ideate | emits session-sized vertical-slice tickets via MCP, completes the `tickets` phase |
14
14
  | `/runcastle:qa` | entry for `kind=qa` | read-only Q&A over an existing feature; never advances phases |
15
- | `/runcastle:project` | entry for `kind=project` | project scope, not feature scope: grills a lump of intent into N features and creates them, routes, answers portfolio questions, curates advisory-only, and owns `CONTEXT.md` |
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 only the compressed knowledge, then drives spec + tickets from it |
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
+ | `/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 |
19
20
 
20
21
  Layout (the verified plugin format — only `plugin.json` lives inside `.claude-plugin/`; `skills/` is a sibling at the plugin root):
21
22
 
@@ -30,12 +31,38 @@ packs/
30
31
  ├── spec/SKILL.md
31
32
  ├── tickets/SKILL.md
32
33
  ├── qa/SKILL.md
33
- ├── project/SKILL.md
34
+ ├── project/
35
+ │ ├── SKILL.md
36
+ │ └── references/ # loaded on demand by SKILL.md, never up front
37
+ │ ├── charter.md
38
+ │ └── health-sweeps.md
34
39
  ├── waypoint/SKILL.md
35
40
  ├── converge/SKILL.md
36
- └── revisit/SKILL.md
41
+ ├── revisit/SKILL.md
42
+ └── code-review/SKILL.md
37
43
  ```
38
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
+
39
66
  ## How the launcher consumes a pack
40
67
 
41
68
  The session launcher spawns Claude Code with the pack's **root** directory (the folder that directly contains `.claude-plugin/`) passed to `--plugin-dir`:
@@ -44,12 +71,77 @@ The session launcher spawns Claude Code with the pack's **root** directory (the
44
71
  claude ... --plugin-dir "<abs path>/packages/skills/packs/runcastle" ...
45
72
  ```
46
73
 
47
- 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). All eight skills carry `disable-model-invocation: false`, so the model may also reach for them by description.
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.
48
140
 
49
141
  ## Adding a pack
50
142
 
51
143
  1. Create `packs/<pack-name>/.claude-plugin/plugin.json` with at least `{ "name": "<pack-name>" }` — `name` sets the `/<pack-name>:` namespace.
52
- 2. Add skills at `packs/<pack-name>/skills/<skill>/SKILL.md` (folder name = skill name; frontmatter needs `description` + `disable-model-invocation`).
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.
53
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`).
54
146
  4. Pass another `--plugin-dir` for it at launch.
55
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 (forks of Matt Pocock's grilling, to-spec and to-tickets, adapted) plus revisit and waypoint (original).",
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
  }
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: code-review
3
+ description: Review a feature branch's diff against its base along two axes — Standards (does the code follow this repo's documented standards?) and Spec (does the code do what docs/features/<slug>/spec.md asked for?). Runs both axes as parallel sub-agents and reports them side by side, never merged. Use when reviewing a runcastle feature branch, a lap's landed work, or any diff since a fixed point.
4
+ disable-model-invocation: false
5
+ ---
6
+ <!-- Forked from Matt Pocock's code-review skill, via https://github.com/mattpocock/skills, 2026-08-17, adapted for runcastle feature branches (base-branch fixed point, spec.md as the spec source) -->
7
+
8
+ # Code review — two axes over a feature branch
9
+
10
+ Two-axis review of the diff between the feature branch and the base it forked from:
11
+
12
+ - **Standards** — is it built right? Does the code follow how this repo writes code?
13
+ - **Spec** — is it the right thing? Does the code do what `spec.md` asked for?
14
+
15
+ Both axes run as **parallel sub-agents**, so neither sees the other's reasoning, and this skill aggregates the two reports without merging them.
16
+
17
+ You review. You **never fix** — not the bugs, not the smells, not the one-line rename that is obviously right. A review that edits the diff it is reviewing has stopped being a review.
18
+
19
+ ## Why two axes
20
+
21
+ A change can pass one and fail the other:
22
+
23
+ - Code that follows every convention while implementing the wrong thing → **Standards pass, Spec fail.**
24
+ - Code that does exactly what the spec asked while breaking the repo's conventions → **Spec pass, Standards fail.**
25
+
26
+ Blending them lets the passing axis hide the failing one. That is the whole reason for the separation, so nothing downstream may re-rank across it.
27
+
28
+ ## 1. Pin the fixed point
29
+
30
+ Unlike upstream, **nobody supplies the fixed point** — a runcastle feature branch has one by construction, and the review runs unattended. Resolve it, in this order, stopping at the first that works:
31
+
32
+ 1. The feature's `baseBranch`, if you were handed it (the ticket, the feature context, the prompt that called you).
33
+ 2. The repo's default branch — `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`, else `master`.
34
+
35
+ Then take the merge-base comparison, three-dot, so the diff is the branch's own work and not everything that landed on base meanwhile:
36
+
37
+ ```
38
+ git merge-base --is-ancestor <base> HEAD # sanity: does the ref resolve at all?
39
+ git log <base>...HEAD --oneline # the commits under review
40
+ git diff <base>...HEAD # the diff both axes read
41
+ ```
42
+
43
+ **Check before you spawn anything.** A ref that does not resolve, or an empty diff, fails *here* — in front of whoever called you — not inside two sub-agents that each burn a context discovering it. Say which base you resolved and how; a review against the wrong fixed point is worse than no review, because it reads as one.
44
+
45
+ Three-dot excludes uncommitted work. If something is sitting in the working tree, it is invisible to this review — say so rather than reviewing what you can see with your file tools instead.
46
+
47
+ ## 2. Find the spec
48
+
49
+ runcastle features always have one, so there is no "no spec available" path to reach for first:
50
+
51
+ 1. `docs/features/<slug>/spec.md` — the requirements. This is the primary source.
52
+ 2. `docs/features/<slug>/decisions.md` — *why* each shape was chosen. Read it: half the Spec-axis findings that look like scope creep are a locked decision the spec states tersely.
53
+ 3. A spec digest already pasted into your prompt — use it verbatim rather than re-reading, if it is the whole document.
54
+
55
+ Only if all of those are genuinely absent does the Spec axis skip, and then it reports "no spec available" rather than inferring requirements from the code it is meant to be checking.
56
+
57
+ Review against the spec **as written**. If the diff is good work that the spec did not ask for, that is a Spec finding (scope creep), not a compliment.
58
+
59
+ ## 3. Gather the standards
60
+
61
+ Whatever this repo documents about how its code should be written:
62
+
63
+ - `CLAUDE.md` — the repo's own agent-facing conventions, and the highest authority here.
64
+ - `CONTEXT.md` — the charter: the vocabulary and the principles it will not violate.
65
+ - `docs/adr/` — live ADRs bind the code. A superseded one does not; check before you cite.
66
+ - Anything else the repo keeps for the purpose (`CONTRIBUTING.md`, a coding-standards doc).
67
+
68
+ On top of those, the Standards axis always carries the **smell baseline** below, so it still has a floor on a repo that documents nothing. Two rules bind it:
69
+
70
+ - **The repo overrides.** A documented standard always wins. Where the repo endorses something the baseline would flag, suppress the smell.
71
+ - **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces. Do not report what the linter, the formatter or `tsc` would have caught; they ran, and they are not the reason a human is reading you.
72
+
73
+ Each smell reads *what it is* → *how to fix*, so a finding arrives with a move attached instead of a complaint:
74
+
75
+ - **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
76
+ - **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both.
77
+ - **Feature Envy** — a method that reaches into another object's data more than its own. → move the method onto the data it envies.
78
+ - **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that.
79
+ - **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type.
80
+ - **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share.
81
+ - **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. → gather what changes together into one module.
82
+ - **Divergent Change** — one file or module is edited for several unrelated reasons. → split so each module changes for one reason.
83
+ - **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows.
84
+ - **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object.
85
+ - **Middle Man** — a class or function that mostly just delegates onward. → cut it, call the real target direct.
86
+ - **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition.
87
+
88
+ ## 4. Spawn both axes in parallel
89
+
90
+ One sub-agent each, in a single dispatch so they actually run concurrently.
91
+
92
+ **Standards sub-agent** — hand it:
93
+
94
+ - The diff command and the commit list from §1.
95
+ - The standards files you found in §3, by path, **plus the smell baseline pasted in full** — the sub-agent has no other access to it.
96
+ - The brief: *"Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls — documented-standard breaches can be hard, baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words. Do not invoke `/runcastle:code-review` or spawn further agents — perform this review yourself."*
97
+
98
+ **Spec sub-agent** — hand it:
99
+
100
+ - The diff command and the commit list.
101
+ - The path or the contents of `spec.md`, and `decisions.md` alongside it.
102
+ - The 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 invoke `/runcastle:code-review` or spawn further agents — perform this review yourself."*
103
+
104
+ **That last sentence in each brief is load-bearing.** Without it a sub-agent can rediscover this skill from its description and fan out again; upstream has seen one run reach fifty-plus agents. Two sub-agents, one level deep, and that is the whole tree.
105
+
106
+ ## 5. Report
107
+
108
+ Present the two reports under `## Standards` and `## Spec`, verbatim or lightly cleaned. Do **not** merge them and do **not** re-rank across them.
109
+
110
+ Close with one line: the count per axis, and the worst issue *within each axis*. Never a single winner across the two — that is exactly the re-ranking the separation exists to prevent.
111
+
112
+ Every finding carries its citation: a standards file plus the rule, or a named smell plus the quoted hunk, or the line of the spec. **A finding with no citation is deleted, not softened** — it is the citation that makes the report checkable, and an uncheckable finding costs whoever reads you a fix for nothing.
113
+
114
+ Sub-agent output is a hypothesis, not evidence. Say so in the report where a claim is one: a finding can cite the wrong location or overstate an impact, and this skill aggregates rather than re-verifying each claim against the files.
115
+
116
+ ## Where the findings go
117
+
118
+ That is the caller's call, not yours. Reporting the two blocks IS this skill's deliverable.
119
+
120
+ - Called **in a session**, the report is the answer: the human reads it and decides.
121
+ - Called **by the review-ticket burner**, each finding becomes its own test note (`mcp__runcastle__add_test_note`) — one note per finding, because each note can be promoted to a fix ticket in a click and a note bundling three findings makes a bad ticket. The burner's prompt says this; follow it there.
122
+
123
+ Either way: **finding problems is a successful review.** The report is the deliverable, not a verdict, and not a pass/fail.
124
+
125
+ ## Do NOT
126
+
127
+ - **Never edit the diff you are reviewing.** No fixes, no commits, no "while I was in there".
128
+ - **Never merge or re-rank the two axes**, and never pick an overall winner.
129
+ - **Never report a finding without its citation**, and never restate what tooling already enforces.
130
+ - **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
131
+ - **Never run this in a loop until it comes back clean.** Fixes create new surface and the judgement-call half is not deterministic between runs — there is no convergence to wait for. One pass, act on the cited leads, stop.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: converge
3
- description: Close a mapped feature. Read ONLY the compressed knowledge (map.md + decisions.md, 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: false
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 ONLY the compressed knowledge
16
+ ## 0. Read the compressed knowledge — and only that
17
17
 
18
- 1. Call `mcp__runcastle__get_feature_context`. It returns the feature (`slug`, `title`, `oneLiner`), current `phase`, and the docs on disk.
19
- 2. Read **only** these two files under `docs/features/<slug>/`:
20
- - `map.md` — the destination, notes, and out-of-scope decisions.
21
- - `decisions.md` — every decision the waypoint sessions locked.
22
- **Do NOT read the waypoint session transcripts.** The map and decisions are the compression; trust them. If a decision 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.
23
- 3. `mcp__runcastle__record_event({ type: "converge.started", message: "<feature title>" })`.
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
- **Re-convergence** — you may be a fresh session continuing a converge that crashed
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
- - If `docs/features/<slug>/spec.md` **already exists**: read it, do **not**
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
- Do not open a new session for these. They run here, on top of the compressed knowledge you just read.
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
- ## 2. Close — hand off to the human
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** implement anything. 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.
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.)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ideate
3
3
  description: The runcastle ideation session. Grill the human relentlessly about a feature, lock decisions incrementally into decisions.md, then drive spec and tickets out of the same unbroken context. Entry skill for kind=ideation sessions.
4
- disable-model-invocation: false
4
+ disable-model-invocation: true
5
5
  ---
6
6
  <!-- Forked from Matt Pocock's grilling + grill-with-docs skills, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
7
7
 
@@ -15,9 +15,17 @@ Everything happens in this one window. **Never compact. Never `/clear`. Never su
15
15
 
16
16
  ## 0. Orient
17
17
 
18
- 1. Call `mcp__runcastle__get_feature_context`. It returns the feature (`slug`, `title`, `oneLiner`), current `phase`, the docs already on disk (`brief.md`, and any `decisions.md` — **this may be a resumed session; read what is already locked and do not re-ask it**), and any tickets.
18
+ 1. Call `mcp__runcastle__get_feature_context`. It returns the feature (`slug`, `title`, `oneLiner`), current `phase` and `lap`, the canonical docs inlined in `docs[]` (`brief.md`, and any `decisions.md` — **this may be a resumed session; read what is already locked and do not re-ask it**), an index of every other doc in `moreDocs[]` (fetch one with `mcp__runcastle__read_feature_doc({ relPath })`), and any tickets.
19
19
  2. Decisions live at `docs/features/<slug>/decisions.md` in this worktree. The injected system prompt carries the slug and paths; trust `get_feature_context` for the live state.
20
- 3. `mcp__runcastle__record_event({ type: "ideation.started", message: "<feature title>" })`.
20
+
21
+ **Your tools**, and there are not many — this session's real work is the conversation:
22
+
23
+ - `mcp__runcastle__get_feature_context` / `mcp__runcastle__read_feature_doc` / `mcp__runcastle__list_tickets` — the record.
24
+ - `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` — park scope creep as a draft feature (§1). `draft: true` is the only door open to you; a full create belongs to the project session.
25
+ - `mcp__runcastle__escalate_to_map` + `mcp__runcastle__emit_waypoints` — the escalation branch (§3). This is the entire entry point into mapped ideation: no other surface charts a map from a linear feature.
26
+ - `mcp__runcastle__complete_phase` — the phase boundaries (§4), and the ones `/runcastle:spec` and `/runcastle:tickets` cross after you.
27
+ - `mcp__runcastle__emit_tickets` — used by `/runcastle:tickets`, not by you directly.
28
+ - `mcp__runcastle__record_event` — the feature timeline. Most state changes already emit their own event; only reach for this when the timeline would otherwise have no record at all.
21
29
 
22
30
  ## 1. Grill relentlessly
23
31
 
@@ -44,9 +52,7 @@ The moment a decision locks, append it to `docs/features/<slug>/decisions.md`
44
52
  **Why:** <the reason / the trade-off chosen over the alternatives>
45
53
  ```
46
54
 
47
- Keep `decisions.md` free of implementation minutiae — it is the record of *what was decided and why*, the raw material spec and tickets are built from. `complete_phase` checkpoints the file into git for you; you just write it.
48
-
49
- Every few locked decisions, `mcp__runcastle__record_event({ type: "decision.locked", message: "<n> decisions locked" })`.
55
+ Keep `decisions.md` free of implementation minutiae — it is the record of *what was decided and why*, the raw material spec and tickets are built from. `complete_phase` checkpoints the file into git for you; you just write it. The file is the record — do not narrate it onto the timeline as you go.
50
56
 
51
57
  ## 3. Escalation branch — when the feature outgrows this window
52
58
 
@@ -67,7 +73,7 @@ Some features are too big for one unbroken context. The tells: you are rabbit-ho
67
73
 
68
74
  When the human confirms shared understanding and the open questions are answered or explicitly deferred:
69
75
 
70
- `mcp__runcastle__complete_phase({ phase: "ideation" })` → returns `{ ok, nextPhase }`. If `ok: false`, it names what the gate wants (e.g. `decisions.md` missing) — fix it and retry. Then `record_event({ type: "phase.completed", message: "ideation" })`.
76
+ `mcp__runcastle__complete_phase({ phase: "ideation" })` → returns `{ ok, nextPhase }`. If `ok: false`, it names what the gate wants (e.g. `decisions.md` missing) — fix it and retry. It logs its own timeline event; do not add one.
71
77
 
72
78
  ## 5. Spec → tickets — stay in this window
73
79
 
@@ -81,4 +87,4 @@ Do not open a new session for these. They run here, on top of everything you jus
81
87
 
82
88
  > Tickets are in the runcastle UI. Review the ticket cards and click **Burn** to start the AFK agents. That is the next step — I will stop here.
83
89
 
84
- Do **not** implement anything. 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.
90
+ 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.)
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: prepare
3
+ description: Establish a project's host settings with evidence — the dev command, the drive setup/stop scripts, the dev-database reset — by running them on the developer's own machine rather than guessing them from config. Covers the drive contract, discovering a project's shape, and the closing dry-run drive. Entry skill for kind=prepare sessions.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Prepare — establish this project's host settings
8
+
9
+ A preparation session runs in the developer's **real checkout on their own machine**, not in a sandbox. That is the whole capability it exists to use: every setting here can be *run* instead of guessed at, and a value with an account of itself is worth more than a value that merely looks plausible.
10
+
11
+ Your system prompt carries the per-session facts — the repo path, which keys are still open, what has already been established and how stale it is. This skill is the method.
12
+
13
+ ## The order of the conversation
14
+
15
+ 1. Open by naming what is still open and what you need from the human for each. Do not survey the repo first; the prompt already told you the gap.
16
+ 2. Work the keys **one at a time**: propose, ask, run it if they agree, record it.
17
+ 3. If the open keys touch the drive loop, author the machinery (below) before you record the two hook commands — the settings are invocation lines for scripts that must exist.
18
+ 4. Close by proposing a dry-run drive (below). Values that have never been driven look exactly as trustworthy as values that run perfectly.
19
+
20
+ **Ask before you act.** Anything that starts or stops a service, creates or migrates a database, installs software, or writes outside the repo needs the human to agree first: say what you are about to run and why, then wait. Their own stack is running next to yours.
21
+
22
+ ## The seven keys
23
+
24
+ Four describe the drive loop and can only be settled here:
25
+
26
+ - **`devCommand`** — spawned in a drive-owned terminal pane for the length of a test drive. The first localhost URL it prints becomes the "Open app" link.
27
+ - **`driveSetupCommand`** / **`driveStopCommand`** — run on the host, in the project repo, before the dev pane starts and after the drive stops. They are the **invocation lines** of scripts you write and commit, not the machinery itself.
28
+ - **`dbResetCommand`** — **not** part of the drive loop. Its only consumer is the migration-drift banner after a drive stops: when the drive branch and the branch you returned to disagree about migration files, this is offered as the one-click dev-database rebuild.
29
+
30
+ Three describe the **sandbox** a burn agent works in, and no amount of running things on this host settles them — they are judgement calls about the repo, so treat them as human-supplied unless the human asks you to work one out with them:
31
+
32
+ - **`setupCommand`** — what a fresh sandbox runs before the agent starts (install, plus any codegen or build step every ticket needs). Setting it *replaces* install detection, so the install command must be included explicitly.
33
+ - **`verifyCommands`** — the exact typecheck/test/lint lines a burn agent should use, one per line. Unset, agents guess workspace filter names and burn whole suite runs discovering the right one.
34
+ - **`knownFailures`** — what already fails on the main branch, so an agent can tell its own breakage from the repo's. Free text; a count plus the suite names is enough.
35
+
36
+ ## The drive contract
37
+
38
+ A drive is a script **you** write, committed to the project, plus the two things only the server can do. Everything else — ports, database names, redis indexes, compose project names, URLs — the script computes for itself. Runcastle mandates these seven points and nothing else:
39
+
40
+ 1. **The machinery lives in `.runcastle/`, committed to the repo.** Write the steps as real scripts and commit them. Versioning them with the code they prepare is the load-bearing part: a branch that adds a package, a service or a migration amends its own script, and the drive on that branch runs the amended version. Nothing inspects a diff, ever.
41
+ 2. **`driveSetupCommand` / `driveStopCommand` are one line each** — how to invoke those scripts. Logic in the setting instead of the script is logic no branch can amend.
42
+ 3. **Identity comes in as `RUNCASTLE_*`.** Every drive hook and the dev pane are handed `RUNCASTLE_SLUG` (the feature slug), `RUNCASTLE_BRANCH` (the branch under the wheel) and `RUNCASTLE_ID` — that slug made identifier-safe (lowercase `[a-z0-9_]`, never leading with a digit, length-capped), so it is legal as a database, schema or container name. Derive every per-drive name from `RUNCASTLE_ID`. Never derive one from `git rev-parse`: the dry run drives under a synthetic identity on whatever branch is checked out.
43
+ 4. **Computed values go back out through `.runcastle/drive.env`.** Setup appends plain `KEY=VALUE` lines there; when it exits, the server parses that file and overlays it verbatim onto the dev pane and the stop hook, and shows the variable NAMES on the timeline. It is the only way a value your script computed reaches the dev server — a variable exported inside the script dies with the script. Truncate the file at the top of setup so a rerun does not accumulate stale lines.
44
+ 5. **`.runcastle/drive.env` MUST be gitignored.** Add the entry yourself. The server deletes the file when a drive ends, but a scratch file holding a connection string must never be one `git add -A` away from a commit.
45
+ 6. **Every step is unconditionally idempotent.** Install, migrate, seed, compose up — run them every time, never behind a "has anything changed?" check. A no-op on a clean tree is cheap; a skipped install on a branch that added a package is a dead drive. This is how the loop absorbs whatever a feature branch changed with no delta detection anywhere.
46
+ 7. **Exit 0 means the services are actually up.** The waits belong INSIDE the script — `docker compose up --wait`, a `pg_isready` loop, curl-until-healthy — because the dev pane starts the instant setup returns. The server waits for the app itself; it will not wait for your database.
47
+
48
+ Stop undoes what setup made, **for this identity only**: drop the database it created, take its compose project down with its volumes, free its ports. The human's own stack is never yours.
49
+
50
+ Writing those files is the one exception to a preparation session not editing the repo: you may write `.runcastle/` and `.gitignore` in this checkout and nothing else — a hook enforces it. Show the human the script before you commit it; it is their repo and their PR.
51
+
52
+ ## Discover the shape before you author anything
53
+
54
+ Projects differ in every dimension this touches, and a script fitted to a project you imagined is worse than no script at all. Your system prompt reports what the server could probe for itself — the platform, whether there is a compose file, whether `.runcastle/` already exists, which lockfile is present. Take those as read and find out the rest, by reading the repo and running things here:
55
+
56
+ - **Workspace layout** — one package or a monorepo with workspaces, and which package the app actually is. Install, migrate and seed each run from somewhere specific.
57
+ - **The services the app needs to boot** — database, redis, queues, object storage, a second process. Read the config and the env example, then confirm with the human.
58
+ - **Whether the human actually uses docker for this project.** A compose file in the repo is not consent to run it.
59
+ - **Hosted or local data stores** — a local postgres you may freely `createdb` on is a very different recipe from a hosted one where you may only branch or add a schema.
60
+ - **How the app loads its environment** — see the audit below.
61
+
62
+ Then write the smallest script that brings THAT shape up. For worked shapes to adapt — postgres-per-drive, docker compose, redis, hosted databases, deterministic ports — read `references/recipes.md`. They are illustrations, not a library: take the idea and fit it to what you found, in the language this host actually runs.
63
+
64
+ ## Audit how the app loads its environment
65
+
66
+ The overlay is process environment, which beats a `.env` file in dotenv, Prisma and Next by default. One pattern defeats it: a loader told to clobber what is already exported — `dotenv.config({ override: true })` and its equivalents — ignores everything the script computed and quietly keeps the app on the shared database. That is a drive that looks perfect while testing the wrong data. No machinery can detect it; you are the detector.
67
+
68
+ Grep the app's entry points and config for env loading and decide, for each, which side wins. Where the process environment loses: get it fixed — you may not edit app code from this session, so propose the change (usually dropping `override`) and let the human make it — or, if it must stay, record the finding with `record_event` naming the file and what it breaks, so the first confusing drive is not a mystery.
69
+
70
+ ## Recording what you establish
71
+
72
+ One `record_finding` call per key, and `evidence` is not optional in spirit: record what you ran and what it printed, or what the human told you. A value with no account of itself is a guess with a source field.
73
+
74
+ `userSupplied` is the one flag worth being careful with. **True** means the human GAVE you this value or confirmed it verbatim — that marks it as theirs and permanently stops automatic runs from overwriting it. Leave it **false** for anything you worked out yourself, even with them watching; that stays improvable by a later run. Getting this backwards silently retires a field from preparation forever, and the only way back is the human clearing it.
75
+
76
+ Secrets: this is a development environment and the human has agreed to supply real connection strings and credentials here. Store them as given. Do not paste a secret into a timeline note or a commit message — `record_finding` is the only place a value belongs.
77
+
78
+ ## Closing move: propose a dry-run drive
79
+
80
+ Once the open drive keys are recorded, end the session by proposing one. Recorded values that have never been driven look exactly as trustworthy as values that run perfectly, and the first person to find out otherwise is someone mid-feature with a broken environment.
81
+
82
+ **Ask first.** This starts services and creates a database on their machine. Name what will run — the setup command, the dev command, the stop command — then wait. If they decline, end the session normally: the keys simply stay unverified, and the drive UI will keep saying so.
83
+
84
+ On a yes, `dry_run_drive({ action })` runs it in two halves and you inspect between them:
85
+
86
+ 1. **`start`** — the server runs `driveSetupCommand` with the identity variables, reads back the `.runcastle/drive.env` it wrote (the reply lists the variable NAMES it parsed) and spawns `devCommand` in a real drive pane under that overlay. Identity is the reserved slug `prep-dry-run`, so `RUNCASTLE_ID` is `prep_dry_run` and a script deriving from it makes e.g. `myapp_prep_dry_run`, on the current branch. Nothing is checked out.
87
+ 2. **While it is up**, check what the server cannot: the variable names came back as you meant them, the temp database exists and is FRESH, the migrations applied, and the app actually RESPONDS at the sniffed URL — the server waits for it to answer before "Open app" goes live, but only you can say the page is the right one. `status` gives you the pane and the URL while you work.
88
+ 3. **`stop`** — the server runs `driveStopCommand` under the same overlay. Then check the cleanup: temp database gone, no orphaned process, container or volume left behind.
89
+
90
+ Anything off at any step, fix it — amend the script, or `record_finding` for a key — and run the WHOLE thing again until a pass is clean. Watch especially for a `myapp_prep_dry_run` left standing after the stop: an idempotent setup will happily reuse it on the next start, so a teardown that never worked reads exactly like a drive that did.
91
+
92
+ The verified stamps are computed server-side from what the machinery observed, and only on a clean full pass. You cannot mark your own homework: your deeper checks decide whether to retry, never what gets stamped.
93
+
94
+ ## When nothing is open
95
+
96
+ If your system prompt says every key is already set, this session is a **confirmation**, not a preparation. Say which values are recorded and how stale they are, ask the human whether they still hold, and stop. Do not re-derive a settled value, and do not propose a dry run unasked — replacing a measured value with a fresh guess makes preparation worse, not better.
@@ -0,0 +1,72 @@
1
+ # Drive recipes — shapes to adapt, never to copy
2
+
3
+ Each of these is one shape that has worked. Take the idea and fit it to the project you actually found.
4
+
5
+ ## Read this first: the snippets are sketches, not scripts
6
+
7
+ Every fragment below is written in POSIX shell because it is the most compact way to show the *idea*. **They are pseudocode.** They have not been run, they omit error handling, and two of them (`pick_port`, `port_in_use`) call helpers that do not exist anywhere — you have to write them.
8
+
9
+ Write the real thing in **whatever language this host actually runs**, which the system prompt reports as `process.platform`:
10
+
11
+ - **`win32`** — PowerShell (`.ps1`), or a cross-platform `node`/`bun` script. Do not hand a Windows developer a bash script and a `cksum` pipeline; `.runcastle/` in runcastle's own repo is TypeScript for exactly this reason.
12
+ - **`darwin` / `linux`** — bash is fine, but a `node`/`bun` script is still the better default in a JS project: the runtime is already installed and it is the only version of the script that survives the project gaining a Windows contributor.
13
+
14
+ The one thing that must NOT change when you translate: the contract (identity in via `RUNCASTLE_*`, computed values out via `.runcastle/drive.env`, unconditional idempotence, exit 0 means up).
15
+
16
+ ## Postgres, one database per drive
17
+
18
+ Name the database from `RUNCASTLE_ID`, create it if it is not there, migrate, and hand the URL back:
19
+
20
+ ```sh
21
+ DB="myapp_$RUNCASTLE_ID"
22
+ createdb "$DB" 2>/dev/null || true # idempotent: already-there is success
23
+ DATABASE_URL="postgres://localhost/$DB" npm run migrate
24
+ echo "DATABASE_URL=postgres://localhost/$DB" >> .runcastle/drive.env
25
+ ```
26
+
27
+ Stop drops exactly that database, and nothing else: `dropdb --if-exists "myapp_$RUNCASTLE_ID"`.
28
+
29
+ ## docker compose
30
+
31
+ Isolate the whole stack per drive with a project name derived from the identity, map host ports from variables the script chose, and let compose do the waiting:
32
+
33
+ ```sh
34
+ export COMPOSE_PROJECT_NAME="myapp_$RUNCASTLE_ID"
35
+ export PG_PORT=$(pick_port "$RUNCASTLE_SLUG-pg") # you write pick_port — see below
36
+ docker compose up --wait -d
37
+ echo "COMPOSE_PROJECT_NAME=$COMPOSE_PROJECT_NAME" >> .runcastle/drive.env
38
+ echo "DATABASE_URL=postgres://localhost:$PG_PORT/app" >> .runcastle/drive.env
39
+ ```
40
+
41
+ The compose file maps `"${PG_PORT}:5432"`. Stop is `docker compose down -v`, which needs the same `COMPOSE_PROJECT_NAME` — which is why it goes into `drive.env` too.
42
+
43
+ ## Redis
44
+
45
+ Do not run a second server. Take a logical database index or a key prefix derived in-script from `RUNCASTLE_ID` — a small hash into 1..15 for the index — and leave db 0 to the human, whose own work is already in it. Write `REDIS_URL=redis://localhost:6379/7` or `REDIS_PREFIX=$RUNCASTLE_ID:` out, and have stop flush only that index or prefix.
46
+
47
+ ## Hosted databases
48
+
49
+ Where the vendor has branches (Neon and its kin), setup creates one named for `RUNCASTLE_ID` with the vendor CLI and writes the connection string it prints to `drive.env`; stop deletes that branch.
50
+
51
+ Where the role has no CREATEDB grant, take a schema per drive instead — `CREATE SCHEMA IF NOT EXISTS "$RUNCASTLE_ID"` plus a URL with the search path pinned to it, and `DROP SCHEMA ... CASCADE` at stop.
52
+
53
+ Ask the human which they have: it is their bill and their production neighbour.
54
+
55
+ ## Deterministic ports
56
+
57
+ Every lap of a feature should keep the same URL, and no drive should collide with the human's own running stack. Hash the SLUG — not the branch, so laps agree — into a high range, then probe upward for a free one.
58
+
59
+ The two helpers here are the part you must actually write:
60
+
61
+ - **`pick_port(seed)`** — hash `seed` into a high range and return the first free port at or above it.
62
+ - **`port_in_use(port)`** — true when something is already bound. There is no portable one-liner: bind a socket and catch the error (`net.createServer().listen()` in node), or shell out to `ss`/`lsof` on linux/macOS and `Get-NetTCPConnection` on Windows.
63
+
64
+ The sketch, with `cksum` standing in for "any stable hash you have to hand" (it is not on a stock Windows box):
65
+
66
+ ```sh
67
+ base=$(( 20000 + $(printf %s "$RUNCASTLE_SLUG" | cksum | cut -d" " -f1) % 10000 ))
68
+ port=$base; while port_in_use "$port"; do port=$((port + 1)); done
69
+ echo "PORT=$port" >> .runcastle/drive.env
70
+ ```
71
+
72
+ The dev pane inherits `PORT` from the overlay, so the URL it prints — the "Open app" link — is the port the script picked.