@zenera/cli 1.1.25 → 1.1.27

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 (96) hide show
  1. package/README.md +13 -1
  2. package/dist/catalog.d.ts +20 -0
  3. package/dist/catalog.js +60 -0
  4. package/dist/commands/index.js +2 -0
  5. package/dist/commands/inspect.d.ts +5 -1
  6. package/dist/commands/inspect.js +54 -14
  7. package/dist/commands/memory.js +28 -3
  8. package/dist/commands/meta.js +24 -5
  9. package/dist/commands/models.js +2 -1
  10. package/dist/commands/sandbox.js +73 -3
  11. package/dist/commands/view.d.ts +8 -0
  12. package/dist/commands/view.js +114 -0
  13. package/dist/engine.d.ts +3 -1
  14. package/dist/engine.js +6 -0
  15. package/dist/eventlog.d.ts +5 -0
  16. package/dist/eventlog.js +25 -1
  17. package/dist/liveness.js +20 -2
  18. package/dist/main.js +0 -0
  19. package/dist/meta/editor.d.ts +1 -1
  20. package/dist/meta/editor.js +4 -21
  21. package/dist/meta/finetune/analyze-brief.d.ts +26 -0
  22. package/dist/meta/finetune/analyze-brief.js +131 -0
  23. package/dist/meta/finetune/apply-brief.d.ts +23 -0
  24. package/dist/meta/finetune/apply-brief.js +247 -0
  25. package/dist/meta/finetune/brief.d.ts +45 -0
  26. package/dist/meta/finetune/brief.js +158 -0
  27. package/dist/meta/finetune/command.d.ts +1 -1
  28. package/dist/meta/finetune/command.js +224 -46
  29. package/dist/meta/finetune/cost.d.ts +46 -0
  30. package/dist/meta/finetune/cost.js +105 -0
  31. package/dist/meta/finetune/dataset/types.d.ts +3 -1
  32. package/dist/meta/finetune/feedback.d.ts +14 -2
  33. package/dist/meta/finetune/feedback.js +22 -3
  34. package/dist/meta/finetune/guard.d.ts +29 -0
  35. package/dist/meta/finetune/guard.js +104 -0
  36. package/dist/meta/finetune/improvements.d.ts +50 -3
  37. package/dist/meta/finetune/improvements.js +290 -33
  38. package/dist/meta/finetune/live.d.ts +17 -7
  39. package/dist/meta/finetune/live.js +56 -10
  40. package/dist/meta/finetune/loop.js +59 -10
  41. package/dist/meta/finetune/recheck.d.ts +58 -0
  42. package/dist/meta/finetune/recheck.js +311 -0
  43. package/dist/meta/finetune/report.d.ts +9 -2
  44. package/dist/meta/finetune/report.js +551 -438
  45. package/dist/meta/finetune/sampler.js +8 -2
  46. package/dist/meta/finetune/train.d.ts +6 -2
  47. package/dist/meta/finetune/train.js +55 -21
  48. package/dist/meta/finetune/tui.d.ts +3 -3
  49. package/dist/meta/finetune/tui.js +37 -12
  50. package/dist/meta/finetune/tuning.d.ts +27 -1
  51. package/dist/meta/finetune/tuning.js +21 -2
  52. package/dist/meta/finetune/usage.d.ts +4 -0
  53. package/dist/meta/finetune/usage.js +35 -0
  54. package/dist/meta/finetune/view.d.ts +9 -0
  55. package/dist/meta/finetune/view.js +27 -0
  56. package/dist/meta/host.d.ts +2 -1
  57. package/dist/meta/host.js +2 -1
  58. package/dist/meta.d.ts +291 -0
  59. package/dist/meta.js +981 -0
  60. package/dist/resources.d.ts +215 -0
  61. package/dist/resources.js +856 -0
  62. package/dist/scaffold.d.ts +3 -4
  63. package/dist/scaffold.js +21 -9
  64. package/dist/trace.d.ts +5 -0
  65. package/dist/trace.js +54 -5
  66. package/dist/tui/app.js +19 -5
  67. package/dist/tui/links.d.ts +27 -0
  68. package/dist/tui/links.js +142 -0
  69. package/dist/tui/models.d.ts +3 -1
  70. package/dist/tui/models.js +65 -7
  71. package/dist/tui/sandbox.d.ts +10 -0
  72. package/dist/tui/sandbox.js +131 -0
  73. package/package.json +3 -2
  74. package/templates/editor/.github/copilot-instructions.md +29 -43
  75. package/templates/editor/.github/prompts/analyze.prompt.md +3 -1
  76. package/templates/editor/.github/prompts/finetune-apply.prompt.md +221 -42
  77. package/templates/editor/.github/prompts/project-review.prompt.md +1 -1
  78. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +162 -57
  79. package/templates/editor/.github/skills/zen-cli/references/finetune.md +74 -14
  80. package/templates/editor/.github/skills/zen-cli/references/inspect.md +16 -2
  81. package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
  82. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +25 -13
  83. package/templates/editor/.github/skills/zen-finetune/SKILL.md +74 -33
  84. package/templates/editor/.github/skills/zen-inspect/SKILL.md +14 -1
  85. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +4 -3
  86. package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
  87. package/templates/editor/.github/skills/zen-memory/SKILL.md +64 -39
  88. package/templates/editor/.github/skills/zen-review/SKILL.md +6 -31
  89. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +1 -2
  90. package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
  91. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +2 -1
  92. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
  93. package/templates/project/agents/fork-instructions.md +78 -28
  94. package/templates/project/agents/memory-instructions.md +222 -493
  95. package/templates/project/agents/tools-instructions.md +7 -0
  96. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
