@zenera/cli 1.1.24 → 1.1.25

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.
Files changed (167) hide show
  1. package/README.md +53 -0
  2. package/dist/ask.js +28 -5
  3. package/dist/banner.js +2 -2
  4. package/dist/batch.d.ts +6 -0
  5. package/dist/batch.js +76 -1
  6. package/dist/catalog.d.ts +11 -1
  7. package/dist/catalog.js +80 -7
  8. package/dist/commands/inspect.d.ts +47 -1
  9. package/dist/commands/inspect.js +167 -18
  10. package/dist/commands/memory.js +128 -29
  11. package/dist/commands/meta.js +311 -21
  12. package/dist/commands/models.js +96 -12
  13. package/dist/commands/open.js +4 -4
  14. package/dist/commands/run.js +38 -4
  15. package/dist/commands/sandbox.js +76 -14
  16. package/dist/engine.d.ts +5 -0
  17. package/dist/engine.js +1 -0
  18. package/dist/eventlog.d.ts +59 -0
  19. package/dist/eventlog.js +146 -0
  20. package/dist/liveness.d.ts +7 -0
  21. package/dist/liveness.js +15 -1
  22. package/dist/meta/editor.d.ts +9 -0
  23. package/dist/meta/editor.js +60 -0
  24. package/dist/meta/finetune/children.d.ts +8 -0
  25. package/dist/meta/finetune/children.js +76 -0
  26. package/dist/meta/finetune/command.d.ts +30 -0
  27. package/dist/meta/finetune/command.js +338 -0
  28. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  29. package/dist/meta/finetune/dataset/anchors.js +239 -0
  30. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  31. package/dist/meta/finetune/dataset/changes.js +319 -0
  32. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  33. package/dist/meta/finetune/dataset/command.js +1011 -0
  34. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  35. package/dist/meta/finetune/dataset/drift.js +71 -0
  36. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  37. package/dist/meta/finetune/dataset/export.js +51 -0
  38. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  39. package/dist/meta/finetune/dataset/index.js +2 -0
  40. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  41. package/dist/meta/finetune/dataset/sample.js +197 -0
  42. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  43. package/dist/meta/finetune/dataset/schema.js +216 -0
  44. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  45. package/dist/meta/finetune/dataset/store.js +177 -0
  46. package/dist/meta/finetune/dataset/types.d.ts +125 -0
  47. package/dist/meta/finetune/dataset/types.js +39 -0
  48. package/dist/meta/finetune/failure.d.ts +15 -0
  49. package/dist/meta/finetune/failure.js +26 -0
  50. package/dist/meta/finetune/feedback.d.ts +45 -0
  51. package/dist/meta/finetune/feedback.js +85 -0
  52. package/dist/meta/finetune/improvements.d.ts +51 -0
  53. package/dist/meta/finetune/improvements.js +244 -0
  54. package/dist/meta/finetune/index.d.ts +2 -0
  55. package/dist/meta/finetune/index.js +2 -0
  56. package/dist/meta/finetune/journal.d.ts +35 -0
  57. package/dist/meta/finetune/journal.js +135 -0
  58. package/dist/meta/finetune/live.d.ts +74 -0
  59. package/dist/meta/finetune/live.js +226 -0
  60. package/dist/meta/finetune/loop.d.ts +3 -0
  61. package/dist/meta/finetune/loop.js +114 -0
  62. package/dist/meta/finetune/memories.d.ts +47 -0
  63. package/dist/meta/finetune/memories.js +154 -0
  64. package/dist/meta/finetune/report.d.ts +33 -0
  65. package/dist/meta/finetune/report.js +1284 -0
  66. package/dist/meta/finetune/retry.d.ts +24 -0
  67. package/dist/meta/finetune/retry.js +96 -0
  68. package/dist/meta/finetune/sampler.d.ts +18 -0
  69. package/dist/meta/finetune/sampler.js +41 -0
  70. package/dist/meta/finetune/system.d.ts +27 -0
  71. package/dist/meta/finetune/system.js +132 -0
  72. package/dist/meta/finetune/train.d.ts +39 -0
  73. package/dist/meta/finetune/train.js +391 -0
  74. package/dist/meta/finetune/tui.d.ts +16 -0
  75. package/dist/meta/finetune/tui.js +291 -0
  76. package/dist/meta/finetune/tuning.d.ts +167 -0
  77. package/dist/meta/finetune/tuning.js +289 -0
  78. package/dist/meta/finetune/usage.d.ts +62 -0
  79. package/dist/meta/finetune/usage.js +225 -0
  80. package/dist/meta/host.d.ts +14 -0
  81. package/dist/meta/host.js +20 -0
  82. package/dist/meta/index.d.ts +11 -0
  83. package/dist/meta/index.js +11 -0
  84. package/dist/meta/launch.d.ts +81 -0
  85. package/dist/meta/launch.js +286 -0
  86. package/dist/meta/model.d.ts +44 -0
  87. package/dist/meta/model.js +119 -0
  88. package/dist/meta/prompts.d.ts +18 -0
  89. package/dist/meta/prompts.js +61 -0
  90. package/dist/meta/relay.d.ts +20 -0
  91. package/dist/meta/relay.js +123 -0
  92. package/dist/meta/tokens.d.ts +52 -0
  93. package/dist/meta/tokens.js +226 -0
  94. package/dist/meta/window.d.ts +61 -0
  95. package/dist/meta/window.js +198 -0
  96. package/dist/meta/wire.d.ts +42 -0
  97. package/dist/meta/wire.js +129 -0
  98. package/dist/modelref.d.ts +16 -0
  99. package/dist/modelref.js +41 -0
  100. package/dist/narrate.js +5 -2
  101. package/dist/podman.d.ts +26 -0
  102. package/dist/podman.js +83 -3
  103. package/dist/projects.d.ts +0 -5
  104. package/dist/projects.js +2 -15
  105. package/dist/sandbox.d.ts +2 -0
  106. package/dist/sandbox.js +10 -3
  107. package/dist/scaffold.d.ts +15 -6
  108. package/dist/scaffold.js +5 -60
  109. package/dist/session.js +4 -29
  110. package/dist/term.d.ts +2 -0
  111. package/dist/term.js +11 -2
  112. package/dist/trace.js +53 -2
  113. package/dist/tui/models.d.ts +33 -0
  114. package/dist/tui/models.js +389 -0
  115. package/dist/tui/wrap.d.ts +5 -0
  116. package/dist/tui/wrap.js +12 -0
  117. package/dist/usage.d.ts +78 -0
  118. package/dist/usage.js +51 -0
  119. package/package.json +3 -2
  120. package/templates/editor/.github/copilot-instructions.md +31 -102
  121. package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
  122. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  123. package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
  124. package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
  125. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  126. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  127. package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
  128. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
  129. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
  130. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  131. package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
  132. package/templates/editor/.github/skills/zen-cli/references/inspect.md +47 -9
  133. package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
  134. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  135. package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
  136. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  137. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
  138. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  139. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  140. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  141. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1449 -658
  142. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  143. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  144. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  145. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  146. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  147. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  148. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  149. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  150. package/templates/editor/.github/skills/zen-inspect/SKILL.md +60 -25
  151. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
  152. package/templates/editor/.github/skills/zen-memory/SKILL.md +18 -14
  153. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  154. package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
  155. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
  156. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +7 -3
  157. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
  158. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  159. package/templates/editor/.vscode/settings.json +1 -0
  160. package/templates/project/agents/files-instructions.md +29 -20
  161. package/templates/project/agents/memory-instructions.md +31 -32
  162. package/templates/project/agents/tools-instructions.md +7 -4
  163. package/templates/project/gitignore +2 -0
  164. package/dist/meta.d.ts +0 -221
  165. package/dist/meta.js +0 -684
  166. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
  167. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+ //
