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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: revisit
|
|
3
3
|
description: Fold late-arriving information into a feature whose sessions are finished — amend the docs, then reconcile tickets (update/cancel/emit); an ordinary revisit never advances phases. Also the lap session: on a Rethink from review, digest the test drive, amend decisions + spec, emit the lap's tickets and complete ideation → spec → tickets in the one session. Entry skill for kind=revisit sessions.
|
|
4
|
-
disable-model-invocation:
|
|
4
|
+
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Revisit
|
|
@@ -13,7 +13,7 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
|
|
|
13
13
|
## Order of operations
|
|
14
14
|
|
|
15
15
|
1. **Listen first.** The human opens with what changed. If this terminal resumed the previous conversation, use that context; do not re-grill what is already settled. Ask only the questions the NEW information raises.
|
|
16
|
-
2. **Context.** Call `mcp__runcastle__get_feature_context` —
|
|
16
|
+
2. **Context.** Call `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, the canonical docs inlined in `docs[]` (brief, map, decisions, spec), an index of everything else in `moreDocs[]` (test notes, `research/*.md`; fetch one with `mcp__runcastle__read_feature_doc({ relPath })`), and every ticket with its id and status. The injected system prompt carries the slug and paths; trust `get_feature_context` for the live state. When all you need is ticket ids to operate on, `mcp__runcastle__list_tickets({ status? })` is the cheap call — it returns the queue without the docs.
|
|
17
17
|
3. **Docs.** Capture the change as decision prose:
|
|
18
18
|
- Append to `docs/features/<slug>/decisions.md` under a dated `## Revisited <date>` heading — never rewrite old decisions, supersede them ("Supersedes: <old decision>").
|
|
19
19
|
- If `spec.md` exists and the change touches it, amend the affected sections in place.
|
|
@@ -29,8 +29,8 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
|
|
|
29
29
|
|
|
30
30
|
Your kickoff line reads `LAP <n> REVIEW ITERATION`. The human burned the last lap, test-drove the branch, and clicked **Rethink**: the code was right, the *spec* wasn't. `feature.rethink` has already incremented the lap and looped the feature back, so it sits at **ideation**, not review. One trip round the pipeline is a lap, and this session is the whole front half of lap `<n>` — you carry it from what-the-drive-taught to ticket cards waiting on the human's **Burn** click, in this one conversation:
|
|
31
31
|
|
|
32
|
-
1. **Digest what the drive taught.** Your inputs are the previous lap's section of `docs/features/<slug>/test-notes.md` and the
|
|
33
|
-
2. **Context.** `mcp__runcastle__get_feature_context` — the feature (including its `lap`), the docs, and the full ticket history across every lap. Skim what the last lap actually landed and what failed before you interview.
|
|
32
|
+
1. **Digest what the drive taught.** Your two inputs are the previous lap's section of `docs/features/<slug>/test-notes.md` — it is in `moreDocs[]`, so read it with `mcp__runcastle__read_feature_doc({ relPath: "test-notes.md" })` (or off disk) — and the `## Later laps` section of `spec.md`, which is inlined in `docs[]`. **Both may be absent** — that is not an error and not something to go hunting for: interview the human from scratch instead. What did they hit, what surprised them, what do they want instead now they've used it?
|
|
33
|
+
2. **Context.** `mcp__runcastle__get_feature_context` — the feature (including its `lap`), the docs, and the full ticket history across every lap; each ticket's own `lap` is what separates them. Skim what the last lap actually landed and what failed before you interview.
|
|
34
34
|
3. **Never re-emit a promoted note.** Notes the human already promoted from the review checklist are tickets in *this* lap and arrive as ids in your context. However well such a note reads as a ticket, if its id is in front of you the work is already carded — emitting it again gives the burner the same job twice.
|
|
35
35
|
4. **Grill, briefly.** A small rethink is a short grilling: a few questions, one at a time, each with your recommended answer attached. Prune and promote `## Later laps` entries with the human as part of that conversation — what the drive taught is usually what decides which deferred scope this lap picks up and which stays parked.
|
|
36
36
|
5. **Amend the docs.** Append this lap's learning to `docs/features/<slug>/decisions.md` under a `## Lap <n>` heading (the convention) — never rewrite old decisions, supersede them. Then amend `spec.md` in place: the sections this lap changes, plus the pruned `## Later laps`.
|
|
@@ -46,12 +46,12 @@ If the rethink turns out to be genuinely big — whole branches of design reopen
|
|
|
46
46
|
|
|
47
47
|
- **Never call `complete_phase` in an ordinary revisit.** It has no gates to cross; the human drives the pipeline from the UI. **Lap mode is the one exception** — advancing ideation → spec → tickets is its job (move 7), and nothing else here licenses it.
|
|
48
48
|
- **Never resolve or reopen waypoints.** If a resolved waypoint's answer is now wrong, record the superseding decision in `decisions.md` — the map's history stays intact.
|
|
49
|
-
- **
|
|
49
|
+
- **Code changes ride tickets** — the burner (or a human) implements them, never this session. (The no-code rule itself is in your injected prompt and enforced by the edit guard.)
|
|
50
50
|
|
|
51
51
|
## Scope check
|
|
52
52
|
|
|
53
|
-
If what the human brings is not an amendment but a whole new capability, say so:
|
|
53
|
+
If what the human brings is not an amendment but a whole new capability, say so — and park it rather than losing it:
|
|
54
54
|
|
|
55
|
-
> That reads like a new feature, not a revision of this one.
|
|
55
|
+
> That reads like a new feature, not a revision of this one. I'll park it as a draft so it isn't lost; you can start it from the runcastle UI when you want it, and it gets grilled there.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
`mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` — a parked draft, no branch cut and nothing written, with the `brief` carrying why you deferred it and what it must not swallow. `draft: true` is the only door open to you; a full create belongs to the project session.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec
|
|
3
|
-
description: Synthesize the ideation conversation and decisions.md into docs/features/<slug>/spec.md, then complete the spec phase. No interview — pure synthesis. Invoked by /runcastle:ideate.
|
|
3
|
+
description: Synthesize the ideation conversation and decisions.md into docs/features/<slug>/spec.md, then complete the spec phase. No interview — pure synthesis. Invoked by /runcastle:ideate or /runcastle:converge.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
<!-- Forked from Matt Pocock's to-spec skill, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
|
|
@@ -42,6 +42,6 @@ Turn the ideation conversation plus `docs/features/<slug>/decisions.md` into a s
|
|
|
42
42
|
|
|
43
43
|
Do **not** hardcode specific file paths or code snippets — they go stale. Exception: if the ideation produced a snippet that pins a decision more precisely than prose can (a state machine, reducer, schema, type shape), inline the decision-rich parts and note it came from that discussion.
|
|
44
44
|
|
|
45
|
-
4. **Complete the phase.** `
|
|
45
|
+
4. **Complete the phase.** `mcp__runcastle__complete_phase({ phase: "spec" })`. If the gate returns `ok: false`, it names what is missing — fix and retry. It logs its own timeline event and checkpoints `spec.md` into git; do not add an event of your own.
|
|
46
46
|
|
|
47
|
-
Return control to `/runcastle:ideate
|
|
47
|
+
Return control to **the session skill that invoked you** — `/runcastle:ideate` for a linear feature, `/runcastle:converge` for a mapped one. It invokes `/runcastle:tickets` next. Do **not** invoke a session skill yourself to hand back: you are already inside one, and loading another session's entry skill would drop a whole procedure this session is not running (a converge session in particular is forbidden to grill) into the window.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tickets
|
|
3
|
-
description: Break the spec into vertical slices sized to earn a fresh sandboxed agent session and land inside it, and emit them via MCP. Then complete the tickets phase. Invoked by /runcastle:ideate.
|
|
3
|
+
description: Break the spec into vertical slices sized to earn a fresh sandboxed agent session and land inside it, and emit them via MCP. Then complete the tickets phase. Invoked by /runcastle:ideate or /runcastle:converge.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
<!-- Forked from Matt Pocock's to-tickets skill, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
|
|
@@ -63,6 +63,17 @@ Each ticket:
|
|
|
63
63
|
- **seams** — `string[]`, the public interfaces to test at (carry them from the spec's Seams section; prefer existing, prefer the highest, prefer one).
|
|
64
64
|
- **blockedBy** — `number[]`, **required**, the seq numbers (from your 1..N ordering) of the tickets that *genuinely* gate this one. Only true gates; pass `[]` when there are none — omitting it fails validation.
|
|
65
65
|
- **kind** — `"implementation"` (the default, omit it) or `"review"` for the one review ticket described above.
|
|
66
|
+
- **model** — optional, and usually omitted. See below.
|
|
67
|
+
|
|
68
|
+
### Assigning a model (only from the annotated roster)
|
|
69
|
+
|
|
70
|
+
`mcp__runcastle__get_feature_context` returns `annotatedModels`: `{ id, runtime, note }` for the models the human wrote a use-case note against, and only those. That list is the *entire* set of ids you may put in a ticket's `model`.
|
|
71
|
+
|
|
72
|
+
- **Empty list → never set `model`.** The human annotated nothing, so there is no stated reason to prefer one model over another. Omit the field on every ticket.
|
|
73
|
+
- **Non-empty → set it only where a note actually fits the ticket.** The note is the human's own vocabulary for what that model is good at ("UI/UX taste", "mechanical refactors"); assign it when this ticket's work is plainly that, and leave the field unset when nothing in the list speaks to it. Unset is the good default — it falls through to the model the project already burns on.
|
|
74
|
+
- **Never invent an id**, never copy one from these docs or from memory, and never write a model that is not in `annotatedModels` — the store rejects it and the whole batch fails to emit.
|
|
75
|
+
|
|
76
|
+
The human sees the assignment on the ticket card and can change or clear it before Burn, so a wrong-but-reasonable choice is cheap; a fabricated id is not.
|
|
66
77
|
|
|
67
78
|
## 3. Self-check, then emit
|
|
68
79
|
|
|
@@ -79,4 +90,4 @@ Then:
|
|
|
79
90
|
- `mcp__runcastle__emit_tickets({ tickets: [...] })` — **emit the array; do NOT write ticket files.** It returns `{ stored, ids }` and logs the timeline event itself; do not record one of your own.
|
|
80
91
|
- `mcp__runcastle__complete_phase({ phase: "tickets" })`. If the gate returns `ok: false`, fix what it names and retry.
|
|
81
92
|
|
|
82
|
-
Return to `/runcastle:ideate`
|
|
93
|
+
Return control to **the session skill that invoked you** — `/runcastle:ideate` for a linear feature, `/runcastle:converge` for a mapped one — and let it close out the session. Do **not** invoke a session skill yourself to hand back: you are already inside one, and loading another session's entry skill would drop a whole procedure this session is not running (a converge session in particular is forbidden to grill) into the window.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: waypoint
|
|
3
3
|
description: The runcastle waypoint session. Work ONE waypoint on a feature's map — grill, prototype, or run a task checklist for its assigned question — write the decision prose to the docs, then resolve the waypoint. Entry skill for kind=waypoint sessions.
|
|
4
|
-
disable-model-invocation:
|
|
4
|
+
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
<!-- runcastle mapped-ideation waypoint session (ADR-0001 §13.5) -->
|
|
7
7
|
|
|
@@ -11,20 +11,25 @@ This is a **mapped-ideation** feature: too big for one context window, so it was
|
|
|
11
11
|
|
|
12
12
|
## 0. Orient
|
|
13
13
|
|
|
14
|
-
1. Call `mcp__runcastle__get_feature_context`.
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
1. Call `mcp__runcastle__get_feature_context`. The injected system prompt carries the slug, the paths and your question; trust `get_feature_context` for the live state. For a mapped feature it returns, besides the usual feature/phase/docs:
|
|
15
|
+
- `waypoints` — every waypoint on the map, with its `title`, `type`, `question`, status and (once terminal) `summary`.
|
|
16
|
+
- `frontierIds` — the ids currently open, unclaimed and unblocked.
|
|
17
|
+
- `assignedWaypointId` — **your** waypoint's id, the one this session claimed. Find that id in `waypoints` and read its `title`, `type` and `question`. That is your whole assignment.
|
|
18
|
+
2. `docs[]` inlines the canonical docs in full — `brief.md`, `map.md`, `decisions.md`, `spec.md` if it exists. Read `map.md` (Destination, Notes, Not-yet-specified, Out-of-scope) so your answer fits the north star, and `decisions.md` for what is already locked — **do not re-litigate settled decisions.**
|
|
19
|
+
3. **Read the research already done for this feature.** Earlier `research` waypoints wrote deliverables to `docs/features/<slug>/research/<seq>-<slug>.md`. They are *not* inlined — they appear in the `moreDocs` index with their `relPath` and `bytes`, and each resolved research waypoint's `summary` in `waypoints` names its file (`researched: <title> — see <path>`). Fetch the ones your question depends on with `mcp__runcastle__read_feature_doc({ relPath })`, or read them off disk in this worktree. A waypoint that re-derives a finding an earlier waypoint already paid an AFK run for is a wasted session.
|
|
17
20
|
|
|
18
21
|
## 1. Work it — mode by type
|
|
19
22
|
|
|
20
|
-
Your `
|
|
23
|
+
Your waypoint's `type` picks the mode:
|
|
21
24
|
|
|
22
|
-
- **`grilling`** → grill the human on the question exactly as `/runcastle:ideate` does: one question at a time, always recommend an answer, look up facts in the code, model the domain, don't move on until you share understanding. Scope the grill to THIS waypoint's question — resist wandering into the whole feature.
|
|
23
|
-
- **`prototype`** → build the smallest throwaway spike that answers the question (a fork to compare approaches, a spike to prove feasibility). The
|
|
25
|
+
- **`grilling`** → grill the human on the question exactly as `/runcastle:ideate` does (`/runcastle:ideate` §1 is the procedure — one question at a time, always recommend an answer, look up facts in the code, model the domain, don't move on until you share understanding). Scope the grill to THIS waypoint's question — resist wandering into the whole feature.
|
|
26
|
+
- **`prototype`** → build the smallest throwaway spike that answers the question (a fork to compare two approaches, a spike to prove feasibility). **Write it under `docs/features/<slug>/prototypes/` and nowhere else.** That directory is the only place a talk session may put code: everything outside `docs/features/<slug>/` is denied by the edit guard, and the deny is real, not advisory. The spike is *evidence for the decision prose*, not shipped code — nothing under `prototypes/` is ever built on, and the working version rides a ticket like any other code. So do not try to write into `src/`, do not conclude the prototype is impossible and emit a ticket instead, and do not ask the human to run it for you: spike it in `prototypes/`, read what it proves, write the decision (§2), and let the spike go.
|
|
24
27
|
- **`task`** → run the concrete checklist the question describes (gather the facts, enumerate the cases, fill the table). Produce the artifact the waypoint asked for.
|
|
25
28
|
|
|
26
29
|
If, while working, you discover the question really splits into new sub-questions or uncovers whole new branches, chart them: `mcp__runcastle__emit_waypoints({ waypoints: [...] })` — each with `title`, `type`, `question`, `blockedBy` (1-based positions in this batch and/or existing waypoint ids), and `originWaypointId: "<your waypoint id>"` so the lineage ("surfaced by …") is recorded. Any mapped session may branch the map; that is the recursion. Emitting a waypoint does **not** relieve you of answering your own.
|
|
27
30
|
|
|
31
|
+
If something surfaces that is plainly its own **feature** rather than another waypoint, park it instead of swallowing 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. Drafts are the only feature you may create here; a full create belongs to the project session.
|
|
32
|
+
|
|
28
33
|
## 2. Write the decision prose — directly, to the docs
|
|
29
34
|
|
|
30
35
|
The answer is **prose in the docs**, not a tool argument. Serial HITL makes these writes race-free, so edit the files directly in this talk worktree:
|
|
@@ -39,13 +44,13 @@ The answer is **prose in the docs**, not a tool argument. Serial HITL makes thes
|
|
|
39
44
|
|
|
40
45
|
- **Dropping** (no longer worth answering): record a one-line gist under `## Out of scope` in `docs/features/<slug>/map.md`, so the map remembers why it was cut.
|
|
41
46
|
|
|
42
|
-
Write the prose **before** you resolve — the resolve tool flips machinery only and does not carry your answer.
|
|
47
|
+
Write the prose **before** you resolve — the resolve tool flips machinery only and does not carry your answer. If a prototype produced the answer, the decision entry must stand on its own: the reader of `decisions.md` is a converge session that will never open your spike.
|
|
43
48
|
|
|
44
49
|
## 3. Resolve — end the session
|
|
45
50
|
|
|
46
51
|
`mcp__runcastle__resolve_waypoint({ id: "<your waypoint id>", disposition: "resolved" | "dropped", summary: "<one-line gist>" })`.
|
|
47
52
|
|
|
48
|
-
This marks your waypoint terminal and frees any waypoint that was blocked only on yours — they appear on the frontier for the next session to Work. The `summary` is the one line shown on the map card.
|
|
53
|
+
This marks your waypoint terminal and frees any waypoint that was blocked only on yours — they appear on the frontier for the next session to Work. The `summary` is the one line shown on the map card, and it is what a later session sees first; if your work left a file behind (a prototype, a table), name its path there.
|
|
49
54
|
|
|
50
55
|
Then tell the human plainly:
|
|
51
56
|
|