@iceinvein/agent-skills 0.2.0 → 0.3.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/README.md +1 -1
- package/dist/cli/index.js +14 -10
- package/package.json +1 -1
- package/skills/index.json +2 -2
- package/skills/sluice/SKILL.md +20 -7
- package/skills/sluice/references/deep-channel.md +20 -0
- package/skills/sluice/references/finish.md +4 -2
- package/skills/sluice/references/meter.md +38 -0
- package/skills/sluice/scripts/run-stats.sh +236 -0
- package/skills/sluice/skill.json +4 -3
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Skills that shape how work gets done rather than analyzing code.
|
|
|
103
103
|
|
|
104
104
|
| Skill | What it does |
|
|
105
105
|
|-------|--------------|
|
|
106
|
-
| **sluice** | 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.
|
|
106
|
+
| **sluice** | 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. Seven rules live as one-liners in the router; the full treatment sits in references read only on friction. Each run closes with a ledger read out of the session transcript: elapsed, tools, tokens, and what every dispatched agent cost. Claude Code only. Conflicts with the superpowers plugin, which requires its own fixed pipeline up front for anything that adds to or changes what the software does, not just code edits; disable superpowers before installing. |
|
|
107
107
|
|
|
108
108
|
### Orchestration
|
|
109
109
|
|
package/dist/cli/index.js
CHANGED
|
@@ -402,7 +402,7 @@ function resolveBundlePaths(entries, bundle) {
|
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
// src/cli/adapters/claude.ts
|
|
405
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
405
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, rmSync, rmdirSync, statSync, unlinkSync } from "node:fs";
|
|
406
406
|
import { dirname, join as join2 } from "node:path";
|
|
407
407
|
function shouldBeExecutable(relPath, content) {
|
|
408
408
|
if (relPath.endsWith(".sh"))
|
|
@@ -569,7 +569,7 @@ var claudeAdapter = {
|
|
|
569
569
|
const stopAt = config.bundleRoot ? join2(cwd, config.bundleRoot, "..") : cwd;
|
|
570
570
|
while (dir !== stopAt && dir !== "/" && dir.startsWith(cwd)) {
|
|
571
571
|
try {
|
|
572
|
-
|
|
572
|
+
rmdirSync(dir);
|
|
573
573
|
} catch {
|
|
574
574
|
break;
|
|
575
575
|
}
|
|
@@ -598,7 +598,7 @@ var claudeAdapter = {
|
|
|
598
598
|
};
|
|
599
599
|
|
|
600
600
|
// src/cli/adapters/cursor.ts
|
|
601
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2,
|
|
601
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "node:fs";
|
|
602
602
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
603
603
|
var cursorAdapter = {
|
|
604
604
|
name: "cursor",
|
|
@@ -651,7 +651,7 @@ var cursorAdapter = {
|
|
|
651
651
|
if (existsSync3(fullPath)) {
|
|
652
652
|
unlinkSync2(fullPath);
|
|
653
653
|
try {
|
|
654
|
-
|
|
654
|
+
rmdirSync2(dirname2(fullPath));
|
|
655
655
|
} catch {}
|
|
656
656
|
}
|
|
657
657
|
}
|
|
@@ -728,7 +728,7 @@ var codexAdapter = {
|
|
|
728
728
|
};
|
|
729
729
|
|
|
730
730
|
// src/cli/adapters/gemini.ts
|
|
731
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3,
|
|
731
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, rmdirSync as rmdirSync3 } from "node:fs";
|
|
732
732
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
733
733
|
var geminiAdapter = {
|
|
734
734
|
name: "gemini",
|
|
@@ -781,7 +781,7 @@ var geminiAdapter = {
|
|
|
781
781
|
if (existsSync5(fullPath)) {
|
|
782
782
|
unlinkSync3(fullPath);
|
|
783
783
|
try {
|
|
784
|
-
|
|
784
|
+
rmdirSync3(dirname3(fullPath));
|
|
785
785
|
} catch {}
|
|
786
786
|
}
|
|
787
787
|
}
|
|
@@ -868,7 +868,7 @@ async function installSkill(cwd, manifest, files, targetTools, activation) {
|
|
|
868
868
|
}
|
|
869
869
|
|
|
870
870
|
// src/cli/commands/remove.ts
|
|
871
|
-
import { existsSync as existsSync6, statSync as statSync2, unlinkSync as unlinkSync4, rmSync as
|
|
871
|
+
import { existsSync as existsSync6, statSync as statSync2, unlinkSync as unlinkSync4, rmSync as rmSync2 } from "node:fs";
|
|
872
872
|
import { basename, join as join7 } from "node:path";
|
|
873
873
|
|
|
874
874
|
// src/cli/github.ts
|
|
@@ -992,7 +992,7 @@ function isSharedConfigFile(relPath) {
|
|
|
992
992
|
function removeFileOrDir(fullPath) {
|
|
993
993
|
const stat = statSync2(fullPath);
|
|
994
994
|
if (stat.isDirectory()) {
|
|
995
|
-
|
|
995
|
+
rmSync2(fullPath, { recursive: true, force: true });
|
|
996
996
|
} else {
|
|
997
997
|
unlinkSync4(fullPath);
|
|
998
998
|
}
|
|
@@ -1015,6 +1015,8 @@ async function removeSkill(cwd, skillName) {
|
|
|
1015
1015
|
return true;
|
|
1016
1016
|
if (config.supporting && Object.values(config.supporting).includes(f))
|
|
1017
1017
|
return true;
|
|
1018
|
+
if (config.bundleRoot && f.startsWith(`${config.bundleRoot}/`) && !isSharedConfigFile(f))
|
|
1019
|
+
return true;
|
|
1018
1020
|
if (config.mcpServers && (f.includes(".claude/settings") || f.includes(".cursor/mcp") || f.includes(".gemini/settings")))
|
|
1019
1021
|
return true;
|
|
1020
1022
|
return false;
|
|
@@ -1064,7 +1066,7 @@ async function infoSkill(skillName) {
|
|
|
1064
1066
|
}
|
|
1065
1067
|
|
|
1066
1068
|
// src/cli/commands/remove.ts
|
|
1067
|
-
import { existsSync as existsSync7, statSync as statSync3, unlinkSync as unlinkSync5, rmSync as
|
|
1069
|
+
import { existsSync as existsSync7, statSync as statSync3, unlinkSync as unlinkSync5, rmSync as rmSync3 } from "node:fs";
|
|
1068
1070
|
import { basename as basename2, join as join8 } from "node:path";
|
|
1069
1071
|
var SHARED_CONFIG_FILES2 = new Set([
|
|
1070
1072
|
".claude/settings.json",
|
|
@@ -1083,7 +1085,7 @@ function isSharedConfigFile2(relPath) {
|
|
|
1083
1085
|
function removeFileOrDir2(fullPath) {
|
|
1084
1086
|
const stat = statSync3(fullPath);
|
|
1085
1087
|
if (stat.isDirectory()) {
|
|
1086
|
-
|
|
1088
|
+
rmSync3(fullPath, { recursive: true, force: true });
|
|
1087
1089
|
} else {
|
|
1088
1090
|
unlinkSync5(fullPath);
|
|
1089
1091
|
}
|
|
@@ -1106,6 +1108,8 @@ async function removeSkill2(cwd, skillName) {
|
|
|
1106
1108
|
return true;
|
|
1107
1109
|
if (config.supporting && Object.values(config.supporting).includes(f))
|
|
1108
1110
|
return true;
|
|
1111
|
+
if (config.bundleRoot && f.startsWith(`${config.bundleRoot}/`) && !isSharedConfigFile2(f))
|
|
1112
|
+
return true;
|
|
1109
1113
|
if (config.mcpServers && (f.includes(".claude/settings") || f.includes(".cursor/mcp") || f.includes(".gemini/settings")))
|
|
1110
1114
|
return true;
|
|
1111
1115
|
return false;
|
package/package.json
CHANGED
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
|
|
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 every dispatched agent cost. Claude Code only; conflicts with the superpowers plugin.",
|
|
285
285
|
"type": "prompt",
|
|
286
|
-
"version": "1.
|
|
286
|
+
"version": "0.1.1"
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"name": "temporal-coupling-detector",
|
package/skills/sluice/SKILL.md
CHANGED
|
@@ -21,17 +21,20 @@ announcement is how your partner redirects you without being asked.
|
|
|
21
21
|
| `deep` | Several subsystems, or a plan was asked for | + written design and plan | "Deep channel, several subsystems. Design before code." |
|
|
22
22
|
|
|
23
23
|
`bypass`, `fast`, and `main` proceed without stopping for approval; only
|
|
24
|
-
`deep` stops, for design sign-off before code
|
|
24
|
+
`deep` stops, for design sign-off before code, and again for the plan when
|
|
25
|
+
agents rather than you will carry it out.
|
|
25
26
|
|
|
26
27
|
Name the channel and the signal that actually routed you there. The strings above
|
|
27
28
|
are examples, not fixed copy, and a channel with a two-part signal should say
|
|
28
29
|
which part applied. `bypass` says nothing at all, because a question that gets
|
|
29
30
|
announced stops being a question.
|
|
30
31
|
|
|
31
|
-
**`root-cause` and `
|
|
32
|
-
triggers the first: a bug report, a red test, behaviour you cannot
|
|
33
|
-
for. An integration event, merging, pushing, or opening a PR, triggers
|
|
34
|
-
second.
|
|
32
|
+
**`root-cause`, `finish` and `meter` are not channel-assigned.** The code
|
|
33
|
+
misbehaving triggers the first: a bug report, a red test, behaviour you cannot
|
|
34
|
+
account for. An integration event, merging, pushing, or opening a PR, triggers
|
|
35
|
+
the second. Handing the work back triggers the third, whether or not it ever
|
|
36
|
+
reaches an integration event. The first two fire in every channel, `bypass`
|
|
37
|
+
included; `meter` cannot, because `bypass` announces nothing to measure from.
|
|
35
38
|
|
|
36
39
|
## The rules
|
|
37
40
|
|
|
@@ -56,6 +59,10 @@ yourself wanting to skip the rule, or arguing that this one is the exception.
|
|
|
56
59
|
yours. `references/review.md`
|
|
57
60
|
- **Finish** deliberately. Green suite first, then let your partner pick
|
|
58
61
|
merge, PR, or leave it. Never pick for them. `references/finish.md`
|
|
62
|
+
- **Meter the run** as you hand it back. `scripts/run-stats.sh` reads the
|
|
63
|
+
ledger out of the session transcript: elapsed, tools, tokens, and what each
|
|
64
|
+
dispatched agent cost. A summary you assemble from memory is the one that
|
|
65
|
+
flatters you. `references/meter.md`
|
|
59
66
|
|
|
60
67
|
## Changing channel
|
|
61
68
|
|
|
@@ -70,9 +77,15 @@ Design to `docs/specs/YYYY-MM-DD-<topic>.md`, plan to
|
|
|
70
77
|
`docs/plans/YYYY-MM-DD-<topic>.md`, unless the repo has a convention or
|
|
71
78
|
your partner states a preference. Get the design signed off before code.
|
|
72
79
|
|
|
80
|
+
The plan needs sign-off as well whenever agents will execute it, because each
|
|
81
|
+
one sees only its own task and so nobody ever reads the plan whole. Executing
|
|
82
|
+
it yourself makes it a worklist instead: write it and carry on.
|
|
83
|
+
|
|
73
84
|
Then read `references/deep-channel.md` for the plan format, the dispatch rules,
|
|
74
|
-
and when a task actually needs a reviewer.
|
|
75
|
-
two implementers at once,
|
|
85
|
+
and when a task actually needs a reviewer. Three that catch people out: never
|
|
86
|
+
run two implementers at once, review is tiered rather than automatic, and a
|
|
87
|
+
`deep` run that cannot dispatch has to replace the review tier with something,
|
|
88
|
+
not quietly ship without one.
|
|
76
89
|
|
|
77
90
|
## Conflicts
|
|
78
91
|
|
|
@@ -50,6 +50,26 @@
|
|
|
50
50
|
- Match model to task: cheap for mechanical work, stronger for judgment
|
|
51
51
|
and final review.
|
|
52
52
|
|
|
53
|
+
## When dispatch is unavailable
|
|
54
|
+
|
|
55
|
+
A session may forbid subagents outright. `deep` then loses most of the
|
|
56
|
+
machinery this file describes, and the place to say so is the routing
|
|
57
|
+
announcement, where your partner can still act on it, not the summary at the
|
|
58
|
+
end where it reads as an excuse.
|
|
59
|
+
|
|
60
|
+
Three things change. The plan becomes your own worklist and needs no sign-off,
|
|
61
|
+
since you are the only one who will ever read it. Task isolation is gone, so
|
|
62
|
+
`TaskCreate` now carries all of the state that outlives compaction and matters
|
|
63
|
+
more, not less. And fresh context is unavailable, which was the entire thing
|
|
64
|
+
review was buying.
|
|
65
|
+
|
|
66
|
+
One thing does not change: the work still owes a review. Reading your own diff
|
|
67
|
+
is not one, and the table below still names which tasks needed the stronger
|
|
68
|
+
tier. List those tasks when you hand back, as review outstanding rather than
|
|
69
|
+
review passed. A `deep` run that ships with nobody having read it has become a
|
|
70
|
+
`fast` run with a design document attached, and your partner is entitled to
|
|
71
|
+
know that before they decide anything.
|
|
72
|
+
|
|
53
73
|
## Review policy
|
|
54
74
|
|
|
55
75
|
A review costs about what the implementation cost, so reviewing every task
|
|
@@ -14,8 +14,10 @@ Confirm the base branch instead of assuming it; untangling a wrong merge
|
|
|
14
14
|
costs far more than asking would have.
|
|
15
15
|
|
|
16
16
|
With the suite green and the base confirmed, put exactly three options to
|
|
17
|
-
your partner
|
|
18
|
-
|
|
17
|
+
your partner, with the run ledger above them so the choice is made against
|
|
18
|
+
what the work actually cost rather than against your account of it;
|
|
19
|
+
`references/meter.md` owns that. The three: merge it locally, push it and
|
|
20
|
+
open a PR, or leave the branch as it stands. Those three exhaust what you may offer. Your partner may still
|
|
19
21
|
ask for something off the list, most notably scrapping the branch, but the
|
|
20
22
|
request has to originate with them and be unmistakable. Then stop. Which of
|
|
21
23
|
the three it is belongs to them, however obvious the choice looks from where
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Meter the run
|
|
2
|
+
|
|
3
|
+
Run `scripts/run-stats.sh` as part of the message that hands the work back,
|
|
4
|
+
and paste its output into that message unedited:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
bash <skill-dir>/scripts/run-stats.sh --tests "<what the suite actually printed>"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
It reads the session transcript, so it needs no state of its own and cannot
|
|
11
|
+
drift from what happened. `--tests` is the one field it cannot know: only you
|
|
12
|
+
watched the suite run. Fill it from the output you read in this session, and
|
|
13
|
+
leave it off entirely if no suite ran, which prints "not reported" rather
|
|
14
|
+
than a number nobody checked. `--base <ref>` overrides the diff base when the
|
|
15
|
+
merge-base with `main` or `master` is not where this branch actually began.
|
|
16
|
+
|
|
17
|
+
The run starts at your channel announcement, not at the session, so three
|
|
18
|
+
unrelated tasks in one session report as three runs. A previous ledger closes
|
|
19
|
+
the run before it. `bypass` announces nothing, so there is nothing to meter
|
|
20
|
+
and the script exits quietly.
|
|
21
|
+
|
|
22
|
+
What the ledger is for: the cost of a channel is otherwise invisible, and a
|
|
23
|
+
channel nobody can price is a channel nobody can choose between. Six agents
|
|
24
|
+
and four hours of agent wall-clock on a two-file change is the sort of thing
|
|
25
|
+
that only becomes obvious once it is written down next to the diff. Expect
|
|
26
|
+
some runs to make the case for a shallower channel next time. That is the
|
|
27
|
+
ledger working, not the ledger complaining.
|
|
28
|
+
|
|
29
|
+
Read it before you paste it. A row showing an agent that errored, or an agent
|
|
30
|
+
whose token count dwarfs every other row, is a finding about the run and
|
|
31
|
+
belongs in your prose, not left for your partner to spot in a table.
|
|
32
|
+
|
|
33
|
+
Exit 2 means there was no run to report. That is a fact about the work, not a
|
|
34
|
+
failure: do not synthesise a ledger to fill the gap.
|
|
35
|
+
|
|
36
|
+
The friction line: "I know roughly what this cost." Roughly is the problem.
|
|
37
|
+
Every number here is already on disk, and the remembered version is reliably
|
|
38
|
+
the flattering one.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Print the run ledger: what this piece of work actually cost.
|
|
3
|
+
#
|
|
4
|
+
# The run starts at the channel announcement, not at the session, so a session
|
|
5
|
+
# holding three unrelated tasks reports each one separately. A previous ledger
|
|
6
|
+
# ends the run before it. `bypass` announces nothing and so prints nothing.
|
|
7
|
+
#
|
|
8
|
+
# Usage: run-stats.sh [--tests "<what the suite reported>"] [--base <git-ref>]
|
|
9
|
+
# [--transcript <path>]
|
|
10
|
+
#
|
|
11
|
+
# Exit 0 printed a ledger, 1 could not read a transcript, 2 no run to report.
|
|
12
|
+
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
TESTS=""
|
|
16
|
+
BASE=""
|
|
17
|
+
TRANSCRIPT=""
|
|
18
|
+
|
|
19
|
+
while [ $# -gt 0 ]; do
|
|
20
|
+
case "$1" in
|
|
21
|
+
--tests) TESTS="${2-}"; shift 2 ;;
|
|
22
|
+
--base) BASE="${2-}"; shift 2 ;;
|
|
23
|
+
--transcript) TRANSCRIPT="${2-}"; shift 2 ;;
|
|
24
|
+
-h|--help) sed -n '2,11p' "$0" | cut -c3-; exit 0 ;;
|
|
25
|
+
*) echo "run-stats: unknown argument '$1'" >&2; exit 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
30
|
+
echo "run-stats: jq is required and was not found on PATH" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if [ -z "$TRANSCRIPT" ]; then
|
|
35
|
+
sid="${CLAUDE_CODE_SESSION_ID:-}"
|
|
36
|
+
if [ -z "$sid" ]; then
|
|
37
|
+
echo "run-stats: no --transcript given and CLAUDE_CODE_SESSION_ID is unset" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
# The session id is unique across projects, so glob rather than guess the slug.
|
|
41
|
+
TRANSCRIPT="$(ls "$HOME"/.claude/projects/*/"$sid".jsonl 2>/dev/null | head -1)"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
|
|
45
|
+
echo "run-stats: transcript not found (looked for '${TRANSCRIPT:-<unresolved>}')" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
SUMMARY="$(jq -s '
|
|
50
|
+
# ---- what counts as a real turn ----------------------------------------
|
|
51
|
+
# Transcript stamps carry milliseconds, which fromdateiso8601 will not take.
|
|
52
|
+
def ts: sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601;
|
|
53
|
+
def is_meta: (.isMeta == true) or (.isSidechain == true);
|
|
54
|
+
def texts: [ .message.content[]? | select(.type == "text") | .text ] | join("\n");
|
|
55
|
+
# The channel is stated in one line before proceeding, so the announcement
|
|
56
|
+
# opens the message. Anchoring there is what separates it from prose that
|
|
57
|
+
# merely discusses a channel, which a session about sluice is full of.
|
|
58
|
+
def marker: "^[*_#>[:space:]]*(?<c>fast|main|deep)[[:space:]]+channel";
|
|
59
|
+
def announces: (.type == "assistant") and (is_meta | not)
|
|
60
|
+
and (texts | test(marker; "i"));
|
|
61
|
+
def chan: texts | capture(marker; "i") | .c | ascii_downcase;
|
|
62
|
+
def is_stats_call: (.type == "assistant") and ([
|
|
63
|
+
.message.content[]? | select(.type == "tool_use" and .name == "Bash")
|
|
64
|
+
| select((.input.command // "") | test("run-stats\\.sh"))
|
|
65
|
+
] | length > 0);
|
|
66
|
+
# Waiting is any turn the partner had to take: a prompt, or an answer to a
|
|
67
|
+
# question you put to them. Leaving the latter out understates the wait on
|
|
68
|
+
# exactly the runs that stopped to ask, which are the ones worth pricing.
|
|
69
|
+
def is_prompt: (.type == "user") and (is_meta | not)
|
|
70
|
+
and (((.message.content | type) == "string"
|
|
71
|
+
or ((.message.content | type) == "array"
|
|
72
|
+
and ([ .message.content[]? | select(.type == "tool_result") ] | length == 0)))
|
|
73
|
+
or ((.toolUseResult | type) == "object" and (.toolUseResult | has("answers"))));
|
|
74
|
+
|
|
75
|
+
. as $all
|
|
76
|
+
| [ range(0; $all | length) ] as $ix
|
|
77
|
+
| [ $ix[] | select($all[.] | announces) ] as $ann
|
|
78
|
+
| if ($ann | length) == 0 then { empty: true } else
|
|
79
|
+
|
|
80
|
+
# A previous ledger closes the run before it. The call running right now has
|
|
81
|
+
# no announcement after it, so it is skipped rather than closing this run.
|
|
82
|
+
([ $ix[] | select($all[.] | is_stats_call) ]
|
|
83
|
+
| map(select(. < ($ann | max)))
|
|
84
|
+
| if length == 0 then -1 else max end) as $boundary
|
|
85
|
+
| [ $ann[] | select(. > $boundary) ] as $ann
|
|
86
|
+
| ($ann | min) as $from
|
|
87
|
+
| [ $all[$from:][] | select(.timestamp) ] as $run
|
|
88
|
+
|
|
89
|
+
# ---- channel, and the trail if it escalated ----------------------------
|
|
90
|
+
| ([ $ann[] | $all[.] | chan ]
|
|
91
|
+
| reduce .[] as $c ([]; if (. | last) == $c then . else . + [$c] end)) as $trail
|
|
92
|
+
|
|
93
|
+
# ---- clocks ------------------------------------------------------------
|
|
94
|
+
| ($run | map(.timestamp) | min) as $start
|
|
95
|
+
| ($run | map(.timestamp) | max) as $end
|
|
96
|
+
| (($end | ts) - ($start | ts)) as $elapsed
|
|
97
|
+
| ([ range(1; $run | length)
|
|
98
|
+
| select($run[.] | is_prompt)
|
|
99
|
+
| ($run[.].timestamp | ts) - ($run[. - 1].timestamp | ts) ]
|
|
100
|
+
| add // 0) as $waiting
|
|
101
|
+
|
|
102
|
+
# ---- tools and tokens, main loop only ----------------------------------
|
|
103
|
+
| [ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
104
|
+
| .message.content[]? | select(.type == "tool_use") | .name ] as $tools
|
|
105
|
+
| ([ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
106
|
+
| .message.usage.output_tokens // 0 ] | add // 0) as $out_tok
|
|
107
|
+
| ([ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
108
|
+
| .message.usage.cache_read_input_tokens // 0 ] | add // 0) as $cache_tok
|
|
109
|
+
|
|
110
|
+
# ---- dispatched agents -------------------------------------------------
|
|
111
|
+
| ([ $run[] | select(.type == "assistant") | .message.content[]?
|
|
112
|
+
| select(.type == "tool_use" and (.name == "Agent" or .name == "Task"))
|
|
113
|
+
| { key: .id, value: (.input.description // .input.subagent_type // "agent") } ]
|
|
114
|
+
| from_entries) as $labels
|
|
115
|
+
# toolUseResult is whatever the tool returned: object, array or string.
|
|
116
|
+
| [ $run[] | select((.toolUseResult | type) == "object") | select(.toolUseResult.agentId)
|
|
117
|
+
| { label: ($labels[[ .message.content[]? | select(.type == "tool_result") | .tool_use_id ][0]] // "agent"),
|
|
118
|
+
model: (.toolUseResult.resolvedModel // "?"),
|
|
119
|
+
status: (.toolUseResult.status // "?"),
|
|
120
|
+
tokens: (.toolUseResult.totalTokens // 0),
|
|
121
|
+
ms: (.toolUseResult.totalDurationMs // 0),
|
|
122
|
+
tools: (.toolUseResult.totalToolUseCount // 0) } ] as $agents
|
|
123
|
+
|
|
124
|
+
| { empty: false,
|
|
125
|
+
trail: ($trail | join(" → ")),
|
|
126
|
+
elapsed: $elapsed, waiting: $waiting,
|
|
127
|
+
tool_total: ($tools | length),
|
|
128
|
+
tool_top: ([ $tools | group_by(.)[] | { n: length, name: (.[0] | ascii_downcase | split("__") | last) } ]
|
|
129
|
+
| sort_by(-.n) | .[0:3]),
|
|
130
|
+
out_tok: $out_tok, cache_tok: $cache_tok,
|
|
131
|
+
agents: $agents,
|
|
132
|
+
agent_tokens: ([ $agents[].tokens ] | add // 0),
|
|
133
|
+
agent_ms: ([ $agents[].ms ] | add // 0) }
|
|
134
|
+
end
|
|
135
|
+
' "$TRANSCRIPT" 2>/dev/null)"
|
|
136
|
+
|
|
137
|
+
if [ -z "$SUMMARY" ]; then
|
|
138
|
+
echo "run-stats: could not parse '$TRANSCRIPT'" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if [ "$(jq -r '.empty' <<<"$SUMMARY")" = "true" ]; then
|
|
143
|
+
exit 2
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# ---- formatting -------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
dur() { # seconds -> 1h05m | 3m11s | 42s
|
|
149
|
+
local s=${1%.*} h m
|
|
150
|
+
h=$((s / 3600)); m=$(((s % 3600) / 60)); s=$((s % 60))
|
|
151
|
+
if [ "$h" -gt 0 ]; then printf '%dh%02dm' "$h" "$m"
|
|
152
|
+
elif [ "$m" -gt 0 ]; then printf '%dm%ds' "$m" "$s"
|
|
153
|
+
else printf '%ds' "$s"; fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
tok() { # tokens -> 1.2M | 313k | 51.0k | 350
|
|
157
|
+
local n=$1
|
|
158
|
+
if [ "$n" -ge 1000000 ]; then awk -v n="$n" 'BEGIN{printf "%.1fM", n/1000000}'
|
|
159
|
+
elif [ "$n" -ge 100000 ]; then awk -v n="$n" 'BEGIN{printf "%dk", n/1000}'
|
|
160
|
+
elif [ "$n" -ge 1000 ]; then awk -v n="$n" 'BEGIN{printf "%.1fk", n/1000}'
|
|
161
|
+
else printf '%d' "$n"; fi
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
g() { jq -r "$1" <<<"$SUMMARY"; }
|
|
165
|
+
|
|
166
|
+
printf '─── run %s\n' "$(printf '─%.0s' $(seq 1 36))"
|
|
167
|
+
printf 'channel %s\n' "$(g '.trail')"
|
|
168
|
+
|
|
169
|
+
elapsed=$(g '.elapsed'); waiting=$(g '.waiting')
|
|
170
|
+
if [ "${waiting%.*}" -gt 0 ]; then
|
|
171
|
+
printf 'elapsed %s · %s of that waiting on you\n' "$(dur "$elapsed")" "$(dur "$waiting")"
|
|
172
|
+
else
|
|
173
|
+
printf 'elapsed %s\n' "$(dur "$elapsed")"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
top="$(g '[.tool_top[] | "\(.n) \(.name)"] | join(" · ")')"
|
|
177
|
+
if [ -n "$top" ]; then
|
|
178
|
+
printf 'tools %s · %s\n' "$(g '.tool_total')" "$top"
|
|
179
|
+
else
|
|
180
|
+
printf 'tools %s\n' "$(g '.tool_total')"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
printf 'tokens %s out · %s cache read\n' "$(tok "$(g '.out_tok')")" "$(tok "$(g '.cache_tok')")"
|
|
184
|
+
printf 'tests %s\n' "${TESTS:-not reported}"
|
|
185
|
+
|
|
186
|
+
# The diff is the branch as a whole against where it left the base, working
|
|
187
|
+
# tree included, because uncommitted work is still work this run produced.
|
|
188
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
189
|
+
if [ -z "$BASE" ]; then
|
|
190
|
+
for cand in main master; do
|
|
191
|
+
if git rev-parse --verify --quiet "$cand" >/dev/null 2>&1; then
|
|
192
|
+
BASE="$(git merge-base HEAD "$cand" 2>/dev/null)" && break
|
|
193
|
+
fi
|
|
194
|
+
done
|
|
195
|
+
[ -z "$BASE" ] && BASE="HEAD"
|
|
196
|
+
fi
|
|
197
|
+
stat="$(git diff --shortstat "$BASE" 2>/dev/null | sed 's/^ *//')"
|
|
198
|
+
files="$(git diff --name-only "$BASE" 2>/dev/null | wc -l | tr -d ' ')"
|
|
199
|
+
# awk, not sed: BSD sed has no \+ and silently matches nothing.
|
|
200
|
+
ins="$(awk '{for(i=1;i<=NF;i++) if($i ~ /^insertion/) print $(i-1)}' <<<"$stat")"
|
|
201
|
+
del="$(awk '{for(i=1;i<=NF;i++) if($i ~ /^deletion/) print $(i-1)}' <<<"$stat")"
|
|
202
|
+
ins=${ins:-0}; del=${del:-0}
|
|
203
|
+
|
|
204
|
+
# A file the run created is not in the diff until it is staged, and staging
|
|
205
|
+
# it here would be a side effect on someone else's index. Count it instead.
|
|
206
|
+
while IFS= read -r f; do
|
|
207
|
+
[ -n "$f" ] || continue
|
|
208
|
+
files=$((files + 1))
|
|
209
|
+
ins=$((ins + $(wc -l <"$f" 2>/dev/null || echo 0)))
|
|
210
|
+
done < <(git ls-files --others --exclude-standard 2>/dev/null)
|
|
211
|
+
|
|
212
|
+
[ "$files" -gt 0 ] && printf 'diff +%s −%s across %s files\n' "$ins" "$del" "$files"
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
count=$(g '.agents | length')
|
|
216
|
+
if [ "$count" -eq 0 ]; then
|
|
217
|
+
printf 'agents none dispatched\n'
|
|
218
|
+
else
|
|
219
|
+
printf 'agents %s dispatched · %s tok · %s wall\n' \
|
|
220
|
+
"$count" "$(tok "$(g '.agent_tokens')")" "$(dur "$(( $(g '.agent_ms') / 1000 ))")"
|
|
221
|
+
|
|
222
|
+
# Up to a dozen rows read as the narrative of the plan. Past that the order
|
|
223
|
+
# stops helping, so show what the run actually spent on and say what is cut.
|
|
224
|
+
if [ "$count" -le 12 ]; then rows='.agents[]'; else rows='(.agents | sort_by(-.tokens) | .[0:10][])'; fi
|
|
225
|
+
while IFS=$'\t' read -r label model status tokens ms tools; do
|
|
226
|
+
model="${model#claude-}"; model="$(sed 's/-[0-9]\{8\}$//' <<<"$model")"
|
|
227
|
+
[ "$status" = "completed" ] && status="" || status=" ($status)"
|
|
228
|
+
printf ' %-22.22s %-10s %6s %7s %s tools%s\n' \
|
|
229
|
+
"$label" "$model" "$(tok "$tokens")" "$(dur "$((ms / 1000))")" "$tools" "$status"
|
|
230
|
+
done < <(g "$rows | [.label, .model, .status, .tokens, .ms, .tools] | @tsv")
|
|
231
|
+
|
|
232
|
+
if [ "$count" -gt 12 ]; then
|
|
233
|
+
printf ' +%s more · %s tok (dearest 10 shown)\n' \
|
|
234
|
+
"$((count - 10))" "$(tok "$(g '(.agents | sort_by(-.tokens) | .[10:] | map(.tokens) | add) // 0')")"
|
|
235
|
+
fi
|
|
236
|
+
fi
|
package/skills/sluice/skill.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sluice",
|
|
3
|
-
"version": "1.
|
|
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
|
|
3
|
+
"version": "0.1.1",
|
|
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, and closes each run with a ledger read out of the session transcript: elapsed, tools, tokens, and what every dispatched agent cost. Claude Code only; conflicts with the superpowers plugin.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|
|
7
7
|
"tools": [
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"bundle": {
|
|
14
14
|
"include": [
|
|
15
|
-
"references"
|
|
15
|
+
"references",
|
|
16
|
+
"scripts"
|
|
16
17
|
]
|
|
17
18
|
},
|
|
18
19
|
"install": {
|