4
+ // Choose the cases this tuning will use: up to the limit, out of the whole dataset.
5
+ //
6
+ // The dataset is every case the training set holds and is never cut down. This
7
+ // is where the limit is applied, and it is applied evenly by class: one case
8
+ // from each class in turn, so a limit of 12 over 4 classes is 3 of each, not
9
+ // the first 12 in the file. Inside a class, cases with a rubric come first,
10
+ // because a graded case says why it failed and an ungraded one only says what it
11
+ // did; then the most complex first, because the first batches are the small ones
12
+ // and a hard case shows more of what is wrong per trajectory read. A class that
13
+ // runs out stops taking turns and the others carry on.
14
+ //
15
+ // Deterministic: the same --seed over the same dataset chooses the same cases,
16
+ // so an interrupted session rebuilds the same selection.
17
+ //
18
+ // Writes { "batch": [ {id, input} ] } - the shape `zen run batch --input` takes,
19
+ // though it is not run as one: batch.mjs takes it a batch at a time.
20
+ //
21
+ // Run it from anywhere; it finds the project root from its own location.
22
+ // See .github/skills/zen-finetune/SKILL.md.
23
+
24
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
25
+ import { dirname, resolve } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ const NAME = 'select.mjs';
29
+
30
+ const USAGE = `Choose the cases this tuning will use: up to the limit, out of the whole dataset.
31
+
32
+ ${NAME} -o finetune/selection.json the limit from config.json
33
+ ${NAME} -n 40 -o finetune/selection.json forty, evenly by class
34
+ ${NAME} -n 12 --class planning twelve out of one class, to stdout
35
+
36
+ -d <file> dataset; default finetune/dataset.json
37
+ -n, --limit <n> how many cases; default config.json "limit", else all
38
+ -o <file> where to write; default stdout
39
+ --class <name> one class only
40
+ --complexity <lvl> one complexity level only
41
+ --rubric-only only cases that have a rubric
42
+ --seed <n> default config.json "seed", else 1`;
43
+
44
+ const CONFIG = 'finetune/config.json';
45
+ const LEVELS = ['complex', 'medium', 'simple'];
46
+
47
+ process.stdout.on('error', (err) => {
48
+ if (/** @type {NodeJS.ErrnoException} */ (err).code === 'EPIPE') {
49
+ process.exit(0);
50
+ }
51
+ });
52
+
53
+ process.chdir(resolve(dirname(fileURLToPath(import.meta.url)), '../../../..'));
54
+
55
+ /** @param {string} msg @param {number} [code] @returns {never} */
56
+ function die(msg, code = 2) {
57
+ console.error(`${NAME}: ${msg}`);
58
+ process.exit(code);
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Arguments
63
+ // ---------------------------------------------------------------------------
64
+
65
+ let dataset = 'finetune/dataset.json';
66
+ /** @type {number | undefined} */
67
+ let limit;
68
+ let klass = '';
69
+ let complexity = '';
70
+ let rubricOnly = false;
71
+ /** @type {number | undefined} */
72
+ let seed;
73
+ let out = '';
74
+
75
+ const argv = process.argv.slice(2);
76
+ /** @param {string} flag @returns {string} */
77
+ const next = (flag) => {
78
+ const value = argv.shift();
79
+ if (value === undefined) {
80
+ die(`${flag} needs a value`);
81
+ }
82
+ return value;
83
+ };
84
+ /** @param {string} flag @returns {number} */
85
+ const whole = (flag) => {
86
+ const raw = next(flag);
87
+ if (!/^\d+$/.test(raw)) {
88
+ die(`${flag} takes a whole number`);
89
+ }
90
+ return Number(raw);
91
+ };
92
+
93
+ while (argv.length > 0) {
94
+ const arg = /** @type {string} */ (argv.shift());
95
+ switch (arg) {
96
+ case '-d':
97
+ dataset = next('-d');
98
+ break;
99
+ case '-n':
100
+ case '--limit':
101
+ limit = whole(arg);
102
+ break;
103
+ case '-o':
104
+ out = next('-o');
105
+ break;
106
+ case '--class':
107
+ klass = next('--class');
108
+ break;
109
+ case '--complexity':
110
+ complexity = next('--complexity');
111
+ break;
112
+ case '--rubric-only':
113
+ rubricOnly = true;
114
+ break;
115
+ case '--seed':
116
+ seed = whole('--seed');
117
+ break;
118
+ case '-h':
119
+ case '--help':
120
+ console.log(USAGE);
121
+ process.exit(0);
122
+ break;
123
+ default:
124
+ console.error(`${NAME}: unknown argument ${arg}`);
125
+ console.error(USAGE);
126
+ process.exit(2);
127
+ }
128
+ }
129
+
130
+ /** @type {Record<string, unknown>} */
131
+ const config = (() => {
132
+ try {
133
+ return JSON.parse(readFileSync(CONFIG, 'utf8'));
134
+ } catch {
135
+ return {};
136
+ }
137
+ })();
138
+ /** @param {string} key @param {number} fallback @returns {number} */
139
+ const cfg = (key, fallback) => {
140
+ const value = Number(config[key]);
141
+ return Number.isFinite(value) ? value : fallback;
142
+ };
143
+ const LIMIT = limit ?? cfg('limit', 0);
144
+ const SEED = seed ?? cfg('seed', 1);
145
+
146
+ /**
147
+ * @typedef {{ id?: unknown, input?: unknown, class?: string, complexity?: string,
148
+ * rubric?: string[] }} Case
149
+ */
150
+
151
+ /** @type {{ cases?: Case[], samples?: Case[] }} */
152
+ let doc;
153
+ try {
154
+ doc = JSON.parse(readFileSync(dataset, 'utf8'));
155
+ } catch (err) {
156
+ if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') {
157
+ console.error(`${NAME}: no ${dataset}`);
158
+ console.error(' build it first: see .github/skills/zen-finetune/SKILL.md, phase 1');
159
+ process.exit(2);
160
+ }
161
+ die(`${dataset} is not valid JSON: ${/** @type {Error} */ (err).message}`);
162
+ }
163
+
164
+ // `samples` is the key datasets were written with before the rename.
165
+ const cases = doc.cases ?? doc.samples;
166
+ if (!Array.isArray(cases) || cases.length === 0) {
167
+ die(`${dataset} has no non-empty "cases" array`);
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // The dataset has to be sound before anything is chosen from it. An id names a
172
+ // directory under the batch dir, and `zen run batch` refuses a bad one item by
173
+ // item, halfway through a long run. Refuse it here instead, all at once.
174
+ // ---------------------------------------------------------------------------
175
+
176
+ /** @param {string[]} ids @param {string} headline @param {string} [hint] */
177
+ const refuse = (ids, headline, hint) => {
178
+ if (ids.length === 0) {
179
+ return;
180
+ }
181
+ console.error(`${NAME}: ${headline}`);
182
+ for (const id of ids) {
183
+ console.error(` ${id}`);
184
+ }
185
+ if (hint) {
186
+ console.error(` ${hint}`);
187
+ }
188
+ process.exit(2);
189
+ };
190
+
191
+ refuse(
192
+ cases
193
+ .filter(
194
+ (c) =>
195
+ typeof c.id !== 'string' ||
196
+ !/^[A-Za-z0-9_.-]+$/.test(c.id) ||
197
+ c.id === '.' ||
198
+ c.id === '..',
199
+ )
200
+ .map((c) => String(c.id)),
201
+ 'these ids cannot name a directory:',
202
+ 'letters, digits, dot, dash and underscore only',
203
+ );
204
+
205
+ const seen = new Set();
206
+ const dupes = new Set();
207
+ for (const c of cases) {
208
+ const id = String(c.id);
209
+ if (seen.has(id)) {
210
+ dupes.add(id);
211
+ }
212
+ seen.add(id);
213
+ }
214
+ refuse([...dupes], `duplicate ids in ${dataset}:`);
215
+
216
+ refuse(
217
+ cases.filter((c) => c.input === null || c.input === undefined).map((c) => String(c.id)),
218
+ 'these cases have no "input":',
219
+ );
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // The choice
223
+ // ---------------------------------------------------------------------------
224
+
225
+ /** @param {Case} c */
226
+ const classOf = (c) => c.class ?? 'unclassified';
227
+ /** @param {Case} c */
228
+ const levelOf = (c) => c.complexity ?? 'unrated';
229
+ /** @param {Case} c */
230
+ const graded = (c) => (c.rubric ?? []).length > 0;
231
+
232
+ const matching = cases.filter(
233
+ (c) =>
234
+ (klass === '' || classOf(c) === klass) &&
235
+ (complexity === '' || levelOf(c) === complexity) &&
236
+ (!rubricOnly || graded(c)),
237
+ );
238
+
239
+ // Lehmer's generator, s * 48271 mod 2^31-1: exact in a double, so the same order
240
+ // on every machine.
241
+ let state = (SEED % 2147483646) + 1;
242
+ const key = new Map(
243
+ matching.map((c) => {
244
+ state = (state * 48271) % 2147483647;
245
+ return [c, state];
246
+ }),
247
+ );
248
+
249
+ /**
250
+ * @template T
251
+ * @param {T[]} items
252
+ * @param {(item: T) => string} by
253
+ * @returns {Map<string, T[]>}
254
+ */
255
+ function groupBy(items, by) {
256
+ /** @type {Map<string, T[]>} */
257
+ const groups = new Map();
258
+ for (const item of items) {
259
+ const name = by(item);
260
+ groups.set(name, [...(groups.get(name) ?? []), item]);
261
+ }
262
+ return groups;
263
+ }
264
+
265
+ /**
266
+ * Take one from each list in turn until every list is empty.
267
+ *
268
+ * @template T
269
+ * @param {T[][]} lists
270
+ * @returns {T[]}
271
+ */
272
+ function roundRobin(lists) {
273
+ const out = [];
274
+ const deepest = lists.reduce((max, l) => Math.max(max, l.length), 0);
275
+ for (let i = 0; i < deepest; i++) {
276
+ for (const list of lists) {
277
+ if (i < list.length) {
278
+ out.push(list[i]);
279
+ }
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+
285
+ /** @param {Case} c */
286
+ const rank = (c) => (LEVELS.includes(levelOf(c)) ? LEVELS.indexOf(levelOf(c)) : LEVELS.length);
287
+
288
+ const byClass = groupBy(matching, classOf);
289
+ const ordered = roundRobin(
290
+ [...byClass.keys()]
291
+ .sort()
292
+ .map((name) =>
293
+ /** @type {Case[]} */ (byClass.get(name)).sort(
294
+ (a, b) =>
295
+ Number(graded(b)) - Number(graded(a)) ||
296
+ rank(a) - rank(b) ||
297
+ /** @type {number} */ (key.get(a)) - /** @type {number} */ (key.get(b)),
298
+ ),
299
+ ),
300
+ );
301
+
302
+ const chosen = LIMIT > 0 ? ordered.slice(0, LIMIT) : ordered;
303
+
304
+ if (chosen.length === 0) {
305
+ console.error(`${NAME}: nothing matched`);
306
+ console.error(' the filters may be narrower than the dataset; what is in it:');
307
+ for (const line of tally(cases.map((c) => `${classOf(c)} / ${levelOf(c)}`))) {
308
+ console.error(line);
309
+ }
310
+ process.exit(1);
311
+ }
312
+
313
+ // Only `id` and `input`: a rubric is not part of a run request, and the dataset
314
+ // stays the one place it is written down.
315
+ const body = `${JSON.stringify({ batch: chosen.map(({ id, input }) => ({ id, input })) }, null, 2)}\n`;
316
+
317
+ if (out) {
318
+ mkdirSync(dirname(resolve(out)), { recursive: true });
319
+ writeFileSync(out, body);
320
+ } else {
321
+ process.stdout.write(body);
322
+ }
323
+
324
+ // On stderr so it survives a redirect of stdout.
325
+ console.error('');
326
+ console.error(
327
+ `${NAME}: ${chosen.length} of ${cases.length} cases, seed ${SEED}${out ? ` -> ${out}` : ''}`,
328
+ );
329
+ console.error('');
330
+ console.error(' class chosen / in dataset');
331
+ const inDataset = groupBy(cases, classOf);
332
+ const inChoice = groupBy(chosen, classOf);
333
+ for (const name of [...inDataset.keys()].sort()) {
334
+ const got = inChoice.get(name)?.length ?? 0;
335
+ const has = inDataset.get(name)?.length ?? 0;
336
+ console.error(` ${name.padEnd(24)} ${String(got).padStart(6)} / ${has}`);
337
+ }
338
+ console.error('');
339
+ console.error(` ${chosen.filter(graded).length} of ${chosen.length} have a rubric`);
340
+ console.error(
341
+ ` ${chosen.filter((c) => levelOf(c) === 'complex').length} of ${chosen.length} are complex`,
342
+ );
343
+
344
+ /**
345
+ * Counts per distinct line, sorted: `sort | uniq -c`.
346
+ *
347
+ * @param {string[]} lines
348
+ * @returns {string[]}
349
+ */
350
+ function tally(lines) {
351
+ return [...groupBy(lines, (l) => l).entries()]
352
+ .sort(([a], [b]) => a.localeCompare(b))
353
+ .map(([line, all]) => `${String(all.length).padStart(6)} ${line}`);
354
+ }