@zenera/cli 1.1.24 → 1.1.26
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 +65 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +31 -1
- package/dist/catalog.js +140 -7
- package/dist/commands/index.js +2 -0
- package/dist/commands/inspect.d.ts +52 -2
- package/dist/commands/inspect.js +220 -31
- package/dist/commands/memory.js +156 -32
- package/dist/commands/meta.js +333 -24
- package/dist/commands/models.js +97 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +148 -16
- package/dist/commands/view.d.ts +8 -0
- package/dist/commands/view.js +114 -0
- package/dist/engine.d.ts +8 -1
- package/dist/engine.js +7 -0
- package/dist/eventlog.d.ts +64 -0
- package/dist/eventlog.js +170 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +35 -3
- package/dist/main.js +0 -0
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +43 -0
- package/dist/meta/finetune/analyze-brief.d.ts +26 -0
- package/dist/meta/finetune/analyze-brief.js +131 -0
- package/dist/meta/finetune/apply-brief.d.ts +23 -0
- package/dist/meta/finetune/apply-brief.js +247 -0
- package/dist/meta/finetune/brief.d.ts +45 -0
- package/dist/meta/finetune/brief.js +158 -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 +516 -0
- package/dist/meta/finetune/cost.d.ts +46 -0
- package/dist/meta/finetune/cost.js +105 -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 +127 -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 +57 -0
- package/dist/meta/finetune/feedback.js +104 -0
- package/dist/meta/finetune/guard.d.ts +29 -0
- package/dist/meta/finetune/guard.js +104 -0
- package/dist/meta/finetune/improvements.d.ts +98 -0
- package/dist/meta/finetune/improvements.js +501 -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 +84 -0
- package/dist/meta/finetune/live.js +272 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +163 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/recheck.d.ts +58 -0
- package/dist/meta/finetune/recheck.js +311 -0
- package/dist/meta/finetune/report.d.ts +40 -0
- package/dist/meta/finetune/report.js +1397 -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 +47 -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 +43 -0
- package/dist/meta/finetune/train.js +425 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +316 -0
- package/dist/meta/finetune/tuning.d.ts +193 -0
- package/dist/meta/finetune/tuning.js +308 -0
- package/dist/meta/finetune/usage.d.ts +66 -0
- package/dist/meta/finetune/usage.js +260 -0
- package/dist/meta/finetune/view.d.ts +9 -0
- package/dist/meta/finetune/view.js +27 -0
- package/dist/meta/host.d.ts +15 -0
- package/dist/meta/host.js +21 -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/meta.d.ts +71 -1
- package/dist/meta.js +303 -6
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/resources.d.ts +215 -0
- package/dist/resources.js +856 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -7
- package/dist/scaffold.js +24 -67
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.d.ts +5 -0
- package/dist/trace.js +107 -7
- package/dist/tui/app.js +19 -5
- package/dist/tui/links.d.ts +27 -0
- package/dist/tui/links.js +142 -0
- package/dist/tui/models.d.ts +35 -0
- package/dist/tui/models.js +447 -0
- package/dist/tui/sandbox.d.ts +10 -0
- package/dist/tui/sandbox.js +131 -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/package.json +4 -2
- package/templates/editor/.github/copilot-instructions.md +60 -145
- package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- 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 +5 -4
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
- 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 +1492 -660
- 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 +74 -26
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
- package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/fork-instructions.md +78 -28
- package/templates/project/agents/memory-instructions.md +222 -494
- package/templates/project/agents/tools-instructions.md +14 -4
- package/templates/project/gitignore +2 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { DatasetStore } from "./dataset/store.js";
|
|
4
|
+
import { errorIn, sizeOf } from "./failure.js";
|
|
5
|
+
import { writeFeedback } from "./report.js";
|
|
6
|
+
import { readJson } from "./tuning.js";
|
|
7
|
+
function rows(file) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(file, 'utf8')
|
|
10
|
+
.split('\n')
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.map((line) => JSON.parse(line));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class Memories {
|
|
19
|
+
#t;
|
|
20
|
+
#chain = Promise.resolve();
|
|
21
|
+
merging;
|
|
22
|
+
constructor(t) {
|
|
23
|
+
this.#t = t;
|
|
24
|
+
}
|
|
25
|
+
get dir() {
|
|
26
|
+
return join(this.#t.dir, 'memories');
|
|
27
|
+
}
|
|
28
|
+
submissions() {
|
|
29
|
+
return rows(join(this.dir, 'submissions.jsonl'));
|
|
30
|
+
}
|
|
31
|
+
merges() {
|
|
32
|
+
return rows(join(this.dir, 'merges.jsonl'));
|
|
33
|
+
}
|
|
34
|
+
lastMerge() {
|
|
35
|
+
return this.merges()
|
|
36
|
+
.filter((m) => m.ok)
|
|
37
|
+
.at(-1);
|
|
38
|
+
}
|
|
39
|
+
unmerged() {
|
|
40
|
+
const merged = new Set(this.lastMerge()?.includes ?? []);
|
|
41
|
+
return this.submissions().filter((s) => !s.empty && !merged.has(s.key));
|
|
42
|
+
}
|
|
43
|
+
/** Keeps a copy of a passing with-memory run's memory. Once per case revision. */
|
|
44
|
+
submit(c, attempt, run) {
|
|
45
|
+
const key = `${c.id}@r${c.rev}`;
|
|
46
|
+
if (this.submissions().some((s) => s.key === key)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const dir = join(this.dir, 'submitted', key);
|
|
50
|
+
const empty = !existsSync(join(run.memory, 'manifest.json'));
|
|
51
|
+
rmSync(dir, { recursive: true, force: true });
|
|
52
|
+
if (!empty) {
|
|
53
|
+
cpSync(run.memory, dir, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
filter: (src) => basename(src) !== '.lock',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const row = {
|
|
59
|
+
key,
|
|
60
|
+
case: c.id,
|
|
61
|
+
rev: c.rev,
|
|
62
|
+
attempt,
|
|
63
|
+
run: run.dir,
|
|
64
|
+
dir,
|
|
65
|
+
at: new Date().toISOString(),
|
|
66
|
+
empty,
|
|
67
|
+
};
|
|
68
|
+
mkdirSync(this.dir, { recursive: true });
|
|
69
|
+
appendFileSync(join(this.dir, 'submissions.jsonl'), `${JSON.stringify(row)}\n`);
|
|
70
|
+
this.#t.event({
|
|
71
|
+
what: 'memory submitted',
|
|
72
|
+
case: c.id,
|
|
73
|
+
rev: c.rev,
|
|
74
|
+
detail: empty ? 'nothing committed' : `${this.unmerged().length} waiting to merge`,
|
|
75
|
+
});
|
|
76
|
+
if (this.unmerged().length >= this.#t.config.mergeEvery) {
|
|
77
|
+
this.#queue();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Merges whatever is left, and waits for every merge queued. */
|
|
81
|
+
async finish() {
|
|
82
|
+
if (this.unmerged().length > 0) {
|
|
83
|
+
this.#queue();
|
|
84
|
+
}
|
|
85
|
+
await this.#chain;
|
|
86
|
+
}
|
|
87
|
+
#queue() {
|
|
88
|
+
this.#chain = this.#chain
|
|
89
|
+
.then(() => this.#merge())
|
|
90
|
+
.catch((err) => {
|
|
91
|
+
this.#t.error('memory merge', err.message);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async #merge() {
|
|
95
|
+
const fresh = this.unmerged();
|
|
96
|
+
if (fresh.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const n = this.merges().length + 1;
|
|
100
|
+
const name = `m${String(n).padStart(2, '0')}`;
|
|
101
|
+
const target = join(this.dir, 'merged', name);
|
|
102
|
+
const log = join(this.dir, 'merged', `${name}.log`);
|
|
103
|
+
await this.#t.journal.step({ kind: 'merge', dir: target, log }, () => this.#mergeInto(fresh, n, name, target, log));
|
|
104
|
+
}
|
|
105
|
+
async #mergeInto(fresh, n, name, target, log) {
|
|
106
|
+
const last = this.lastMerge();
|
|
107
|
+
rmSync(target, { recursive: true, force: true });
|
|
108
|
+
mkdirSync(join(this.dir, 'merged'), { recursive: true });
|
|
109
|
+
const began = Date.now();
|
|
110
|
+
const from = sizeOf(log);
|
|
111
|
+
this.merging = { name, since: began };
|
|
112
|
+
this.#t.event({ what: 'memory merge', detail: `${name}: ${fresh.length} new` });
|
|
113
|
+
const sources = [...(last ? [last.dir] : []), ...fresh.map((s) => s.dir)];
|
|
114
|
+
const res = await this.#t
|
|
115
|
+
.zen(['memory', 'merge', ...sources, '--dir', target, '--yes'], log)
|
|
116
|
+
.finally(() => {
|
|
117
|
+
this.merging = undefined;
|
|
118
|
+
});
|
|
119
|
+
const manifest = readJson(join(target, 'manifest.json'));
|
|
120
|
+
const row = {
|
|
121
|
+
n,
|
|
122
|
+
name,
|
|
123
|
+
at: new Date().toISOString(),
|
|
124
|
+
dir: target,
|
|
125
|
+
log,
|
|
126
|
+
includes: [...(last?.includes ?? []), ...fresh.map((s) => s.key)],
|
|
127
|
+
added: fresh.map((s) => s.key),
|
|
128
|
+
ok: res.code === 0 && manifest !== undefined,
|
|
129
|
+
...(manifest?.nodes !== undefined ? { nodes: manifest.nodes } : {}),
|
|
130
|
+
...(manifest?.edges !== undefined ? { edges: manifest.edges } : {}),
|
|
131
|
+
};
|
|
132
|
+
appendFileSync(join(this.dir, 'merges.jsonl'), `${JSON.stringify(row)}\n`);
|
|
133
|
+
if (!row.ok) {
|
|
134
|
+
const e = errorIn(log, from);
|
|
135
|
+
this.#t.error(`memory ${name}`, e.message ?? 'the merge wrote no manifest', e.log);
|
|
136
|
+
}
|
|
137
|
+
const store = DatasetStore.open(this.#t.root);
|
|
138
|
+
for (const s of fresh) {
|
|
139
|
+
const c = store.get(s.case);
|
|
140
|
+
if (c?.rev === s.rev) {
|
|
141
|
+
writeFeedback(this.#t, c);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.#t.event({
|
|
145
|
+
what: row.ok ? 'memory merged' : 'memory merge failed',
|
|
146
|
+
detail: `${name}: ${row.includes.length} case(s)${row.ok ? '' : ' - see the log'}`,
|
|
147
|
+
result: row.ok
|
|
148
|
+
? `${row.includes.length} case(s), ${row.nodes ?? '?'} nodes`
|
|
149
|
+
: 'see the log',
|
|
150
|
+
took: Date.now() - began,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=memories.js.map
|
|
@@ -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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Case } from './dataset/types.ts';
|
|
2
|
+
import { type Feedback, type Phase } from './feedback.ts';
|
|
3
|
+
import type { Run } from './train.ts';
|
|
4
|
+
import { type Tuning } from './tuning.ts';
|
|
5
|
+
import { type ByStage } from './usage.ts';
|
|
6
|
+
export interface Attempt {
|
|
7
|
+
phase: Phase;
|
|
8
|
+
attempt: number;
|
|
9
|
+
dir: string;
|
|
10
|
+
run?: Run;
|
|
11
|
+
feedback?: Feedback;
|
|
12
|
+
}
|
|
13
|
+
/** A table cell: one line, no column breaks. */
|
|
14
|
+
export declare const cell: (text: string | number | undefined) => string;
|
|
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;
|
|
18
|
+
export declare function attemptsOf(t: Tuning, c: Case): Attempt[];
|
|
19
|
+
/** Every try of a case, per stage and model. Applies serve many cases at once, so they are not split. */
|
|
20
|
+
export declare function caseTokens(t: Tuning, c: Case): ByStage;
|
|
21
|
+
export declare const rubricScore: (f: Attempt["feedback"]) => string;
|
|
22
|
+
export declare function feedbackPath(t: Tuning, c: {
|
|
23
|
+
id: string;
|
|
24
|
+
}): string;
|
|
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;
|
|
28
|
+
export declare function patchFiles(patch: string): {
|
|
29
|
+
file: string;
|
|
30
|
+
added: number;
|
|
31
|
+
removed: number;
|
|
32
|
+
text: string;
|
|
33
|
+
}[];
|
|
34
|
+
export declare function applyPath(dir: string): string;
|
|
35
|
+
export declare function writeApply(t: Tuning, dir: string): void;
|
|
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;
|
|
40
|
+
//# sourceMappingURL=report.d.ts.map
|