@use-aistack/cli 0.8.0 → 0.10.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 +1363 -425
- 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.10.0" : "0.0.0-dev";
|
|
8
8
|
|
|
9
9
|
// src/api.ts
|
|
10
10
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
@@ -122,6 +122,29 @@ async function syncPublish(token, bodyJson) {
|
|
|
122
122
|
}
|
|
123
123
|
return res.json();
|
|
124
124
|
}
|
|
125
|
+
async function fetchDayManifest(baseUrl, token) {
|
|
126
|
+
const res = await fetch(`${baseUrl}/api/cli/sync-manifest`, {
|
|
127
|
+
headers: { "Content-Type": "application/json", ...authHeaders(token) }
|
|
128
|
+
});
|
|
129
|
+
if (res.status === 404) return null;
|
|
130
|
+
if (res.status === 401)
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Authentication expired. Run `npx @use-aistack/cli login` again."
|
|
133
|
+
);
|
|
134
|
+
if (res.status === 403 || res.status === 429)
|
|
135
|
+
throw failure("Manifest fetch failed", res);
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
throw new Error(await formatHttpError(res, "Manifest fetch failed"));
|
|
138
|
+
}
|
|
139
|
+
const body = await res.json();
|
|
140
|
+
const retentionDays = typeof body.retentionDays === "number" && body.retentionDays > 0 ? body.retentionDays : 400;
|
|
141
|
+
const aggregateVersion = typeof body.aggregateVersion === "string" ? body.aggregateVersion : "";
|
|
142
|
+
const days = Array.isArray(body.days) ? body.days.flatMap((d) => {
|
|
143
|
+
const row = d;
|
|
144
|
+
return typeof row?.date === "string" && typeof row?.fingerprint === "string" ? [{ date: row.date, fingerprint: row.fingerprint }] : [];
|
|
145
|
+
}) : [];
|
|
146
|
+
return { retentionDays, aggregateVersion, days };
|
|
147
|
+
}
|
|
125
148
|
async function setAutoSync(token, flag) {
|
|
126
149
|
const res = await request("/api/cli/auto-sync", {
|
|
127
150
|
method: "POST",
|
|
@@ -1592,6 +1615,64 @@ var asName = (v) => {
|
|
|
1592
1615
|
const s = asStr(v);
|
|
1593
1616
|
return s === null ? null : cleanName(s);
|
|
1594
1617
|
};
|
|
1618
|
+
var utcDateOf = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
1619
|
+
function usageDayAcc(agg, tsMs) {
|
|
1620
|
+
const date = utcDateOf(tsMs);
|
|
1621
|
+
let day = agg.usageDays.get(date);
|
|
1622
|
+
if (!day) {
|
|
1623
|
+
day = {
|
|
1624
|
+
models: /* @__PURE__ */ new Map(),
|
|
1625
|
+
subagentTokens: 0,
|
|
1626
|
+
syntheticTokens: 0,
|
|
1627
|
+
projectDirs: /* @__PURE__ */ new Set()
|
|
1628
|
+
};
|
|
1629
|
+
agg.usageDays.set(date, day);
|
|
1630
|
+
}
|
|
1631
|
+
return day;
|
|
1632
|
+
}
|
|
1633
|
+
function noteUsageResponse(agg, response, sign = 1) {
|
|
1634
|
+
if (response.tsMs === null) return;
|
|
1635
|
+
const day = usageDayAcc(agg, response.tsMs);
|
|
1636
|
+
let m = day.models.get(response.modelKey);
|
|
1637
|
+
if (!m) {
|
|
1638
|
+
m = {
|
|
1639
|
+
counts: {
|
|
1640
|
+
input: 0,
|
|
1641
|
+
output: 0,
|
|
1642
|
+
cacheWrite5m: 0,
|
|
1643
|
+
cacheWrite1h: 0,
|
|
1644
|
+
cacheWriteUnsplit: 0,
|
|
1645
|
+
cacheRead: 0
|
|
1646
|
+
},
|
|
1647
|
+
costUSD: 0,
|
|
1648
|
+
unpricedTokens: 0
|
|
1649
|
+
};
|
|
1650
|
+
day.models.set(response.modelKey, m);
|
|
1651
|
+
}
|
|
1652
|
+
const c = response.counts;
|
|
1653
|
+
m.counts.input += sign * c.input;
|
|
1654
|
+
m.counts.output += sign * c.output;
|
|
1655
|
+
m.counts.cacheWrite5m += sign * c.cacheWrite5m;
|
|
1656
|
+
m.counts.cacheWrite1h += sign * c.cacheWrite1h;
|
|
1657
|
+
m.counts.cacheWriteUnsplit += sign * c.cacheWriteUnsplit;
|
|
1658
|
+
m.counts.cacheRead += sign * c.cacheRead;
|
|
1659
|
+
if (response.costUSD === null) m.unpricedTokens += sign * countsTotal(c);
|
|
1660
|
+
else m.costUSD += sign * response.costUSD;
|
|
1661
|
+
if (response.sidechain) day.subagentTokens += sign * countsTotal(c);
|
|
1662
|
+
}
|
|
1663
|
+
function noteSyntheticTokens(agg, tsMs, tokens) {
|
|
1664
|
+
if (tsMs === null) return;
|
|
1665
|
+
usageDayAcc(agg, tsMs).syntheticTokens += tokens;
|
|
1666
|
+
}
|
|
1667
|
+
function noteSessionStart(agg, sessionId, tsMs) {
|
|
1668
|
+
if (tsMs === null) return;
|
|
1669
|
+
const held = agg.sessionStarts.get(sessionId);
|
|
1670
|
+
if (held === void 0 || tsMs < held) agg.sessionStarts.set(sessionId, tsMs);
|
|
1671
|
+
}
|
|
1672
|
+
function noteProjectDay(agg, directory, tsMs) {
|
|
1673
|
+
if (tsMs === null) return;
|
|
1674
|
+
usageDayAcc(agg, tsMs).projectDirs.add(directory);
|
|
1675
|
+
}
|
|
1595
1676
|
function createAggregate() {
|
|
1596
1677
|
return {
|
|
1597
1678
|
files: 0,
|
|
@@ -1631,7 +1712,9 @@ function createAggregate() {
|
|
|
1631
1712
|
textBlocks: 0,
|
|
1632
1713
|
webSearchRequests: 0,
|
|
1633
1714
|
webFetchRequests: 0,
|
|
1634
|
-
seen: /* @__PURE__ */ new Map()
|
|
1715
|
+
seen: /* @__PURE__ */ new Map(),
|
|
1716
|
+
usageDays: /* @__PURE__ */ new Map(),
|
|
1717
|
+
sessionStarts: /* @__PURE__ */ new Map()
|
|
1635
1718
|
};
|
|
1636
1719
|
}
|
|
1637
1720
|
var bump = (m, k, n = 1) => m.set(k, (m.get(k) ?? 0) + n);
|
|
@@ -1649,7 +1732,16 @@ function emptyUsage() {
|
|
|
1649
1732
|
};
|
|
1650
1733
|
}
|
|
1651
1734
|
var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
|
|
1652
|
-
function addModelUsage(agg, modelKey, counts, costUSD, messages = 1) {
|
|
1735
|
+
function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
|
|
1736
|
+
if (at) {
|
|
1737
|
+
noteUsageResponse(agg, {
|
|
1738
|
+
tsMs: at.tsMs,
|
|
1739
|
+
modelKey,
|
|
1740
|
+
counts,
|
|
1741
|
+
costUSD,
|
|
1742
|
+
...at.sidechain ? { sidechain: true } : {}
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1653
1745
|
let m = agg.byModel.get(modelKey);
|
|
1654
1746
|
if (!m) {
|
|
1655
1747
|
m = emptyUsage();
|
|
@@ -2133,192 +2225,634 @@ async function hasRecentFile(roots, matches, sinceMs, opts = {}) {
|
|
|
2133
2225
|
return false;
|
|
2134
2226
|
}
|
|
2135
2227
|
|
|
2136
|
-
// ../workflow-rules/src/
|
|
2137
|
-
var
|
|
2228
|
+
// ../workflow-rules/src/daily.ts
|
|
2229
|
+
var WORKFLOW_AGGREGATES_V2 = "workflow-aggregates/v2";
|
|
2230
|
+
var LOG_BUCKETS_V1 = "log-buckets/v1";
|
|
2231
|
+
var EMPTY_PHASE_TOTALS = Object.freeze({
|
|
2232
|
+
scout: 0,
|
|
2233
|
+
build: 0,
|
|
2234
|
+
verify: 0,
|
|
2235
|
+
handoff: 0,
|
|
2236
|
+
unknown: 0
|
|
2237
|
+
});
|
|
2238
|
+
var EFFORT_LEVELS = [
|
|
2239
|
+
"low",
|
|
2240
|
+
"medium",
|
|
2241
|
+
"high",
|
|
2242
|
+
"other"
|
|
2243
|
+
];
|
|
2244
|
+
function effortLevelOf(effort) {
|
|
2245
|
+
switch (effort.toLowerCase()) {
|
|
2246
|
+
case "low":
|
|
2247
|
+
case "minimal":
|
|
2248
|
+
return "low";
|
|
2249
|
+
case "medium":
|
|
2250
|
+
return "medium";
|
|
2251
|
+
case "high":
|
|
2252
|
+
case "xhigh":
|
|
2253
|
+
case "max":
|
|
2254
|
+
case "ultra":
|
|
2255
|
+
return "high";
|
|
2256
|
+
default:
|
|
2257
|
+
return "other";
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function logBucket(value) {
|
|
2261
|
+
if (!(value >= 1)) return 0;
|
|
2262
|
+
return Math.floor(Math.log2(value)) + 1;
|
|
2263
|
+
}
|
|
2264
|
+
function bucketRange(bucket) {
|
|
2265
|
+
if (bucket <= 0) return { low: 0, high: 1 };
|
|
2266
|
+
return { low: 2 ** (bucket - 1), high: 2 ** bucket };
|
|
2267
|
+
}
|
|
2268
|
+
function bucketMid(bucket) {
|
|
2269
|
+
const { low, high } = bucketRange(bucket);
|
|
2270
|
+
return Math.sqrt(Math.max(low, 0.25) * high);
|
|
2271
|
+
}
|
|
2272
|
+
function medianBucket(buckets) {
|
|
2273
|
+
const total = buckets.reduce((sum, row) => sum + row.count, 0);
|
|
2274
|
+
if (total <= 0) return void 0;
|
|
2275
|
+
const sorted = [...buckets].sort((a, b) => a.bucket - b.bucket);
|
|
2276
|
+
const middle = (total + 1) / 2;
|
|
2277
|
+
let seen = 0;
|
|
2278
|
+
for (const row of sorted) {
|
|
2279
|
+
seen += row.count;
|
|
2280
|
+
if (seen >= middle) return row.bucket;
|
|
2281
|
+
}
|
|
2282
|
+
return sorted[sorted.length - 1]?.bucket;
|
|
2283
|
+
}
|
|
2138
2284
|
function median(values) {
|
|
2139
2285
|
if (values.length === 0) return void 0;
|
|
2140
2286
|
const sorted = [...values].sort((a, b) => a - b);
|
|
2141
2287
|
const mid = Math.floor(sorted.length / 2);
|
|
2142
2288
|
const midValue = sorted[mid];
|
|
2143
|
-
if (midValue === void 0) return void 0;
|
|
2144
2289
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + midValue) / 2 : midValue;
|
|
2145
2290
|
}
|
|
2146
|
-
function
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2291
|
+
function addPhaseTotals(into, from) {
|
|
2292
|
+
for (const phase of Object.keys(into)) {
|
|
2293
|
+
into[phase] += from[phase] ?? 0;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
function sumBy(rows, key, add, clone) {
|
|
2297
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2298
|
+
for (const row of rows) {
|
|
2299
|
+
const k = key(row);
|
|
2300
|
+
const held = merged.get(k);
|
|
2301
|
+
if (held) add(held, row);
|
|
2302
|
+
else merged.set(k, clone(row));
|
|
2303
|
+
}
|
|
2304
|
+
return [...merged.values()];
|
|
2305
|
+
}
|
|
2306
|
+
function foldCells(rows, field) {
|
|
2307
|
+
return sumBy(
|
|
2308
|
+
rows,
|
|
2309
|
+
(row) => `${row.weekdayUtc}:${row.hourUtc}`,
|
|
2310
|
+
(into, from) => {
|
|
2311
|
+
into[field] += from[field];
|
|
2312
|
+
},
|
|
2313
|
+
(row) => ({ ...row })
|
|
2314
|
+
).sort((a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc);
|
|
2315
|
+
}
|
|
2316
|
+
function foldModels(rows) {
|
|
2317
|
+
return sumBy(
|
|
2318
|
+
rows,
|
|
2319
|
+
(row) => row.model,
|
|
2320
|
+
(into, from) => {
|
|
2321
|
+
into.tokens += from.tokens;
|
|
2322
|
+
},
|
|
2323
|
+
(row) => ({ ...row })
|
|
2324
|
+
).sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model));
|
|
2325
|
+
}
|
|
2326
|
+
function foldLengths(rows) {
|
|
2327
|
+
return sumBy(
|
|
2328
|
+
rows,
|
|
2329
|
+
(row) => String(row.bucket),
|
|
2330
|
+
(into, from) => {
|
|
2331
|
+
into.sessions += from.sessions;
|
|
2332
|
+
addPhaseTotals(into.phaseSec, from.phaseSec);
|
|
2333
|
+
into.merged += from.merged;
|
|
2334
|
+
into.verified += from.verified;
|
|
2335
|
+
into.mergedVerified += from.mergedVerified;
|
|
2336
|
+
into.openedWithScout += from.openedWithScout;
|
|
2337
|
+
},
|
|
2338
|
+
(row) => ({ ...row, phaseSec: { ...row.phaseSec } })
|
|
2339
|
+
).sort((a, b) => a.bucket - b.bucket);
|
|
2340
|
+
}
|
|
2341
|
+
function foldHarnessDays(days) {
|
|
2342
|
+
const first = days[0];
|
|
2343
|
+
if (!first) throw new Error("foldHarnessDays needs at least one day");
|
|
2344
|
+
const versions = (values) => [...new Set(values)].sort().join(" \xB7 ");
|
|
2345
|
+
const out = {
|
|
2346
|
+
harness: first.harness,
|
|
2347
|
+
sessions: days.reduce((sum, day) => sum + day.sessions, 0),
|
|
2348
|
+
startHours: sumBy(
|
|
2349
|
+
days.flatMap((day) => day.startHours),
|
|
2350
|
+
(row) => String(row.hourUtc),
|
|
2351
|
+
(into, from) => {
|
|
2352
|
+
into.sessions += from.sessions;
|
|
2353
|
+
},
|
|
2354
|
+
(row) => ({ ...row })
|
|
2355
|
+
).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
2356
|
+
activity: foldCells(
|
|
2357
|
+
days.flatMap((day) => day.activity),
|
|
2358
|
+
"events"
|
|
2359
|
+
)
|
|
2360
|
+
};
|
|
2361
|
+
const phases = days.flatMap((day) => day.phase ? [day.phase] : []);
|
|
2362
|
+
if (phases.length > 0) {
|
|
2363
|
+
const phaseSec = { ...EMPTY_PHASE_TOTALS };
|
|
2364
|
+
const phaseEvents = { ...EMPTY_PHASE_TOTALS };
|
|
2365
|
+
for (const phase of phases) {
|
|
2366
|
+
addPhaseTotals(phaseSec, phase.phaseSec);
|
|
2367
|
+
addPhaseTotals(phaseEvents, phase.phaseEvents);
|
|
2368
|
+
}
|
|
2369
|
+
out.phase = {
|
|
2370
|
+
ruleVersion: versions(phases.map((phase) => phase.ruleVersion)),
|
|
2371
|
+
sessions: phases.reduce((sum, phase) => sum + phase.sessions, 0),
|
|
2372
|
+
phaseSec,
|
|
2373
|
+
phaseEvents,
|
|
2374
|
+
waitingSec: phases.reduce((sum, phase) => sum + phase.waitingSec, 0),
|
|
2375
|
+
idleSec: phases.reduce((sum, phase) => sum + phase.idleSec, 0),
|
|
2376
|
+
sessionsWithVerify: phases.reduce(
|
|
2377
|
+
(sum, phase) => sum + phase.sessionsWithVerify,
|
|
2378
|
+
0
|
|
2379
|
+
),
|
|
2380
|
+
sessionsWithHandoff: phases.reduce(
|
|
2381
|
+
(sum, phase) => sum + phase.sessionsWithHandoff,
|
|
2382
|
+
0
|
|
2383
|
+
),
|
|
2384
|
+
bucketRuleVersion: versions(
|
|
2385
|
+
phases.map((phase) => phase.bucketRuleVersion)
|
|
2386
|
+
),
|
|
2387
|
+
lengths: foldLengths(phases.flatMap((phase) => phase.lengths))
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
const routings = days.flatMap((day) => day.routing ? [day.routing] : []);
|
|
2391
|
+
if (routings.length > 0) {
|
|
2392
|
+
out.routing = {
|
|
2393
|
+
main: foldModels(routings.flatMap((routing) => routing.main)),
|
|
2394
|
+
subagents: foldModels(routings.flatMap((routing) => routing.subagents))
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
const delegations = days.flatMap(
|
|
2398
|
+
(day) => day.delegation ? [day.delegation] : []
|
|
2399
|
+
);
|
|
2400
|
+
if (delegations.length > 0) {
|
|
2401
|
+
out.delegation = {
|
|
2402
|
+
mainToolCalls: delegations.reduce((sum, d) => sum + d.mainToolCalls, 0),
|
|
2403
|
+
subagentToolCalls: delegations.reduce(
|
|
2404
|
+
(sum, d) => sum + d.subagentToolCalls,
|
|
2405
|
+
0
|
|
2406
|
+
),
|
|
2407
|
+
widestFanOut: Math.max(...delegations.map((d) => d.widestFanOut)),
|
|
2408
|
+
mostSubagents: Math.max(...delegations.map((d) => d.mostSubagents))
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
const efforts = days.flatMap((day) => day.effort ?? []);
|
|
2412
|
+
if (days.some((day) => day.effort)) {
|
|
2413
|
+
out.effort = sumBy(
|
|
2414
|
+
efforts,
|
|
2415
|
+
(row) => row.level,
|
|
2416
|
+
(into, from) => {
|
|
2417
|
+
into.turns += from.turns;
|
|
2418
|
+
},
|
|
2419
|
+
(row) => ({ ...row })
|
|
2420
|
+
).sort(
|
|
2421
|
+
(a, b) => EFFORT_LEVELS.indexOf(a.level) - EFFORT_LEVELS.indexOf(b.level)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
const thinkings = days.flatMap((day) => day.thinking ? [day.thinking] : []);
|
|
2425
|
+
if (thinkings.length > 0) {
|
|
2426
|
+
out.thinking = {
|
|
2427
|
+
thinkingTokens: thinkings.reduce((sum, t) => sum + t.thinkingTokens, 0),
|
|
2428
|
+
responseTokens: thinkings.reduce((sum, t) => sum + t.responseTokens, 0)
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
const durations = days.flatMap(
|
|
2432
|
+
(day) => day.turnDurations ? [day.turnDurations] : []
|
|
2433
|
+
);
|
|
2434
|
+
if (durations.length > 0) {
|
|
2435
|
+
out.turnDurations = {
|
|
2436
|
+
bucketRuleVersion: versions(durations.map((d) => d.bucketRuleVersion)),
|
|
2437
|
+
buckets: sumBy(
|
|
2438
|
+
durations.flatMap((d) => d.buckets),
|
|
2439
|
+
(row) => String(row.bucket),
|
|
2440
|
+
(into, from) => {
|
|
2441
|
+
into.turns += from.turns;
|
|
2442
|
+
},
|
|
2443
|
+
(row) => ({ ...row })
|
|
2444
|
+
).sort((a, b) => a.bucket - b.bucket)
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
const questions = days.flatMap(
|
|
2448
|
+
(day) => day.questions ? [day.questions] : []
|
|
2449
|
+
);
|
|
2450
|
+
if (questions.length > 0) {
|
|
2451
|
+
out.questions = {
|
|
2452
|
+
asked: questions.reduce((sum, q) => sum + q.asked, 0),
|
|
2453
|
+
turns: questions.reduce((sum, q) => sum + q.turns, 0)
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
if (days.some((day) => day.webSearches !== void 0)) {
|
|
2457
|
+
out.webSearches = days.reduce(
|
|
2458
|
+
(sum, day) => sum + (day.webSearches ?? 0),
|
|
2459
|
+
0
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
return out;
|
|
2463
|
+
}
|
|
2464
|
+
function foldGitDays(days) {
|
|
2465
|
+
const versions = (values) => [...new Set(values)].sort().join(" \xB7 ");
|
|
2466
|
+
return {
|
|
2467
|
+
testFileRuleVersion: versions(days.map((d) => d.testFileRuleVersion)),
|
|
2468
|
+
fileTypeRuleVersion: versions(days.map((d) => d.fileTypeRuleVersion)),
|
|
2469
|
+
commitSetRuleVersion: versions(days.map((d) => d.commitSetRuleVersion)),
|
|
2470
|
+
commits: days.reduce((sum, d) => sum + d.commits, 0),
|
|
2471
|
+
lateNightCommits: days.reduce((sum, d) => sum + d.lateNightCommits, 0),
|
|
2472
|
+
additions: days.reduce((sum, d) => sum + d.additions, 0),
|
|
2473
|
+
removals: days.reduce((sum, d) => sum + d.removals, 0),
|
|
2474
|
+
changedLinesPerCommit: days.flatMap((d) => [...d.changedLinesPerCommit]),
|
|
2475
|
+
testFileCommits: days.reduce((sum, d) => sum + d.testFileCommits, 0),
|
|
2476
|
+
changedLinesByExtension: sumBy(
|
|
2477
|
+
days.flatMap((d) => d.changedLinesByExtension),
|
|
2478
|
+
(row) => row.extension,
|
|
2479
|
+
(into, from) => {
|
|
2480
|
+
into.changedLines += from.changedLines;
|
|
2481
|
+
},
|
|
2482
|
+
(row) => ({ ...row })
|
|
2483
|
+
).sort((a, b) => a.extension.localeCompare(b.extension)),
|
|
2484
|
+
withheldExtensionLines: days.reduce(
|
|
2485
|
+
(sum, d) => sum + d.withheldExtensionLines,
|
|
2486
|
+
0
|
|
2487
|
+
),
|
|
2488
|
+
weekdayHourCells: foldCells(
|
|
2489
|
+
days.flatMap((d) => d.weekdayHourCells),
|
|
2490
|
+
"commits"
|
|
2491
|
+
)
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
function foldWorkflowDays(days, options) {
|
|
2495
|
+
if (days.length === 0) return void 0;
|
|
2496
|
+
const byHarness = /* @__PURE__ */ new Map();
|
|
2497
|
+
for (const day of days) {
|
|
2498
|
+
for (const harness of day.harnesses) {
|
|
2499
|
+
const held = byHarness.get(harness.harness) ?? [];
|
|
2500
|
+
held.push(harness);
|
|
2501
|
+
byHarness.set(harness.harness, held);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
const parallelProjectDays = days.flatMap(
|
|
2505
|
+
(day) => day.parallelProjects === void 0 ? [] : [day.parallelProjects]
|
|
2506
|
+
);
|
|
2507
|
+
const webSearchDays = days.filter(
|
|
2508
|
+
(day) => day.harnesses.some((harness) => harness.webSearches !== void 0)
|
|
2509
|
+
).length;
|
|
2510
|
+
return {
|
|
2511
|
+
aggregateVersion: options.aggregateVersion,
|
|
2512
|
+
...options.utcOffsetMinutes === void 0 ? {} : { utcOffsetMinutes: options.utcOffsetMinutes },
|
|
2513
|
+
dates: [...new Set(days.map((day) => day.date))].sort(),
|
|
2514
|
+
harnesses: [...byHarness.values()].map(foldHarnessDays).sort((a, b) => a.harness.localeCompare(b.harness)),
|
|
2515
|
+
git: foldGitDays(days.map((day) => day.git)),
|
|
2516
|
+
...parallelProjectDays.length === 0 ? {} : { parallelProjects: Math.max(...parallelProjectDays) },
|
|
2517
|
+
parallelProjectDays,
|
|
2518
|
+
gitDays: [...days].sort((a, b) => a.date.localeCompare(b.date)).map((day) => ({
|
|
2519
|
+
date: day.date,
|
|
2520
|
+
additions: day.git.additions,
|
|
2521
|
+
removals: day.git.removals,
|
|
2522
|
+
commits: day.git.commits
|
|
2523
|
+
})),
|
|
2524
|
+
webSearchDays
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// ../workflow-rules/src/reading.ts
|
|
2529
|
+
function playbookHarnesses(reading) {
|
|
2530
|
+
return reading.harnesses.filter((harness) => harness.phase !== void 0);
|
|
2531
|
+
}
|
|
2532
|
+
function startHoursUtc(reading) {
|
|
2533
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2534
|
+
for (const harness of reading.harnesses) {
|
|
2535
|
+
for (const cell of harness.startHours) {
|
|
2536
|
+
counts.set(cell.hourUtc, (counts.get(cell.hourUtc) ?? 0) + cell.sessions);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
return counts;
|
|
2540
|
+
}
|
|
2541
|
+
function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
2542
|
+
return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
|
|
2543
|
+
}
|
|
2544
|
+
function modalStartHour(reading) {
|
|
2545
|
+
const offsetMinutes = reading.utcOffsetMinutes;
|
|
2546
|
+
if (offsetMinutes === void 0) return void 0;
|
|
2547
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2548
|
+
for (const [hourUtc, sessions] of startHoursUtc(reading)) {
|
|
2549
|
+
const hour = ownerLocalHour(hourUtc, offsetMinutes);
|
|
2550
|
+
counts.set(hour, (counts.get(hour) ?? 0) + sessions);
|
|
2551
|
+
}
|
|
2552
|
+
if (counts.size === 0) return void 0;
|
|
2553
|
+
return [...counts.entries()].sort(
|
|
2554
|
+
(a, b) => b[1] - a[1] || a[0] - b[0]
|
|
2555
|
+
)[0]?.[0];
|
|
2150
2556
|
}
|
|
2557
|
+
|
|
2558
|
+
// ../workflow-rules/src/componentRules.ts
|
|
2559
|
+
var COMPONENT_RULES_V2 = "component-rules/v2";
|
|
2560
|
+
var gitCoverage = () => 1;
|
|
2561
|
+
function harnessShare(input, counts) {
|
|
2562
|
+
const synced = input.reading.harnesses.length;
|
|
2563
|
+
if (synced === 0) return 0;
|
|
2564
|
+
return counts(input) / synced;
|
|
2565
|
+
}
|
|
2566
|
+
function topShare(entries) {
|
|
2567
|
+
const total = entries.reduce((sum, entry) => sum + entry.value, 0);
|
|
2568
|
+
if (total <= 0) return void 0;
|
|
2569
|
+
const top = Math.max(...entries.map((entry) => entry.value));
|
|
2570
|
+
return top / total;
|
|
2571
|
+
}
|
|
2572
|
+
var COMPONENT_RULES = [
|
|
2573
|
+
{
|
|
2574
|
+
id: "activity-heatmap",
|
|
2575
|
+
version: COMPONENT_RULES_V2,
|
|
2576
|
+
label: "of events fall in the three busiest hours of the day",
|
|
2577
|
+
unit: "share",
|
|
2578
|
+
// Three of twenty-four hours is an eighth of the clock. A day spread evenly
|
|
2579
|
+
// lands near it; a night owl runs far above it.
|
|
2580
|
+
band: { low: 0.125, high: 0.35 },
|
|
2581
|
+
evaluate: ({ reading }) => {
|
|
2582
|
+
const byHour = /* @__PURE__ */ new Map();
|
|
2583
|
+
for (const harness of reading.harnesses) {
|
|
2584
|
+
for (const cell of harness.activity) {
|
|
2585
|
+
byHour.set(
|
|
2586
|
+
cell.hourUtc,
|
|
2587
|
+
(byHour.get(cell.hourUtc) ?? 0) + cell.events
|
|
2588
|
+
);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
const total = [...byHour.values()].reduce((sum, n) => sum + n, 0);
|
|
2592
|
+
if (total <= 0) return void 0;
|
|
2593
|
+
const busiest = [...byHour.values()].sort((a, b) => b - a).slice(0, 3);
|
|
2594
|
+
return busiest.reduce((sum, n) => sum + n, 0) / total;
|
|
2595
|
+
},
|
|
2596
|
+
coverage: (input) => harnessShare(
|
|
2597
|
+
input,
|
|
2598
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.activity.length > 0).length
|
|
2599
|
+
)
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
id: "start-hours",
|
|
2603
|
+
version: COMPONENT_RULES_V2,
|
|
2604
|
+
label: "is the most common start hour",
|
|
2605
|
+
unit: "hour",
|
|
2606
|
+
// A band on a clock face means little; the row is never ranked, and the
|
|
2607
|
+
// figure is a position rather than a size. Kept for shape.
|
|
2608
|
+
band: { low: 9, high: 18 },
|
|
2609
|
+
evaluate: ({ reading }) => modalStartHour(reading),
|
|
2610
|
+
coverage: (input) => harnessShare(
|
|
2611
|
+
input,
|
|
2612
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.startHours.length > 0).length
|
|
2613
|
+
)
|
|
2614
|
+
},
|
|
2615
|
+
{
|
|
2616
|
+
id: "phase-playbook",
|
|
2617
|
+
version: COMPONENT_RULES_V2,
|
|
2618
|
+
label: "median measured session",
|
|
2619
|
+
unit: "minutes",
|
|
2620
|
+
band: { low: 10, high: 60 },
|
|
2621
|
+
evaluate: ({ reading }) => {
|
|
2622
|
+
const bucket = medianBucket(
|
|
2623
|
+
playbookHarnesses(reading).flatMap(
|
|
2624
|
+
(harness) => (harness.phase?.lengths ?? []).map((row) => ({
|
|
2625
|
+
bucket: row.bucket,
|
|
2626
|
+
count: row.sessions
|
|
2627
|
+
}))
|
|
2628
|
+
)
|
|
2629
|
+
);
|
|
2630
|
+
return bucket === void 0 ? void 0 : bucketMid(bucket);
|
|
2631
|
+
},
|
|
2632
|
+
coverage: (input) => harnessShare(input, ({ reading }) => playbookHarnesses(reading).length)
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
id: "git-ledger",
|
|
2636
|
+
version: COMPONENT_RULES_V2,
|
|
2637
|
+
label: "of changed lines are removals",
|
|
2638
|
+
unit: "share",
|
|
2639
|
+
// Most work adds more than it takes away. A ledger that removes as much as
|
|
2640
|
+
// it adds is the surprising one, and so is one that never removes.
|
|
2641
|
+
band: { low: 0.15, high: 0.35 },
|
|
2642
|
+
evaluate: ({ reading }) => {
|
|
2643
|
+
const changed = reading.git.additions + reading.git.removals;
|
|
2644
|
+
return changed > 0 ? reading.git.removals / changed : void 0;
|
|
2645
|
+
},
|
|
2646
|
+
coverage: gitCoverage
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
id: "coding-languages",
|
|
2650
|
+
version: COMPONENT_RULES_V2,
|
|
2651
|
+
label: "of changed lines are one file type",
|
|
2652
|
+
unit: "share",
|
|
2653
|
+
band: { low: 0.35, high: 0.7 },
|
|
2654
|
+
evaluate: ({ reading }) => {
|
|
2655
|
+
const named = reading.git.changedLinesByExtension.map((row) => ({
|
|
2656
|
+
value: row.changedLines
|
|
2657
|
+
}));
|
|
2658
|
+
const total = named.reduce((sum, row) => sum + row.value, 0) + reading.git.withheldExtensionLines;
|
|
2659
|
+
if (total <= 0 || named.length === 0) return void 0;
|
|
2660
|
+
return Math.max(...named.map((row) => row.value)) / total;
|
|
2661
|
+
},
|
|
2662
|
+
coverage: gitCoverage
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
id: "kit",
|
|
2666
|
+
version: COMPONENT_RULES_V2,
|
|
2667
|
+
label: "of skill and MCP calls go to one artifact",
|
|
2668
|
+
unit: "share",
|
|
2669
|
+
band: { low: 0.15, high: 0.4 },
|
|
2670
|
+
evaluate: ({ kit }) => {
|
|
2671
|
+
if (!kit) return void 0;
|
|
2672
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2673
|
+
for (const harness of kit) {
|
|
2674
|
+
for (const atom of [...harness.skills, ...harness.mcpServers]) {
|
|
2675
|
+
byName.set(atom.name, (byName.get(atom.name) ?? 0) + atom.callShare);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
return topShare([...byName.values()].map((share) => ({ value: share })));
|
|
2679
|
+
},
|
|
2680
|
+
coverage: (input) => harnessShare(
|
|
2681
|
+
input,
|
|
2682
|
+
({ kit }) => (kit ?? []).filter(
|
|
2683
|
+
(harness) => harness.skills.length > 0 || harness.mcpServers.length > 0
|
|
2684
|
+
).length
|
|
2685
|
+
)
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
id: "model-routing",
|
|
2689
|
+
version: COMPONENT_RULES_V2,
|
|
2690
|
+
label: "of main-loop tokens run on one model",
|
|
2691
|
+
unit: "share",
|
|
2692
|
+
band: { low: 0.4, high: 0.85 },
|
|
2693
|
+
evaluate: ({ reading }) => {
|
|
2694
|
+
const main = reading.harnesses.flatMap((harness) => [
|
|
2695
|
+
...harness.routing?.main ?? []
|
|
2696
|
+
]);
|
|
2697
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
2698
|
+
for (const row of main) {
|
|
2699
|
+
byModel.set(row.model, (byModel.get(row.model) ?? 0) + row.tokens);
|
|
2700
|
+
}
|
|
2701
|
+
return topShare(
|
|
2702
|
+
[...byModel.values()].map((tokens) => ({ value: tokens }))
|
|
2703
|
+
);
|
|
2704
|
+
},
|
|
2705
|
+
coverage: (input) => harnessShare(
|
|
2706
|
+
input,
|
|
2707
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.routing).length
|
|
2708
|
+
)
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
id: "delegation",
|
|
2712
|
+
version: COMPONENT_RULES_V2,
|
|
2713
|
+
label: "of tool calls run inside a subagent",
|
|
2714
|
+
unit: "share",
|
|
2715
|
+
band: { low: 0, high: 0.3 },
|
|
2716
|
+
evaluate: ({ reading }) => {
|
|
2717
|
+
let main = 0;
|
|
2718
|
+
let subagents = 0;
|
|
2719
|
+
for (const harness of reading.harnesses) {
|
|
2720
|
+
main += harness.delegation?.mainToolCalls ?? 0;
|
|
2721
|
+
subagents += harness.delegation?.subagentToolCalls ?? 0;
|
|
2722
|
+
}
|
|
2723
|
+
const total = main + subagents;
|
|
2724
|
+
return total > 0 ? subagents / total : void 0;
|
|
2725
|
+
},
|
|
2726
|
+
coverage: (input) => harnessShare(
|
|
2727
|
+
input,
|
|
2728
|
+
({ reading }) => reading.harnesses.filter((harness) => harness.delegation).length
|
|
2729
|
+
)
|
|
2730
|
+
}
|
|
2731
|
+
];
|
|
2732
|
+
|
|
2733
|
+
// ../workflow-rules/src/metricRules.ts
|
|
2734
|
+
var METRIC_RULES_V2 = "metric-rules/v2";
|
|
2151
2735
|
var METRIC_RULES = [
|
|
2152
2736
|
{
|
|
2153
2737
|
id: "late-night-commits",
|
|
2154
|
-
version:
|
|
2738
|
+
version: METRIC_RULES_V2,
|
|
2155
2739
|
label: "of commits land between 23:00 and 03:00",
|
|
2156
2740
|
kind: "exact",
|
|
2157
2741
|
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.
|
|
2742
|
+
counts: "all",
|
|
2161
2743
|
band: { low: 0, high: 0.15 },
|
|
2162
|
-
evaluate: (
|
|
2163
|
-
const git =
|
|
2164
|
-
if (
|
|
2165
|
-
return git.lateNightCommits / git.
|
|
2744
|
+
evaluate: (reading) => {
|
|
2745
|
+
const git = reading.git;
|
|
2746
|
+
if (git.commits === 0) return void 0;
|
|
2747
|
+
return git.lateNightCommits / git.commits;
|
|
2166
2748
|
}
|
|
2167
2749
|
},
|
|
2168
2750
|
{
|
|
2169
2751
|
id: "parallel-projects",
|
|
2170
|
-
version:
|
|
2752
|
+
version: METRIC_RULES_V2,
|
|
2171
2753
|
label: "projects run in parallel on a median active day",
|
|
2172
2754
|
kind: "proxy",
|
|
2173
2755
|
unit: "count",
|
|
2174
|
-
|
|
2756
|
+
counts: "all",
|
|
2175
2757
|
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
|
-
)
|
|
2758
|
+
evaluate: (reading) => median(reading.parallelProjectDays)
|
|
2196
2759
|
},
|
|
2197
2760
|
{
|
|
2198
2761
|
id: "thinking-share",
|
|
2199
|
-
version:
|
|
2762
|
+
version: METRIC_RULES_V2,
|
|
2200
2763
|
label: "of response tokens are thinking",
|
|
2201
2764
|
kind: "proxy",
|
|
2202
2765
|
unit: "share",
|
|
2203
|
-
|
|
2766
|
+
counts: (harness) => harness.thinking !== void 0,
|
|
2204
2767
|
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;
|
|
2768
|
+
evaluate: (reading) => {
|
|
2210
2769
|
let thinking = 0;
|
|
2211
2770
|
let response = 0;
|
|
2212
|
-
for (const
|
|
2213
|
-
thinking +=
|
|
2214
|
-
response +=
|
|
2771
|
+
for (const harness of reading.harnesses) {
|
|
2772
|
+
thinking += harness.thinking?.thinkingTokens ?? 0;
|
|
2773
|
+
response += harness.thinking?.responseTokens ?? 0;
|
|
2215
2774
|
}
|
|
2216
2775
|
return response > 0 ? thinking / response : void 0;
|
|
2217
2776
|
}
|
|
2218
2777
|
},
|
|
2219
2778
|
{
|
|
2220
|
-
id: "
|
|
2221
|
-
version:
|
|
2779
|
+
id: "effort-levels",
|
|
2780
|
+
version: METRIC_RULES_V2,
|
|
2222
2781
|
label: "of turns run at high effort",
|
|
2223
2782
|
kind: "exact",
|
|
2224
2783
|
unit: "share",
|
|
2225
|
-
|
|
2226
|
-
harnessSupport: ["claude-code", "codex"],
|
|
2784
|
+
counts: (harness) => harness.effort !== void 0,
|
|
2227
2785
|
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;
|
|
2786
|
+
evaluate: (reading) => {
|
|
2231
2787
|
let high = 0;
|
|
2232
2788
|
let total = 0;
|
|
2233
|
-
for (const
|
|
2234
|
-
|
|
2235
|
-
|
|
2789
|
+
for (const harness of reading.harnesses) {
|
|
2790
|
+
for (const row of harness.effort ?? []) {
|
|
2791
|
+
total += row.turns;
|
|
2792
|
+
if (row.level === "high") high += row.turns;
|
|
2793
|
+
}
|
|
2236
2794
|
}
|
|
2237
2795
|
return total > 0 ? high / total : void 0;
|
|
2238
2796
|
}
|
|
2239
2797
|
},
|
|
2240
2798
|
{
|
|
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",
|
|
2799
|
+
id: "turn-duration",
|
|
2800
|
+
version: METRIC_RULES_V2,
|
|
2801
|
+
label: "median turn duration",
|
|
2257
2802
|
kind: "exact",
|
|
2258
2803
|
unit: "minutes",
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2804
|
+
counts: (harness) => harness.turnDurations !== void 0,
|
|
2805
|
+
band: { low: 0.25, high: 2 },
|
|
2806
|
+
evaluate: (reading) => {
|
|
2807
|
+
const bucket = medianBucket(
|
|
2808
|
+
reading.harnesses.flatMap(
|
|
2809
|
+
(harness) => (harness.turnDurations?.buckets ?? []).map((row) => ({
|
|
2810
|
+
bucket: row.bucket,
|
|
2811
|
+
count: row.turns
|
|
2812
|
+
}))
|
|
2813
|
+
)
|
|
2814
|
+
);
|
|
2815
|
+
return bucket === void 0 ? void 0 : bucketMid(bucket) / 60;
|
|
2266
2816
|
}
|
|
2267
2817
|
},
|
|
2268
2818
|
{
|
|
2269
2819
|
id: "question-back-share",
|
|
2270
|
-
version:
|
|
2820
|
+
version: METRIC_RULES_V2,
|
|
2271
2821
|
label: "of turns end with a question back to the human",
|
|
2272
2822
|
kind: "proxy",
|
|
2273
2823
|
unit: "share",
|
|
2274
|
-
|
|
2824
|
+
counts: (harness) => harness.questions !== void 0,
|
|
2275
2825
|
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;
|
|
2826
|
+
evaluate: (reading) => {
|
|
2281
2827
|
let asked = 0;
|
|
2282
2828
|
let turns = 0;
|
|
2283
|
-
for (const
|
|
2284
|
-
asked +=
|
|
2285
|
-
turns +=
|
|
2829
|
+
for (const harness of reading.harnesses) {
|
|
2830
|
+
asked += harness.questions?.asked ?? 0;
|
|
2831
|
+
turns += harness.questions?.turns ?? 0;
|
|
2286
2832
|
}
|
|
2287
2833
|
return turns > 0 ? asked / turns : void 0;
|
|
2288
2834
|
}
|
|
2289
2835
|
},
|
|
2290
2836
|
{
|
|
2291
2837
|
id: "web-searches-per-active-day",
|
|
2292
|
-
version:
|
|
2838
|
+
version: METRIC_RULES_V2,
|
|
2293
2839
|
label: "web searches per active day, inside the harness",
|
|
2294
2840
|
kind: "proxy",
|
|
2295
2841
|
unit: "count",
|
|
2296
|
-
|
|
2842
|
+
counts: (harness) => harness.webSearches !== void 0,
|
|
2297
2843
|
band: { low: 0, high: 4 },
|
|
2298
|
-
evaluate: (
|
|
2299
|
-
|
|
2300
|
-
|
|
2844
|
+
evaluate: (reading) => {
|
|
2845
|
+
if (reading.webSearchDays === 0) return void 0;
|
|
2846
|
+
const total = reading.harnesses.reduce(
|
|
2847
|
+
(sum, harness) => sum + (harness.webSearches ?? 0),
|
|
2848
|
+
0
|
|
2301
2849
|
);
|
|
2302
|
-
|
|
2303
|
-
const total = days.reduce((sum, d) => sum + (d.webSearches ?? 0), 0);
|
|
2304
|
-
return total / days.length;
|
|
2850
|
+
return total / reading.webSearchDays;
|
|
2305
2851
|
}
|
|
2306
2852
|
}
|
|
2307
2853
|
];
|
|
2308
2854
|
|
|
2309
2855
|
// ../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
2856
|
var PHASES = [
|
|
2323
2857
|
"scout",
|
|
2324
2858
|
"build",
|
|
@@ -2327,39 +2861,6 @@ var PHASES = [
|
|
|
2327
2861
|
"unknown"
|
|
2328
2862
|
];
|
|
2329
2863
|
|
|
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
2864
|
// ../workflow-rules/src/phaseRules.ts
|
|
2364
2865
|
var PHASE_RULES_V1 = "phase-rules/v1";
|
|
2365
2866
|
var UNKNOWN_GATE = 0.2;
|
|
@@ -2778,8 +3279,81 @@ function deriveSessionPhases(events, ruleSet = PHASE_RULES_V1, harness) {
|
|
|
2778
3279
|
};
|
|
2779
3280
|
}
|
|
2780
3281
|
|
|
2781
|
-
// ../workflow-rules/src/
|
|
2782
|
-
var
|
|
3282
|
+
// ../workflow-rules/src/usage.ts
|
|
3283
|
+
var MEASURED_DAYS_V1 = "measured-days/v1";
|
|
3284
|
+
function canonicalJson(value) {
|
|
3285
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
3286
|
+
if (value && typeof value === "object") {
|
|
3287
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3288
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
3289
|
+
}
|
|
3290
|
+
return JSON.stringify(value) ?? "null";
|
|
3291
|
+
}
|
|
3292
|
+
function fnv1a64(text) {
|
|
3293
|
+
const prime = 0x100000001b3n;
|
|
3294
|
+
const mask = 0xffffffffffffffffn;
|
|
3295
|
+
let hash = 0xcbf29ce484222325n;
|
|
3296
|
+
const bytes = new TextEncoder().encode(text);
|
|
3297
|
+
for (const byte of bytes) {
|
|
3298
|
+
hash ^= BigInt(byte);
|
|
3299
|
+
hash = hash * prime & mask;
|
|
3300
|
+
}
|
|
3301
|
+
return hash.toString(16).padStart(16, "0");
|
|
3302
|
+
}
|
|
3303
|
+
function dayFingerprint(day) {
|
|
3304
|
+
return fnv1a64(
|
|
3305
|
+
canonicalJson({
|
|
3306
|
+
version: MEASURED_DAYS_V1,
|
|
3307
|
+
date: day.date,
|
|
3308
|
+
usage: day.usage,
|
|
3309
|
+
workflow: day.workflow
|
|
3310
|
+
})
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
// ../workflow-rules/src/workflowRows.ts
|
|
3315
|
+
function metricRowId(metricId) {
|
|
3316
|
+
return `metric:${metricId}`;
|
|
3317
|
+
}
|
|
3318
|
+
function componentRowId(componentId) {
|
|
3319
|
+
return `component:${componentId}`;
|
|
3320
|
+
}
|
|
3321
|
+
var WORKFLOW_ROW_ORDER = [
|
|
3322
|
+
{
|
|
3323
|
+
rowId: "component:activity-heatmap",
|
|
3324
|
+
name: "When work happens",
|
|
3325
|
+
flat: false
|
|
3326
|
+
},
|
|
3327
|
+
{ rowId: "component:start-hours", name: "Session start times", flat: false },
|
|
3328
|
+
{
|
|
3329
|
+
rowId: "metric:late-night-commits",
|
|
3330
|
+
name: "Late-night commits",
|
|
3331
|
+
flat: true
|
|
3332
|
+
},
|
|
3333
|
+
{ rowId: "component:phase-playbook", name: "Session length", flat: false },
|
|
3334
|
+
{ rowId: "component:git-ledger", name: "Lines changed", flat: false },
|
|
3335
|
+
{ rowId: "component:coding-languages", name: "Languages", flat: false },
|
|
3336
|
+
{ rowId: "component:kit", name: "Skills and MCP", flat: false },
|
|
3337
|
+
{ rowId: "component:model-routing", name: "Models used", flat: false },
|
|
3338
|
+
{ rowId: "component:delegation", name: "Subagents", flat: false },
|
|
3339
|
+
{ rowId: "metric:effort-levels", name: "Effort levels", flat: false },
|
|
3340
|
+
{ rowId: "metric:thinking-share", name: "Thinking tokens", flat: false },
|
|
3341
|
+
{ rowId: "metric:turn-duration", name: "Turn length", flat: false },
|
|
3342
|
+
{ rowId: "metric:question-back-share", name: "Questions asked", flat: true },
|
|
3343
|
+
{
|
|
3344
|
+
rowId: "metric:web-searches-per-active-day",
|
|
3345
|
+
name: "Web searches",
|
|
3346
|
+
flat: true
|
|
3347
|
+
},
|
|
3348
|
+
{ rowId: "metric:parallel-projects", name: "Parallel projects", flat: true }
|
|
3349
|
+
];
|
|
3350
|
+
var ORDER_INDEX = new Map(
|
|
3351
|
+
WORKFLOW_ROW_ORDER.map((row, index) => [row.rowId, index])
|
|
3352
|
+
);
|
|
3353
|
+
var KNOWN_ROW_IDS = /* @__PURE__ */ new Set([
|
|
3354
|
+
...METRIC_RULES.map((rule) => metricRowId(rule.id)),
|
|
3355
|
+
...COMPONENT_RULES.map((rule) => componentRowId(rule.id))
|
|
3356
|
+
]);
|
|
2783
3357
|
|
|
2784
3358
|
// src/harness/shared/window.ts
|
|
2785
3359
|
var DEFAULT_WINDOW_DAYS = 30;
|
|
@@ -3037,23 +3611,23 @@ function buildPayload(input) {
|
|
|
3037
3611
|
}
|
|
3038
3612
|
};
|
|
3039
3613
|
}
|
|
3040
|
-
function
|
|
3041
|
-
return {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
};
|
|
3614
|
+
function applyDayConsent(days, syncConfig) {
|
|
3615
|
+
return days.map((day) => {
|
|
3616
|
+
const { workflow, usage, ...rest } = day;
|
|
3617
|
+
const out = { ...rest };
|
|
3618
|
+
if (usage) {
|
|
3619
|
+
out.usage = syncConfig.publishCost ? usage : {
|
|
3620
|
+
harnesses: usage.harnesses.map((h) => ({
|
|
3621
|
+
...h,
|
|
3622
|
+
models: h.models.map(
|
|
3623
|
+
({ usd: _usd, pricingTable: _table, ...model }) => model
|
|
3624
|
+
)
|
|
3625
|
+
}))
|
|
3626
|
+
};
|
|
3627
|
+
}
|
|
3628
|
+
if (workflow && syncConfig.publishWorkflow) out.workflow = workflow;
|
|
3629
|
+
return out;
|
|
3630
|
+
});
|
|
3057
3631
|
}
|
|
3058
3632
|
function mergeKeptPrivate(halves) {
|
|
3059
3633
|
const out = {};
|
|
@@ -3072,11 +3646,18 @@ function mergeKeptPrivate(halves) {
|
|
|
3072
3646
|
}
|
|
3073
3647
|
return out;
|
|
3074
3648
|
}
|
|
3075
|
-
function buildSyncBody(built, syncConfig, autoSync, trigger = "manual",
|
|
3649
|
+
function buildSyncBody(built, syncConfig, autoSync, trigger = "manual", measuredDays, cliVersion) {
|
|
3076
3650
|
const payloads = built.map((b) => b.payload);
|
|
3077
3651
|
const base = autoSync ? { payloads, autoSync, trigger } : { payloads, trigger };
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3652
|
+
const withDays = measuredDays ? {
|
|
3653
|
+
...base,
|
|
3654
|
+
measuredDays: {
|
|
3655
|
+
aggregateVersion: MEASURED_DAYS_V1,
|
|
3656
|
+
utcOffsetMinutes: measuredDays.utcOffsetMinutes,
|
|
3657
|
+
days: applyDayConsent(measuredDays.days, syncConfig)
|
|
3658
|
+
}
|
|
3659
|
+
} : base;
|
|
3660
|
+
const withVersion = cliVersion ? { ...withDays, cliVersion } : withDays;
|
|
3080
3661
|
if (!syncConfig.reviewKeptPrivate) return withVersion;
|
|
3081
3662
|
return {
|
|
3082
3663
|
...withVersion,
|
|
@@ -3085,7 +3666,7 @@ function buildSyncBody(built, syncConfig, autoSync, trigger = "manual", workflow
|
|
|
3085
3666
|
}
|
|
3086
3667
|
|
|
3087
3668
|
// src/workflow/reducer.ts
|
|
3088
|
-
var WORKFLOW_AGGREGATE_VERSION =
|
|
3669
|
+
var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V2;
|
|
3089
3670
|
function createWorkflowLocalSources() {
|
|
3090
3671
|
return { projectWorkspaces: /* @__PURE__ */ new Set(), activeProjectDays: /* @__PURE__ */ new Map() };
|
|
3091
3672
|
}
|
|
@@ -3097,10 +3678,10 @@ var emptyPhase = () => ({
|
|
|
3097
3678
|
unknown: 0
|
|
3098
3679
|
});
|
|
3099
3680
|
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
3681
|
var bump2 = (map, key, amount = 1) => {
|
|
3102
3682
|
map.set(key, (map.get(key) ?? 0) + amount);
|
|
3103
3683
|
};
|
|
3684
|
+
var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3104
3685
|
var PHASE_RANK = {
|
|
3105
3686
|
verify: 4,
|
|
3106
3687
|
handoff: 3,
|
|
@@ -3145,18 +3726,9 @@ function reduceEventBatches(recorded, harness) {
|
|
|
3145
3726
|
}
|
|
3146
3727
|
return output;
|
|
3147
3728
|
}
|
|
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
|
-
};
|
|
3729
|
+
var hasVerifyRun = (events, harness) => events.some(
|
|
3730
|
+
(event) => deriveSessionPhases([event], PHASE_RULES_V1, harness).phaseEvents.verify > 0
|
|
3731
|
+
);
|
|
3160
3732
|
function shellIncludes(arg, head) {
|
|
3161
3733
|
return arg.split(/(?:&&|\|\||;|\|)/).some((part) => part.trim() === head || part.trim().startsWith(`${head} `));
|
|
3162
3734
|
}
|
|
@@ -3174,10 +3746,47 @@ function sessionState() {
|
|
|
3174
3746
|
lastTs: void 0
|
|
3175
3747
|
};
|
|
3176
3748
|
}
|
|
3749
|
+
function dayState() {
|
|
3750
|
+
return {
|
|
3751
|
+
sessions: 0,
|
|
3752
|
+
startHours: /* @__PURE__ */ new Map(),
|
|
3753
|
+
phase: {
|
|
3754
|
+
sessions: 0,
|
|
3755
|
+
phaseSec: emptyPhase(),
|
|
3756
|
+
phaseEvents: emptyPhase(),
|
|
3757
|
+
waitingSec: 0,
|
|
3758
|
+
idleSec: 0,
|
|
3759
|
+
sessionsWithVerify: 0,
|
|
3760
|
+
sessionsWithHandoff: 0,
|
|
3761
|
+
lengths: /* @__PURE__ */ new Map()
|
|
3762
|
+
},
|
|
3763
|
+
routing: { main: /* @__PURE__ */ new Map(), subagents: /* @__PURE__ */ new Map() },
|
|
3764
|
+
hasRouting: false,
|
|
3765
|
+
delegation: {
|
|
3766
|
+
mainToolCalls: 0,
|
|
3767
|
+
subagentToolCalls: 0,
|
|
3768
|
+
widestFanOut: 0,
|
|
3769
|
+
mostSubagents: 0
|
|
3770
|
+
},
|
|
3771
|
+
hasDelegation: false,
|
|
3772
|
+
activity: /* @__PURE__ */ new Map(),
|
|
3773
|
+
effort: /* @__PURE__ */ new Map(),
|
|
3774
|
+
hasEffort: false,
|
|
3775
|
+
thinking: { thinkingTokens: 0, responseTokens: 0 },
|
|
3776
|
+
hasThinking: false,
|
|
3777
|
+
turnDurations: /* @__PURE__ */ new Map(),
|
|
3778
|
+
hasDurations: false,
|
|
3779
|
+
questions: { asked: 0, turns: 0 },
|
|
3780
|
+
hasQuestions: false,
|
|
3781
|
+
webSearches: 0,
|
|
3782
|
+
hasWebSearches: false
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3177
3785
|
function createHarnessWorkflowReducer(harness, localSources = createWorkflowLocalSources()) {
|
|
3178
3786
|
const sessions = /* @__PURE__ */ new Map();
|
|
3179
|
-
const
|
|
3787
|
+
const eventCells = /* @__PURE__ */ new Map();
|
|
3180
3788
|
const webSearchesByDate = /* @__PURE__ */ new Map();
|
|
3789
|
+
const eventDates = /* @__PURE__ */ new Set();
|
|
3181
3790
|
let finished;
|
|
3182
3791
|
const getSession = (key) => {
|
|
3183
3792
|
let state = sessions.get(key);
|
|
@@ -3197,7 +3806,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3197
3806
|
state.parentSession ??= observation.parentSession;
|
|
3198
3807
|
state.sidechain ||= observation.sidechain === true;
|
|
3199
3808
|
const at = new Date(observation.tsMs);
|
|
3200
|
-
const date =
|
|
3809
|
+
const date = utcDateOf2(observation.tsMs);
|
|
3201
3810
|
if (observation.projectWorkspace) {
|
|
3202
3811
|
state.projectWorkspaces.add(observation.projectWorkspace);
|
|
3203
3812
|
localSources.projectWorkspaces.add(observation.projectWorkspace);
|
|
@@ -3208,7 +3817,10 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3208
3817
|
event: [observation.tsMs, observation.tool, arg],
|
|
3209
3818
|
...observation.batchId ? { batchId: observation.batchId } : {}
|
|
3210
3819
|
});
|
|
3211
|
-
|
|
3820
|
+
eventDates.add(date);
|
|
3821
|
+
const cells = eventCells.get(date) ?? /* @__PURE__ */ new Map();
|
|
3822
|
+
bump2(cells, `${at.getUTCDay()}:${at.getUTCHours()}`);
|
|
3823
|
+
eventCells.set(date, cells);
|
|
3212
3824
|
if (["WebSearch", "web_search", "websearch"].includes(observation.tool))
|
|
3213
3825
|
bump2(webSearchesByDate, date);
|
|
3214
3826
|
} else if (observation.type === "response") {
|
|
@@ -3234,135 +3846,118 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3234
3846
|
},
|
|
3235
3847
|
finish() {
|
|
3236
3848
|
if (finished) return finished;
|
|
3237
|
-
const
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3849
|
+
const days = /* @__PURE__ */ new Map();
|
|
3850
|
+
const dayOf = (date) => {
|
|
3851
|
+
let state = days.get(date);
|
|
3852
|
+
if (!state) {
|
|
3853
|
+
state = dayState();
|
|
3854
|
+
days.set(date, state);
|
|
3855
|
+
}
|
|
3856
|
+
return state;
|
|
3244
3857
|
};
|
|
3245
|
-
|
|
3246
|
-
let idleSec = 0;
|
|
3247
|
-
let mainToolCalls = 0;
|
|
3248
|
-
let subagentToolCalls = 0;
|
|
3858
|
+
const windowPhaseSec = emptyPhase();
|
|
3249
3859
|
let phaseSessionCount = 0;
|
|
3250
3860
|
for (const state of sessions.values()) {
|
|
3861
|
+
if (state.firstTs === void 0) continue;
|
|
3862
|
+
const day = dayOf(utcDateOf2(state.firstTs));
|
|
3251
3863
|
const events = reduceEventBatches(state.events, harness);
|
|
3252
3864
|
const responses = [...state.responses.values()];
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
);
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3865
|
+
day.sessions++;
|
|
3866
|
+
const startHour = new Date(state.firstTs).getUTCHours();
|
|
3867
|
+
day.startHours.set(startHour, (day.startHours.get(startHour) ?? 0) + 1);
|
|
3868
|
+
const phases = deriveSessionPhases(events, PHASE_RULES_V1, harness);
|
|
3869
|
+
if (state.events.length > 0) phaseSessionCount++;
|
|
3870
|
+
day.phase.sessions++;
|
|
3871
|
+
for (const phase of PHASES) {
|
|
3872
|
+
day.phase.phaseSec[phase] += phases.phaseSec[phase];
|
|
3873
|
+
day.phase.phaseEvents[phase] += phases.phaseEvents[phase];
|
|
3874
|
+
windowPhaseSec[phase] += phases.phaseSec[phase];
|
|
3875
|
+
}
|
|
3876
|
+
day.phase.waitingSec += phases.waitingSec;
|
|
3877
|
+
day.phase.idleSec += phases.idleSec;
|
|
3878
|
+
if (phases.phaseEvents.verify > 0) day.phase.sessionsWithVerify++;
|
|
3879
|
+
if (phases.phaseEvents.handoff > 0) day.phase.sessionsWithHandoff++;
|
|
3880
|
+
const measuredSec = PHASES.reduce(
|
|
3881
|
+
(sum, phase) => sum + phases.phaseSec[phase],
|
|
3882
|
+
0
|
|
3266
3883
|
);
|
|
3267
|
-
const
|
|
3268
|
-
|
|
3884
|
+
const bucket = logBucket(measuredSec / 60);
|
|
3885
|
+
const merged = events.some(
|
|
3886
|
+
([, tool, arg]) => ["Bash", "bash", "shell", "local_shell", "exec_command"].includes(
|
|
3887
|
+
tool
|
|
3888
|
+
) && shellIncludes(arg, "gh pr merge")
|
|
3269
3889
|
);
|
|
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
|
-
}
|
|
3890
|
+
const verified = hasVerifyRun(events, harness);
|
|
3891
|
+
const openedWithScout = (events[0] ? deriveSessionPhases([events[0]], PHASE_RULES_V1, harness).phaseEvents.scout : 0) > 0;
|
|
3892
|
+
const length = day.phase.lengths.get(bucket) ?? {
|
|
3893
|
+
bucket,
|
|
3894
|
+
sessions: 0,
|
|
3895
|
+
phaseSec: emptyPhase(),
|
|
3896
|
+
merged: 0,
|
|
3897
|
+
verified: 0,
|
|
3898
|
+
mergedVerified: 0,
|
|
3899
|
+
openedWithScout: 0
|
|
3289
3900
|
};
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
sessionFact.longestTurnDurationSec = Math.max(...durations);
|
|
3299
|
-
facts.push(sessionFact);
|
|
3901
|
+
length.sessions++;
|
|
3902
|
+
for (const phase of PHASES)
|
|
3903
|
+
length.phaseSec[phase] += phases.phaseSec[phase];
|
|
3904
|
+
if (merged) length.merged++;
|
|
3905
|
+
if (verified) length.verified++;
|
|
3906
|
+
if (merged && verified) length.mergedVerified++;
|
|
3907
|
+
if (openedWithScout) length.openedWithScout++;
|
|
3908
|
+
day.phase.lengths.set(bucket, length);
|
|
3300
3909
|
const routing = state.sidechain || state.parentSession ? "subagents" : "main";
|
|
3301
3910
|
for (const response of responses) {
|
|
3302
3911
|
if (!response.model) continue;
|
|
3912
|
+
day.hasRouting = true;
|
|
3303
3913
|
bump2(
|
|
3304
|
-
|
|
3914
|
+
day.routing[routing],
|
|
3305
3915
|
response.model,
|
|
3306
3916
|
response.routingTokens ?? response.responseTokens ?? 0
|
|
3307
3917
|
);
|
|
3308
3918
|
}
|
|
3309
|
-
if (routing === "subagents")
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
for (const
|
|
3314
|
-
|
|
3315
|
-
|
|
3919
|
+
if (routing === "subagents") {
|
|
3920
|
+
day.delegation.subagentToolCalls += state.events.length;
|
|
3921
|
+
day.hasDelegation ||= state.events.length > 0;
|
|
3922
|
+
} else day.delegation.mainToolCalls += state.events.length;
|
|
3923
|
+
for (const response of responses) {
|
|
3924
|
+
if (response.effort) {
|
|
3925
|
+
day.hasEffort = true;
|
|
3926
|
+
const level = effortLevelOf(response.effort);
|
|
3927
|
+
day.effort.set(level, (day.effort.get(level) ?? 0) + 1);
|
|
3928
|
+
}
|
|
3929
|
+
if (response.thinkingTokens !== void 0) {
|
|
3930
|
+
day.hasThinking = true;
|
|
3931
|
+
day.thinking.thinkingTokens += response.thinkingTokens;
|
|
3932
|
+
day.thinking.responseTokens += response.responseTokens ?? 0;
|
|
3933
|
+
}
|
|
3934
|
+
if (response.durationSec !== void 0) {
|
|
3935
|
+
day.hasDurations = true;
|
|
3936
|
+
const durationBucket = logBucket(response.durationSec);
|
|
3937
|
+
day.turnDurations.set(
|
|
3938
|
+
durationBucket,
|
|
3939
|
+
(day.turnDurations.get(durationBucket) ?? 0) + 1
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
if (harness !== "pi-mono") {
|
|
3944
|
+
day.hasQuestions = true;
|
|
3945
|
+
day.questions.turns += state.turns.size;
|
|
3946
|
+
day.questions.asked += [...state.turns.values()].filter(
|
|
3947
|
+
Boolean
|
|
3948
|
+
).length;
|
|
3316
3949
|
}
|
|
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
3950
|
}
|
|
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;
|
|
3951
|
+
for (const date of eventDates) {
|
|
3952
|
+
const day = dayOf(date);
|
|
3953
|
+
for (const [key, events] of eventCells.get(date) ?? []) {
|
|
3954
|
+
bump2(day.activity, key, events);
|
|
3955
|
+
}
|
|
3956
|
+
if (harness !== "pi-mono") {
|
|
3957
|
+
day.hasWebSearches = true;
|
|
3958
|
+
day.webSearches = webSearchesByDate.get(date) ?? 0;
|
|
3363
3959
|
}
|
|
3364
3960
|
}
|
|
3365
|
-
const projectsByDate = localSources.activeProjectDays;
|
|
3366
3961
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
3367
3962
|
for (const state of sessions.values()) {
|
|
3368
3963
|
if (!state.parentSession) continue;
|
|
@@ -3370,10 +3965,16 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3370
3965
|
children.push(state);
|
|
3371
3966
|
childrenByParent.set(state.parentSession, children);
|
|
3372
3967
|
}
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3968
|
+
for (const [parentKey, children] of childrenByParent) {
|
|
3969
|
+
const parent = sessions.get(parentKey);
|
|
3970
|
+
const anchor = parent?.firstTs ?? Math.min(...children.map((child) => child.firstTs ?? Infinity));
|
|
3971
|
+
if (!Number.isFinite(anchor)) continue;
|
|
3972
|
+
const day = dayOf(utcDateOf2(anchor));
|
|
3973
|
+
day.hasDelegation = true;
|
|
3974
|
+
day.delegation.mostSubagents = Math.max(
|
|
3975
|
+
day.delegation.mostSubagents,
|
|
3976
|
+
children.length
|
|
3977
|
+
);
|
|
3377
3978
|
const boundaries = children.flatMap((child) => [
|
|
3378
3979
|
{ ts: child.firstTs ?? 0, delta: 1 },
|
|
3379
3980
|
{ ts: child.lastTs ?? child.firstTs ?? 0, delta: -1 }
|
|
@@ -3382,61 +3983,109 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3382
3983
|
let active = 0;
|
|
3383
3984
|
for (const boundary of boundaries) {
|
|
3384
3985
|
active += boundary.delta;
|
|
3385
|
-
widestFanOut = Math.max(
|
|
3986
|
+
day.delegation.widestFanOut = Math.max(
|
|
3987
|
+
day.delegation.widestFanOut,
|
|
3988
|
+
active
|
|
3989
|
+
);
|
|
3386
3990
|
}
|
|
3387
3991
|
}
|
|
3992
|
+
localSources.activeProjectDays.clear();
|
|
3993
|
+
for (const state of sessions.values()) {
|
|
3994
|
+
if (state.firstTs === void 0 || state.lastTs === void 0) continue;
|
|
3995
|
+
let day = Date.parse(`${utcDateOf2(state.firstTs)}T00:00:00Z`);
|
|
3996
|
+
const lastDay = Date.parse(`${utcDateOf2(state.lastTs)}T00:00:00Z`);
|
|
3997
|
+
while (day <= lastDay) {
|
|
3998
|
+
const date = utcDateOf2(day);
|
|
3999
|
+
const projects = localSources.activeProjectDays.get(date) ?? /* @__PURE__ */ new Set();
|
|
4000
|
+
for (const project of state.projectWorkspaces) projects.add(project);
|
|
4001
|
+
if (projects.size > 0)
|
|
4002
|
+
localSources.activeProjectDays.set(date, projects);
|
|
4003
|
+
day += 864e5;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
const attributed = PHASES.reduce(
|
|
4007
|
+
(sum, phase) => sum + windowPhaseSec[phase],
|
|
4008
|
+
0
|
|
4009
|
+
);
|
|
4010
|
+
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
4011
|
+
const routesModels = harness === "claude-code" || harness === "opencode";
|
|
3388
4012
|
const asRows = (map) => {
|
|
3389
4013
|
const safe = /* @__PURE__ */ new Map();
|
|
3390
4014
|
for (const [model, tokens] of map) {
|
|
3391
4015
|
bump2(safe, sanitizeModelId(model), tokens);
|
|
3392
4016
|
}
|
|
3393
|
-
return [...safe].map(([model, tokens]) => ({ model, tokens }))
|
|
4017
|
+
return [...safe].map(([model, tokens]) => ({ model, tokens })).sort(
|
|
4018
|
+
(a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model)
|
|
4019
|
+
);
|
|
3394
4020
|
};
|
|
3395
|
-
const hasDelegation = subagentToolCalls > 0 || mostSubagents > 0 || widestFanOut > 0;
|
|
3396
4021
|
finished = {
|
|
3397
4022
|
aggregateVersion: WORKFLOW_AGGREGATE_VERSION,
|
|
3398
4023
|
harness,
|
|
3399
|
-
|
|
4024
|
+
gate: {
|
|
3400
4025
|
ruleVersion: PHASE_RULES_V1,
|
|
3401
4026
|
publishable: phaseSessionCount > 0 && unknown <= UNKNOWN_GATE,
|
|
3402
4027
|
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
|
-
}))
|
|
4028
|
+
unknownShare: unknown
|
|
3417
4029
|
},
|
|
3418
|
-
...
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4030
|
+
days: [...days].sort(([a], [b]) => a.localeCompare(b)).map(([date, day]) => ({
|
|
4031
|
+
date,
|
|
4032
|
+
harness,
|
|
4033
|
+
sessions: day.sessions,
|
|
4034
|
+
startHours: [...day.startHours].map(([hourUtc, count]) => ({ hourUtc, sessions: count })).sort((a, b) => a.hourUtc - b.hourUtc),
|
|
4035
|
+
...day.phase.sessions > 0 ? {
|
|
4036
|
+
phase: {
|
|
4037
|
+
ruleVersion: PHASE_RULES_V1,
|
|
4038
|
+
sessions: day.phase.sessions,
|
|
4039
|
+
phaseSec: day.phase.phaseSec,
|
|
4040
|
+
phaseEvents: day.phase.phaseEvents,
|
|
4041
|
+
waitingSec: day.phase.waitingSec,
|
|
4042
|
+
idleSec: day.phase.idleSec,
|
|
4043
|
+
sessionsWithVerify: day.phase.sessionsWithVerify,
|
|
4044
|
+
sessionsWithHandoff: day.phase.sessionsWithHandoff,
|
|
4045
|
+
bucketRuleVersion: LOG_BUCKETS_V1,
|
|
4046
|
+
lengths: [...day.phase.lengths.values()].sort(
|
|
4047
|
+
(a, b) => a.bucket - b.bucket
|
|
4048
|
+
)
|
|
4049
|
+
}
|
|
4050
|
+
} : {},
|
|
4051
|
+
...routesModels && day.hasRouting ? {
|
|
4052
|
+
routing: {
|
|
4053
|
+
main: asRows(day.routing.main),
|
|
4054
|
+
subagents: asRows(day.routing.subagents)
|
|
4055
|
+
}
|
|
4056
|
+
} : {},
|
|
4057
|
+
...day.hasDelegation ? { delegation: day.delegation } : {},
|
|
4058
|
+
activity: [...day.activity].map(([key, events]) => {
|
|
4059
|
+
const [weekdayUtc, hourUtc] = key.split(":").map(Number);
|
|
4060
|
+
return {
|
|
4061
|
+
weekdayUtc: weekdayUtc ?? 0,
|
|
4062
|
+
hourUtc: hourUtc ?? 0,
|
|
4063
|
+
events
|
|
4064
|
+
};
|
|
4065
|
+
}).sort(
|
|
4066
|
+
(a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc
|
|
4067
|
+
),
|
|
4068
|
+
...day.hasEffort ? {
|
|
4069
|
+
effort: EFFORT_LEVELS.flatMap((level) => {
|
|
4070
|
+
const turns = day.effort.get(level) ?? 0;
|
|
4071
|
+
return turns > 0 ? [{ level, turns }] : [];
|
|
4072
|
+
})
|
|
4073
|
+
} : {},
|
|
4074
|
+
...day.hasThinking ? { thinking: day.thinking } : {},
|
|
4075
|
+
...day.hasDurations ? {
|
|
4076
|
+
turnDurations: {
|
|
4077
|
+
bucketRuleVersion: LOG_BUCKETS_V1,
|
|
4078
|
+
buckets: [...day.turnDurations].map(([bucket, turns]) => ({ bucket, turns })).sort((a, b) => a.bucket - b.bucket)
|
|
4079
|
+
}
|
|
4080
|
+
} : {},
|
|
4081
|
+
...day.hasQuestions ? { questions: day.questions } : {},
|
|
4082
|
+
...day.hasWebSearches ? { webSearches: day.webSearches } : {}
|
|
4083
|
+
}))
|
|
3436
4084
|
};
|
|
3437
4085
|
sessions.clear();
|
|
3438
|
-
|
|
4086
|
+
eventCells.clear();
|
|
3439
4087
|
webSearchesByDate.clear();
|
|
4088
|
+
eventDates.clear();
|
|
3440
4089
|
return finished;
|
|
3441
4090
|
}
|
|
3442
4091
|
};
|
|
@@ -3460,7 +4109,8 @@ function ingestRecord(agg, raw, ctx) {
|
|
|
3460
4109
|
const rec = asObj(raw);
|
|
3461
4110
|
if (!rec) return;
|
|
3462
4111
|
agg.records++;
|
|
3463
|
-
|
|
4112
|
+
const projectDir = projectWorkspaceDirectory(rec) ?? ctx.projectDir;
|
|
4113
|
+
agg.projectDirs.add(projectDir);
|
|
3464
4114
|
const version = asStr(rec.version);
|
|
3465
4115
|
if (version) agg.ccVersions.add(cleanName(version));
|
|
3466
4116
|
const sessionId = asStr(rec.sessionId);
|
|
@@ -3476,6 +4126,8 @@ function ingestRecord(agg, raw, ctx) {
|
|
|
3476
4126
|
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
3477
4127
|
}
|
|
3478
4128
|
}
|
|
4129
|
+
if (sessionId) noteSessionStart(agg, sessionId, tsMs);
|
|
4130
|
+
noteProjectDay(agg, projectDir, tsMs);
|
|
3479
4131
|
const type = asStr(rec.type);
|
|
3480
4132
|
if (type === "assistant") {
|
|
3481
4133
|
ingestClaudeWorkflow(agg, rec, ctx, tsMs);
|
|
@@ -3587,7 +4239,9 @@ function ingestAssistant(agg, rec, tsMs) {
|
|
|
3587
4239
|
const model = asName(msg.model) ?? "(unknown)";
|
|
3588
4240
|
if (model.startsWith("<")) {
|
|
3589
4241
|
agg.syntheticRecords++;
|
|
3590
|
-
|
|
4242
|
+
const synthetic = countsTotal(readCounts(usage));
|
|
4243
|
+
agg.syntheticTokens += synthetic;
|
|
4244
|
+
noteSyntheticTokens(agg, tsMs, synthetic);
|
|
3591
4245
|
return;
|
|
3592
4246
|
}
|
|
3593
4247
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
@@ -3687,6 +4341,7 @@ function buildContribution(usage, model, sidechain, tsMs) {
|
|
|
3687
4341
|
entries,
|
|
3688
4342
|
total: entries.reduce((a, e) => a + countsTotal(e.counts), 0),
|
|
3689
4343
|
sidechain,
|
|
4344
|
+
tsMs,
|
|
3690
4345
|
webSearch: serverTools ? asNum(serverTools.web_search_requests) : 0,
|
|
3691
4346
|
webFetch: serverTools ? asNum(serverTools.web_fetch_requests) : 0,
|
|
3692
4347
|
mirroredIterationTypes: [...mirrored],
|
|
@@ -3710,6 +4365,11 @@ function applyContribution(agg, c, sign) {
|
|
|
3710
4365
|
m.cacheRead += sign * counts.cacheRead;
|
|
3711
4366
|
if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts);
|
|
3712
4367
|
else m.costUSD += sign * costUSD;
|
|
4368
|
+
noteUsageResponse(
|
|
4369
|
+
agg,
|
|
4370
|
+
{ tsMs: c.tsMs, modelKey, counts, costUSD, sidechain: c.sidechain },
|
|
4371
|
+
sign
|
|
4372
|
+
);
|
|
3713
4373
|
});
|
|
3714
4374
|
if (c.sidechain) agg.sidechainTokens += sign * c.total;
|
|
3715
4375
|
else agg.mainTokens += sign * c.total;
|
|
@@ -3976,12 +4636,14 @@ function ingestLine(agg, raw, state, sinceMs) {
|
|
|
3976
4636
|
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
3977
4637
|
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
3978
4638
|
}
|
|
3979
|
-
noteActivity(agg, state);
|
|
4639
|
+
noteActivity(agg, state, tsMs);
|
|
4640
|
+
noteProjectDay(agg, state.cwd ?? "(unknown)", tsMs);
|
|
3980
4641
|
if (type === "event_msg" && payload) ingestEvent(agg, payload, state, tsMs);
|
|
3981
4642
|
else if (type === "response_item" && payload)
|
|
3982
4643
|
ingestItem(agg, payload, state, tsMs);
|
|
3983
4644
|
}
|
|
3984
|
-
function noteActivity(agg, state) {
|
|
4645
|
+
function noteActivity(agg, state, tsMs) {
|
|
4646
|
+
if (state.sessionId) noteSessionStart(agg, state.sessionId, tsMs);
|
|
3985
4647
|
if (state.counted) return;
|
|
3986
4648
|
state.counted = true;
|
|
3987
4649
|
if (state.sessionId) agg.sessions.add(state.sessionId);
|
|
@@ -4012,7 +4674,9 @@ function ingestEvent(agg, payload, state, tsMs) {
|
|
|
4012
4674
|
agg,
|
|
4013
4675
|
modelKey,
|
|
4014
4676
|
counts,
|
|
4015
|
-
apiEquivalentCost(modelKey, counts, tsMs)
|
|
4677
|
+
apiEquivalentCost(modelKey, counts, tsMs),
|
|
4678
|
+
1,
|
|
4679
|
+
{ tsMs }
|
|
4016
4680
|
);
|
|
4017
4681
|
agg.mainTokens += total;
|
|
4018
4682
|
if (tsMs !== null && state.sessionId) {
|
|
@@ -4415,10 +5079,14 @@ function ingestMessageRow(agg, state, row) {
|
|
|
4415
5079
|
const session = sessionId ? state.sessions.get(sessionId) : void 0;
|
|
4416
5080
|
if (sessionId) {
|
|
4417
5081
|
agg.sessions.add(sessionId);
|
|
5082
|
+
noteSessionStart(agg, sessionId, tsMs);
|
|
4418
5083
|
if (session?.version) agg.ccVersions.add(cleanName(session.version));
|
|
4419
5084
|
}
|
|
4420
5085
|
const cwd = asStr(row.cwd);
|
|
4421
|
-
if (cwd)
|
|
5086
|
+
if (cwd) {
|
|
5087
|
+
agg.projectDirs.add(cwd);
|
|
5088
|
+
noteProjectDay(agg, cwd, tsMs);
|
|
5089
|
+
}
|
|
4422
5090
|
if (asStr(row.role) !== "assistant") return;
|
|
4423
5091
|
agg.assistantRecords++;
|
|
4424
5092
|
agg.distinctResponses++;
|
|
@@ -4441,7 +5109,9 @@ function ingestMessageRow(agg, state, row) {
|
|
|
4441
5109
|
agg,
|
|
4442
5110
|
modelKey,
|
|
4443
5111
|
counts,
|
|
4444
|
-
apiEquivalentCost(modelKey, counts, tsMs)
|
|
5112
|
+
apiEquivalentCost(modelKey, counts, tsMs),
|
|
5113
|
+
1,
|
|
5114
|
+
{ tsMs, sidechain: Boolean(session?.parentId) }
|
|
4445
5115
|
);
|
|
4446
5116
|
if (sessionId && tsMs !== null) {
|
|
4447
5117
|
const completed = asNum(row.completedTsMs);
|
|
@@ -4917,7 +5587,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
4917
5587
|
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
4918
5588
|
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
4919
5589
|
}
|
|
4920
|
-
noteActivity2(agg, state);
|
|
5590
|
+
noteActivity2(agg, state, tsMs);
|
|
5591
|
+
noteProjectDay(agg, state.cwd ?? "(unknown)", tsMs);
|
|
4921
5592
|
if (role === "assistant" && message) {
|
|
4922
5593
|
agg.assistantRecords++;
|
|
4923
5594
|
const served = asStr(message.responseModel);
|
|
@@ -4964,7 +5635,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
4964
5635
|
countUsage(agg, fold, rec, 0, rec.usage, state.modelKey, tsMs);
|
|
4965
5636
|
}
|
|
4966
5637
|
}
|
|
4967
|
-
function noteActivity2(agg, state) {
|
|
5638
|
+
function noteActivity2(agg, state, tsMs) {
|
|
5639
|
+
if (state.sessionId) noteSessionStart(agg, state.sessionId, tsMs);
|
|
4968
5640
|
if (state.counted) return;
|
|
4969
5641
|
state.counted = true;
|
|
4970
5642
|
if (state.sessionId) agg.sessions.add(state.sessionId);
|
|
@@ -4993,7 +5665,9 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
|
|
|
4993
5665
|
agg,
|
|
4994
5666
|
key,
|
|
4995
5667
|
counts,
|
|
4996
|
-
priceable ? apiEquivalentCost(key, counts, tsMs) : null
|
|
5668
|
+
priceable ? apiEquivalentCost(key, counts, tsMs) : null,
|
|
5669
|
+
1,
|
|
5670
|
+
{ tsMs }
|
|
4997
5671
|
);
|
|
4998
5672
|
agg.mainTokens += total;
|
|
4999
5673
|
return "counted";
|
|
@@ -5944,6 +6618,152 @@ import { dirname as dirname7, join as join10 } from "path";
|
|
|
5944
6618
|
// src/sync/stage.ts
|
|
5945
6619
|
import { createHash as createHash2 } from "crypto";
|
|
5946
6620
|
|
|
6621
|
+
// src/usage/days.ts
|
|
6622
|
+
var round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
6623
|
+
function buildUsageDays(input) {
|
|
6624
|
+
const { aggregate: agg, harness, publishCost, projectWorkspaceId } = input;
|
|
6625
|
+
const sessionsByDay = /* @__PURE__ */ new Map();
|
|
6626
|
+
for (const startMs of agg.sessionStarts.values()) {
|
|
6627
|
+
const date = utcDateOf(startMs);
|
|
6628
|
+
sessionsByDay.set(date, (sessionsByDay.get(date) ?? 0) + 1);
|
|
6629
|
+
}
|
|
6630
|
+
const dates = [
|
|
6631
|
+
.../* @__PURE__ */ new Set([...agg.usageDays.keys(), ...sessionsByDay.keys()])
|
|
6632
|
+
].sort();
|
|
6633
|
+
const out = /* @__PURE__ */ new Map();
|
|
6634
|
+
for (const date of dates) {
|
|
6635
|
+
const acc = agg.usageDays.get(date);
|
|
6636
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6637
|
+
let unpriced = 0;
|
|
6638
|
+
for (const [modelKey, m] of acc?.models ?? []) {
|
|
6639
|
+
const id = sanitizeModelId(baseModelId(modelKey));
|
|
6640
|
+
let g = groups.get(id);
|
|
6641
|
+
if (!g) {
|
|
6642
|
+
g = {
|
|
6643
|
+
tokens: {
|
|
6644
|
+
input: 0,
|
|
6645
|
+
output: 0,
|
|
6646
|
+
cacheWrite: 0,
|
|
6647
|
+
cacheRead: 0,
|
|
6648
|
+
cacheWriteTtl: { fiveMinute: 0, oneHour: 0, unsplit: 0 }
|
|
6649
|
+
},
|
|
6650
|
+
costUSD: 0,
|
|
6651
|
+
unpricedTokens: 0,
|
|
6652
|
+
table: null
|
|
6653
|
+
};
|
|
6654
|
+
groups.set(id, g);
|
|
6655
|
+
}
|
|
6656
|
+
g.table ??= pricingTableFor(modelKey);
|
|
6657
|
+
g.tokens.input += m.counts.input;
|
|
6658
|
+
g.tokens.output += m.counts.output;
|
|
6659
|
+
g.tokens.cacheRead += m.counts.cacheRead;
|
|
6660
|
+
g.tokens.cacheWrite += m.counts.cacheWrite5m + m.counts.cacheWrite1h + m.counts.cacheWriteUnsplit;
|
|
6661
|
+
g.tokens.cacheWriteTtl.fiveMinute += m.counts.cacheWrite5m;
|
|
6662
|
+
g.tokens.cacheWriteTtl.oneHour += m.counts.cacheWrite1h;
|
|
6663
|
+
g.tokens.cacheWriteTtl.unsplit += m.counts.cacheWriteUnsplit;
|
|
6664
|
+
g.costUSD += m.costUSD;
|
|
6665
|
+
g.unpricedTokens += m.unpricedTokens;
|
|
6666
|
+
unpriced += m.unpricedTokens;
|
|
6667
|
+
}
|
|
6668
|
+
const models = [...groups.entries()].map(([model, g]) => {
|
|
6669
|
+
const { cacheWriteTtl, ...plain } = g.tokens;
|
|
6670
|
+
const tokens = g.tokens.cacheWrite > 0 ? { ...plain, cacheWriteTtl } : plain;
|
|
6671
|
+
const row = { model, tokens };
|
|
6672
|
+
if (publishCost && g.unpricedTokens === 0 && g.table !== null && countsTotalOf(tokens) > 0) {
|
|
6673
|
+
row.usd = round6(g.costUSD);
|
|
6674
|
+
row.pricingTable = g.table;
|
|
6675
|
+
}
|
|
6676
|
+
return row;
|
|
6677
|
+
}).filter((row) => countsTotalOf(row.tokens) > 0).sort(
|
|
6678
|
+
(a, b) => countsTotalOf(b.tokens) - countsTotalOf(a.tokens) || a.model.localeCompare(b.model)
|
|
6679
|
+
);
|
|
6680
|
+
out.set(date, {
|
|
6681
|
+
harness,
|
|
6682
|
+
sessions: sessionsByDay.get(date) ?? 0,
|
|
6683
|
+
projectKeys: [
|
|
6684
|
+
...new Set([...acc?.projectDirs ?? []].map(projectWorkspaceId))
|
|
6685
|
+
].sort(),
|
|
6686
|
+
models,
|
|
6687
|
+
subagentTokens: acc?.subagentTokens ?? 0,
|
|
6688
|
+
excludedTokens: { unpriced, synthetic: acc?.syntheticTokens ?? 0 }
|
|
6689
|
+
});
|
|
6690
|
+
}
|
|
6691
|
+
return out;
|
|
6692
|
+
}
|
|
6693
|
+
var countsTotalOf = (t) => t.input + t.output + t.cacheWrite + t.cacheRead;
|
|
6694
|
+
function mergeUsageDays(perHarness) {
|
|
6695
|
+
const out = /* @__PURE__ */ new Map();
|
|
6696
|
+
const dates = [
|
|
6697
|
+
...new Set(perHarness.flatMap((days) => [...days.keys()]))
|
|
6698
|
+
].sort();
|
|
6699
|
+
for (const date of dates) {
|
|
6700
|
+
const harnesses = [];
|
|
6701
|
+
for (const days of perHarness) {
|
|
6702
|
+
const day = days.get(date);
|
|
6703
|
+
if (day) harnesses.push(day);
|
|
6704
|
+
}
|
|
6705
|
+
out.set(date, { harnesses });
|
|
6706
|
+
}
|
|
6707
|
+
return out;
|
|
6708
|
+
}
|
|
6709
|
+
function buildMeasuredDays(input) {
|
|
6710
|
+
const workflowByDate = /* @__PURE__ */ new Map();
|
|
6711
|
+
for (const day of input.workflow ?? []) workflowByDate.set(day.date, day);
|
|
6712
|
+
const dates = [.../* @__PURE__ */ new Set([...input.usage.keys(), ...workflowByDate.keys()])].filter(
|
|
6713
|
+
(d) => /^\d{4}-\d{2}-\d{2}$/.test(d) && d >= input.from && d <= input.to
|
|
6714
|
+
).sort();
|
|
6715
|
+
return dates.map((date) => {
|
|
6716
|
+
const usage = input.usage.get(date);
|
|
6717
|
+
const workflow = workflowByDate.get(date);
|
|
6718
|
+
return {
|
|
6719
|
+
date,
|
|
6720
|
+
...usage ? { usage } : {},
|
|
6721
|
+
...workflow ? { workflow } : {}
|
|
6722
|
+
};
|
|
6723
|
+
});
|
|
6724
|
+
}
|
|
6725
|
+
|
|
6726
|
+
// src/usage/diff.ts
|
|
6727
|
+
var MAX_DAY_WINDOW = 400;
|
|
6728
|
+
var DAY_MS = 864e5;
|
|
6729
|
+
function retentionFloor(todayUtc, days) {
|
|
6730
|
+
const span = Math.max(1, Math.min(days, MAX_DAY_WINDOW));
|
|
6731
|
+
const todayMs = Date.parse(`${todayUtc}T00:00:00.000Z`);
|
|
6732
|
+
return new Date(todayMs - (span - 1) * DAY_MS).toISOString().slice(0, 10);
|
|
6733
|
+
}
|
|
6734
|
+
function selectDaysToPublish(input) {
|
|
6735
|
+
const { local, manifest, todayUtc } = input;
|
|
6736
|
+
const retention = manifest?.retentionDays ?? MAX_DAY_WINDOW;
|
|
6737
|
+
const floor = retentionFloor(todayUtc, retention);
|
|
6738
|
+
const comparable = manifest !== null && manifest.aggregateVersion === MEASURED_DAYS_V1;
|
|
6739
|
+
const held = /* @__PURE__ */ new Map();
|
|
6740
|
+
if (comparable) {
|
|
6741
|
+
for (const day of manifest.days) held.set(day.date, day.fingerprint);
|
|
6742
|
+
}
|
|
6743
|
+
const send = [];
|
|
6744
|
+
const skipped = [];
|
|
6745
|
+
for (const day of [...local].sort((a, b) => a.date.localeCompare(b.date))) {
|
|
6746
|
+
if (day.date < floor) {
|
|
6747
|
+
skipped.push({ date: day.date, reason: "expired" });
|
|
6748
|
+
continue;
|
|
6749
|
+
}
|
|
6750
|
+
if (comparable && day.date !== todayUtc) {
|
|
6751
|
+
const fingerprint = held.get(day.date);
|
|
6752
|
+
if (fingerprint !== void 0 && fingerprint === dayFingerprint(day)) {
|
|
6753
|
+
skipped.push({ date: day.date, reason: "unchanged" });
|
|
6754
|
+
continue;
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
send.push(day);
|
|
6758
|
+
}
|
|
6759
|
+
return {
|
|
6760
|
+
send,
|
|
6761
|
+
unchanged: skipped.filter((s) => s.reason === "unchanged").length,
|
|
6762
|
+
skipped,
|
|
6763
|
+
mode: comparable ? "diff" : "full"
|
|
6764
|
+
};
|
|
6765
|
+
}
|
|
6766
|
+
|
|
5947
6767
|
// src/workflow/git.ts
|
|
5948
6768
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5949
6769
|
import path6 from "path";
|
|
@@ -5962,11 +6782,11 @@ var defaultRunner = (cwd, args) => {
|
|
|
5962
6782
|
return null;
|
|
5963
6783
|
}
|
|
5964
6784
|
};
|
|
5965
|
-
var
|
|
6785
|
+
var emptyGitDay = () => ({
|
|
5966
6786
|
testFileRuleVersion: TEST_FILE_RULE_VERSION,
|
|
5967
6787
|
fileTypeRuleVersion: FILE_TYPE_RULE_VERSION,
|
|
5968
6788
|
commitSetRuleVersion: COMMIT_SET_RULE_VERSION,
|
|
5969
|
-
|
|
6789
|
+
commits: 0,
|
|
5970
6790
|
lateNightCommits: 0,
|
|
5971
6791
|
additions: 0,
|
|
5972
6792
|
removals: 0,
|
|
@@ -6116,9 +6936,25 @@ function extractGitWorkflow(options) {
|
|
|
6116
6936
|
const root = run(directory, ["rev-parse", "--show-toplevel"])?.trim();
|
|
6117
6937
|
if (root) roots.add(root);
|
|
6118
6938
|
}
|
|
6119
|
-
const
|
|
6120
|
-
const
|
|
6121
|
-
|
|
6939
|
+
const days = /* @__PURE__ */ new Map();
|
|
6940
|
+
const dayOf = (date) => {
|
|
6941
|
+
let day = days.get(date);
|
|
6942
|
+
if (!day) {
|
|
6943
|
+
const {
|
|
6944
|
+
changedLinesByExtension: _extensions,
|
|
6945
|
+
weekdayHourCells: _cells,
|
|
6946
|
+
...rest
|
|
6947
|
+
} = emptyGitDay();
|
|
6948
|
+
day = {
|
|
6949
|
+
...rest,
|
|
6950
|
+
changedLinesPerCommit: [],
|
|
6951
|
+
extensionLines: /* @__PURE__ */ new Map(),
|
|
6952
|
+
cells: /* @__PURE__ */ new Map()
|
|
6953
|
+
};
|
|
6954
|
+
days.set(date, day);
|
|
6955
|
+
}
|
|
6956
|
+
return day;
|
|
6957
|
+
};
|
|
6122
6958
|
const seenCommits = /* @__PURE__ */ new Set();
|
|
6123
6959
|
for (const root of roots) {
|
|
6124
6960
|
const history = run(root, [
|
|
@@ -6133,17 +6969,25 @@ function extractGitWorkflow(options) {
|
|
|
6133
6969
|
let current;
|
|
6134
6970
|
const finishCommit = () => {
|
|
6135
6971
|
if (!current?.included || !current.authored) return;
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6972
|
+
const day = dayOf(current.date);
|
|
6973
|
+
day.commits++;
|
|
6974
|
+
day.additions += current.additions;
|
|
6975
|
+
day.removals += current.removals;
|
|
6976
|
+
day.changedLinesPerCommit.push(current.changedLines);
|
|
6977
|
+
if (current.touchesTest) day.testFileCommits++;
|
|
6141
6978
|
const { weekdayUtc, hourUtc } = current.cell;
|
|
6142
6979
|
if (isLateNight(localHour(hourUtc, options.utcOffsetMinutes))) {
|
|
6143
|
-
|
|
6980
|
+
day.lateNightCommits++;
|
|
6144
6981
|
}
|
|
6145
6982
|
const cellKey = `${weekdayUtc}:${hourUtc}`;
|
|
6146
|
-
cells.set(cellKey, (cells.get(cellKey) ?? 0) + 1);
|
|
6983
|
+
day.cells.set(cellKey, (day.cells.get(cellKey) ?? 0) + 1);
|
|
6984
|
+
day.withheldExtensionLines += current.withheldLines;
|
|
6985
|
+
for (const [extension, lines2] of current.extensionLines) {
|
|
6986
|
+
day.extensionLines.set(
|
|
6987
|
+
extension,
|
|
6988
|
+
(day.extensionLines.get(extension) ?? 0) + lines2
|
|
6989
|
+
);
|
|
6990
|
+
}
|
|
6147
6991
|
};
|
|
6148
6992
|
const fields = history.split("\0");
|
|
6149
6993
|
for (let fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
|
|
@@ -6156,12 +7000,15 @@ function extractGitWorkflow(options) {
|
|
|
6156
7000
|
const included = Number.isFinite(authoredMs) && authoredMs >= options.fromMs && authoredMs <= options.toMs && !seenCommits.has(hash);
|
|
6157
7001
|
current = {
|
|
6158
7002
|
included,
|
|
7003
|
+
date: included ? new Date(authoredMs).toISOString().slice(0, 10) : "",
|
|
6159
7004
|
cell: utcCell(included ? authoredMs : 0),
|
|
6160
7005
|
authored: false,
|
|
6161
7006
|
additions: 0,
|
|
6162
7007
|
removals: 0,
|
|
6163
7008
|
changedLines: 0,
|
|
6164
|
-
touchesTest: false
|
|
7009
|
+
touchesTest: false,
|
|
7010
|
+
withheldLines: 0,
|
|
7011
|
+
extensionLines: /* @__PURE__ */ new Map()
|
|
6165
7012
|
};
|
|
6166
7013
|
if (included) seenCommits.add(hash);
|
|
6167
7014
|
continue;
|
|
@@ -6184,20 +7031,34 @@ function extractGitWorkflow(options) {
|
|
|
6184
7031
|
if (fileChangedLines <= 0) continue;
|
|
6185
7032
|
const extension = path6.extname(file).toLowerCase();
|
|
6186
7033
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
6187
|
-
extensionLines.set(
|
|
7034
|
+
current.extensionLines.set(
|
|
6188
7035
|
extension,
|
|
6189
|
-
(extensionLines.get(extension) ?? 0) + fileChangedLines
|
|
7036
|
+
(current.extensionLines.get(extension) ?? 0) + fileChangedLines
|
|
6190
7037
|
);
|
|
6191
|
-
} else
|
|
7038
|
+
} else current.withheldLines += fileChangedLines;
|
|
6192
7039
|
}
|
|
6193
7040
|
finishCommit();
|
|
6194
7041
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
7042
|
+
return {
|
|
7043
|
+
days: [...days].sort(([a], [b]) => a.localeCompare(b)).map(([date, day]) => {
|
|
7044
|
+
const { extensionLines, cells, ...rest } = day;
|
|
7045
|
+
return {
|
|
7046
|
+
date,
|
|
7047
|
+
...rest,
|
|
7048
|
+
changedLinesByExtension: [...extensionLines].map(([extension, changedLines]) => ({ extension, changedLines })).sort((a, b) => a.extension.localeCompare(b.extension)),
|
|
7049
|
+
weekdayHourCells: [...cells].map(([key, commits]) => {
|
|
7050
|
+
const [weekdayUtc, hourUtc] = key.split(":").map(Number);
|
|
7051
|
+
return {
|
|
7052
|
+
weekdayUtc: weekdayUtc ?? 0,
|
|
7053
|
+
hourUtc: hourUtc ?? 0,
|
|
7054
|
+
commits
|
|
7055
|
+
};
|
|
7056
|
+
}).sort(
|
|
7057
|
+
(a, b) => a.weekdayUtc - b.weekdayUtc || a.hourUtc - b.hourUtc
|
|
7058
|
+
)
|
|
7059
|
+
};
|
|
7060
|
+
})
|
|
7061
|
+
};
|
|
6201
7062
|
}
|
|
6202
7063
|
|
|
6203
7064
|
// src/workflow/extract.ts
|
|
@@ -6218,50 +7079,44 @@ function machineUtcOffsetMinutes(now = /* @__PURE__ */ new Date()) {
|
|
|
6218
7079
|
return -now.getTimezoneOffset();
|
|
6219
7080
|
}
|
|
6220
7081
|
function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machineUtcOffsetMinutes()) {
|
|
7082
|
+
const harnessDays = /* @__PURE__ */ new Map();
|
|
6221
7083
|
const projectDays = /* @__PURE__ */ new Map();
|
|
6222
|
-
const
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
7084
|
+
for (const { aggregate, local } of harnessWorkflows) {
|
|
7085
|
+
for (const { date, ...day } of aggregate.days) {
|
|
7086
|
+
const rows = harnessDays.get(date) ?? [];
|
|
7087
|
+
const { phase, ...safe } = day;
|
|
7088
|
+
rows.push(
|
|
7089
|
+
aggregate.gate.publishable && phase ? { ...safe, phase } : safe
|
|
7090
|
+
);
|
|
7091
|
+
harnessDays.set(date, rows);
|
|
7092
|
+
}
|
|
6227
7093
|
for (const [date, workspaces] of local.activeProjectDays) {
|
|
6228
7094
|
const projects = projectDays.get(date) ?? /* @__PURE__ */ new Set();
|
|
6229
7095
|
for (const project of workspaces) projects.add(project);
|
|
6230
7096
|
projectDays.set(date, projects);
|
|
6231
7097
|
}
|
|
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
7098
|
}
|
|
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
|
-
];
|
|
7099
|
+
const gitDays = /* @__PURE__ */ new Map();
|
|
7100
|
+
for (const { date, ...day } of git.days) gitDays.set(date, day);
|
|
7101
|
+
const dates = [
|
|
7102
|
+
.../* @__PURE__ */ new Set([
|
|
7103
|
+
...harnessDays.keys(),
|
|
7104
|
+
...gitDays.keys(),
|
|
7105
|
+
...projectDays.keys()
|
|
7106
|
+
])
|
|
7107
|
+
].sort();
|
|
6256
7108
|
return {
|
|
6257
|
-
aggregateVersion:
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
7109
|
+
aggregateVersion: WORKFLOW_AGGREGATES_V2,
|
|
7110
|
+
utcOffsetMinutes,
|
|
7111
|
+
days: dates.map((date) => {
|
|
7112
|
+
const projects = projectDays.get(date)?.size;
|
|
7113
|
+
return {
|
|
7114
|
+
date,
|
|
7115
|
+
harnesses: harnessDays.get(date) ?? [],
|
|
7116
|
+
git: gitDays.get(date) ?? emptyGitDay(),
|
|
7117
|
+
...projects === void 0 ? {} : { parallelProjects: projects }
|
|
7118
|
+
};
|
|
7119
|
+
})
|
|
6265
7120
|
};
|
|
6266
7121
|
}
|
|
6267
7122
|
|
|
@@ -6470,18 +7325,25 @@ function payloadBlock(payload, width, ownWindow, stats) {
|
|
|
6470
7325
|
}
|
|
6471
7326
|
var MODEL_ROLLUP = 0.01;
|
|
6472
7327
|
var PHASE_ORDER = ["scout", "build", "verify", "handoff", "unknown"];
|
|
6473
|
-
function workflowBlock(
|
|
7328
|
+
function workflowBlock(workflowDays, utcOffsetMinutes, host) {
|
|
6474
7329
|
const out = [];
|
|
6475
|
-
const
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
7330
|
+
const folded = foldWorkflowDays(workflowDays, {
|
|
7331
|
+
aggregateVersion: WORKFLOW_AGGREGATES_V2,
|
|
7332
|
+
utcOffsetMinutes
|
|
7333
|
+
});
|
|
7334
|
+
const harnesses = folded?.harnesses ?? [];
|
|
7335
|
+
const withPlaybook = harnesses.filter((h) => h.phase);
|
|
7336
|
+
const sessions = harnesses.reduce((a, h) => a + h.sessions, 0);
|
|
6480
7337
|
const ruleVersions = [
|
|
6481
7338
|
...new Set(withPlaybook.map((h) => h.phase?.ruleVersion ?? ""))
|
|
6482
7339
|
].filter(Boolean);
|
|
6483
7340
|
out.push(
|
|
6484
|
-
`workflow ${
|
|
7341
|
+
`workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${WORKFLOW_AGGREGATES_V2}`
|
|
7342
|
+
);
|
|
7343
|
+
const first = folded?.dates[0];
|
|
7344
|
+
const last = folded?.dates.at(-1);
|
|
7345
|
+
out.push(
|
|
7346
|
+
` ${workflowDays.length} day${workflowDays.length === 1 ? "" : "s"}${first && last ? ` \xB7 ${first} to ${last}` : ""}`
|
|
6485
7347
|
);
|
|
6486
7348
|
const seconds = PHASE_ORDER.map(
|
|
6487
7349
|
(phase) => withPlaybook.reduce((a, h) => a + (h.phase?.phaseSec[phase] ?? 0), 0)
|
|
@@ -6493,16 +7355,31 @@ function workflowBlock(workflow, host) {
|
|
|
6493
7355
|
).join(" \xB7 ");
|
|
6494
7356
|
out.push(` ${mix} \xB7 ${ruleVersions.join(", ")}`);
|
|
6495
7357
|
}
|
|
6496
|
-
const git =
|
|
6497
|
-
out.push(
|
|
6498
|
-
`git ${git.totalCommits} commits \xB7 ${fmtTokens(git.additions + git.removals)} lines changed`
|
|
6499
|
-
);
|
|
7358
|
+
const git = folded?.git;
|
|
6500
7359
|
out.push(
|
|
6501
|
-
`
|
|
7360
|
+
`git ${git?.commits ?? 0} commits \xB7 ${fmtTokens((git?.additions ?? 0) + (git?.removals ?? 0))} lines changed`
|
|
6502
7361
|
);
|
|
6503
7362
|
out.push(` (Publish workflow is on for ${host})`);
|
|
6504
7363
|
return out;
|
|
6505
7364
|
}
|
|
7365
|
+
function daysLine(measuredDays, selection) {
|
|
7366
|
+
const n = measuredDays.days.length;
|
|
7367
|
+
const head = `${n} day${n === 1 ? "" : "s"} to publish`;
|
|
7368
|
+
const unchanged = selection?.unchanged ?? 0;
|
|
7369
|
+
return unchanged > 0 ? `${head}, ${unchanged} unchanged` : head;
|
|
7370
|
+
}
|
|
7371
|
+
function daysBlock(measuredDays, selection) {
|
|
7372
|
+
const out = [`days ${daysLine(measuredDays, selection)}`];
|
|
7373
|
+
const first = measuredDays.days[0]?.date;
|
|
7374
|
+
const last = measuredDays.days.at(-1)?.date;
|
|
7375
|
+
const usageDays = measuredDays.days.filter((d) => d.usage).length;
|
|
7376
|
+
if (first && last) {
|
|
7377
|
+
out.push(
|
|
7378
|
+
` ${first} to ${last} \xB7 ${usageDays} with usage \xB7 ${measuredDays.aggregateVersion}`
|
|
7379
|
+
);
|
|
7380
|
+
}
|
|
7381
|
+
return out;
|
|
7382
|
+
}
|
|
6506
7383
|
function buildGateSummary(ctx) {
|
|
6507
7384
|
const { body, keptPrivate, config, source, baseUrl } = ctx;
|
|
6508
7385
|
const { payloads } = body;
|
|
@@ -6533,10 +7410,23 @@ function buildGateSummary(ctx) {
|
|
|
6533
7410
|
out.push(...payloadBlock(payload, width, windows.size > 1, stats));
|
|
6534
7411
|
}
|
|
6535
7412
|
if (out[out.length - 1] === "") out.pop();
|
|
7413
|
+
if (body.measuredDays) {
|
|
7414
|
+
out.push("");
|
|
7415
|
+
out.push(...daysBlock(body.measuredDays, ctx.days));
|
|
7416
|
+
}
|
|
6536
7417
|
out.push("");
|
|
6537
|
-
|
|
6538
|
-
|
|
7418
|
+
const workflowDays = (body.measuredDays?.days ?? []).flatMap(
|
|
7419
|
+
(d) => d.workflow ? [d.workflow] : []
|
|
6539
7420
|
);
|
|
7421
|
+
if (!config.publishWorkflow || !body.measuredDays) {
|
|
7422
|
+
out.push("workflow not published");
|
|
7423
|
+
} else if (workflowDays.length === 0) {
|
|
7424
|
+
out.push("workflow on, no changed day to publish");
|
|
7425
|
+
} else {
|
|
7426
|
+
out.push(
|
|
7427
|
+
...workflowBlock(workflowDays, body.measuredDays.utcOffsetMinutes, host)
|
|
7428
|
+
);
|
|
7429
|
+
}
|
|
6540
7430
|
const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
|
|
6541
7431
|
if (n > 0) {
|
|
6542
7432
|
out.push("");
|
|
@@ -6578,6 +7468,7 @@ function buildGateSummary(ctx) {
|
|
|
6578
7468
|
}
|
|
6579
7469
|
|
|
6580
7470
|
// src/sync/stage.ts
|
|
7471
|
+
var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
6581
7472
|
function stageId(bodyJson) {
|
|
6582
7473
|
return createHash2("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
6583
7474
|
}
|
|
@@ -6588,20 +7479,33 @@ async function stageSync(deps) {
|
|
|
6588
7479
|
const adapters = deps.adaptersImpl ?? detectedAdapters;
|
|
6589
7480
|
const windowDays = deps.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
6590
7481
|
const projectWorkspaceId = deps.getProjectWorkspaceIdImpl ?? getProjectWorkspaceId;
|
|
7482
|
+
const fetchManifest = deps.fetchManifestImpl ?? fetchDayManifest;
|
|
6591
7483
|
const { config, source } = await loadConfig({
|
|
6592
7484
|
baseUrl: deps.baseUrl,
|
|
6593
7485
|
...token ? { token } : {}
|
|
6594
7486
|
});
|
|
7487
|
+
let manifest = null;
|
|
7488
|
+
if (token) {
|
|
7489
|
+
try {
|
|
7490
|
+
manifest = await fetchManifest(deps.baseUrl, token);
|
|
7491
|
+
} catch {
|
|
7492
|
+
manifest = null;
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
const retentionDays = Math.max(
|
|
7496
|
+
1,
|
|
7497
|
+
Math.min(manifest?.retentionDays ?? MAX_DAY_WINDOW, MAX_DAY_WINDOW)
|
|
7498
|
+
);
|
|
6595
7499
|
const built = [];
|
|
6596
7500
|
const scanStats = {};
|
|
6597
7501
|
const workflowScans = [];
|
|
7502
|
+
const usageScans = [];
|
|
6598
7503
|
const sinceMs = windowStartMs(now, windowDays);
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
});
|
|
7504
|
+
const daysSinceMs = windowStartMs(now, retentionDays);
|
|
7505
|
+
const active = await adapters(sinceMs);
|
|
7506
|
+
for (const adapter of active) {
|
|
7507
|
+
const { aggregate, stats } = await adapter.scan({ sinceMs });
|
|
6603
7508
|
scanStats[adapter.name] = stats;
|
|
6604
|
-
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
6605
7509
|
built.push(
|
|
6606
7510
|
buildPayload({
|
|
6607
7511
|
aggregate,
|
|
@@ -6615,19 +7519,51 @@ async function stageSync(deps) {
|
|
|
6615
7519
|
})
|
|
6616
7520
|
);
|
|
6617
7521
|
}
|
|
7522
|
+
for (const adapter of active) {
|
|
7523
|
+
const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
|
|
7524
|
+
sinceMs: daysSinceMs
|
|
7525
|
+
});
|
|
7526
|
+
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
7527
|
+
usageScans.push(
|
|
7528
|
+
buildUsageDays({
|
|
7529
|
+
harness: adapter.name,
|
|
7530
|
+
aggregate,
|
|
7531
|
+
publishCost: config.publishCost,
|
|
7532
|
+
projectWorkspaceId
|
|
7533
|
+
})
|
|
7534
|
+
);
|
|
7535
|
+
}
|
|
6618
7536
|
const settings = (deps.getSettingsImpl ?? getSettings)();
|
|
6619
7537
|
const workflow = workflowScans.length > 0 && config.publishWorkflow ? extractLocalWorkflow({
|
|
6620
7538
|
harnesses: workflowScans,
|
|
6621
|
-
fromMs:
|
|
7539
|
+
fromMs: daysSinceMs,
|
|
6622
7540
|
toMs: now,
|
|
6623
7541
|
...deps.gitRunnerImpl ? { run: deps.gitRunnerImpl } : {}
|
|
6624
7542
|
}) : void 0;
|
|
7543
|
+
const localDays = applyDayConsent(
|
|
7544
|
+
buildMeasuredDays({
|
|
7545
|
+
usage: mergeUsageDays(usageScans),
|
|
7546
|
+
...workflow ? { workflow: workflow.days } : {},
|
|
7547
|
+
from: utcDate2(daysSinceMs),
|
|
7548
|
+
to: utcDate2(now)
|
|
7549
|
+
}),
|
|
7550
|
+
config
|
|
7551
|
+
);
|
|
7552
|
+
const days = selectDaysToPublish({
|
|
7553
|
+
local: localDays,
|
|
7554
|
+
manifest,
|
|
7555
|
+
todayUtc: utcDate2(now)
|
|
7556
|
+
});
|
|
6625
7557
|
const body = buildSyncBody(
|
|
6626
7558
|
built,
|
|
6627
7559
|
config,
|
|
6628
7560
|
settings.autoSync,
|
|
6629
7561
|
deps.trigger,
|
|
6630
|
-
|
|
7562
|
+
active.length > 0 ? {
|
|
7563
|
+
aggregateVersion: MEASURED_DAYS_V1,
|
|
7564
|
+
utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
|
|
7565
|
+
days: days.send
|
|
7566
|
+
} : void 0,
|
|
6631
7567
|
CLI_VERSION
|
|
6632
7568
|
);
|
|
6633
7569
|
const bodyJson = JSON.stringify(body);
|
|
@@ -6639,6 +7575,7 @@ async function stageSync(deps) {
|
|
|
6639
7575
|
source,
|
|
6640
7576
|
baseUrl: deps.baseUrl,
|
|
6641
7577
|
scanStats,
|
|
7578
|
+
days,
|
|
6642
7579
|
// The real terminal, so the inventory rows break where this window ends
|
|
6643
7580
|
// (#217). A pipe reports nothing and the preview falls back to 80.
|
|
6644
7581
|
width: process.stdout.columns
|
|
@@ -6661,7 +7598,8 @@ async function stageSync(deps) {
|
|
|
6661
7598
|
config,
|
|
6662
7599
|
token,
|
|
6663
7600
|
stagedAt: now,
|
|
6664
|
-
blockedReason
|
|
7601
|
+
blockedReason,
|
|
7602
|
+
days
|
|
6665
7603
|
};
|
|
6666
7604
|
}
|
|
6667
7605
|
|