@akagilnc/pi-workflow-roles 0.1.5303 → 0.1.5307

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.
@@ -15559,11 +15559,21 @@ function admitFrameSpanFromRows(rows) {
15559
15559
  }
15560
15560
  return {
15561
15561
  models,
15562
- frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt },
15562
+ frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt, wallMs: endedMs - startedMs },
15563
15563
  partialFirstFrameAt: { status: "present", at: span.startedAt },
15564
15564
  partialLastFrameAt: { status: "present", at: span.endedAt }
15565
15565
  };
15566
15566
  }
15567
+ function sumAnalystRunWallMs(runs) {
15568
+ let total = 0;
15569
+ for (const run of runs) total += run.frameSpan.wallMs;
15570
+ return total;
15571
+ }
15572
+ function compareAnalystLegs(a, b) {
15573
+ if (a.book !== b.book) return a.book.localeCompare(b.book);
15574
+ if (a.role !== b.role) return a.role.localeCompare(b.role);
15575
+ return a.runId.localeCompare(b.runId);
15576
+ }
15567
15577
  async function classifyScopedRun(input) {
15568
15578
  const missingSources = [];
15569
15579
  const reasons = [];
@@ -15844,9 +15854,6 @@ var init_analyst_ledger = __esm({
15844
15854
  function isRecord13(value) {
15845
15855
  return typeof value === "object" && value !== null && !Array.isArray(value);
15846
15856
  }
15847
- function wallMsFromSpan(span) {
15848
- return Date.parse(span.endedAt) - Date.parse(span.startedAt);
15849
- }
15850
15857
  function findCollectorGroups(body) {
15851
15858
  if (Array.isArray(body.groups)) return body.groups;
15852
15859
  const receipt = body.receipt;
@@ -15955,7 +15962,7 @@ function projectLegs(runs) {
15955
15962
  book: run.book,
15956
15963
  role: run.role,
15957
15964
  startedAt: run.frameSpan.startedAt,
15958
- wallMs: wallMsFromSpan(run.frameSpan),
15965
+ wallMs: run.frameSpan.wallMs,
15959
15966
  terminalLabel: mapped.terminalLabel,
15960
15967
  accepted: mapped.accepted,
15961
15968
  success: mapped.success,
@@ -15965,11 +15972,7 @@ function projectLegs(runs) {
15965
15972
  rework: ordinal >= 2
15966
15973
  });
15967
15974
  }
15968
- return legs.sort((a, b) => {
15969
- if (a.book !== b.book) return a.book.localeCompare(b.book);
15970
- if (a.role !== b.role) return a.role.localeCompare(b.role);
15971
- return a.runId.localeCompare(b.runId);
15972
- });
15975
+ return legs.sort(compareAnalystLegs);
15973
15976
  }
15974
15977
  function aggregateByRole(legs) {
15975
15978
  const roles = [...new Set(legs.map((leg) => leg.role))].sort((a, b) => a.localeCompare(b));
@@ -16013,12 +16016,10 @@ function aggregateByRole(legs) {
16013
16016
  };
16014
16017
  });
16015
16018
  }
