orizu 0.5.18 → 0.5.19

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.
@@ -1,5 +1,6 @@
1
1
  import { extractErrorMessage, isJsonErrorResponsePayload, } from './error-response.js';
2
2
  import { sanitizeHumanInlineText } from './json-response.js';
3
+ import { formatFiniteLengthCount, } from './prompt-length-wire.js';
3
4
  const DETAIL_LEVELS = ['hunk', 'diff', 'full'];
4
5
  const CONTEXT_UNAVAILABLE_WITHOUT_REASON = ' Context unavailable (no reason supplied)';
5
6
  function isCandidatePair(pair) {
@@ -47,6 +48,56 @@ function humanLineNumber(ctx, line) {
47
48
  ? '-'
48
49
  : sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(line + 1));
49
50
  }
51
+ function formatStats(stats) {
52
+ const tokens = formatFiniteLengthCount(stats.tokens);
53
+ return `${tokens === null ? '—' : `~${tokens}`} tokens · ` +
54
+ `${formatFiniteLengthCount(stats.lines) ?? '—'} lines · ` +
55
+ `${formatFiniteLengthCount(stats.chars) ?? '—'} chars · ` +
56
+ `${formatFiniteLengthCount(stats.words) ?? '—'} words`;
57
+ }
58
+ function formatDeltaValue(value, metric, signed = false) {
59
+ const formatted = formatFiniteLengthCount(value, signed);
60
+ if (formatted === null)
61
+ return '—';
62
+ return metric === 'tokens' ? `~${formatted}` : formatted;
63
+ }
64
+ function printLengthMeasurements(ctx, pair) {
65
+ function unavailableSuffix(reason) {
66
+ if (typeof reason !== 'string' || reason.length === 0)
67
+ return '';
68
+ return ` (${sanitizeHumanInlineText(ctx.sanitizeTerminalText, reason)})`;
69
+ }
70
+ if (!pair.lengthStats && !pair.lengthDelta) {
71
+ ctx.printLine(` Length: unavailable${unavailableSuffix(pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason)}`);
72
+ return;
73
+ }
74
+ if (pair.lengthStats) {
75
+ ctx.printLine(pair.lengthStats.from
76
+ ? ` From: ${formatStats(pair.lengthStats.from)}`
77
+ : ` From: unavailable${unavailableSuffix(pair.lengthStats.fromUnavailableReason)}`);
78
+ ctx.printLine(pair.lengthStats.to
79
+ ? ` To: ${formatStats(pair.lengthStats.to)}`
80
+ : ` To: unavailable${unavailableSuffix(pair.lengthStats.toUnavailableReason)}`);
81
+ }
82
+ if (!pair.lengthDelta) {
83
+ ctx.printLine(` Length delta: unavailable${unavailableSuffix(pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason)}`);
84
+ return;
85
+ }
86
+ if (pair.lengthDelta.splitUnavailableReason) {
87
+ ctx.printLine(` Split unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.lengthDelta.splitUnavailableReason)}`);
88
+ }
89
+ for (const [metric, label] of [
90
+ ['tokens', 'Tokens'],
91
+ ['lines', 'Lines'],
92
+ ['chars', 'Chars'],
93
+ ['words', 'Words'],
94
+ ]) {
95
+ const delta = pair.lengthDelta[metric];
96
+ ctx.printLine(` ${label}: removed ${formatDeltaValue(delta.removed, metric)} · ` +
97
+ `added ${formatDeltaValue(delta.added, metric)} · ` +
98
+ `net ${formatDeltaValue(delta.net, metric, true)}`);
99
+ }
100
+ }
50
101
  function buildParams(ctx) {
51
102
  const runId = ctx.getArg('--run');
52
103
  const promptId = ctx.getArg('--prompt');
@@ -106,6 +157,7 @@ function printDiffComments(ctx, payload) {
106
157
  ctx.printLine(`${hasCandidateIds ? 'Candidates' : 'Versions'} ` +
107
158
  `${sanitizeHumanInlineText(ctx.sanitizeTerminalText, from)} → ` +
108
159
  `${sanitizeHumanInlineText(ctx.sanitizeTerminalText, to)}`);
160
+ printLengthMeasurements(ctx, pair);
109
161
  if (pair.degraded) {
110
162
  ctx.printLine(` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.degraded.reason)}`);
111
163
  }
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ import { runGitCredentialInvocation } from './git-credential.js';
43
43
  import { pushPromptDraft } from './prompt-draft-push.js';
44
44
  import { readMarkdownReportInput } from './markdown-report-input.js';
45
45
  import { promptPushErrorMessage, promptReportCommand } from './prompt-report-cli.js';
46
+ import { printPromptSummaryTable } from './prompt-summary-table.js';
46
47
  import { assertSnapshotManifestConfined, verifyGepaRunnerDirsFromArgs, verifyRunnerDirRegistered } from './runner-dir-verify.js';
47
48
  import { runScorersRegister } from './scorer-draft-push.js';
48
49
  import { runZipArtifactPush } from './zip-draft-push.js';
@@ -246,27 +247,6 @@ export function formatTerminalLink(url) {
246
247
  function shellQuote(value) {
247
248
  return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
248
249
  }
249
- function printPromptSummaries(items, emptyMessage) {
250
- if (items.length === 0) {
251
- printLine(emptyMessage);
252
- return;
253
- }
254
- const rows = items.map(item => ({
255
- id: sanitizeTerminalText(item.id),
256
- name: sanitizeTerminalText(item.name),
257
- role: sanitizeTerminalText(item.role),
258
- status: sanitizeTerminalText(item.status || 'active'),
259
- }));
260
- const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length));
261
- const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length));
262
- const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length));
263
- const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length));
264
- printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)} ${'STATUS'.padEnd(statusWidth)}`);
265
- printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)} ${'-'.repeat(statusWidth)}`);
266
- rows.forEach(row => {
267
- printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)} ${row.status.padEnd(statusWidth)}`);
268
- });
269
- }
270
250
  function readJsonObjectArg(valueArg, label) {
271
251
  if (!valueArg) {
272
252
  return {};
@@ -905,7 +885,7 @@ async function listPrompts() {
905
885
  printJson(data);
906
886
  return;
907
887
  }
908
- printPromptSummaries(data.prompts, 'No prompts found.');
888
+ printPromptSummaryTable(data.prompts, 'No prompts found.', printLine);
909
889
  }
910
890
  async function listJudges() {
911
891
  const project = getArg('--project') || await resolveProjectSlug(null);
@@ -923,7 +903,7 @@ async function listJudges() {
923
903
  printJson(data);
924
904
  return;
925
905
  }
926
- printPromptSummaries(data.judges, 'No judges found.');
906
+ printPromptSummaryTable(data.judges, 'No judges found.', printLine);
927
907
  }
928
908
  async function listScorers() {
929
909
  const project = getArg('--project') || await resolveProjectSlug(null);
@@ -0,0 +1,30 @@
1
+ export const CLI_LENGTH_STATS_FIELDS = [
2
+ 'tokens',
3
+ 'lines',
4
+ 'chars',
5
+ 'words',
6
+ ];
7
+ export const CLI_LENGTH_DELTA_SPLIT_UNAVAILABLE_REASONS = [
8
+ 'diff_degraded_size_limit',
9
+ 'diff_degraded_cell_limit',
10
+ ];
11
+ export const CLI_LENGTH_MEASUREMENT_UNAVAILABLE_REASONS = [
12
+ 'length_measurement_size_limit',
13
+ 'length_measurement_failed',
14
+ 'body_resolution_failed',
15
+ 'measurement_cap_exceeded',
16
+ 'enrichment_failed',
17
+ 'bodies_unavailable_event_cap',
18
+ ];
19
+ export function formatFiniteLengthCount(value, signed = false) {
20
+ if (typeof value !== 'number' || !Number.isFinite(value))
21
+ return null;
22
+ const formatted = Math.abs(value).toLocaleString('en-US');
23
+ if (!signed)
24
+ return formatted;
25
+ if (value > 0)
26
+ return `+${formatted}`;
27
+ if (value < 0)
28
+ return `-${formatted}`;
29
+ return formatted;
30
+ }
@@ -0,0 +1,37 @@
1
+ import { sanitizeTerminalText } from './json-response.js';
2
+ import { formatFiniteLengthCount, } from './prompt-length-wire.js';
3
+ export function printPromptSummaryTable(items, emptyMessage, printLine) {
4
+ if (items.length === 0) {
5
+ printLine(emptyMessage);
6
+ return;
7
+ }
8
+ const rows = items.map(item => ({
9
+ id: sanitizeTerminalText(item.id),
10
+ name: sanitizeTerminalText(item.name),
11
+ role: sanitizeTerminalText(item.role),
12
+ status: sanitizeTerminalText(item.status || 'active'),
13
+ tokens: item.lengthStats == null
14
+ ? '—'
15
+ : (() => {
16
+ const count = formatFiniteLengthCount(item.lengthStats.tokens);
17
+ return count === null ? '—' : `~${count}`;
18
+ })(),
19
+ lines: item.lengthStats == null
20
+ ? '—'
21
+ : formatFiniteLengthCount(item.lengthStats.lines) ?? '—',
22
+ chars: item.lengthStats == null
23
+ ? '—'
24
+ : formatFiniteLengthCount(item.lengthStats.chars) ?? '—',
25
+ words: item.lengthStats == null
26
+ ? '—'
27
+ : formatFiniteLengthCount(item.lengthStats.words) ?? '—',
28
+ }));
29
+ const headers = ['ID', 'NAME', 'ROLE', 'STATUS', 'TOKENS', 'LINES', 'CHARS', 'WORDS'];
30
+ const keys = ['id', 'name', 'role', 'status', 'tokens', 'lines', 'chars', 'words'];
31
+ const widths = keys.map((key, index) => Math.max(headers[index].length, ...rows.map(row => row[key].length)));
32
+ printLine(headers.map((header, index) => header.padEnd(widths[index])).join(' '));
33
+ printLine(widths.map(width => '-'.repeat(width)).join(' '));
34
+ rows.forEach(row => {
35
+ printLine(keys.map((key, index) => row[key].padEnd(widths[index])).join(' '));
36
+ });
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,6 +3,13 @@ import {
3
3
  isJsonErrorResponsePayload,
4
4
  } from './error-response.js'
5
5
  import { sanitizeHumanInlineText } from './json-response.js'
6
+ import {
7
+ formatFiniteLengthCount,
8
+ type CliLengthDelta,
9
+ type CliLengthMeasurementUnavailableReason,
10
+ type CliLengthStats,
11
+ type CliLengthStatsPair,
12
+ } from './prompt-length-wire.js'
6
13
 
7
14
  interface DiffCommentAuthor {
8
15
  name?: string
@@ -42,6 +49,9 @@ interface DiffCommentPayloadPairContext {
42
49
  diff: string | null
43
50
  bodies: { from: string; to: string } | null
44
51
  degraded: { reason: DiffCommentContextUnavailableReason } | null
52
+ lengthStats: CliLengthStatsPair | null
53
+ lengthDelta: CliLengthDelta | null
54
+ lengthDeltaUnavailableReason?: CliLengthMeasurementUnavailableReason
45
55
  comments: DiffCommentPayloadComment[]
46
56
  }
47
57
 
@@ -143,6 +153,86 @@ function humanLineNumber(
143
153
  : sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(line + 1))
144
154
  }
145
155
 
156
+ function formatStats(stats: CliLengthStats): string {
157
+ const tokens = formatFiniteLengthCount(stats.tokens)
158
+ return `${tokens === null ? '—' : `~${tokens}`} tokens · ` +
159
+ `${formatFiniteLengthCount(stats.lines) ?? '—'} lines · ` +
160
+ `${formatFiniteLengthCount(stats.chars) ?? '—'} chars · ` +
161
+ `${formatFiniteLengthCount(stats.words) ?? '—'} words`
162
+ }
163
+
164
+ function formatDeltaValue(
165
+ value: number | null,
166
+ metric: keyof Omit<CliLengthDelta, 'splitUnavailableReason'>,
167
+ signed = false
168
+ ): string {
169
+ const formatted = formatFiniteLengthCount(value, signed)
170
+ if (formatted === null) return '—'
171
+ return metric === 'tokens' ? `~${formatted}` : formatted
172
+ }
173
+
174
+ function printLengthMeasurements(
175
+ ctx: DiffCommentsCliContext,
176
+ pair: DiffCommentPayloadPair
177
+ ) {
178
+ function unavailableSuffix(reason: unknown): string {
179
+ if (typeof reason !== 'string' || reason.length === 0) return ''
180
+ return ` (${sanitizeHumanInlineText(ctx.sanitizeTerminalText, reason)})`
181
+ }
182
+
183
+ if (!pair.lengthStats && !pair.lengthDelta) {
184
+ ctx.printLine(
185
+ ` Length: unavailable${unavailableSuffix(
186
+ pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason
187
+ )}`
188
+ )
189
+ return
190
+ }
191
+
192
+ if (pair.lengthStats) {
193
+ ctx.printLine(pair.lengthStats.from
194
+ ? ` From: ${formatStats(pair.lengthStats.from)}`
195
+ : ` From: unavailable${unavailableSuffix(
196
+ pair.lengthStats.fromUnavailableReason
197
+ )}`)
198
+ ctx.printLine(pair.lengthStats.to
199
+ ? ` To: ${formatStats(pair.lengthStats.to)}`
200
+ : ` To: unavailable${unavailableSuffix(
201
+ pair.lengthStats.toUnavailableReason
202
+ )}`)
203
+ }
204
+ if (!pair.lengthDelta) {
205
+ ctx.printLine(
206
+ ` Length delta: unavailable${unavailableSuffix(
207
+ pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason
208
+ )}`
209
+ )
210
+ return
211
+ }
212
+
213
+ if (pair.lengthDelta.splitUnavailableReason) {
214
+ ctx.printLine(
215
+ ` Split unavailable: ${sanitizeHumanInlineText(
216
+ ctx.sanitizeTerminalText,
217
+ pair.lengthDelta.splitUnavailableReason
218
+ )}`
219
+ )
220
+ }
221
+ for (const [metric, label] of [
222
+ ['tokens', 'Tokens'],
223
+ ['lines', 'Lines'],
224
+ ['chars', 'Chars'],
225
+ ['words', 'Words'],
226
+ ] as const) {
227
+ const delta = pair.lengthDelta[metric]
228
+ ctx.printLine(
229
+ ` ${label}: removed ${formatDeltaValue(delta.removed, metric)} · ` +
230
+ `added ${formatDeltaValue(delta.added, metric)} · ` +
231
+ `net ${formatDeltaValue(delta.net, metric, true)}`
232
+ )
233
+ }
234
+ }
235
+
146
236
  function buildParams(ctx: DiffCommentsCliContext): URLSearchParams {
147
237
  const runId = ctx.getArg('--run')
148
238
  const promptId = ctx.getArg('--prompt')
@@ -210,6 +300,7 @@ function printDiffComments(ctx: DiffCommentsCliContext, payload: DiffCommentsPay
210
300
  `${sanitizeHumanInlineText(ctx.sanitizeTerminalText, from)} → ` +
211
301
  `${sanitizeHumanInlineText(ctx.sanitizeTerminalText, to)}`
212
302
  )
303
+ printLengthMeasurements(ctx, pair)
213
304
  if (pair.degraded) {
214
305
  ctx.printLine(
215
306
  ` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.degraded.reason)}`
package/src/index.ts CHANGED
@@ -88,6 +88,11 @@ import { runGitCredentialInvocation } from './git-credential.js'
88
88
  import { pushPromptDraft } from './prompt-draft-push.js'
89
89
  import { readMarkdownReportInput } from './markdown-report-input.js'
90
90
  import { promptPushErrorMessage, promptReportCommand } from './prompt-report-cli.js'
91
+ import { printPromptSummaryTable } from './prompt-summary-table.js'
92
+ import type {
93
+ CliLengthMeasurementUnavailableReason,
94
+ CliLengthStats,
95
+ } from './prompt-length-wire.js'
91
96
  import { assertSnapshotManifestConfined, verifyGepaRunnerDirsFromArgs, verifyRunnerDirRegistered } from './runner-dir-verify.js'
92
97
  import { runScorersRegister } from './scorer-draft-push.js'
93
98
  import { runZipArtifactPush } from './zip-draft-push.js'
@@ -182,6 +187,10 @@ interface PromptSummary {
182
187
  status?: string
183
188
  archivedAt?: string | null
184
189
  description?: string | null
190
+ lengthStats?: CliLengthStats | null
191
+ lengthStatsUnavailableReason?: CliLengthMeasurementUnavailableReason
192
+ lengthStatsVersionId?: string
193
+ lengthStatsVersionNumber?: number
185
194
  }
186
195
 
187
196
  interface TaskReportPayload {
@@ -536,30 +545,6 @@ function shellQuote(value: string): string {
536
545
  return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
537
546
  }
538
547
 
539
- function printPromptSummaries(items: PromptSummary[], emptyMessage: string) {
540
- if (items.length === 0) {
541
- printLine(emptyMessage)
542
- return
543
- }
544
-
545
- const rows = items.map(item => ({
546
- id: sanitizeTerminalText(item.id),
547
- name: sanitizeTerminalText(item.name),
548
- role: sanitizeTerminalText(item.role),
549
- status: sanitizeTerminalText(item.status || 'active'),
550
- }))
551
- const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
552
- const nameWidth = Math.max('NAME'.length, ...rows.map(row => row.name.length))
553
- const roleWidth = Math.max('ROLE'.length, ...rows.map(row => row.role.length))
554
- const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length))
555
-
556
- printLine(`${'ID'.padEnd(idWidth)} ${'NAME'.padEnd(nameWidth)} ${'ROLE'.padEnd(roleWidth)} ${'STATUS'.padEnd(statusWidth)}`)
557
- printLine(`${'-'.repeat(idWidth)} ${'-'.repeat(nameWidth)} ${'-'.repeat(roleWidth)} ${'-'.repeat(statusWidth)}`)
558
- rows.forEach(row => {
559
- printLine(`${row.id.padEnd(idWidth)} ${row.name.padEnd(nameWidth)} ${row.role.padEnd(roleWidth)} ${row.status.padEnd(statusWidth)}`)
560
- })
561
- }
562
-
563
548
  function readJsonObjectArg(valueArg: string | null, label: string): Record<string, unknown> {
564
549
  if (!valueArg) {
565
550
  return {}
@@ -1383,7 +1368,7 @@ async function listPrompts() {
1383
1368
  printJson(data as unknown as Record<string, unknown>)
1384
1369
  return
1385
1370
  }
1386
- printPromptSummaries(data.prompts, 'No prompts found.')
1371
+ printPromptSummaryTable(data.prompts, 'No prompts found.', printLine)
1387
1372
  }
1388
1373
 
1389
1374
  async function listJudges() {
@@ -1404,7 +1389,7 @@ async function listJudges() {
1404
1389
  printJson(data as unknown as Record<string, unknown>)
1405
1390
  return
1406
1391
  }
1407
- printPromptSummaries(data.judges, 'No judges found.')
1392
+ printPromptSummaryTable(data.judges, 'No judges found.', printLine)
1408
1393
  }
1409
1394
 
1410
1395
  async function listScorers() {
@@ -0,0 +1,65 @@
1
+ export const CLI_LENGTH_STATS_FIELDS = [
2
+ 'tokens',
3
+ 'lines',
4
+ 'chars',
5
+ 'words',
6
+ ] as const
7
+
8
+ export const CLI_LENGTH_DELTA_SPLIT_UNAVAILABLE_REASONS = [
9
+ 'diff_degraded_size_limit',
10
+ 'diff_degraded_cell_limit',
11
+ ] as const
12
+ export const CLI_LENGTH_MEASUREMENT_UNAVAILABLE_REASONS = [
13
+ 'length_measurement_size_limit',
14
+ 'length_measurement_failed',
15
+ 'body_resolution_failed',
16
+ 'measurement_cap_exceeded',
17
+ 'enrichment_failed',
18
+ 'bodies_unavailable_event_cap',
19
+ ] as const
20
+
21
+ export type CliLengthStatsField = typeof CLI_LENGTH_STATS_FIELDS[number]
22
+ export type CliLengthDeltaSplitUnavailableReason =
23
+ typeof CLI_LENGTH_DELTA_SPLIT_UNAVAILABLE_REASONS[number]
24
+ export type CliLengthMeasurementUnavailableReason =
25
+ typeof CLI_LENGTH_MEASUREMENT_UNAVAILABLE_REASONS[number]
26
+
27
+ export interface CliLengthStats {
28
+ tokens: number
29
+ lines: number
30
+ chars: number
31
+ words: number
32
+ }
33
+
34
+ export interface CliLengthStatsPair {
35
+ from: CliLengthStats | null
36
+ to: CliLengthStats | null
37
+ fromUnavailableReason?: CliLengthMeasurementUnavailableReason
38
+ toUnavailableReason?: CliLengthMeasurementUnavailableReason
39
+ }
40
+
41
+ export interface CliLengthMetricDelta {
42
+ removed: number | null
43
+ added: number | null
44
+ net: number
45
+ }
46
+
47
+ export interface CliLengthDelta extends Record<
48
+ CliLengthStatsField,
49
+ CliLengthMetricDelta
50
+ > {
51
+ splitUnavailableReason: CliLengthDeltaSplitUnavailableReason | null
52
+ }
53
+
54
+ export function formatFiniteLengthCount(
55
+ value: unknown,
56
+ signed = false
57
+ ): string | null {
58
+ if (typeof value !== 'number' || !Number.isFinite(value)) return null
59
+
60
+ const formatted = Math.abs(value).toLocaleString('en-US')
61
+ if (!signed) return formatted
62
+ if (value > 0) return `+${formatted}`
63
+ if (value < 0) return `-${formatted}`
64
+ return formatted
65
+ }
@@ -0,0 +1,57 @@
1
+ import { sanitizeTerminalText } from './json-response.js'
2
+ import {
3
+ formatFiniteLengthCount,
4
+ type CliLengthStats,
5
+ } from './prompt-length-wire.js'
6
+
7
+ interface PromptSummaryRow {
8
+ id: string
9
+ name: string
10
+ role: string
11
+ status?: string
12
+ lengthStats?: CliLengthStats | null
13
+ }
14
+
15
+ export function printPromptSummaryTable(
16
+ items: PromptSummaryRow[],
17
+ emptyMessage: string,
18
+ printLine: (line: string) => void
19
+ ) {
20
+ if (items.length === 0) {
21
+ printLine(emptyMessage)
22
+ return
23
+ }
24
+
25
+ const rows = items.map(item => ({
26
+ id: sanitizeTerminalText(item.id),
27
+ name: sanitizeTerminalText(item.name),
28
+ role: sanitizeTerminalText(item.role),
29
+ status: sanitizeTerminalText(item.status || 'active'),
30
+ tokens: item.lengthStats == null
31
+ ? '—'
32
+ : (() => {
33
+ const count = formatFiniteLengthCount(item.lengthStats.tokens)
34
+ return count === null ? '—' : `~${count}`
35
+ })(),
36
+ lines: item.lengthStats == null
37
+ ? '—'
38
+ : formatFiniteLengthCount(item.lengthStats.lines) ?? '—',
39
+ chars: item.lengthStats == null
40
+ ? '—'
41
+ : formatFiniteLengthCount(item.lengthStats.chars) ?? '—',
42
+ words: item.lengthStats == null
43
+ ? '—'
44
+ : formatFiniteLengthCount(item.lengthStats.words) ?? '—',
45
+ }))
46
+ const headers = ['ID', 'NAME', 'ROLE', 'STATUS', 'TOKENS', 'LINES', 'CHARS', 'WORDS']
47
+ const keys = ['id', 'name', 'role', 'status', 'tokens', 'lines', 'chars', 'words'] as const
48
+ const widths = keys.map((key, index) =>
49
+ Math.max(headers[index].length, ...rows.map(row => row[key].length))
50
+ )
51
+
52
+ printLine(headers.map((header, index) => header.padEnd(widths[index])).join(' '))
53
+ printLine(widths.map(width => '-'.repeat(width)).join(' '))
54
+ rows.forEach(row => {
55
+ printLine(keys.map((key, index) => row[key].padEnd(widths[index])).join(' '))
56
+ })
57
+ }
@@ -290,6 +290,15 @@ orizu comments diff --prompt <prompt-id> [--from <version> --to <version>] [--de
290
290
  Exactly one target is required, and `--from` / `--to` must appear together.
291
291
  Prompt pair selectors are integer version numbers. Human output groups comments
292
292
  by pair and renders anchored hunks; `--json` emits the complete machine payload.
293
+ Each available side prints `From:` or `To:` length stats; one side can be
294
+ `unavailable (<reason>)` while the other remains visible. Machine output allows
295
+ `lengthStats.from` and `lengthStats.to` to be independently null and names the
296
+ side failures in `fromUnavailableReason` and `toUnavailableReason`. A wholly
297
+ unavailable pair prints `Length: unavailable (<reason>)`; a missing delta
298
+ prints `Length delta: unavailable`. Otherwise `Tokens:`, `Lines:`, `Chars:`,
299
+ and `Words:` show removed/added/net rows. A `Split unavailable:` row names the
300
+ exact guard when split churn cannot be computed; removed/added are `—` but the
301
+ independent net remains numeric.
293
302
  Unrecoverable context is retained with a named degradation reason rather than
294
303
  dropping a comment. Optimization export includes the same data at
295
304
  `diffComments` using hunk detail. It also includes
@@ -313,6 +322,7 @@ Behavior:
313
322
  - `run-gepa` writes a complete local trace under `logs/<optimization_run_id>` by default.
314
323
  - The local trace is the best artifact for coding-agent analysis because it includes full rows, outputs, scores, feedback, scorer responses, reflection prompts, reflection responses, candidate text, and `result.json`.
315
324
  - `optimizations export` writes a portable JSON artifact from server data when the local log is unavailable or the run happened elsewhere.
325
+ - The v1 export preserves the run row's `best_candidate_id` in `summary.bestCandidateId` when event derivation rejects it as unknown; candidate detail may be absent, and the field is `null` when neither source names a best candidate.
316
326
  - Server optimization events redact row snapshots and reflection prompts by default; export rehydrates row inputs from dataset artifacts when possible and includes bundled `run-gepa` reflection responses.
317
327
 
318
328
  ## End-to-End Flows
@@ -413,6 +413,20 @@ orizu --local judges list --project <team>/<project>
413
413
  Prompt and judge lists show active artifacts by default. Use `--status archived`
414
414
  or `--status all` when you need archived artifacts.
415
415
 
416
+ Prompt and judge list tables include `ID`, `NAME`, `ROLE`, `STATUS`, `TOKENS`,
417
+ `LINES`, `CHARS`, and `WORDS`, measured from the latest sealed version. `—`
418
+ means the canonical body could not be measured; zero remains `0`. The `~`
419
+ prefix on token counts means approximate: Orizu uses one fixed, model-agnostic
420
+ `gpt-tokenizer` encoding rather than claiming an exact count for every model.
421
+ Failed or deliberately skipped JSON enrichment carries a named
422
+ `lengthStatsUnavailableReason`. Measured summaries also include
423
+ `lengthStatsVersionId` and `lengthStatsVersionNumber`, identifying the latest
424
+ sealed version behind each measurement even when the canonical body could not
425
+ be measured. Length enrichment is best-effort: a supporting query failure
426
+ leaves the list available with null stats and `enrichment_failed`. At most the
427
+ first 500 sorted summaries are enriched per request; later summaries carry
428
+ null stats and `measurement_cap_exceeded`.
429
+
416
430
  Archive or restore a prompt:
417
431
 
418
432
  ```bash
@@ -742,6 +756,11 @@ orizu --local optimizations export <optimization-run-id> \
742
756
 
743
757
  Use export when the local log is unavailable, the run happened on another machine, or a coding agent needs a portable single JSON artifact. The export fetches all optimization events, derives seed vs best, Pareto frontier, score-over-time, candidates, iterations, minibatch rows, and validation rows, and rehydrates row inputs from the dataset version artifact when possible. Server events may not contain row snapshots or reflection prompts unless the run used `--log-row-snapshots`; reflection responses are included for bundled `run-gepa` runs.
744
758
 
759
+ The v1 export keeps the run row's `best_candidate_id` in
760
+ `summary.bestCandidateId` when event derivation rejects that identifier as an
761
+ unknown candidate. Candidate detail can therefore be absent for the preserved
762
+ identifier. If neither source names a best candidate, the field is `null`.
763
+
745
764
  Lifecycle controls:
746
765
 
747
766
  ```bash