@olegkoval/agent-skills 1.42.0 → 1.42.1
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/adapters/claude/olko-reflection/skills/agent-ops-retro/scripts/mine-transcripts.mjs +46 -16
- package/adapters/cursor/olko-reflection/skills/agent-ops-retro/scripts/mine-transcripts.mjs +46 -16
- package/adapters/grok/olko-reflection/skills/agent-ops-retro/scripts/mine-transcripts.mjs +46 -16
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/workflow.js +4 -0
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/skills/agent-ops-retro/scripts/mine-transcripts.mjs +46 -16
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
|
@@ -27,7 +27,7 @@ const OUT = arg('out', './mine.json');
|
|
|
27
27
|
const SINCE = Date.parse(`${arg('since', '')}T00:00:00Z`);
|
|
28
28
|
const UNTIL = Date.parse(`${arg('until', '')}T00:00:00Z`);
|
|
29
29
|
|
|
30
|
-
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)
|
|
30
|
+
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)) {
|
|
31
31
|
console.error('usage: mine-transcripts.mjs --since YYYY-MM-DD --until YYYY-MM-DD [--out path] [--root dir]');
|
|
32
32
|
process.exit(2);
|
|
33
33
|
}
|
|
@@ -46,7 +46,23 @@ function walk(dir, out = []) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// A user-role text record that is really harness output, not something a person typed.
|
|
49
|
-
|
|
49
|
+
// A user-role text record that is really harness output, not something a person typed.
|
|
50
|
+
// Each alternative below was found by a run that mis-counted. The first pass caught the
|
|
51
|
+
// obvious wrappers and still let 26% through: skill body dumps, image attachments,
|
|
52
|
+
// already-loaded stubs and permission-grant echoes all sit in human-turn position.
|
|
53
|
+
const HARNESS = new RegExp('^(' + [
|
|
54
|
+
'<system-reminder', '<command-name', '<command-message', '<local-command',
|
|
55
|
+
'<bash-input', '<bash-stdout', '<bash-stderr', '<user-prompt-submit-hook',
|
|
56
|
+
'<task-notification', '<cross-session-message',
|
|
57
|
+
'Caveat: The messages below',
|
|
58
|
+
'\\[Request interrupted',
|
|
59
|
+
'Base directory for this skill:', // a skill body dumped into the turn
|
|
60
|
+
'Skill /[^ ]+ is already loaded', // re-invocation stub
|
|
61
|
+
'Permission granted for:', // permission-grant echo
|
|
62
|
+
'\\[Image #\\d+\\]', // bare attachment
|
|
63
|
+
'\\[Image: source:',
|
|
64
|
+
'\\[Pasted text',
|
|
65
|
+
].join('|') + ')');
|
|
50
66
|
|
|
51
67
|
const textOf = (msg) => {
|
|
52
68
|
const c = msg?.content;
|
|
@@ -68,7 +84,8 @@ const S = {
|
|
|
68
84
|
limits: [],
|
|
69
85
|
humanTurns: [],
|
|
70
86
|
echoTurns: 0,
|
|
71
|
-
|
|
87
|
+
interruptsAll: 0,
|
|
88
|
+
interruptsMain: 0,
|
|
72
89
|
compacts: 0,
|
|
73
90
|
usage: { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 },
|
|
74
91
|
};
|
|
@@ -134,16 +151,8 @@ for (const f of files) {
|
|
|
134
151
|
|
|
135
152
|
if (r.type === 'assistant') {
|
|
136
153
|
const model = r.message?.model;
|
|
154
|
+
if (model) bump(S.models, model);
|
|
137
155
|
const u = r.message?.usage;
|
|
138
|
-
if (model) {
|
|
139
|
-
const stats = S.models.get(model) || { messages: 0, in: 0, out: 0, cacheRead: 0, cacheWrite: 0 };
|
|
140
|
-
stats.messages++;
|
|
141
|
-
stats.in += u?.input_tokens || 0;
|
|
142
|
-
stats.out += u?.output_tokens || 0;
|
|
143
|
-
stats.cacheRead += u?.cache_read_input_tokens || 0;
|
|
144
|
-
stats.cacheWrite += u?.cache_creation_input_tokens || 0;
|
|
145
|
-
S.models.set(model, stats);
|
|
146
|
-
}
|
|
147
156
|
if (u) {
|
|
148
157
|
S.usage.in += u.input_tokens || 0;
|
|
149
158
|
S.usage.out += u.output_tokens || 0;
|
|
@@ -182,9 +191,12 @@ for (const f of files) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const raw = textOf(r.message);
|
|
194
|
+
// Two scopes, two names. The first version reported the all-records count and the
|
|
195
|
+
// per-session count under one label `interrupts`, so the top-level figure (27) did not
|
|
196
|
+
// match the sum of the per-session rows (15) and neither number could be trusted.
|
|
185
197
|
if (raw.includes('[Request interrupted')) {
|
|
186
|
-
S.
|
|
187
|
-
if (sess) sess.interrupts++;
|
|
198
|
+
S.interruptsAll++;
|
|
199
|
+
if (sess) { S.interruptsMain++; sess.interrupts++; }
|
|
188
200
|
}
|
|
189
201
|
if (!main || r.userType !== 'external' || !raw || sawToolResult) continue;
|
|
190
202
|
|
|
@@ -219,13 +231,14 @@ const report = {
|
|
|
219
231
|
humanTurnsPerDay: byDay,
|
|
220
232
|
turnsPerSession: { median: pct(0.5), p90: pct(0.9), max: humanCounts.at(-1) ?? 0 },
|
|
221
233
|
shortTurnsUnder40Chars: S.humanTurns.filter((t) => t.text.trim().length < 40).length,
|
|
222
|
-
|
|
234
|
+
interruptsMainSessions: S.interruptsMain,
|
|
235
|
+
interruptsAllRecords: S.interruptsAll,
|
|
223
236
|
compactEvents: S.compacts,
|
|
224
237
|
capacityLimitEvents: S.limits.length,
|
|
225
238
|
capacityLimitSamples: S.limits.slice(0, 10),
|
|
226
239
|
usage: S.usage,
|
|
227
240
|
cacheReadToOutputRatio: S.usage.out ? +(S.usage.cacheRead / S.usage.out).toFixed(1) : null,
|
|
228
|
-
models: [...S.models].sort((a, b) => b[1]
|
|
241
|
+
models: [...S.models].sort((a, b) => b[1] - a[1]),
|
|
229
242
|
topTools: [...S.tools].sort((a, b) => b[1] - a[1]).slice(0, 40),
|
|
230
243
|
agentSpawns: [...S.agents].sort((a, b) => b[1] - a[1]),
|
|
231
244
|
skillInvocations: [...S.skills].sort((a, b) => b[1] - a[1]),
|
|
@@ -233,6 +246,23 @@ const report = {
|
|
|
233
246
|
sessions,
|
|
234
247
|
};
|
|
235
248
|
|
|
249
|
+
// Internal consistency gate. A report whose aggregates disagree with its own rows is not
|
|
250
|
+
// evidence, and a previous version shipped exactly that. Fail loudly rather than emit it.
|
|
251
|
+
const rowInterrupts = sessions.reduce((n, s) => n + s.interrupts, 0);
|
|
252
|
+
const rowHuman = sessions.reduce((n, s) => n + s.human, 0);
|
|
253
|
+
const problems = [];
|
|
254
|
+
if (rowInterrupts !== report.interruptsMainSessions) {
|
|
255
|
+
problems.push(`interruptsMainSessions ${report.interruptsMainSessions} != sum of session rows ${rowInterrupts}`);
|
|
256
|
+
}
|
|
257
|
+
if (rowHuman !== report.humanTurnsOrganic) {
|
|
258
|
+
problems.push(`humanTurnsOrganic ${report.humanTurnsOrganic} != sum of session rows ${rowHuman}`);
|
|
259
|
+
}
|
|
260
|
+
if (problems.length) {
|
|
261
|
+
console.error('INCONSISTENT REPORT, refusing to write:\n ' + problems.join('\n '));
|
|
262
|
+
process.exit(3);
|
|
263
|
+
}
|
|
264
|
+
report.echoFilterVersion = 2;
|
|
265
|
+
|
|
236
266
|
fs.writeFileSync(OUT, JSON.stringify(report, null, 1));
|
|
237
267
|
fs.writeFileSync(OUT.replace(/\.json$/, '') + '-turns.json', JSON.stringify(S.humanTurns, null, 1));
|
|
238
268
|
|
|
@@ -27,7 +27,7 @@ const OUT = arg('out', './mine.json');
|
|
|
27
27
|
const SINCE = Date.parse(`${arg('since', '')}T00:00:00Z`);
|
|
28
28
|
const UNTIL = Date.parse(`${arg('until', '')}T00:00:00Z`);
|
|
29
29
|
|
|
30
|
-
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)
|
|
30
|
+
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)) {
|
|
31
31
|
console.error('usage: mine-transcripts.mjs --since YYYY-MM-DD --until YYYY-MM-DD [--out path] [--root dir]');
|
|
32
32
|
process.exit(2);
|
|
33
33
|
}
|
|
@@ -46,7 +46,23 @@ function walk(dir, out = []) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// A user-role text record that is really harness output, not something a person typed.
|
|
49
|
-
|
|
49
|
+
// A user-role text record that is really harness output, not something a person typed.
|
|
50
|
+
// Each alternative below was found by a run that mis-counted. The first pass caught the
|
|
51
|
+
// obvious wrappers and still let 26% through: skill body dumps, image attachments,
|
|
52
|
+
// already-loaded stubs and permission-grant echoes all sit in human-turn position.
|
|
53
|
+
const HARNESS = new RegExp('^(' + [
|
|
54
|
+
'<system-reminder', '<command-name', '<command-message', '<local-command',
|
|
55
|
+
'<bash-input', '<bash-stdout', '<bash-stderr', '<user-prompt-submit-hook',
|
|
56
|
+
'<task-notification', '<cross-session-message',
|
|
57
|
+
'Caveat: The messages below',
|
|
58
|
+
'\\[Request interrupted',
|
|
59
|
+
'Base directory for this skill:', // a skill body dumped into the turn
|
|
60
|
+
'Skill /[^ ]+ is already loaded', // re-invocation stub
|
|
61
|
+
'Permission granted for:', // permission-grant echo
|
|
62
|
+
'\\[Image #\\d+\\]', // bare attachment
|
|
63
|
+
'\\[Image: source:',
|
|
64
|
+
'\\[Pasted text',
|
|
65
|
+
].join('|') + ')');
|
|
50
66
|
|
|
51
67
|
const textOf = (msg) => {
|
|
52
68
|
const c = msg?.content;
|
|
@@ -68,7 +84,8 @@ const S = {
|
|
|
68
84
|
limits: [],
|
|
69
85
|
humanTurns: [],
|
|
70
86
|
echoTurns: 0,
|
|
71
|
-
|
|
87
|
+
interruptsAll: 0,
|
|
88
|
+
interruptsMain: 0,
|
|
72
89
|
compacts: 0,
|
|
73
90
|
usage: { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 },
|
|
74
91
|
};
|
|
@@ -134,16 +151,8 @@ for (const f of files) {
|
|
|
134
151
|
|
|
135
152
|
if (r.type === 'assistant') {
|
|
136
153
|
const model = r.message?.model;
|
|
154
|
+
if (model) bump(S.models, model);
|
|
137
155
|
const u = r.message?.usage;
|
|
138
|
-
if (model) {
|
|
139
|
-
const stats = S.models.get(model) || { messages: 0, in: 0, out: 0, cacheRead: 0, cacheWrite: 0 };
|
|
140
|
-
stats.messages++;
|
|
141
|
-
stats.in += u?.input_tokens || 0;
|
|
142
|
-
stats.out += u?.output_tokens || 0;
|
|
143
|
-
stats.cacheRead += u?.cache_read_input_tokens || 0;
|
|
144
|
-
stats.cacheWrite += u?.cache_creation_input_tokens || 0;
|
|
145
|
-
S.models.set(model, stats);
|
|
146
|
-
}
|
|
147
156
|
if (u) {
|
|
148
157
|
S.usage.in += u.input_tokens || 0;
|
|
149
158
|
S.usage.out += u.output_tokens || 0;
|
|
@@ -182,9 +191,12 @@ for (const f of files) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const raw = textOf(r.message);
|
|
194
|
+
// Two scopes, two names. The first version reported the all-records count and the
|
|
195
|
+
// per-session count under one label `interrupts`, so the top-level figure (27) did not
|
|
196
|
+
// match the sum of the per-session rows (15) and neither number could be trusted.
|
|
185
197
|
if (raw.includes('[Request interrupted')) {
|
|
186
|
-
S.
|
|
187
|
-
if (sess) sess.interrupts++;
|
|
198
|
+
S.interruptsAll++;
|
|
199
|
+
if (sess) { S.interruptsMain++; sess.interrupts++; }
|
|
188
200
|
}
|
|
189
201
|
if (!main || r.userType !== 'external' || !raw || sawToolResult) continue;
|
|
190
202
|
|
|
@@ -219,13 +231,14 @@ const report = {
|
|
|
219
231
|
humanTurnsPerDay: byDay,
|
|
220
232
|
turnsPerSession: { median: pct(0.5), p90: pct(0.9), max: humanCounts.at(-1) ?? 0 },
|
|
221
233
|
shortTurnsUnder40Chars: S.humanTurns.filter((t) => t.text.trim().length < 40).length,
|
|
222
|
-
|
|
234
|
+
interruptsMainSessions: S.interruptsMain,
|
|
235
|
+
interruptsAllRecords: S.interruptsAll,
|
|
223
236
|
compactEvents: S.compacts,
|
|
224
237
|
capacityLimitEvents: S.limits.length,
|
|
225
238
|
capacityLimitSamples: S.limits.slice(0, 10),
|
|
226
239
|
usage: S.usage,
|
|
227
240
|
cacheReadToOutputRatio: S.usage.out ? +(S.usage.cacheRead / S.usage.out).toFixed(1) : null,
|
|
228
|
-
models: [...S.models].sort((a, b) => b[1]
|
|
241
|
+
models: [...S.models].sort((a, b) => b[1] - a[1]),
|
|
229
242
|
topTools: [...S.tools].sort((a, b) => b[1] - a[1]).slice(0, 40),
|
|
230
243
|
agentSpawns: [...S.agents].sort((a, b) => b[1] - a[1]),
|
|
231
244
|
skillInvocations: [...S.skills].sort((a, b) => b[1] - a[1]),
|
|
@@ -233,6 +246,23 @@ const report = {
|
|
|
233
246
|
sessions,
|
|
234
247
|
};
|
|
235
248
|
|
|
249
|
+
// Internal consistency gate. A report whose aggregates disagree with its own rows is not
|
|
250
|
+
// evidence, and a previous version shipped exactly that. Fail loudly rather than emit it.
|
|
251
|
+
const rowInterrupts = sessions.reduce((n, s) => n + s.interrupts, 0);
|
|
252
|
+
const rowHuman = sessions.reduce((n, s) => n + s.human, 0);
|
|
253
|
+
const problems = [];
|
|
254
|
+
if (rowInterrupts !== report.interruptsMainSessions) {
|
|
255
|
+
problems.push(`interruptsMainSessions ${report.interruptsMainSessions} != sum of session rows ${rowInterrupts}`);
|
|
256
|
+
}
|
|
257
|
+
if (rowHuman !== report.humanTurnsOrganic) {
|
|
258
|
+
problems.push(`humanTurnsOrganic ${report.humanTurnsOrganic} != sum of session rows ${rowHuman}`);
|
|
259
|
+
}
|
|
260
|
+
if (problems.length) {
|
|
261
|
+
console.error('INCONSISTENT REPORT, refusing to write:\n ' + problems.join('\n '));
|
|
262
|
+
process.exit(3);
|
|
263
|
+
}
|
|
264
|
+
report.echoFilterVersion = 2;
|
|
265
|
+
|
|
236
266
|
fs.writeFileSync(OUT, JSON.stringify(report, null, 1));
|
|
237
267
|
fs.writeFileSync(OUT.replace(/\.json$/, '') + '-turns.json', JSON.stringify(S.humanTurns, null, 1));
|
|
238
268
|
|
|
@@ -27,7 +27,7 @@ const OUT = arg('out', './mine.json');
|
|
|
27
27
|
const SINCE = Date.parse(`${arg('since', '')}T00:00:00Z`);
|
|
28
28
|
const UNTIL = Date.parse(`${arg('until', '')}T00:00:00Z`);
|
|
29
29
|
|
|
30
|
-
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)
|
|
30
|
+
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)) {
|
|
31
31
|
console.error('usage: mine-transcripts.mjs --since YYYY-MM-DD --until YYYY-MM-DD [--out path] [--root dir]');
|
|
32
32
|
process.exit(2);
|
|
33
33
|
}
|
|
@@ -46,7 +46,23 @@ function walk(dir, out = []) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// A user-role text record that is really harness output, not something a person typed.
|
|
49
|
-
|
|
49
|
+
// A user-role text record that is really harness output, not something a person typed.
|
|
50
|
+
// Each alternative below was found by a run that mis-counted. The first pass caught the
|
|
51
|
+
// obvious wrappers and still let 26% through: skill body dumps, image attachments,
|
|
52
|
+
// already-loaded stubs and permission-grant echoes all sit in human-turn position.
|
|
53
|
+
const HARNESS = new RegExp('^(' + [
|
|
54
|
+
'<system-reminder', '<command-name', '<command-message', '<local-command',
|
|
55
|
+
'<bash-input', '<bash-stdout', '<bash-stderr', '<user-prompt-submit-hook',
|
|
56
|
+
'<task-notification', '<cross-session-message',
|
|
57
|
+
'Caveat: The messages below',
|
|
58
|
+
'\\[Request interrupted',
|
|
59
|
+
'Base directory for this skill:', // a skill body dumped into the turn
|
|
60
|
+
'Skill /[^ ]+ is already loaded', // re-invocation stub
|
|
61
|
+
'Permission granted for:', // permission-grant echo
|
|
62
|
+
'\\[Image #\\d+\\]', // bare attachment
|
|
63
|
+
'\\[Image: source:',
|
|
64
|
+
'\\[Pasted text',
|
|
65
|
+
].join('|') + ')');
|
|
50
66
|
|
|
51
67
|
const textOf = (msg) => {
|
|
52
68
|
const c = msg?.content;
|
|
@@ -68,7 +84,8 @@ const S = {
|
|
|
68
84
|
limits: [],
|
|
69
85
|
humanTurns: [],
|
|
70
86
|
echoTurns: 0,
|
|
71
|
-
|
|
87
|
+
interruptsAll: 0,
|
|
88
|
+
interruptsMain: 0,
|
|
72
89
|
compacts: 0,
|
|
73
90
|
usage: { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 },
|
|
74
91
|
};
|
|
@@ -134,16 +151,8 @@ for (const f of files) {
|
|
|
134
151
|
|
|
135
152
|
if (r.type === 'assistant') {
|
|
136
153
|
const model = r.message?.model;
|
|
154
|
+
if (model) bump(S.models, model);
|
|
137
155
|
const u = r.message?.usage;
|
|
138
|
-
if (model) {
|
|
139
|
-
const stats = S.models.get(model) || { messages: 0, in: 0, out: 0, cacheRead: 0, cacheWrite: 0 };
|
|
140
|
-
stats.messages++;
|
|
141
|
-
stats.in += u?.input_tokens || 0;
|
|
142
|
-
stats.out += u?.output_tokens || 0;
|
|
143
|
-
stats.cacheRead += u?.cache_read_input_tokens || 0;
|
|
144
|
-
stats.cacheWrite += u?.cache_creation_input_tokens || 0;
|
|
145
|
-
S.models.set(model, stats);
|
|
146
|
-
}
|
|
147
156
|
if (u) {
|
|
148
157
|
S.usage.in += u.input_tokens || 0;
|
|
149
158
|
S.usage.out += u.output_tokens || 0;
|
|
@@ -182,9 +191,12 @@ for (const f of files) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const raw = textOf(r.message);
|
|
194
|
+
// Two scopes, two names. The first version reported the all-records count and the
|
|
195
|
+
// per-session count under one label `interrupts`, so the top-level figure (27) did not
|
|
196
|
+
// match the sum of the per-session rows (15) and neither number could be trusted.
|
|
185
197
|
if (raw.includes('[Request interrupted')) {
|
|
186
|
-
S.
|
|
187
|
-
if (sess) sess.interrupts++;
|
|
198
|
+
S.interruptsAll++;
|
|
199
|
+
if (sess) { S.interruptsMain++; sess.interrupts++; }
|
|
188
200
|
}
|
|
189
201
|
if (!main || r.userType !== 'external' || !raw || sawToolResult) continue;
|
|
190
202
|
|
|
@@ -219,13 +231,14 @@ const report = {
|
|
|
219
231
|
humanTurnsPerDay: byDay,
|
|
220
232
|
turnsPerSession: { median: pct(0.5), p90: pct(0.9), max: humanCounts.at(-1) ?? 0 },
|
|
221
233
|
shortTurnsUnder40Chars: S.humanTurns.filter((t) => t.text.trim().length < 40).length,
|
|
222
|
-
|
|
234
|
+
interruptsMainSessions: S.interruptsMain,
|
|
235
|
+
interruptsAllRecords: S.interruptsAll,
|
|
223
236
|
compactEvents: S.compacts,
|
|
224
237
|
capacityLimitEvents: S.limits.length,
|
|
225
238
|
capacityLimitSamples: S.limits.slice(0, 10),
|
|
226
239
|
usage: S.usage,
|
|
227
240
|
cacheReadToOutputRatio: S.usage.out ? +(S.usage.cacheRead / S.usage.out).toFixed(1) : null,
|
|
228
|
-
models: [...S.models].sort((a, b) => b[1]
|
|
241
|
+
models: [...S.models].sort((a, b) => b[1] - a[1]),
|
|
229
242
|
topTools: [...S.tools].sort((a, b) => b[1] - a[1]).slice(0, 40),
|
|
230
243
|
agentSpawns: [...S.agents].sort((a, b) => b[1] - a[1]),
|
|
231
244
|
skillInvocations: [...S.skills].sort((a, b) => b[1] - a[1]),
|
|
@@ -233,6 +246,23 @@ const report = {
|
|
|
233
246
|
sessions,
|
|
234
247
|
};
|
|
235
248
|
|
|
249
|
+
// Internal consistency gate. A report whose aggregates disagree with its own rows is not
|
|
250
|
+
// evidence, and a previous version shipped exactly that. Fail loudly rather than emit it.
|
|
251
|
+
const rowInterrupts = sessions.reduce((n, s) => n + s.interrupts, 0);
|
|
252
|
+
const rowHuman = sessions.reduce((n, s) => n + s.human, 0);
|
|
253
|
+
const problems = [];
|
|
254
|
+
if (rowInterrupts !== report.interruptsMainSessions) {
|
|
255
|
+
problems.push(`interruptsMainSessions ${report.interruptsMainSessions} != sum of session rows ${rowInterrupts}`);
|
|
256
|
+
}
|
|
257
|
+
if (rowHuman !== report.humanTurnsOrganic) {
|
|
258
|
+
problems.push(`humanTurnsOrganic ${report.humanTurnsOrganic} != sum of session rows ${rowHuman}`);
|
|
259
|
+
}
|
|
260
|
+
if (problems.length) {
|
|
261
|
+
console.error('INCONSISTENT REPORT, refusing to write:\n ' + problems.join('\n '));
|
|
262
|
+
process.exit(3);
|
|
263
|
+
}
|
|
264
|
+
report.echoFilterVersion = 2;
|
|
265
|
+
|
|
236
266
|
fs.writeFileSync(OUT, JSON.stringify(report, null, 1));
|
|
237
267
|
fs.writeFileSync(OUT.replace(/\.json$/, '') + '-turns.json', JSON.stringify(S.humanTurns, null, 1));
|
|
238
268
|
|
package/package.json
CHANGED
|
@@ -424,6 +424,10 @@ const budgetAtStart = budget.spent()
|
|
|
424
424
|
`First Read and follow the prompt file: ${promptDir}/${dim.file}.`,
|
|
425
425
|
`Parameters: REPO_SLUG=${repoSlug}, PR_NUMBER=${prNumber}, PR_URL=${prUrl},`,
|
|
426
426
|
`DIFF_FILE=${diffFile}, CONTEXT_FILE=${contextFile}, WORKTREE_PATH=${wtDisplay}.`,
|
|
427
|
+
`Your StructuredOutput MUST be a JSON object with a top-level "findings" array`
|
|
428
|
+
+ ` (it is a REQUIRED property, so do not omit it even when there is nothing to report).`
|
|
429
|
+
+ ` If you found no issues, return {"findings": []}, never prose, never an empty object,`
|
|
430
|
+
+ ` never a "findings" key nested under anything else.`,
|
|
427
431
|
].join(' ')
|
|
428
432
|
|
|
429
433
|
if (prevSha) {
|
|
@@ -27,7 +27,7 @@ const OUT = arg('out', './mine.json');
|
|
|
27
27
|
const SINCE = Date.parse(`${arg('since', '')}T00:00:00Z`);
|
|
28
28
|
const UNTIL = Date.parse(`${arg('until', '')}T00:00:00Z`);
|
|
29
29
|
|
|
30
|
-
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)
|
|
30
|
+
if (!Number.isFinite(SINCE) || !Number.isFinite(UNTIL)) {
|
|
31
31
|
console.error('usage: mine-transcripts.mjs --since YYYY-MM-DD --until YYYY-MM-DD [--out path] [--root dir]');
|
|
32
32
|
process.exit(2);
|
|
33
33
|
}
|
|
@@ -46,7 +46,23 @@ function walk(dir, out = []) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// A user-role text record that is really harness output, not something a person typed.
|
|
49
|
-
|
|
49
|
+
// A user-role text record that is really harness output, not something a person typed.
|
|
50
|
+
// Each alternative below was found by a run that mis-counted. The first pass caught the
|
|
51
|
+
// obvious wrappers and still let 26% through: skill body dumps, image attachments,
|
|
52
|
+
// already-loaded stubs and permission-grant echoes all sit in human-turn position.
|
|
53
|
+
const HARNESS = new RegExp('^(' + [
|
|
54
|
+
'<system-reminder', '<command-name', '<command-message', '<local-command',
|
|
55
|
+
'<bash-input', '<bash-stdout', '<bash-stderr', '<user-prompt-submit-hook',
|
|
56
|
+
'<task-notification', '<cross-session-message',
|
|
57
|
+
'Caveat: The messages below',
|
|
58
|
+
'\\[Request interrupted',
|
|
59
|
+
'Base directory for this skill:', // a skill body dumped into the turn
|
|
60
|
+
'Skill /[^ ]+ is already loaded', // re-invocation stub
|
|
61
|
+
'Permission granted for:', // permission-grant echo
|
|
62
|
+
'\\[Image #\\d+\\]', // bare attachment
|
|
63
|
+
'\\[Image: source:',
|
|
64
|
+
'\\[Pasted text',
|
|
65
|
+
].join('|') + ')');
|
|
50
66
|
|
|
51
67
|
const textOf = (msg) => {
|
|
52
68
|
const c = msg?.content;
|
|
@@ -68,7 +84,8 @@ const S = {
|
|
|
68
84
|
limits: [],
|
|
69
85
|
humanTurns: [],
|
|
70
86
|
echoTurns: 0,
|
|
71
|
-
|
|
87
|
+
interruptsAll: 0,
|
|
88
|
+
interruptsMain: 0,
|
|
72
89
|
compacts: 0,
|
|
73
90
|
usage: { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 },
|
|
74
91
|
};
|
|
@@ -134,16 +151,8 @@ for (const f of files) {
|
|
|
134
151
|
|
|
135
152
|
if (r.type === 'assistant') {
|
|
136
153
|
const model = r.message?.model;
|
|
154
|
+
if (model) bump(S.models, model);
|
|
137
155
|
const u = r.message?.usage;
|
|
138
|
-
if (model) {
|
|
139
|
-
const stats = S.models.get(model) || { messages: 0, in: 0, out: 0, cacheRead: 0, cacheWrite: 0 };
|
|
140
|
-
stats.messages++;
|
|
141
|
-
stats.in += u?.input_tokens || 0;
|
|
142
|
-
stats.out += u?.output_tokens || 0;
|
|
143
|
-
stats.cacheRead += u?.cache_read_input_tokens || 0;
|
|
144
|
-
stats.cacheWrite += u?.cache_creation_input_tokens || 0;
|
|
145
|
-
S.models.set(model, stats);
|
|
146
|
-
}
|
|
147
156
|
if (u) {
|
|
148
157
|
S.usage.in += u.input_tokens || 0;
|
|
149
158
|
S.usage.out += u.output_tokens || 0;
|
|
@@ -182,9 +191,12 @@ for (const f of files) {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const raw = textOf(r.message);
|
|
194
|
+
// Two scopes, two names. The first version reported the all-records count and the
|
|
195
|
+
// per-session count under one label `interrupts`, so the top-level figure (27) did not
|
|
196
|
+
// match the sum of the per-session rows (15) and neither number could be trusted.
|
|
185
197
|
if (raw.includes('[Request interrupted')) {
|
|
186
|
-
S.
|
|
187
|
-
if (sess) sess.interrupts++;
|
|
198
|
+
S.interruptsAll++;
|
|
199
|
+
if (sess) { S.interruptsMain++; sess.interrupts++; }
|
|
188
200
|
}
|
|
189
201
|
if (!main || r.userType !== 'external' || !raw || sawToolResult) continue;
|
|
190
202
|
|
|
@@ -219,13 +231,14 @@ const report = {
|
|
|
219
231
|
humanTurnsPerDay: byDay,
|
|
220
232
|
turnsPerSession: { median: pct(0.5), p90: pct(0.9), max: humanCounts.at(-1) ?? 0 },
|
|
221
233
|
shortTurnsUnder40Chars: S.humanTurns.filter((t) => t.text.trim().length < 40).length,
|
|
222
|
-
|
|
234
|
+
interruptsMainSessions: S.interruptsMain,
|
|
235
|
+
interruptsAllRecords: S.interruptsAll,
|
|
223
236
|
compactEvents: S.compacts,
|
|
224
237
|
capacityLimitEvents: S.limits.length,
|
|
225
238
|
capacityLimitSamples: S.limits.slice(0, 10),
|
|
226
239
|
usage: S.usage,
|
|
227
240
|
cacheReadToOutputRatio: S.usage.out ? +(S.usage.cacheRead / S.usage.out).toFixed(1) : null,
|
|
228
|
-
models: [...S.models].sort((a, b) => b[1]
|
|
241
|
+
models: [...S.models].sort((a, b) => b[1] - a[1]),
|
|
229
242
|
topTools: [...S.tools].sort((a, b) => b[1] - a[1]).slice(0, 40),
|
|
230
243
|
agentSpawns: [...S.agents].sort((a, b) => b[1] - a[1]),
|
|
231
244
|
skillInvocations: [...S.skills].sort((a, b) => b[1] - a[1]),
|
|
@@ -233,6 +246,23 @@ const report = {
|
|
|
233
246
|
sessions,
|
|
234
247
|
};
|
|
235
248
|
|
|
249
|
+
// Internal consistency gate. A report whose aggregates disagree with its own rows is not
|
|
250
|
+
// evidence, and a previous version shipped exactly that. Fail loudly rather than emit it.
|
|
251
|
+
const rowInterrupts = sessions.reduce((n, s) => n + s.interrupts, 0);
|
|
252
|
+
const rowHuman = sessions.reduce((n, s) => n + s.human, 0);
|
|
253
|
+
const problems = [];
|
|
254
|
+
if (rowInterrupts !== report.interruptsMainSessions) {
|
|
255
|
+
problems.push(`interruptsMainSessions ${report.interruptsMainSessions} != sum of session rows ${rowInterrupts}`);
|
|
256
|
+
}
|
|
257
|
+
if (rowHuman !== report.humanTurnsOrganic) {
|
|
258
|
+
problems.push(`humanTurnsOrganic ${report.humanTurnsOrganic} != sum of session rows ${rowHuman}`);
|
|
259
|
+
}
|
|
260
|
+
if (problems.length) {
|
|
261
|
+
console.error('INCONSISTENT REPORT, refusing to write:\n ' + problems.join('\n '));
|
|
262
|
+
process.exit(3);
|
|
263
|
+
}
|
|
264
|
+
report.echoFilterVersion = 2;
|
|
265
|
+
|
|
236
266
|
fs.writeFileSync(OUT, JSON.stringify(report, null, 1));
|
|
237
267
|
fs.writeFileSync(OUT.replace(/\.json$/, '') + '-turns.json', JSON.stringify(S.humanTurns, null, 1));
|
|
238
268
|
|