@iceinvein/agent-skills 0.18.2 → 0.19.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 +14 -6
- package/package.json +1 -1
- package/skills/index.json +1 -1
- package/skills/sluice/SKILL.md +6 -0
- package/skills/sluice/evals/README.md +79 -0
- package/skills/sluice/evals/bypass-question-stays-silent/graders/answers-the-question.md +9 -0
- package/skills/sluice/evals/bypass-question-stays-silent/graders/no-channel-announcement.md +7 -0
- package/skills/sluice/evals/bypass-question-stays-silent/graders/writes-nothing.md +5 -0
- package/skills/sluice/evals/bypass-question-stays-silent/prompt.md +10 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/graders/announces-deep-channel.md +7 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/graders/design-written-to-docs.md +5 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/graders/no-implementation-yet.md +6 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/graders/stopped-for-signoff.md +8 -0
- package/skills/sluice/evals/deep-plan-across-subsystems/prompt.md +11 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/case.yaml +4 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/fixture.sh +332 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/contract-not-rewritten.md +6 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/ends-on-one-decision.md +15 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/quiet-flag-parsed.md +5 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/task-4-blocked.md +7 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/graders/three-tasks-landed.md +7 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/prompt.md +11 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/case.yaml +4 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/fixture.sh +304 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/did-not-check-in-between-tasks.md +13 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/every-task-done.md +7 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/no-task-left-todo.md +7 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/quiet-flag-landed.md +5 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/graders/suite-was-run.md +6 -0
- package/skills/sluice/evals/deep-run-finishes-every-task/prompt.md +11 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/case.yaml +4 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/fixture.sh +73 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/graders/announces-fast-channel.md +7 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/graders/collapsed-not-negotiated.md +10 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/graders/no-design-or-plan-file.md +6 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/graders/seam-implemented.md +9 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/explicit-instruction-collapses-to-fast/prompt.md +11 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/case.yaml +4 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/fixture.sh +73 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/announces-fast-channel.md +7 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/quiet-flag-implemented.md +5 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/stayed-in-fast.md +9 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/suite-was-run.md +6 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/graders/test-edited-before-source.md +6 -0
- package/skills/sluice/evals/fast-flag-on-existing-command/prompt.md +11 -0
- package/skills/sluice/evals/main-new-interface/case.yaml +4 -0
- package/skills/sluice/evals/main-new-interface/fixture.sh +73 -0
- package/skills/sluice/evals/main-new-interface/graders/announces-main-channel.md +7 -0
- package/skills/sluice/evals/main-new-interface/graders/behaviour-preserved.md +9 -0
- package/skills/sluice/evals/main-new-interface/graders/shape-agreed-before-building.md +10 -0
- package/skills/sluice/evals/main-new-interface/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/main-new-interface/graders/suite-was-run.md +6 -0
- package/skills/sluice/evals/main-new-interface/prompt.md +11 -0
- package/skills/sluice/evals/results/2026-09-20T01-51-28-540Z/aggregate-result.json +105 -0
- package/skills/sluice/evals/results/2026-09-20T01-51-28-540Z/report.html +300 -0
- package/skills/sluice/evals/results/2026-09-20T01-52-06-287Z/aggregate-result.json +122 -0
- package/skills/sluice/evals/results/2026-09-20T01-52-06-287Z/report.html +324 -0
- package/skills/sluice/evals/superpowers-conflict-stands-down/case.yaml +4 -0
- package/skills/sluice/evals/superpowers-conflict-stands-down/fixture.sh +39 -0
- package/skills/sluice/evals/superpowers-conflict-stands-down/graders/names-no-channel.md +8 -0
- package/skills/sluice/evals/superpowers-conflict-stands-down/graders/stands-down-once.md +9 -0
- package/skills/sluice/evals/superpowers-conflict-stands-down/prompt.md +10 -0
- package/skills/sluice/scripts/session-start.sh +51 -0
- package/skills/sluice/scripts/stop-guard.sh +101 -0
- package/skills/sluice/scripts/tree-snapshot.sh +75 -0
- package/skills/sluice/skill.json +1 -1
|
@@ -45,13 +45,114 @@ fi
|
|
|
45
45
|
|
|
46
46
|
cwd="$PWD"
|
|
47
47
|
active="false"
|
|
48
|
+
sid=""
|
|
49
|
+
transcript=""
|
|
48
50
|
if [ -n "$input" ]; then
|
|
49
51
|
got="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true)"
|
|
50
52
|
[ -n "$got" ] && cwd="$got"
|
|
51
53
|
active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null || echo false)"
|
|
54
|
+
sid="$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null || true)"
|
|
55
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null || true)"
|
|
52
56
|
fi
|
|
53
57
|
[ "$active" = "true" ] && exit 0
|
|
54
58
|
[ -d "$cwd" ] || exit 0
|
|
59
|
+
|
|
60
|
+
# ---- entry check --------------------------------------------------------
|
|
61
|
+
# The run guard below only arms once .sluice/run.json exists, and that file
|
|
62
|
+
# only exists once the skill has been invoked. So the session that never
|
|
63
|
+
# routed at all, the one this whole guard is for, walks straight past it in
|
|
64
|
+
# silence. This catches that session from the other end: the tree moved while
|
|
65
|
+
# it held it, and no channel was ever announced.
|
|
66
|
+
#
|
|
67
|
+
# The comparison is against where the tree stood when the session opened, not
|
|
68
|
+
# against whether it is dirty now. A session that opens on someone's
|
|
69
|
+
# work-in-progress and only answers questions changed nothing, and nudging it
|
|
70
|
+
# would teach its partner to ignore the nudge.
|
|
71
|
+
#
|
|
72
|
+
# Refused once and then never again for this session: the check cannot tell
|
|
73
|
+
# a partner who routed afterwards from one who read the nudge and chose to
|
|
74
|
+
# carry on, and only the first of those is worth a second refusal.
|
|
75
|
+
#
|
|
76
|
+
# The stamp is per session and the tree it watches is not, so anything else
|
|
77
|
+
# writing to the tree reads as this session's work: a second session, the
|
|
78
|
+
# partner's own editor, an install touching a lockfile, a build emitting
|
|
79
|
+
# something the tree does not ignore. There is nothing in a git tree that
|
|
80
|
+
# attributes a change to who made it, so this is not fixable here, only
|
|
81
|
+
# bounded: one refusal per session, and a reason that offers "not mine" as an
|
|
82
|
+
# answer and takes it.
|
|
83
|
+
entry_nudge() {
|
|
84
|
+
local stamps tree line_tree line_digest now
|
|
85
|
+
[ -n "$sid" ] || return 0
|
|
86
|
+
case "$sid" in */* | .*) return 0 ;; esac
|
|
87
|
+
|
|
88
|
+
# Two directories rather than one name and one suffixed name: sharing a
|
|
89
|
+
# namespace means a session id ending in `.nudged` silently disarms the
|
|
90
|
+
# session whose id is its prefix.
|
|
91
|
+
local root="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}/sluice"
|
|
92
|
+
stamps="$root/stamps"
|
|
93
|
+
[ -f "$stamps/$sid" ] || return 0
|
|
94
|
+
[ -f "$root/nudged/$sid" ] && return 0
|
|
95
|
+
|
|
96
|
+
tree="$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
97
|
+
[ -n "$tree" ] || return 0
|
|
98
|
+
|
|
99
|
+
line_tree="$(sed -n '1p' "$stamps/$sid" 2>/dev/null)"
|
|
100
|
+
line_digest="$(sed -n '2p' "$stamps/$sid" 2>/dev/null)"
|
|
101
|
+
[ -n "$line_tree" ] && [ -n "$line_digest" ] || return 0
|
|
102
|
+
# A session that moved trees carries a baseline for the one it left, and
|
|
103
|
+
# reading this tree's changes against it would be reading someone else's.
|
|
104
|
+
[ "$line_tree" = "$tree" ] || return 0
|
|
105
|
+
|
|
106
|
+
# A tree that cannot be read is unknown, not moved. Losing git's exit
|
|
107
|
+
# status here would turn a held lock or a rebase in flight into a refused
|
|
108
|
+
# turn over a tree nobody touched.
|
|
109
|
+
now="$(bash "$here/tree-snapshot.sh" "$tree" 2>/dev/null)" || return 0
|
|
110
|
+
[ -n "$now" ] || return 0
|
|
111
|
+
[ "$now" = "$line_digest" ] && return 0
|
|
112
|
+
|
|
113
|
+
# Whether the session routed is read exactly the way run-stats.sh reads it,
|
|
114
|
+
# from a copy of its `marker` and `lead` patterns and its `invokes_sluice`:
|
|
115
|
+
# a gate that disagreed with the meter would refuse turns the ledger
|
|
116
|
+
# reports as a run. The copy is kept in step with that file by hand.
|
|
117
|
+
#
|
|
118
|
+
# Lines are parsed one at a time and unparseable ones dropped, because the
|
|
119
|
+
# harness is still appending to this file while the hook reads it and the
|
|
120
|
+
# last line is regularly half written. Slurping would fail on that whole
|
|
121
|
+
# file and read a routed session as an unrouted one.
|
|
122
|
+
[ -f "$transcript" ] || return 0
|
|
123
|
+
jq -e -n -R '
|
|
124
|
+
def marker: "^[*_#>[:space:]]*(fast|main|deep)[[:space:]]+channel";
|
|
125
|
+
def lead: "^[^.!?\n]{0,100}[:=][[:space:]]*[*_]*(fast|main|deep)[[:space:]]+channel";
|
|
126
|
+
def texts: [ .message.content[]? | select(.type == "text") | .text ] | join("\n");
|
|
127
|
+
def invokes_sluice: [ .message.content[]?
|
|
128
|
+
| select(.type == "tool_use" and .name == "Skill")
|
|
129
|
+
| .input.skill? // empty ] | any(. == "sluice");
|
|
130
|
+
[ inputs | fromjson? // empty ]
|
|
131
|
+
| any(.[];
|
|
132
|
+
(.type == "assistant") and (((.isMeta == true) or (.isSidechain == true)) | not)
|
|
133
|
+
and ((texts | test(marker; "i") or test(lead; "i")) or invokes_sluice))
|
|
134
|
+
' "$transcript" >/dev/null 2>&1
|
|
135
|
+
case "$?" in
|
|
136
|
+
# 0 routed, 1 not. Anything else is jq failing rather than an answer
|
|
137
|
+
# about this session, and an unanswered question is not a refusal.
|
|
138
|
+
0) return 0 ;;
|
|
139
|
+
1) ;;
|
|
140
|
+
*) return 0 ;;
|
|
141
|
+
esac
|
|
142
|
+
|
|
143
|
+
mkdir -p "$root/nudged" 2>/dev/null || true
|
|
144
|
+
: >"$root/nudged/$sid" 2>/dev/null || true
|
|
145
|
+
jq -n '{
|
|
146
|
+
decision: "block",
|
|
147
|
+
reason: "sluice: this session has changed the tree since it opened and no channel was ever announced, so the work is running with none of the rules that its shape calls for and nothing to meter it from. Invoke the sluice skill now, route what you have been doing, and say which channel it is. If it genuinely changed nothing you own (a scratch file, or an edit that was not yours), say so and stop; this will not ask twice."
|
|
148
|
+
}'
|
|
149
|
+
return 1
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if ! entry_nudge; then
|
|
153
|
+
exit 0
|
|
154
|
+
fi
|
|
155
|
+
|
|
55
156
|
[ -f "$STATUS" ] || exit 0
|
|
56
157
|
|
|
57
158
|
# The session's own tree, and only that. status.sh lets a tree with no run of
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Print one digest of where a git tree stands, for stop-guard.sh's entry check.
|
|
3
|
+
#
|
|
4
|
+
# Usage: tree-snapshot.sh <tree>
|
|
5
|
+
# Exit 0 printed a digest, non-zero the tree could not be read. Prints nothing
|
|
6
|
+
# on failure, and a caller that cannot read the tree has no comparison to make
|
|
7
|
+
# rather than a change to report: an interrupted index, a lock held by another
|
|
8
|
+
# git, or a rebase in flight are all "unknown", never "moved".
|
|
9
|
+
#
|
|
10
|
+
# It lives in its own file because two hooks compute it at two different moments
|
|
11
|
+
# and a difference between their two copies would not read as a bug, it would
|
|
12
|
+
# read as the tree having moved. That is the whole measurement.
|
|
13
|
+
#
|
|
14
|
+
# Three signals, because each covers a way of finishing work that the others
|
|
15
|
+
# call standing still:
|
|
16
|
+
#
|
|
17
|
+
# HEAD a session that committed leaves a clean tree
|
|
18
|
+
# diff HEAD a session that rewrote a file already modified
|
|
19
|
+
# leaves the porcelain's shape untouched
|
|
20
|
+
# status --porcelain -uall a session that filled an untracked directory
|
|
21
|
+
# leaves one collapsed `?? build/` either way
|
|
22
|
+
#
|
|
23
|
+
# What it still misses, and why each is left:
|
|
24
|
+
#
|
|
25
|
+
# - the content of a file that was already untracked when the session opened.
|
|
26
|
+
# Listing untracked paths is cheap and hashing their contents is not, and
|
|
27
|
+
# this runs at the end of every turn.
|
|
28
|
+
# - anything inside a nested repository. `status` does not descend into a
|
|
29
|
+
# directory carrying its own .git, so a session spent editing a scratch
|
|
30
|
+
# clone or a vendored checkout reads as a tree nobody touched.
|
|
31
|
+
# - who made the change. Nothing in a git tree records that, so a branch
|
|
32
|
+
# switch, a pull, or another session writing to the same tree all read as
|
|
33
|
+
# this session's work. stop-guard.sh bounds that rather than fixing it.
|
|
34
|
+
|
|
35
|
+
set -uo pipefail
|
|
36
|
+
|
|
37
|
+
tree="${1-}"
|
|
38
|
+
[ -n "$tree" ] || exit 1
|
|
39
|
+
command -v cksum >/dev/null 2>&1 || exit 1
|
|
40
|
+
|
|
41
|
+
# `git diff HEAD` has no meaning before the first commit, and a repo without one
|
|
42
|
+
# is a repo whose every file is untracked, which the porcelain covers. So an
|
|
43
|
+
# unresolvable HEAD is carried as "no-head" rather than refused.
|
|
44
|
+
#
|
|
45
|
+
# It would be better to refuse when HEAD is unresolvable because the ref store
|
|
46
|
+
# cannot be read, as opposed to because no commit exists yet, since the first is
|
|
47
|
+
# a tree this script cannot describe. git gives no way to tell them apart:
|
|
48
|
+
# `rev-parse --verify --quiet` exits 1 for both, and `for-each-ref` returns
|
|
49
|
+
# empty for both, hiding an unreadable ref exactly as it reports an absent one.
|
|
50
|
+
# Refusing on both would disarm the check permanently in any repo before its
|
|
51
|
+
# first commit, which is an ordinary place to be writing code. Carrying both
|
|
52
|
+
# costs a single nudge, and only in a tree whose refs broke partway through the
|
|
53
|
+
# session it had already been read in.
|
|
54
|
+
head="$(git -C "$tree" rev-parse --verify --quiet HEAD 2>/dev/null)" || head=""
|
|
55
|
+
|
|
56
|
+
# Streamed into cksum rather than gathered into variables first. A diff is
|
|
57
|
+
# unbounded, and this runs at the end of every turn until the session is either
|
|
58
|
+
# routed or nudged: an 80MB file rewritten peaks at 334MB resident streamed,
|
|
59
|
+
# against 622MB for a 61MB one held in a shell variable and then piped. What is
|
|
60
|
+
# left is git loading the blobs, which is the price of reading content at all
|
|
61
|
+
# and is the same for --numstat. Streaming also keeps NUL bytes intact, which a
|
|
62
|
+
# command substitution silently drops, and dropping them would hide a change
|
|
63
|
+
# confined to them.
|
|
64
|
+
#
|
|
65
|
+
# `exit 1` inside the group reaches the caller through pipefail, and the digest
|
|
66
|
+
# cksum prints from a truncated read is discarded with it: a caller that cannot
|
|
67
|
+
# read the tree needs no answer, and a short answer is a wrong one.
|
|
68
|
+
digest="$( {
|
|
69
|
+
printf '%s\n' "${head:-no-head}"
|
|
70
|
+
git -C "$tree" status --porcelain -uall 2>/dev/null || exit 1
|
|
71
|
+
[ -n "$head" ] && { git -C "$tree" diff HEAD 2>/dev/null || exit 1; }
|
|
72
|
+
true
|
|
73
|
+
} | cksum )" || exit 1
|
|
74
|
+
|
|
75
|
+
printf '%s\n' "$digest"
|
package/skills/sluice/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sluice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|