@iceinvein/agent-skills 0.6.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 +29 -24
- package/skills/sluice/references/deep-channel.md +42 -18
- package/skills/sluice/references/meter.md +33 -16
- package/skills/sluice/references/review.md +6 -3
- package/skills/sluice/scripts/run-stats.sh +136 -76
- 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,32 +90,32 @@ 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
|
|
109
114
|
run at once is derivable rather than guessed. Fan out wherever that graph
|
|
110
115
|
allows; serial is the fallback for where it doesn't, not the default.
|
|
111
116
|
|
|
112
|
-
Then read `references/deep-channel.md` for the plan format, that stop,
|
|
113
|
-
the dispatch rules, and when a task actually needs a reviewer. Three that
|
|
117
|
+
Then read `references/deep-channel.md` for the plan format, that stop, the run
|
|
118
|
+
record, the dispatch rules, and when a task actually needs a reviewer. Three that
|
|
114
119
|
catch people out: concurrent implementers need a worktree each and the flip
|
|
115
120
|
runs alone, review is tiered rather than automatic, and a `deep` run that
|
|
116
121
|
cannot dispatch has to replace the review tier with something, not quietly
|
|
@@ -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,8 +122,8 @@ 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
|
|
@@ -121,8 +144,8 @@ Derive the sets at dispatch rather than writing wave numbers into the plan. A
|
|
|
121
144
|
declared schedule is wrong the moment one task lands late or comes back with a
|
|
122
145
|
blocking finding. A derived one just recomputes.
|
|
123
146
|
|
|
124
|
-
- One
|
|
125
|
-
outlives compaction; your memory doesn't.
|
|
147
|
+
- One line per task in the run record, marked in progress then complete as it
|
|
148
|
+
moves. That state outlives compaction; your memory doesn't.
|
|
126
149
|
- Each task goes to a fresh agent with that task's text and nothing else.
|
|
127
150
|
What this session accumulated is yours to hold, not theirs.
|
|
128
151
|
- **Fan out wherever the graph allows.** Work that does not write is always
|
|
@@ -169,10 +192,11 @@ say so is the routing announcement, where your partner can still act on it,
|
|
|
169
192
|
not the summary at the end where it reads as an excuse.
|
|
170
193
|
|
|
171
194
|
Three things change. The plan stops being a brief for strangers and becomes
|
|
172
|
-
your own worklist,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
198
|
+
the run record now carries all of the state that outlives compaction and
|
|
199
|
+
matters more, not less. And fresh context is unavailable, which was the entire thing
|
|
176
200
|
review was buying.
|
|
177
201
|
|
|
178
202
|
One thing does not change: the work still owes a review. Reading your own diff
|
|
@@ -225,8 +249,8 @@ a reviewer writes nothing, so it collides with nothing. The final review is
|
|
|
225
249
|
the only one that waits, because it is the only one that needs everything to
|
|
226
250
|
have landed.
|
|
227
251
|
|
|
228
|
-
Record the base in the
|
|
229
|
-
agent's first commit lands. Recovering it afterwards is archaeology, and the
|
|
252
|
+
Record the base against that task in the run record when you dispatch, before
|
|
253
|
+
the agent's first commit lands. Recovering it afterwards is archaeology, and the
|
|
230
254
|
answer you will guess at is `HEAD~1`, which `references/review.md` already
|
|
231
255
|
names as the standing mistake.
|
|
232
256
|
|
|
@@ -260,8 +284,8 @@ Contracts mean tasks are built blind, so a reviewer reading Task 6 will
|
|
|
260
284
|
sometimes be right about Task 2. That finding fits neither destination: Task
|
|
261
285
|
6's agent cannot act on it, and it does not quietly become yours to fix.
|
|
262
286
|
|
|
263
|
-
Record it
|
|
264
|
-
agent as well if that agent is still live. If it invalidates an `Offers` that
|
|
287
|
+
Record it against the earlier task in the run record, and send it to that
|
|
288
|
+
task's agent as well if that agent is still live. If it invalidates an `Offers` that
|
|
265
289
|
later tasks have already built against, it has stopped being a finding and
|
|
266
290
|
become a plan change, which is your partner's call rather than something to
|
|
267
291
|
absorb into the next task's brief.
|
|
@@ -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,22 +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);
|
|
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.
|
|
98
117
|
def is_stats_call: (.type == "assistant") and ([
|
|
99
118
|
.message.content[]? | select(.type == "tool_use" and .name == "Bash")
|
|
100
|
-
|
|
|
119
|
+
| (.input.command // "") as $cmd
|
|
120
|
+
| select($cmd | test("run-stats\\.sh"))
|
|
121
|
+
| select(($cmd | test("--transcript") | not) or ($cmd | test($sid)))
|
|
101
122
|
] | length > 0);
|
|
102
123
|
# Waiting is any turn the partner had to take: a prompt, or an answer to a
|
|
103
124
|
# question you put to them. Leaving the latter out understates the wait on
|
|
@@ -112,9 +133,11 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
112
133
|
| [ range(0; $all | length) ] as $ix
|
|
113
134
|
| [ $ix[] | select($all[.] | announces) ] as $said
|
|
114
135
|
# A run whose announcement never matched is still a run when the skill was
|
|
115
|
-
# 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.
|
|
116
139
|
| [ $ix[] | select($all[.] | invokes_sluice) ] as $called
|
|
117
|
-
| (
|
|
140
|
+
| (($said + $called) | sort) as $ann
|
|
118
141
|
| if ($ann | length) == 0 then { empty: true } else
|
|
119
142
|
|
|
120
143
|
# A previous ledger closes the run before it. The call running right now has
|
|
@@ -142,10 +165,18 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
142
165
|
# ---- tools and tokens, main loop only ----------------------------------
|
|
143
166
|
| [ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
144
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.
|
|
145
172
|
| ([ $run[] | select((.type == "assistant") and (is_meta | not))
|
|
146
|
-
| .message.usage
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
149
180
|
|
|
150
181
|
# ---- dispatched agents -------------------------------------------------
|
|
151
182
|
| ([ $run[] | select(.type == "assistant") | .message.content[]?
|
|
@@ -155,37 +186,44 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
155
186
|
# toolUseResult is whatever the tool returned: object, array or string.
|
|
156
187
|
| [ $run[] | select((.toolUseResult | type) == "object") | select(.toolUseResult.agentId)
|
|
157
188
|
| . as $e
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
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.
|
|
162
194
|
| ($costs[$e.toolUseResult.agentId] // null) as $c
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
| ($
|
|
170
|
-
| { label: ($labels[
|
|
171
|
-
|
|
172
|
-
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,
|
|
173
204
|
status: ($e.toolUseResult.status // "?"),
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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),
|
|
177
214
|
# The session transcript timestamps when an agent returned, not when
|
|
178
|
-
# it began, so
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
else (($e.timestamp | ts) - (($ms // 0) / 1000)) end),
|
|
183
|
-
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
|
|
184
219
|
|
|
185
|
-
| [ $agents[] | select(.
|
|
220
|
+
| [ $agents[] | select(.src == "inline") ] as $priced
|
|
221
|
+
| [ $agents[] | select(.src == "log") ] as $logged
|
|
222
|
+
| [ $agents[] | select(.src == "none") ] as $unpriced
|
|
186
223
|
|
|
187
224
|
# Union of the agent intervals: sum the merged runs rather than the raw ones,
|
|
188
|
-
# 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.
|
|
189
227
|
| ([ $priced[] | { s: .starts, e: .ends } ] | sort_by(.s)
|
|
190
228
|
| reduce .[] as $i ([];
|
|
191
229
|
if (length == 0) or (.[-1].e < $i.s)
|
|
@@ -201,8 +239,11 @@ SUMMARY="$(jq -s --argjson costs "$COSTS" '
|
|
|
201
239
|
| sort_by(-.n) | .[0:3]),
|
|
202
240
|
out_tok: $out_tok, cache_tok: $cache_tok,
|
|
203
241
|
agents: $agents,
|
|
204
|
-
|
|
242
|
+
priced_count: ($priced | length),
|
|
243
|
+
logged_count: ($logged | length),
|
|
244
|
+
unpriced_count: ($unpriced | length),
|
|
205
245
|
agent_tokens: ([ $priced[].tokens ] | add // 0),
|
|
246
|
+
logged_tokens: ([ $logged[].tokens ] | add // 0),
|
|
206
247
|
agent_ms: ([ $priced[].ms ] | add // 0),
|
|
207
248
|
concurrency: (if $agent_span > 0
|
|
208
249
|
then (([ $priced[].ms ] | add // 0) / 1000) / $agent_span
|
|
@@ -289,41 +330,60 @@ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
289
330
|
fi
|
|
290
331
|
|
|
291
332
|
count=$(g '.agents | length')
|
|
292
|
-
|
|
333
|
+
priced=$(g '.priced_count')
|
|
334
|
+
logged=$(g '.logged_count')
|
|
335
|
+
unpriced=$(g '.unpriced_count')
|
|
293
336
|
if [ "$count" -eq 0 ]; then
|
|
294
337
|
printf 'agents none dispatched\n'
|
|
295
338
|
else
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# and none to invent. Saying so beats a row of zeroes that reads as a total.
|
|
299
|
-
printf 'agents %s dispatched · cost not reported\n' "$count"
|
|
300
|
-
else
|
|
339
|
+
printf 'agents %s dispatched' "$count"
|
|
340
|
+
if [ "$priced" -gt 0 ]; then
|
|
301
341
|
# 1.0× means every agent had the clock to itself. On a plan whose graph had
|
|
302
342
|
# independent tasks in it, that number is the finding.
|
|
303
|
-
printf '
|
|
304
|
-
"$
|
|
343
|
+
printf ' · %s tok · %s wall · %s× concurrent' \
|
|
344
|
+
"$(tok "$(g '.agent_tokens')")" "$(dur "$(( $(g '.agent_ms') / 1000 ))")" \
|
|
305
345
|
"$(printf '%.1f' "$(g '.concurrency')")"
|
|
306
|
-
[ "$unmeasured" -gt 0 ] && printf ' · %s unmeasured' "$unmeasured"
|
|
307
|
-
printf '\n'
|
|
308
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'
|
|
309
361
|
|
|
310
362
|
# Up to a dozen rows read as the narrative of the plan. Past that the order
|
|
311
363
|
# stops helping, so show what the run actually spent on and say what is cut.
|
|
312
364
|
if [ "$count" -le 12 ]; then rows='.agents[]'; else rows='(.agents | sort_by(-.tokens) | .[0:10][])'; fi
|
|
313
|
-
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
|
|
314
366
|
model="${model#claude-}"; model="$(sed 's/-[0-9]\{8\}$//' <<<"$model")"
|
|
315
367
|
[ "$status" = "completed" ] && status="" || status=" ($status)"
|
|
316
|
-
|
|
317
|
-
printf ' %-22.22s %-10s %6s %7s %s tools%s\n' \
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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'
|
|
324
381
|
|
|
325
382
|
if [ "$count" -gt 12 ]; then
|
|
326
|
-
|
|
327
|
-
|
|
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'
|
|
328
388
|
fi
|
|
329
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": [
|