@iceinvein/agent-skills 0.23.5 → 0.23.8
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/package.json +1 -1
- package/skills/index.json +2 -2
- package/skills/sluice/SKILL.md +8 -0
- package/skills/sluice/evals/README.md +3 -2
- package/skills/sluice/evals/deep-plan-traces-the-example/case.yaml +4 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/fixture.sh +151 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/announces-deep-channel.md +12 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/design-written-to-docs.md +5 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/no-source-edit.md +11 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/no-source-write.md +11 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/spec-has-a-trace.md +7 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/stopped-for-signoff.md +8 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/graders/trace-finds-no-accepted-slide.md +27 -0
- package/skills/sluice/evals/deep-plan-traces-the-example/prompt.md +17 -0
- package/skills/sluice/evals/rules.json +12 -2
- package/skills/sluice/references/deep-channel.md +38 -0
- package/skills/sluice/references/status.md +95 -4
- package/skills/sluice/scripts/board-index.sh +160 -0
- package/skills/sluice/scripts/board.sh +304 -0
- package/skills/sluice/scripts/status.sh +50 -3
- package/skills/sluice/scripts/stop-guard.sh +107 -44
- package/skills/sluice/skill.json +2 -2
package/package.json
CHANGED
package/skills/index.json
CHANGED
|
@@ -293,9 +293,9 @@
|
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
"name": "sluice",
|
|
296
|
-
"description": "Routes work by change shape into four channels (bypass, fast, main, deep) and applies only the rules each channel needs, so a one-line fix does not pay the cost of a multi-subsystem build. Carries nine rules as one-liners in the router and the full treatment in references read only on friction. Checks the finished plan with plan.sh validate rather than trusting it to memory, seeds the run state from it, keeps a deep run's task breakdown in .sluice/run.json so a statusline segment, one status command and a SessionStart hook can answer where the run is (the hook prints a live run at every session start, compaction included), and closes each run with a ledger read out of the session transcript: elapsed, tools, tokens, and what each dispatched agent cost where the transcript recorded it. Two Stop hooks hold a handback: one refuses a tree that moved since its last recorded check, the other refuses a changed existing test not proven to fail on the session's starting code (or recorded with a reason). Claude Code only; stands down where the superpowers pipeline governs the repo.",
|
|
296
|
+
"description": "Routes work by change shape into four channels (bypass, fast, main, deep) and applies only the rules each channel needs, so a one-line fix does not pay the cost of a multi-subsystem build. Carries nine rules as one-liners in the router and the full treatment in references read only on friction. Checks the finished plan with plan.sh validate rather than trusting it to memory, seeds the run state from it, keeps a deep run's task breakdown in .sluice/run.json so a statusline segment, one status command and a SessionStart hook can answer where the run is (the hook prints a live run at every session start, compaction included), lists every live deep run and routed fast or main session on the machine in one board.sh view (drawn once, watched, or as JSON), and closes each run with a ledger read out of the session transcript: elapsed, tools, tokens, and what each dispatched agent cost where the transcript recorded it. Two Stop hooks hold a handback: one refuses a tree that moved since its last recorded check, the other refuses a changed existing test not proven to fail on the session's starting code (or recorded with a reason). Claude Code only; stands down where the superpowers pipeline governs the repo.",
|
|
297
297
|
"type": "prompt",
|
|
298
|
-
"version": "0.
|
|
298
|
+
"version": "0.27.0"
|
|
299
299
|
},
|
|
300
300
|
{
|
|
301
301
|
"name": "temporal-coupling-detector",
|
package/skills/sluice/SKILL.md
CHANGED
|
@@ -132,6 +132,14 @@ the design's shape turns on a fork only your partner can settle, ask that one
|
|
|
132
132
|
question through `AskUserQuestion`, with your recommendation, before the design
|
|
133
133
|
is written. `references/deep-channel.md` has the test for what counts.
|
|
134
134
|
|
|
135
|
+
The design carries a `## Trace` section before it goes up for sign-off: the
|
|
136
|
+
request's own example, quoted, walked through the data as it stands, with what
|
|
137
|
+
each reused function requires and writes read from the code. Per-task reviews
|
|
138
|
+
never run that example and the hunt runs it only once every task is built, so
|
|
139
|
+
a design checked only against the shape of the code is wrong for a whole plan
|
|
140
|
+
before anything catches it.
|
|
141
|
+
`references/deep-channel.md` has what the section holds.
|
|
142
|
+
|
|
135
143
|
Take the design stop through the harness's plan mode where there is one. Its
|
|
136
144
|
gate is enforced rather than requested and it holds edits shut while it is open,
|
|
137
145
|
so nothing gets built against a design nobody signed. It carries the first stop
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sluice evals
|
|
2
2
|
|
|
3
|
-
Twenty-
|
|
3
|
+
Twenty-two cases for `claude plugin eval`. Fifteen pin the routing decision: which
|
|
4
4
|
channel the run names, and whether the behaviour that channel owes actually
|
|
5
5
|
happened. Seven pin what a deep run does after pre-flight, where the question is
|
|
6
6
|
no longer which channel but whether the run keeps going.
|
|
@@ -12,6 +12,7 @@ no longer which channel but whether the run keeps going.
|
|
|
12
12
|
| `main-new-interface` | Adds a port the repo does not have | Main channel; shape stated with a recommendation before building |
|
|
13
13
|
| `deep-plan-across-subsystems` | A plan asked for, three subsystems | Deep channel; design written to `docs/specs/`; stops before code |
|
|
14
14
|
| `deep-plan-asks-the-fork` | The same plan with nothing saying where the counters can live | One question with a recommendation before any design; no spec yet |
|
|
15
|
+
| `deep-plan-traces-the-example` | A ticket whose own example the obvious design fails: the lookup it says to reuse matches accepted slides, and the smoke deck has none | A `## Trace` section in the spec that walks the example through the smoke deck, a lookup designed around what it found, and no write under `src/` or `tests/` |
|
|
15
16
|
| `explicit-instruction-collapses-to-fast` | Main-shaped work plus "just do it" | Collapses to fast; no design, no proposal |
|
|
16
17
|
| `superpowers-conflict-stands-down` | Repo mandates the superpowers sequence | Stands down once, names no channel |
|
|
17
18
|
| `announcement-reaches-the-ledger` | A flag that a surface test turns into a new export | `status.sh route main` before any write under `src/` and before any fast or deep route |
|
|
@@ -36,7 +37,7 @@ Execution, where the run is already past both stops:
|
|
|
36
37
|
|
|
37
38
|
## Running
|
|
38
39
|
|
|
39
|
-
Twenty cases scaffold a small Node repo and then change it, so they need the
|
|
40
|
+
Twenty-one cases scaffold a small Node repo and then change it, so they need the
|
|
40
41
|
scaffold flag and a tool grant. Pin the model, because the eval child does not
|
|
41
42
|
inherit it, and pin the judge, because `--model` does not reach it and the
|
|
42
43
|
`llm` graders otherwise run on Haiku. From the repo root:
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A deck app where the ticket's own example fails the obvious design. The deck
|
|
3
|
+
# store looks slides up by the title of their accepted version, and only the
|
|
4
|
+
# canvas ever accepts one, so the smoke deck, built in chat, has no accepted
|
|
5
|
+
# slides and the lookup the ticket says to reuse finds nothing. Nothing in the
|
|
6
|
+
# code is wrong; the trap is in designing from which functions exist instead of
|
|
7
|
+
# walking the example through the data. The README fixes where the design goes, so the graders can read it,
|
|
8
|
+
# what an unaccepted slide is called, and what writes slide text, because
|
|
9
|
+
# without them each is a fork the run rightly asks before writing any design.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
mkdir -p src/chat src/deck src/canvas data/decks tests docs/specs
|
|
13
|
+
|
|
14
|
+
cat > package.json <<'JSON'
|
|
15
|
+
{
|
|
16
|
+
"name": "decks",
|
|
17
|
+
"version": "0.9.0",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test \"tests/*.test.js\""
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
JSON
|
|
24
|
+
|
|
25
|
+
cat > src/deck/store.js <<'JS'
|
|
26
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
27
|
+
|
|
28
|
+
const path = (id) => new URL(`../../data/decks/${id}.json`, import.meta.url);
|
|
29
|
+
|
|
30
|
+
export function loadDeck(id) {
|
|
31
|
+
return JSON.parse(readFileSync(path(id), "utf8"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function saveDeck(deck) {
|
|
35
|
+
writeFileSync(path(deck.id), JSON.stringify(deck, null, 2) + "\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A slide's title is the title of the version the user accepted on the canvas.
|
|
39
|
+
export function findSlideByTitle(deck, title) {
|
|
40
|
+
const wanted = title.trim().toLowerCase();
|
|
41
|
+
return deck.slides.find((slide) => {
|
|
42
|
+
const accepted = slide.versions[slide.accepted];
|
|
43
|
+
return accepted !== undefined && accepted.title.toLowerCase() === wanted;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
JS
|
|
47
|
+
|
|
48
|
+
cat > src/deck/redraft.js <<'JS'
|
|
49
|
+
// Appends a new version; the canvas decides whether the user accepts it.
|
|
50
|
+
export function redraftSlide(slide, draft) {
|
|
51
|
+
slide.versions.push({ title: draft.title, body: draft.body });
|
|
52
|
+
return slide.versions.length - 1;
|
|
53
|
+
}
|
|
54
|
+
JS
|
|
55
|
+
|
|
56
|
+
cat > src/deck/draft.js <<'JS'
|
|
57
|
+
// Slide text comes from the model; callers pass the completion so tests can stub it.
|
|
58
|
+
export async function draftSlide(version, instruction, complete) {
|
|
59
|
+
const text = await complete(
|
|
60
|
+
`Rewrite this slide. ${instruction}\nTitle: ${version.title}\nBody: ${version.body}`,
|
|
61
|
+
);
|
|
62
|
+
const [title, ...body] = text.trim().split("\n");
|
|
63
|
+
return { title: title.replace(/^Title:\s*/, ""), body: body.join("\n").replace(/^Body:\s*/, "") };
|
|
64
|
+
}
|
|
65
|
+
JS
|
|
66
|
+
|
|
67
|
+
cat > src/canvas/accept.js <<'JS'
|
|
68
|
+
export function accept(slide, versionIndex) {
|
|
69
|
+
if (slide.versions[versionIndex] === undefined) {
|
|
70
|
+
throw new Error(`no version ${versionIndex} on slide ${slide.id}`);
|
|
71
|
+
}
|
|
72
|
+
slide.accepted = versionIndex;
|
|
73
|
+
}
|
|
74
|
+
JS
|
|
75
|
+
|
|
76
|
+
cat > src/chat/build-deck.js <<'JS'
|
|
77
|
+
export function buildDeck(id, outline) {
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
slides: outline.map((item, index) => ({
|
|
81
|
+
id: `s${index + 1}`,
|
|
82
|
+
accepted: null,
|
|
83
|
+
versions: [{ title: item.title, body: item.body }],
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
JS
|
|
88
|
+
|
|
89
|
+
cat > src/chat/router.js <<'JS'
|
|
90
|
+
export function route(message) {
|
|
91
|
+
if (/^(prepare|build|make)\b.*\bslides?\b/i.test(message)) {
|
|
92
|
+
return { intent: "build-deck" };
|
|
93
|
+
}
|
|
94
|
+
return { intent: "chat" };
|
|
95
|
+
}
|
|
96
|
+
JS
|
|
97
|
+
|
|
98
|
+
cat > data/decks/smoke.json <<'JSON'
|
|
99
|
+
{
|
|
100
|
+
"id": "smoke",
|
|
101
|
+
"slides": [
|
|
102
|
+
{
|
|
103
|
+
"id": "s1",
|
|
104
|
+
"accepted": null,
|
|
105
|
+
"versions": [{ "title": "Why a pilot", "body": "Three sites asked for it." }]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "s2",
|
|
109
|
+
"accepted": null,
|
|
110
|
+
"versions": [{ "title": "Pilot Stages", "body": "Recruit, run for six weeks, review." }]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "s3",
|
|
114
|
+
"accepted": null,
|
|
115
|
+
"versions": [{ "title": "Next steps", "body": "Pick the sites by May." }]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
JSON
|
|
120
|
+
|
|
121
|
+
cat > tests/store.test.js <<'JS'
|
|
122
|
+
import assert from "node:assert/strict";
|
|
123
|
+
import { test } from "node:test";
|
|
124
|
+
import { findSlideByTitle } from "../src/deck/store.js";
|
|
125
|
+
|
|
126
|
+
test("a slide is found by the title of its accepted version", () => {
|
|
127
|
+
const deck = { slides: [{ id: "s1", accepted: 0, versions: [{ title: "Pilot Stages" }] }] };
|
|
128
|
+
assert.equal(findSlideByTitle(deck, "pilot stages").id, "s1");
|
|
129
|
+
});
|
|
130
|
+
JS
|
|
131
|
+
|
|
132
|
+
cat > README.md <<'MD'
|
|
133
|
+
# decks
|
|
134
|
+
|
|
135
|
+
Chat builds a deck (`src/chat/build-deck.js`), the canvas lets a user accept a
|
|
136
|
+
slide version (`src/canvas/accept.js`), and the deck store reads and writes
|
|
137
|
+
`data/decks/<id>.json`. Until a user accepts a version, a slide shows, and is
|
|
138
|
+
called by, its latest one. Slide text, for a new deck or a redraft, comes from
|
|
139
|
+
`draftSlide` in `src/deck/draft.js`. `data/decks/smoke.json` is the smoke deck QA runs
|
|
140
|
+
tickets against. `npm test` runs the suite.
|
|
141
|
+
|
|
142
|
+
## Designs
|
|
143
|
+
|
|
144
|
+
A ticket's design goes in `docs/specs/<ticket>.md`, for example
|
|
145
|
+
`docs/specs/DEK-12.md`, and its plan in `docs/plans/<ticket>.md`.
|
|
146
|
+
MD
|
|
147
|
+
touch docs/specs/.gitkeep
|
|
148
|
+
|
|
149
|
+
git init --quiet
|
|
150
|
+
git add -A
|
|
151
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "decks 0.9.0"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# The trace carries SKILL.md's own routing table, which names every channel, so
|
|
4
|
+
# `contains` passes whenever the skill loads. Anchor on the two ways the agent
|
|
5
|
+
# routes instead, the same ones run-stats.sh meters: a Bash call that invokes
|
|
6
|
+
# `status.sh route deep` (a quoted mention, as in a grep, does not count), or the
|
|
7
|
+
# words opening an assistant message or following a label such as "Sluice:".
|
|
8
|
+
pattern: '"command":"(?:(?:[^"\\]|\\.)*?(?:[\s/]|\\[nt]))?status\.sh(?:\\"|'')?(?:\s|\\[nt])+route(?:\s|\\[nt])+deep(?:\s|\\[nt]|[;&|]|")|"text":"(?:[^.!?\n"]{0,100}[:=]\s*)?[\s*_#>\\]*deep channel'
|
|
9
|
+
flags: i
|
|
10
|
+
target: trace
|
|
11
|
+
weight: 2
|
|
12
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: tool_used
|
|
3
|
+
# The run plans and stops for sign-off, so no Edit may land under src/ or
|
|
4
|
+
# tests/. Bash is left out of the case's tools, so these two are the only ways
|
|
5
|
+
# code could change.
|
|
6
|
+
tool: Edit
|
|
7
|
+
input_match: '"file_path"\s*:\s*"(?:[^"]*/)?(?:src|tests)/'
|
|
8
|
+
min: 0
|
|
9
|
+
max: 0
|
|
10
|
+
weight: 2
|
|
11
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: tool_used
|
|
3
|
+
# The run plans and stops for sign-off, so no Write may land under src/ or
|
|
4
|
+
# tests/. Bash is left out of the case's tools, so these two are the only ways
|
|
5
|
+
# code could change.
|
|
6
|
+
tool: Write
|
|
7
|
+
input_match: '"file_path"\s*:\s*"(?:[^"]*/)?(?:src|tests)/'
|
|
8
|
+
min: 0
|
|
9
|
+
max: 0
|
|
10
|
+
weight: 2
|
|
11
|
+
---
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The agent was asked to plan a ticket that spans the chat router, the deck store and the redraft path.
|
|
6
|
+
|
|
7
|
+
PASS if the final message hands a written design back for sign-off: it says where the design was written and asks for approval, or otherwise makes clear it is waiting before any code is written.
|
|
8
|
+
FAIL if the final message reports implementation work as done, moves straight into building, or presents the design as settled with no sign-off asked for.
|
package/skills/sluice/evals/deep-plan-traces-the-example/graders/trace-finds-no-accepted-slide.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
focus: { source: file, path: docs/specs/DEK-41.md }
|
|
4
|
+
weight: 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This is the design for a ticket whose example is "Redraft the Pilot Stages
|
|
8
|
+
slide to lead with the pilot outcome" on the smoke deck. In the repo,
|
|
9
|
+
`findSlideByTitle` matches only on the title of a slide's accepted version,
|
|
10
|
+
only the canvas ever sets `accepted`, and every slide in
|
|
11
|
+
`data/decks/smoke.json` has `"accepted": null`. So the lookup the ticket says
|
|
12
|
+
to reuse finds no slide for the ticket's own example.
|
|
13
|
+
|
|
14
|
+
Judge the section under the design's Trace heading for the walk, and the rest
|
|
15
|
+
of the design only for the lookup it settles on.
|
|
16
|
+
|
|
17
|
+
PASS if the Trace section quotes the ticket's example, walks it through the
|
|
18
|
+
smoke deck's actual data, and records there that its slides have no accepted
|
|
19
|
+
version (so the existing lookup returns nothing), and the design builds the
|
|
20
|
+
slide lookup around that, consistent with the
|
|
21
|
+
README (an unaccepted slide is called by its latest version): for example,
|
|
22
|
+
matching the accepted version's title when there is one and the latest
|
|
23
|
+
version's otherwise.
|
|
24
|
+
FAIL if there is no Trace section, if the accepted-slide finding appears only
|
|
25
|
+
outside it, if the design reuses `findSlideByTitle` or the accepted version as
|
|
26
|
+
it stands, or if the accepted state is mentioned only as a general risk
|
|
27
|
+
without saying what the smoke deck holds.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-plan-traces-the-example
|
|
3
|
+
description: A deep plan whose ticket example contradicts the obvious design. Pins a Trace section in the spec that walks the example through the fixture's real data before sign-off.
|
|
4
|
+
tags: [routing, deep, write]
|
|
5
|
+
max_turns: 25
|
|
6
|
+
timeout_seconds: 900
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write]
|
|
8
|
+
expected_outcome: Announces the deep channel, writes docs/specs/DEK-41.md with a Trace section that runs the ticket's sentence against the smoke deck, finds its slides have no accepted version, designs around that, and stops for sign-off without code.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Plan DEK-41 for us.
|
|
12
|
+
|
|
13
|
+
> Let people revise a single slide from chat. Example, on the smoke deck:
|
|
14
|
+
> "Redraft the Pilot Stages slide to lead with the pilot outcome" should redraft
|
|
15
|
+
> that slide and leave the rest alone. The chat router needs a revise intent;
|
|
16
|
+
> finding the slide and saving the redraft should reuse what the deck store and
|
|
17
|
+
> the redraft path already do.
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"origin": "docs/specs/2026-08-07-sluice-design.md",
|
|
32
32
|
"cases": [
|
|
33
33
|
"deep-plan-across-subsystems",
|
|
34
|
-
"deep-plan-asks-the-fork"
|
|
34
|
+
"deep-plan-asks-the-fork",
|
|
35
|
+
"deep-plan-traces-the-example"
|
|
35
36
|
]
|
|
36
37
|
},
|
|
37
38
|
{
|
|
@@ -63,7 +64,8 @@
|
|
|
63
64
|
"origin": "docs/specs/2026-08-07-sluice-design.md",
|
|
64
65
|
"cases": [
|
|
65
66
|
"deep-plan-across-subsystems",
|
|
66
|
-
"deep-plan-asks-the-fork"
|
|
67
|
+
"deep-plan-asks-the-fork",
|
|
68
|
+
"deep-plan-traces-the-example"
|
|
67
69
|
]
|
|
68
70
|
},
|
|
69
71
|
{
|
|
@@ -74,6 +76,14 @@
|
|
|
74
76
|
"deep-plan-asks-the-fork"
|
|
75
77
|
]
|
|
76
78
|
},
|
|
79
|
+
{
|
|
80
|
+
"id": "deep-design-traces-the-example",
|
|
81
|
+
"states": "SKILL.md: Deep channel",
|
|
82
|
+
"origin": "commit:7decf60",
|
|
83
|
+
"cases": [
|
|
84
|
+
"deep-plan-traces-the-example"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
77
87
|
{
|
|
78
88
|
"id": "deep-stops-for-plan-and-pre-flight",
|
|
79
89
|
"states": "SKILL.md: Deep channel",
|
|
@@ -183,6 +183,44 @@ on it, and let the next instruction start the plan. Nothing else about this
|
|
|
183
183
|
section changes, because the obligation was never the mode's, only the
|
|
184
184
|
enforcement was.
|
|
185
185
|
|
|
186
|
+
## The trace
|
|
187
|
+
|
|
188
|
+
A design written from the shape of the code (which functions exist, which slots
|
|
189
|
+
they fill) can be sound on paper and still fail the request's own example,
|
|
190
|
+
and nothing after it notices: per-task reviews read each task against its own
|
|
191
|
+
text, and the hunt runs once every task is done. So walk the request through
|
|
192
|
+
the system as it stands before you draft, and give the design a `## Trace`
|
|
193
|
+
section that records the walk (in the plan file under plan mode, carried into
|
|
194
|
+
the spec on approval):
|
|
195
|
+
|
|
196
|
+
1. **The example, verbatim.** Quote the sentence, command or scenario the
|
|
197
|
+
request gives. Where it gives none, write the acceptance criterion as one
|
|
198
|
+
concrete case and say that you did.
|
|
199
|
+
2. **Through the real data.** Follow that case step by step through the rows,
|
|
200
|
+
files or fixtures it would touch, as they exist now rather than as the
|
|
201
|
+
design assumes them: read the record, run the query, open the fixture, and
|
|
202
|
+
cite what each step read. "A deck built in chat has no accepted slides" is
|
|
203
|
+
a fact one read away, and a design keyed on accepted slides is wrong without
|
|
204
|
+
it.
|
|
205
|
+
3. **What each reused path requires and writes.** For every function the
|
|
206
|
+
design reuses, its preconditions and its side effects, read from the code
|
|
207
|
+
and not from its name. Run the example a second time where the request
|
|
208
|
+
implies a repeat (a redraft of a redraft, an edit after an edit), because
|
|
209
|
+
what the first pass wrote is what the second pass reads.
|
|
210
|
+
4. **What the walk ruled out.** Each place it contradicted the obvious design
|
|
211
|
+
(the reuse the request suggests, the first shape that came to mind), and
|
|
212
|
+
what the design does instead. An empty list is a result, not a skipped
|
|
213
|
+
step.
|
|
214
|
+
|
|
215
|
+
Walking first is also what puts a fork where **The design stop and plan mode**
|
|
216
|
+
wants it: one the walk turns up only your partner can settle is asked before
|
|
217
|
+
the design is written, and the trace goes into the design with the answer.
|
|
218
|
+
|
|
219
|
+
The trace is what the sign-off is signing: a design without the section is
|
|
220
|
+
incomplete, however good the rest reads. It is not a prototype and not a hunt;
|
|
221
|
+
anything that only shows at runtime (routing, timing, a renderer) stays with
|
|
222
|
+
the hunt after the flip.
|
|
223
|
+
|
|
186
224
|
## Pre-flight
|
|
187
225
|
|
|
188
226
|
Design signed off, plan written, nothing built yet. Before Task 1, stop once
|
|
@@ -29,14 +29,21 @@ bash <skill-dir>/scripts/status.sh close
|
|
|
29
29
|
bash <skill-dir>/scripts/status.sh route deep
|
|
30
30
|
bash <skill-dir>/scripts/status.sh verify -- bun test
|
|
31
31
|
bash <skill-dir>/scripts/status.sh verify --none "docs only, nothing executes them"
|
|
32
|
+
bash <skill-dir>/scripts/board.sh
|
|
33
|
+
bash <skill-dir>/scripts/board.sh --watch 5
|
|
34
|
+
bash <skill-dir>/scripts/board.sh --json
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
`route` and `verify` are the two exceptions: neither needs a run.
|
|
35
38
|
|
|
36
|
-
`route`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
`route` prints `sluice: <channel> channel` so the channel lands in the
|
|
40
|
+
transcript as a tool input, which is kept as written where the announcement's
|
|
41
|
+
prose may not be. `references/meter.md` says why that matters. It opens no run,
|
|
42
|
+
but with a session id (`--session`, defaulting to `$CLAUDE_CODE_SESSION_ID`) it
|
|
43
|
+
writes that session's entry on the board, because a route is the only sign a
|
|
44
|
+
`fast` or `main` session leaves. With no id it writes nothing: there is nobody
|
|
45
|
+
to name, and none is made up. Either way it exits 0 once the channel is
|
|
46
|
+
printed, since the announcement is the part that matters.
|
|
40
47
|
|
|
41
48
|
`verify -- <cmd>` runs the check from the tree's top level, passes a failing
|
|
42
49
|
check's exit code through, and on a pass writes the tree's content digest to
|
|
@@ -483,6 +490,90 @@ cannot redirect. That is the same argument the channel announcement makes, and
|
|
|
483
490
|
the statusline is where it holds for the hour after the announcement scrolled
|
|
484
491
|
away.
|
|
485
492
|
|
|
493
|
+
## Board
|
|
494
|
+
|
|
495
|
+
Everything above answers from inside one worktree set. `scripts/board.sh`
|
|
496
|
+
answers across the machine: every live `deep` run and every routed `fast` or
|
|
497
|
+
`main` session, drawn once, or redrawn in place every two seconds (or every
|
|
498
|
+
`<seconds>` given) with `--watch` until interrupted. `<seconds>` is a positive
|
|
499
|
+
whole number read in base ten, leading zeros allowed (`05` is five, `08`
|
|
500
|
+
eight); `0`, `00` or anything else exits 4. `--json` prints the combined state
|
|
501
|
+
for another reader instead, with the counts the header shows, and does not
|
|
502
|
+
combine with `--watch`.
|
|
503
|
+
|
|
504
|
+
It reads an index of pointers, never copies, at `$SLUICE_BOARD_DIR`, else
|
|
505
|
+
`$CLAUDE_CONFIG_DIR/sluice/board`, else `$HOME/.claude/sluice/board`; with none
|
|
506
|
+
of the three set nothing is written and the board is empty. `runs/` holds one
|
|
507
|
+
file per tree with a live run, naming the tree, and `sessions/<id>.json` one
|
|
508
|
+
per routed session. `run.json` stays the single authority on a run, so the
|
|
509
|
+
board can never show a stale copy of one. `SLUICE_BOARD_DIR` exists so the
|
|
510
|
+
tests can point every script at a temporary directory and never touch the real
|
|
511
|
+
board: the suite's preload sets it to a fresh one and makes every child a test
|
|
512
|
+
spawns inherit it, since Bun hands a child spawned with no `env` the
|
|
513
|
+
environment bun started with rather than the one the preload changed.
|
|
514
|
+
|
|
515
|
+
`scripts/board-index.sh` is the only writer. `init` adds the run's pointer,
|
|
516
|
+
`move` re-points it and `close` drops it. A task flip writes nothing to the
|
|
517
|
+
board, because a plan's flips are many and each write is a process; the cost
|
|
518
|
+
is that a run opened before the board existed stays off it until it is
|
|
519
|
+
closed and a new one opened. `route` writes the session's
|
|
520
|
+
entry as `working`, a later route in the same session overwriting it, so an
|
|
521
|
+
escalation from `fast` to `main` shows as `main`. The Stop hook marks a `fast`
|
|
522
|
+
or `main` entry `handed back` when it lets a stop through, and only then: a
|
|
523
|
+
refused stop is the session still working. It marks a `deep` entry the same
|
|
524
|
+
way when no run is live (the design stop before any run exists, the stop
|
|
525
|
+
after `close`, a session that routed deep and never opened a run), and leaves
|
|
526
|
+
it as it is while one is, since the board draws that run's bar for it. The
|
|
527
|
+
run is found the way the hook's run guard finds one, taking the first in this
|
|
528
|
+
order: a run in the stop's own tree, then the run found through the session's
|
|
529
|
+
pointer at the main tree, then the tree's `run.at` forward, never the main
|
|
530
|
+
tree's run read from a runless worktree. That first run counts as live only
|
|
531
|
+
when it is the session's own: its `run.json` names that session. A run that
|
|
532
|
+
names no session is no session's own, matching the board, which hides a
|
|
533
|
+
session row only behind a run naming it. When it names none or another session,
|
|
534
|
+
whether sitting in the stop's own tree or reached through the forward, or its
|
|
535
|
+
`run.json` cannot be read, the entry is marked `handed back`, and no later run
|
|
536
|
+
in the order is looked at. A failed
|
|
537
|
+
board write is one
|
|
538
|
+
`status.sh: note: board not updated: ...` line on stderr and never changes the
|
|
539
|
+
command's result, because the board is a view across trees and not part of
|
|
540
|
+
any run.
|
|
541
|
+
|
|
542
|
+
The header counts each channel's working and handed-back work. A drawn run
|
|
543
|
+
counts as working in its `run.json` channel: a `deep` run toward `N deep`
|
|
544
|
+
alongside the working `deep` sessions, a `main` run toward `N main working`, a
|
|
545
|
+
`fast` run toward `N fast working`. A run naming any other channel, or none,
|
|
546
|
+
counts toward `deep`, since it still draws as a run's bar. `--json` `counts`
|
|
547
|
+
holds the same numbers: `counts.working.<channel>` is that channel's working
|
|
548
|
+
sessions plus its drawn runs (so `working.deep` includes the `deep` runs, where
|
|
549
|
+
before it held the working `deep` sessions only and the header added the runs
|
|
550
|
+
itself), and `counts.runs` is every run drawn, whatever its channel.
|
|
551
|
+
|
|
552
|
+
A run block is `status.sh line --full` for its tree, unchanged, so it reads
|
|
553
|
+
exactly as the statusline does. Blocked runs sort first, then those with a task
|
|
554
|
+
active or in review, then the rest. A session row shows its channel, the tree's
|
|
555
|
+
name and its state: `working` with the time since it was routed, `handed back`
|
|
556
|
+
with the time since. The channel, name and state columns are padded to the
|
|
557
|
+
widest value among that frame's session rows, two spaces apart, so the ages
|
|
558
|
+
line up. Width is counted in characters of the drawn text (jq's count, so
|
|
559
|
+
the same under a C locale), never colour codes. A wide glyph such as `日` that
|
|
560
|
+
takes two terminal cells counts as one, so a row holding one draws a cell
|
|
561
|
+
short; a name stored decomposed (a letter plus a combining accent, as some
|
|
562
|
+
filesystems write paths) counts one column per code point, so it can draw a
|
|
563
|
+
cell short too. A `deep` session row reads `handed back` once a turn ends with
|
|
564
|
+
no run live for it, and otherwise stays `working` while its run's bar is
|
|
565
|
+
drawn. A session of any channel whose id is a drawn run's `session` is not
|
|
566
|
+
listed or counted again, so one piece of work is one row and one count. Session
|
|
567
|
+
entries past a day since their last update are hidden, the same one-day line
|
|
568
|
+
`show` uses for an idle run, and the next `init`, or `route` with a session
|
|
569
|
+
id, removes them along with pointers to trees that no longer hold a run. An
|
|
570
|
+
entry that cannot be read is skipped on its own, so one bad file costs one row
|
|
571
|
+
rather than the board.
|
|
572
|
+
|
|
573
|
+
The board writes nothing, so it takes no lock. It needs `jq` and exits 5
|
|
574
|
+
without it, and 6 when the board cannot be read at all, because unlike the
|
|
575
|
+
statusline it has somewhere to put an error.
|
|
576
|
+
|
|
486
577
|
## Friction
|
|
487
578
|
|
|
488
579
|
"I know which task I'm on." You do, until this session is summarised. The file
|