@iceinvein/agent-skills 0.7.0 → 0.8.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 +1 -1
- package/skills/index.json +3 -3
- package/skills/magpie/scripts/__tests__/cleanup-cmd.test.ts +5 -1
- package/skills/magpie/skill.json +1 -1
- package/skills/sluice/SKILL.md +27 -22
- package/skills/sluice/references/deep-channel.md +34 -25
- package/skills/sluice/references/meter.md +33 -16
- package/skills/sluice/references/review.md +6 -3
- package/skills/sluice/scripts/run-stats.sh +136 -80
- package/skills/sluice/skill.json +2 -2
package/package.json
CHANGED
package/skills/index.json
CHANGED
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"name": "magpie",
|
|
222
222
|
"description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request. Splits oversized diffs into budgeted shards and rebuilds the diff from the local clone when gh pr diff refuses it.",
|
|
223
223
|
"type": "prompt",
|
|
224
|
-
"version": "0.10.
|
|
224
|
+
"version": "0.10.1"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"name": "migrate",
|
|
@@ -281,9 +281,9 @@
|
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
"name": "sluice",
|
|
284
|
-
"description": "Routes work by change shape into four channels (bypass, fast, main, deep) and applies only the rules each channel needs, so a one-line fix does not pay the cost of a multi-subsystem build. Carries seven rules as one-liners in the router and the full treatment in references read only on friction, and closes each run with a ledger read out of the session transcript: elapsed, tools, tokens, and what
|
|
284
|
+
"description": "Routes work by change shape into four channels (bypass, fast, main, deep) and applies only the rules each channel needs, so a one-line fix does not pay the cost of a multi-subsystem build. Carries seven rules as one-liners in the router and the full treatment in references read only on friction, and closes each run with a ledger read out of the session transcript: elapsed, tools, tokens, and what each dispatched agent cost where the transcript recorded it. Claude Code only; stands down where the superpowers pipeline governs the repo.",
|
|
285
285
|
"type": "prompt",
|
|
286
|
-
"version": "0.
|
|
286
|
+
"version": "0.5.0"
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"name": "temporal-coupling-detector",
|
|
@@ -105,7 +105,11 @@ test('killServer escalates to SIGKILL when SIGTERM is ignored', async () => {
|
|
|
105
105
|
expect(result.outcome).toBe('sigkill')
|
|
106
106
|
expect(result.pid).toBe(child.pid)
|
|
107
107
|
await child.exited
|
|
108
|
-
|
|
108
|
+
// Everything asserted here is bounded: killServer polls to a 200ms deadline
|
|
109
|
+
// and then signals. The clock this test can actually run out of is subprocess
|
|
110
|
+
// startup, seven git spawns in beforeEach plus a cold `node`, which on a
|
|
111
|
+
// shared runner has overrun the 5s default and failed a release.
|
|
112
|
+
}, 30_000)
|
|
109
113
|
|
|
110
114
|
test('runCleanup logs kill outcome to log.jsonl', async () => {
|
|
111
115
|
await writeFile(join(runDir, 'log.jsonl'), '')
|
package/skills/magpie/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magpie",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request. Splits oversized diffs into budgeted shards and rebuilds the diff from the local clone when gh pr diff refuses it.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|
package/skills/sluice/SKILL.md
CHANGED
|
@@ -33,15 +33,18 @@ are examples, not fixed copy, and a channel with a two-part signal should say
|
|
|
33
33
|
which part applied. `bypass` says nothing at all, because a question that gets
|
|
34
34
|
announced stops being a question.
|
|
35
35
|
|
|
36
|
-
**`root-cause`, `finish` and `
|
|
37
|
-
misbehaving triggers the first: a bug report, a red test, behaviour you
|
|
38
|
-
account for. An integration event, merging, pushing, or opening a PR,
|
|
39
|
-
the second. Handing the work back triggers the third, whether or not it
|
|
40
|
-
reaches an integration event.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
**`root-cause`, `finish`, `meter` and `show-or-say` are not channel-assigned.**
|
|
37
|
+
The code misbehaving triggers the first: a bug report, a red test, behaviour you
|
|
38
|
+
cannot account for. An integration event, merging, pushing, or opening a PR,
|
|
39
|
+
triggers the second. Handing the work back triggers the third, whether or not it
|
|
40
|
+
ever reaches an integration event. A question that turns on how something looks
|
|
41
|
+
rather than on what it means triggers the fourth: say it where a sentence would
|
|
42
|
+
settle it, show it where the candidates have to sit side by side for an eye to
|
|
43
|
+
compare them. `references/show-or-say.md`
|
|
44
|
+
|
|
45
|
+
The first, second and fourth fire in every channel, `bypass` included, and
|
|
46
|
+
`bypass` is where the fourth comes up most. `meter` does not, because `bypass`
|
|
47
|
+
announces nothing to measure from.
|
|
45
48
|
|
|
46
49
|
## The rules
|
|
47
50
|
|
|
@@ -63,8 +66,10 @@ yourself wanting to skip the rule, or arguing that this one is the exception.
|
|
|
63
66
|
something anyone else can check. `references/verify.md`
|
|
64
67
|
- **Review** before merge. Dispatch a reviewer with fresh context and put the
|
|
65
68
|
diff on disk for it to read, so those bytes fill their context instead of
|
|
66
|
-
yours.
|
|
67
|
-
|
|
69
|
+
yours. A session that dispatches only when asked has not refused you one, so
|
|
70
|
+
ask rather than announce that you cannot. Genuinely without one? Say so in
|
|
71
|
+
the announcement and again at the merge, and nowhere in between.
|
|
72
|
+
`references/review.md`
|
|
68
73
|
- **Finish** deliberately. Green suite first, then let your partner pick
|
|
69
74
|
merge, PR, or leave it. Never pick for them. `references/finish.md`
|
|
70
75
|
- **Meter the run** as you hand it back. `scripts/run-stats.sh` reads the
|
|
@@ -85,24 +90,24 @@ Design to `docs/specs/YYYY-MM-DD-<topic>.md`, plan to
|
|
|
85
90
|
`docs/plans/YYYY-MM-DD-<topic>.md`, unless the repo has a convention or
|
|
86
91
|
your partner states a preference. Get the design signed off before code.
|
|
87
92
|
|
|
88
|
-
The
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
The second stop is the plan's, whoever executes it. Dispatched agents each see
|
|
94
|
+
only their own task, so nobody reads it whole; executing it yourself is the
|
|
95
|
+
weaker case rather than the exempt one, because then nobody reads it but its
|
|
96
|
+
author.
|
|
92
97
|
|
|
93
98
|
Order the plan so the tasks that change nothing come first, and mark the one
|
|
94
99
|
task that turns the new behaviour on. Then a late re-baseline, re-blessed
|
|
95
100
|
snapshots, regenerated fixtures, attributes to that one point instead of to
|
|
96
101
|
the branch at large.
|
|
97
102
|
|
|
98
|
-
Pre-flight rides in
|
|
103
|
+
Pre-flight rides in that same stop: which flagged tasks get a reviewer, and
|
|
99
104
|
whether the work runs in a worktree. Ask them as choices with the counts in
|
|
100
|
-
them, never as a paragraph, then end the turn
|
|
101
|
-
|
|
102
|
-
ruled dispatch out, it has made
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
actionable while the plan can still change.
|
|
105
|
+
them, never as a paragraph, then end the turn. Task 1 opens on their next
|
|
106
|
+
instruction, and an answer that already carries one is that instruction. A
|
|
107
|
+
session that dispatches only when asked has not ruled dispatch out, it has made
|
|
108
|
+
this question the place to ask; genuine unavailability is the tool not being
|
|
109
|
+
there at all, which `references/deep-channel.md` handles separately. Review that
|
|
110
|
+
turns out to be missing is only actionable while the plan can still change.
|
|
106
111
|
|
|
107
112
|
Read the plan as a graph rather than a list. `Needs` and `Offers` are
|
|
108
113
|
dependency edges and `Touches` says what cannot overlap, so which tasks may
|
|
@@ -58,15 +58,38 @@ the diff, and they will derive it wrong.
|
|
|
58
58
|
Two tasks carrying `Flips` means the plan holds two branches' worth of work.
|
|
59
59
|
Split it. A plan with none is not a `deep` plan: nothing in it does anything.
|
|
60
60
|
|
|
61
|
+
## The run record
|
|
62
|
+
|
|
63
|
+
A `deep` run outlives its own context, so what it learns has to sit on disk
|
|
64
|
+
rather than in the session. Open one file for the run before pre-flight and
|
|
65
|
+
write it as you go. It holds what a stranger resuming tomorrow would need and
|
|
66
|
+
you would otherwise be recalling: the base each task was dispatched from, each
|
|
67
|
+
task with its status and its commits, the review decisions pre-flight settled
|
|
68
|
+
and the reason each one was settled that way, and any finding belonging to a
|
|
69
|
+
task other than the one that surfaced it.
|
|
70
|
+
|
|
71
|
+
Where it goes follows the repo if the repo has a convention, and
|
|
72
|
+
`docs/plans/YYYY-MM-DD-<topic>-record.md` if it does not. It belongs to you
|
|
73
|
+
rather than to any task, so it lives in the tree you are working from, no
|
|
74
|
+
task's `Touches` names it, and you commit it yourself alongside the plan.
|
|
75
|
+
Assembling it at handback defeats it: a record written from memory is memory,
|
|
76
|
+
which is the one thing the file exists to replace.
|
|
77
|
+
|
|
78
|
+
A file only outlives compaction if you go back to it. Read it before the next
|
|
79
|
+
dispatch whenever this session has been summarised, and treat what it says over
|
|
80
|
+
what you remember, including where the two agree. Each task closes by writing
|
|
81
|
+
its commit into the record, which means asking the implementer to report the
|
|
82
|
+
SHA it committed and putting that in the row rather than deriving it later.
|
|
83
|
+
|
|
61
84
|
## Pre-flight
|
|
62
85
|
|
|
63
|
-
Design signed off, plan written, nothing built yet. Before Task 1,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
Design signed off, plan written, nothing built yet. Before Task 1, stop once
|
|
87
|
+
and settle two things with your partner. Ask them as questions with options,
|
|
88
|
+
not as a paragraph they have to reply to in prose: what you are after is a
|
|
89
|
+
decision, and a wall of considerations asks them to extract the decision from
|
|
90
|
+
it first.
|
|
68
91
|
|
|
69
|
-
This
|
|
92
|
+
This stop is the plan's sign-off as well, so it ends your turn, and a
|
|
70
93
|
question tool does not end it for you. That tool returns an answer without
|
|
71
94
|
returning control: two options came back, the plan itself did not, and your
|
|
72
95
|
partner reads the summary of it in the same message as Task 1's first edit,
|
|
@@ -99,29 +122,14 @@ repo, a lint target that resolves through a symlink, a build that needs its own
|
|
|
99
122
|
install, belongs in the option text where it can affect the answer, not in a
|
|
100
123
|
footnote after they have chosen.
|
|
101
124
|
|
|
102
|
-
If one of the two has only one live answer, say which and ask the other. A
|
|
103
|
-
|
|
125
|
+
If one of the two has only one live answer, say which and ask the other. A stop
|
|
126
|
+
down to a single question is still a stop.
|
|
104
127
|
|
|
105
128
|
A session that forbids subagents does not skip this; it changes what the
|
|
106
129
|
review options are. Skipping it is how "review outstanding" first appears in
|
|
107
130
|
the closing summary, at the one moment your partner can no longer do anything
|
|
108
131
|
about it.
|
|
109
132
|
|
|
110
|
-
## The run record
|
|
111
|
-
|
|
112
|
-
A `deep` run outlives its own context, so what it learns has to sit on disk
|
|
113
|
-
rather than in the session. Keep one file for the run and write it as you go.
|
|
114
|
-
It holds what a stranger resuming tomorrow would need and you would otherwise
|
|
115
|
-
be recalling: the base each task was dispatched from, each task with its status
|
|
116
|
-
and its commits, the review decisions pre-flight settled and the reason each
|
|
117
|
-
one was settled that way, and any finding belonging to a task other than the
|
|
118
|
-
one that surfaced it.
|
|
119
|
-
|
|
120
|
-
Where it goes follows the repo if the repo has a convention, and
|
|
121
|
-
`docs/plans/YYYY-MM-DD-<topic>-record.md` if it does not. Assembling it at
|
|
122
|
-
handback defeats it: a record written from memory is memory, which is the one
|
|
123
|
-
thing the file exists to replace.
|
|
124
|
-
|
|
125
133
|
## Dispatch rules
|
|
126
134
|
|
|
127
135
|
Read the plan as a graph before you read it as a list. `Needs` and `Offers`
|
|
@@ -184,8 +192,9 @@ say so is the routing announcement, where your partner can still act on it,
|
|
|
184
192
|
not the summary at the end where it reads as an excuse.
|
|
185
193
|
|
|
186
194
|
Three things change. The plan stops being a brief for strangers and becomes
|
|
187
|
-
your own worklist,
|
|
188
|
-
|
|
195
|
+
your own worklist, so its stop is no longer buying alignment with the agents
|
|
196
|
+
who will carry it out, only your partner's read of work you will do yourself.
|
|
197
|
+
Task isolation is gone, so
|
|
189
198
|
the run record now carries all of the state that outlives compaction and
|
|
190
199
|
matters more, not less. And fresh context is unavailable, which was the entire thing
|
|
191
200
|
review was buying.
|
|
@@ -14,10 +14,13 @@ leave it off entirely if no suite ran, which prints "not reported" rather
|
|
|
14
14
|
than a number nobody checked. `--base <ref>` overrides the diff base when the
|
|
15
15
|
merge-base with `main` or `master` is not where this branch actually began.
|
|
16
16
|
|
|
17
|
-
The run starts at your channel announcement,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
The run starts at your channel announcement, or at the point this skill was
|
|
18
|
+
invoked by name when that came first, not at the session. Three unrelated tasks
|
|
19
|
+
in one session therefore report as three runs, and a previous ledger closes the
|
|
20
|
+
run before it. `bypass` announces nothing, so there is nothing there to meter,
|
|
21
|
+
and the rule does not fire; if you run the script anyway in a session that
|
|
22
|
+
loaded the skill, it will happily print a ledger headed `not announced`, which
|
|
23
|
+
is a measurement of nothing you were asked to account for.
|
|
21
24
|
|
|
22
25
|
What the ledger is for: the cost of a channel is otherwise invisible, and a
|
|
23
26
|
channel nobody can price is a channel nobody can choose between. Six agents
|
|
@@ -31,26 +34,40 @@ summed, over the span those agents actually occupied. 1.0× means every agent
|
|
|
31
34
|
had the clock to itself. That is the right number for a plan whose graph was a
|
|
32
35
|
chain and a finding for one whose graph was not, so read it against the plan
|
|
33
36
|
rather than on its own. Nine agents at 1.0× on a plan with four independent
|
|
34
|
-
tasks in it is a run that took four times longer than it needed to.
|
|
37
|
+
tasks in it is a run that took four times longer than it needed to. Both
|
|
38
|
+
figures cover only the agents the harness timed, so a run whose agents were all
|
|
39
|
+
dispatched into the background shows neither.
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
Agents are priced from two different sources, and the line keeps them apart
|
|
42
|
+
rather than adding them. Where the harness priced an agent itself, that is the
|
|
43
|
+
figure: it is the accounting the session was billed by and it covers input as
|
|
44
|
+
well as output. An agent dispatched into the background is handed back before
|
|
45
|
+
it runs, so the harness never priced it at all, and the only record left is the
|
|
46
|
+
agent's own transcript. Those rows are marked `~`, count output tokens only,
|
|
47
|
+
and carry no wall-clock, because a log holds no duration: its span runs from
|
|
48
|
+
first message to last and swallows the idle whenever an agent was resumed.
|
|
49
|
+
Adding a `~` figure to a harness figure would produce a number that means
|
|
50
|
+
nothing, which is why you will see two totals on one line and never one.
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
An agent nothing priced reads "cost not reported" and the totals leave it out,
|
|
53
|
+
with the count on the agent line. That is a gap in what could be measured
|
|
54
|
+
rather than a cheap run, and it needs a sentence from you: a smaller total must
|
|
55
|
+
not be allowed to stand for the whole.
|
|
46
56
|
|
|
47
57
|
Read it before you paste it. A row showing an agent that errored, or an agent
|
|
48
58
|
whose token count dwarfs every other row, is a finding about the run and
|
|
49
|
-
belongs in your prose, not left for your partner to spot in a table.
|
|
59
|
+
belongs in your prose, not left for your partner to spot in a table. A row
|
|
60
|
+
whose figure is missing or marked is a finding about the ledger, and saying
|
|
61
|
+
which of the two you are looking at is the whole job here.
|
|
50
62
|
|
|
51
63
|
Exit 2 means neither an announcement nor an invocation of this skill was found,
|
|
52
64
|
so there was no run to report. That is a fact about the work, not a failure: do
|
|
53
|
-
not synthesise a ledger to fill the gap.
|
|
65
|
+
not synthesise a ledger to fill the gap. It can also mean your announcement was
|
|
66
|
+
worded in a shape the script does not recognise, so check that it is not simply
|
|
67
|
+
missing you before you report the run as nothing. Any other non-zero exit is a
|
|
68
|
+
broken tool, not a fact about the work: it prints its reason on stderr, and the
|
|
69
|
+
answer is to say the ledger could not be produced rather than to describe the
|
|
70
|
+
run from memory.
|
|
54
71
|
|
|
55
72
|
The friction line: "I know roughly what this cost." Roughly is the problem.
|
|
56
73
|
Every number here is already on disk, and the remembered version is reliably
|
|
@@ -21,9 +21,12 @@ wrong still gets raised, then argued.
|
|
|
21
21
|
|
|
22
22
|
Every finding has one of two destinations. Either it goes back to the agent
|
|
23
23
|
that wrote the code and the work stays where it is until the fix lands, or it
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
is written down against the task and waits. In `deep` that is the run record
|
|
25
|
+
`references/deep-channel.md` sets up, and it is what the final review reads. In
|
|
26
|
+
`main` there is no plan and no record, so a finding that waits waits in the
|
|
27
|
+
message you hand back, named there rather than carried silently to the merge.
|
|
28
|
+
Reviewers grade findings Critical, Important or Minor, and the grade picks
|
|
29
|
+
between the two: only Minor may wait, and anything above it blocks.
|
|
27
30
|
|
|
28
31
|
The grade is the sort, and it has to be load-bearing. The alternative is you
|
|
29
32
|
reading every finding to decide where it goes, which is an adjudication pass
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
# Usage: run-stats.sh [--tests "<what the suite reported>"] [--base <git-ref>]
|
|
9
9
|
# [--transcript <path>]
|
|
10
10
|
#
|
|
11
|
-
# Exit 0 printed a ledger,
|
|
11
|
+
# Exit 0 printed a ledger, 2 no run to report. Any other non-zero is the tool
|
|
12
|
+
# failing rather than a fact about the run: no jq, no readable transcript, an
|
|
13
|
+
# unknown argument, an unresolvable session id. Each prints its reason first.
|
|
12
14
|
|
|
13
15
|
set -uo pipefail
|
|
14
16
|
|
|
@@ -46,32 +48,43 @@ if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then
|
|
|
46
48
|
exit 1
|
|
47
49
|
fi
|
|
48
50
|
|
|
49
|
-
# An agent
|
|
51
|
+
# An agent the harness never priced leaves no cost on its tool result, but it
|
|
50
52
|
# keeps a transcript of its own beside the session. That file is the only place
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
+
# such an agent is ever written down, so read it rather than reporting the work
|
|
54
|
+
# as free. Only ids this transcript actually mentions are read: a long-lived
|
|
55
|
+
# session accumulates far more logs than any one run dispatched.
|
|
56
|
+
#
|
|
57
|
+
# No duration is taken from a log. Its span is first timestamp to last, which
|
|
58
|
+
# covers the idle between rounds whenever the agent was resumed, and that is
|
|
59
|
+
# not a measure of work. Tokens are summed per message rather than per line,
|
|
60
|
+
# because one message is written as several lines carrying the same cumulative
|
|
61
|
+
# usage, and summing the lines counts the message more than once.
|
|
53
62
|
SUBS="$(dirname "$TRANSCRIPT")/$(basename "$TRANSCRIPT" .jsonl)/subagents"
|
|
54
63
|
COSTS='{}'
|
|
55
64
|
if [ -d "$SUBS" ]; then
|
|
56
65
|
COSTS="$(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
jq -r 'select((.toolUseResult | type) == "object")
|
|
67
|
+
| .toolUseResult.agentId // empty' "$TRANSCRIPT" 2>/dev/null | sort -u |
|
|
68
|
+
while IFS= read -r id; do
|
|
69
|
+
[ -n "$id" ] || continue
|
|
70
|
+
f="$SUBS/agent-$id.jsonl"
|
|
71
|
+
[ -f "$f" ] || continue
|
|
60
72
|
jq -s --arg id "$id" '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
tokens: ([
|
|
73
|
+
[ .[] | select(.message.usage) ] as $u
|
|
74
|
+
| if ($u | length) == 0 then {} else
|
|
75
|
+
{ ($id): {
|
|
76
|
+
tokens: ([ $u[] | { k: (.message.id // "?"),
|
|
77
|
+
v: (.message.usage.output_tokens // 0) } ]
|
|
78
|
+
| group_by(.k) | map(map(.v) | max) | add // 0),
|
|
65
79
|
tools: ([ .[] | .message.content[]? | select(.type == "tool_use") ] | length),
|
|
66
|
-
model: ([ .[] | .message.model // empty ] | last // "?")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
done | jq -s 'add // {}'
|
|
80
|
+
model: ([ .[] | .message.model // empty ] | last // "?") } }
|
|
81
|
+
end' "$f" 2>/dev/null
|
|
82
|
+
done | jq -s 'add // {}' 2>/dev/null
|
|
70
83
|
)"
|
|
71
84
|
fi
|
|
72
85
|
[ -n "$COSTS" ] || COSTS='{}'
|
|
73
86
|
|
|
74
|
-
SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
87
|
+
SUMMARY="$(jq -s --argjson costs "$COSTS" --arg sid "$(basename "$TRANSCRIPT" .jsonl)" '
|
|
75
88
|
# ---- what counts as a real turn ----------------------------------------
|
|
76
89
|
# Transcript stamps carry milliseconds, which fromdateiso8601 will not take.
|
|
77
90
|
def ts: sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601;
|
|
@@ -82,26 +95,30 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
82
95
|
# merely discusses a channel, which a session about sluice is full of.
|
|
83
96
|
def marker: "^[*_#>[:space:]]*(?<c>fast|main|deep)[[:space:]]+channel";
|
|
84
97
|
# Real announcements carry a lead-in the anchor above misses: "Sluice: **deep
|
|
85
|
-
# channel**", "Tier 2 (new contract surface) = **deep channel**".
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
# channel**", "Tier 2 (new contract surface) = **deep channel**". What makes
|
|
99
|
+
# those announcements and not prose is that a label introduces them, so the
|
|
100
|
+
# channel has to follow a colon or an equals directly. Emphasis alone will not
|
|
101
|
+
# do: a closing ** reads the same as an opening one, which let "not a **big**
|
|
102
|
+
# deep channel job" announce. The lead-in cannot cross a sentence or a line,
|
|
103
|
+
# which is what keeps a session reviewing sluice from starting a run per quote.
|
|
104
|
+
def lead: "^[^.!?\n]{0,100}[:=][[:space:]]*[*_]*(?<c>fast|main|deep)[[:space:]]+channel";
|
|
90
105
|
def announces: (.type == "assistant") and (is_meta | not)
|
|
91
|
-
and ((texts | test(marker; "i")) or (
|
|
106
|
+
and ((texts | test(marker; "i")) or (texts | test(lead; "i")));
|
|
92
107
|
def chan: (if (texts | test(marker; "i")) then (texts | capture(marker; "i"))
|
|
93
|
-
else (
|
|
108
|
+
else (texts | capture(lead; "i")) end) | .c | ascii_downcase;
|
|
94
109
|
def invokes_sluice: (.type == "assistant") and (is_meta | not) and ([
|
|
95
110
|
.message.content[]? | select(.type == "tool_use" and .name == "Skill")
|
|
96
111
|
| select((.input.skill // "") == "sluice")
|
|
97
112
|
] | length > 0);
|
|
98
|
-
# A call carrying --transcript is reading another run rather than closing
|
|
99
|
-
#
|
|
100
|
-
#
|
|
113
|
+
# A call carrying --transcript is reading another run rather than closing this
|
|
114
|
+
# one, unless the transcript it names is this session, which is a documented
|
|
115
|
+
# and legitimate way to meter yourself. Without the exception, a session
|
|
116
|
+
# working on the ledger clips its own run at the last session it tested.
|
|
101
117
|
def is_stats_call: (.type == "assistant") and ([
|
|
102
118
|
.message.content[]? | select(.type == "tool_use" and .name == "Bash")
|
|
103
|
-
|
|
|
104
|
-
| select(
|
|
119
|
+
| (.input.command // "") as $cmd
|
|
120
|
+
| select($cmd | test("run-stats\\.sh"))
|
|
121
|
+
| select(($cmd | test("--transcript") | not) or ($cmd | test($sid)))
|
|
105
122
|
] | length > 0);
|
|
106
123
|
# Waiting is any turn the partner had to take: a prompt, or an answer to a
|
|
107
124
|
# question you put to them. Leaving the latter out understates the wait on
|
|
@@ -116,9 +133,11 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
116
133
|
| [ range(0; $all | length) ] as $ix
|
|
117
134
|
| [ $ix[] | select($all[.] | announces) ] as $said
|
|
118
135
|
# A run whose announcement never matched is still a run when the skill was
|
|
119
|
-
# invoked by name.
|
|
136
|
+
# invoked by name. Both kinds of anchor go in together rather than the
|
|
137
|
+
# invocations being a fallback for the whole file: a session where run 1
|
|
138
|
+
# announced and run 2 did not still has to report run 2 on its own.
|
|
120
139
|
| [ $ix[] | select($all[.] | invokes_sluice) ] as $called
|
|
121
|
-
| (
|
|
140
|
+
| (($said + $called) | sort) as $ann
|
|
122
141
|
| if ($ann | length) == 0 then { empty: true } else
|
|
123
142
|
|
|
124
143
|
# A previous ledger closes the run before it. The call running right now has
|
|
@@ -146,10 +165,18 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
146
165
|
# ---- tools and tokens, main loop only ----------------------------------
|
|
147
166
|
| [ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
148
167
|
| .message.content[]? | select(.type == "tool_use") | .name ] as $tools
|
|
168
|
+
# One message is written as several lines, each repeating the same cumulative
|
|
169
|
+
# usage, so these are summed per message rather than per line. Summed per line
|
|
170
|
+
# the figure for this run roughly doubles, and it is the one most likely to
|
|
171
|
+
# be read.
|
|
149
172
|
| ([ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
150
|
-
| .message.usage
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
| select(.message.usage)
|
|
174
|
+
| { k: (.message.id // .uuid // "?"),
|
|
175
|
+
o: (.message.usage.output_tokens // 0),
|
|
176
|
+
c: (.message.usage.cache_read_input_tokens // 0) } ]
|
|
177
|
+
| group_by(.k)) as $usage
|
|
178
|
+
| ([ $usage[] | map(.o) | max ] | add // 0) as $out_tok
|
|
179
|
+
| ([ $usage[] | map(.c) | max ] | add // 0) as $cache_tok
|
|
153
180
|
|
|
154
181
|
# ---- dispatched agents -------------------------------------------------
|
|
155
182
|
| ([ $run[] | select(.type == "assistant") | .message.content[]?
|
|
@@ -159,37 +186,44 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
159
186
|
# toolUseResult is whatever the tool returned: object, array or string.
|
|
160
187
|
| [ $run[] | select((.toolUseResult | type) == "object") | select(.toolUseResult.agentId)
|
|
161
188
|
| . as $e
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
189
|
+
# Where the harness priced the agent, that is the number: it is the
|
|
190
|
+
# accounting the session itself was billed by, and it covers input as well
|
|
191
|
+
# as output. The log is the fallback for agents the harness never priced,
|
|
192
|
+
# and it holds output tokens only, so the two are different units and the
|
|
193
|
+
# totals below never add one to the other.
|
|
166
194
|
| ($costs[$e.toolUseResult.agentId] // null) as $c
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
| ($
|
|
174
|
-
| { label: ($labels[
|
|
175
|
-
|
|
176
|
-
model: ($c.model // $e.toolUseResult.resolvedModel // "?"),
|
|
195
|
+
| (if ($e.toolUseResult.totalTokens != null)
|
|
196
|
+
or ($e.toolUseResult.totalDurationMs != null) then "inline"
|
|
197
|
+
elif $c != null then "log"
|
|
198
|
+
else "none" end) as $src
|
|
199
|
+
# Indexing with null throws, and a result without a tool_result block is
|
|
200
|
+
# not worth failing the whole ledger over.
|
|
201
|
+
| (([ $e.message.content[]? | select(.type == "tool_result") | .tool_use_id ][0]) // "") as $tid
|
|
202
|
+
| { label: ($labels[$tid] // $e.toolUseResult.commandName // "agent"),
|
|
203
|
+
src: $src,
|
|
177
204
|
status: ($e.toolUseResult.status // "?"),
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
205
|
+
# Model, tool count and cost all come from whichever source priced the
|
|
206
|
+
# agent, so a row never mixes one source with another.
|
|
207
|
+
model: (if $src == "log" then $c.model
|
|
208
|
+
else $e.toolUseResult.resolvedModel end // "?"),
|
|
209
|
+
tokens: (if $src == "log" then $c.tokens
|
|
210
|
+
else $e.toolUseResult.totalTokens end // 0),
|
|
211
|
+
tools: (if $src == "log" then $c.tools
|
|
212
|
+
else $e.toolUseResult.totalToolUseCount end // 0),
|
|
213
|
+
ms: ($e.toolUseResult.totalDurationMs // 0),
|
|
181
214
|
# The session transcript timestamps when an agent returned, not when
|
|
182
|
-
# it began, so
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
else (($e.timestamp | ts) - (($ms // 0) / 1000)) end),
|
|
187
|
-
tools: ($c.tools // $e.toolUseResult.totalToolUseCount // 0) } ] as $agents
|
|
215
|
+
# it began, so the start is back-derived from its own duration.
|
|
216
|
+
ends: ($e.timestamp | ts),
|
|
217
|
+
starts: (($e.timestamp | ts)
|
|
218
|
+
- (($e.toolUseResult.totalDurationMs // 0) / 1000)) } ] as $agents
|
|
188
219
|
|
|
189
|
-
| [ $agents[] | select(.
|
|
220
|
+
| [ $agents[] | select(.src == "inline") ] as $priced
|
|
221
|
+
| [ $agents[] | select(.src == "log") ] as $logged
|
|
222
|
+
| [ $agents[] | select(.src == "none") ] as $unpriced
|
|
190
223
|
|
|
191
224
|
# Union of the agent intervals: sum the merged runs rather than the raw ones,
|
|
192
|
-
# so overlapping agents are counted once against the clock they shared.
|
|
225
|
+
# so overlapping agents are counted once against the clock they shared. Only
|
|
226
|
+
# agents the harness timed are in here; a log carries no duration to add.
|
|
193
227
|
| ([ $priced[] | { s: .starts, e: .ends } ] | sort_by(.s)
|
|
194
228
|
| reduce .[] as $i ([];
|
|
195
229
|
if (length == 0) or (.[-1].e < $i.s)
|
|
@@ -205,8 +239,11 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
205
239
|
| sort_by(-.n) | .[0:3]),
|
|
206
240
|
out_tok: $out_tok, cache_tok: $cache_tok,
|
|
207
241
|
agents: $agents,
|
|
208
|
-
|
|
242
|
+
priced_count: ($priced | length),
|
|
243
|
+
logged_count: ($logged | length),
|
|
244
|
+
unpriced_count: ($unpriced | length),
|
|
209
245
|
agent_tokens: ([ $priced[].tokens ] | add // 0),
|
|
246
|
+
logged_tokens: ([ $logged[].tokens ] | add // 0),
|
|
210
247
|
agent_ms: ([ $priced[].ms ] | add // 0),
|
|
211
248
|
concurrency: (if $agent_span > 0
|
|
212
249
|
then (([ $priced[].ms ] | add // 0) / 1000) / $agent_span
|
|
@@ -293,41 +330,60 @@ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
293
330
|
fi
|
|
294
331
|
|
|
295
332
|
count=$(g '.agents | length')
|
|
296
|
-
|
|
333
|
+
priced=$(g '.priced_count')
|
|
334
|
+
logged=$(g '.logged_count')
|
|
335
|
+
unpriced=$(g '.unpriced_count')
|
|
297
336
|
if [ "$count" -eq 0 ]; then
|
|
298
337
|
printf 'agents none dispatched\n'
|
|
299
338
|
else
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
# and none to invent. Saying so beats a row of zeroes that reads as a total.
|
|
303
|
-
printf 'agents %s dispatched · cost not reported\n' "$count"
|
|
304
|
-
else
|
|
339
|
+
printf 'agents %s dispatched' "$count"
|
|
340
|
+
if [ "$priced" -gt 0 ]; then
|
|
305
341
|
# 1.0× means every agent had the clock to itself. On a plan whose graph had
|
|
306
342
|
# independent tasks in it, that number is the finding.
|
|
307
|
-
printf '
|
|
308
|
-
"$
|
|
343
|
+
printf ' · %s tok · %s wall · %s× concurrent' \
|
|
344
|
+
"$(tok "$(g '.agent_tokens')")" "$(dur "$(( $(g '.agent_ms') / 1000 ))")" \
|
|
309
345
|
"$(printf '%.1f' "$(g '.concurrency')")"
|
|
310
|
-
[ "$unmeasured" -gt 0 ] && printf ' · %s unmeasured' "$unmeasured"
|
|
311
|
-
printf '\n'
|
|
312
346
|
fi
|
|
347
|
+
if [ "$logged" -gt 1 ]; then
|
|
348
|
+
printf ' · %s out from %s logs' "$(tok "$(g '.logged_tokens')")" "$logged"
|
|
349
|
+
elif [ "$logged" -eq 1 ]; then
|
|
350
|
+
printf ' · %s out from 1 log' "$(tok "$(g '.logged_tokens')")"
|
|
351
|
+
fi
|
|
352
|
+
if [ "$priced" -eq 0 ] && [ "$logged" -eq 0 ]; then
|
|
353
|
+
# Nothing priced any of them, so there is no cost to report and none to
|
|
354
|
+
# invent. Saying so beats a row of zeroes that reads as a total, and beats
|
|
355
|
+
# a count of unpriced agents when the count is all of them.
|
|
356
|
+
printf ' · cost not reported'
|
|
357
|
+
elif [ "$unpriced" -gt 0 ]; then
|
|
358
|
+
printf ' · %s unpriced' "$unpriced"
|
|
359
|
+
fi
|
|
360
|
+
printf '\n'
|
|
313
361
|
|
|
314
362
|
# Up to a dozen rows read as the narrative of the plan. Past that the order
|
|
315
363
|
# stops helping, so show what the run actually spent on and say what is cut.
|
|
316
364
|
if [ "$count" -le 12 ]; then rows='.agents[]'; else rows='(.agents | sort_by(-.tokens) | .[0:10][])'; fi
|
|
317
|
-
while IFS=$'\t' read -r label model status tokens ms tools
|
|
365
|
+
while IFS=$'\t' read -r label model status tokens ms tools src; do
|
|
318
366
|
model="${model#claude-}"; model="$(sed 's/-[0-9]\{8\}$//' <<<"$model")"
|
|
319
367
|
[ "$status" = "completed" ] && status="" || status=" ($status)"
|
|
320
|
-
|
|
321
|
-
printf ' %-22.22s %-10s %6s %7s %s tools%s\n' \
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
368
|
+
case "$src" in
|
|
369
|
+
inline) printf ' %-22.22s %-10s %6s %7s %s tools%s\n' \
|
|
370
|
+
"$label" "$model" "$(tok "$tokens")" "$(dur "$((ms / 1000))")" "$tools" "$status" ;;
|
|
371
|
+
# A tilde marks the other unit, and the dash is the duration a log cannot
|
|
372
|
+
# give. Both are explained by the footnote below.
|
|
373
|
+
log) printf ' %-22.22s %-10s %6s %7s %s tools%s\n' \
|
|
374
|
+
"$label" "$model" "~$(tok "$tokens")" "-" "$tools" "$status" ;;
|
|
375
|
+
*) printf ' %-22.22s %-10s %6s %7s %s%s\n' \
|
|
376
|
+
"$label" "$model" "-" "-" "cost not reported" "$status" ;;
|
|
377
|
+
esac
|
|
378
|
+
done < <(g "$rows | [.label, .model, .status, .tokens, .ms, .tools, .src] | @tsv")
|
|
379
|
+
|
|
380
|
+
[ "$logged" -gt 0 ] && printf ' ~ output tokens from the agent log; the harness never priced these\n'
|
|
328
381
|
|
|
329
382
|
if [ "$count" -gt 12 ]; then
|
|
330
|
-
|
|
331
|
-
|
|
383
|
+
cut_priced="$(g '(.agents | sort_by(-.tokens) | .[10:] | map(select(.src == "inline") | .tokens) | add) // 0')"
|
|
384
|
+
cut_unpriced="$(g '(.agents | sort_by(-.tokens) | .[10:] | map(select(.src != "inline")) | length)')"
|
|
385
|
+
printf ' +%s more · %s tok (dearest 10 shown)' "$((count - 10))" "$(tok "$cut_priced")"
|
|
386
|
+
[ "$cut_unpriced" -gt 0 ] && printf ', %s of them unpriced' "$cut_unpriced"
|
|
387
|
+
printf '\n'
|
|
332
388
|
fi
|
|
333
389
|
fi
|
package/skills/sluice/skill.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sluice",
|
|
3
|
-
"version": "0.
|
|
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
|
|
3
|
+
"version": "0.5.0",
|
|
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 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",
|
|
7
7
|
"tools": [
|