@use-aistack/cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +935 -395
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var CLI_VERSION = true ? "0.
|
|
7
|
+
var CLI_VERSION = true ? "0.9.0" : "0.0.0-dev";
|
|
8
8
|
|
|
9
9
|
// src/api.ts
|
|
10
10
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
@@ -2133,192 +2133,628 @@ async function hasRecentFile(roots, matches, sinceMs, opts = {}) {
|
|
|
2133
2133
|
return false;
|
|
2134
2134
|
}
|
|
2135
2135
|
|
|
2136
|
-
// ../workflow-rules/src/
|
|
2137
|
-
var
|
|
2136
|
+
// ../workflow-rules/src/daily.ts
|
|
2137
|
+
var WORKFLOW_AGGREGATES_V2 = "workflow-aggregates/v2";
|
|
2138
|
+
var LOG_BUCKETS_V1 = "log-buckets/v1";
|
|
2139
|
+
var EMPTY_PHASE_TOTALS = Object.freeze({
|
|
2140
|
+
scout: 0,
|
|
2141
|
+
build: 0,
|
|
2142
|
+
verify: 0,
|
|
2143
|
+
handoff: 0,
|
|
2144
|
+
unknown: 0
|
|
2145
|
+
});
|
|
2146
|
+
var EFFORT_LEVELS = [
|
|
2147
|
+
"low",
|
|
2148
|
+
"medium",
|
|
2149
|
+
"high",
|
|
2150
|
+
"other"
|
|
2151
|
+
];
|
|
2152
|
+
function effortLevelOf(effort) {
|
|
2153
|
+
switch (effort.toLowerCase()) {
|
|
2154
|
+
case "low":
|
|
2155
|
+
case "minimal":
|
|
2156
|
+
return "low";
|
|
2157
|
+
case "medium":
|
|
2158
|
+
return "medium";
|
|
2159
|
+
case "high":
|
|
2160
|
+
case "xhigh":
|
|
2161
|
+
case "max":
|
|
2162
|
+
case "ultra":
|
|
2163
|
+
return "high";
|
|
2164
|
+
default:
|
|
2165
|
+
return "other";
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
function logBucket(value) {
|
|
2169
|
+
if (!(value >= 1)) return 0;
|
|
2170
|
+
return Math.floor(Math.log2(value)) + 1;
|
|
2171
|
+
}
|
|
2172
|
+
function bucketRange(bucket) {
|
|
2173
|
+
if (bucket <= 0) return { low: 0, high: 1 };
|
|
2174
|
+
return { low: 2 ** (bucket - 1), high: 2 ** bucket };
|
|
2175
|
+
}
|
|
2176
|
+
function bucketMid(bucket) {
|
|
2177
|
+
const { low, high } = bucketRange(bucket);
|
|
2178
|
+
return Math.sqrt(Math.max(low, 0.25) * high);
|
|
2179
|
+
}
|
|
2180
|
+
function medianBucket(buckets) {
|
|
2181
|
+
const total = buckets.reduce((sum, row) => sum + row.count, 0);
|
|
2182
|
+
if (total <= 0) return void 0;
|
|
2183
|
+
const sorted = [...buckets].sort((a, b) => a.bucket - b.bucket);
|
|
2184
|
+
const middle = (total + 1) / 2;
|
|
2185
|
+
let seen = 0;
|
|
2186
|
+
for (const row of sorted) {
|
|
2187
|
+
seen += row.count;
|
|
2188
|
+
if (seen >= middle) return row.bucket;
|
|
2189
|
+
}
|
|
2190
|
+
return sorted[sorted.length - 1]?.bucket;
|
|
2191
|
+
}
|
|
2138
2192
|
function median(values) {
|
|
2139
2193
|
if (values.length === 0) return void 0;
|
|
2140
2194
|
const sorted = [...values].sort((a, b) => a - b);
|
|
2141
2195
|
const mid = Math.floor(sorted.length / 2);
|
|
2142
2196
|
const midValue = sorted[mid];
|
|
2143
|
-
if (midValue === void 0) return void 0;
|
|
2144
2197
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + midValue) / 2 : midValue;
|
|
2145
2198
|
}
|
|
2146
|
-
function
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2199
|
+
function addPhaseTotals(into, from) {
|
|
2200
|
+
for (const phase of Object.keys(into)) {
|
|
2201
|
+
into[phase] += from[phase] ?? 0;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function sumBy(rows, key, add, clone) {
|
|
2205
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2206
|
+
for (const row of rows) {
|
|
2207
|
+
const k = key(row);
|
|
2208
|
+
const held = merged.get(k);
|
|
2209
|
+
if (held) add(held, row);
|
|
2210
|
+
else merged.set(k, clone(row));
|
|
2211
|
+
}
|
|
2212
|
+
return [...merged.values()];
|
|
2213
|
+
}
|
|
2214
|
+
function foldCells(rows, field) {
|
|
2215
|
+
return sumBy(
|
|
2216
|
+
rows,
|
|
2217
|
+
(row) => `${row.weekdayUtc}:${row.hourUtc}`,
|
|
2218
|
+
(into, from) => {
|
|
2219
|
+
into[field] += from[field];
|
|
2220
|
+
},
|
|
2221
|
+
(row) => ({ ...row })
|
|
2222
|
+
).sort((a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc);
|
|
2223
|
+
}
|
|
2224
|
+
function foldModels(rows) {
|
|
2225
|
+
return sumBy(
|
|
2226
|
+
rows,
|
|
2227
|
+
(row) => row.model,
|
|
2228
|
+
(into, from) => {
|
|
2229
|
+
into.tokens += from.tokens;
|
|
2230
|
+
},
|
|
2231
|
+
(row) => ({ ...row })
|
|
2232
|
+
).sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model));
|
|
2233
|
+
}
|
|
2234
|
+
function foldLengths(rows) {
|
|
2235
|
+
return sumBy(
|
|
2236
|
+
rows,
|
|
2237
|
+
(row) => String(row.bucket),
|
|
2238
|
+
(into, from) => {
|
|
2239
|
+
into.sessions += from.sessions;
|
|
2240
|
+
addPhaseTotals(into.phaseSec, from.phaseSec);
|
|
2241
|
+
into.merged += from.merged;
|
|
2242
|
+
into.verified += from.verified;
|
|
2243
|
+
into.mergedVerified += from.mergedVerified;
|
|
2244
|
+
into.openedWithScout += from.openedWithScout;
|
|
2245
|
+
},
|
|
2246
|
+
(row) => ({ ...row, phaseSec: { ...row.phaseSec } })
|
|
2247
|
+
).sort((a, b) => a.bucket - b.bucket);
|
|
2248
|
+
}
|
|
2249
|
+
function foldHarnessDays(days) {
|
|
2250
|
+
const first = days[0];
|
|
2251
|
+
if (!first) throw new Error("foldHarnessDays needs at least one day");
|
|
2252
|
+
const versions = (values) => [...new Set(values)].sort().join(" \xB7 ");
|
|
2253
|
+
const out = {
|
|
2254
|
+
harness: first.harness,
|
|
2255
|
+
sessions: days.reduce((sum, day) => sum + day.sessions, 0),
|
|
2256
|
+
startHours: sumBy(
|
|
2257
|
+
days.flatMap((day) => day.startHours),
|
|
2258
|
+
(row) => String(row.hourUtc),
|
|
2259
|
+
(into, from) => {
|
|
2260
|
+
into.sessions += from.sessions;
|
|
2261
|
+
},
|
|
2262
|
+
(row) => ({ ...row })
|
|
2263
|
+
).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
2264
|
+
activity: foldCells(
|
|
2265
|
+
days.flatMap((day) => day.activity),
|
|
2266
|
+
"events"
|
|
2267
|
+
)
|
|
2268
|
+
};
|
|
2269
|
+
const phases = days.flatMap((day) => day.phase ? [day.phase] : []);
|
|
2270
|
+
if (phases.length > 0) {
|
|
2271
|
+
const phaseSec = { ...EMPTY_PHASE_TOTALS };
|
|
2272
|
+
const phaseEvents = { ...EMPTY_PHASE_TOTALS };
|
|
2273
|
+
for (const phase of phases) {
|
|
2274
|
+
addPhaseTotals(phaseSec, phase.phaseSec);
|
|
2275
|
+
addPhaseTotals(phaseEvents, phase.phaseEvents);
|
|
2276
|
+
}
|
|
2277
|
+
out.phase = {
|
|
2278
|
+
ruleVersion: versions(phases.map((phase) => phase.ruleVersion)),
|
|
2279
|
+
sessions: phases.reduce((sum, phase) => sum + phase.sessions, 0),
|
|
2280
|
+
phaseSec,
|
|
2281
|
+
phaseEvents,
|
|
2282
|
+
waitingSec: phases.reduce((sum, phase) => sum + phase.waitingSec, 0),
|
|
2283
|
+
idleSec: phases.reduce((sum, phase) => sum + phase.idleSec, 0),
|
|
2284
|
+
sessionsWithVerify: phases.reduce(
|
|
2285
|
+
(sum, phase) => sum + phase.sessionsWithVerify,
|
|
2286
|
+
0
|
|
2287
|
+
),
|
|
2288
|
+
sessionsWithHandoff: phases.reduce(
|
|
2289
|
+
(sum, phase) => sum + phase.sessionsWithHandoff,
|
|
2290
|
+
0
|
|
2291
|
+
),
|
|
2292
|
+
bucketRuleVersion: versions(
|
|
2293
|
+
phases.map((phase) => phase.bucketRuleVersion)
|
|
2294
|
+
),
|
|
2295
|
+
lengths: foldLengths(phases.flatMap((phase) => phase.lengths))
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
const routings = days.flatMap((day) => day.routing ? [day.routing] : []);
|
|
2299
|
+
if (routings.length > 0) {
|
|
2300
|
+
out.routing = {
|
|
2301
|
+
main: foldModels(routings.flatMap((routing) => routing.main)),
|
|
2302
|
+
subagents: foldModels(routings.flatMap((routing) => routing.subagents))
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const delegations = days.flatMap(
|
|
2306
|
+
(day) => day.delegation ? [day.delegation] : []
|
|
2307
|
+
);
|
|
2308
|
+
if (delegations.length > 0) {
|
|
2309
|
+
out.delegation = {
|
|
2310
|
+
mainToolCalls: delegations.reduce((sum, d) => sum + d.mainToolCalls, 0),
|
|
2311
|
+
subagentToolCalls: delegations.reduce(
|
|
2312
|
+
(sum, d) => sum + d.subagentToolCalls,
|
|
2313
|
+
0
|
|
2314
|
+
),
|
|
2315
|
+
widestFanOut: Math.max(...delegations.map((d) => d.widestFanOut)),
|
|
2316
|
+
mostSubagents: Math.max(...delegations.map((d) => d.mostSubagents))
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
const efforts = days.flatMap((day) => day.effort ?? []);
|
|
2320
|
+
if (days.some((day) => day.effort)) {
|
|
2321
|
+
out.effort = sumBy(
|
|
2322
|
+
efforts,
|
|
2323
|
+
(row) => row.level,
|
|
2324
|
+
(into, from) => {
|
|
2325
|
+
into.turns += from.turns;
|
|
2326
|
+
},
|
|
2327
|
+
(row) => ({ ...row })
|
|
2328
|
+
).sort(
|
|
2329
|
+
(a, b) => EFFORT_LEVELS.indexOf(a.level) - EFFORT_LEVELS.indexOf(b.level)
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
const thinkings = days.flatMap((day) => day.thinking ? [day.thinking] : []);
|
|
2333
|
+
if (thinkings.length > 0) {
|
|
2334
|
+
out.thinking = {
|
|
2335
|
+
thinkingTokens: thinkings.reduce((sum, t) => sum + t.thinkingTokens, 0),
|
|
2336
|
+
responseTokens: thinkings.reduce((sum, t) => sum + t.responseTokens, 0)
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
const durations = days.flatMap(
|
|
2340
|
+
(day) => day.turnDurations ? [day.turnDurations] : []
|
|
2341
|
+
);
|
|
2342
|
+
if (durations.length > 0) {
|
|
2343
|
+
out.turnDurations = {
|
|
2344
|
+
bucketRuleVersion: versions(durations.map((d) => d.bucketRuleVersion)),
|
|
2345
|
+
buckets: sumBy(
|
|
2346
|
+
durations.flatMap((d) => d.buckets),
|
|
2347
|
+
(row) => String(row.bucket),
|
|
2348
|
+
(into, from) => {
|
|
2349
|
+
into.turns += from.turns;
|
|
2350
|
+
},
|
|
2351
|
+
(row) => ({ ...row })
|
|
2352
|
+
).sort((a, b) => a.bucket - b.bucket)
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
const questions = days.flatMap(
|
|
2356
|
+
(day) => day.questions ? [day.questions] : []
|
|
2357
|
+
);
|
|
2358
|
+
if (questions.length > 0) {
|
|
2359
|
+
out.questions = {
|
|
2360
|
+
asked: questions.reduce((sum, q) => sum + q.asked, 0),
|
|
2361
|
+
turns: questions.reduce((sum, q) => sum + q.turns, 0)
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
if (days.some((day) => day.webSearches !== void 0)) {
|
|
2365
|
+
out.webSearches = days.reduce(
|
|
2366
|
+
(sum, day) => sum + (day.webSearches ?? 0),
|
|
2367
|
+
0
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
return out;
|
|
2371
|
+
}
|
|
2372
|
+
function foldGitDays(days) {
|
|
2373
|
+
const versions = (values) => [...new Set(values)].sort().join(" \xB7 ");
|
|
2374
|
+
return {
|
|
2375
|
+
testFileRuleVersion: versions(days.map((d) => d.testFileRuleVersion)),
|
|
2376
|
+
fileTypeRuleVersion: versions(days.map((d) => d.fileTypeRuleVersion)),
|
|
2377
|
+
commitSetRuleVersion: versions(days.map((d) => d.commitSetRuleVersion)),
|
|
2378
|
+
commits: days.reduce((sum, d) => sum + d.commits, 0),
|
|
2379
|
+
lateNightCommits: days.reduce((sum, d) => sum + d.lateNightCommits, 0),
|
|
2380
|
+
additions: days.reduce((sum, d) => sum + d.additions, 0),
|
|
2381
|
+
removals: days.reduce((sum, d) => sum + d.removals, 0),
|
|
2382
|
+
changedLinesPerCommit: days.flatMap((d) => [...d.changedLinesPerCommit]),
|
|
2383
|
+
testFileCommits: days.reduce((sum, d) => sum + d.testFileCommits, 0),
|
|
2384
|
+
changedLinesByExtension: sumBy(
|
|
2385
|
+
days.flatMap((d) => d.changedLinesByExtension),
|
|
2386
|
+
(row) => row.extension,
|
|
2387
|
+
(into, from) => {
|
|
2388
|
+
into.changedLines += from.changedLines;
|
|
2389
|
+
},
|
|
2390
|
+
(row) => ({ ...row })
|
|
2391
|
+
).sort((a, b) => a.extension.localeCompare(b.extension)),
|
|
2392
|
+
withheldExtensionLines: days.reduce(
|
|
2393
|
+
(sum, d) => sum + d.withheldExtensionLines,
|
|
2394
|
+
0
|
|
2395
|
+
),
|
|
2396
|
+
weekdayHourCells: foldCells(
|
|
2397
|
+
days.flatMap((d) => d.weekdayHourCells),
|
|
2398
|
+
"commits"
|
|
2399
|
+
)
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
function foldWorkflowDays(days, options) {
|
|
2403
|
+
if (days.length === 0) return void 0;
|
|
2404
|
+
const byHarness = /* @__PURE__ */ new Map();
|
|
2405
|
+
for (const day of days) {
|
|
2406
|
+
for (const harness of day.harnesses) {
|
|
2407
|
+
const held = byHarness.get(harness.harness) ?? [];
|
|
2408
|
+
held.push(harness);
|
|
2409
|
+
byHarness.set(harness.harness, held);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
const parallelProjectDays = days.flatMap(
|
|
2413
|
+
(day) => day.parallelProjects === void 0 ? [] : [day.parallelProjects]
|
|
2414
|
+
);
|
|
2415
|
+
const webSearchDays = days.filter(
|
|
2416
|
+
(day) => day.harnesses.some((harness) => harness.webSearches !== void 0)
|
|
2417
|
+
).length;
|
|
2418
|
+
return {
|
|
2419
|
+
aggregateVersion: options.aggregateVersion,
|
|
2420
|
+
...options.utcOffsetMinutes === void 0 ? {} : { utcOffsetMinutes: options.utcOffsetMinutes },
|
|
2421
|
+
dates: [...new Set(days.map((day) => day.date))].sort(),
|
|
2422
|
+
harnesses: [...byHarness.values()].map(foldHarnessDays).sort((a, b) => a.harness.localeCompare(b.harness)),
|
|
2423
|
+
git: foldGitDays(days.map((day) => day.git)),
|
|
2424
|
+
...parallelProjectDays.length === 0 ? {} : { parallelProjects: Math.max(...parallelProjectDays) },
|
|
2425
|
+
parallelProjectDays,
|
|
2426
|
+
webSearchDays
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// ../workflow-rules/src/reading.ts
|
|
2431
|
+
function playbookHarnesses(reading) {
|
|
2432
|
+
return reading.harnesses.filter((harness) => harness.phase !== void 0);
|
|
2433
|
+
}
|
|
2434
|
+
function startHoursUtc(reading) {
|
|
2435
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2436
|
+
for (const harness of reading.harnesses) {
|
|
2437
|
+
for (const cell of harness.startHours) {
|
|
2438
|
+
counts.set(cell.hourUtc, (counts.get(cell.hourUtc) ?? 0) + cell.sessions);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
return counts;
|
|
2442
|
+
}
|
|
2443
|
+
function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
2444
|
+
return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
|
|
2445
|
+
}
|
|
2446
|
+
function modalStartHour(reading) {
|
|
2447
|
+
const offsetMinutes = reading.utcOffsetMinutes;
|
|
2448
|
+
if (offsetMinutes === void 0) return void 0;
|
|
2449
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2450
|
+
for (const [hourUtc, sessions] of startHoursUtc(reading)) {
|
|
2451
|
+
const hour = ownerLocalHour(hourUtc, offsetMinutes);
|
|
2452
|
+
counts.set(hour, (counts.get(hour) ?? 0) + sessions);
|
|
2453
|
+
}
|
|
2454
|
+
if (counts.size === 0) return void 0;
|
|
2455
|
+
return [...counts.entries()].sort(
|
|
2456
|
+
(a, b) => b[1] - a[1] || a[0] - b[0]
|
|
2457
|
+
)[0]?.[0];
|
|
2150
2458
|
}
|
|
2459
|
+
|
|
2460
|
+
// ../workflow-rules/src/componentRules.ts
|
|
2461
|
+
var COMPONENT_RULES_V2 = "component-rules/v2";
|
|
2462
|
+
var gitCoverage = () => 1;
|
|
2463
|
+
function harnessShare(input, counts) {
|
|
2464
|
+
const synced = input.reading.harnesses.length;
|
|
2465
|
+
if (synced === 0) return 0;
|
|
2466
|
+
return counts(input) / synced;
|
|
2467
|
+
}
|
|
2468
|
+
function topShare(entries) {
|
|
2469
|
+
const total = entries.reduce((sum, entry) => sum + entry.value, 0);
|
|
2470
|
+
if (total <= 0) return void 0;
|
|
2471
|
+
const top = Math.max(...entries.map((entry) => entry.value));
|
|
2472
|
+
return top / total;
|
|
2473
|
+
}
|
|
2474
|
+
var COMPONENT_RULES = [
|
|
2475
|
+
{
|
|
2476
|
+
id: "activity-heatmap",
|
|
2477
|
+
version: COMPONENT_RULES_V2,
|
|
2478
|
+
label: "of events fall in the three busiest hours of the day",
|
|
2479
|
+
unit: "share",
|
|
2480
|
+
// Three of twenty-four hours is an eighth of the clock. A day spread evenly
|
|
2481
|
+
// lands near it; a night owl runs far above it.
|
|
2482
|
+
band: { low: 0.125, high: 0.35 },
|
|
2483
|
+
evaluate: ({ reading }) => {
|
|
2484
|
+
const byHour = /* @__PURE__ */ new Map();
|
|
2485
|
+
for (const harness of reading.harnesses) {
|
|
2486
|
+
for (const cell of harness.activity) {
|
|
2487
|
+
byHour.set(
|
|
2488
|
+
cell.hourUtc,
|
|
2489
|
+
(byHour.get(cell.hourUtc) ?? 0) + cell.events
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
const total = [...byHour.values()].reduce((sum, n) => sum + n, 0);
|
|
2494
|
+
if (total <= 0) return void 0;
|
|
2495
|
+
const busiest = [...byHour.values()].sort((a, b) => b - a).slice(0, 3);
|
|
2496
|
+
return busiest.reduce((sum, n) => sum + n, 0) / total;
|
|
2497
|
+
},
|
|
2498
|
+
coverage: (input) => harnessShare(
|
|
2499
|
+
input,
|
|
2500
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.activity.length > 0).length
|
|
2501
|
+
)
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
id: "start-hours",
|
|
2505
|
+
version: COMPONENT_RULES_V2,
|
|
2506
|
+
label: "is the most common start hour",
|
|
2507
|
+
unit: "hour",
|
|
2508
|
+
// A band on a clock face means little; the row is never ranked, and the
|
|
2509
|
+
// figure is a position rather than a size. Kept for shape.
|
|
2510
|
+
band: { low: 9, high: 18 },
|
|
2511
|
+
evaluate: ({ reading }) => modalStartHour(reading),
|
|
2512
|
+
coverage: (input) => harnessShare(
|
|
2513
|
+
input,
|
|
2514
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.startHours.length > 0).length
|
|
2515
|
+
)
|
|
2516
|
+
},
|
|
2517
|
+
{
|
|
2518
|
+
id: "phase-playbook",
|
|
2519
|
+
version: COMPONENT_RULES_V2,
|
|
2520
|
+
label: "median measured session",
|
|
2521
|
+
unit: "minutes",
|
|
2522
|
+
band: { low: 10, high: 60 },
|
|
2523
|
+
evaluate: ({ reading }) => {
|
|
2524
|
+
const bucket = medianBucket(
|
|
2525
|
+
playbookHarnesses(reading).flatMap(
|
|
2526
|
+
(harness) => (harness.phase?.lengths ?? []).map((row) => ({
|
|
2527
|
+
bucket: row.bucket,
|
|
2528
|
+
count: row.sessions
|
|
2529
|
+
}))
|
|
2530
|
+
)
|
|
2531
|
+
);
|
|
2532
|
+
return bucket === void 0 ? void 0 : bucketMid(bucket);
|
|
2533
|
+
},
|
|
2534
|
+
coverage: (input) => harnessShare(input, ({ reading }) => playbookHarnesses(reading).length)
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
id: "git-ledger",
|
|
2538
|
+
version: COMPONENT_RULES_V2,
|
|
2539
|
+
label: "of changed lines are removals",
|
|
2540
|
+
unit: "share",
|
|
2541
|
+
// Most work adds more than it takes away. A ledger that removes as much as
|
|
2542
|
+
// it adds is the surprising one, and so is one that never removes.
|
|
2543
|
+
band: { low: 0.15, high: 0.35 },
|
|
2544
|
+
evaluate: ({ reading }) => {
|
|
2545
|
+
const changed = reading.git.additions + reading.git.removals;
|
|
2546
|
+
return changed > 0 ? reading.git.removals / changed : void 0;
|
|
2547
|
+
},
|
|
2548
|
+
coverage: gitCoverage
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
id: "coding-languages",
|
|
2552
|
+
version: COMPONENT_RULES_V2,
|
|
2553
|
+
label: "of changed lines are one file type",
|
|
2554
|
+
unit: "share",
|
|
2555
|
+
band: { low: 0.35, high: 0.7 },
|
|
2556
|
+
evaluate: ({ reading }) => {
|
|
2557
|
+
const named = reading.git.changedLinesByExtension.map((row) => ({
|
|
2558
|
+
value: row.changedLines
|
|
2559
|
+
}));
|
|
2560
|
+
const total = named.reduce((sum, row) => sum + row.value, 0) + reading.git.withheldExtensionLines;
|
|
2561
|
+
if (total <= 0 || named.length === 0) return void 0;
|
|
2562
|
+
return Math.max(...named.map((row) => row.value)) / total;
|
|
2563
|
+
},
|
|
2564
|
+
coverage: gitCoverage
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
id: "kit",
|
|
2568
|
+
version: COMPONENT_RULES_V2,
|
|
2569
|
+
label: "of skill and MCP calls go to one artifact",
|
|
2570
|
+
unit: "share",
|
|
2571
|
+
band: { low: 0.15, high: 0.4 },
|
|
2572
|
+
evaluate: ({ kit }) => {
|
|
2573
|
+
if (!kit) return void 0;
|
|
2574
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2575
|
+
for (const harness of kit) {
|
|
2576
|
+
for (const atom of [...harness.skills, ...harness.mcpServers]) {
|
|
2577
|
+
byName.set(atom.name, (byName.get(atom.name) ?? 0) + atom.callShare);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
return topShare([...byName.values()].map((share) => ({ value: share })));
|
|
2581
|
+
},
|
|
2582
|
+
coverage: (input) => harnessShare(
|
|
2583
|
+
input,
|
|
2584
|
+
({ kit }) => (kit ?? []).filter(
|
|
2585
|
+
(harness) => harness.skills.length > 0 || harness.mcpServers.length > 0
|
|
2586
|
+
).length
|
|
2587
|
+
)
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
id: "model-routing",
|
|
2591
|
+
version: COMPONENT_RULES_V2,
|
|
2592
|
+
label: "of main-loop tokens run on one model",
|
|
2593
|
+
unit: "share",
|
|
2594
|
+
band: { low: 0.4, high: 0.85 },
|
|
2595
|
+
evaluate: ({ reading }) => {
|
|
2596
|
+
const main = reading.harnesses.flatMap((harness) => [
|
|
2597
|
+
...harness.routing?.main ?? []
|
|
2598
|
+
]);
|
|
2599
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
2600
|
+
for (const row of main) {
|
|
2601
|
+
byModel.set(row.model, (byModel.get(row.model) ?? 0) + row.tokens);
|
|
2602
|
+
}
|
|
2603
|
+
return topShare(
|
|
2604
|
+
[...byModel.values()].map((tokens) => ({ value: tokens }))
|
|
2605
|
+
);
|
|
2606
|
+
},
|
|
2607
|
+
coverage: (input) => harnessShare(
|
|
2608
|
+
input,
|
|
2609
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.routing).length
|
|
2610
|
+
)
|
|
2611
|
+
},
|
|
2612
|
+
{
|
|
2613
|
+
id: "delegation",
|
|
2614
|
+
version: COMPONENT_RULES_V2,
|
|
2615
|
+
label: "of tool calls run inside a subagent",
|
|
2616
|
+
unit: "share",
|
|
2617
|
+
band: { low: 0, high: 0.3 },
|
|
2618
|
+
evaluate: ({ reading }) => {
|
|
2619
|
+
let main = 0;
|
|
2620
|
+
let subagents = 0;
|
|
2621
|
+
for (const harness of reading.harnesses) {
|
|
2622
|
+
main += harness.delegation?.mainToolCalls ?? 0;
|
|
2623
|
+
subagents += harness.delegation?.subagentToolCalls ?? 0;
|
|
2624
|
+
}
|
|
2625
|
+
const total = main + subagents;
|
|
2626
|
+
return total > 0 ? subagents / total : void 0;
|
|
2627
|
+
},
|
|
2628
|
+
coverage: (input) => harnessShare(
|
|
2629
|
+
input,
|
|
2630
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.delegation).length
|
|
2631
|
+
)
|
|
2632
|
+
}
|
|
2633
|
+
];
|
|
2634
|
+
|
|
2635
|
+
// ../workflow-rules/src/metricRules.ts
|
|
2636
|
+
var METRIC_RULES_V2 = "metric-rules/v2";
|
|
2151
2637
|
var METRIC_RULES = [
|
|
2152
2638
|
{
|
|
2153
2639
|
id: "late-night-commits",
|
|
2154
|
-
version:
|
|
2640
|
+
version: METRIC_RULES_V2,
|
|
2155
2641
|
label: "of commits land between 23:00 and 03:00",
|
|
2156
2642
|
kind: "exact",
|
|
2157
2643
|
unit: "share",
|
|
2158
|
-
|
|
2159
|
-
// Most commit activity clusters in daytime hours; a wide late-night
|
|
2160
|
-
// share is the surprising case this metric exists to surface.
|
|
2644
|
+
counts: "all",
|
|
2161
2645
|
band: { low: 0, high: 0.15 },
|
|
2162
|
-
evaluate: (
|
|
2163
|
-
const git =
|
|
2164
|
-
if (
|
|
2165
|
-
return git.lateNightCommits / git.
|
|
2646
|
+
evaluate: (reading) => {
|
|
2647
|
+
const git = reading.git;
|
|
2648
|
+
if (git.commits === 0) return void 0;
|
|
2649
|
+
return git.lateNightCommits / git.commits;
|
|
2166
2650
|
}
|
|
2167
2651
|
},
|
|
2168
2652
|
{
|
|
2169
2653
|
id: "parallel-projects",
|
|
2170
|
-
version:
|
|
2654
|
+
version: METRIC_RULES_V2,
|
|
2171
2655
|
label: "projects run in parallel on a median active day",
|
|
2172
2656
|
kind: "proxy",
|
|
2173
2657
|
unit: "count",
|
|
2174
|
-
|
|
2658
|
+
counts: "all",
|
|
2175
2659
|
band: { low: 1, high: 1.5 },
|
|
2176
|
-
evaluate: (
|
|
2177
|
-
const days = facts.activeDays;
|
|
2178
|
-
if (!days || days.length === 0) return void 0;
|
|
2179
|
-
return median(days.map((d) => d.parallelProjectCount));
|
|
2180
|
-
}
|
|
2181
|
-
},
|
|
2182
|
-
{
|
|
2183
|
-
id: "model-switches-mid-run",
|
|
2184
|
-
version: METRIC_RULES_V1,
|
|
2185
|
-
label: "of sessions switch model mid-run",
|
|
2186
|
-
kind: "exact",
|
|
2187
|
-
unit: "share",
|
|
2188
|
-
harnessSupport: "all",
|
|
2189
|
-
band: { low: 0, high: 0.1 },
|
|
2190
|
-
evaluate: (facts) => shareOf(
|
|
2191
|
-
facts.sessions?.filter(
|
|
2192
|
-
(session) => session.modelSwitched !== void 0
|
|
2193
|
-
),
|
|
2194
|
-
(session) => session.modelSwitched === true
|
|
2195
|
-
)
|
|
2660
|
+
evaluate: (reading) => median(reading.parallelProjectDays)
|
|
2196
2661
|
},
|
|
2197
2662
|
{
|
|
2198
2663
|
id: "thinking-share",
|
|
2199
|
-
version:
|
|
2664
|
+
version: METRIC_RULES_V2,
|
|
2200
2665
|
label: "of response tokens are thinking",
|
|
2201
2666
|
kind: "proxy",
|
|
2202
2667
|
unit: "share",
|
|
2203
|
-
|
|
2668
|
+
counts: (harness) => harness.thinking !== void 0,
|
|
2204
2669
|
band: { low: 0.1, high: 0.3 },
|
|
2205
|
-
evaluate: (
|
|
2206
|
-
const sessions = facts.sessions?.filter(
|
|
2207
|
-
(session) => session.harness !== "claude-code" && session.thinkingTokens !== void 0 && session.responseTokens !== void 0
|
|
2208
|
-
);
|
|
2209
|
-
if (!sessions || sessions.length === 0) return void 0;
|
|
2670
|
+
evaluate: (reading) => {
|
|
2210
2671
|
let thinking = 0;
|
|
2211
2672
|
let response = 0;
|
|
2212
|
-
for (const
|
|
2213
|
-
thinking +=
|
|
2214
|
-
response +=
|
|
2673
|
+
for (const harness of reading.harnesses) {
|
|
2674
|
+
thinking += harness.thinking?.thinkingTokens ?? 0;
|
|
2675
|
+
response += harness.thinking?.responseTokens ?? 0;
|
|
2215
2676
|
}
|
|
2216
2677
|
return response > 0 ? thinking / response : void 0;
|
|
2217
2678
|
}
|
|
2218
2679
|
},
|
|
2219
2680
|
{
|
|
2220
|
-
id: "
|
|
2221
|
-
version:
|
|
2681
|
+
id: "effort-levels",
|
|
2682
|
+
version: METRIC_RULES_V2,
|
|
2222
2683
|
label: "of turns run at high effort",
|
|
2223
2684
|
kind: "exact",
|
|
2224
2685
|
unit: "share",
|
|
2225
|
-
|
|
2226
|
-
harnessSupport: ["claude-code", "codex"],
|
|
2686
|
+
counts: (harness) => harness.effort !== void 0,
|
|
2227
2687
|
band: { low: 0.2, high: 0.5 },
|
|
2228
|
-
evaluate: (
|
|
2229
|
-
const sessions = facts.sessions?.filter((s) => s.effortTurns);
|
|
2230
|
-
if (!sessions || sessions.length === 0) return void 0;
|
|
2688
|
+
evaluate: (reading) => {
|
|
2231
2689
|
let high = 0;
|
|
2232
2690
|
let total = 0;
|
|
2233
|
-
for (const
|
|
2234
|
-
|
|
2235
|
-
|
|
2691
|
+
for (const harness of reading.harnesses) {
|
|
2692
|
+
for (const row of harness.effort ?? []) {
|
|
2693
|
+
total += row.turns;
|
|
2694
|
+
if (row.level === "high") high += row.turns;
|
|
2695
|
+
}
|
|
2236
2696
|
}
|
|
2237
2697
|
return total > 0 ? high / total : void 0;
|
|
2238
2698
|
}
|
|
2239
2699
|
},
|
|
2240
2700
|
{
|
|
2241
|
-
id: "
|
|
2242
|
-
version:
|
|
2243
|
-
label: "
|
|
2244
|
-
kind: "exact",
|
|
2245
|
-
unit: "share",
|
|
2246
|
-
harnessSupport: ["claude-code", "codex"],
|
|
2247
|
-
band: { low: 0, high: 0.1 },
|
|
2248
|
-
evaluate: (facts) => shareOf(
|
|
2249
|
-
facts.sessions?.filter((s) => s.effortTurns),
|
|
2250
|
-
(s) => s.effortChangedMidRun === true
|
|
2251
|
-
)
|
|
2252
|
-
},
|
|
2253
|
-
{
|
|
2254
|
-
id: "longest-turn-duration",
|
|
2255
|
-
version: METRIC_RULES_V1,
|
|
2256
|
-
label: "longest recorded turn duration",
|
|
2701
|
+
id: "turn-duration",
|
|
2702
|
+
version: METRIC_RULES_V2,
|
|
2703
|
+
label: "median turn duration",
|
|
2257
2704
|
kind: "exact",
|
|
2258
2705
|
unit: "minutes",
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2706
|
+
counts: (harness) => harness.turnDurations !== void 0,
|
|
2707
|
+
band: { low: 0.25, high: 2 },
|
|
2708
|
+
evaluate: (reading) => {
|
|
2709
|
+
const bucket = medianBucket(
|
|
2710
|
+
reading.harnesses.flatMap(
|
|
2711
|
+
(harness) => (harness.turnDurations?.buckets ?? []).map((row) => ({
|
|
2712
|
+
bucket: row.bucket,
|
|
2713
|
+
count: row.turns
|
|
2714
|
+
}))
|
|
2715
|
+
)
|
|
2716
|
+
);
|
|
2717
|
+
return bucket === void 0 ? void 0 : bucketMid(bucket) / 60;
|
|
2266
2718
|
}
|
|
2267
2719
|
},
|
|
2268
2720
|
{
|
|
2269
2721
|
id: "question-back-share",
|
|
2270
|
-
version:
|
|
2722
|
+
version: METRIC_RULES_V2,
|
|
2271
2723
|
label: "of turns end with a question back to the human",
|
|
2272
2724
|
kind: "proxy",
|
|
2273
2725
|
unit: "share",
|
|
2274
|
-
|
|
2726
|
+
counts: (harness) => harness.questions !== void 0,
|
|
2275
2727
|
band: { low: 0, high: 0.15 },
|
|
2276
|
-
evaluate: (
|
|
2277
|
-
const sessions = facts.sessions?.filter(
|
|
2278
|
-
(session) => session.questionBackTurns !== void 0 && session.totalTurns !== void 0
|
|
2279
|
-
);
|
|
2280
|
-
if (!sessions || sessions.length === 0) return void 0;
|
|
2728
|
+
evaluate: (reading) => {
|
|
2281
2729
|
let asked = 0;
|
|
2282
2730
|
let turns = 0;
|
|
2283
|
-
for (const
|
|
2284
|
-
asked +=
|
|
2285
|
-
turns +=
|
|
2731
|
+
for (const harness of reading.harnesses) {
|
|
2732
|
+
asked += harness.questions?.asked ?? 0;
|
|
2733
|
+
turns += harness.questions?.turns ?? 0;
|
|
2286
2734
|
}
|
|
2287
2735
|
return turns > 0 ? asked / turns : void 0;
|
|
2288
2736
|
}
|
|
2289
2737
|
},
|
|
2290
2738
|
{
|
|
2291
2739
|
id: "web-searches-per-active-day",
|
|
2292
|
-
version:
|
|
2740
|
+
version: METRIC_RULES_V2,
|
|
2293
2741
|
label: "web searches per active day, inside the harness",
|
|
2294
2742
|
kind: "proxy",
|
|
2295
2743
|
unit: "count",
|
|
2296
|
-
|
|
2744
|
+
counts: (harness) => harness.webSearches !== void 0,
|
|
2297
2745
|
band: { low: 0, high: 4 },
|
|
2298
|
-
evaluate: (
|
|
2299
|
-
|
|
2300
|
-
|
|
2746
|
+
evaluate: (reading) => {
|
|
2747
|
+
if (reading.webSearchDays === 0) return void 0;
|
|
2748
|
+
const total = reading.harnesses.reduce(
|
|
2749
|
+
(sum, harness) => sum + (harness.webSearches ?? 0),
|
|
2750
|
+
0
|
|
2301
2751
|
);
|
|
2302
|
-
|
|
2303
|
-
const total = days.reduce((sum, d) => sum + (d.webSearches ?? 0), 0);
|
|
2304
|
-
return total / days.length;
|
|
2752
|
+
return total / reading.webSearchDays;
|
|
2305
2753
|
}
|
|
2306
2754
|
}
|
|
2307
2755
|
];
|
|
2308
2756
|
|
|
2309
2757
|
// ../workflow-rules/src/types.ts
|
|
2310
|
-
function harnessLabel(name) {
|
|
2311
|
-
switch (name) {
|
|
2312
|
-
case "claude-code":
|
|
2313
|
-
return "Claude Code";
|
|
2314
|
-
case "codex":
|
|
2315
|
-
return "Codex";
|
|
2316
|
-
case "opencode":
|
|
2317
|
-
return "opencode";
|
|
2318
|
-
case "pi-mono":
|
|
2319
|
-
return "Pi";
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
2758
|
var PHASES = [
|
|
2323
2759
|
"scout",
|
|
2324
2760
|
"build",
|
|
@@ -2327,39 +2763,6 @@ var PHASES = [
|
|
|
2327
2763
|
"unknown"
|
|
2328
2764
|
];
|
|
2329
2765
|
|
|
2330
|
-
// ../workflow-rules/src/fit.ts
|
|
2331
|
-
function coverageFor(harnessSupport, syncedHarnesses) {
|
|
2332
|
-
if (harnessSupport === "all") return 1;
|
|
2333
|
-
if (syncedHarnesses.length === 0) return 0;
|
|
2334
|
-
const supported = new Set(harnessSupport);
|
|
2335
|
-
const counted = syncedHarnesses.filter((h) => supported.has(h));
|
|
2336
|
-
return counted.length / syncedHarnesses.length;
|
|
2337
|
-
}
|
|
2338
|
-
function coverageTag(harnessSupport, syncedHarnesses) {
|
|
2339
|
-
if (harnessSupport === "all") return void 0;
|
|
2340
|
-
const supported = new Set(harnessSupport);
|
|
2341
|
-
const counted = syncedHarnesses.filter((h) => supported.has(h));
|
|
2342
|
-
if (counted.length === 0 || counted.length === syncedHarnesses.length)
|
|
2343
|
-
return void 0;
|
|
2344
|
-
return `counts: ${counted.map(harnessLabel).join(" \xB7 ")}`;
|
|
2345
|
-
}
|
|
2346
|
-
function buildFitInputs(facts, syncedHarnesses) {
|
|
2347
|
-
const rows = [];
|
|
2348
|
-
for (const rule of METRIC_RULES) {
|
|
2349
|
-
const value = rule.evaluate(facts);
|
|
2350
|
-
if (value === void 0) continue;
|
|
2351
|
-
rows.push({
|
|
2352
|
-
metricId: rule.id,
|
|
2353
|
-
ruleVersion: rule.version,
|
|
2354
|
-
value,
|
|
2355
|
-
band: rule.band,
|
|
2356
|
-
coverage: coverageFor(rule.harnessSupport, syncedHarnesses),
|
|
2357
|
-
coverageTag: coverageTag(rule.harnessSupport, syncedHarnesses)
|
|
2358
|
-
});
|
|
2359
|
-
}
|
|
2360
|
-
return rows;
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
2766
|
// ../workflow-rules/src/phaseRules.ts
|
|
2364
2767
|
var PHASE_RULES_V1 = "phase-rules/v1";
|
|
2365
2768
|
var UNKNOWN_GATE = 0.2;
|
|
@@ -2778,8 +3181,49 @@ function deriveSessionPhases(events, ruleSet = PHASE_RULES_V1, harness) {
|
|
|
2778
3181
|
};
|
|
2779
3182
|
}
|
|
2780
3183
|
|
|
2781
|
-
// ../workflow-rules/src/
|
|
2782
|
-
|
|
3184
|
+
// ../workflow-rules/src/workflowRows.ts
|
|
3185
|
+
function metricRowId(metricId) {
|
|
3186
|
+
return `metric:${metricId}`;
|
|
3187
|
+
}
|
|
3188
|
+
function componentRowId(componentId) {
|
|
3189
|
+
return `component:${componentId}`;
|
|
3190
|
+
}
|
|
3191
|
+
var WORKFLOW_ROW_ORDER = [
|
|
3192
|
+
{
|
|
3193
|
+
rowId: "component:activity-heatmap",
|
|
3194
|
+
name: "When work happens",
|
|
3195
|
+
flat: false
|
|
3196
|
+
},
|
|
3197
|
+
{ rowId: "component:start-hours", name: "Session start times", flat: false },
|
|
3198
|
+
{
|
|
3199
|
+
rowId: "metric:late-night-commits",
|
|
3200
|
+
name: "Late-night commits",
|
|
3201
|
+
flat: true
|
|
3202
|
+
},
|
|
3203
|
+
{ rowId: "component:phase-playbook", name: "Session length", flat: false },
|
|
3204
|
+
{ rowId: "component:git-ledger", name: "Lines changed", flat: false },
|
|
3205
|
+
{ rowId: "component:coding-languages", name: "Languages", flat: false },
|
|
3206
|
+
{ rowId: "component:kit", name: "Skills and MCP", flat: false },
|
|
3207
|
+
{ rowId: "component:model-routing", name: "Models used", flat: false },
|
|
3208
|
+
{ rowId: "component:delegation", name: "Subagents", flat: false },
|
|
3209
|
+
{ rowId: "metric:effort-levels", name: "Effort levels", flat: false },
|
|
3210
|
+
{ rowId: "metric:thinking-share", name: "Thinking tokens", flat: false },
|
|
3211
|
+
{ rowId: "metric:turn-duration", name: "Turn length", flat: false },
|
|
3212
|
+
{ rowId: "metric:question-back-share", name: "Questions asked", flat: true },
|
|
3213
|
+
{
|
|
3214
|
+
rowId: "metric:web-searches-per-active-day",
|
|
3215
|
+
name: "Web searches",
|
|
3216
|
+
flat: true
|
|
3217
|
+
},
|
|
3218
|
+
{ rowId: "metric:parallel-projects", name: "Parallel projects", flat: true }
|
|
3219
|
+
];
|
|
3220
|
+
var ORDER_INDEX = new Map(
|
|
3221
|
+
WORKFLOW_ROW_ORDER.map((row, index) => [row.rowId, index])
|
|
3222
|
+
);
|
|
3223
|
+
var KNOWN_ROW_IDS = /* @__PURE__ */ new Set([
|
|
3224
|
+
...METRIC_RULES.map((rule) => metricRowId(rule.id)),
|
|
3225
|
+
...COMPONENT_RULES.map((rule) => componentRowId(rule.id))
|
|
3226
|
+
]);
|
|
2783
3227
|
|
|
2784
3228
|
// src/harness/shared/window.ts
|
|
2785
3229
|
var DEFAULT_WINDOW_DAYS = 30;
|
|
@@ -3040,19 +3484,8 @@ function buildPayload(input) {
|
|
|
3040
3484
|
function toPayloadWorkflow(extraction) {
|
|
3041
3485
|
return {
|
|
3042
3486
|
aggregateVersion: extraction.aggregateVersion,
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
),
|
|
3046
|
-
git: extraction.git,
|
|
3047
|
-
metrics: extraction.metricInputs.map((row) => ({
|
|
3048
|
-
metricId: row.metricId,
|
|
3049
|
-
ruleVersion: row.ruleVersion,
|
|
3050
|
-
value: row.value,
|
|
3051
|
-
band: row.band,
|
|
3052
|
-
coverage: row.coverage,
|
|
3053
|
-
...row.coverageTag === void 0 ? {} : { coverageTag: row.coverageTag }
|
|
3054
|
-
})),
|
|
3055
|
-
utcOffsetMinutes: extraction.utcOffsetMinutes
|
|
3487
|
+
utcOffsetMinutes: extraction.utcOffsetMinutes,
|
|
3488
|
+
days: extraction.days
|
|
3056
3489
|
};
|
|
3057
3490
|
}
|
|
3058
3491
|
function mergeKeptPrivate(halves) {
|
|
@@ -3085,7 +3518,7 @@ function buildSyncBody(built, syncConfig, autoSync, trigger = "manual", workflow
|
|
|
3085
3518
|
}
|
|
3086
3519
|
|
|
3087
3520
|
// src/workflow/reducer.ts
|
|
3088
|
-
var WORKFLOW_AGGREGATE_VERSION =
|
|
3521
|
+
var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V2;
|
|
3089
3522
|
function createWorkflowLocalSources() {
|
|
3090
3523
|
return { projectWorkspaces: /* @__PURE__ */ new Set(), activeProjectDays: /* @__PURE__ */ new Map() };
|
|
3091
3524
|
}
|
|
@@ -3097,10 +3530,10 @@ var emptyPhase = () => ({
|
|
|
3097
3530
|
unknown: 0
|
|
3098
3531
|
});
|
|
3099
3532
|
var finiteNonnegative = (value) => value !== void 0 && Number.isFinite(value) && value > 0 ? value : 0;
|
|
3100
|
-
var isHighEffort = (effort) => ["high", "xhigh", "max", "ultra"].includes(effort.toLowerCase());
|
|
3101
3533
|
var bump2 = (map, key, amount = 1) => {
|
|
3102
3534
|
map.set(key, (map.get(key) ?? 0) + amount);
|
|
3103
3535
|
};
|
|
3536
|
+
var utcDateOf = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3104
3537
|
var PHASE_RANK = {
|
|
3105
3538
|
verify: 4,
|
|
3106
3539
|
handoff: 3,
|
|
@@ -3145,18 +3578,9 @@ function reduceEventBatches(recorded, harness) {
|
|
|
3145
3578
|
}
|
|
3146
3579
|
return output;
|
|
3147
3580
|
}
|
|
3148
|
-
var
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
);
|
|
3152
|
-
let runs = 0;
|
|
3153
|
-
let inside = false;
|
|
3154
|
-
for (const phase of phases) {
|
|
3155
|
-
if (phase === "verify" && !inside) runs++;
|
|
3156
|
-
inside = phase === "verify";
|
|
3157
|
-
}
|
|
3158
|
-
return runs;
|
|
3159
|
-
};
|
|
3581
|
+
var hasVerifyRun = (events, harness) => events.some(
|
|
3582
|
+
(event) => deriveSessionPhases([event], PHASE_RULES_V1, harness).phaseEvents.verify > 0
|
|
3583
|
+
);
|
|
3160
3584
|
function shellIncludes(arg, head) {
|
|
3161
3585
|
return arg.split(/(?:&&|\|\||;|\|)/).some((part) => part.trim() === head || part.trim().startsWith(`${head} `));
|
|
3162
3586
|
}
|
|
@@ -3174,10 +3598,47 @@ function sessionState() {
|
|
|
3174
3598
|
lastTs: void 0
|
|
3175
3599
|
};
|
|
3176
3600
|
}
|
|
3601
|
+
function dayState() {
|
|
3602
|
+
return {
|
|
3603
|
+
sessions: 0,
|
|
3604
|
+
startHours: /* @__PURE__ */ new Map(),
|
|
3605
|
+
phase: {
|
|
3606
|
+
sessions: 0,
|
|
3607
|
+
phaseSec: emptyPhase(),
|
|
3608
|
+
phaseEvents: emptyPhase(),
|
|
3609
|
+
waitingSec: 0,
|
|
3610
|
+
idleSec: 0,
|
|
3611
|
+
sessionsWithVerify: 0,
|
|
3612
|
+
sessionsWithHandoff: 0,
|
|
3613
|
+
lengths: /* @__PURE__ */ new Map()
|
|
3614
|
+
},
|
|
3615
|
+
routing: { main: /* @__PURE__ */ new Map(), subagents: /* @__PURE__ */ new Map() },
|
|
3616
|
+
hasRouting: false,
|
|
3617
|
+
delegation: {
|
|
3618
|
+
mainToolCalls: 0,
|
|
3619
|
+
subagentToolCalls: 0,
|
|
3620
|
+
widestFanOut: 0,
|
|
3621
|
+
mostSubagents: 0
|
|
3622
|
+
},
|
|
3623
|
+
hasDelegation: false,
|
|
3624
|
+
activity: /* @__PURE__ */ new Map(),
|
|
3625
|
+
effort: /* @__PURE__ */ new Map(),
|
|
3626
|
+
hasEffort: false,
|
|
3627
|
+
thinking: { thinkingTokens: 0, responseTokens: 0 },
|
|
3628
|
+
hasThinking: false,
|
|
3629
|
+
turnDurations: /* @__PURE__ */ new Map(),
|
|
3630
|
+
hasDurations: false,
|
|
3631
|
+
questions: { asked: 0, turns: 0 },
|
|
3632
|
+
hasQuestions: false,
|
|
3633
|
+
webSearches: 0,
|
|
3634
|
+
hasWebSearches: false
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3177
3637
|
function createHarnessWorkflowReducer(harness, localSources = createWorkflowLocalSources()) {
|
|
3178
3638
|
const sessions = /* @__PURE__ */ new Map();
|
|
3179
|
-
const
|
|
3639
|
+
const eventCells = /* @__PURE__ */ new Map();
|
|
3180
3640
|
const webSearchesByDate = /* @__PURE__ */ new Map();
|
|
3641
|
+
const eventDates = /* @__PURE__ */ new Set();
|
|
3181
3642
|
let finished;
|
|
3182
3643
|
const getSession = (key) => {
|
|
3183
3644
|
let state = sessions.get(key);
|
|
@@ -3197,7 +3658,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3197
3658
|
state.parentSession ??= observation.parentSession;
|
|
3198
3659
|
state.sidechain ||= observation.sidechain === true;
|
|
3199
3660
|
const at = new Date(observation.tsMs);
|
|
3200
|
-
const date =
|
|
3661
|
+
const date = utcDateOf(observation.tsMs);
|
|
3201
3662
|
if (observation.projectWorkspace) {
|
|
3202
3663
|
state.projectWorkspaces.add(observation.projectWorkspace);
|
|
3203
3664
|
localSources.projectWorkspaces.add(observation.projectWorkspace);
|
|
@@ -3208,7 +3669,10 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3208
3669
|
event: [observation.tsMs, observation.tool, arg],
|
|
3209
3670
|
...observation.batchId ? { batchId: observation.batchId } : {}
|
|
3210
3671
|
});
|
|
3211
|
-
|
|
3672
|
+
eventDates.add(date);
|
|
3673
|
+
const cells = eventCells.get(date) ?? /* @__PURE__ */ new Map();
|
|
3674
|
+
bump2(cells, `${at.getUTCDay()}:${at.getUTCHours()}`);
|
|
3675
|
+
eventCells.set(date, cells);
|
|
3212
3676
|
if (["WebSearch", "web_search", "websearch"].includes(observation.tool))
|
|
3213
3677
|
bump2(webSearchesByDate, date);
|
|
3214
3678
|
} else if (observation.type === "response") {
|
|
@@ -3234,135 +3698,118 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3234
3698
|
},
|
|
3235
3699
|
finish() {
|
|
3236
3700
|
if (finished) return finished;
|
|
3237
|
-
const
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3701
|
+
const days = /* @__PURE__ */ new Map();
|
|
3702
|
+
const dayOf = (date) => {
|
|
3703
|
+
let state = days.get(date);
|
|
3704
|
+
if (!state) {
|
|
3705
|
+
state = dayState();
|
|
3706
|
+
days.set(date, state);
|
|
3707
|
+
}
|
|
3708
|
+
return state;
|
|
3244
3709
|
};
|
|
3245
|
-
|
|
3246
|
-
let idleSec = 0;
|
|
3247
|
-
let mainToolCalls = 0;
|
|
3248
|
-
let subagentToolCalls = 0;
|
|
3710
|
+
const windowPhaseSec = emptyPhase();
|
|
3249
3711
|
let phaseSessionCount = 0;
|
|
3250
3712
|
for (const state of sessions.values()) {
|
|
3713
|
+
if (state.firstTs === void 0) continue;
|
|
3714
|
+
const day = dayOf(utcDateOf(state.firstTs));
|
|
3251
3715
|
const events = reduceEventBatches(state.events, harness);
|
|
3252
3716
|
const responses = [...state.responses.values()];
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
);
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3717
|
+
day.sessions++;
|
|
3718
|
+
const startHour = new Date(state.firstTs).getUTCHours();
|
|
3719
|
+
day.startHours.set(startHour, (day.startHours.get(startHour) ?? 0) + 1);
|
|
3720
|
+
const phases = deriveSessionPhases(events, PHASE_RULES_V1, harness);
|
|
3721
|
+
if (state.events.length > 0) phaseSessionCount++;
|
|
3722
|
+
day.phase.sessions++;
|
|
3723
|
+
for (const phase of PHASES) {
|
|
3724
|
+
day.phase.phaseSec[phase] += phases.phaseSec[phase];
|
|
3725
|
+
day.phase.phaseEvents[phase] += phases.phaseEvents[phase];
|
|
3726
|
+
windowPhaseSec[phase] += phases.phaseSec[phase];
|
|
3727
|
+
}
|
|
3728
|
+
day.phase.waitingSec += phases.waitingSec;
|
|
3729
|
+
day.phase.idleSec += phases.idleSec;
|
|
3730
|
+
if (phases.phaseEvents.verify > 0) day.phase.sessionsWithVerify++;
|
|
3731
|
+
if (phases.phaseEvents.handoff > 0) day.phase.sessionsWithHandoff++;
|
|
3732
|
+
const measuredSec = PHASES.reduce(
|
|
3733
|
+
(sum, phase) => sum + phases.phaseSec[phase],
|
|
3734
|
+
0
|
|
3266
3735
|
);
|
|
3267
|
-
const
|
|
3268
|
-
|
|
3736
|
+
const bucket = logBucket(measuredSec / 60);
|
|
3737
|
+
const merged = events.some(
|
|
3738
|
+
([, tool, arg]) => ["Bash", "bash", "shell", "local_shell", "exec_command"].includes(
|
|
3739
|
+
tool
|
|
3740
|
+
) && shellIncludes(arg, "gh pr merge")
|
|
3269
3741
|
);
|
|
3270
|
-
const
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
responseTokens: outputResponses.reduce(
|
|
3281
|
-
(sum, response) => sum + (response.responseTokens ?? 0),
|
|
3282
|
-
0
|
|
3283
|
-
)
|
|
3284
|
-
} : {},
|
|
3285
|
-
...harness === "pi-mono" ? {} : {
|
|
3286
|
-
questionBackTurns: [...state.turns.values()].filter(Boolean).length,
|
|
3287
|
-
totalTurns: state.turns.size
|
|
3288
|
-
}
|
|
3742
|
+
const verified = hasVerifyRun(events, harness);
|
|
3743
|
+
const openedWithScout = (events[0] ? deriveSessionPhases([events[0]], PHASE_RULES_V1, harness).phaseEvents.scout : 0) > 0;
|
|
3744
|
+
const length = day.phase.lengths.get(bucket) ?? {
|
|
3745
|
+
bucket,
|
|
3746
|
+
sessions: 0,
|
|
3747
|
+
phaseSec: emptyPhase(),
|
|
3748
|
+
merged: 0,
|
|
3749
|
+
verified: 0,
|
|
3750
|
+
mergedVerified: 0,
|
|
3751
|
+
openedWithScout: 0
|
|
3289
3752
|
};
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
sessionFact.longestTurnDurationSec = Math.max(...durations);
|
|
3299
|
-
facts.push(sessionFact);
|
|
3753
|
+
length.sessions++;
|
|
3754
|
+
for (const phase of PHASES)
|
|
3755
|
+
length.phaseSec[phase] += phases.phaseSec[phase];
|
|
3756
|
+
if (merged) length.merged++;
|
|
3757
|
+
if (verified) length.verified++;
|
|
3758
|
+
if (merged && verified) length.mergedVerified++;
|
|
3759
|
+
if (openedWithScout) length.openedWithScout++;
|
|
3760
|
+
day.phase.lengths.set(bucket, length);
|
|
3300
3761
|
const routing = state.sidechain || state.parentSession ? "subagents" : "main";
|
|
3301
3762
|
for (const response of responses) {
|
|
3302
3763
|
if (!response.model) continue;
|
|
3764
|
+
day.hasRouting = true;
|
|
3303
3765
|
bump2(
|
|
3304
|
-
|
|
3766
|
+
day.routing[routing],
|
|
3305
3767
|
response.model,
|
|
3306
3768
|
response.routingTokens ?? response.responseTokens ?? 0
|
|
3307
3769
|
);
|
|
3308
3770
|
}
|
|
3309
|
-
if (routing === "subagents")
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
for (const
|
|
3314
|
-
|
|
3315
|
-
|
|
3771
|
+
if (routing === "subagents") {
|
|
3772
|
+
day.delegation.subagentToolCalls += state.events.length;
|
|
3773
|
+
day.hasDelegation ||= state.events.length > 0;
|
|
3774
|
+
} else day.delegation.mainToolCalls += state.events.length;
|
|
3775
|
+
for (const response of responses) {
|
|
3776
|
+
if (response.effort) {
|
|
3777
|
+
day.hasEffort = true;
|
|
3778
|
+
const level = effortLevelOf(response.effort);
|
|
3779
|
+
day.effort.set(level, (day.effort.get(level) ?? 0) + 1);
|
|
3780
|
+
}
|
|
3781
|
+
if (response.thinkingTokens !== void 0) {
|
|
3782
|
+
day.hasThinking = true;
|
|
3783
|
+
day.thinking.thinkingTokens += response.thinkingTokens;
|
|
3784
|
+
day.thinking.responseTokens += response.responseTokens ?? 0;
|
|
3785
|
+
}
|
|
3786
|
+
if (response.durationSec !== void 0) {
|
|
3787
|
+
day.hasDurations = true;
|
|
3788
|
+
const durationBucket = logBucket(response.durationSec);
|
|
3789
|
+
day.turnDurations.set(
|
|
3790
|
+
durationBucket,
|
|
3791
|
+
(day.turnDurations.get(durationBucket) ?? 0) + 1
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
if (harness !== "pi-mono") {
|
|
3796
|
+
day.hasQuestions = true;
|
|
3797
|
+
day.questions.turns += state.turns.size;
|
|
3798
|
+
day.questions.asked += [...state.turns.values()].filter(
|
|
3799
|
+
Boolean
|
|
3800
|
+
).length;
|
|
3316
3801
|
}
|
|
3317
|
-
waitingSec += phases.waitingSec;
|
|
3318
|
-
idleSec += phases.idleSec;
|
|
3319
|
-
const first = state.firstTs;
|
|
3320
|
-
const classifications = events.map(
|
|
3321
|
-
(event) => deriveSessionPhases([event], PHASE_RULES_V1, harness)
|
|
3322
|
-
);
|
|
3323
|
-
sessionRows2.push({
|
|
3324
|
-
startHourUtc: first === void 0 ? 0 : new Date(first).getUTCHours(),
|
|
3325
|
-
eventCount: events.length,
|
|
3326
|
-
phaseSec: phases.phaseSec,
|
|
3327
|
-
phaseEvents: phases.phaseEvents,
|
|
3328
|
-
waitingSec: phases.waitingSec,
|
|
3329
|
-
idleSec: phases.idleSec,
|
|
3330
|
-
merged: events.some(
|
|
3331
|
-
([, tool, arg]) => ["Bash", "bash", "shell", "local_shell", "exec_command"].includes(
|
|
3332
|
-
tool
|
|
3333
|
-
) && shellIncludes(arg, "gh pr merge")
|
|
3334
|
-
),
|
|
3335
|
-
verifyRuns: verifyRuns(events, harness),
|
|
3336
|
-
reviewRounds: events.filter(
|
|
3337
|
-
([, tool]) => ["mcp__curia__request_review", "request_review"].includes(tool)
|
|
3338
|
-
).length,
|
|
3339
|
-
openedWithScout: (classifications[0]?.phaseEvents.scout ?? 0) > 0
|
|
3340
|
-
});
|
|
3341
3802
|
}
|
|
3342
|
-
const
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
let day = Date.parse(
|
|
3351
|
-
`${new Date(state.firstTs).toISOString().slice(0, 10)}T00:00:00Z`
|
|
3352
|
-
);
|
|
3353
|
-
const lastDay = Date.parse(
|
|
3354
|
-
`${new Date(state.lastTs).toISOString().slice(0, 10)}T00:00:00Z`
|
|
3355
|
-
);
|
|
3356
|
-
while (day <= lastDay) {
|
|
3357
|
-
const date = new Date(day).toISOString().slice(0, 10);
|
|
3358
|
-
const projects = localSources.activeProjectDays.get(date) ?? /* @__PURE__ */ new Set();
|
|
3359
|
-
for (const project of state.projectWorkspaces) projects.add(project);
|
|
3360
|
-
if (projects.size > 0)
|
|
3361
|
-
localSources.activeProjectDays.set(date, projects);
|
|
3362
|
-
day += 864e5;
|
|
3803
|
+
for (const date of eventDates) {
|
|
3804
|
+
const day = dayOf(date);
|
|
3805
|
+
for (const [key, events] of eventCells.get(date) ?? []) {
|
|
3806
|
+
bump2(day.activity, key, events);
|
|
3807
|
+
}
|
|
3808
|
+
if (harness !== "pi-mono") {
|
|
3809
|
+
day.hasWebSearches = true;
|
|
3810
|
+
day.webSearches = webSearchesByDate.get(date) ?? 0;
|
|
3363
3811
|
}
|
|
3364
3812
|
}
|
|
3365
|
-
const projectsByDate = localSources.activeProjectDays;
|
|
3366
3813
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
3367
3814
|
for (const state of sessions.values()) {
|
|
3368
3815
|
if (!state.parentSession) continue;
|
|
@@ -3370,10 +3817,16 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3370
3817
|
children.push(state);
|
|
3371
3818
|
childrenByParent.set(state.parentSession, children);
|
|
3372
3819
|
}
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3820
|
+
for (const [parentKey, children] of childrenByParent) {
|
|
3821
|
+
const parent = sessions.get(parentKey);
|
|
3822
|
+
const anchor = parent?.firstTs ?? Math.min(...children.map((child) => child.firstTs ?? Infinity));
|
|
3823
|
+
if (!Number.isFinite(anchor)) continue;
|
|
3824
|
+
const day = dayOf(utcDateOf(anchor));
|
|
3825
|
+
day.hasDelegation = true;
|
|
3826
|
+
day.delegation.mostSubagents = Math.max(
|
|
3827
|
+
day.delegation.mostSubagents,
|
|
3828
|
+
children.length
|
|
3829
|
+
);
|
|
3377
3830
|
const boundaries = children.flatMap((child) => [
|
|
3378
3831
|
{ ts: child.firstTs ?? 0, delta: 1 },
|
|
3379
3832
|
{ ts: child.lastTs ?? child.firstTs ?? 0, delta: -1 }
|
|
@@ -3382,61 +3835,109 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3382
3835
|
let active = 0;
|
|
3383
3836
|
for (const boundary of boundaries) {
|
|
3384
3837
|
active += boundary.delta;
|
|
3385
|
-
widestFanOut = Math.max(
|
|
3838
|
+
day.delegation.widestFanOut = Math.max(
|
|
3839
|
+
day.delegation.widestFanOut,
|
|
3840
|
+
active
|
|
3841
|
+
);
|
|
3386
3842
|
}
|
|
3387
3843
|
}
|
|
3844
|
+
localSources.activeProjectDays.clear();
|
|
3845
|
+
for (const state of sessions.values()) {
|
|
3846
|
+
if (state.firstTs === void 0 || state.lastTs === void 0) continue;
|
|
3847
|
+
let day = Date.parse(`${utcDateOf(state.firstTs)}T00:00:00Z`);
|
|
3848
|
+
const lastDay = Date.parse(`${utcDateOf(state.lastTs)}T00:00:00Z`);
|
|
3849
|
+
while (day <= lastDay) {
|
|
3850
|
+
const date = utcDateOf(day);
|
|
3851
|
+
const projects = localSources.activeProjectDays.get(date) ?? /* @__PURE__ */ new Set();
|
|
3852
|
+
for (const project of state.projectWorkspaces) projects.add(project);
|
|
3853
|
+
if (projects.size > 0)
|
|
3854
|
+
localSources.activeProjectDays.set(date, projects);
|
|
3855
|
+
day += 864e5;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
const attributed = PHASES.reduce(
|
|
3859
|
+
(sum, phase) => sum + windowPhaseSec[phase],
|
|
3860
|
+
0
|
|
3861
|
+
);
|
|
3862
|
+
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
3863
|
+
const routesModels = harness === "claude-code" || harness === "opencode";
|
|
3388
3864
|
const asRows = (map) => {
|
|
3389
3865
|
const safe = /* @__PURE__ */ new Map();
|
|
3390
3866
|
for (const [model, tokens] of map) {
|
|
3391
3867
|
bump2(safe, sanitizeModelId(model), tokens);
|
|
3392
3868
|
}
|
|
3393
|
-
return [...safe].map(([model, tokens]) => ({ model, tokens }))
|
|
3869
|
+
return [...safe].map(([model, tokens]) => ({ model, tokens })).sort(
|
|
3870
|
+
(a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model)
|
|
3871
|
+
);
|
|
3394
3872
|
};
|
|
3395
|
-
const hasDelegation = subagentToolCalls > 0 || mostSubagents > 0 || widestFanOut > 0;
|
|
3396
3873
|
finished = {
|
|
3397
3874
|
aggregateVersion: WORKFLOW_AGGREGATE_VERSION,
|
|
3398
3875
|
harness,
|
|
3399
|
-
|
|
3876
|
+
gate: {
|
|
3400
3877
|
ruleVersion: PHASE_RULES_V1,
|
|
3401
3878
|
publishable: phaseSessionCount > 0 && unknown <= UNKNOWN_GATE,
|
|
3402
3879
|
sessions: sessions.size,
|
|
3403
|
-
|
|
3404
|
-
phaseEvents,
|
|
3405
|
-
waitingSec,
|
|
3406
|
-
idleSec,
|
|
3407
|
-
unknownShare: unknown,
|
|
3408
|
-
sessionRows: sessionRows2
|
|
3409
|
-
},
|
|
3410
|
-
facts: {
|
|
3411
|
-
sessions: facts,
|
|
3412
|
-
activeDays: [...projectsByDate].sort(([a], [b]) => a.localeCompare(b)).map(([date, projects]) => ({
|
|
3413
|
-
date,
|
|
3414
|
-
parallelProjectCount: projects.size,
|
|
3415
|
-
...harness === "pi-mono" ? {} : { webSearches: webSearchesByDate.get(date) ?? 0 }
|
|
3416
|
-
}))
|
|
3880
|
+
unknownShare: unknown
|
|
3417
3881
|
},
|
|
3418
|
-
...
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3882
|
+
days: [...days].sort(([a], [b]) => a.localeCompare(b)).map(([date, day]) => ({
|
|
3883
|
+
date,
|
|
3884
|
+
harness,
|
|
3885
|
+
sessions: day.sessions,
|
|
3886
|
+
startHours: [...day.startHours].map(([hourUtc, count]) => ({ hourUtc, sessions: count })).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
3887
|
+
...day.phase.sessions > 0 ? {
|
|
3888
|
+
phase: {
|
|
3889
|
+
ruleVersion: PHASE_RULES_V1,
|
|
3890
|
+
sessions: day.phase.sessions,
|
|
3891
|
+
phaseSec: day.phase.phaseSec,
|
|
3892
|
+
phaseEvents: day.phase.phaseEvents,
|
|
3893
|
+
waitingSec: day.phase.waitingSec,
|
|
3894
|
+
idleSec: day.phase.idleSec,
|
|
3895
|
+
sessionsWithVerify: day.phase.sessionsWithVerify,
|
|
3896
|
+
sessionsWithHandoff: day.phase.sessionsWithHandoff,
|
|
3897
|
+
bucketRuleVersion: LOG_BUCKETS_V1,
|
|
3898
|
+
lengths: [...day.phase.lengths.values()].sort(
|
|
3899
|
+
(a, b) => a.bucket - b.bucket
|
|
3900
|
+
)
|
|
3901
|
+
}
|
|
3902
|
+
} : {},
|
|
3903
|
+
...routesModels && day.hasRouting ? {
|
|
3904
|
+
routing: {
|
|
3905
|
+
main: asRows(day.routing.main),
|
|
3906
|
+
subagents: asRows(day.routing.subagents)
|
|
3907
|
+
}
|
|
3908
|
+
} : {},
|
|
3909
|
+
...day.hasDelegation ? { delegation: day.delegation } : {},
|
|
3910
|
+
activity: [...day.activity].map(([key, events]) => {
|
|
3911
|
+
const [weekdayUtc, hourUtc] = key.split(":").map(Number);
|
|
3912
|
+
return {
|
|
3913
|
+
weekdayUtc: weekdayUtc ?? 0,
|
|
3914
|
+
hourUtc: hourUtc ?? 0,
|
|
3915
|
+
events
|
|
3916
|
+
};
|
|
3917
|
+
}).sort(
|
|
3918
|
+
(a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc
|
|
3919
|
+
),
|
|
3920
|
+
...day.hasEffort ? {
|
|
3921
|
+
effort: EFFORT_LEVELS.flatMap((level) => {
|
|
3922
|
+
const turns = day.effort.get(level) ?? 0;
|
|
3923
|
+
return turns > 0 ? [{ level, turns }] : [];
|
|
3924
|
+
})
|
|
3925
|
+
} : {},
|
|
3926
|
+
...day.hasThinking ? { thinking: day.thinking } : {},
|
|
3927
|
+
...day.hasDurations ? {
|
|
3928
|
+
turnDurations: {
|
|
3929
|
+
bucketRuleVersion: LOG_BUCKETS_V1,
|
|
3930
|
+
buckets: [...day.turnDurations].map(([bucket, turns]) => ({ bucket, turns })).sort((a, b) => a.bucket - b.bucket)
|
|
3931
|
+
}
|
|
3932
|
+
} : {},
|
|
3933
|
+
...day.hasQuestions ? { questions: day.questions } : {},
|
|
3934
|
+
...day.hasWebSearches ? { webSearches: day.webSearches } : {}
|
|
3935
|
+
}))
|
|
3436
3936
|
};
|
|
3437
3937
|
sessions.clear();
|
|
3438
|
-
|
|
3938
|
+
eventCells.clear();
|
|
3439
3939
|
webSearchesByDate.clear();
|
|
3940
|
+
eventDates.clear();
|
|
3440
3941
|
return finished;
|
|
3441
3942
|
}
|
|
3442
3943
|
};
|
|
@@ -5962,11 +6463,11 @@ var defaultRunner = (cwd, args) => {
|
|
|
5962
6463
|
return null;
|
|
5963
6464
|
}
|
|
5964
6465
|
};
|
|
5965
|
-
var
|
|
6466
|
+
var emptyGitDay = () => ({
|
|
5966
6467
|
testFileRuleVersion: TEST_FILE_RULE_VERSION,
|
|
5967
6468
|
fileTypeRuleVersion: FILE_TYPE_RULE_VERSION,
|
|
5968
6469
|
commitSetRuleVersion: COMMIT_SET_RULE_VERSION,
|
|
5969
|
-
|
|
6470
|
+
commits: 0,
|
|
5970
6471
|
lateNightCommits: 0,
|
|
5971
6472
|
additions: 0,
|
|
5972
6473
|
removals: 0,
|
|
@@ -6116,9 +6617,25 @@ function extractGitWorkflow(options) {
|
|
|
6116
6617
|
const root = run(directory, ["rev-parse", "--show-toplevel"])?.trim();
|
|
6117
6618
|
if (root) roots.add(root);
|
|
6118
6619
|
}
|
|
6119
|
-
const
|
|
6120
|
-
const
|
|
6121
|
-
|
|
6620
|
+
const days = /* @__PURE__ */ new Map();
|
|
6621
|
+
const dayOf = (date) => {
|
|
6622
|
+
let day = days.get(date);
|
|
6623
|
+
if (!day) {
|
|
6624
|
+
const {
|
|
6625
|
+
changedLinesByExtension: _extensions,
|
|
6626
|
+
weekdayHourCells: _cells,
|
|
6627
|
+
...rest
|
|
6628
|
+
} = emptyGitDay();
|
|
6629
|
+
day = {
|
|
6630
|
+
...rest,
|
|
6631
|
+
changedLinesPerCommit: [],
|
|
6632
|
+
extensionLines: /* @__PURE__ */ new Map(),
|
|
6633
|
+
cells: /* @__PURE__ */ new Map()
|
|
6634
|
+
};
|
|
6635
|
+
days.set(date, day);
|
|
6636
|
+
}
|
|
6637
|
+
return day;
|
|
6638
|
+
};
|
|
6122
6639
|
const seenCommits = /* @__PURE__ */ new Set();
|
|
6123
6640
|
for (const root of roots) {
|
|
6124
6641
|
const history = run(root, [
|
|
@@ -6133,17 +6650,25 @@ function extractGitWorkflow(options) {
|
|
|
6133
6650
|
let current;
|
|
6134
6651
|
const finishCommit = () => {
|
|
6135
6652
|
if (!current?.included || !current.authored) return;
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6653
|
+
const day = dayOf(current.date);
|
|
6654
|
+
day.commits++;
|
|
6655
|
+
day.additions += current.additions;
|
|
6656
|
+
day.removals += current.removals;
|
|
6657
|
+
day.changedLinesPerCommit.push(current.changedLines);
|
|
6658
|
+
if (current.touchesTest) day.testFileCommits++;
|
|
6141
6659
|
const { weekdayUtc, hourUtc } = current.cell;
|
|
6142
6660
|
if (isLateNight(localHour(hourUtc, options.utcOffsetMinutes))) {
|
|
6143
|
-
|
|
6661
|
+
day.lateNightCommits++;
|
|
6144
6662
|
}
|
|
6145
6663
|
const cellKey = `${weekdayUtc}:${hourUtc}`;
|
|
6146
|
-
cells.set(cellKey, (cells.get(cellKey) ?? 0) + 1);
|
|
6664
|
+
day.cells.set(cellKey, (day.cells.get(cellKey) ?? 0) + 1);
|
|
6665
|
+
day.withheldExtensionLines += current.withheldLines;
|
|
6666
|
+
for (const [extension, lines2] of current.extensionLines) {
|
|
6667
|
+
day.extensionLines.set(
|
|
6668
|
+
extension,
|
|
6669
|
+
(day.extensionLines.get(extension) ?? 0) + lines2
|
|
6670
|
+
);
|
|
6671
|
+
}
|
|
6147
6672
|
};
|
|
6148
6673
|
const fields = history.split("\0");
|
|
6149
6674
|
for (let fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
|
|
@@ -6156,12 +6681,15 @@ function extractGitWorkflow(options) {
|
|
|
6156
6681
|
const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(hash);
|
|
6157
6682
|
current = {
|
|
6158
6683
|
included,
|
|
6684
|
+
date: included ? new Date(authoredMs).toISOString().slice(0, 10) : "",
|
|
6159
6685
|
cell: utcCell(included ? authoredMs : 0),
|
|
6160
6686
|
authored: false,
|
|
6161
6687
|
additions: 0,
|
|
6162
6688
|
removals: 0,
|
|
6163
6689
|
changedLines: 0,
|
|
6164
|
-
touchesTest: false
|
|
6690
|
+
touchesTest: false,
|
|
6691
|
+
withheldLines: 0,
|
|
6692
|
+
extensionLines: /* @__PURE__ */ new Map()
|
|
6165
6693
|
};
|
|
6166
6694
|
if (included) seenCommits.add(hash);
|
|
6167
6695
|
continue;
|
|
@@ -6184,20 +6712,34 @@ function extractGitWorkflow(options) {
|
|
|
6184
6712
|
if (fileChangedLines <= 0) continue;
|
|
6185
6713
|
const extension = path6.extname(file).toLowerCase();
|
|
6186
6714
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
6187
|
-
extensionLines.set(
|
|
6715
|
+
current.extensionLines.set(
|
|
6188
6716
|
extension,
|
|
6189
|
-
(extensionLines.get(extension) ?? 0) + fileChangedLines
|
|
6717
|
+
(current.extensionLines.get(extension) ?? 0) + fileChangedLines
|
|
6190
6718
|
);
|
|
6191
|
-
} else
|
|
6719
|
+
} else current.withheldLines += fileChangedLines;
|
|
6192
6720
|
}
|
|
6193
6721
|
finishCommit();
|
|
6194
6722
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6723
|
+
return {
|
|
6724
|
+
days: [...days].sort(([a], [b]) => a.localeCompare(b)).map(([date, day]) => {
|
|
6725
|
+
const { extensionLines, cells, ...rest } = day;
|
|
6726
|
+
return {
|
|
6727
|
+
date,
|
|
6728
|
+
...rest,
|
|
6729
|
+
changedLinesByExtension: [...extensionLines].map(([extension, changedLines]) => ({ extension, changedLines })).sort((a, b) => a.extension.localeCompare(b.extension)),
|
|
6730
|
+
weekdayHourCells: [...cells].map(([key, commits]) => {
|
|
6731
|
+
const [weekdayUtc, hourUtc] = key.split(":").map(Number);
|
|
6732
|
+
return {
|
|
6733
|
+
weekdayUtc: weekdayUtc ?? 0,
|
|
6734
|
+
hourUtc: hourUtc ?? 0,
|
|
6735
|
+
commits
|
|
6736
|
+
};
|
|
6737
|
+
}).sort(
|
|
6738
|
+
(a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc
|
|
6739
|
+
)
|
|
6740
|
+
};
|
|
6741
|
+
})
|
|
6742
|
+
};
|
|
6201
6743
|
}
|
|
6202
6744
|
|
|
6203
6745
|
// src/workflow/extract.ts
|
|
@@ -6218,50 +6760,44 @@ function machineUtcOffsetMinutes(now = /* @__PURE__ */ new Date()) {
|
|
|
6218
6760
|
return -now.getTimezoneOffset();
|
|
6219
6761
|
}
|
|
6220
6762
|
function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machineUtcOffsetMinutes()) {
|
|
6763
|
+
const harnessDays = /* @__PURE__ */ new Map();
|
|
6221
6764
|
const projectDays = /* @__PURE__ */ new Map();
|
|
6222
|
-
const
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6765
|
+
for (const { aggregate, local } of harnessWorkflows) {
|
|
6766
|
+
for (const { date, ...day } of aggregate.days) {
|
|
6767
|
+
const rows = harnessDays.get(date) ?? [];
|
|
6768
|
+
const { phase, ...safe } = day;
|
|
6769
|
+
rows.push(
|
|
6770
|
+
aggregate.gate.publishable && phase ? { ...safe, phase } : safe
|
|
6771
|
+
);
|
|
6772
|
+
harnessDays.set(date, rows);
|
|
6773
|
+
}
|
|
6227
6774
|
for (const [date, workspaces] of local.activeProjectDays) {
|
|
6228
6775
|
const projects = projectDays.get(date) ?? /* @__PURE__ */ new Set();
|
|
6229
6776
|
for (const project of workspaces) projects.add(project);
|
|
6230
6777
|
projectDays.set(date, projects);
|
|
6231
6778
|
}
|
|
6232
|
-
for (const day of workflow.facts.activeDays) {
|
|
6233
|
-
if (day.webSearches === void 0) continue;
|
|
6234
|
-
webSearchDays.add(day.date);
|
|
6235
|
-
webSearches.set(
|
|
6236
|
-
day.date,
|
|
6237
|
-
(webSearches.get(day.date) ?? 0) + day.webSearches
|
|
6238
|
-
);
|
|
6239
|
-
}
|
|
6240
6779
|
}
|
|
6241
|
-
const
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
...webSearchDays.has(date) ? { webSearches: webSearches.get(date) ?? 0 } : {}
|
|
6251
|
-
}))
|
|
6252
|
-
};
|
|
6253
|
-
const syncedHarnesses = [
|
|
6254
|
-
...new Set(harnessWorkflows.map(({ aggregate }) => aggregate.harness))
|
|
6255
|
-
];
|
|
6780
|
+
const gitDays = /* @__PURE__ */ new Map();
|
|
6781
|
+
for (const { date, ...day } of git.days) gitDays.set(date, day);
|
|
6782
|
+
const dates = [
|
|
6783
|
+
.../* @__PURE__ */ new Set([
|
|
6784
|
+
...harnessDays.keys(),
|
|
6785
|
+
...gitDays.keys(),
|
|
6786
|
+
...projectDays.keys()
|
|
6787
|
+
])
|
|
6788
|
+
].sort();
|
|
6256
6789
|
return {
|
|
6257
|
-
aggregateVersion:
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6790
|
+
aggregateVersion: WORKFLOW_AGGREGATES_V2,
|
|
6791
|
+
utcOffsetMinutes,
|
|
6792
|
+
days: dates.map((date) => {
|
|
6793
|
+
const projects = projectDays.get(date)?.size;
|
|
6794
|
+
return {
|
|
6795
|
+
date,
|
|
6796
|
+
harnesses: harnessDays.get(date) ?? [],
|
|
6797
|
+
git: gitDays.get(date) ?? emptyGitDay(),
|
|
6798
|
+
...projects === void 0 ? {} : { parallelProjects: projects }
|
|
6799
|
+
};
|
|
6800
|
+
})
|
|
6265
6801
|
};
|
|
6266
6802
|
}
|
|
6267
6803
|
|
|
@@ -6472,16 +7008,23 @@ var MODEL_ROLLUP = 0.01;
|
|
|
6472
7008
|
var PHASE_ORDER = ["scout", "build", "verify", "handoff", "unknown"];
|
|
6473
7009
|
function workflowBlock(workflow, host) {
|
|
6474
7010
|
const out = [];
|
|
6475
|
-
const
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
7011
|
+
const folded = foldWorkflowDays(workflow.days, {
|
|
7012
|
+
aggregateVersion: workflow.aggregateVersion,
|
|
7013
|
+
utcOffsetMinutes: workflow.utcOffsetMinutes
|
|
7014
|
+
});
|
|
7015
|
+
const harnesses = folded?.harnesses ?? [];
|
|
7016
|
+
const withPlaybook = harnesses.filter((h) => h.phase);
|
|
7017
|
+
const sessions = harnesses.reduce((a, h) => a + h.sessions, 0);
|
|
6480
7018
|
const ruleVersions = [
|
|
6481
7019
|
...new Set(withPlaybook.map((h) => h.phase?.ruleVersion ?? ""))
|
|
6482
7020
|
].filter(Boolean);
|
|
6483
7021
|
out.push(
|
|
6484
|
-
`workflow ${
|
|
7022
|
+
`workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${workflow.aggregateVersion}`
|
|
7023
|
+
);
|
|
7024
|
+
const first = folded?.dates[0];
|
|
7025
|
+
const last = folded?.dates.at(-1);
|
|
7026
|
+
out.push(
|
|
7027
|
+
`days ${workflow.days.length} day${workflow.days.length === 1 ? "" : "s"}${first && last ? ` \xB7 ${first} to ${last}` : ""}`
|
|
6485
7028
|
);
|
|
6486
7029
|
const seconds = PHASE_ORDER.map(
|
|
6487
7030
|
(phase) => withPlaybook.reduce((a, h) => a + (h.phase?.phaseSec[phase] ?? 0), 0)
|
|
@@ -6493,12 +7036,9 @@ function workflowBlock(workflow, host) {
|
|
|
6493
7036
|
).join(" \xB7 ");
|
|
6494
7037
|
out.push(` ${mix} \xB7 ${ruleVersions.join(", ")}`);
|
|
6495
7038
|
}
|
|
6496
|
-
const git =
|
|
6497
|
-
out.push(
|
|
6498
|
-
`git ${git.totalCommits} commits \xB7 ${fmtTokens(git.additions + git.removals)} lines changed`
|
|
6499
|
-
);
|
|
7039
|
+
const git = folded?.git;
|
|
6500
7040
|
out.push(
|
|
6501
|
-
`
|
|
7041
|
+
`git ${git?.commits ?? 0} commits \xB7 ${fmtTokens((git?.additions ?? 0) + (git?.removals ?? 0))} lines changed`
|
|
6502
7042
|
);
|
|
6503
7043
|
out.push(` (Publish workflow is on for ${host})`);
|
|
6504
7044
|
return out;
|