@usepipr/runtime 0.3.6 → 0.3.7

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.
@@ -304,6 +304,19 @@ declare const publicationMetadataSchema: z.ZodObject<{
304
304
  validFindings: z.ZodNumber;
305
305
  droppedFindings: z.ZodNumber;
306
306
  cappedInlineFindings: z.ZodNumber;
307
+ stats: z.ZodOptional<z.ZodObject<{
308
+ models: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
309
+ agentRuns: z.ZodNumber;
310
+ durationMs: z.ZodNumber;
311
+ inputTokens: z.ZodNumber;
312
+ outputTokens: z.ZodNumber;
313
+ costUsd: z.ZodNumber;
314
+ usageStatus: z.ZodEnum<{
315
+ complete: "complete";
316
+ partial: "partial";
317
+ unavailable: "unavailable";
318
+ }>;
319
+ }, z.core.$strict>>;
307
320
  }, z.core.$strict>;
308
321
  type PublicationMetadata = z.infer<typeof publicationMetadataSchema>;
309
322
  declare const publicationPlanSchema: z.ZodObject<{
@@ -334,6 +347,19 @@ declare const publicationPlanSchema: z.ZodObject<{
334
347
  validFindings: z.ZodNumber;
335
348
  droppedFindings: z.ZodNumber;
336
349
  cappedInlineFindings: z.ZodNumber;
350
+ stats: z.ZodOptional<z.ZodObject<{
351
+ models: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
352
+ agentRuns: z.ZodNumber;
353
+ durationMs: z.ZodNumber;
354
+ inputTokens: z.ZodNumber;
355
+ outputTokens: z.ZodNumber;
356
+ costUsd: z.ZodNumber;
357
+ usageStatus: z.ZodEnum<{
358
+ complete: "complete";
359
+ partial: "partial";
360
+ unavailable: "unavailable";
361
+ }>;
362
+ }, z.core.$strict>>;
337
363
  }, z.core.$strict>;
338
364
  reviewState: z.ZodObject<{
339
365
  version: z.ZodLiteral<1>;
@@ -872,4 +898,4 @@ declare function runActionCommand(options: ActionCommandOptions): Promise<Action
872
898
  declare function runActionCommandWithDependencies(options: ActionCommandDependencyOptions): Promise<ActionCommandResult>;
873
899
  //#endregion
874
900
  export { PlatformInfo as A, GitHubPublicationClient as C, ChangeRequestRef as D, ChangeRequestEventContext as E, supportedOfficialInitAdapters as F, RepositoryRef as M, RuntimeSettings as N, DiffManifest as O, OfficialInitAdapter as P, ActionLogSink as S, PublicationResult as T, piBuiltinToolNames as _, runInspectCommand as a, piThinkingLevels as b, ActionCommandOptions as c, DryRunCommandResult as d, InitCommandOptions as f, RuntimeCommandOptions as g, LocalReviewCommandResult as h, runInitCommand as i, ProviderConfig as j, PiprConfig as k, ActionCommandResult as l, LocalReviewCommandOptions as m, runActionCommandWithDependencies as n, runLocalReviewCommand as o, InspectCommandResult as p, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, DryRunCommandOptions as u, piReadOnlyToolNames as v, PublicationError as w, ActionLogRecord as x, piRequiredCliFlags as y };
875
- //# sourceMappingURL=commands-C6wfO-y7.d.mts.map
901
+ //# sourceMappingURL=commands-Bcta0T4L.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands-Bcta0T4L.d.mts","names":[],"sources":["../src/config/init.ts","../src/types.ts","../src/config/project.ts","../src/review/prior-state.ts","../src/review/comment.ts","../src/review/publication-result.ts","../src/hosts/types.ts","../src/hosts/github/command.ts","../src/hosts/github/publication-client.ts","../src/shared/logging.ts","../src/pi/contract.ts","../src/review/task/task-output.ts","../src/review/task/task-runtime.ts","../src/action/types.ts","../src/action/commands.ts"],"mappings":"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"}
@@ -526,7 +526,7 @@ function compareStableSemver(left, right) {
526
526
  }
527
527
  //#endregion
528
528
  //#region src/shared/version.ts
529
- const runtimeVersion = "0.3.6";
529
+ const runtimeVersion = "0.3.7";
530
530
  //#endregion
531
531
  //#region src/config/version-compat.ts
532
532
  async function resolveConfigVersionCompatibility(options) {
@@ -3544,8 +3544,8 @@ function isOfficialInitRecipeId(recipe) {
3544
3544
  //#endregion
3545
3545
  //#region src/config/init.ts
3546
3546
  const supportedOfficialInitAdapters = ["github"];
3547
- const defaultWorkflowActionRef = "somus/pipr@v0.3.6";
3548
- const defaultSdkVersion = "0.3.6";
3547
+ const defaultWorkflowActionRef = "somus/pipr@v0.3.7";
3548
+ const defaultSdkVersion = "0.3.7";
3549
3549
  function resolveOfficialInitAdapters(adapters) {
3550
3550
  if (adapters === void 0) return [...supportedOfficialInitAdapters];
3551
3551
  if (adapters.length === 0) return [];
@@ -4746,6 +4746,24 @@ const ignoredWorkspacePaths = /* @__PURE__ */ new Set([
4746
4746
  ".fallow",
4747
4747
  "coverage"
4748
4748
  ]);
4749
+ const eventRecordSchema = z.record(z.string(), z.unknown());
4750
+ const tokenCountSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
4751
+ const assistantMessageEventSchema = z.looseObject({
4752
+ type: z.literal("message_end"),
4753
+ message: z.looseObject({
4754
+ role: z.literal("assistant"),
4755
+ model: z.string().min(1).optional(),
4756
+ responseModel: z.string().min(1).optional()
4757
+ })
4758
+ });
4759
+ const assistantUsageMessageSchema = z.looseObject({
4760
+ role: z.literal("assistant"),
4761
+ usage: z.looseObject({
4762
+ input: tokenCountSchema,
4763
+ output: tokenCountSchema,
4764
+ cost: z.looseObject({ total: z.number().nonnegative() })
4765
+ })
4766
+ });
4749
4767
  async function runPi(options) {
4750
4768
  const started = Date.now();
4751
4769
  const sandbox = await createPiRunSandbox(options.workspace);
@@ -4768,10 +4786,13 @@ async function runPi(options) {
4768
4786
  started,
4769
4787
  timeoutSeconds: options.timeoutSeconds
4770
4788
  });
4771
- return result.exitCode === 0 ? {
4789
+ const events = parsePiJsonEvents(result.stdout);
4790
+ return {
4772
4791
  ...result,
4773
- stdout: extractAssistantTextFromJsonEvents(result.stdout) ?? result.stdout
4774
- } : result;
4792
+ ...events?.models.length ? { models: events.models } : {},
4793
+ ...events?.usage ? { usage: events.usage } : {},
4794
+ stdout: result.exitCode === 0 ? events?.assistantText ?? result.stdout : result.stdout
4795
+ };
4775
4796
  } finally {
4776
4797
  await preparedTools?.custom?.close();
4777
4798
  await chmodRecursive(sandbox.root, 493);
@@ -4893,19 +4914,69 @@ async function chmodRecursive(target, mode) {
4893
4914
  const entries = await readdir(target, { withFileTypes: true });
4894
4915
  for (const entry of entries) await chmodRecursive(path.join(target, entry.name), mode);
4895
4916
  }
4896
- function extractAssistantTextFromJsonEvents(stdout) {
4917
+ function parsePiJsonEvents(stdout) {
4897
4918
  const events = [];
4898
4919
  for (const line of stdout.split(/\r?\n/).map((item) => item.trim()).filter(Boolean)) try {
4899
4920
  const value = JSON.parse(line);
4900
- if (!isPlainObject(value)) return;
4901
- events.push(value);
4921
+ const parsed = eventRecordSchema.safeParse(value);
4922
+ if (!parsed.success) return;
4923
+ events.push(parsed.data);
4902
4924
  } catch {
4903
4925
  return;
4904
4926
  }
4905
4927
  if (!events.some((event) => typeof event.type === "string")) return;
4906
- let text;
4907
- for (const event of events) text = assistantTextFromEvent(event) ?? text;
4908
- return text;
4928
+ let assistantText;
4929
+ for (const event of events) assistantText = assistantTextFromEvent(event) ?? assistantText;
4930
+ const assistantMessages = assistantMessagesFromEvents(events);
4931
+ const models = assistantModels(assistantMessages);
4932
+ const usage = assistantUsage(assistantMessages);
4933
+ return {
4934
+ ...assistantText !== void 0 ? { assistantText } : {},
4935
+ models,
4936
+ ...usage ? { usage } : {}
4937
+ };
4938
+ }
4939
+ function assistantMessagesFromEvents(events) {
4940
+ return events.flatMap((event) => {
4941
+ const parsed = assistantMessageEventSchema.safeParse(event);
4942
+ return parsed.success ? [parsed.data.message] : [];
4943
+ });
4944
+ }
4945
+ function assistantModels(messages) {
4946
+ const models = [];
4947
+ for (const message of messages) {
4948
+ const model = message.responseModel ?? message.model;
4949
+ if (model && !models.includes(model)) models.push(model);
4950
+ }
4951
+ return models;
4952
+ }
4953
+ function assistantUsage(messages) {
4954
+ const usageMessages = messages.flatMap((message) => {
4955
+ const parsed = assistantUsageMessageSchema.safeParse(message);
4956
+ return parsed.success ? [parsed.data] : [];
4957
+ });
4958
+ if (usageMessages.length === 0) return;
4959
+ let inputTokens = 0;
4960
+ let outputTokens = 0;
4961
+ let costUsd = 0;
4962
+ let partial = usageMessages.length !== messages.length;
4963
+ for (const message of usageMessages) {
4964
+ const nextInputTokens = inputTokens + message.usage.input;
4965
+ if (Number.isSafeInteger(nextInputTokens)) inputTokens = nextInputTokens;
4966
+ else partial = true;
4967
+ const nextOutputTokens = outputTokens + message.usage.output;
4968
+ if (Number.isSafeInteger(nextOutputTokens)) outputTokens = nextOutputTokens;
4969
+ else partial = true;
4970
+ const nextCostUsd = costUsd + message.usage.cost.total;
4971
+ if (Number.isFinite(nextCostUsd)) costUsd = nextCostUsd;
4972
+ else partial = true;
4973
+ }
4974
+ return {
4975
+ status: partial ? "partial" : "complete",
4976
+ inputTokens,
4977
+ outputTokens,
4978
+ costUsd
4979
+ };
4909
4980
  }
4910
4981
  function assistantTextFromEvent(event) {
4911
4982
  if (event.type === "message_end" || event.type === "turn_end") return assistantMessageText(event.message);
@@ -5521,16 +5592,27 @@ async function runPiForPrompt(options, provider, prompt) {
5521
5592
  const customTools = customToolsForRun(options);
5522
5593
  const timeoutSeconds = promptTimeoutSeconds(options);
5523
5594
  logPiStart(options, provider, prompt, builtinTools, runtimeTools, customTools);
5524
- const result = await (options.runtime.piRunner ?? runPi)({
5525
- workspace: options.runtime.workspace,
5526
- provider,
5527
- prompt,
5528
- env: options.runtime.env,
5529
- piExecutable: options.runtime.piExecutable,
5530
- builtinTools,
5531
- runtimeTools,
5532
- customTools,
5533
- timeoutSeconds
5595
+ let result;
5596
+ try {
5597
+ result = await (options.runtime.piRunner ?? runPi)({
5598
+ workspace: options.runtime.workspace,
5599
+ provider,
5600
+ prompt,
5601
+ env: options.runtime.env,
5602
+ piExecutable: options.runtime.piExecutable,
5603
+ builtinTools,
5604
+ runtimeTools,
5605
+ customTools,
5606
+ timeoutSeconds
5607
+ });
5608
+ } catch (error) {
5609
+ options.runtime.piRunSink?.({ models: [provider.model] });
5610
+ throw error;
5611
+ }
5612
+ const reportedModels = result.models?.map((model) => model.trim()).filter(Boolean);
5613
+ options.runtime.piRunSink?.({
5614
+ models: reportedModels?.length ? reportedModels : [provider.model],
5615
+ ...result.usage ? { usage: result.usage } : {}
5534
5616
  });
5535
5617
  logPiResult(options, provider, result, timeoutSeconds);
5536
5618
  assertSuccessfulPiResult(result, options.runtime.log);
@@ -5674,6 +5756,8 @@ const mainCommentTitles = /* @__PURE__ */ new Set([
5674
5756
  "# Pipr Review",
5675
5757
  mainCommentTitle
5676
5758
  ]);
5759
+ const reviewStatsStartMarker = "<!-- pipr:stats:start -->";
5760
+ const reviewStatsEndMarker = "<!-- pipr:stats:end -->";
5677
5761
  const escapedRepositoryUrl = piprRepositoryUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5678
5762
  const mainCommentAttributionPattern = new RegExp(`^<sub>Review generated by \\[Pipr\\]\\(${escapedRepositoryUrl}\\) for commit \`[^\`]+\`\\.(?: Config SDK \\d+\\.\\d+\\.\\d+ is behind (?:Pipr \\d+\\.\\d+\\.\\d+|\\[Pipr \\d+\\.\\d+\\.\\d+\\]\\(${escapedRepositoryUrl}/releases/tag/v\\d+\\.\\d+\\.\\d+\\))\\.)?</sub>$`);
5679
5763
  //#endregion
@@ -5933,6 +6017,29 @@ function parseAttrs(input) {
5933
6017
  function hashParts(parts) {
5934
6018
  return new Bun.CryptoHasher("sha256").update(parts.join("\n")).digest("hex").slice(0, 16);
5935
6019
  }
6020
+ const maxReviewStatsModelLength = 200;
6021
+ const credentialLikeModelPattern = /(?:(?:AKIA|ASIA)[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|(?:sk|rk)_live_[A-Za-z0-9]{16,}|sk-(?:proj-)?[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{16,}|glpat-[A-Za-z0-9_-]{16,}|npm_[A-Za-z0-9]{16,}|xox[baprs]-[A-Za-z0-9-]{16,})/i;
6022
+ const highEntropyModelSegmentPattern = /[A-Za-z0-9]{24,}/;
6023
+ const reviewStatsModelSchema = z.string().min(1).max(maxReviewStatsModelLength).transform((model) => sanitizeReviewStatsModel(model) ?? "[invalid model]");
6024
+ const reviewStatsSchema = z.strictObject({
6025
+ models: z.array(reviewStatsModelSchema).min(1).max(20),
6026
+ agentRuns: z.number().int().positive(),
6027
+ durationMs: z.number().int().nonnegative(),
6028
+ inputTokens: z.number().int().nonnegative(),
6029
+ outputTokens: z.number().int().nonnegative(),
6030
+ costUsd: z.number().nonnegative(),
6031
+ usageStatus: z.enum([
6032
+ "complete",
6033
+ "partial",
6034
+ "unavailable"
6035
+ ])
6036
+ });
6037
+ function sanitizeReviewStatsModel(model) {
6038
+ const normalized = model.replace(/\s+/g, " ").trim();
6039
+ if (credentialLikeModelPattern.test(normalized) || highEntropyModelSegmentPattern.test(normalized)) return "[redacted credential]";
6040
+ const sanitized = redactPotentialSecrets(normalized);
6041
+ return sanitized ? sanitized.slice(0, maxReviewStatsModelLength) : void 0;
6042
+ }
5936
6043
  //#endregion
5937
6044
  //#region src/review/comment.ts
5938
6045
  const inlinePublicationItemSchema = z.strictObject({
@@ -5990,7 +6097,8 @@ const publicationMetadataSchema = z.strictObject({
5990
6097
  failedTasks: z.array(z.string().min(1)),
5991
6098
  validFindings: z.number().int().min(0),
5992
6099
  droppedFindings: z.number().int().min(0),
5993
- cappedInlineFindings: z.number().int().min(0)
6100
+ cappedInlineFindings: z.number().int().min(0),
6101
+ stats: reviewStatsSchema.optional()
5994
6102
  });
5995
6103
  const publicationPlanSchema = z.strictObject({
5996
6104
  mainComment: z.string().min(1),
@@ -6106,10 +6214,54 @@ function renderMainComment(options) {
6106
6214
  ...options.metadata.validFindings > 0 ? [`**Findings:** ${options.metadata.validFindings}`, ""] : [],
6107
6215
  redactPotentialSecrets(options.main),
6108
6216
  "",
6217
+ ...options.metadata.stats ? [renderReviewStats(options.metadata.stats), ""] : [],
6109
6218
  renderMainCommentAttribution(options.metadata),
6110
6219
  ""
6111
6220
  ].join("\n");
6112
6221
  }
6222
+ function renderReviewStats(stats) {
6223
+ const usageSuffix = stats.usageStatus === "partial" ? " (reported)" : "";
6224
+ const usageUnavailable = stats.usageStatus === "unavailable";
6225
+ return [
6226
+ reviewStatsStartMarker,
6227
+ "<details>",
6228
+ "<summary>Review stats</summary>",
6229
+ "",
6230
+ "| Metric | Total |",
6231
+ "| --- | ---: |",
6232
+ `| Models | ${stats.models.map(formatModel).join(", ")} |`,
6233
+ `| Agent runs | ${stats.agentRuns} |`,
6234
+ `| Elapsed | ${formatDuration(stats.durationMs)} |`,
6235
+ `| Input tokens | ${usageUnavailable ? "Unavailable" : `${formatInteger(stats.inputTokens)}${usageSuffix}`} |`,
6236
+ `| Output tokens | ${usageUnavailable ? "Unavailable" : `${formatInteger(stats.outputTokens)}${usageSuffix}`} |`,
6237
+ `| Cost (USD) | ${usageUnavailable ? "Unavailable" : `${formatCost(stats.costUsd)}${usageSuffix}`} |`,
6238
+ "",
6239
+ "</details>",
6240
+ reviewStatsEndMarker
6241
+ ].join("\n");
6242
+ }
6243
+ function formatModel(model) {
6244
+ return `<code>${model.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\|/g, "&#124;")}</code>`;
6245
+ }
6246
+ function formatInteger(value) {
6247
+ return value.toLocaleString("en-US");
6248
+ }
6249
+ function formatDuration(durationMs) {
6250
+ if (durationMs < 1e3) return `${durationMs}ms`;
6251
+ const totalSeconds = durationMs / 1e3;
6252
+ if (totalSeconds < 60) return `${formatTenths(totalSeconds)}s`;
6253
+ const minutes = Math.floor(totalSeconds / 60);
6254
+ return `${minutes}m ${formatTenths(totalSeconds - minutes * 60)}s`;
6255
+ }
6256
+ function formatTenths(value) {
6257
+ return value.toFixed(1).replace(/\.0$/, "");
6258
+ }
6259
+ function formatCost(costUsd) {
6260
+ if (costUsd === 0) return "$0.00";
6261
+ if (costUsd < 1e-4) return `$${costUsd.toFixed(6)}`;
6262
+ if (costUsd < .01) return `$${costUsd.toFixed(4)}`;
6263
+ return `$${costUsd.toFixed(2)}`;
6264
+ }
6113
6265
  function renderMainCommentAttribution(metadata) {
6114
6266
  const configNotice = configVersionNotice(metadata);
6115
6267
  return `<sub>Review generated by [Pipr](${piprRepositoryUrl}) for commit \`${metadata.reviewedHeadSha.slice(0, 7)}\`.${configNotice}</sub>`;
@@ -6266,7 +6418,8 @@ async function runInternalVerifier(options) {
6266
6418
  piExecutable: options.piExecutable,
6267
6419
  piRunner: options.piRunner,
6268
6420
  runId: options.runId,
6269
- log: options.log
6421
+ log: options.log,
6422
+ ...options.piRunSink ? { piRunSink: options.piRunSink } : {}
6270
6423
  }
6271
6424
  });
6272
6425
  return applyVerifierOutput(options, candidates, verifierOutputSchema.parse(result.value), result.providerModels);
@@ -6438,6 +6591,20 @@ function boundedVerifierText(value) {
6438
6591
  //#endregion
6439
6592
  //#region src/review/task/task-output.ts
6440
6593
  const agentInlineFindingsOutputSchema = z.custom((value) => z.looseObject({ inlineFindings: z.array(reviewFindingSchema) }).safeParse(value).success);
6594
+ const generatedReviewStatsShape = [
6595
+ /^$/,
6596
+ /^\| Metric \| Total \|$/,
6597
+ /^\| --- \| ---: \|$/,
6598
+ /^\| Models \| .+ \|$/,
6599
+ /^\| Agent runs \| \d+ \|$/,
6600
+ /^\| Elapsed \| .+ \|$/,
6601
+ /^\| Input tokens \| (?:Unavailable|[\d,]+(?: \(reported\))?) \|$/,
6602
+ /^\| Output tokens \| (?:Unavailable|[\d,]+(?: \(reported\))?) \|$/,
6603
+ /^\| Cost \(USD\) \| (?:Unavailable|\$\d+(?:\.\d+)?(?:e[+-]?\d+)?(?: \(reported\))?) \|$/,
6604
+ /^$/,
6605
+ /^<\/details>$/,
6606
+ /^<!-- pipr:stats:end -->$/
6607
+ ];
6441
6608
  function createOutputState() {
6442
6609
  return {
6443
6610
  findings: [],
@@ -6457,6 +6624,66 @@ function mergeTaskOutputs(results) {
6457
6624
  }
6458
6625
  return merged;
6459
6626
  }
6627
+ function reviewStatsForRuns(runs, durationMs) {
6628
+ if (runs.length === 0) return;
6629
+ const usage = aggregateReviewUsage(runs);
6630
+ return {
6631
+ models: collectReviewModels(runs),
6632
+ agentRuns: runs.length,
6633
+ durationMs,
6634
+ inputTokens: usage.inputTokens,
6635
+ outputTokens: usage.outputTokens,
6636
+ costUsd: usage.costUsd,
6637
+ usageStatus: usage.status
6638
+ };
6639
+ }
6640
+ function collectReviewModels(runs) {
6641
+ const models = [];
6642
+ for (const model of runs.flatMap((run) => run.models)) {
6643
+ const sanitized = sanitizeReviewStatsModel(model);
6644
+ if (sanitized && models.length < 20 && !models.includes(sanitized)) models.push(sanitized);
6645
+ }
6646
+ return models.length > 0 ? models : ["[invalid model]"];
6647
+ }
6648
+ function aggregateReviewUsage(runs) {
6649
+ let inputTokens = 0;
6650
+ let outputTokens = 0;
6651
+ let costUsd = 0;
6652
+ let reportedRuns = 0;
6653
+ let partialUsage = false;
6654
+ for (const run of runs) {
6655
+ if (!run.usage) continue;
6656
+ reportedRuns += 1;
6657
+ const input = addReportedUsage(inputTokens, run.usage.inputTokens, Number.isSafeInteger);
6658
+ const output = addReportedUsage(outputTokens, run.usage.outputTokens, Number.isSafeInteger);
6659
+ const cost = addReportedUsage(costUsd, run.usage.costUsd, Number.isFinite);
6660
+ inputTokens = input.total;
6661
+ outputTokens = output.total;
6662
+ costUsd = cost.total;
6663
+ const sumsComplete = [
6664
+ input,
6665
+ output,
6666
+ cost
6667
+ ].every((sum) => sum.complete);
6668
+ partialUsage ||= run.usage.status === "partial" || !sumsComplete;
6669
+ }
6670
+ return {
6671
+ inputTokens,
6672
+ outputTokens,
6673
+ costUsd,
6674
+ status: reportedRuns === 0 ? "unavailable" : reportedRuns < runs.length || partialUsage ? "partial" : "complete"
6675
+ };
6676
+ }
6677
+ function addReportedUsage(current, reported, isValid) {
6678
+ const next = current + reported;
6679
+ return isValid(next) ? {
6680
+ total: next,
6681
+ complete: true
6682
+ } : {
6683
+ total: current,
6684
+ complete: false
6685
+ };
6686
+ }
6460
6687
  function mergeCommentContribution(merged, comment) {
6461
6688
  if (!comment) return;
6462
6689
  assertOutputContributionAllowed(merged, "comment", comment.taskName, (existing, next) => `ctx.comment(...) may be called once per selected run; received comments from '${existing}' and '${next}'`);
@@ -6534,12 +6761,37 @@ function priorReviewForTask(priorMainComment, priorReviewState) {
6534
6761
  };
6535
6762
  }
6536
6763
  function visibleMainComment(body) {
6537
- const lines = body.split("\n").filter((line) => !line.startsWith("<!-- pipr:main-comment ") && !mainCommentAttributionPattern.test(line));
6764
+ const sourceLines = body.split("\n");
6765
+ const statsRange = generatedReviewStatsRange(sourceLines);
6766
+ const lines = sourceLines.filter((line, index) => {
6767
+ return !(statsRange && index >= statsRange.start && index <= statsRange.end) && !line.startsWith("<!-- pipr:main-comment ") && !mainCommentAttributionPattern.test(line);
6768
+ });
6538
6769
  while (lines[0] === "") lines.shift();
6539
6770
  if (lines[0] && mainCommentTitles.has(lines[0])) lines.shift();
6540
6771
  while (lines[0] === "") lines.shift();
6541
6772
  return lines.join("\n").trim();
6542
6773
  }
6774
+ function generatedReviewStatsRange(lines) {
6775
+ const attributionIndex = lines.findLastIndex((line) => mainCommentAttributionPattern.test(line));
6776
+ if (attributionIndex < 0) return;
6777
+ const end = lines.slice(0, attributionIndex).findLastIndex((line) => line !== "");
6778
+ if (end < 0) return;
6779
+ if (lines[end] !== "<!-- pipr:stats:end -->") return;
6780
+ if (lines[end - 1] !== "</details>") return;
6781
+ const start = lines.lastIndexOf(reviewStatsStartMarker, end - 2);
6782
+ if (start < 0) return;
6783
+ if (lines[start + 1] !== "<details>") return;
6784
+ if (lines[start + 2] !== "<summary>Review stats</summary>") return;
6785
+ if (!matchesGeneratedReviewStatsShape(lines, start, end)) return;
6786
+ return {
6787
+ start,
6788
+ end
6789
+ };
6790
+ }
6791
+ function matchesGeneratedReviewStatsShape(lines, start, end) {
6792
+ const generatedShape = lines.slice(start + 3, end + 1);
6793
+ return generatedShape.length === generatedReviewStatsShape.length && generatedReviewStatsShape.every((pattern, index) => pattern.test(generatedShape[index] ?? ""));
6794
+ }
6543
6795
  function collectInlineFindings(state, findings) {
6544
6796
  if (!findings) return;
6545
6797
  const arrayScope = state.findingScopes.get(findings);
@@ -6563,6 +6815,7 @@ function collectedReview(output, summaryBody) {
6563
6815
  //#region src/review/task/task-runtime.ts
6564
6816
  const genericTaskFailureSummary = "Task failed; see logs for details.";
6565
6817
  async function runTaskRuntime(options) {
6818
+ const runtimeStarted = Date.now();
6566
6819
  const config = parsePiprConfig(options.config);
6567
6820
  const provider = options.providerOverride ? parseProviderConfig(options.providerOverride) : resolveProvider(config, config.defaultProvider);
6568
6821
  const diffManifest = parseDiffManifest((options.diffManifestBuilder ?? buildDiffManifest)({
@@ -6615,11 +6868,15 @@ async function runTaskRuntime(options) {
6615
6868
  const loadedPriorReviewState = options.priorReviewState ?? await options.loadPriorReviewState?.();
6616
6869
  const priorMainComment = options.priorMainComment ?? await options.loadPriorMainComment?.();
6617
6870
  const priorReviewState = priorReviewStateForSelectedTasks(loadedPriorReviewState, selectedTasks);
6871
+ const piRuns = [];
6618
6872
  const runtimeOptions = {
6619
6873
  ...options,
6620
6874
  priorReviewState,
6621
6875
  priorMainComment,
6622
- runId
6876
+ runId,
6877
+ piRunSink(run) {
6878
+ piRuns.push(run);
6879
+ }
6623
6880
  };
6624
6881
  const manifestCache = /* @__PURE__ */ new Map();
6625
6882
  const taskResults = await Promise.all(tasks.map(async (task, taskOrder) => {
@@ -6703,8 +6960,10 @@ async function runTaskRuntime(options) {
6703
6960
  provider,
6704
6961
  diffManifest,
6705
6962
  priorReviewState,
6706
- runId
6963
+ runId,
6964
+ piRunSink: runtimeOptions.piRunSink
6707
6965
  });
6966
+ const stats = reviewStatsForRuns(piRuns, Date.now() - runtimeStarted);
6708
6967
  const publishing = buildCommentPublishingPlan({
6709
6968
  event: options.event,
6710
6969
  main,
@@ -6723,7 +6982,8 @@ async function runTaskRuntime(options) {
6723
6982
  selectedTasks,
6724
6983
  failedTasks: [],
6725
6984
  validFindings: validated.validFindings.length,
6726
- droppedFindings: validated.droppedFindings.length
6985
+ droppedFindings: validated.droppedFindings.length,
6986
+ ...stats ? { stats } : {}
6727
6987
  }
6728
6988
  });
6729
6989
  const publicationPlan = publishing.publicationPlan;
@@ -6782,7 +7042,8 @@ async function runSynchronizeVerifier(options) {
6782
7042
  priorReviewState: options.priorReviewState,
6783
7043
  threadContexts: await options.options.loadInlineThreadContexts?.() ?? [],
6784
7044
  mode: { kind: "synchronize" },
6785
- runId: options.runId
7045
+ runId: options.runId,
7046
+ piRunSink: options.piRunSink
6786
7047
  });
6787
7048
  }
6788
7049
  function createTaskContext(options) {
@@ -6843,7 +7104,8 @@ function createTaskContext(options) {
6843
7104
  runtime: {
6844
7105
  ...options,
6845
7106
  taskContext,
6846
- runId: options.runId
7107
+ runId: options.runId,
7108
+ piRunSink: options.piRunSink
6847
7109
  }
6848
7110
  });
6849
7111
  options.output.providerModels.push(...result.providerModels);
@@ -9167,4 +9429,4 @@ async function runActionCommandWithDependencies(options) {
9167
9429
  //#endregion
9168
9430
  export { runInspectCommand as a, PublicationError as c, supportedOfficialInitRecipes as d, piBuiltinToolNames as f, piThinkingLevels as h, runInitCommand as i, supportedOfficialInitAdapters as l, piRequiredCliFlags as m, runActionCommandWithDependencies as n, runLocalReviewCommand as o, piReadOnlyToolNames as p, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, listOfficialInitRecipes as u };
9169
9431
 
9170
- //# sourceMappingURL=commands-Btw_Ummi.mjs.map
9432
+ //# sourceMappingURL=commands-DM63Wmy0.mjs.map