runcastle 1.1.1 → 1.2.1
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 +1158 -374
- package/drizzle/0016_red_nextwave.sql +1 -0
- package/drizzle/0017_certain_ezekiel_stane.sql +2 -0
- package/drizzle/0018_nice_lorna_dane.sql +10 -0
- package/drizzle/meta/0016_snapshot.json +766 -0
- package/drizzle/meta/0017_snapshot.json +780 -0
- package/drizzle/meta/0018_snapshot.json +846 -0
- package/drizzle/meta/_journal.json +21 -0
- package/index.js +1156 -372
- package/package.json +1 -1
- package/skills/packs/runcastle/skills/project/SKILL.md +10 -4
- package/skills/packs/runcastle/skills/tickets/SKILL.md +17 -14
- package/web/assets/{index-BeUzCnxq.js → index-Co7aViQi.js} +41 -40
- package/web/assets/index-o2brqt2b.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-BVNfUdrV.css +0 -1
package/package.json
CHANGED
|
@@ -24,11 +24,17 @@ Four, and deliberately none of the feature pipeline's. A session with no feature
|
|
|
24
24
|
|
|
25
25
|
Every **merged** feature's docs are already on disk in this worktree — `docs/features/<slug>/`. Read them with your ordinary `Read`/`Grep`; the index says where. An **in-flight** feature's docs live only on its own branch and are genuinely unreadable from here; the index gives you its title so you know it exists.
|
|
26
26
|
|
|
27
|
-
## 0.
|
|
27
|
+
## 0. Open by asking
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Your first visible move is a **question**, not a lookup. Greet them and put it:
|
|
30
|
+
|
|
31
|
+
> What are we cutting into features today?
|
|
32
|
+
|
|
33
|
+
Then orient **lazily**: reach for context when intake, routing, or a portfolio question actually needs it, never as an opening ritual. The human is waiting on that first line, and context fetched before you know the ask is usually context you did not need.
|
|
34
|
+
|
|
35
|
+
When you do need it, size the read to the question. `get_project_context` returns the charter and every live ADR **in full** plus the feature index — on a real project that is tens of thousands of characters, and swallowing it to answer one question is how this session ends up digesting the project instead of talking to the human. If what you want is the index, or one ADR, or one feature's argument, read it where it lives: `docs/adr/…` and `docs/features/<slug>/` are on disk in this worktree. Call `get_project_context` when you genuinely want the whole picture.
|
|
36
|
+
|
|
37
|
+
If it turns out there is **no charter**, note it and read on — see §5. Do not scaffold one.
|
|
32
38
|
|
|
33
39
|
## 1. Intake — grill the lump until it resolves into features
|
|
34
40
|
|
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tickets
|
|
3
|
-
description: Break the spec into
|
|
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.
|
|
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 -->
|
|
7
7
|
|
|
8
8
|
# Tickets
|
|
9
9
|
|
|
10
|
-
Break the work into **tickets** — vertical slices, each cutting end-to-end through every layer it needs,
|
|
10
|
+
Break the work into **tickets** — vertical slices, each cutting end-to-end through every layer it needs, each big enough to be worth a whole agent session and small enough to finish inside one — and emit them to the runcastle store via `mcp__runcastle__emit_tickets`. You are still in the unbroken ideation window: synthesize from the spec (`docs/features/<slug>/spec.md`). Do not compact or clear.
|
|
11
11
|
|
|
12
12
|
## The one thing that makes a runcastle ticket different
|
|
13
13
|
|
|
14
14
|
Each ticket is executed by a **single fresh agent, alone, in a sandbox, with ~100k tokens and NO way to ask a follow-up question.** It sees only what you put in the ticket plus the code already in the repo. So — *inverting* the usual "avoid file paths, they go stale" rule — the ticket's `context` field **must be concrete and self-sufficient**: name the files to touch, the existing patterns to copy, the type/schema shapes, the gotchas, the commands to run and verify with. A ticket that assumes the agent can ask "where does X live?" is a broken ticket. Over-specify context; under-specify nothing.
|
|
15
15
|
|
|
16
|
-
## The second thing: a ticket costs ~10 minutes before it does any work
|
|
16
|
+
## The second thing: a ticket costs ~10 minutes before it does any work — and pays it twice if it overruns
|
|
17
17
|
|
|
18
18
|
That fresh agent is fresh *every time*. Each ticket gets its own container: cold build, a full dependency install (measured 70–507s, ~2.5 min average), then an agent that re-reads the spec, the docs digest, and every file your `context` names — reading the repo was 16% of all agent time across 42 measured attempts. **This overhead is paid per ticket and is the same whether the ticket is twenty lines or a day's work.** Splitting one coherent piece of work into three tickets triples it, and adds two seams where a new agent has to reconstruct what the previous one had just finished learning.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
But that meter runs both ways. A ticket too big to finish in one pass gets no cheap second wind: an iteration is not a retry (ADR-0008) — the container is rebuilt, the install runs again, and a brand-new agent re-reads everything from the last commit, having lost every conclusion its predecessor reached. An overstuffed ticket pays the exact overhead it was merged to avoid, on top of the orientation it already paid once, and it pays it *after* the work is half-done rather than before.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
So size a ticket to **earn its container and still land in one pass**: substantial, with room left at the end for the tests, the self-review and the commits — not the smallest slice you can name, and not the largest you can imagine fitting. Fine-grained slicing still matters — it just belongs *inside* a ticket, as the `acceptanceCriteria` list. The burner works red→green one criterion at a time and commits each green slice, so one ticket with six criteria produces six small commits and orients once; six one-criterion tickets produce the same commits and orient six times.
|
|
23
|
+
|
|
24
|
+
Expect a **handful of substantial tickets** for a feature — roughly 4–8, each worth a session. A dozen thin ones means you sliced by layer or by file, not by behaviour. Two or three giant ones means you will pay back the containers you saved, with interest, when they overrun.
|
|
23
25
|
|
|
24
26
|
## 1. Draft vertical slices
|
|
25
27
|
|
|
26
28
|
<vertical-slice-rules>
|
|
27
29
|
- Each slice cuts a narrow but COMPLETE path through every layer it needs (schema, API, UI, tests) — vertical, NOT a horizontal slice of one layer.
|
|
28
30
|
- A completed slice is demoable or verifiable on its own.
|
|
29
|
-
- Each slice
|
|
30
|
-
- **Merge
|
|
31
|
-
- **Split only for a real reason:** (a) a genuine gate — the later work cannot be written until the earlier work's interface exists; (b) the combined work
|
|
31
|
+
- Each slice is worth one fresh ~100k-token agent session working alone and **lands inside it** — the orientation at the front and the tests, self-review and commits at the back are fixed tax, so leave them room instead of filling the window to the brim.
|
|
32
|
+
- **Merge when a merge test holds.** Two drafts are one ticket if they touch the same files, share a type or module, or the second one's first act would be re-reading what the first just wrote. Same for the obvious siblings of one behaviour: create/edit/delete of a resource, an endpoint and the UI that calls it, code and the tests that cover it. Two drafts that share none of that are not one ticket merely because both would fit.
|
|
33
|
+
- **Split only for a real reason:** (a) a genuine gate — the later work cannot be written until the earlier work's interface exists; (b) the combined work would not land in one session; (c) a prefactor, which is its own slice done first ("make the change easy, then make the easy change"); (d) parallel width, under the rule below; (e) the wide-refactor sequence below.
|
|
32
34
|
- **Not reasons to split:** one ticket per file, layer, endpoint or component; keeping each ticket easy; keeping diffs small; separating tests from the code they test.
|
|
33
|
-
- Tickets with no blocking edge
|
|
35
|
+
- **Parallelism pays when the halves are file-disjoint.** Tickets with no blocking edge burn concurrently, each on its own temp branch, landing one at a time (ADR-0002) — so two substantial slices touching *different* files buy real wall-clock for one container. Two touching the *same* files race to land, and the loser fails on a merge conflict: that width costs a re-burn, which is worse than the container it bought. Overlapping files → merge.
|
|
34
36
|
</vertical-slice-rules>
|
|
35
37
|
|
|
36
38
|
Give each ticket its **blocking edges** — the tickets that must finish before it can start. No blockers → it can start immediately.
|
|
@@ -47,7 +49,7 @@ Each ticket:
|
|
|
47
49
|
- **title** — short, descriptive, in the domain vocabulary.
|
|
48
50
|
- **goal** — the end-to-end behaviour this ticket makes work, from the user's/caller's perspective. Not a layer list.
|
|
49
51
|
- **context** — everything the sandboxed agent needs and cannot ask for: file paths to touch, existing patterns/modules to follow, type/schema shapes, gotchas, how to run and verify. Be concrete (see above).
|
|
50
|
-
- **acceptanceCriteria** — `string[]`, each independently *verifiable* (a behaviour you can observe, a command that passes). These are what the burner works red-green against, one at a time, committing each green one — so this is where the fine granularity lives. A substantial ticket having
|
|
52
|
+
- **acceptanceCriteria** — `string[]`, each independently *verifiable* (a behaviour you can observe, a command that passes). These are what the burner works red-green against, one at a time, committing each green one — so this is where the fine granularity lives. A substantial ticket having four to eight criteria is right, not a smell; a list running well past that is a ticket that will not land in one pass.
|
|
51
53
|
- **seams** — `string[]`, the public interfaces to test at (carry them from the spec's Seams section; prefer existing, prefer the highest, prefer one).
|
|
52
54
|
- **blockedBy** — `number[]`, the seq numbers (from your 1..N ordering) of the tickets that *genuinely* gate this one. Only true gates.
|
|
53
55
|
|
|
@@ -55,10 +57,11 @@ Each ticket:
|
|
|
55
57
|
|
|
56
58
|
There is no in-session quiz — the human's review is the **Burn** gate in the runcastle UI, reading these cards. So make them right before you emit. Check yourself, in this order:
|
|
57
59
|
|
|
58
|
-
1. **Can any two of these be one ticket?** Go pair by pair. Same files, shared type, sibling behaviours, or one whose blocker is the only thing it waits on and together they'd still
|
|
59
|
-
2.
|
|
60
|
-
3.
|
|
61
|
-
4.
|
|
60
|
+
1. **Can any two of these be one ticket?** Go pair by pair. Same files, shared type, sibling behaviours, or one whose blocker is the only thing it waits on and together they'd still land in one session → merge them. Do this first, because merging changes the rest.
|
|
61
|
+
2. **Would any one of these overrun?** A criteria list longer than one agent can take red→green — with tests, self-review and commits still to come — costs a whole second container to finish. Split it where its files stop overlapping.
|
|
62
|
+
3. Is each survivor a demoable vertical slice rather than a horizontal layer, and is it worth a session rather than rattling around in one?
|
|
63
|
+
4. Are the blocking edges minimal and genuinely true gates? Where two are independent, do they touch different files — or will they collide on landing?
|
|
64
|
+
5. Is every `context` self-sufficient for an agent that cannot ask — covering every file and pattern the ticket touches?
|
|
62
65
|
|
|
63
66
|
Then:
|
|
64
67
|
- `mcp__runcastle__emit_tickets({ tickets: [...] })` — **emit the array; do NOT write ticket files.** It returns `{ stored, ids }`.
|