16016
- function reworkLens(legs) {
16019
+ function reworkLens(legs, totalWallMs) {
16017
16020
  let reworkWallMs = 0;
16018
- let totalWallMs = 0;
16019
16021
  let reworkLegCount = 0;
16020
16022
  for (const leg of legs) {
16021
- totalWallMs += leg.wallMs;
16022
16023
  if (leg.rework) {
16023
16024
  reworkWallMs += leg.wallMs;
16024
16025
  reworkLegCount += 1;
@@ -16039,13 +16040,14 @@ function buildAcceptanceSuccessReworkSection(runs) {
16039
16040
  kind: "analyst-acceptance-success-rework",
16040
16041
  legs,
16041
16042
  byRole: aggregateByRole(legs),
16042
- rework: reworkLens(legs)
16043
+ rework: reworkLens(legs, sumAnalystRunWallMs(runs))
16043
16044
  };
16044
16045
  }
16045
16046
  var WORKER_ROLES, ACCEPTED_STATUS, SUCCESS_STATUS, acceptanceSuccessReworkFamily, acceptance_success_rework_default;
16046
16047
  var init_acceptance_success_rework = __esm({
16047
16048
  "src/analyst-metric-families/acceptance-success-rework.ts"() {
16048
16049
  "use strict";
16050
+ init_analyst_ledger();
16049
16051
  init_analyst_median();
16050
16052
  init_packaged_role_registry();
16051
16053
  WORKER_ROLES = /* @__PURE__ */ new Set(["coder", "fixer"]);
@@ -16078,117 +16080,6 @@ var init_acceptance_success_rework = __esm({
16078
16080
  }
16079
16081
  });
16080
16082
 
16081
- // src/analyst-model-groups.ts
16082
- function analystModelGroupKey(models) {
16083
- if (models.length === 0) return void 0;
16084
- if (models.length === 1) return models[0];
16085
- return `mixed:${models.join("+")}`;
16086
- }
16087
- function rate(numerator, denominator) {
16088
- if (denominator === 0) return void 0;
16089
- return numerator / denominator;
16090
- }
16091
- function displayNameFor(rawGroupKey, combinationMapping) {
16092
- if (combinationMapping === void 0) return rawGroupKey;
16093
- const aliased = combinationMapping[rawGroupKey];
16094
- return aliased === void 0 ? rawGroupKey : aliased;
16095
- }
16096
- function sortUnreadable(unreadable) {
16097
- return [...unreadable].sort((a, b) => {
16098
- if (a.book !== b.book) return a.book.localeCompare(b.book);
16099
- return a.runId.localeCompare(b.runId);
16100
- });
16101
- }
16102
- function modelIdentityAbsentEntry(run) {
16103
- return {
16104
- runId: run.runId,
16105
- book: run.book,
16106
- missingSources: ["session-model"],
16107
- reason: "session has no usable model identity",
16108
- firstFrameAt: { status: "present", at: run.frameSpan.startedAt },
16109
- // Readable legs already admitted a full span — retain end edge for lastActivityAt.
16110
- lastFrameAt: { status: "present", at: run.frameSpan.endedAt }
16111
- };
16112
- }
16113
- function buildAnalystModelGroupsPage(input) {
16114
- const groupedRuns = [];
16115
- const modelAbsent = [];
16116
- for (const run of input.runs) {
16117
- const rawGroupKey = analystModelGroupKey(run.models);
16118
- if (rawGroupKey === void 0) {
16119
- modelAbsent.push(modelIdentityAbsentEntry(run));
16120
- } else {
16121
- groupedRuns.push({ run, rawGroupKey });
16122
- }
16123
- }
16124
- const acceptance = buildAcceptanceSuccessReworkSection(
16125
- groupedRuns.map(({ run }) => run)
16126
- );
16127
- const legByRunId = new Map(
16128
- (acceptance?.legs ?? []).map((leg) => [leg.runId, leg])
16129
- );
16130
- const byRaw = /* @__PURE__ */ new Map();
16131
- for (const { run, rawGroupKey } of groupedRuns) {
16132
- const leg = legByRunId.get(run.runId);
16133
- if (leg === void 0) {
16134
- throw new Error(
16135
- `analyst model-groups: missing acceptance projection for run ${run.runId}`
16136
- );
16137
- }
16138
- let acc = byRaw.get(rawGroupKey);
16139
- if (acc === void 0) {
16140
- acc = {
16141
- acceptedCount: 0,
16142
- successCount: 0,
16143
- successEligibleCount: 0,
16144
- noReceiptCount: 0,
16145
- walls: []
16146
- };
16147
- byRaw.set(rawGroupKey, acc);
16148
- }
16149
- if (leg.accepted) acc.acceptedCount += 1;
16150
- if (leg.success) acc.successCount += 1;
16151
- if (leg.successEligible) acc.successEligibleCount += 1;
16152
- if (leg.noReceipt) acc.noReceiptCount += 1;
16153
- acc.walls.push(leg.wallMs);
16154
- }
16155
- const rawKeys = [...byRaw.keys()].sort((a, b) => a.localeCompare(b));
16156
- const groups = rawKeys.map((rawGroupKey) => {
16157
- const acc = byRaw.get(rawGroupKey);
16158
- const legCount = acc.walls.length;
16159
- return {
16160
- rawGroupKey,
16161
- displayName: displayNameFor(rawGroupKey, input.combinationMapping),
16162
- legCount,
16163
- acceptedCount: acc.acceptedCount,
16164
- acceptanceRate: rate(acc.acceptedCount, legCount),
16165
- successCount: acc.successCount,
16166
- successEligibleCount: acc.successEligibleCount,
16167
- successRate: rate(acc.successCount, acc.successEligibleCount),
16168
- noReceiptCount: acc.noReceiptCount,
16169
- noReceiptRate: rate(acc.noReceiptCount, legCount),
16170
- wallClockMedianMs: medianNumber(acc.walls)
16171
- };
16172
- });
16173
- const unreadable = sortUnreadable([...input.unreadable, ...modelAbsent]);
16174
- return {
16175
- kind: "analyst-model-groups",
16176
- mode: "model-groups",
16177
- projectRoots: [...input.projectRoots].sort((a, b) => a.localeCompare(b)),
16178
- groups,
16179
- legCount: groupedRuns.length,
16180
- unreadableCount: unreadable.length,
16181
- unreadable
16182
- };
16183
- }
16184
- var init_analyst_model_groups = __esm({
16185
- "src/analyst-model-groups.ts"() {
16186
- "use strict";
16187
- init_acceptance_success_rework();
16188
- init_analyst_median();
16189
- }
16190
- });
16191
-
16192
16083
  // src/analyst-metric-families/b2-frame-buckets-actions.ts
16193
16084
  function timestampMs(iso) {
16194
16085
  const ms = Date.parse(iso);
@@ -16311,7 +16202,7 @@ function sortActionsDescending(actions) {
16311
16202
  function computeAnalystB2RunMetrics(facts) {
16312
16203
  const frameStartMs = timestampMs(facts.frameSpan.startedAt);
16313
16204
  const frameEndMs = timestampMs(facts.frameSpan.endedAt);
16314
- const wallMs = Math.max(0, frameEndMs - frameStartMs);
16205
+ const wallMs = facts.frameSpan.wallMs;
16315
16206
  const tools = clipToolsToFrame(closedTools(facts.toolIntervals), frameStartMs, frameEndMs);
16316
16207
  const toolUnion = mergeUnion(tools);
16317
16208
  const toolBucketMs = toolUnion.reduce(
@@ -16341,15 +16232,12 @@ var init_b2_frame_buckets_actions = __esm({
16341
16232
  "src/analyst-metric-families/b2-frame-buckets-actions.ts"() {
16342
16233
  "use strict";
16343
16234
  init_analyst_median();
16235
+ init_analyst_ledger();
16344
16236
  b2FrameBucketsActionsFamily = {
16345
16237
  id: "b2-frame-buckets-actions",
16346
16238
  contribute(input) {
16347
16239
  if (input.runs.length === 0) return void 0;
16348
- const runs = [...input.runs].map(computeAnalystB2RunMetrics).sort((a, b) => {
16349
- if (a.book !== b.book) return a.book.localeCompare(b.book);
16350
- if (a.role !== b.role) return a.role.localeCompare(b.role);
16351
- return a.runId.localeCompare(b.runId);
16352
- });
16240
+ const runs = [...input.runs].map(computeAnalystB2RunMetrics).sort(compareAnalystLegs);
16353
16241
  const section = {
16354
16242
  kind: "analyst-b2-frame-buckets-actions",
16355
16243
  runs
@@ -16382,11 +16270,6 @@ function projectLeg(facts) {
16382
16270
  rounds
16383
16271
  };
16384
16272
  }
16385
- function compareLegs(a, b) {
16386
- if (a.book !== b.book) return a.book.localeCompare(b.book);
16387
- if (a.role !== b.role) return a.role.localeCompare(b.role);
16388
- return a.runId.localeCompare(b.runId);
16389
- }
16390
16273
  function emptyAccum() {
16391
16274
  return { rounds: 0, bounceCount: 0, passCount: 0, wallSum: 0 };
16392
16275
  }
@@ -16422,11 +16305,12 @@ var gateCyclesFamily, gate_cycles_default;
16422
16305
  var init_gate_cycles = __esm({
16423
16306
  "src/analyst-metric-families/gate-cycles.ts"() {
16424
16307
  "use strict";
16308
+ init_analyst_ledger();
16425
16309
  gateCyclesFamily = {
16426
16310
  id: "gate-cycles",
16427
16311
  contribute(input) {
16428
16312
  if (input.runs.length === 0) return void 0;
16429
- const legs = input.runs.map(projectLeg).sort(compareLegs);
16313
+ const legs = input.runs.map(projectLeg).sort(compareAnalystLegs);
16430
16314
  const section = {
16431
16315
  kind: "analyst-gate-cycles",
16432
16316
  legs,
@@ -16440,28 +16324,24 @@ var init_gate_cycles = __esm({
16440
16324
  });
16441
16325
 
16442
16326
  // src/analyst-metric-families/leg-wall-clock.ts
16443
- function frameSpanWallMs(span) {
16444
- return Date.parse(span.endedAt) - Date.parse(span.startedAt);
16445
- }
16446
16327
  function projectEntry(facts) {
16447
16328
  return {
16448
16329
  runId: facts.runId,
16449
16330
  book: facts.book,
16450
16331
  role: facts.role,
16451
- wallMs: frameSpanWallMs(facts.frameSpan)
16332
+ wallMs: facts.frameSpan.wallMs
16452
16333
  };
16453
16334
  }
16454
16335
  function compareRankingDesc(a, b) {
16455
16336
  if (b.wallMs !== a.wallMs) return b.wallMs - a.wallMs;
16456
- if (a.book !== b.book) return a.book.localeCompare(b.book);
16457
- if (a.role !== b.role) return a.role.localeCompare(b.role);
16458
- return a.runId.localeCompare(b.runId);
16337
+ return compareAnalystLegs(a, b);
16459
16338
  }
16460
16339
  var legWallClockFamily, leg_wall_clock_default;
16461
16340
  var init_leg_wall_clock = __esm({
16462
16341
  "src/analyst-metric-families/leg-wall-clock.ts"() {
16463
16342
  "use strict";
16464
16343
  init_analyst_median();
16344
+ init_analyst_ledger();
16465
16345
  legWallClockFamily = {
16466
16346
  id: "leg-wall-clock",
16467
16347
  contribute(input) {
@@ -16474,13 +16354,11 @@ var init_leg_wall_clock = __esm({
16474
16354
  if (medianWallMs === void 0) {
16475
16355
  return void 0;
16476
16356
  }
16477
- let totalElapsedMs = 0;
16478
- for (const wallMs of walls) totalElapsedMs += wallMs;
16479
16357
  const section = {
16480
16358
  kind: "analyst-leg-wall-clock",
16481
16359
  ranking,
16482
16360
  medianWallMs,
16483
- totalElapsedMs
16361
+ totalElapsedMs: sumAnalystRunWallMs(input.runs)
16484
16362
  };
16485
16363
  return { legWallClock: section };
16486
16364
  }
@@ -16493,9 +16371,6 @@ var init_leg_wall_clock = __esm({
16493
16371
  function isRecord14(value) {
16494
16372
  return typeof value === "object" && value !== null && !Array.isArray(value);
16495
16373
  }
16496
- function wallMsFromSpan2(startedAt, endedAt) {
16497
- return Date.parse(endedAt) - Date.parse(startedAt);
16498
- }
16499
16374
  function readClassCount(body) {
16500
16375
  if (!isRecord14(body.outcome)) return void 0;
16501
16376
  if (!isRecord14(body.outcome.decisiveFacts)) return void 0;
@@ -16532,7 +16407,7 @@ function projectRunRow(facts) {
16532
16407
  role: facts.role,
16533
16408
  startedAt,
16534
16409
  endedAt,
16535
- wallMs: wallMsFromSpan2(startedAt, endedAt),
16410
+ wallMs: facts.frameSpan.wallMs,
16536
16411
  terminal: projectTerminal(facts)
16537
16412
  };
16538
16413
  }
@@ -16664,13 +16539,9 @@ function analystIssuePageAddressFromPage(page) {
16664
16539
  };
16665
16540
  }
16666
16541
  function sortLegs(legs) {
16667
- return [...legs].sort((a, b) => {
16668
- if (a.book !== b.book) return a.book.localeCompare(b.book);
16669
- if (a.role !== b.role) return a.role.localeCompare(b.role);
16670
- return a.runId.localeCompare(b.runId);
16671
- });
16542
+ return [...legs].sort(compareAnalystLegs);
16672
16543
  }
16673
- function sortUnreadable2(unreadable) {
16544
+ function sortUnreadable(unreadable) {
16674
16545
  return [...unreadable].sort((a, b) => {
16675
16546
  if (a.book !== b.book) return a.book.localeCompare(b.book);
16676
16547
  return a.runId.localeCompare(b.runId);
@@ -16706,14 +16577,10 @@ function computeAnalystMsPerKLines(totalElapsedMs, changedLines) {
16706
16577
  value: totalElapsedMs / (changedLines.value / 1e3)
16707
16578
  };
16708
16579
  }
16709
- function frameSpanWallMs2(span) {
16710
- return Date.parse(span.endedAt) - Date.parse(span.startedAt);
16711
- }
16712
16580
  function summarizeAnalystRunEfficiency(runs, unreadable = []) {
16713
- let totalElapsedMs = 0;
16581
+ const totalElapsedMs = sumAnalystRunWallMs(runs);
16714
16582
  let latestEndedAt;
16715
16583
  for (const run of runs) {
16716
- totalElapsedMs += frameSpanWallMs2(run.frameSpan);
16717
16584
  const endedAt = run.frameSpan.endedAt;
16718
16585
  if (latestEndedAt === void 0 || endedAt > latestEndedAt) {
16719
16586
  latestEndedAt = endedAt;
@@ -16738,7 +16605,7 @@ async function buildAnalystIssueMetricsPage(input) {
16738
16605
  role: run.role
16739
16606
  }))
16740
16607
  );
16741
- const unreadable = sortUnreadable2(input.unreadable);
16608
+ const unreadable = sortUnreadable(input.unreadable);
16742
16609
  const scopeConflicts = sortScopeConflicts(input.scopeConflicts ?? []);
16743
16610
  const projectRoot = physicalPathIdentity(input.projectRoot);
16744
16611
  const { totalElapsedMs, lastActivityAt } = summarizeAnalystRunEfficiency(
@@ -16784,11 +16651,118 @@ var init_analyst_page = __esm({
16784
16651
  "use strict";
16785
16652
  init_atomic_write();
16786
16653
  init_activation_ledger_topology();
16654
+ init_analyst_ledger();
16787
16655
  init_analyst_metric_families();
16788
16656
  init_analyst_metric_family();
16789
16657
  }
16790
16658
  });
16791
16659
 
16660
+ // src/analyst-model-groups.ts
16661
+ function analystModelGroupKey(models) {
16662
+ if (models.length === 0) return void 0;
16663
+ if (models.length === 1) return models[0];
16664
+ return `mixed:${models.join("+")}`;
16665
+ }
16666
+ function rate(numerator, denominator) {
16667
+ if (denominator === 0) return void 0;
16668
+ return numerator / denominator;
16669
+ }
16670
+ function displayNameFor(rawGroupKey, combinationMapping) {
16671
+ if (combinationMapping === void 0) return rawGroupKey;
16672
+ const aliased = combinationMapping[rawGroupKey];
16673
+ return aliased === void 0 ? rawGroupKey : aliased;
16674
+ }
16675
+ function modelIdentityAbsentEntry(run) {
16676
+ return {
16677
+ runId: run.runId,
16678
+ book: run.book,
16679
+ missingSources: ["session-model"],
16680
+ reason: "session has no usable model identity",
16681
+ firstFrameAt: { status: "present", at: run.frameSpan.startedAt },
16682
+ // Readable legs already admitted a full span — retain end edge for lastActivityAt.
16683
+ lastFrameAt: { status: "present", at: run.frameSpan.endedAt }
16684
+ };
16685
+ }
16686
+ function buildAnalystModelGroupsPage(input) {
16687
+ const groupedRuns = [];
16688
+ const modelAbsent = [];
16689
+ for (const run of input.runs) {
16690
+ const rawGroupKey = analystModelGroupKey(run.models);
16691
+ if (rawGroupKey === void 0) {
16692
+ modelAbsent.push(modelIdentityAbsentEntry(run));
16693
+ } else {
16694
+ groupedRuns.push({ run, rawGroupKey });
16695
+ }
16696
+ }
16697
+ const acceptance = buildAcceptanceSuccessReworkSection(
16698
+ groupedRuns.map(({ run }) => run)
16699
+ );
16700
+ const legByRunId = new Map(
16701
+ (acceptance?.legs ?? []).map((leg) => [leg.runId, leg])
16702
+ );
16703
+ const byRaw = /* @__PURE__ */ new Map();
16704
+ for (const { run, rawGroupKey } of groupedRuns) {
16705
+ const leg = legByRunId.get(run.runId);
16706
+ if (leg === void 0) {
16707
+ throw new Error(
16708
+ `analyst model-groups: missing acceptance projection for run ${run.runId}`
16709
+ );
16710
+ }
16711
+ let acc = byRaw.get(rawGroupKey);
16712
+ if (acc === void 0) {
16713
+ acc = {
16714
+ acceptedCount: 0,
16715
+ successCount: 0,
16716
+ successEligibleCount: 0,
16717
+ noReceiptCount: 0,
16718
+ walls: []
16719
+ };
16720
+ byRaw.set(rawGroupKey, acc);
16721
+ }
16722
+ if (leg.accepted) acc.acceptedCount += 1;
16723
+ if (leg.success) acc.successCount += 1;
16724
+ if (leg.successEligible) acc.successEligibleCount += 1;
16725
+ if (leg.noReceipt) acc.noReceiptCount += 1;
16726
+ acc.walls.push(leg.wallMs);
16727
+ }
16728
+ const rawKeys = [...byRaw.keys()].sort((a, b) => a.localeCompare(b));
16729
+ const groups = rawKeys.map((rawGroupKey) => {
16730
+ const acc = byRaw.get(rawGroupKey);
16731
+ const legCount = acc.walls.length;
16732
+ return {
16733
+ rawGroupKey,
16734
+ displayName: displayNameFor(rawGroupKey, input.combinationMapping),
16735
+ legCount,
16736
+ acceptedCount: acc.acceptedCount,
16737
+ acceptanceRate: rate(acc.acceptedCount, legCount),
16738
+ successCount: acc.successCount,
16739
+ successEligibleCount: acc.successEligibleCount,
16740
+ successRate: rate(acc.successCount, acc.successEligibleCount),
16741
+ noReceiptCount: acc.noReceiptCount,
16742
+ noReceiptRate: rate(acc.noReceiptCount, legCount),
16743
+ wallClockMedianMs: medianNumber(acc.walls)
16744
+ };
16745
+ });
16746
+ const unreadable = sortUnreadable([...input.unreadable, ...modelAbsent]);
16747
+ return {
16748
+ kind: "analyst-model-groups",
16749
+ mode: "model-groups",
16750
+ projectRoots: [...input.projectRoots].sort((a, b) => a.localeCompare(b)),
16751
+ groups,
16752
+ legCount: groupedRuns.length,
16753
+ unreadableCount: unreadable.length,
16754
+ unreadable
16755
+ };
16756
+ }
16757
+ var init_analyst_model_groups = __esm({
16758
+ "src/analyst-model-groups.ts"() {
16759
+ "use strict";
16760
+ init_acceptance_success_rework();
16761
+ init_analyst_median();
16762
+ init_analyst_page();
16763
+ }
16764
+ });
16765
+
16792
16766
  // src/analyst-entry.ts
16793
16767
  import { readFile as readFile19 } from "node:fs/promises";
16794
16768
  import { Type as Type11 } from "typebox";
@@ -212,11 +212,26 @@ function admitFrameSpanFromRows(rows) {
212
212
  }
213
213
  return {
214
214
  models,
215
- frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt },
215
+ frameSpan: { startedAt: span.startedAt, endedAt: span.endedAt, wallMs: endedMs - startedMs },
216
216
  partialFirstFrameAt: { status: "present", at: span.startedAt },
217
217
  partialLastFrameAt: { status: "present", at: span.endedAt },
218
218
  };
219
219
  }
220
+ /** Total wall clock for exactly the readable runs supplied by the caller. */
221
+ export function sumAnalystRunWallMs(runs) {
222
+ let total = 0;
223
+ for (const run of runs)
224
+ total += run.frameSpan.wallMs;
225
+ return total;
226
+ }
227
+ /** Shared identity order for readable legs and their page/metric projections. */
228
+ export function compareAnalystLegs(a, b) {
229
+ if (a.book !== b.book)
230
+ return a.book.localeCompare(b.book);
231
+ if (a.role !== b.role)
232
+ return a.role.localeCompare(b.role);
233
+ return a.runId.localeCompare(b.runId);
234
+ }
220
235
  async function classifyScopedRun(input) {
221
236
  const missingSources = [];
222
237
  const reasons = [];
@@ -1,3 +1,15 @@
1
+ /**
2
+ * B3 metric family: acceptance terminal mapping, rework lens, first-pass / success rates.
3
+ *
4
+ * Consumes only A2 typed per-run facts (frame span + terminal face). Drop-in
5
+ * registration via analyst-metric-families/ discovery — no shared skeleton edits.
6
+ *
7
+ * Denominator contract (PRD #298 + ticket #327 票面补正):
8
+ * - first-pass rate den = appearance lanes (no-receipt first call stays in den)
9
+ * - success rate den = success-eligible accepted legs (no-receipt out; planned out)
10
+ * - planned = plan-duty acceptance; never success numerator or denominator
11
+ */
12
+ import { compareAnalystLegs, sumAnalystRunWallMs } from "../analyst-ledger.js";
1
13
  import { medianNumber } from "../analyst-median.js";
2
14
  import { packagedAnalystTerminal } from "../packaged-role-registry.js";
3
15
  const WORKER_ROLES = new Set(["coder", "fixer"]);
@@ -23,9 +35,6 @@ const SUCCESS_STATUS = {
23
35
  function isRecord(value) {
24
36
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
37
  }
26
- function wallMsFromSpan(span) {
27
- return Date.parse(span.endedAt) - Date.parse(span.startedAt);
28
- }
29
38
  /** Locate collector groups array: receipt.groups or top-level groups. */
30
39
  function findCollectorGroups(body) {
31
40
  if (Array.isArray(body.groups))
@@ -143,7 +152,7 @@ function projectLegs(runs) {
143
152
  book: run.book,
144
153
  role: run.role,
145
154
  startedAt: run.frameSpan.startedAt,
146
- wallMs: wallMsFromSpan(run.frameSpan),
155
+ wallMs: run.frameSpan.wallMs,
147
156
  terminalLabel: mapped.terminalLabel,
148
157
  accepted: mapped.accepted,
149
158
  success: mapped.success,
@@ -154,13 +163,7 @@ function projectLegs(runs) {
154
163
  });
155
164
  }
156
165
  // Stable page order: book, role, runId (match A1 leg sort).
157
- return legs.sort((a, b) => {
158
- if (a.book !== b.book)
159
- return a.book.localeCompare(b.book);
160
- if (a.role !== b.role)
161
- return a.role.localeCompare(b.role);
162
- return a.runId.localeCompare(b.runId);
163
- });
166
+ return legs.sort(compareAnalystLegs);
164
167
  }
165
168
  function aggregateByRole(legs) {
166
169
  const roles = [...new Set(legs.map((leg) => leg.role))].sort((a, b) => a.localeCompare(b));
@@ -207,12 +210,10 @@ function aggregateByRole(legs) {
207
210
  };
208
211
  });
209
212
  }
210
- function reworkLens(legs) {
213
+ function reworkLens(legs, totalWallMs) {
211
214
  let reworkWallMs = 0;
212
- let totalWallMs = 0;
213
215
  let reworkLegCount = 0;
214
216
  for (const leg of legs) {
215
- totalWallMs += leg.wallMs;
216
217
  if (leg.rework) {
217
218
  reworkWallMs += leg.wallMs;
218
219
  reworkLegCount += 1;
@@ -234,7 +235,7 @@ export function buildAcceptanceSuccessReworkSection(runs) {
234
235
  kind: "analyst-acceptance-success-rework",
235
236
  legs,
236
237
  byRole: aggregateByRole(legs),
237
- rework: reworkLens(legs),
238
+ rework: reworkLens(legs, sumAnalystRunWallMs(runs)),
238
239
  };
239
240
  }
240
241
  const acceptanceSuccessReworkFamily = {
@@ -1,4 +1,5 @@
1
1
  import { medianNumber } from "../analyst-median.js";
2
+ import { compareAnalystLegs } from "../analyst-ledger.js";
2
3
  function timestampMs(iso) {
3
4
  const ms = Date.parse(iso);
4
5
  if (!Number.isFinite(ms)) {
@@ -141,7 +142,7 @@ function sortActionsDescending(actions) {
141
142
  export function computeAnalystB2RunMetrics(facts) {
142
143
  const frameStartMs = timestampMs(facts.frameSpan.startedAt);
143
144
  const frameEndMs = timestampMs(facts.frameSpan.endedAt);
144
- const wallMs = Math.max(0, frameEndMs - frameStartMs);
145
+ const wallMs = facts.frameSpan.wallMs;
145
146
  // Single frame-bounded core: clip → union once → bucket/complement/actions.
146
147
  const tools = clipToolsToFrame(closedTools(facts.toolIntervals), frameStartMs, frameEndMs);
147
148
  const toolUnion = mergeUnion(tools);
@@ -172,13 +173,7 @@ const b2FrameBucketsActionsFamily = {
172
173
  return undefined;
173
174
  const runs = [...input.runs]
174
175
  .map(computeAnalystB2RunMetrics)
175
- .sort((a, b) => {
176
- if (a.book !== b.book)
177
- return a.book.localeCompare(b.book);
178
- if (a.role !== b.role)
179
- return a.role.localeCompare(b.role);
180
- return a.runId.localeCompare(b.runId);
181
- });
176
+ .sort(compareAnalystLegs);
182
177
  const section = {
183
178
  kind: "analyst-b2-frame-buckets-actions",
184
179
  runs,
@@ -1,3 +1,4 @@
1
+ import { compareAnalystLegs } from "../analyst-ledger.js";
1
2
  function projectRound(round) {
2
3
  return {
3
4
  roundIndex: round.roundIndex,
@@ -18,13 +19,6 @@ function projectLeg(facts) {
18
19
  rounds,
19
20
  };
20
21
  }
21
- function compareLegs(a, b) {
22
- if (a.book !== b.book)
23
- return a.book.localeCompare(b.book);
24
- if (a.role !== b.role)
25
- return a.role.localeCompare(b.role);
26
- return a.runId.localeCompare(b.runId);
27
- }
28
22
  function emptyAccum() {
29
23
  return { rounds: 0, bounceCount: 0, passCount: 0, wallSum: 0 };
30
24
  }
@@ -65,7 +59,7 @@ const gateCyclesFamily = {
65
59
  contribute(input) {
66
60
  if (input.runs.length === 0)
67
61
  return undefined;
68
- const legs = input.runs.map(projectLeg).sort(compareLegs);
62
+ const legs = input.runs.map(projectLeg).sort(compareAnalystLegs);
69
63
  const section = {
70
64
  kind: "analyst-gate-cycles",
71
65
  legs,
@@ -6,25 +6,19 @@
6
6
  * Registers by file drop under analyst-metric-families/ (A2 discovery).
7
7
  */
8
8
  import { medianNumber } from "../analyst-median.js";
9
- function frameSpanWallMs(span) {
10
- return Date.parse(span.endedAt) - Date.parse(span.startedAt);
11
- }
9
+ import { compareAnalystLegs, sumAnalystRunWallMs } from "../analyst-ledger.js";
12
10
  function projectEntry(facts) {
13
11
  return {
14
12
  runId: facts.runId,
15
13
  book: facts.book,
16
14
  role: facts.role,
17
- wallMs: frameSpanWallMs(facts.frameSpan),
15
+ wallMs: facts.frameSpan.wallMs,
18
16
  };
19
17
  }
20
18
  function compareRankingDesc(a, b) {
21
19
  if (b.wallMs !== a.wallMs)
22
20
  return b.wallMs - a.wallMs;
23
- if (a.book !== b.book)
24
- return a.book.localeCompare(b.book);
25
- if (a.role !== b.role)
26
- return a.role.localeCompare(b.role);
27
- return a.runId.localeCompare(b.runId);
21
+ return compareAnalystLegs(a, b);
28
22
  }
29
23
  /** Discovered by analyst-metric-families loader (default export). */
30
24
  const legWallClockFamily = {
@@ -41,14 +35,11 @@ const legWallClockFamily = {
41
35
  if (medianWallMs === undefined) {
42
36
  return undefined;
43
37
  }
44
- let totalElapsedMs = 0;
45
- for (const wallMs of walls)
46
- totalElapsedMs += wallMs;
47
38
  const section = {
48
39
  kind: "analyst-leg-wall-clock",
49
40
  ranking,
50
41
  medianWallMs,
51
- totalElapsedMs,
42
+ totalElapsedMs: sumAnalystRunWallMs(input.runs),
52
43
  };
53
44
  return { legWallClock: section };
54
45
  },