@iceinvein/agent-skills 0.12.0 → 0.13.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/dist/cli/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
20
20
|
return cached;
|
|
21
21
|
}
|
|
22
22
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
23
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
24
24
|
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
25
25
|
for (let key of __getOwnPropNames(mod))
|
|
26
26
|
if (!__hasOwnProp.call(to, key))
|
|
@@ -1032,9 +1032,9 @@ async function removeSkill(cwd, skillName) {
|
|
|
1032
1032
|
const warnings = [];
|
|
1033
1033
|
for (const file of entry.files) {
|
|
1034
1034
|
if (isSharedConfigFile(file)) {
|
|
1035
|
-
const
|
|
1036
|
-
if (existsSync6(
|
|
1037
|
-
await unwireSessionStartHook(
|
|
1035
|
+
const fullPath = join7(cwd, file);
|
|
1036
|
+
if (existsSync6(fullPath)) {
|
|
1037
|
+
await unwireSessionStartHook(fullPath, skillName);
|
|
1038
1038
|
}
|
|
1039
1039
|
warnings.push(`Manifest unavailable (${manifestResult.error}). Left '${file}' in place, only removing '${skillName}'s SessionStart hook from it; any MCP server entries it owns were not touched.`);
|
|
1040
1040
|
continue;
|
|
@@ -1125,9 +1125,9 @@ async function removeSkill2(cwd, skillName) {
|
|
|
1125
1125
|
const warnings = [];
|
|
1126
1126
|
for (const file of entry.files) {
|
|
1127
1127
|
if (isSharedConfigFile2(file)) {
|
|
1128
|
-
const
|
|
1129
|
-
if (existsSync7(
|
|
1130
|
-
await unwireSessionStartHook(
|
|
1128
|
+
const fullPath = join8(cwd, file);
|
|
1129
|
+
if (existsSync7(fullPath)) {
|
|
1130
|
+
await unwireSessionStartHook(fullPath, skillName);
|
|
1131
1131
|
}
|
|
1132
1132
|
warnings.push(`Manifest unavailable (${manifestResult.error}). Left '${file}' in place, only removing '${skillName}'s SessionStart hook from it; any MCP server entries it owns were not touched.`);
|
|
1133
1133
|
continue;
|
|
@@ -1518,7 +1518,7 @@ function isFactory(value) {
|
|
|
1518
1518
|
}
|
|
1519
1519
|
function useState(defaultValue) {
|
|
1520
1520
|
return withPointer((pointer) => {
|
|
1521
|
-
const setState = AsyncResource2.bind(function
|
|
1521
|
+
const setState = AsyncResource2.bind(function setState(newValue) {
|
|
1522
1522
|
if (pointer.get() !== newValue) {
|
|
1523
1523
|
pointer.set(newValue);
|
|
1524
1524
|
handleChange();
|
|
@@ -2387,7 +2387,7 @@ if (process.platform === "linux") {
|
|
|
2387
2387
|
}
|
|
2388
2388
|
|
|
2389
2389
|
// node_modules/signal-exit/dist/mjs/index.js
|
|
2390
|
-
var processOk = (
|
|
2390
|
+
var processOk = (process2) => !!process2 && typeof process2 === "object" && typeof process2.removeListener === "function" && typeof process2.emit === "function" && typeof process2.reallyExit === "function" && typeof process2.listeners === "function" && typeof process2.kill === "function" && typeof process2.pid === "number" && typeof process2.on === "function";
|
|
2391
2391
|
var kExitEmitter = Symbol.for("signal-exit emitter");
|
|
2392
2392
|
var global = globalThis;
|
|
2393
2393
|
var ObjectDefineProperty = Object.defineProperty.bind(Object);
|
|
@@ -2477,15 +2477,15 @@ class SignalExit extends SignalExitBase {
|
|
|
2477
2477
|
#originalProcessReallyExit;
|
|
2478
2478
|
#sigListeners = {};
|
|
2479
2479
|
#loaded = false;
|
|
2480
|
-
constructor(
|
|
2480
|
+
constructor(process2) {
|
|
2481
2481
|
super();
|
|
2482
|
-
this.#process =
|
|
2482
|
+
this.#process = process2;
|
|
2483
2483
|
this.#sigListeners = {};
|
|
2484
2484
|
for (const sig of signals) {
|
|
2485
2485
|
this.#sigListeners[sig] = () => {
|
|
2486
2486
|
const listeners = this.#process.listeners(sig);
|
|
2487
2487
|
let { count } = this.#emitter;
|
|
2488
|
-
const p =
|
|
2488
|
+
const p = process2;
|
|
2489
2489
|
if (typeof p.__signal_exit_emitter__ === "object" && typeof p.__signal_exit_emitter__.count === "number") {
|
|
2490
2490
|
count += p.__signal_exit_emitter__.count;
|
|
2491
2491
|
}
|
|
@@ -2494,12 +2494,12 @@ class SignalExit extends SignalExitBase {
|
|
|
2494
2494
|
const ret = this.#emitter.emit("exit", null, sig);
|
|
2495
2495
|
const s = sig === "SIGHUP" ? this.#hupSig : sig;
|
|
2496
2496
|
if (!ret)
|
|
2497
|
-
|
|
2497
|
+
process2.kill(process2.pid, s);
|
|
2498
2498
|
}
|
|
2499
2499
|
};
|
|
2500
2500
|
}
|
|
2501
|
-
this.#originalProcessReallyExit =
|
|
2502
|
-
this.#originalProcessEmit =
|
|
2501
|
+
this.#originalProcessReallyExit = process2.reallyExit;
|
|
2502
|
+
this.#originalProcessEmit = process2.emit;
|
|
2503
2503
|
}
|
|
2504
2504
|
onExit(cb, opts) {
|
|
2505
2505
|
if (!processOk(this.#process)) {
|
|
@@ -2725,8 +2725,8 @@ function createPrompt(view) {
|
|
|
2725
2725
|
signal.addEventListener("abort", abort);
|
|
2726
2726
|
cleanups.add(() => signal.removeEventListener("abort", abort));
|
|
2727
2727
|
}
|
|
2728
|
-
cleanups.add(onExit((code,
|
|
2729
|
-
reject(new ExitPromptError(`User force closed the prompt with ${code} ${
|
|
2728
|
+
cleanups.add(onExit((code, signal) => {
|
|
2729
|
+
reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
|
|
2730
2730
|
}));
|
|
2731
2731
|
const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
|
|
2732
2732
|
rl.on("SIGINT", sigint);
|
|
@@ -2847,12 +2847,12 @@ function normalizeChoices(choices) {
|
|
|
2847
2847
|
if (Separator.isSeparator(choice))
|
|
2848
2848
|
return choice;
|
|
2849
2849
|
if (typeof choice !== "object" || choice === null || !("value" in choice)) {
|
|
2850
|
-
const
|
|
2850
|
+
const name = String(choice);
|
|
2851
2851
|
return {
|
|
2852
2852
|
value: choice,
|
|
2853
|
-
name
|
|
2854
|
-
short:
|
|
2855
|
-
checkedName:
|
|
2853
|
+
name,
|
|
2854
|
+
short: name,
|
|
2855
|
+
checkedName: name,
|
|
2856
2856
|
disabled: false,
|
|
2857
2857
|
checked: false
|
|
2858
2858
|
};
|
|
@@ -2957,8 +2957,8 @@ var dist_default4 = createPrompt((config, done) => {
|
|
|
2957
2957
|
const cursor = isActive ? theme.icon.cursor : " ";
|
|
2958
2958
|
if (item.disabled) {
|
|
2959
2959
|
const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
|
|
2960
|
-
const
|
|
2961
|
-
return theme.style.disabled(`${cursor}${
|
|
2960
|
+
const checkbox = item.checked ? theme.icon.disabledChecked : theme.icon.disabledUnchecked;
|
|
2961
|
+
return theme.style.disabled(`${cursor}${checkbox} ${item.name} ${disabledLabel}`);
|
|
2962
2962
|
}
|
|
2963
2963
|
if (isActive) {
|
|
2964
2964
|
description = item.description;
|
|
@@ -3021,11 +3021,11 @@ function normalizeChoices2(choices) {
|
|
|
3021
3021
|
if (Separator.isSeparator(choice))
|
|
3022
3022
|
return choice;
|
|
3023
3023
|
if (typeof choice !== "object" || choice === null || !("value" in choice)) {
|
|
3024
|
-
const
|
|
3024
|
+
const name = String(choice);
|
|
3025
3025
|
return {
|
|
3026
3026
|
value: choice,
|
|
3027
|
-
name
|
|
3028
|
-
short:
|
|
3027
|
+
name,
|
|
3028
|
+
short: name,
|
|
3029
3029
|
disabled: false
|
|
3030
3030
|
};
|
|
3031
3031
|
}
|
|
@@ -3095,8 +3095,8 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
3095
3095
|
} else if (isNumberKey(key) && !Number.isNaN(Number(rl.line))) {
|
|
3096
3096
|
const selectedIndex = Number(rl.line) - 1;
|
|
3097
3097
|
let selectableIndex = -1;
|
|
3098
|
-
const position = items.findIndex((
|
|
3099
|
-
if (Separator.isSeparator(
|
|
3098
|
+
const position = items.findIndex((item) => {
|
|
3099
|
+
if (Separator.isSeparator(item))
|
|
3100
3100
|
return false;
|
|
3101
3101
|
selectableIndex++;
|
|
3102
3102
|
return selectableIndex === selectedIndex;
|
|
@@ -3477,10 +3477,10 @@ Installing ${names.length} skills...
|
|
|
3477
3477
|
}
|
|
3478
3478
|
const other = await otherScopeSkillCount(updateDir);
|
|
3479
3479
|
if (other.count > 0) {
|
|
3480
|
-
const
|
|
3480
|
+
const command = other.scope === "global" ? "agent-skills update --all -g" : "agent-skills update --all";
|
|
3481
3481
|
const noun = other.count === 1 ? "skill" : "skills";
|
|
3482
3482
|
console.log(`
|
|
3483
|
-
Note: ${other.count} ${noun} installed in the ${other.scope} scope. Run \`${
|
|
3483
|
+
Note: ${other.count} ${noun} installed in the ${other.scope} scope. Run \`${command}\` to update those too.`);
|
|
3484
3484
|
}
|
|
3485
3485
|
break;
|
|
3486
3486
|
}
|
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 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.
|
|
286
|
+
"version": "0.14.0"
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"name": "temporal-coupling-detector",
|
|
@@ -285,7 +285,9 @@ reads the run state rather than the plan: it sees what has actually landed.
|
|
|
285
285
|
- Isolate the workspace before a multi-task plan: the harness's worktree
|
|
286
286
|
tool, not `git worktree` yourself. Implementing straight onto main or
|
|
287
287
|
master needs your partner's say-so, which pre-flight is where you got, and
|
|
288
|
-
it forecloses concurrent implementers for the whole run.
|
|
288
|
+
it forecloses concurrent implementers for the whole run. The run state
|
|
289
|
+
follows the set rather than the tree, so a worktree cut after the plan still
|
|
290
|
+
reads the rows the plan seeded and flips them where you are watching.
|
|
289
291
|
- **The agent that built the task commits it**, once its own tests pass, and
|
|
290
292
|
only the paths in its `Touches`. Never `git add -A`: the tree is shared, and
|
|
291
293
|
on a branch you did not isolate it holds work that is not this task's. The
|
|
@@ -7,7 +7,7 @@ session that is running it: your partner has to ask, and asking costs a turn
|
|
|
7
7
|
and gets an answer from memory.
|
|
8
8
|
|
|
9
9
|
`.sluice/run.json` is that answer in a form something else can read. One file
|
|
10
|
-
per
|
|
10
|
+
per worktree set, holding only what changes as the run moves.
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
bash <skill-dir>/scripts/status.sh init --topic <t> --channel deep \
|
|
@@ -24,7 +24,9 @@ bash <skill-dir>/scripts/status.sh line --full
|
|
|
24
24
|
bash <skill-dir>/scripts/status.sh close
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
`--dir <path>` reads another tree, which is what the statusline uses
|
|
27
|
+
`--dir <path>` reads another tree, which is what the statusline uses; it names
|
|
28
|
+
a tree in the set rather than a state file, so a worktree resolves to the same
|
|
29
|
+
run as the tree it was cut from. Statuses
|
|
28
30
|
are `todo`, `active`, `review`, `done` and `blocked`. A new id needs `--name`;
|
|
29
31
|
after that every call is a bare flip, so keeping it current costs one command
|
|
30
32
|
per transition rather than a paragraph. `close` archives the run under
|
|
@@ -42,6 +44,31 @@ everything durable in it lands somewhere else anyway: the commits are in git and
|
|
|
42
44
|
the reasons are in the record, which is the file that does get committed. Delete
|
|
43
45
|
that `.gitignore` if you want a run tracked; it is only written when absent.
|
|
44
46
|
|
|
47
|
+
## Worktrees
|
|
48
|
+
|
|
49
|
+
Ignoring itself is what makes the run invisible to a worktree unless something
|
|
50
|
+
is done about it, and a `deep` run makes worktrees after the plan is written:
|
|
51
|
+
`git worktree add` gives the implementer a clean checkout, and an ignored
|
|
52
|
+
directory is not in a checkout. Read from the tree it was called in, the run
|
|
53
|
+
the plan seeded would be absent from every implementer, `init` there would
|
|
54
|
+
start a second run nothing else reads, and the worktree would take that state
|
|
55
|
+
with it when it went.
|
|
56
|
+
|
|
57
|
+
So every command anchors on the main worktree of whatever tree it is pointed
|
|
58
|
+
at, and one run covers the set. The statusline renders the same run in every
|
|
59
|
+
window, a flip made by an implementer lands where the controller is watching,
|
|
60
|
+
and `init` from a worktree reports the run that is already live rather than
|
|
61
|
+
replacing it. A submodule anchors on its own checkout, not the superproject's,
|
|
62
|
+
and a directory that is no git work tree keeps its run exactly where it sits.
|
|
63
|
+
|
|
64
|
+
One file for several writers is one file to contend on, so `init`, `task`,
|
|
65
|
+
`preflight` and `close` take a lock first: four implementers each flipping
|
|
66
|
+
their own row would otherwise have the later write built on a snapshot taken
|
|
67
|
+
before the earlier one landed, dropping that row without saying so. The lock
|
|
68
|
+
carries its holder's pid, so a killed run is broken through rather than waited
|
|
69
|
+
out. Reads take nothing, state being installed through a rename, which is what
|
|
70
|
+
keeps `line` cheap enough to render on.
|
|
71
|
+
|
|
45
72
|
Open it with `init` when you open the run record, at the same point and for the
|
|
46
73
|
same reason, then seed the rows with `plan.sh import <plan>` rather than a
|
|
47
74
|
command per task. The ids, names, the flip, the `Model` marks and the tiers are
|
|
@@ -169,7 +196,7 @@ state file existing so a session with no run spawns no process at all:
|
|
|
169
196
|
|
|
170
197
|
```bash
|
|
171
198
|
sluice_line=""
|
|
172
|
-
if [ -n "$cwd" ] && [ -f "$cwd/.sluice/run.json" ]; then
|
|
199
|
+
if [ -n "$cwd" ] && { [ -f "$cwd/.sluice/run.json" ] || [ -f "$cwd/.git" ]; }; then
|
|
173
200
|
for sluice_sh in "$cwd/.claude/skills/sluice/scripts/status.sh" \
|
|
174
201
|
"${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills/sluice/scripts/status.sh" \
|
|
175
202
|
"$HOME/.claude/skills/sluice/scripts/status.sh"; do
|
|
@@ -186,6 +213,12 @@ then print it last, after whatever else the command emits:
|
|
|
186
213
|
if [ -n "$sluice_line" ]; then printf '%s\n' "$sluice_line"; fi
|
|
187
214
|
```
|
|
188
215
|
|
|
216
|
+
The gate is two tests because a linked worktree holds no state file of its own:
|
|
217
|
+
there `.git` is a regular file naming the tree it was cut from, and the script
|
|
218
|
+
resolves the run from it. In a tree with no run and no worktree behind it `.git`
|
|
219
|
+
is a directory, so both tests fail and no process is spawned, which is the
|
|
220
|
+
property the gate is for.
|
|
221
|
+
|
|
189
222
|
`if` rather than `[ ... ] &&`: as the last command of a statusline script the
|
|
190
223
|
short form makes it exit 1 on every render with no run live, which is the common
|
|
191
224
|
case. `%s` rather than `%b`: the render already carries real escape bytes, and
|
|
@@ -92,8 +92,27 @@ if [ -z "$SUB" ]; then
|
|
|
92
92
|
exit 4
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
|
+
# A linked worktree is another view of the same run, not a new one. The run
|
|
96
|
+
# directory ignores itself, so `git worktree add` never carries it across: read
|
|
97
|
+
# from the tree it was called in, the run a plan seeded is absent from every
|
|
98
|
+
# implementer created after that plan, and an `init` there lands a rival state
|
|
99
|
+
# file that dies with the worktree. Every tree in a set anchors on the main
|
|
100
|
+
# worktree instead, which is the one path all of them agree on.
|
|
101
|
+
#
|
|
102
|
+
# `git worktree list` names the main worktree first. A submodule names its own
|
|
103
|
+
# checkout there rather than the superproject's, which is what keeps a
|
|
104
|
+
# submodule's run beside its own working tree, and a directory that is no git
|
|
105
|
+
# work tree at all is left exactly as it was given.
|
|
106
|
+
if [ "$(git -C "$DIR" rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
|
|
107
|
+
MAIN_TREE="$(git -C "$DIR" worktree list --porcelain 2>/dev/null | sed -n '1s/^worktree //p')"
|
|
108
|
+
if [ -n "${MAIN_TREE:-}" ] && [ -d "$MAIN_TREE" ]; then
|
|
109
|
+
DIR="$MAIN_TREE"
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
|
|
95
113
|
STATE="$DIR/.sluice/run.json"
|
|
96
114
|
ARCHIVE="$DIR/.sluice/archive"
|
|
115
|
+
LOCK="$DIR/.sluice/run.lock"
|
|
97
116
|
|
|
98
117
|
# `line` swallows everything: a missing jq, unreadable state, no run at all.
|
|
99
118
|
# Any of those printing would put permanent clutter in the status bar.
|
|
@@ -252,6 +271,45 @@ write_state() {
|
|
|
252
271
|
mv "$tmp" "$STATE" || { rm -f "$tmp"; err "could not replace $STATE"; exit 1; }
|
|
253
272
|
}
|
|
254
273
|
|
|
274
|
+
# One state file now serves a whole worktree set, so two implementers can flip
|
|
275
|
+
# their own task at the same moment. A flip reads the whole file, edits it with
|
|
276
|
+
# jq and writes it back, so unserialised the later write is built on a snapshot
|
|
277
|
+
# taken before the earlier one landed and drops that row without saying so.
|
|
278
|
+
# mkdir is the atomic primitive every platform this runs on has; flock is Linux
|
|
279
|
+
# only. Reads do not take it: write_state installs through a rename, so a reader
|
|
280
|
+
# sees either the whole old file or the whole new one.
|
|
281
|
+
#
|
|
282
|
+
# The holder's pid goes inside the directory so a killed run cannot wedge every
|
|
283
|
+
# later one. A lock whose holder is gone is broken rather than waited out, and
|
|
284
|
+
# one whose holder is alive is waited on for a bounded time and then reported,
|
|
285
|
+
# because a command that hangs in a status bar is worse than one that fails.
|
|
286
|
+
lock_taken=0
|
|
287
|
+
release_lock() {
|
|
288
|
+
[ "$lock_taken" -eq 1 ] || return 0
|
|
289
|
+
rm -rf "$LOCK"
|
|
290
|
+
lock_taken=0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
take_lock() {
|
|
294
|
+
local waited=0 holder
|
|
295
|
+
while ! mkdir "$LOCK" 2>/dev/null; do
|
|
296
|
+
holder="$(cat "$LOCK/pid" 2>/dev/null)"
|
|
297
|
+
if [ -n "$holder" ] && ! kill -0 "$holder" 2>/dev/null; then
|
|
298
|
+
rm -rf "$LOCK"
|
|
299
|
+
continue
|
|
300
|
+
fi
|
|
301
|
+
if [ "$waited" -ge 100 ]; then
|
|
302
|
+
err "another sluice command has held $LOCK for 10s; remove it if nothing is running"
|
|
303
|
+
exit 1
|
|
304
|
+
fi
|
|
305
|
+
sleep 0.1
|
|
306
|
+
waited=$((waited + 1))
|
|
307
|
+
done
|
|
308
|
+
printf '%s\n' "$$" >"$LOCK/pid" 2>/dev/null || true
|
|
309
|
+
lock_taken=1
|
|
310
|
+
trap release_lock EXIT INT TERM
|
|
311
|
+
}
|
|
312
|
+
|
|
255
313
|
case "$SUB" in
|
|
256
314
|
init)
|
|
257
315
|
TOPIC="" CHANNEL="" PLAN="" RECORD="" FORCE=0
|
|
@@ -269,13 +327,14 @@ case "$SUB" in
|
|
|
269
327
|
[ -n "$CHANNEL" ] || { err "init needs --channel"; exit 4; }
|
|
270
328
|
in_set "$CHANNEL" "$CHANNELS" || { err "unknown channel: $CHANNEL (one of: $CHANNELS)"; exit 4; }
|
|
271
329
|
|
|
330
|
+
mk_dir "$DIR/.sluice"
|
|
331
|
+
take_lock
|
|
272
332
|
if [ -f "$STATE" ] && [ "$FORCE" -eq 0 ]; then
|
|
273
333
|
live="$(jq -r '.topic // "?"' "$STATE" 2>/dev/null || echo "?")"
|
|
274
334
|
err "a run is already live (topic: $live); pass --force to replace it"
|
|
275
335
|
exit 3
|
|
276
336
|
fi
|
|
277
337
|
|
|
278
|
-
mk_dir "$DIR/.sluice"
|
|
279
338
|
jq -n \
|
|
280
339
|
--arg topic "$TOPIC" \
|
|
281
340
|
--arg channel "$CHANNEL" \
|
|
@@ -332,6 +391,7 @@ case "$SUB" in
|
|
|
332
391
|
fi
|
|
333
392
|
|
|
334
393
|
require_run
|
|
394
|
+
take_lock
|
|
335
395
|
require_readable
|
|
336
396
|
|
|
337
397
|
# A row with no name is a number nobody can act on, so a new id has to
|
|
@@ -404,6 +464,7 @@ case "$SUB" in
|
|
|
404
464
|
exit 4
|
|
405
465
|
fi
|
|
406
466
|
require_run
|
|
467
|
+
take_lock
|
|
407
468
|
require_readable
|
|
408
469
|
|
|
409
470
|
jq --arg review "$REVIEW" --arg model "$MODEL" --arg workspace "$WORKSPACE" '
|
|
@@ -528,6 +589,7 @@ case "$SUB" in
|
|
|
528
589
|
close)
|
|
529
590
|
[ $# -eq 0 ] || { err "close takes no arguments"; exit 4; }
|
|
530
591
|
require_run
|
|
592
|
+
take_lock
|
|
531
593
|
|
|
532
594
|
# Deliberately not `require_readable`. The parse error every other
|
|
533
595
|
# subcommand raises names close as the way out, so close is the one
|
package/skills/sluice/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sluice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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 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.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|