@iceinvein/agent-skills 0.8.5 → 0.10.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iceinvein/agent-skills",
3
- "version": "0.8.5",
3
+ "version": "0.10.0",
4
4
  "description": "Install agent skills into AI coding tools",
5
5
  "author": "iceinvein",
6
6
  "license": "MIT",
package/skills/index.json CHANGED
@@ -281,9 +281,9 @@
281
281
  },
282
282
  {
283
283
  "name": "sluice",
284
- "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 seven rules as one-liners in the router and the full treatment in references read only on friction, 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. Claude Code only; stands down where the superpowers pipeline governs the repo.",
284
+ "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 seven 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 and one status command can answer where the run is, 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. Claude Code only; stands down where the superpowers pipeline governs the repo.",
285
285
  "type": "prompt",
286
- "version": "0.8.0"
286
+ "version": "0.12.0"
287
287
  },
288
288
  {
289
289
  "name": "temporal-coupling-detector",
@@ -90,6 +90,16 @@ Design to `docs/specs/YYYY-MM-DD-<topic>.md`, plan to
90
90
  `docs/plans/YYYY-MM-DD-<topic>.md`, unless the repo has a convention or
91
91
  your partner states a preference. Get the design signed off before code.
92
92
 
93
+ Take the design stop through the harness's plan mode where there is one. Its
94
+ gate is enforced rather than requested and it holds edits shut while it is open,
95
+ so nothing gets built against a design nobody signed. It carries the first stop
96
+ only; pre-flight still wants answers, and an approval is not one.
97
+
98
+ The run's state goes in `.sluice/run.json`, written a command at a time by
99
+ `scripts/status.sh`. That is what makes the breakdown readable from outside the
100
+ session running it: a statusline segment while it runs, `status.sh show` after a
101
+ compaction, and neither costing your partner a turn to ask. `references/status.md`
102
+
93
103
  **Read `references/deep-channel.md` before you write the plan.** It carries the
94
104
  plan format, the pre-flight questions, the run record, the dispatch rules and
95
105
  the review tiers, and none of those are derivable from what is on this page.
@@ -141,10 +151,11 @@ this question the place to ask; genuine unavailability is the tool not being
141
151
  there at all, which `references/deep-channel.md` handles separately. Review that
142
152
  turns out to be missing is only actionable while the plan can still change.
143
153
 
144
- All three answers go into the run record before Task 1's first edit, each with
145
- the reason it went that way. Those rows are what discharge pre-flight, not the
146
- approval: one reply arrives for several obligations, so a "yes" with no rows
147
- behind it signed off the plan and nothing else.
154
+ All three answers go into `run.json` and the run record before Task 1's first
155
+ edit, the answer in the first and the reason in the second. Those rows are what
156
+ discharge pre-flight, not the approval: one reply arrives for several
157
+ obligations, so a "yes" with no rows behind it signed off the plan and nothing
158
+ else.
148
159
 
149
160
  Three from that file that catch people out: concurrent implementers need a
150
161
  worktree each and the flip runs alone, review is tiered rather than automatic,
@@ -46,6 +46,16 @@
46
46
  the instruction out again in full. "TBD" is another. So is naming a type
47
47
  or a function that no task in the plan ever creates.
48
48
 
49
+ **Run `scripts/plan.sh validate <plan>` on the finished plan, before pre-flight.**
50
+ Most of what this section asks for needs no judgement to check: a `Needs` no
51
+ task `Offers`, a "TBD", a step deferring to a neighbour, a missing `Contract`,
52
+ a plan with no flip or two of them, a `Model` mark on a task the tier table
53
+ sends to tier 3. Those come back as errors with the task number on them. The
54
+ warnings are the judgement calls left to you: a step with no proof, a `Needs`
55
+ satisfied only by a later task, two tasks whose `Touches` overlap. Reading the
56
+ plan yourself catches these on a good day, and the point of a check is the
57
+ other kind of day.
58
+
49
59
  ```
50
60
  # Plan: <topic>
51
61
  ## Ground Rules
@@ -72,14 +82,33 @@ Split it. A plan with none is not a `deep` plan: nothing in it does anything.
72
82
  ## The run record
73
83
 
74
84
  A `deep` run outlives its own context, so what it learns has to sit on disk
75
- rather than in the session. Open one file for the run before pre-flight and
76
- write it as you go. It holds what a stranger resuming tomorrow would need and
77
- you would otherwise be recalling: the base each task was dispatched from, each
78
- task with its status and its commits, the three answers pre-flight settled,
79
- review, model and workspace, with the reason each one went that way, and any finding
80
- belonging to a task other than the one that surfaced it. Those pre-flight rows
81
- come first and open the file, because they are also what says the stop happened
82
- at all.
85
+ rather than in the session. That lands in two files, and which one a thing goes
86
+ in follows from who has to read it.
87
+
88
+ `.sluice/run.json` holds the state that moves: each task with its status, base
89
+ and commit, its tier, its `Model` mark and its `Flips` line, and the answers
90
+ pre-flight settled. `scripts/status.sh` writes and reads it, and
91
+ `references/status.md` carries the commands and the statusline segment that
92
+ makes a run visible without anyone asking. Open it with `init` when you open the
93
+ record, seed the rows with `scripts/plan.sh import <plan>` rather than typing a
94
+ command per task, then flip each task as it moves. It carries the ids, the names,
95
+ the flip, the model marks and the tiers, the last of these floored off `Touches`
96
+ and the contract graph rather than guessed. Import is safe to re-run: a status, a
97
+ review mark or a ratified model already recorded is left alone and a tier is only
98
+ ever raised, so resuming after a compaction cannot rewind the run.
99
+
100
+ The record is the other file, and it holds what a status cannot: the reason
101
+ review went the way it did, the reason a task was downshifted, the reason the
102
+ workspace answer went that way, any finding belonging to a task other than the
103
+ one that surfaced it, and whatever else a stranger resuming tomorrow would need
104
+ and could not derive. **It no longer carries task rows with statuses in them.**
105
+ Status written in both places drifts, and once it has there are two answers and
106
+ nothing to say which is stale.
107
+
108
+ Pre-flight lands in both, which is the one deliberate overlap: `run.json` holds
109
+ the answer, so the file can say whether the stop happened at all, and the record
110
+ holds the reason, so a reader can tell whether it should have gone that way.
111
+ Those rows come first and open the record for the same reason they always did.
83
112
 
84
113
  Where it goes follows the repo if the repo has a convention, and
85
114
  `docs/plans/YYYY-MM-DD-<topic>-record.md` if it does not. It belongs to you
@@ -88,11 +117,42 @@ task's `Touches` names it, and you commit it yourself alongside the plan.
88
117
  Assembling it at handback defeats it: a record written from memory is memory,
89
118
  which is the one thing the file exists to replace.
90
119
 
91
- A file only outlives compaction if you go back to it. Read it before the next
92
- dispatch whenever this session has been summarised, and treat what it says over
93
- what you remember, including where the two agree. Each task closes by writing
94
- its commit into the record, which means asking the implementer to report the
95
- SHA it committed and putting that in the row rather than deriving it later.
120
+ A file only outlives compaction if you go back to it. Run `status.sh show` and
121
+ read the record before the next dispatch whenever this session has been
122
+ summarised, and treat what they say over what you remember, including where the
123
+ two agree. Each task closes by writing its commit into `run.json`, which means
124
+ asking the implementer to report the SHA it committed and passing it to
125
+ `status.sh task <id> --status done --commit <sha>` rather than deriving it later.
126
+
127
+ ## The design stop and plan mode
128
+
129
+ The design stop is a stop because a plan written against the wrong design wastes
130
+ a plan's worth of work. Sluice enforced it with a sentence, "a stop ends your
131
+ turn", which is the weakest gate available in a harness that has a real one.
132
+
133
+ Claude Code's plan mode is the real one. `EnterPlanMode` needs your partner's
134
+ consent to enter, `ExitPlanMode` will not proceed without their approval, and
135
+ edits are held shut in between, so the design cannot be quietly built against
136
+ while it is still a draft. Take the design stop through it.
137
+
138
+ What that changes: the design gets drafted in the plan file the harness names,
139
+ and `ExitPlanMode` is the sign-off rather than a paragraph asking for one.
140
+
141
+ What it does not replace is pre-flight. That stop wants three answers, and an
142
+ approval is not an answer to any of them, so it stays where it is, after plan
143
+ mode has exited and the plan is written. One enforced gate does not collapse two
144
+ stops into one; it only makes the first of them hold.
145
+
146
+ **The harness's plan file is not the artifact.** It belongs to the mode and not
147
+ to the run. On approval, write the design to `docs/specs/YYYY-MM-DD-<topic>.md`,
148
+ the plan to `docs/plans/YYYY-MM-DD-<topic>.md`, and open the run record and
149
+ `run.json`. Those are the durable files, the ones a session resuming next week
150
+ reads, and none of them is the one you drafted in.
151
+
152
+ Where plan mode is unavailable, the prose stop is what you have and it is the
153
+ same stop: end the turn on the design and let the next instruction start the
154
+ plan. Nothing else about this section changes, because the obligation was never
155
+ the mode's, only the enforcement was.
96
156
 
97
157
  ## Pre-flight
98
158
 
@@ -155,9 +215,10 @@ is the declared schedule the dispatch rules reject.
155
215
  If one of the two has only one live answer, say which and ask the other. A stop
156
216
  down to a single question is still a stop.
157
217
 
158
- **Write the answers into the run record before Task 1's first edit.** Both of
159
- them, each with the reason it went that way. This is what discharges pre-flight,
160
- rather than the approval you got, and the distinction is the whole point: a stop
218
+ **Write the answers down before Task 1's first edit.** Both files:
219
+ `status.sh preflight` for the answers, the run record for the reason each one
220
+ went that way. That pair is what discharges pre-flight, rather than the
221
+ approval you got, and the distinction is the whole point: a stop
161
222
  that carries the plan and pre-flight together has one reply for two obligations,
162
223
  so a bare "yes" satisfies the plan and leaves no trace either way of the
163
224
  questions. Rows in a file leave that trace. If Task 1 is about to open and the
@@ -175,19 +236,32 @@ about it.
175
236
  Read the plan as a graph before you read it as a list. `Needs` and `Offers`
176
237
  are dependency edges, not only blindness insurance: a task is ready when every
177
238
  `Needs` it names is offered by a task already done, and any two ready tasks
178
- with disjoint `Touches` can go at the same time. Do that read once, before
179
- Task 1. A plan run in the order it happened to be written is a plan whose
239
+ with disjoint `Touches` can go at the same time.
240
+
241
+ **`scripts/status.sh ready` does that read.** It prints the ready set, names which
242
+ of them share a path and so cannot go together, says which tasks are still waiting
243
+ on a contract and what for, and holds the flip out of every wave. Run it before
244
+ each wave rather than deriving it again by hand, and note that it also checks the
245
+ candidates against whatever is already `active` or in `review`: those hold their
246
+ paths too, and a wave checked only against itself reads as safe while colliding
247
+ with work in flight. A plan run in the order it happened to be written is a plan whose
180
248
  graph nobody looked at, and inert-first ordering tends to put the independent
181
249
  tasks at the front, so the opportunity is usually real.
182
250
 
183
251
  Derive the sets at dispatch rather than writing wave numbers into the plan. A
184
252
  declared schedule is wrong the moment one task lands late or comes back with a
185
- blocking finding. A derived one just recomputes.
253
+ blocking finding. A derived one just recomputes, which is the whole reason `ready`
254
+ reads the run state rather than the plan: it sees what has actually landed.
186
255
 
187
- - One line per task in the run record, marked in progress then complete as it
256
+ - One row per task in `run.json`, flipped to `active` and then `done` as it
188
257
  moves. That state outlives compaction; your memory doesn't.
189
258
  - Each task goes to a fresh agent with that task's text and nothing else.
190
259
  What this session accumulated is yours to hold, not theirs.
260
+ - **Label the dispatch `T<n>: <task name>`.** The harness lists running agents
261
+ under whatever label the dispatch gave them, so labelled by task that list
262
+ reads as the plan and labelled anything else it reads as a row of anonymous
263
+ agents. It costs nothing and it is the only place a partner can see which
264
+ task is in flight without asking.
191
265
  - **Fan out wherever the graph allows.** Work that does not write is always
192
266
  safe and always parallel: investigations, searches and reviewers, one agent
193
267
  per question, all in one message so they run at once.
@@ -246,8 +320,8 @@ Three things change. The plan stops being a brief for strangers and becomes
246
320
  your own worklist, so its stop is no longer buying alignment with the agents
247
321
  who will carry it out, only your partner's read of work you will do yourself.
248
322
  Task isolation is gone, so
249
- the run record now carries all of the state that outlives compaction and
250
- matters more, not less. And fresh context is unavailable, which was the entire thing
323
+ `run.json` and the record now carry all of the state that outlives compaction
324
+ and matter more, not less. And fresh context is unavailable, which was the entire thing
251
325
  review was buying.
252
326
 
253
327
  One thing does not change: the work still owes a review. Reading your own diff
@@ -301,14 +375,22 @@ or four dispatches rather than nine. If most of your plan qualifies for a
301
375
  dispatch, the tasks are interleaved rather than ordered, and reordering them
302
376
  is cheaper than reviewing them.
303
377
 
378
+ **Mark each review with `status.sh task <id> --reviewed` when it comes back.**
379
+ What that buys is a count of what this table promised and nobody delivered: tasks
380
+ that are done, that qualified for a dispatch, and that carry no mark. `show` and
381
+ the statusline both carry it from the moment it exists, which is the whole point.
382
+ Unmarked, the count sits permanently non-zero and stops being a signal, and
383
+ "review outstanding" goes back to first appearing in the closing summary, at the
384
+ one moment your partner can no longer do anything about it.
385
+
304
386
  Reviews are reads, so they are always parallel. Every review a wave earned
305
387
  goes out in one message, and they run while the next wave's implementers work:
306
388
  a reviewer writes nothing, so it collides with nothing. The final review is
307
389
  the only one that waits, because it is the only one that needs everything to
308
390
  have landed.
309
391
 
310
- Record the base against that task in the run record when you dispatch, before
311
- the agent's first commit lands. Recovering it afterwards is archaeology, and the
392
+ Record the base against that task with `status.sh task <id> --base <sha>` when
393
+ you dispatch, before the agent's first commit lands. Recovering it afterwards is archaeology, and the
312
394
  answer you will guess at is `HEAD~1`, which `references/review.md` already
313
395
  names as the standing mistake.
314
396
 
@@ -0,0 +1,213 @@
1
+ # Run state
2
+
3
+ The plan says what the tasks are. The run record says why each decision went
4
+ the way it did. Neither answers "where is this right now" to anything but a
5
+ model reading prose, which is why a `deep` run is invisible from outside the
6
+ session that is running it: your partner has to ask, and asking costs a turn
7
+ and gets an answer from memory.
8
+
9
+ `.sluice/run.json` is that answer in a form something else can read. One file
10
+ per tree, holding only what changes as the run moves.
11
+
12
+ ```
13
+ bash <skill-dir>/scripts/status.sh init --topic <t> --channel deep \
14
+ --plan docs/plans/<date>-<topic>.md --record docs/plans/<date>-<topic>-record.md
15
+ bash <skill-dir>/scripts/status.sh task 3 --name "adapter seam" --tier 1 --model cheap
16
+ bash <skill-dir>/scripts/status.sh task 3 --status active --base 75014c9
17
+ bash <skill-dir>/scripts/status.sh task 3 --status done --commit 2c7f261
18
+ bash <skill-dir>/scripts/status.sh task 3 --reviewed
19
+ bash <skill-dir>/scripts/status.sh preflight --review "tier 3 only" --model "6 of 9 cheap" \
20
+ --workspace "one worktree per implementer"
21
+ bash <skill-dir>/scripts/status.sh show
22
+ bash <skill-dir>/scripts/status.sh ready
23
+ bash <skill-dir>/scripts/status.sh line --full
24
+ bash <skill-dir>/scripts/status.sh close
25
+ ```
26
+
27
+ `--dir <path>` reads another tree, which is what the statusline uses. Statuses
28
+ are `todo`, `active`, `review`, `done` and `blocked`. A new id needs `--name`;
29
+ after that every call is a bare flip, so keeping it current costs one command
30
+ per transition rather than a paragraph. `close` archives the run under
31
+ `.sluice/archive/` and frees the tree for the next one.
32
+
33
+ A command that cannot finish leaves the state exactly as it found it, so a
34
+ failed `task` never costs you the rows already in the file. Two argument rules
35
+ follow from that being worth guaranteeing: a value beginning with `--` is
36
+ rejected rather than accepted as a value, and a flag with no value at all is
37
+ rejected rather than taking the next flag as one.
38
+
39
+ Gitignore `.sluice/`. It is working state, and everything durable in it lands
40
+ somewhere else anyway: the commits are in git and the reasons are in the record,
41
+ which is the file that does get committed.
42
+
43
+ Open it with `init` when you open the run record, at the same point and for the
44
+ same reason, then seed the rows with `plan.sh import <plan>` rather than a
45
+ command per task. The ids, names, the flip, the `Model` marks and the tiers are
46
+ all fixed the moment the plan is written and are already in the file, so typing
47
+ them again is transcription with a chance of error in it.
48
+
49
+ The tier import writes is a floor read off `Touches`: an `(edit)` means existing
50
+ code changed, no `(test)` means nothing executable covers the task, and `Flips`
51
+ or a `Review` flag is tier 3 outright. Raise one by hand with `--tier` where the
52
+ work is more delicate than its paths suggest; nothing lowers it for you, because
53
+ the tier table takes the highest row a task matches.
54
+
55
+ Re-importing is safe and is the right move after the plan changes. It refreshes
56
+ names, the contract graph and the flip, moving the flip when the plan moved it,
57
+ and it raises a tier without ever lowering one. A status, a review mark or a model
58
+ ratified at pre-flight is left alone, so resuming after a compaction cannot rewind
59
+ the run. The one consequence worth knowing: adding a missing `(test)` to a plan
60
+ will not drop a task from tier 2 back to tier 1, because the tier table takes the
61
+ highest row a task matches and nothing here can tell a correction from a
62
+ regression. Lower it by hand with `--tier` if that is what you mean.
63
+
64
+ ## What goes where
65
+
66
+ **`run.json` owns status. The record owns why.** A status written into both
67
+ drifts, and the moment it does there are two answers and no way to tell which
68
+ is stale. So the record stops carrying task rows with statuses in them and
69
+ carries what a status cannot hold: the reason review went the way it did, the
70
+ reason a task was downshifted, a finding that belongs to a task other than the
71
+ one that surfaced it, and what a stranger resuming tomorrow would need and
72
+ could not derive.
73
+
74
+ Pre-flight answers land in both, and that is deliberate rather than an
75
+ exception: `run.json` holds the answer so the file can say whether the stop
76
+ happened, the record holds the reason so a reader can tell whether it should
77
+ have gone that way. Those are different claims.
78
+
79
+ ## Reading it back
80
+
81
+ `show` prints the whole run: channel, topic, how many tasks are done, the plan
82
+ and record paths, the pre-flight answers, and a row per task with its base,
83
+ commit, tier and model. Run it after compaction instead of reconstructing the
84
+ run from what you remember, and run it in the message that hands the work back,
85
+ where "four of nine, task five blocked" is a fact your partner can act on.
86
+
87
+ `show --json` is the same state for another reader. `line` is the compact
88
+ one-line form and `line --full` the wide one; both exit 0 in silence on a missing
89
+ run, unreadable state or a missing jq, because their caller is a status bar with
90
+ nowhere to put an error.
91
+
92
+ `line --full` renders three rows: the run and its clock, the bar alone, then the
93
+ detail. The bar gets a row to itself so it never competes with text for width,
94
+ which is what lets a cell be wide enough to read as a block rather than a tick.
95
+
96
+ A group of cells per task, one repeated glyph each: `▰` done, `◈` active, `▨` in
97
+ review, `▮` blocked, `▱` still to do. The glyphs are distinct before they are
98
+ coloured, so the rows survive having their colour stripped. The width is chosen
99
+ from what the whole bar would occupy, gaps included, rather than from the task
100
+ count: keyed off the count alone the schedule was not monotonic, and thirty tasks
101
+ at two cells each ran wider than twelve at three.
102
+
103
+ **A done task still owed a review trails the review glyph**, `▰▰▨` against
104
+ `▰▰▰`. Debt then reads in position rather than only as a count at the end of the
105
+ row, which is the difference between knowing how much there is and knowing where.
106
+ Tier 0 was never owed a dispatch, so it reads as plainly done. On a plan long
107
+ enough to narrow cells to one, there is no trailing cell to give up and the
108
+ positional reading stops: the count in the third row is then the only carrier,
109
+ which is why it is printed whether or not the bar could show the same thing.
110
+
111
+ **The flip draws as a rule, `┃`, before its task.** Everything left of it is inert
112
+ and safe to leave landed; everything right of it is not. That is what the flip
113
+ means, and it is a boundary between tasks rather than a property of one, so a
114
+ name in the header could not say it. `plan.sh validate` rejects a plan with two
115
+ flips and `import` clears a stale one, so the bar is only ever asked to draw the
116
+ single legal case.
117
+
118
+ The third row carries the progress count, whichever task wants attention, and the
119
+ review debt. A blocked task displaces the active one there, being the one of the
120
+ two worth interrupting for, and a `+n` follows when more than one task shares that
121
+ state, since a plan running four wide has four actives by design.
122
+
123
+ Mark a review with `task <id> --reviewed` when a reviewer comes back. What that
124
+ buys is the debt count: a task that is done, that the tier table owed a dispatch,
125
+ and that nobody marked. Tier 0 is excluded, having only ever been owed a stat
126
+ read. Without it "review outstanding" first appears in the closing summary, at
127
+ the one moment your partner can no longer do anything about it, and `show` and
128
+ the statusline both carry it from the moment it exists.
129
+
130
+ ## The next wave
131
+
132
+ `ready` answers the one question the other commands do not: not what the state is,
133
+ but what may go now. A task is ready when every symbol it `Needs` is offered by a
134
+ task already done, and two ready tasks are safe together when their `Touches` are
135
+ disjoint.
136
+
137
+ ```
138
+ 6 ready now · a worktree each
139
+ T1 extract the bundle writer src/cli/adapters/bundle.ts, …
140
+ T4 record what the harness offers skills/sluice/scripts/status.sh, …
141
+ T4 and T5 share skills/sluice/scripts/status.sh, so not together
142
+
143
+ 2 waiting on a contract
144
+ T2 cursor and gemini write bundles needs writeBundle
145
+
146
+ the flip runs alone
147
+ T8 sluice installs on four harnesses
148
+ ```
149
+
150
+ It reads the graph `plan.sh import` recorded, so a run seeded before that existed
151
+ says so and tells you to re-import rather than reporting everything ready. The
152
+ flip is held out of every wave whatever the graph says, because the invariant it
153
+ establishes is what the tasks after it are checked against.
154
+
155
+ Derive the wave here rather than writing wave numbers into the plan. A declared
156
+ schedule is wrong the moment one task lands late; this recomputes.
157
+
158
+ ## Statusline
159
+
160
+ This is the part that makes a run visible without anyone asking. Give it rows of
161
+ its own rather than a segment among the badges: it then costs nothing when no run
162
+ is live and contends with nothing for width when one is, which is what lets the
163
+ bar be wide and the task carry its name rather than only its number.
164
+
165
+ Capture it wherever the statusline command builds its other lines, keyed off the
166
+ state file existing so a session with no run spawns no process at all:
167
+
168
+ ```bash
169
+ sluice_line=""
170
+ if [ -n "$cwd" ] && [ -f "$cwd/.sluice/run.json" ]; then
171
+ for sluice_sh in "$cwd/.claude/skills/sluice/scripts/status.sh" \
172
+ "$HOME/.claude/skills/sluice/scripts/status.sh"; do
173
+ [ -f "$sluice_sh" ] || continue
174
+ sluice_line=$(bash "$sluice_sh" line --full --dir "$cwd" 2>/dev/null)
175
+ break
176
+ done
177
+ fi
178
+ ```
179
+
180
+ then print it last, after whatever else the command emits:
181
+
182
+ ```bash
183
+ if [ -n "$sluice_line" ]; then printf '%s\n' "$sluice_line"; fi
184
+ ```
185
+
186
+ `if` rather than `[ ... ] &&`: as the last command of a statusline script the
187
+ short form makes it exit 1 on every render with no run live, which is the common
188
+ case. `%s` rather than `%b`: the render already carries real escape bytes, and
189
+ `%b` would reinterpret a backslash inside a task name. `$cwd` is
190
+ `workspace.current_dir` from the JSON the harness sends on stdin. It renders as:
191
+
192
+ ```
193
+ ⧗ deep · sluice-cross-harness ◷ 38m
194
+ ▰▰▰ ▰▰▨ ▨▨▨ ◈◈◈ ▱▱▱ ▮▮▮ ▱▱▱ ┃ ▱▱▱ ▱▱▱
195
+ 2/9 done · !T6 model tiers rather than model names +1 · ⟲1 unreviewed
196
+ ```
197
+
198
+ The colour comes out of the script rather than being applied by the caller,
199
+ because the mapping from state to colour belongs next to the state. A caller that
200
+ coloured the line itself would have to re-derive each cell's meaning from its
201
+ glyph, which is the same fact stored twice.
202
+
203
+ A run that is only visible to the session running it is a run your partner
204
+ cannot redirect. That is the same argument the channel announcement makes, and
205
+ the statusline is where it holds for the hour after the announcement scrolled
206
+ away.
207
+
208
+ ## Friction
209
+
210
+ "I know which task I'm on." You do, until this session is summarised. The file
211
+ costs one command per transition and is the only thing in the run that survives
212
+ that, and a plan whose progress lives in one context window is a plan nobody
213
+ outside that window can read.