@@ -12,9 +12,10 @@ import { Stopped } from "./tuning.js";
12
12
  // The loop: N workers, cases started as they come free, one applier
13
13
  //
14
14
  // Whenever a worker is free - and no woken case is waiting for one - the next
15
- // case starts on it and is trained to the end. A parked case gives its worker
16
- // back, so a lane never sits idle waiting for an apply. A stop never interrupts
17
- // a step: it is noticed between steps, and the case resumes next time.
15
+ // recheck, or else the next case, starts on it and is trained to the end. A
16
+ // parked case gives its worker back, so a lane never sits idle waiting for an
17
+ // apply. A stop never interrupts a step: it is noticed between steps, and the
18
+ // case resumes next time.
18
19
  // ---------------------------------------------------------------------------
19
20
  const REPORT_EVERY_MS = 15_000;
20
21
  const LIVE_EVERY_MS = 1000;
@@ -24,10 +25,15 @@ export async function runLoop(t) {
24
25
  claimLock(lock, ownLock(), (held) => new CliError(`a tuning is already running here (pid ${held.pid}, since ${held.startedAt})`, EXIT.failed, `stop it with: zen meta finetune stop - or remove ${lock} if it crashed`));
25
26
  rmSync(t.stopFile, { force: true });
26
27
  const timer = setInterval(() => t.report(), REPORT_EVERY_MS);
27
- const live = setInterval(() => publish(t), LIVE_EVERY_MS);
28
+ const live = setInterval(() => {
29
+ publish(t);
30
+ t.report(true);
31
+ }, LIVE_EVERY_MS);
28
32
  try {
29
33
  await recover(t);
34
+ t.rechecks.resume();
30
35
  const start = t.system.record('start');
36
+ await t.improvements.baseline();
31
37
  t.event({ what: 'started', detail: `system v${start.v}, ${t.workers.length} workers` });
32
38
  publish(t);
33
39
  const applier = t.improvements.loop();
@@ -42,6 +48,7 @@ export async function runLoop(t) {
42
48
  clearInterval(live);
43
49
  t.finished = true;
44
50
  publish(t);
51
+ t.report();
45
52
  try {
46
53
  unlinkSync(lock);
47
54
  }
@@ -55,33 +62,75 @@ export async function runLoop(t) {
55
62
  }
56
63
  async function dispatch(t) {
57
64
  const running = new Set();
65
+ const start = (seat, work) => {
66
+ const task = work(seat).finally(() => running.delete(task));
67
+ running.add(task);
68
+ };
58
69
  while (!t.stopping) {
59
70
  if (t.improvements.applying || t.resuming > 0 || !t.freeWorker()) {
60
71
  await t.tick();
61
72
  continue;
62
73
  }
74
+ // A recheck tests the prose of the day, so it goes before a new case.
75
+ const taken = t.rechecks.take();
76
+ if (taken) {
77
+ const seat = { case: taken.case };
78
+ t.claimed.add(taken.case.id);
79
+ await t.seat(seat, false);
80
+ start(seat, (s) => recheckCase(t, s, taken.recheck));
81
+ continue;
82
+ }
63
83
  const c = nextCase(t);
64
84
  if (!c) {
65
85
  t.drained = true;
66
- break;
86
+ // An apply still to come queues rechecks; only when none can is the work done.
87
+ const more = running.size > 0 ||
88
+ t.improvements.applying ||
89
+ t.improvements.pending.length > 0 ||
90
+ t.rechecks.waiting() > 0;
91
+ if (!more) {
92
+ break;
93
+ }
94
+ await t.tick();
95
+ continue;
67
96
  }
68
97
  const seat = { case: c };
69
98
  t.claimed.add(c.id);
70
99
  t.tried.add(c.id);
71
100
  const before = resultOf(t, c);
72
101
  if (before?.state === 'failed') {
73
- // Taken again: until it ends anew it is in progress, not failed. Its analyze session
74
- // may be what broke (another model, a dead provider), so the next analysis starts fresh.
102
+ // Taken again: until it ends anew it is in progress, not failed.
75
103
  rmSync(join(t.caseDir(c), 'result.json'), { force: true });
76
- rmSync(join(t.caseDir(c), 'session'), { force: true });
77
104
  t.event({ what: 'resumed', case: c.id, rev: c.rev, detail: before.reason });
78
105
  }
79
106
  await t.seat(seat, false);
80
- const task = trainCase(t, seat).finally(() => running.delete(task));
81
- running.add(task);
107
+ start(seat, (s) => trainCase(t, s));
82
108
  }
83
109
  await Promise.all(running);
84
110
  }
111
+ async function recheckCase(t, s, r) {
112
+ try {
113
+ await t.rechecks.run(s, r, s.case);
114
+ }
115
+ catch (err) {
116
+ if (!(err instanceof Stopped)) {
117
+ t.error(`${s.case.id} · recheck after ${r.apply}`, err.message, err instanceof StepError ? err.log : undefined);
118
+ if (err instanceof SandboxDown) {
119
+ // Left running: the next start queues it again.
120
+ t.sandboxDown(err.message);
121
+ }
122
+ else {
123
+ t.rechecks.void(r, err.message);
124
+ t.failed(`recheck: ${err.message}`);
125
+ }
126
+ }
127
+ }
128
+ finally {
129
+ t.unseat(s);
130
+ t.claimed.delete(s.case.id);
131
+ t.report();
132
+ }
133
+ }
85
134
  async function trainCase(t, s) {
86
135
  const c = s.case;
87
136
  try {
@@ -0,0 +1,58 @@
1
+ import type { Case } from './dataset/types.ts';
2
+ import { type Seat, type Tuning } from './tuning.ts';
3
+ import { type ByStage } from './usage.ts';
4
+ export interface Recheck {
5
+ case: string;
6
+ rev: number;
7
+ /** the apply whose version it runs on */
8
+ apply: string;
9
+ state: 'queued' | 'running' | 'passed' | 'regressed' | 'void';
10
+ at: string;
11
+ /** each try's verdict */
12
+ tries?: string[];
13
+ /** the request a regression sent to the improvement queue */
14
+ request?: string;
15
+ /** why it was void, or why a regression sent no request */
16
+ note?: string;
17
+ }
18
+ /** A case regresses only when every one of these fails: one failure may be the model's noise. */
19
+ export declare const RECHECK_TRIES = 2;
20
+ export declare class Rechecks {
21
+ #private;
22
+ constructor(t: Tuning);
23
+ get file(): string;
24
+ all(): Recheck[];
25
+ dir(r: {
26
+ case: string;
27
+ apply: string;
28
+ }): string;
29
+ /** Each try's folder, oldest first. */
30
+ tryDirs(r: {
31
+ case: string;
32
+ apply: string;
33
+ }): string[];
34
+ waiting(): number;
35
+ /**
36
+ * After a stop: a recheck cut off part-way runs again, and a regression's request
37
+ * - kept only in memory until an apply takes it - goes back in the queue.
38
+ */
39
+ resume(): void;
40
+ /**
41
+ * Queues completed cases to run on the version `apply` made: a case whose
42
+ * last recheck regressed first - this apply was meant to repair it - then
43
+ * the rest in an order fixed by the seed and the apply.
44
+ */
45
+ queue(apply: string): Recheck[];
46
+ /** The next queued recheck, marked running, with its case; one whose case changed is dropped. */
47
+ take(): {
48
+ recheck: Recheck;
49
+ case: Case;
50
+ } | undefined;
51
+ /** A step broke for a reason outside the prose: the recheck says nothing. */
52
+ void(r: Recheck, why: string): void;
53
+ /** Runs a recheck on the seat's worker, to its verdict. */
54
+ run(s: Seat, r: Recheck, c: Case): Promise<void>;
55
+ /** Every recheck run and analysis so far. */
56
+ tokens(): ByStage;
57
+ }
58
+ //# sourceMappingURL=recheck.d.ts.map
@@ -0,0 +1,311 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { writeAnalyzeBrief } from "./analyze-brief.js";
5
+ import { readFeedback } from "./feedback.js";
6
+ import { writeApply } from "./report.js";
7
+ import { resultOf, selection } from "./sampler.js";
8
+ import { keepAnalysis, runOnce } from "./train.js";
9
+ import { readJson } from "./tuning.js";
10
+ import { envelopesIn, mergeStages, spent } from "./usage.js";
11
+ /** A case regresses only when every one of these fails: one failure may be the model's noise. */
12
+ export const RECHECK_TRIES = 2;
13
+ /** Requests one case's regressions may send; past that it is reported, so the tuning ends. */
14
+ const REQUESTS_PER_CASE = 2;
15
+ const pad = (n) => String(n).padStart(2, '0');
16
+ export class Rechecks {
17
+ #t;
18
+ constructor(t) {
19
+ this.#t = t;
20
+ }
21
+ get file() {
22
+ return join(this.#t.dir, 'rechecks.json');
23
+ }
24
+ all() {
25
+ return readJson(this.file) ?? [];
26
+ }
27
+ #save(all) {
28
+ mkdirSync(this.#t.dir, { recursive: true });
29
+ writeFileSync(this.file, `${JSON.stringify(all, null, 2)}\n`);
30
+ }
31
+ #update(r, change) {
32
+ const all = this.all();
33
+ const i = all.findIndex((x) => x.case === r.case && x.apply === r.apply);
34
+ Object.assign(r, change, { at: new Date().toISOString() });
35
+ if (i >= 0) {
36
+ all[i] = r;
37
+ this.#save(all);
38
+ }
39
+ if (change.state && change.state !== 'running') {
40
+ this.#page(r.apply);
41
+ }
42
+ }
43
+ /** The apply's page lists its rechecks; it was written before they ran. */
44
+ #page(apply) {
45
+ try {
46
+ writeApply(this.#t, join(this.#t.improvements.dir, apply));
47
+ }
48
+ catch {
49
+ // A page that failed to render must not stop the tuning.
50
+ }
51
+ }
52
+ dir(r) {
53
+ return join(this.#t.dir, 'rechecks', r.apply, r.case);
54
+ }
55
+ /** Each try's folder, oldest first. */
56
+ tryDirs(r) {
57
+ try {
58
+ return readdirSync(this.dir(r))
59
+ .filter((n) => /^\d{2}-nomem$/.test(n))
60
+ .sort()
61
+ .map((n) => join(this.dir(r), n));
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
67
+ waiting() {
68
+ return this.all().filter((r) => r.state === 'queued' || r.state === 'running').length;
69
+ }
70
+ /**
71
+ * After a stop: a recheck cut off part-way runs again, and a regression's request
72
+ * - kept only in memory until an apply takes it - goes back in the queue.
73
+ */
74
+ resume() {
75
+ const all = this.all();
76
+ let changed = false;
77
+ for (const r of all) {
78
+ if (r.state === 'running') {
79
+ r.state = 'queued';
80
+ changed = true;
81
+ }
82
+ if (r.state === 'regressed' &&
83
+ r.request &&
84
+ !this.#t.improvements.isApplied(r.request)) {
85
+ const f = this.#feedback(r);
86
+ if (f) {
87
+ this.#t.improvements.regressed(f);
88
+ }
89
+ }
90
+ }
91
+ if (changed) {
92
+ this.#save(all);
93
+ }
94
+ }
95
+ /**
96
+ * Queues completed cases to run on the version `apply` made: a case whose
97
+ * last recheck regressed first - this apply was meant to repair it - then
98
+ * the rest in an order fixed by the seed and the apply.
99
+ */
100
+ queue(apply) {
101
+ const t = this.#t;
102
+ if (t.config.recheck === 0) {
103
+ return [];
104
+ }
105
+ const all = this.all();
106
+ const open = new Set(all.filter((r) => r.state === 'queued' || r.state === 'running').map((r) => r.case));
107
+ const last = new Map(all.map((r) => [r.case, r]));
108
+ const key = (id) => createHash('sha256').update(`${t.config.seed}:${apply}:${id}`).digest('hex');
109
+ const rank = (id) => (last.get(id)?.state === 'regressed' ? 0 : 1);
110
+ const at = new Date().toISOString();
111
+ const picked = selection(t)
112
+ .filter((c) => !open.has(c.id) && resultOf(t, c)?.state === 'completed')
113
+ .sort((a, b) => rank(a.id) - rank(b.id) || key(a.id).localeCompare(key(b.id)))
114
+ .slice(0, t.config.recheck)
115
+ .map((c) => ({ case: c.id, rev: c.rev, apply, state: 'queued', at }));
116
+ if (picked.length > 0) {
117
+ this.#save([...all, ...picked]);
118
+ this.#page(apply);
119
+ t.event({
120
+ what: 'recheck',
121
+ detail: `${apply}: ${picked.map((r) => r.case).join(', ')} queued to run again on the edit`,
122
+ });
123
+ }
124
+ return picked;
125
+ }
126
+ /** The next queued recheck, marked running, with its case; one whose case changed is dropped. */
127
+ take() {
128
+ const t = this.#t;
129
+ for (;;) {
130
+ const r = this.all().find((x) => x.state === 'queued' && !t.claimed.has(x.case));
131
+ if (!r) {
132
+ return undefined;
133
+ }
134
+ const c = selection(t).find((x) => x.id === r.case);
135
+ if (!c || c.rev !== r.rev || resultOf(t, c)?.state !== 'completed') {
136
+ this.#update(r, { state: 'void', note: 'the case changed since it was queued' });
137
+ continue;
138
+ }
139
+ this.#update(r, { state: 'running' });
140
+ return { recheck: r, case: c };
141
+ }
142
+ }
143
+ /** A step broke for a reason outside the prose: the recheck says nothing. */
144
+ void(r, why) {
145
+ this.#update(r, { state: 'void', note: why });
146
+ }
147
+ /** Runs a recheck on the seat's worker, to its verdict. */
148
+ async run(s, r, c) {
149
+ const t = this.#t;
150
+ const tries = [];
151
+ let last;
152
+ for (let n = 1; n <= RECHECK_TRIES; n++) {
153
+ const dir = join(this.dir(r), `${pad(n)}-nomem`);
154
+ const one = await this.#try(s, r, c, n, dir);
155
+ tries.push(one.verdict);
156
+ if (one.passed) {
157
+ const state = one.verdict === 'right' ? 'passed' : 'void';
158
+ this.#update(r, { state, tries, ...(one.note ? { note: one.note } : {}) });
159
+ t.event({
160
+ what: `recheck ${state}`,
161
+ case: c.id,
162
+ rev: c.rev,
163
+ result: tries.join(', '),
164
+ });
165
+ return;
166
+ }
167
+ last = one.feedback;
168
+ }
169
+ const sent = this.all().filter((x) => x.case === c.id && x.request).length;
170
+ if (last && sent < REQUESTS_PER_CASE) {
171
+ const request = `${c.id}@recheck-${r.apply}`;
172
+ const f = {
173
+ ...last,
174
+ id: request,
175
+ recheck: r.apply,
176
+ dir: join(this.dir(r), `${pad(RECHECK_TRIES)}-nomem`),
177
+ };
178
+ this.#update(r, { state: 'regressed', tries, request });
179
+ t.improvements.regressed(f);
180
+ }
181
+ else {
182
+ this.#update(r, {
183
+ state: 'regressed',
184
+ tries,
185
+ note: `${sent} request(s) sent for this case already - reported, not queued`,
186
+ });
187
+ }
188
+ t.event({
189
+ what: 'regressed',
190
+ case: c.id,
191
+ rev: c.rev,
192
+ detail: `passed before ${r.apply}, failed ${RECHECK_TRIES} tries on it`,
193
+ result: tries.join(', '),
194
+ });
195
+ }
196
+ /** A void run or an unreadable analysis counts as a pass: only a graded failure is a regression. */
197
+ async #try(s, r, c, n, dir) {
198
+ const t = this.#t;
199
+ const w = s.worker;
200
+ mkdirSync(dir, { recursive: true });
201
+ w.recheck = { apply: r.apply, dir };
202
+ let why;
203
+ if (!existsSync(join(dir, 'run.json'))) {
204
+ await t.improvements.notApplying();
205
+ t.at(s, 'nomem', n, 'run');
206
+ t.improvements.runs++;
207
+ const began = Date.now();
208
+ try {
209
+ why = await runOnce(t, c, dir);
210
+ }
211
+ finally {
212
+ t.improvements.runs--;
213
+ }
214
+ const ran = why ? undefined : readJson(join(dir, 'run.json'));
215
+ t.ended(s, {
216
+ what: why ? 'void' : 'ran',
217
+ phase: 'nomem',
218
+ attempt: n,
219
+ result: `recheck: ${why ?? `${ran?.turns ?? '?'} turns`}`,
220
+ took: Date.now() - began,
221
+ tokens: spent(ran?.tokens),
222
+ });
223
+ }
224
+ if (why) {
225
+ return { verdict: 'void', passed: true, note: why };
226
+ }
227
+ const run = readJson(join(dir, 'run.json'));
228
+ const file = join(dir, 'feedback.json');
229
+ const known = { case: c.id, caseRev: c.rev, phase: 'nomem', attempt: n };
230
+ const rubricIds = c.rubric.map((x) => x.id);
231
+ let read = readFeedback(file, known, rubricIds);
232
+ if (!('feedback' in read)) {
233
+ t.checkStopping();
234
+ t.at(s, 'nomem', n, 'analyze');
235
+ const began = Date.now();
236
+ const patch = join(t.improvements.dir, r.apply, 'diff.patch');
237
+ const session = randomUUID();
238
+ writeFileSync(join(dir, 'session'), `${session}\n`);
239
+ const brief = await writeAnalyzeBrief(t, dir, {
240
+ run,
241
+ case: c,
242
+ phase: 'nomem',
243
+ attempt: n,
244
+ earlier: [],
245
+ recheck: patch,
246
+ });
247
+ const res = await t.zen([
248
+ 'meta',
249
+ 'run',
250
+ '--no-refresh',
251
+ '--json',
252
+ '--events',
253
+ join(dir, 'analyze.events.jsonl'),
254
+ '--session-id',
255
+ session,
256
+ '--prompt-file',
257
+ brief,
258
+ '/analyze',
259
+ run.dir,
260
+ `case=${c.id}`,
261
+ `rev=${c.rev}`,
262
+ 'phase=nomem',
263
+ `attempt=${n}`,
264
+ `feedback=${file}`,
265
+ `recheck=${patch}`,
266
+ ], join(dir, 'analyze.log'));
267
+ keepAnalysis(dir, res.stdout);
268
+ read = readFeedback(file, known, rubricIds);
269
+ t.ended(s, {
270
+ what: 'analyzed',
271
+ phase: 'nomem',
272
+ attempt: n,
273
+ result: `recheck: ${'feedback' in read ? read.feedback.verdict : read.problem}`,
274
+ took: Date.now() - began,
275
+ tokens: spent(envelopesIn(dir, 'analyze')),
276
+ });
277
+ }
278
+ if (!('feedback' in read)) {
279
+ return { verdict: 'unread', passed: true, note: read.problem };
280
+ }
281
+ const f = read.feedback;
282
+ if (f.verdict === 'void') {
283
+ return { verdict: 'void', passed: true, note: f.infra };
284
+ }
285
+ const passed = f.verdict === 'right' && !Object.values(f.rubric).includes('fail');
286
+ return { verdict: f.verdict, passed, feedback: f };
287
+ }
288
+ /** A regression's request, read back from its last try. */
289
+ #feedback(r) {
290
+ const c = selection(this.#t).find((x) => x.id === r.case);
291
+ const dir = join(this.dir(r), `${pad(RECHECK_TRIES)}-nomem`);
292
+ const read = readFeedback(join(dir, 'feedback.json'), { case: r.case, caseRev: r.rev, phase: 'nomem', attempt: RECHECK_TRIES }, c?.rubric.map((x) => x.id) ?? []);
293
+ return 'feedback' in read
294
+ ? { ...read.feedback, id: r.request, recheck: r.apply, dir }
295
+ : undefined;
296
+ }
297
+ /** Every recheck run and analysis so far. */
298
+ tokens() {
299
+ const parts = [];
300
+ for (const r of this.all()) {
301
+ for (const dir of this.tryDirs(r)) {
302
+ parts.push({
303
+ run: readJson(join(dir, 'run.json'))?.tokens,
304
+ analyze: envelopesIn(dir, 'analyze'),
305
+ });
306
+ }
307
+ }
308
+ return mergeStages(...parts);
309
+ }
310
+ }
311
+ //# sourceMappingURL=recheck.js.map
@@ -13,13 +13,18 @@ export interface Attempt {
13
13
  /** A table cell: one line, no column breaks. */
14
14
  export declare const cell: (text: string | number | undefined) => string;
15
15
  export declare function since(ms: number): string;
16
+ /** The analyze session of one try, kept in its folder. */
17
+ export declare function analyzeSession(dir: string): string | undefined;
16
18
  export declare function attemptsOf(t: Tuning, c: Case): Attempt[];
17
19
  /** Every try of a case, per stage and model. Applies serve many cases at once, so they are not split. */
18
20
  export declare function caseTokens(t: Tuning, c: Case): ByStage;
21
+ export declare const rubricScore: (f: Attempt["feedback"]) => string;
19
22
  export declare function feedbackPath(t: Tuning, c: {
20
23
  id: string;
21
24
  }): string;
22
25
  export declare function writeFeedback(t: Tuning, c: Case): void;
26
+ /** A fence longer than any run of backticks in `text`, so nothing inside can close it. */
27
+ export declare function fence(text: string): string;
23
28
  export declare function patchFiles(patch: string): {
24
29
  file: string;
25
30
  added: number;
@@ -28,6 +33,8 @@ export declare function patchFiles(patch: string): {
28
33
  }[];
29
34
  export declare function applyPath(dir: string): string;
30
35
  export declare function writeApply(t: Tuning, dir: string): void;
31
- export declare function writeStatus(t: Tuning): void;
32
- export declare function markdownPage(title: string, md: string, live: boolean): string;
36
+ /** A live line in running text: cut, and escaped. */
37
+ export declare const liveCell: (text: string | undefined) => string;
38
+ /** `live` reuses the parts read from every case and apply; only what moves by the second is read. */
39
+ export declare function writeStatus(t: Tuning, live?: boolean): void;
33
40
  //# sourceMappingURL=report.d.ts.map