@iceinvein/agent-skills 0.15.0 → 0.16.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
package/skills/index.json
CHANGED
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
"name": "sluice",
|
|
284
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, 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. Claude Code only; stands down where the superpowers pipeline governs the repo.",
|
|
285
285
|
"type": "prompt",
|
|
286
|
-
"version": "0.
|
|
286
|
+
"version": "0.17.0"
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"name": "temporal-coupling-detector",
|
package/skills/sluice/SKILL.md
CHANGED
|
@@ -155,7 +155,9 @@ there at all, which `references/deep-channel.md` handles separately. Review that
|
|
|
155
155
|
turns out to be missing is only actionable while the plan can still change.
|
|
156
156
|
|
|
157
157
|
All three answers go into `run.json` and the run record before Task 1's first
|
|
158
|
-
edit, the answer in the first and the reason in the second
|
|
158
|
+
edit, the answer in the first and the reason in the second, and both files
|
|
159
|
+
open inside the worktree once it is cut, not in the main tree before it
|
|
160
|
+
exists. Those rows are what
|
|
159
161
|
discharge pre-flight, not the approval: one reply arrives for several
|
|
160
162
|
obligations, so a "yes" with no rows behind it signed off the plan and nothing
|
|
161
163
|
else.
|
|
@@ -167,9 +169,9 @@ something, not quietly ship without one, and the run ends: `status.sh final`
|
|
|
167
169
|
when the plan's own review clears, `status.sh close` once the work is no longer
|
|
168
170
|
yours to act on, after a local merge, when the branch is left as it stands, or
|
|
169
171
|
when an open PR lands, not when it opens. A run left open reads as live to the
|
|
170
|
-
statusline and blocks the next one. The SessionStart hook prints a live run at
|
|
171
|
-
compaction and resume included, so a run you did not
|
|
172
|
-
shown, not one you have to remember.
|
|
172
|
+
statusline and blocks the next one. The SessionStart hook prints a live run at
|
|
173
|
+
every session start, compaction and resume included, so a run you did not
|
|
174
|
+
start is one you were shown, not one you have to remember.
|
|
173
175
|
|
|
174
176
|
## Conflicts
|
|
175
177
|
|
|
@@ -89,9 +89,14 @@ in follows from who has to read it.
|
|
|
89
89
|
and commit, its tier, its `Model` mark and its `Flips` line, and the answers
|
|
90
90
|
pre-flight settled. `scripts/status.sh` writes and reads it, and
|
|
91
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
|
|
93
|
-
record,
|
|
94
|
-
|
|
92
|
+
makes a run visible without anyone asking. Open it with `init` when you open
|
|
93
|
+
the record, which is after pre-flight and inside the tree the work runs in, the
|
|
94
|
+
worktree when pre-flight bought one: a run opened in the main tree before the
|
|
95
|
+
worktree exists lives where every later session's `init` collides with it, and
|
|
96
|
+
that session cannot tell an abandoned run from yours. One already stranded
|
|
97
|
+
there moves with `status.sh move --to <worktree>`. Seed the rows with
|
|
98
|
+
`scripts/plan.sh import <plan>` rather than typing a command per task, then
|
|
99
|
+
flip each task as it moves. It carries the ids, the names,
|
|
95
100
|
the flip, the model marks and the tiers, the last of these floored off `Touches`
|
|
96
101
|
and the contract graph rather than guessed. Import is safe to re-run: a status, a
|
|
97
102
|
review mark or a ratified model already recorded is left alone and a tier is only
|
|
@@ -144,10 +149,11 @@ mode has exited and the plan is written. One enforced gate does not collapse two
|
|
|
144
149
|
stops into one; it only makes the first of them hold.
|
|
145
150
|
|
|
146
151
|
**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
|
|
149
|
-
`run.json
|
|
150
|
-
|
|
152
|
+
to the run. On approval, write the design to `docs/specs/YYYY-MM-DD-<topic>.md`
|
|
153
|
+
and the plan to `docs/plans/YYYY-MM-DD-<topic>.md`. The run record and
|
|
154
|
+
`run.json` open after pre-flight, in the tree the work runs in, for the reason
|
|
155
|
+
the run record section gives. Those are the durable files, the ones a session
|
|
156
|
+
resuming next week reads, and none of them is the one you drafted in.
|
|
151
157
|
|
|
152
158
|
Where plan mode is unavailable, the prose stop is what you have and it is the
|
|
153
159
|
same stop: end the turn on the design and let the next instruction start the
|
|
@@ -215,9 +221,27 @@ is the declared schedule the dispatch rules reject.
|
|
|
215
221
|
If one of the two has only one live answer, say which and ask the other. A stop
|
|
216
222
|
down to a single question is still a stop.
|
|
217
223
|
|
|
218
|
-
**Write the answers down before Task 1's first edit
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
**Write the answers down before Task 1's first edit, in the tree the work runs
|
|
225
|
+
in.** The order on the instruction that follows the stop: cut the worktree
|
|
226
|
+
first, when that was the answer, through the harness's worktree tool. A fresh
|
|
227
|
+
worktree branches from the remote's default branch, so nothing uncommitted or
|
|
228
|
+
unpushed in the main tree comes across, and `docs/` may not exist there yet.
|
|
229
|
+
Move the design and plan into it yourself, each into its own directory since
|
|
230
|
+
the two share a basename and one `mv` into one directory would leave the plan
|
|
231
|
+
where the design was:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
mkdir -p <worktree>/docs/specs <worktree>/docs/plans
|
|
235
|
+
mv docs/specs/YYYY-MM-DD-<topic>.md <worktree>/docs/specs/
|
|
236
|
+
mv docs/plans/YYYY-MM-DD-<topic>.md <worktree>/docs/plans/
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The main tree is then clean and the worktree holds the only copy. Then `init`,
|
|
240
|
+
`plan.sh import` and `status.sh preflight` from inside it; then the record
|
|
241
|
+
with the reason each answer went that way; then commit design, plan and record
|
|
242
|
+
there, on the branch the work is on rather than on master. Both files carry the
|
|
243
|
+
answers: `status.sh preflight` for what was decided, the run record for why.
|
|
244
|
+
That pair is what discharges pre-flight, rather than the
|
|
221
245
|
approval you got, and the distinction is the whole point: a stop
|
|
222
246
|
that carries the plan and pre-flight together has one reply for two obligations,
|
|
223
247
|
so a bare "yes" satisfies the plan and leaves no trace either way of the
|
|
@@ -293,9 +317,11 @@ reads the run state rather than the plan: it sees what has actually landed.
|
|
|
293
317
|
- Isolate the workspace before a multi-task plan: the harness's worktree
|
|
294
318
|
tool, not `git worktree` yourself. Implementing straight onto main or
|
|
295
319
|
master needs your partner's say-so, which pre-flight is where you got, and
|
|
296
|
-
it forecloses concurrent implementers for the whole run.
|
|
297
|
-
|
|
298
|
-
|
|
320
|
+
it forecloses concurrent implementers for the whole run. Cut it before the
|
|
321
|
+
run opens, so the state lives in it. A worktree cut after `init` still reads
|
|
322
|
+
the main tree's run, so nothing breaks for you, but the run stays in the main
|
|
323
|
+
tree where the next session to start a `deep` run finds it blocking `init`;
|
|
324
|
+
`status.sh move --to <worktree>` puts it where it belongs.
|
|
299
325
|
- **The agent that built the task commits it**, once its own tests pass, and
|
|
300
326
|
only the paths in its `Touches`. Never `git add -A`: the tree is shared, and
|
|
301
327
|
on a branch you did not isolate it holds work that is not this task's. The
|
|
@@ -21,13 +21,16 @@ bash <skill-dir>/scripts/status.sh preflight --review "tier 3 only" --model "6 o
|
|
|
21
21
|
bash <skill-dir>/scripts/status.sh show
|
|
22
22
|
bash <skill-dir>/scripts/status.sh ready
|
|
23
23
|
bash <skill-dir>/scripts/status.sh final
|
|
24
|
+
bash <skill-dir>/scripts/status.sh move --to <worktree>
|
|
24
25
|
bash <skill-dir>/scripts/status.sh line --full
|
|
25
26
|
bash <skill-dir>/scripts/status.sh close
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
`--dir <path>` reads another tree, which is what the statusline uses
|
|
29
|
-
a
|
|
30
|
-
|
|
29
|
+
`--dir <path>` reads another tree, which is what the statusline uses. A tree
|
|
30
|
+
with a run of its own is read as itself; one with none resolves to the main
|
|
31
|
+
worktree of its set, which is the tree a worktree is cut from and not the
|
|
32
|
+
controller's own worktree, so once the run lives there `--dir <implementer
|
|
33
|
+
worktree>` finds nothing. Statuses
|
|
31
34
|
are `todo`, `active`, `review`, `done` and `blocked`. A new id needs `--name`;
|
|
32
35
|
after that every call is a bare flip, so keeping it current costs one command
|
|
33
36
|
per transition rather than a paragraph. `close` archives the run under
|
|
@@ -44,8 +47,16 @@ which is the row the debt count counts.
|
|
|
44
47
|
|
|
45
48
|
A task going `active` with no `--base` takes the HEAD of the tree the command
|
|
46
49
|
is pointed at, `--dir` if given and the current tree otherwise, once; a base
|
|
47
|
-
already on the row is kept.
|
|
48
|
-
|
|
50
|
+
already on the row is kept. Issued from the controller's tree that is the
|
|
51
|
+
controller's HEAD, which is what an implementer worktree cut from that branch
|
|
52
|
+
starts at, so the default is right at dispatch. Where the implementer's tree
|
|
53
|
+
has moved on, pass `--base $(git -C <implementer worktree> rev-parse --short
|
|
54
|
+
HEAD)` rather than `--dir` that tree: with the run in your worktree, `--dir`
|
|
55
|
+
pointed at the implementer's resolves to the main tree and finds no run.
|
|
56
|
+
|
|
57
|
+
`init` reports any other run live in a tree of the same set, without refusing:
|
|
58
|
+
two sessions in two worktrees is legal, and a run stranded in the main tree
|
|
59
|
+
beside a fresh one in a worktree looks the same until someone says so.
|
|
49
60
|
|
|
50
61
|
Every write stamps `updated`. Past a day since the last one, `show` and the
|
|
51
62
|
statusline both say how long the run has sat idle, because a finished plan
|
|
@@ -69,37 +80,35 @@ that `.gitignore` if you want a run tracked; it is only written when absent.
|
|
|
69
80
|
|
|
70
81
|
## Worktrees
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
set took over every other session's statusline and refused every other `init`.
|
|
89
|
-
A submodule anchors on its own checkout, not the superproject's, and a
|
|
90
|
-
directory that is no git work tree keeps its run exactly where it sits.
|
|
83
|
+
The run lives with the controller: in the worktree the work runs in when
|
|
84
|
+
pre-flight bought one, in the main tree otherwise. Implementers never read or
|
|
85
|
+
write it, the controller flips every row, so nothing an implementer does
|
|
86
|
+
depends on seeing the run from its own worktree, and two sessions working
|
|
87
|
+
independently in two worktrees of one repo each keep their own run with
|
|
88
|
+
neither shown the other's.
|
|
89
|
+
|
|
90
|
+
`init` therefore always lands in the tree it is given, and every other command
|
|
91
|
+
reads that tree's own state when it has one. A tree with none falls back to the
|
|
92
|
+
main worktree of its set, which is what keeps a session working before this
|
|
93
|
+
rule existed, run in the main tree and worktree cut afterwards, reading the run
|
|
94
|
+
it started. That run stays in the main tree, where it blocks the next
|
|
95
|
+
session's `init`; `move --to <worktree>` relocates it, refusing a destination
|
|
96
|
+
that already holds a run or that is not a work tree of the same repository. A
|
|
97
|
+
submodule anchors on its own checkout, not the superproject's, and a directory
|
|
98
|
+
that is no git work tree keeps its run exactly where it sits.
|
|
91
99
|
|
|
92
100
|
One file for several writers is one file to contend on, so `init`, `task`,
|
|
93
|
-
`preflight`, `final` and `
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
saying so. The lock
|
|
101
|
+
`preflight`, `final`, `close` and `move` take a lock first, `move` taking the
|
|
102
|
+
destination tree's as well as its own: two flips issued at the same moment
|
|
103
|
+
from different trees would otherwise have the later write built on a snapshot
|
|
104
|
+
taken before the earlier one landed, dropping that row without saying so. The lock
|
|
97
105
|
carries its holder's pid, so a killed run is broken through rather than waited
|
|
98
106
|
out. Reads take nothing, state being installed through a rename, which is what
|
|
99
107
|
keeps `line` cheap enough to render on.
|
|
100
108
|
|
|
101
109
|
Open it with `init` when you open the run record, at the same point and for the
|
|
102
|
-
same reason,
|
|
110
|
+
same reason, and in the same tree: after pre-flight, inside the worktree when
|
|
111
|
+
one was bought. Then seed the rows with `plan.sh import <plan>` rather than a
|
|
103
112
|
command per task. The ids, names, the flip, the `Model` marks and the tiers are
|
|
104
113
|
all fixed the moment the plan is written and are already in the file, so typing
|
|
105
114
|
them again is transcription with a chance of error in it.
|
|
@@ -263,11 +272,12 @@ then print it last, after whatever else the command emits:
|
|
|
263
272
|
if [ -n "$sluice_line" ]; then printf '%s\n' "$sluice_line"; fi
|
|
264
273
|
```
|
|
265
274
|
|
|
266
|
-
The gate is two tests because a linked worktree
|
|
267
|
-
there `.git` is a regular file naming
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
The gate is two tests because a linked worktree may hold no state file of its
|
|
276
|
+
own and still belong to a set with a run: there `.git` is a regular file naming
|
|
277
|
+
the tree it was cut from, and the script falls back to that tree. A worktree
|
|
278
|
+
with its own run passes the first test. In a tree with no run and no worktree
|
|
279
|
+
behind it `.git` is a directory, so both tests fail and no process is spawned,
|
|
280
|
+
which is the property the gate is for.
|
|
271
281
|
|
|
272
282
|
`if` rather than `[ ... ] &&`: as the last command of a statusline script the
|
|
273
283
|
short form makes it exit 1 on every render with no run live, which is the common
|
|
@@ -282,7 +292,7 @@ variable is unset, at the price of a second `[ -f ]`. It renders as:
|
|
|
282
292
|
```
|
|
283
293
|
⧗ deep · sluice-cross-harness ◷ 38m
|
|
284
294
|
▰▰▰ ▰▰▨ ▨▨▨ ◈◈◈ ▱▱▱ ▮▮▮ ▱▱▱ ┃ ▱▱▱ ▱▱▱
|
|
285
|
-
2/9 done · !T6 model tiers rather than model names +1 · ⟲1 unreviewed
|
|
295
|
+
2/9 done · !T6 model tiers rather than model names +1 · ⟲ 1 unreviewed
|
|
286
296
|
```
|
|
287
297
|
|
|
288
298
|
The colour comes out of the script rather than being applied by the caller,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# status.sh show [--json]
|
|
17
17
|
# status.sh ready
|
|
18
18
|
# status.sh final
|
|
19
|
+
# status.sh move --to <tree>
|
|
19
20
|
# status.sh line [--full]
|
|
20
21
|
# status.sh close
|
|
21
22
|
#
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
# directory ignores itself, so no project needs a .gitignore line for it.
|
|
25
26
|
#
|
|
26
27
|
# Exit: 0 ok, 1 the state could not be written, 2 no live run, 3 a run is
|
|
27
|
-
# already live, 4 bad arguments, 5 jq missing,
|
|
28
|
+
# already live (here, or at move's destination), 4 bad arguments, 5 jq missing,
|
|
29
|
+
# 6 the state file is unreadable.
|
|
28
30
|
# `line` is exempt and always exits 0 in silence, because a statusline renders
|
|
29
31
|
# on every keystroke and has nowhere to put an error.
|
|
30
32
|
#
|
|
@@ -40,7 +42,7 @@ err() { echo "status.sh: $*" >&2; }
|
|
|
40
42
|
|
|
41
43
|
usage() {
|
|
42
44
|
echo "usage:" >&2
|
|
43
|
-
sed -n '/^# status.sh init
|
|
45
|
+
sed -n '/^# status.sh init/,/unreadable\.$/p' "$0" | sed 's/^# \{0,2\}//' >&2
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
# A flag's value has to be checked before `shift 2`, not after. Bash refuses to
|
|
@@ -242,7 +244,7 @@ if [ "$SUB" = "line" ]; then
|
|
|
242
244
|
elif $active then paint("96"; "▸T\($active.id)") + " " + ($active.name // "")
|
|
243
245
|
else "" end)
|
|
244
246
|
+ (if $attn > 1 then paint("2"; " +\($attn - 1)") else "" end),
|
|
245
|
-
(if $debt > 0 then paint("33"; "
|
|
247
|
+
(if $debt > 0 then paint("33"; "⟲ \($debt) unreviewed") else "" end)
|
|
246
248
|
] | join_parts)
|
|
247
249
|
)
|
|
248
250
|
' "$STATE" 2>/dev/null || exit 0
|
|
@@ -269,9 +271,9 @@ require_readable() {
|
|
|
269
271
|
# runs in having to add a line to its own .gitignore. `*` matches the .gitignore
|
|
270
272
|
# file too, so the whole directory drops out of `git status`. An existing file is
|
|
271
273
|
# left alone, and a tree that refuses the write still gets its run.
|
|
272
|
-
mk_dir() { # <directory to create under .sluice>
|
|
274
|
+
mk_dir() { # <directory to create under .sluice> [<tree whose .sluice it is, default $DIR>]
|
|
273
275
|
mkdir -p "$1" || { err "could not create $1"; exit 1; }
|
|
274
|
-
local ignore="$DIR/.sluice/.gitignore"
|
|
276
|
+
local ignore="${2:-$DIR}/.sluice/.gitignore"
|
|
275
277
|
[ -e "$ignore" ] || printf '*\n' >"$ignore" 2>/dev/null || true
|
|
276
278
|
}
|
|
277
279
|
|
|
@@ -313,30 +315,31 @@ write_state() {
|
|
|
313
315
|
# later one. A lock whose holder is gone is broken rather than waited out, and
|
|
314
316
|
# one whose holder is alive is waited on for a bounded time and then reported,
|
|
315
317
|
# because a command that hangs in a status bar is worse than one that fails.
|
|
316
|
-
|
|
318
|
+
# `move` spans two trees and holds both locks, so the locks taken are a list.
|
|
319
|
+
LOCKS_TAKEN=()
|
|
317
320
|
release_lock() {
|
|
318
|
-
|
|
319
|
-
rm -rf "$
|
|
320
|
-
|
|
321
|
+
local l
|
|
322
|
+
for l in "${LOCKS_TAKEN[@]+"${LOCKS_TAKEN[@]}"}"; do rm -rf "$l"; done
|
|
323
|
+
LOCKS_TAKEN=()
|
|
321
324
|
}
|
|
322
325
|
|
|
323
|
-
take_lock() {
|
|
324
|
-
local waited=0 holder
|
|
325
|
-
while ! mkdir "$
|
|
326
|
-
holder="$(cat "$
|
|
326
|
+
take_lock() { # [<lock path>, default the run's own]
|
|
327
|
+
local lock="${1:-$LOCK}" waited=0 holder
|
|
328
|
+
while ! mkdir "$lock" 2>/dev/null; do
|
|
329
|
+
holder="$(cat "$lock/pid" 2>/dev/null)"
|
|
327
330
|
if [ -n "$holder" ] && ! kill -0 "$holder" 2>/dev/null; then
|
|
328
|
-
rm -rf "$
|
|
331
|
+
rm -rf "$lock"
|
|
329
332
|
continue
|
|
330
333
|
fi
|
|
331
334
|
if [ "$waited" -ge 100 ]; then
|
|
332
|
-
err "another sluice command has held $
|
|
335
|
+
err "another sluice command has held $lock for 10s; remove it if nothing is running"
|
|
333
336
|
exit 1
|
|
334
337
|
fi
|
|
335
338
|
sleep 0.1
|
|
336
339
|
waited=$((waited + 1))
|
|
337
340
|
done
|
|
338
|
-
printf '%s\n' "$$" >"$
|
|
339
|
-
|
|
341
|
+
printf '%s\n' "$$" >"$lock/pid" 2>/dev/null || true
|
|
342
|
+
LOCKS_TAKEN+=("$lock")
|
|
340
343
|
trap release_lock EXIT INT TERM
|
|
341
344
|
}
|
|
342
345
|
|
|
@@ -361,10 +364,25 @@ case "$SUB" in
|
|
|
361
364
|
take_lock
|
|
362
365
|
if [ -f "$STATE" ] && [ "$FORCE" -eq 0 ]; then
|
|
363
366
|
live="$(jq -r '.topic // "?"' "$STATE" 2>/dev/null || echo "?")"
|
|
364
|
-
err "a run is already live (topic: $live)
|
|
367
|
+
err "a run is already live (topic: $live). If it is yours and your work runs in a worktree, put it there: status.sh move --to <worktree>. If it is another session's, leave it and start yours from your own worktree. If it is finished, status.sh close; --force replaces it"
|
|
365
368
|
exit 3
|
|
366
369
|
fi
|
|
367
370
|
|
|
371
|
+
# Other trees in the set may hold runs of their own, legitimately or as
|
|
372
|
+
# one stranded before its session moved into a worktree. Said, never
|
|
373
|
+
# refused: the session starting here cannot tell which, and the one that
|
|
374
|
+
# can is the owner.
|
|
375
|
+
if [ "$(git -C "$DIR" rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
|
|
376
|
+
here="$(cd "$DIR" && pwd -P)"
|
|
377
|
+
git -C "$DIR" worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | while IFS= read -r tree; do
|
|
378
|
+
[ -d "$tree" ] || continue
|
|
379
|
+
[ "$(cd "$tree" && pwd -P)" != "$here" ] || continue
|
|
380
|
+
[ -f "$tree/.sluice/run.json" ] || continue
|
|
381
|
+
other="$(jq -r '.topic // "?"' "$tree/.sluice/run.json" 2>/dev/null || echo "?")"
|
|
382
|
+
err "note: another run is live in $tree (topic: $other); if it is this work stranded before a worktree was cut, move it there instead"
|
|
383
|
+
done
|
|
384
|
+
fi
|
|
385
|
+
|
|
368
386
|
jq -n \
|
|
369
387
|
--arg topic "$TOPIC" \
|
|
370
388
|
--arg channel "$CHANNEL" \
|
|
@@ -664,6 +682,53 @@ case "$SUB" in
|
|
|
664
682
|
jq --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.final_review = $now' "$STATE" | write_state
|
|
665
683
|
;;
|
|
666
684
|
|
|
685
|
+
move)
|
|
686
|
+
TO=""
|
|
687
|
+
while [ $# -gt 0 ]; do
|
|
688
|
+
case "$1" in
|
|
689
|
+
--to) need_value --to $# "${2-}"; TO="$2"; shift 2 ;;
|
|
690
|
+
*) err "unknown flag: $1"; exit 4 ;;
|
|
691
|
+
esac
|
|
692
|
+
done
|
|
693
|
+
[ -n "$TO" ] || { err "move needs --to <tree>"; exit 4; }
|
|
694
|
+
[ -d "$TO" ] || { err "no such directory: $TO"; exit 4; }
|
|
695
|
+
TO="$(cd "$TO" && pwd -P)"
|
|
696
|
+
require_run
|
|
697
|
+
|
|
698
|
+
# The destination is taken as given rather than anchored: the whole point
|
|
699
|
+
# is to put the run in one particular tree, the controller's worktree,
|
|
700
|
+
# which anchoring would resolve straight back to the main tree it is
|
|
701
|
+
# leaving. It does have to be a work tree of the same set: a typo would
|
|
702
|
+
# otherwise strand the run at a path no command issued from the tree
|
|
703
|
+
# resolves, and the only way back is knowing where it went.
|
|
704
|
+
src_common="$(git -C "$DIR" rev-parse --git-common-dir 2>/dev/null)"
|
|
705
|
+
dst_common="$(git -C "$TO" rev-parse --git-common-dir 2>/dev/null)"
|
|
706
|
+
dst_top="$(git -C "$TO" rev-parse --show-toplevel 2>/dev/null)"
|
|
707
|
+
if [ -n "$src_common" ]; then
|
|
708
|
+
src_common="$(cd "$DIR" && cd "$src_common" 2>/dev/null && pwd -P)"
|
|
709
|
+
dst_common="$([ -n "$dst_common" ] && cd "$TO" && cd "$dst_common" 2>/dev/null && pwd -P)"
|
|
710
|
+
if [ -z "$dst_common" ] || [ "$dst_common" != "$src_common" ] || [ "$dst_top" != "$TO" ]; then
|
|
711
|
+
err "$TO is not a work tree of the same repository as $(cd "$DIR" && pwd -P); move only relocates a run between trees of one set"
|
|
712
|
+
exit 4
|
|
713
|
+
fi
|
|
714
|
+
fi
|
|
715
|
+
[ "$TO" != "$(cd "$DIR" && pwd -P)" ] || { err "the run is already in $TO"; exit 4; }
|
|
716
|
+
|
|
717
|
+
# Both trees' locks: a racing init in the destination takes that tree's
|
|
718
|
+
# lock, not this one's, and the mv would land on top of what it wrote.
|
|
719
|
+
take_lock
|
|
720
|
+
mk_dir "$TO/.sluice" "$TO"
|
|
721
|
+
take_lock "$TO/.sluice/run.lock"
|
|
722
|
+
DEST_STATE="$TO/.sluice/run.json"
|
|
723
|
+
if [ -f "$DEST_STATE" ]; then
|
|
724
|
+
there="$(jq -r '.topic // "?"' "$DEST_STATE" 2>/dev/null || echo "?")"
|
|
725
|
+
err "a run is already live in $TO (topic: $there); close it there first"
|
|
726
|
+
exit 3
|
|
727
|
+
fi
|
|
728
|
+
mv "$STATE" "$DEST_STATE" || { err "could not move $STATE to $DEST_STATE"; exit 1; }
|
|
729
|
+
echo "moved $(jq -r '.topic // "run"' "$DEST_STATE" 2>/dev/null || echo run) to $TO"
|
|
730
|
+
;;
|
|
731
|
+
|
|
667
732
|
close)
|
|
668
733
|
[ $# -eq 0 ] || { err "close takes no arguments"; exit 4; }
|
|
669
734
|
require_run
|
package/skills/sluice/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sluice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"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, 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. Claude Code only; stands down where the superpowers pipeline governs the repo.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|