@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.
Files changed (184) hide show
  1. package/README.md +88 -2
  2. package/dist/ask.d.ts +51 -0
  3. package/dist/ask.js +166 -0
  4. package/dist/banner.js +2 -2
  5. package/dist/batch.d.ts +83 -0
  6. package/dist/batch.js +413 -0
  7. package/dist/catalog.d.ts +11 -1
  8. package/dist/catalog.js +80 -7
  9. package/dist/command.d.ts +6 -0
  10. package/dist/commands/inspect.d.ts +57 -0
  11. package/dist/commands/inspect.js +539 -91
  12. package/dist/commands/list.js +128 -19
  13. package/dist/commands/memory.js +461 -32
  14. package/dist/commands/meta.js +311 -21
  15. package/dist/commands/models.js +110 -14
  16. package/dist/commands/open.js +4 -4
  17. package/dist/commands/run.js +175 -36
  18. package/dist/commands/sandbox.js +148 -15
  19. package/dist/engine.d.ts +24 -0
  20. package/dist/engine.js +61 -8
  21. package/dist/eventlog.d.ts +59 -0
  22. package/dist/eventlog.js +146 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +20 -3
  25. package/dist/main.js +5 -2
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +60 -0
  28. package/dist/meta/finetune/children.d.ts +8 -0
  29. package/dist/meta/finetune/children.js +76 -0
  30. package/dist/meta/finetune/command.d.ts +30 -0
  31. package/dist/meta/finetune/command.js +338 -0
  32. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  33. package/dist/meta/finetune/dataset/anchors.js +239 -0
  34. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  35. package/dist/meta/finetune/dataset/changes.js +319 -0
  36. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  37. package/dist/meta/finetune/dataset/command.js +1011 -0
  38. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  39. package/dist/meta/finetune/dataset/drift.js +71 -0
  40. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  41. package/dist/meta/finetune/dataset/export.js +51 -0
  42. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  43. package/dist/meta/finetune/dataset/index.js +2 -0
  44. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  45. package/dist/meta/finetune/dataset/sample.js +197 -0
  46. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/schema.js +216 -0
  48. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  49. package/dist/meta/finetune/dataset/store.js +177 -0
  50. package/dist/meta/finetune/dataset/types.d.ts +125 -0
  51. package/dist/meta/finetune/dataset/types.js +39 -0
  52. package/dist/meta/finetune/failure.d.ts +15 -0
  53. package/dist/meta/finetune/failure.js +26 -0
  54. package/dist/meta/finetune/feedback.d.ts +45 -0
  55. package/dist/meta/finetune/feedback.js +85 -0
  56. package/dist/meta/finetune/improvements.d.ts +51 -0
  57. package/dist/meta/finetune/improvements.js +244 -0
  58. package/dist/meta/finetune/index.d.ts +2 -0
  59. package/dist/meta/finetune/index.js +2 -0
  60. package/dist/meta/finetune/journal.d.ts +35 -0
  61. package/dist/meta/finetune/journal.js +135 -0
  62. package/dist/meta/finetune/live.d.ts +74 -0
  63. package/dist/meta/finetune/live.js +226 -0
  64. package/dist/meta/finetune/loop.d.ts +3 -0
  65. package/dist/meta/finetune/loop.js +114 -0
  66. package/dist/meta/finetune/memories.d.ts +47 -0
  67. package/dist/meta/finetune/memories.js +154 -0
  68. package/dist/meta/finetune/report.d.ts +33 -0
  69. package/dist/meta/finetune/report.js +1284 -0
  70. package/dist/meta/finetune/retry.d.ts +24 -0
  71. package/dist/meta/finetune/retry.js +96 -0
  72. package/dist/meta/finetune/sampler.d.ts +18 -0
  73. package/dist/meta/finetune/sampler.js +41 -0
  74. package/dist/meta/finetune/system.d.ts +27 -0
  75. package/dist/meta/finetune/system.js +132 -0
  76. package/dist/meta/finetune/train.d.ts +39 -0
  77. package/dist/meta/finetune/train.js +391 -0
  78. package/dist/meta/finetune/tui.d.ts +16 -0
  79. package/dist/meta/finetune/tui.js +291 -0
  80. package/dist/meta/finetune/tuning.d.ts +167 -0
  81. package/dist/meta/finetune/tuning.js +289 -0
  82. package/dist/meta/finetune/usage.d.ts +62 -0
  83. package/dist/meta/finetune/usage.js +225 -0
  84. package/dist/meta/host.d.ts +14 -0
  85. package/dist/meta/host.js +20 -0
  86. package/dist/meta/index.d.ts +11 -0
  87. package/dist/meta/index.js +11 -0
  88. package/dist/meta/launch.d.ts +81 -0
  89. package/dist/meta/launch.js +286 -0
  90. package/dist/meta/model.d.ts +44 -0
  91. package/dist/meta/model.js +119 -0
  92. package/dist/meta/prompts.d.ts +18 -0
  93. package/dist/meta/prompts.js +61 -0
  94. package/dist/meta/relay.d.ts +20 -0
  95. package/dist/meta/relay.js +123 -0
  96. package/dist/meta/tokens.d.ts +52 -0
  97. package/dist/meta/tokens.js +226 -0
  98. package/dist/meta/window.d.ts +61 -0
  99. package/dist/meta/window.js +198 -0
  100. package/dist/meta/wire.d.ts +42 -0
  101. package/dist/meta/wire.js +129 -0
  102. package/dist/modelref.d.ts +16 -0
  103. package/dist/modelref.js +41 -0
  104. package/dist/narrate.js +5 -2
  105. package/dist/podman.d.ts +42 -0
  106. package/dist/podman.js +198 -5
  107. package/dist/progress.d.ts +33 -0
  108. package/dist/progress.js +402 -0
  109. package/dist/projects.d.ts +0 -5
  110. package/dist/projects.js +2 -15
  111. package/dist/request.d.ts +45 -0
  112. package/dist/request.js +300 -0
  113. package/dist/sandbox.d.ts +2 -0
  114. package/dist/sandbox.js +20 -3
  115. package/dist/scaffold.d.ts +15 -6
  116. package/dist/scaffold.js +5 -60
  117. package/dist/session.d.ts +26 -1
  118. package/dist/session.js +46 -32
  119. package/dist/term.d.ts +5 -0
  120. package/dist/term.js +28 -3
  121. package/dist/trace.d.ts +84 -0
  122. package/dist/trace.js +737 -0
  123. package/dist/tui/models.d.ts +33 -0
  124. package/dist/tui/models.js +389 -0
  125. package/dist/tui/wrap.d.ts +5 -0
  126. package/dist/tui/wrap.js +12 -0
  127. package/dist/usage.d.ts +78 -0
  128. package/dist/usage.js +51 -0
  129. package/dist/validate.js +25 -3
  130. package/package.json +3 -2
  131. package/templates/editor/.github/copilot-instructions.md +41 -107
  132. package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
  133. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  134. package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
  135. package/templates/editor/.github/prompts/finetune.prompt.md +744 -0
  136. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  137. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  138. package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
  139. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
  140. package/templates/editor/.github/skills/zen-cli/SKILL.md +18 -4
  141. package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
  142. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  143. package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
  144. package/templates/editor/.github/skills/zen-cli/references/inspect.md +300 -21
  145. package/templates/editor/.github/skills/zen-cli/references/memory.md +121 -28
  146. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  147. package/templates/editor/.github/skills/zen-cli/references/models.md +6 -2
  148. package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
  149. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  150. package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
  151. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
  152. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  153. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  154. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  155. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1791 -0
  156. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  157. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  158. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  159. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  160. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  161. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  162. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  163. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  164. package/templates/editor/.github/skills/zen-inspect/SKILL.md +431 -0
  165. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
  166. package/templates/editor/.github/skills/zen-memory/SKILL.md +78 -29
  167. package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
  168. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  169. package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
  170. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
  171. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +25 -2
  172. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
  173. package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
  174. package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
  175. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  176. package/templates/editor/.vscode/agents.schema.json +1 -1
  177. package/templates/editor/.vscode/settings.json +2 -0
  178. package/templates/project/agents/files-instructions.md +35 -13
  179. package/templates/project/agents/fork-instructions.md +27 -0
  180. package/templates/project/agents/memory-instructions.md +106 -34
  181. package/templates/project/agents/tools-instructions.md +7 -4
  182. package/templates/project/gitignore +5 -0
  183. package/dist/meta.d.ts +0 -221
  184. package/dist/meta.js +0 -684
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+ //
4
+ // Say what to do next in the fine-tuning, read off finetune/.
5
+ //
6
+ // This is how an interrupted session finds its place. Everything it reads is on
7
+ // disk - the dataset, the config, the selection, the README, the difficult list,
8
+ // the memory pointers, every run directory and the verdict heading of every
9
+ // findings.md - so it never guesses.
10
+ //
11
+ // Every batch goes the same way:
12
+ //
13
+ // no-memory runs run, grade, fix the prose, run again - until one PASSES
14
+ // memory.mjs candidate last good memory + what that run's cases committed
15
+ // with-memory runs every case from its own copy of the candidate; grade the
16
+ // memory use, fix the memory policy, run again - until one PASSES
17
+ // memory.mjs checkpoint the candidate becomes the last good memory
18
+ // the next batch
19
+ //
20
+ // A PASSED is only accepted from a run whose findings.md has the review its kind
21
+ // needs - `## Cost review` without memory, `## Memory review` with it - and which
22
+ // applied no edits of its own (no changes.md): edits are a guess until the next
23
+ // run confirms them. Each kind gets at most MAX_RUNS runs per batch.
24
+ //
25
+ // The number of batches is not fixed: the tuning goes on while the selection has
26
+ // unused cases or the difficult list has open ones.
27
+ //
28
+ // When finetune/README.md is older than the newest thing a run wrote, it says
29
+ // so first: the README is written by hand and is only useful if it is current.
30
+ //
31
+ // Run directories are `batch<NN>-<nomem|mem>-run<N>`; anything else is ignored.
32
+ // Run it from anywhere; it finds the project root from its own location.
33
+
34
+ import { spawnSync } from 'node:child_process';
35
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
36
+ import { dirname, join, resolve } from 'node:path';
37
+ import { fileURLToPath } from 'node:url';
38
+
39
+ const NAME = 'next.mjs';
40
+
41
+ process.chdir(resolve(dirname(fileURLToPath(import.meta.url)), '../../../..'));
42
+
43
+ for (const arg of process.argv.slice(2)) {
44
+ if (arg === '-h' || arg === '--help') {
45
+ console.log(`Say what to do next in the fine-tuning, read off finetune/.\n\n ${NAME}`);
46
+ process.exit(0);
47
+ }
48
+ console.error(`${NAME}: unknown argument ${arg}`);
49
+ process.exit(2);
50
+ }
51
+
52
+ const DATASET = 'finetune/dataset.json';
53
+ const CONFIG = 'finetune/config.json';
54
+ const SELECTION = 'finetune/selection.json';
55
+ const README = 'finetune/README.md';
56
+ const DIFFICULT = 'finetune/difficult.json';
57
+ const LAST_GOOD = 'finetune/memory/last-good.json';
58
+ const CANDIDATE = 'finetune/memory/candidate.json';
59
+ const RUNS = 'finetune/runs';
60
+ const FINAL = `${RUNS}/final-check`;
61
+ const SCRIPTS = '.github/skills/zen-finetune/scripts';
62
+ const MAX_RUNS = 4;
63
+ const RUN_NAME = /^batch(\d{2})-(nomem|mem)-run(\d+)$/;
64
+
65
+ /** @param {string} path @returns {any} */
66
+ function json(path) {
67
+ try {
68
+ return JSON.parse(readFileSync(path, 'utf8'));
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
73
+
74
+ /** @param {string} path @returns {number} */
75
+ const mtime = (path) => (existsSync(path) ? statSync(path).mtimeMs : 0);
76
+
77
+ const datasetDoc = json(DATASET);
78
+ const datasetSize = (datasetDoc?.cases ?? datasetDoc?.samples ?? []).length;
79
+ const config = json(CONFIG);
80
+ /** @type {string[]} */
81
+ const selection = (json(SELECTION)?.batch ?? []).map((/** @type {{ id: string }} */ c) => c.id);
82
+ const maxRetries = Number(config?.maxRetries) || 3;
83
+ const concurrency = Number(config?.concurrency) || 16;
84
+
85
+ /**
86
+ * The verdict heading of a run's findings.md, or '' if it has none.
87
+ *
88
+ * @param {string} dir
89
+ * @returns {string}
90
+ */
91
+ function verdict(dir) {
92
+ const path = join(dir, 'findings.md');
93
+ if (!existsSync(path)) {
94
+ return '';
95
+ }
96
+ const match = /^## ([A-Z][A-Z ]*[A-Z])/m.exec(readFileSync(path, 'utf8'));
97
+ return match ? match[1].trimEnd() : '';
98
+ }
99
+
100
+ /** @typedef {{ name: string, batch: number, kind: 'nomem' | 'mem', run: number }} Run */
101
+
102
+ /** Every run directory whose name parses, in the order they ran. @type {Run[]} */
103
+ const runs = (() => {
104
+ try {
105
+ return readdirSync(RUNS, { withFileTypes: true })
106
+ .filter((e) => e.isDirectory() && RUN_NAME.test(e.name))
107
+ .map((e) => {
108
+ const m = /** @type {RegExpExecArray} */ (RUN_NAME.exec(e.name));
109
+ return {
110
+ name: e.name,
111
+ batch: +m[1],
112
+ kind: /** @type {'nomem' | 'mem'} */ (m[2]),
113
+ run: +m[3],
114
+ };
115
+ })
116
+ .sort(
117
+ (a, b) =>
118
+ a.batch - b.batch ||
119
+ Number(a.kind === 'mem') - Number(b.kind === 'mem') ||
120
+ a.run - b.run,
121
+ );
122
+ } catch {
123
+ return [];
124
+ }
125
+ })();
126
+
127
+ const pad = (/** @type {number} */ n) => String(n).padStart(2, '0');
128
+ /** @param {number} batch @param {'nomem' | 'mem'} kind @param {number} run */
129
+ const runDir = (batch, kind, run) => `${RUNS}/batch${pad(batch)}-${kind}-run${run}`;
130
+ /** @param {'nomem' | 'mem'} kind */
131
+ const memoryFlag = (kind) =>
132
+ kind === 'nomem'
133
+ ? '--memory finetune/empty'
134
+ : `--memory "$(${SCRIPTS}/memory.mjs path --candidate)"`;
135
+ /** @param {number} batch @param {'nomem' | 'mem'} kind @param {number} run @param {number} [c] */
136
+ const runIt = (batch, kind, run, c = concurrency) => {
137
+ const dir = runDir(batch, kind, run);
138
+ const copy =
139
+ kind === 'nomem' && run === 1
140
+ ? ''
141
+ : `mkdir -p ${dir} && cp ${runDir(batch, 'nomem', 1)}/cases.json ${dir}/ && `;
142
+ return (
143
+ `${copy}zen run batch --input ${dir}/cases.json --batch-dir ${dir}/batch ` +
144
+ `${memoryFlag(kind)} --concurrency ${c}`
145
+ );
146
+ };
147
+
148
+ /** @param {Run} r @returns {number} runs of r's batch and kind up to r, not counting void ones */
149
+ const tuned = (r) =>
150
+ runs.filter(
151
+ (x) =>
152
+ x.batch === r.batch &&
153
+ x.kind === r.kind &&
154
+ x.run <= r.run &&
155
+ verdict(join(RUNS, x.name)) !== 'VOID',
156
+ ).length;
157
+
158
+ /** @returns {Map<number, string[]>} case ids of every batch, from its first no-memory run */
159
+ function batches() {
160
+ /** @type {Map<number, string[]>} */
161
+ const found = new Map();
162
+ for (const r of runs) {
163
+ if (r.kind === 'nomem' && r.run === 1) {
164
+ found.set(
165
+ r.batch,
166
+ (json(join(RUNS, r.name, 'cases.json'))?.batch ?? []).map(
167
+ (/** @type {{ id: string }} */ c) => c.id,
168
+ ),
169
+ );
170
+ }
171
+ }
172
+ return found;
173
+ }
174
+
175
+ /**
176
+ * Open and stuck difficult cases. Stuck: `maxRetries` batches have held it
177
+ * since it was added, and it is still not fixed.
178
+ *
179
+ * @returns {{ open: number, stuck: number }}
180
+ */
181
+ function difficulty() {
182
+ const held = batches();
183
+ const unfixed = (json(DIFFICULT)?.cases ?? []).filter((/** @type {any} */ e) => !e.fixed);
184
+ const stuck = unfixed.filter((/** @type {any} */ e) => {
185
+ const since = Number(RUN_NAME.exec(e.added)?.[1] ?? 0);
186
+ const retries = [...held.entries()].filter(
187
+ ([n, ids]) => n > since && ids.includes(e.id),
188
+ ).length;
189
+ return retries >= maxRetries;
190
+ }).length;
191
+ return { open: unfixed.length - stuck, stuck };
192
+ }
193
+
194
+ /**
195
+ * Why a run that says PASSED may not pass, or '' if it may.
196
+ *
197
+ * @param {Run} r
198
+ * @returns {string}
199
+ */
200
+ function refusal(r) {
201
+ const dir = join(RUNS, r.name);
202
+ const findings = existsSync(join(dir, 'findings.md'))
203
+ ? readFileSync(join(dir, 'findings.md'), 'utf8')
204
+ : '';
205
+ const review = r.kind === 'nomem' ? 'Cost review' : 'Memory review';
206
+ if (!new RegExp(`^## ${review}`, 'm').test(findings)) {
207
+ return r.kind === 'nomem'
208
+ ? `${r.name}: says PASSED but has no "## Cost review" - review llm calls, ` +
209
+ 'forks and discovery for every case before passing'
210
+ : `${r.name}: says PASSED but has no "## Memory review" - review recall, ` +
211
+ 're-commits and savings for every case before passing';
212
+ }
213
+ if (existsSync(join(dir, 'changes.md')) && tuned(r) < MAX_RUNS) {
214
+ return (
215
+ `${r.name}: says PASSED but applied edits, which nothing has confirmed - ` +
216
+ `run it again: ${runIt(r.batch, r.kind, r.run + 1)}`
217
+ );
218
+ }
219
+ return '';
220
+ }
221
+
222
+ /**
223
+ * After a batch passed with memory: the next batch while there is anything left,
224
+ * else the final check.
225
+ *
226
+ * @param {number} batch
227
+ * @returns {string}
228
+ */
229
+ function afterPass(batch) {
230
+ const { open, stuck } = difficulty();
231
+ const used = new Set([...batches().values()].flat());
232
+ const unused = selection.filter((id) => !used.has(id)).length;
233
+ const n = batch + 1;
234
+
235
+ if (unused > 0 || open > 0) {
236
+ return (
237
+ `batch ${batch} passed (${unused} unused case(s), ${open} difficult open) - ` +
238
+ `build batch ${n}: ${SCRIPTS}/batch.mjs next -o ${runDir(n, 'nomem', 1)}/cases.json`
239
+ );
240
+ }
241
+ const stuckNote = stuck > 0 ? ` (${stuck} difficult case(s) stuck - report them)` : '';
242
+ if (!existsSync(FINAL)) {
243
+ return (
244
+ `every case used, none open${stuckNote} - run the final check into ${FINAL}/: ` +
245
+ `the whole selection, with the last good memory`
246
+ );
247
+ }
248
+ if (!existsSync(join(FINAL, 'batch', 'batch.json'))) {
249
+ return `${FINAL}: the run never finished - move batch/ aside and re-run it`;
250
+ }
251
+ switch (verdict(FINAL)) {
252
+ case 'VERIFIED':
253
+ return 'done - the final check is VERIFIED and nothing is left to tune';
254
+ case '':
255
+ return `${FINAL}: not graded yet - ${SCRIPTS}/report.mjs -d ${FINAL}/batch oom, then grade`;
256
+ default:
257
+ return (
258
+ `${FINAL}: not VERIFIED - report what regressed under "Open problems" and ` +
259
+ 'tell the user; the tuning does not re-open itself'
260
+ );
261
+ }
262
+ }
263
+
264
+ /**
265
+ * The step after `last`, the latest run of the latest batch.
266
+ *
267
+ * @param {Run} last
268
+ * @returns {string}
269
+ */
270
+ function afterRun(last) {
271
+ const dir = join(RUNS, last.name);
272
+ if (!existsSync(join(dir, 'batch'))) {
273
+ return `${last.name}: not run yet - ${runIt(last.batch, last.kind, last.run)}`;
274
+ }
275
+ if (!existsSync(join(dir, 'batch', 'batch.json'))) {
276
+ return `${last.name}: the run never finished - move batch/ aside and re-run it`;
277
+ }
278
+
279
+ switch (verdict(dir)) {
280
+ case '':
281
+ return `${last.name}: not graded yet - ${SCRIPTS}/report.mjs -d ${dir}/batch oom, then grade`;
282
+ case 'VOID': {
283
+ // What the void run actually ran at, not the config: the agent may
284
+ // already have halved that.
285
+ const ran =
286
+ Number(json(join(dir, 'batch', 'batch.json'))?.batch?.concurrency) || concurrency;
287
+ if (ran <= 4) {
288
+ return (
289
+ `${last.name}: void - out of memory at concurrency ${ran}, the floor: ` +
290
+ 'the machine cannot run this batch - stop and tell the user'
291
+ );
292
+ }
293
+ const halved = Math.max(4, Math.floor(ran / 2));
294
+ const setIt =
295
+ concurrency <= halved ? '' : `set "concurrency": ${halved} in ${CONFIG}, then `;
296
+ return (
297
+ `${last.name}: void - out of memory at concurrency ${ran}: ${setIt}` +
298
+ `run the same cases again: ${runIt(last.batch, last.kind, last.run + 1, Math.min(concurrency, halved))}`
299
+ );
300
+ }
301
+ case 'PASSED': {
302
+ if (refusal(last)) {
303
+ return refusal(last);
304
+ }
305
+ if (last.kind === 'nomem') {
306
+ if (json(CANDIDATE)?.from !== last.name) {
307
+ return (
308
+ `${last.name}: passed without memory - build the candidate memory: ` +
309
+ `${SCRIPTS}/memory.mjs candidate ${last.name}`
310
+ );
311
+ }
312
+ return (
313
+ `batch ${last.batch} passed without memory and the candidate is built - ` +
314
+ `run it with memory: ${runIt(last.batch, 'mem', 1)}`
315
+ );
316
+ }
317
+ if (!(json(LAST_GOOD)?.batches ?? []).includes(last.batch)) {
318
+ return (
319
+ `batch ${last.batch} passed with memory - keep its memory first: ` +
320
+ `${SCRIPTS}/memory.mjs checkpoint ${last.name}`
321
+ );
322
+ }
323
+ return afterPass(last.batch);
324
+ }
325
+ default: {
326
+ const what = last.kind === 'nomem' ? 'no-memory' : 'with-memory';
327
+ if (tuned(last) >= MAX_RUNS) {
328
+ return (
329
+ `batch ${last.batch} has had ${MAX_RUNS} ${what} runs - stop tuning it: revert what ` +
330
+ `${last.name} did not confirm, add what is still wrong to difficult.mjs, ` +
331
+ 'and write PASSED'
332
+ );
333
+ }
334
+ return existsSync(join(dir, 'changes.md'))
335
+ ? `edits applied - confirm them: ${runIt(last.batch, last.kind, last.run + 1)}`
336
+ : `${last.name}: graded, nothing applied - merge the proposals and write changes.md`;
337
+ }
338
+ }
339
+ }
340
+
341
+ /** @returns {string} */
342
+ function nextStep() {
343
+ if (datasetSize === 0) {
344
+ return `build ${DATASET} from the whole training set - every case, no limit`;
345
+ }
346
+ if (!config) {
347
+ return `write ${CONFIG}: limit, minBatch, maxBatch, recheck, concurrency, seed, maxRetries`;
348
+ }
349
+ if (selection.length === 0) {
350
+ return `choose the cases: ${SCRIPTS}/select.mjs -o ${SELECTION}`;
351
+ }
352
+ if (!existsSync(README)) {
353
+ return `write ${README}: the plan - before anything runs`;
354
+ }
355
+ const last = runs.at(-1);
356
+ if (!last) {
357
+ return `build batch 1: ${SCRIPTS}/batch.mjs next -o ${runDir(1, 'nomem', 1)}/cases.json`;
358
+ }
359
+ return afterRun(last);
360
+ }
361
+
362
+ // The README is patched by hand after every step; say so when it fell behind.
363
+ if (existsSync(README)) {
364
+ const written = mtime(README);
365
+ const newer = [
366
+ CONFIG,
367
+ SELECTION,
368
+ DIFFICULT,
369
+ LAST_GOOD,
370
+ CANDIDATE,
371
+ ...[...runs.map((r) => join(RUNS, r.name)), FINAL].flatMap((dir) =>
372
+ ['batch/batch.json', 'findings.md', 'changes.md'].map((f) => join(dir, f)),
373
+ ),
374
+ ].filter((path) => mtime(path) > written);
375
+ if (newer.length > 0) {
376
+ const latest = newer.sort((a, b) => mtime(b) - mtime(a))[0];
377
+ console.log(`first: patch ${README} - ${latest} is newer than it`);
378
+ }
379
+ }
380
+
381
+ // A cut dataset is only right if the user asked for one; never let it pass unseen.
382
+ if (selection.length > 0 && selection.length < datasetSize) {
383
+ console.log(
384
+ `note: limit ${selection.length} of ${datasetSize} cases - ${datasetSize - selection.length} ` +
385
+ `are never tuned against. Unless the user asked for that, set "limit": ${datasetSize} ` +
386
+ `in ${CONFIG} and re-run select.mjs -o ${SELECTION}`,
387
+ );
388
+ }
389
+
390
+ // Every step passes through here, so the usage report never lags by more than one.
391
+ if (existsSync(`${SCRIPTS}/usage.mjs`)) {
392
+ spawnSync(process.execPath, [`${SCRIPTS}/usage.mjs`, '-q'], {
393
+ stdio: 'ignore',
394
+ timeout: 30_000,
395
+ });
396
+ }
397
+
398
+ console.log(nextStep());