@zenera/cli 1.1.23 → 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.
- package/README.md +88 -2
- package/dist/ask.d.ts +51 -0
- package/dist/ask.js +166 -0
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +83 -0
- package/dist/batch.js +413 -0
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/command.d.ts +6 -0
- package/dist/commands/inspect.d.ts +57 -0
- package/dist/commands/inspect.js +539 -91
- package/dist/commands/list.js +128 -19
- package/dist/commands/memory.js +461 -32
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +110 -14
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +175 -36
- package/dist/commands/sandbox.js +148 -15
- package/dist/engine.d.ts +24 -0
- package/dist/engine.js +61 -8
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +20 -3
- package/dist/main.js +5 -2
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +338 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +125 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +41 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +42 -0
- package/dist/podman.js +198 -5
- package/dist/progress.d.ts +33 -0
- package/dist/progress.js +402 -0
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/request.d.ts +45 -0
- package/dist/request.js +300 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +20 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.d.ts +26 -1
- package/dist/session.js +46 -32
- package/dist/term.d.ts +5 -0
- package/dist/term.js +28 -3
- package/dist/trace.d.ts +84 -0
- package/dist/trace.js +737 -0
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/dist/validate.js +25 -3
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +41 -107
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +744 -0
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +18 -4
- package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +300 -21
- package/templates/editor/.github/skills/zen-cli/references/memory.md +121 -28
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +6 -2
- package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1791 -0
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +431 -0
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +78 -29
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +25 -2
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/agents.schema.json +1 -1
- package/templates/editor/.vscode/settings.json +2 -0
- package/templates/project/agents/files-instructions.md +35 -13
- package/templates/project/agents/fork-instructions.md +27 -0
- package/templates/project/agents/memory-instructions.md +106 -34
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +5 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
|
@@ -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
|
+
}
|