@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.
Files changed (199) hide show
  1. package/README.md +65 -0
  2. package/dist/ask.js +28 -5
  3. package/dist/banner.js +2 -2
  4. package/dist/batch.d.ts +6 -0
  5. package/dist/batch.js +76 -1
  6. package/dist/catalog.d.ts +31 -1
  7. package/dist/catalog.js +140 -7
  8. package/dist/commands/index.js +2 -0
  9. package/dist/commands/inspect.d.ts +52 -2
  10. package/dist/commands/inspect.js +220 -31
  11. package/dist/commands/memory.js +156 -32
  12. package/dist/commands/meta.js +333 -24
  13. package/dist/commands/models.js +97 -12
  14. package/dist/commands/open.js +4 -4
  15. package/dist/commands/run.js +38 -4
  16. package/dist/commands/sandbox.js +148 -16
  17. package/dist/commands/view.d.ts +8 -0
  18. package/dist/commands/view.js +114 -0
  19. package/dist/engine.d.ts +8 -1
  20. package/dist/engine.js +7 -0
  21. package/dist/eventlog.d.ts +64 -0
  22. package/dist/eventlog.js +170 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +35 -3
  25. package/dist/main.js +0 -0
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +43 -0
  28. package/dist/meta/finetune/analyze-brief.d.ts +26 -0
  29. package/dist/meta/finetune/analyze-brief.js +131 -0
  30. package/dist/meta/finetune/apply-brief.d.ts +23 -0
  31. package/dist/meta/finetune/apply-brief.js +247 -0
  32. package/dist/meta/finetune/brief.d.ts +45 -0
  33. package/dist/meta/finetune/brief.js +158 -0
  34. package/dist/meta/finetune/children.d.ts +8 -0
  35. package/dist/meta/finetune/children.js +76 -0
  36. package/dist/meta/finetune/command.d.ts +30 -0
  37. package/dist/meta/finetune/command.js +516 -0
  38. package/dist/meta/finetune/cost.d.ts +46 -0
  39. package/dist/meta/finetune/cost.js +105 -0
  40. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  41. package/dist/meta/finetune/dataset/anchors.js +239 -0
  42. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  43. package/dist/meta/finetune/dataset/changes.js +319 -0
  44. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  45. package/dist/meta/finetune/dataset/command.js +1011 -0
  46. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/drift.js +71 -0
  48. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  49. package/dist/meta/finetune/dataset/export.js +51 -0
  50. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  51. package/dist/meta/finetune/dataset/index.js +2 -0
  52. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  53. package/dist/meta/finetune/dataset/sample.js +197 -0
  54. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  55. package/dist/meta/finetune/dataset/schema.js +216 -0
  56. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  57. package/dist/meta/finetune/dataset/store.js +177 -0
  58. package/dist/meta/finetune/dataset/types.d.ts +127 -0
  59. package/dist/meta/finetune/dataset/types.js +39 -0
  60. package/dist/meta/finetune/failure.d.ts +15 -0
  61. package/dist/meta/finetune/failure.js +26 -0
  62. package/dist/meta/finetune/feedback.d.ts +57 -0
  63. package/dist/meta/finetune/feedback.js +104 -0
  64. package/dist/meta/finetune/guard.d.ts +29 -0
  65. package/dist/meta/finetune/guard.js +104 -0
  66. package/dist/meta/finetune/improvements.d.ts +98 -0
  67. package/dist/meta/finetune/improvements.js +501 -0
  68. package/dist/meta/finetune/index.d.ts +2 -0
  69. package/dist/meta/finetune/index.js +2 -0
  70. package/dist/meta/finetune/journal.d.ts +35 -0
  71. package/dist/meta/finetune/journal.js +135 -0
  72. package/dist/meta/finetune/live.d.ts +84 -0
  73. package/dist/meta/finetune/live.js +272 -0
  74. package/dist/meta/finetune/loop.d.ts +3 -0
  75. package/dist/meta/finetune/loop.js +163 -0
  76. package/dist/meta/finetune/memories.d.ts +47 -0
  77. package/dist/meta/finetune/memories.js +154 -0
  78. package/dist/meta/finetune/recheck.d.ts +58 -0
  79. package/dist/meta/finetune/recheck.js +311 -0
  80. package/dist/meta/finetune/report.d.ts +40 -0
  81. package/dist/meta/finetune/report.js +1397 -0
  82. package/dist/meta/finetune/retry.d.ts +24 -0
  83. package/dist/meta/finetune/retry.js +96 -0
  84. package/dist/meta/finetune/sampler.d.ts +18 -0
  85. package/dist/meta/finetune/sampler.js +47 -0
  86. package/dist/meta/finetune/system.d.ts +27 -0
  87. package/dist/meta/finetune/system.js +132 -0
  88. package/dist/meta/finetune/train.d.ts +43 -0
  89. package/dist/meta/finetune/train.js +425 -0
  90. package/dist/meta/finetune/tui.d.ts +16 -0
  91. package/dist/meta/finetune/tui.js +316 -0
  92. package/dist/meta/finetune/tuning.d.ts +193 -0
  93. package/dist/meta/finetune/tuning.js +308 -0
  94. package/dist/meta/finetune/usage.d.ts +66 -0
  95. package/dist/meta/finetune/usage.js +260 -0
  96. package/dist/meta/finetune/view.d.ts +9 -0
  97. package/dist/meta/finetune/view.js +27 -0
  98. package/dist/meta/host.d.ts +15 -0
  99. package/dist/meta/host.js +21 -0
  100. package/dist/meta/index.d.ts +11 -0
  101. package/dist/meta/index.js +11 -0
  102. package/dist/meta/launch.d.ts +81 -0
  103. package/dist/meta/launch.js +286 -0
  104. package/dist/meta/model.d.ts +44 -0
  105. package/dist/meta/model.js +119 -0
  106. package/dist/meta/prompts.d.ts +18 -0
  107. package/dist/meta/prompts.js +61 -0
  108. package/dist/meta/relay.d.ts +20 -0
  109. package/dist/meta/relay.js +123 -0
  110. package/dist/meta/tokens.d.ts +52 -0
  111. package/dist/meta/tokens.js +226 -0
  112. package/dist/meta/window.d.ts +61 -0
  113. package/dist/meta/window.js +198 -0
  114. package/dist/meta/wire.d.ts +42 -0
  115. package/dist/meta/wire.js +129 -0
  116. package/dist/meta.d.ts +71 -1
  117. package/dist/meta.js +303 -6
  118. package/dist/modelref.d.ts +16 -0
  119. package/dist/modelref.js +41 -0
  120. package/dist/narrate.js +5 -2
  121. package/dist/podman.d.ts +26 -0
  122. package/dist/podman.js +83 -3
  123. package/dist/projects.d.ts +0 -5
  124. package/dist/projects.js +2 -15
  125. package/dist/resources.d.ts +215 -0
  126. package/dist/resources.js +856 -0
  127. package/dist/sandbox.d.ts +2 -0
  128. package/dist/sandbox.js +10 -3
  129. package/dist/scaffold.d.ts +15 -7
  130. package/dist/scaffold.js +24 -67
  131. package/dist/session.js +4 -29
  132. package/dist/term.d.ts +2 -0
  133. package/dist/term.js +11 -2
  134. package/dist/trace.d.ts +5 -0
  135. package/dist/trace.js +107 -7
  136. package/dist/tui/app.js +19 -5
  137. package/dist/tui/links.d.ts +27 -0
  138. package/dist/tui/links.js +142 -0
  139. package/dist/tui/models.d.ts +35 -0
  140. package/dist/tui/models.js +447 -0
  141. package/dist/tui/sandbox.d.ts +10 -0
  142. package/dist/tui/sandbox.js +131 -0
  143. package/dist/tui/wrap.d.ts +5 -0
  144. package/dist/tui/wrap.js +12 -0
  145. package/dist/usage.d.ts +78 -0
  146. package/dist/usage.js +51 -0
  147. package/package.json +4 -2
  148. package/templates/editor/.github/copilot-instructions.md +60 -145
  149. package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
  150. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  151. package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
  152. package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
  153. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  154. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  155. package/templates/editor/.github/prompts/project-review.prompt.md +5 -4
  156. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
  157. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
  158. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  159. package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
  160. package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
  161. package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
  162. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  163. package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
  164. package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
  165. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  166. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
  167. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  168. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  169. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  170. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1492 -660
  171. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  172. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  173. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  174. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  175. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  176. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  177. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  178. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  179. package/templates/editor/.github/skills/zen-inspect/SKILL.md +74 -26
  180. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
  181. package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
  182. package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
  183. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  184. package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
  185. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
  186. package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
  187. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
  188. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
  189. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
  190. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  191. package/templates/editor/.vscode/settings.json +1 -0
  192. package/templates/project/agents/files-instructions.md +29 -20
  193. package/templates/project/agents/fork-instructions.md +78 -28
  194. package/templates/project/agents/memory-instructions.md +222 -494
  195. package/templates/project/agents/tools-instructions.md +14 -4
  196. package/templates/project/gitignore +2 -0
  197. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
  198. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
  199. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
@@ -0,0 +1,308 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { setTimeout as sleep } from 'node:timers/promises';
5
+ import { CliError, EXIT } from "../host.js";
6
+ import { forget, remember } from "./children.js";
7
+ import { Improvements } from "./improvements.js";
8
+ import { Journal } from "./journal.js";
9
+ import { Memories } from "./memories.js";
10
+ import { Rechecks } from "./recheck.js";
11
+ import { writeStatus } from "./report.js";
12
+ import { SystemVersions } from "./system.js";
13
+ // ---------------------------------------------------------------------------
14
+ // One tuning, in progress
15
+ //
16
+ // Everything the workers and the applier share: where things go, the settings,
17
+ // how `zen` is run, and what each worker is doing right now. Each case's own
18
+ // progress is not held here - it is on disk, one folder per attempt.
19
+ // ---------------------------------------------------------------------------
20
+ export const FINETUNE_DIR = 'finetune';
21
+ /** Where a tuning was kept before it moved to {@link FINETUNE_DIR}. */
22
+ export const LEGACY_FINETUNE_DIR = '.finetune';
23
+ export const DEFAULTS = {
24
+ workers: 4,
25
+ applyAt: 4,
26
+ tries: 4,
27
+ memTries: 3,
28
+ mergeEvery: 3,
29
+ seed: 1,
30
+ recheck: 3,
31
+ budget: 1500,
32
+ maxGrowth: 50,
33
+ skillCap: 12_000,
34
+ maxSkills: 10,
35
+ };
36
+ export class Stopped extends Error {
37
+ constructor() {
38
+ super('stopped');
39
+ }
40
+ }
41
+ const pad = (n) => String(n).padStart(2, '0');
42
+ /** Failed cases in a row, with nothing going through between, that stop the loop. */
43
+ export const FAILURES_IN_A_ROW = 3;
44
+ export class Tuning {
45
+ root;
46
+ dir;
47
+ config;
48
+ zen;
49
+ tickMs;
50
+ workers;
51
+ claimed = new Set();
52
+ startedAt = Date.now();
53
+ recent = [];
54
+ system;
55
+ journal;
56
+ improvements;
57
+ rechecks;
58
+ memories;
59
+ stopping = false;
60
+ finished = false;
61
+ fatal;
62
+ /** woken cases waiting for a worker: they go before any new case */
63
+ resuming = 0;
64
+ /** every case of the selection has been started */
65
+ drained = false;
66
+ /** cases started by this process: a failed one is not taken twice in one start */
67
+ tried = new Set();
68
+ /** `--more`: cases of any kind this start may take; not kept in loop.json */
69
+ more;
70
+ /** cases failed since the last step that went through */
71
+ failures = 0;
72
+ /** what the last status page counted */
73
+ progress;
74
+ /** called with every live snapshot */
75
+ watchers = new Set();
76
+ /** the latest live snapshot */
77
+ live;
78
+ /** the latest step failures, newest first */
79
+ errors = [];
80
+ constructor(root, config, zen, tickMs = 1000) {
81
+ this.root = root;
82
+ this.dir = join(root, FINETUNE_DIR);
83
+ this.config = config;
84
+ this.zen = zen;
85
+ this.tickMs = tickMs;
86
+ this.workers = Array.from({ length: config.workers }, (_, slot) => ({
87
+ slot: slot + 1,
88
+ step: 'idle',
89
+ since: Date.now(),
90
+ }));
91
+ this.system = new SystemVersions(root, join(this.dir, 'systems'));
92
+ this.journal = new Journal(join(this.dir, 'journal.jsonl'));
93
+ this.improvements = new Improvements(this);
94
+ this.rechecks = new Rechecks(this);
95
+ this.memories = new Memories(this);
96
+ }
97
+ get stopFile() {
98
+ return join(this.dir, 'stop');
99
+ }
100
+ async tick() {
101
+ await sleep(this.tickMs);
102
+ if (!this.stopping && existsSync(this.stopFile)) {
103
+ this.stop('stop requested');
104
+ }
105
+ }
106
+ stop(why) {
107
+ if (!this.stopping) {
108
+ this.stopping = true;
109
+ this.event({ what: 'stopping', detail: why });
110
+ }
111
+ }
112
+ /** Between steps: a stop takes effect here, never in the middle of one. */
113
+ checkStopping() {
114
+ if (this.stopping) {
115
+ throw new Stopped();
116
+ }
117
+ }
118
+ caseDir(c) {
119
+ return join(this.dir, 'cases', c.id, `r${c.rev}`);
120
+ }
121
+ /** Where `retry` puts a case's earlier rounds: `r<rev>.<round>`. */
122
+ roundsDir(c) {
123
+ return join(this.dir, 'cases', c.id, 'rounds');
124
+ }
125
+ /** 1 for a case never retried at this revision. */
126
+ round(c) {
127
+ try {
128
+ return (readdirSync(this.roundsDir(c)).filter((n) => n.startsWith(`r${c.rev}.`)).length + 1);
129
+ }
130
+ catch {
131
+ return 1;
132
+ }
133
+ }
134
+ /** A step went through: the sandbox and the providers are up. */
135
+ succeeded() {
136
+ this.failures = 0;
137
+ }
138
+ /** A case failed for a reason outside the prose. Enough in a row means nothing will go through. */
139
+ failed(why) {
140
+ this.failures++;
141
+ if (this.failures >= FAILURES_IN_A_ROW && !this.stopping) {
142
+ this.fatal = new CliError(`${this.failures} cases failed in a row - the last: ${why}`, EXIT.failed, 'fix what broke (the sandbox, a provider, a key), then start again: failed cases carry on where they stopped');
143
+ this.stop(`${this.failures} cases failed in a row`);
144
+ }
145
+ }
146
+ /** No run goes through without the container engine: stop at the first sign, not the third. */
147
+ sandboxDown(why) {
148
+ this.fatal ??= new CliError(`the sandbox failed: ${why}`, EXIT.sandbox, 'see what the engine says: zen sandbox status - fix it, then start again: failed cases carry on where they stopped');
149
+ this.stop('the sandbox failed');
150
+ }
151
+ attemptDir(c, phase, attempt) {
152
+ return join(this.caseDir(c), `${pad(attempt)}-${phase}`);
153
+ }
154
+ /** The folder of the try a worker is on: the case's own, or a recheck's. */
155
+ workDir(w) {
156
+ return w.recheck?.dir ?? this.attemptDir(w.case, w.phase ?? 'nomem', w.attempt ?? 1);
157
+ }
158
+ /** What the loop knows of a try, which a feedback file is never trusted for. */
159
+ known(c, phase, attempt) {
160
+ const round = this.round(c);
161
+ return { case: c.id, caseRev: c.rev, ...(round > 1 ? { round } : {}), phase, attempt };
162
+ }
163
+ freeWorker() {
164
+ return this.workers.find((w) => !w.case);
165
+ }
166
+ busy() {
167
+ return this.workers.filter((w) => w.case).length;
168
+ }
169
+ /** Puts a case on a free worker, waiting for one; a woken case goes before a new one. */
170
+ async seat(s, woken) {
171
+ if (woken) {
172
+ this.resuming++;
173
+ }
174
+ try {
175
+ for (;;) {
176
+ const w = this.freeWorker();
177
+ if (w && (woken || this.resuming === 0)) {
178
+ Object.assign(w, {
179
+ case: s.case,
180
+ phase: undefined,
181
+ attempt: undefined,
182
+ recheck: undefined,
183
+ step: 'idle',
184
+ since: Date.now(),
185
+ });
186
+ s.worker = w;
187
+ return;
188
+ }
189
+ this.checkStopping();
190
+ await this.tick();
191
+ }
192
+ }
193
+ finally {
194
+ if (woken) {
195
+ this.resuming--;
196
+ }
197
+ }
198
+ }
199
+ /** Gives the case's worker back, for another case to use. */
200
+ unseat(s) {
201
+ if (!s.worker) {
202
+ return;
203
+ }
204
+ Object.assign(s.worker, {
205
+ case: undefined,
206
+ phase: undefined,
207
+ attempt: undefined,
208
+ recheck: undefined,
209
+ step: 'idle',
210
+ since: Date.now(),
211
+ });
212
+ s.worker = undefined;
213
+ }
214
+ /** A case moved to another step on its worker. */
215
+ at(s, phase, attempt, step) {
216
+ const w = s.worker;
217
+ s.phase = phase;
218
+ Object.assign(w, { case: s.case, phase, attempt, step, since: Date.now() });
219
+ this.event({
220
+ what: step,
221
+ worker: w.slot,
222
+ case: s.case.id,
223
+ rev: s.case.rev,
224
+ phase,
225
+ attempt,
226
+ });
227
+ }
228
+ /** A step ended: the worker is between steps until the next `at`, and the event says how it went. */
229
+ ended(s, e) {
230
+ if (s.worker) {
231
+ Object.assign(s.worker, { step: 'idle', since: Date.now() });
232
+ }
233
+ this.event({ worker: s.worker?.slot, case: s.case.id, rev: s.case.rev, ...e });
234
+ }
235
+ /** A step failed: kept for the live view, and in events.jsonl with its log. */
236
+ error(where, message, log) {
237
+ this.errors.unshift({
238
+ at: new Date().toISOString(),
239
+ where,
240
+ message,
241
+ ...(log ? { log } : {}),
242
+ });
243
+ this.errors.length = Math.min(this.errors.length, 20);
244
+ this.event({ what: 'error', detail: `${where}: ${message}${log ? ` - see ${log}` : ''}` });
245
+ }
246
+ event(e) {
247
+ const row = { at: new Date().toISOString(), ...e };
248
+ mkdirSync(this.dir, { recursive: true });
249
+ appendFileSync(join(this.dir, 'events.jsonl'), `${JSON.stringify(row)}\n`);
250
+ this.recent.unshift(row);
251
+ this.recent.length = Math.min(this.recent.length, 50);
252
+ this.report();
253
+ }
254
+ /** `live`: only what moves by the second is read again; the rest comes from the last full report. */
255
+ report(live = false) {
256
+ try {
257
+ writeStatus(this, live);
258
+ }
259
+ catch {
260
+ // A status page that failed to render must not stop the tuning.
261
+ }
262
+ }
263
+ }
264
+ /** The real `zen`: this same binary, in its own process group so Ctrl-C reaches only us. */
265
+ export function spawnZen(root, children) {
266
+ return (args, log) => new Promise((resolve) => {
267
+ mkdirSync(dirname(log), { recursive: true });
268
+ const fd = openSync(log, 'a');
269
+ const child = spawn(process.execPath, [process.argv[1], ...args], {
270
+ cwd: root,
271
+ stdio: ['ignore', 'pipe', fd],
272
+ detached: true,
273
+ });
274
+ children.add(child);
275
+ const dir = join(root, FINETUNE_DIR);
276
+ if (child.pid !== undefined) {
277
+ remember(dir, child.pid, process.argv[1]);
278
+ }
279
+ let stdout = '';
280
+ child.stdout?.on('data', (chunk) => {
281
+ stdout += chunk.toString('utf8');
282
+ });
283
+ let finished = false;
284
+ const done = (code) => {
285
+ if (finished) {
286
+ return;
287
+ }
288
+ finished = true;
289
+ children.delete(child);
290
+ if (child.pid !== undefined) {
291
+ forget(dir, child.pid);
292
+ }
293
+ closeSync(fd);
294
+ resolve({ code, stdout });
295
+ };
296
+ child.on('error', () => done(1));
297
+ child.on('close', (code) => done(code ?? 1));
298
+ });
299
+ }
300
+ export function readJson(file) {
301
+ try {
302
+ return JSON.parse(readFileSync(file, 'utf8'));
303
+ }
304
+ catch {
305
+ return undefined;
306
+ }
307
+ }
308
+ //# sourceMappingURL=tuning.js.map
@@ -0,0 +1,66 @@
1
+ export interface Tokens {
2
+ calls: number;
3
+ input: number;
4
+ cached: number;
5
+ output: number;
6
+ reasoning: number;
7
+ }
8
+ export type ByModel = Record<string, Tokens>;
9
+ export type Stage = 'run' | 'analyze' | 'apply';
10
+ export declare const STAGES: Stage[];
11
+ export type ByStage = Partial<Record<Stage, ByModel>>;
12
+ export declare function mergeModels(...all: (ByModel | undefined)[]): ByModel;
13
+ export declare function mergeStages(...all: (ByStage | undefined)[]): ByStage;
14
+ export declare function total(by: ByModel | undefined): Tokens;
15
+ /** Input plus output, the input counting cached tokens. */
16
+ export declare function spent(by: ByModel | undefined): number;
17
+ /** `vertex/gemini-3.8-flash` and `gemini-3.8-flash` are one model in a table. */
18
+ export declare const modelName: (m: string | undefined) => string;
19
+ export interface ModelMetrics extends Tokens {
20
+ /** the largest input of a single call */
21
+ peak: number;
22
+ /** time spent waiting on this model */
23
+ ms: number;
24
+ }
25
+ export interface RunMetrics {
26
+ models: Record<string, ModelMetrics>;
27
+ /** summed over every lane, so parallel branches can exceed the wall clock */
28
+ llmMs: number;
29
+ toolMs: number;
30
+ toolCalls: number;
31
+ toolErrors: number;
32
+ memory: {
33
+ recall: number;
34
+ load: number;
35
+ grep: number;
36
+ commit: number;
37
+ forget: number;
38
+ };
39
+ forks: number;
40
+ branches: number;
41
+ compactions: number;
42
+ handoffs: number;
43
+ /** skills loaded, preloads included, in the order first loaded */
44
+ skills: string[];
45
+ /** the first error a sandbox tool got from the container engine, not from a command */
46
+ sandbox?: string;
47
+ }
48
+ /**
49
+ * One walk of a run's `state.json`, branches included. Nested records repeat a
50
+ * node, so nodes are counted by id. A node is stamped when the work behind it
51
+ * finished, so the gap to the node before it in the same lane is that work -
52
+ * the same timing as the Stats tab of report.html.
53
+ */
54
+ export declare function runMetrics(runDir: string): RunMetrics | undefined;
55
+ /** The token part of a run's metrics, per model. */
56
+ export declare function tokensOf(m: RunMetrics): ByModel;
57
+ export declare function runTokens(runDir: string): ByModel | undefined;
58
+ /** One `zen meta run --json` envelope: `{ model, tokens: { calls, inputTokens, … } }`. */
59
+ export declare function envelopeTokens(file: string): ByModel | undefined;
60
+ /** Every envelope in `dir` named `<stem>.json` or `<stem>-<n>.json` - retries keep their own. */
61
+ export declare function envelopesIn(dir: string, stem: string): ByModel | undefined;
62
+ /** An apply's own envelopes, and - for applies from before rechecks - its guard's runs: `guard/<attempt>/<case>/<try>/`. */
63
+ export declare function applyTokens(dir: string): ByModel | undefined;
64
+ /** 1234 → 1.2k, 2184341 → 2.2M. */
65
+ export declare function short(n: number): string;
66
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1,260 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const STAGES = ['run', 'analyze', 'apply'];
4
+ const zero = () => ({ calls: 0, input: 0, cached: 0, output: 0, reasoning: 0 });
5
+ function addTo(into, model, t) {
6
+ const m = (into[model] ??= zero());
7
+ m.calls += t.calls;
8
+ m.input += t.input;
9
+ m.cached += t.cached;
10
+ m.output += t.output;
11
+ m.reasoning += t.reasoning;
12
+ }
13
+ export function mergeModels(...all) {
14
+ const out = {};
15
+ for (const by of all) {
16
+ for (const [model, t] of Object.entries(by ?? {})) {
17
+ addTo(out, model, t);
18
+ }
19
+ }
20
+ return out;
21
+ }
22
+ export function mergeStages(...all) {
23
+ const out = {};
24
+ for (const stage of STAGES) {
25
+ const parts = all.map((s) => s?.[stage]).filter((p) => !!p);
26
+ if (parts.length > 0) {
27
+ out[stage] = mergeModels(...parts);
28
+ }
29
+ }
30
+ return out;
31
+ }
32
+ export function total(by) {
33
+ const sum = {};
34
+ for (const t of Object.values(by ?? {})) {
35
+ addTo(sum, 'all', t);
36
+ }
37
+ return sum.all ?? zero();
38
+ }
39
+ /** Input plus output, the input counting cached tokens. */
40
+ export function spent(by) {
41
+ const s = total(by);
42
+ return s.input + s.output;
43
+ }
44
+ const fromUsage = (u, calls) => ({
45
+ calls,
46
+ input: u.inputTokens ?? 0,
47
+ cached: u.cachedInputTokens ?? 0,
48
+ output: u.outputTokens ?? 0,
49
+ reasoning: u.reasoningTokens ?? 0,
50
+ });
51
+ /** `vertex/gemini-3.8-flash` and `gemini-3.8-flash` are one model in a table. */
52
+ export const modelName = (m) => m ? m.replace(/^[^/:]+[/:]/, '') : '?';
53
+ /** `SandboxError`s the engine causes; a path outside the workspace is the model's own doing. */
54
+ const SANDBOX_DOWN = /could not (create|start) container|the sandbox container \S+ stopped|could not (write|start) the job|is not installed, or not on PATH|exceeded num_locks|cannot connect to podman/i;
55
+ const cache = new Map();
56
+ /**
57
+ * One walk of a run's `state.json`, branches included. Nested records repeat a
58
+ * node, so nodes are counted by id. A node is stamped when the work behind it
59
+ * finished, so the gap to the node before it in the same lane is that work -
60
+ * the same timing as the Stats tab of report.html.
61
+ */
62
+ export function runMetrics(runDir) {
63
+ const file = join(runDir, 'state.json');
64
+ if (!existsSync(file)) {
65
+ return undefined;
66
+ }
67
+ const st = statSync(file);
68
+ const key = `${st.mtimeMs}:${st.size}`;
69
+ const hit = cache.get(file);
70
+ if (hit?.key === key) {
71
+ return hit.metrics;
72
+ }
73
+ let root;
74
+ try {
75
+ root = JSON.parse(readFileSync(file, 'utf8'));
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ const m = {
81
+ models: {},
82
+ llmMs: 0,
83
+ toolMs: 0,
84
+ toolCalls: 0,
85
+ toolErrors: 0,
86
+ memory: { recall: 0, load: 0, grep: 0, commit: 0, forget: 0 },
87
+ forks: 0,
88
+ branches: 0,
89
+ compactions: 0,
90
+ handoffs: 0,
91
+ skills: [],
92
+ };
93
+ const seen = new Set();
94
+ const count = (o, ms) => {
95
+ switch (o.type) {
96
+ case 'llm_call':
97
+ case 'compaction': {
98
+ if (!o.usage) {
99
+ return;
100
+ }
101
+ const model = o.type === 'compaction' ? 'summarizer' : modelName(o.model);
102
+ const t = fromUsage(o.usage, 1);
103
+ const into = (m.models[model] ??= { ...zero(), peak: 0, ms: 0 });
104
+ into.calls += 1;
105
+ into.input += t.input;
106
+ into.cached += t.cached;
107
+ into.output += t.output;
108
+ into.reasoning += t.reasoning;
109
+ into.peak = Math.max(into.peak, t.input);
110
+ into.ms += ms;
111
+ m.llmMs += ms;
112
+ if (o.type === 'compaction') {
113
+ m.compactions++;
114
+ }
115
+ return;
116
+ }
117
+ case 'tool_result': {
118
+ m.toolCalls++;
119
+ m.toolErrors += o.isError ? 1 : 0;
120
+ m.toolMs += typeof o.durationMs === 'number' ? o.durationMs : ms;
121
+ const said = o.result?.preview;
122
+ if (!m.sandbox && typeof said === 'string' && SANDBOX_DOWN.test(said)) {
123
+ m.sandbox = /"error":\s*"([^"]+)/.exec(said)?.[1] ?? said;
124
+ }
125
+ return;
126
+ }
127
+ case 'memory_recall':
128
+ m.memory.recall++;
129
+ return;
130
+ case 'memory_op': {
131
+ const op = o.op;
132
+ if (op in m.memory) {
133
+ m.memory[op]++;
134
+ }
135
+ return;
136
+ }
137
+ case 'fork':
138
+ m.forks++;
139
+ m.branches += Array.isArray(o.branches) ? o.branches.length : 0;
140
+ return;
141
+ case 'handoff':
142
+ m.handoffs++;
143
+ return;
144
+ case 'load_skills':
145
+ for (const s of Array.isArray(o.skills) ? o.skills : []) {
146
+ const name = s?.name;
147
+ if (typeof name === 'string' && !m.skills.includes(name)) {
148
+ m.skills.push(name);
149
+ }
150
+ }
151
+ return;
152
+ }
153
+ };
154
+ // A branch lane starts where its parent lane was when the fork began.
155
+ const walk = (v, lanePrev) => {
156
+ if (Array.isArray(v)) {
157
+ let prev = lanePrev;
158
+ for (const item of v) {
159
+ const o = item;
160
+ const ts = o && typeof o.ts === 'string' ? Date.parse(o.ts) : NaN;
161
+ if (o &&
162
+ typeof o.type === 'string' &&
163
+ typeof o.id === 'string' &&
164
+ !seen.has(o.id)) {
165
+ seen.add(o.id);
166
+ count(o, prev !== undefined && !Number.isNaN(ts) ? Math.max(0, ts - prev) : 0);
167
+ }
168
+ walk(item, prev);
169
+ if (!Number.isNaN(ts)) {
170
+ prev = ts;
171
+ }
172
+ }
173
+ }
174
+ else if (v && typeof v === 'object') {
175
+ for (const x of Object.values(v)) {
176
+ walk(x, lanePrev);
177
+ }
178
+ }
179
+ };
180
+ walk(root, undefined);
181
+ cache.set(file, { key, metrics: m });
182
+ return m;
183
+ }
184
+ /** The token part of a run's metrics, per model. */
185
+ export function tokensOf(m) {
186
+ return Object.fromEntries(Object.entries(m.models).map(([model, { calls, input, cached, output, reasoning }]) => [
187
+ model,
188
+ { calls, input, cached, output, reasoning },
189
+ ]));
190
+ }
191
+ export function runTokens(runDir) {
192
+ const m = runMetrics(runDir);
193
+ return m ? tokensOf(m) : undefined;
194
+ }
195
+ /** One `zen meta run --json` envelope: `{ model, tokens: { calls, inputTokens, … } }`. */
196
+ export function envelopeTokens(file) {
197
+ if (!existsSync(file)) {
198
+ return undefined;
199
+ }
200
+ let env;
201
+ try {
202
+ env = JSON.parse(readFileSync(file, 'utf8'));
203
+ }
204
+ catch {
205
+ return undefined;
206
+ }
207
+ if (!env.tokens) {
208
+ return undefined;
209
+ }
210
+ return { [modelName(env.model)]: fromUsage(env.tokens, env.tokens.calls ?? 0) };
211
+ }
212
+ /** Every envelope in `dir` named `<stem>.json` or `<stem>-<n>.json` - retries keep their own. */
213
+ export function envelopesIn(dir, stem) {
214
+ let names = [];
215
+ try {
216
+ names = readdirSync(dir).filter((n) => new RegExp(`^${stem}(-\\d+)?\\.json$`).test(n));
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ const parts = names.map((n) => envelopeTokens(join(dir, n))).filter((p) => !!p);
222
+ return parts.length > 0 ? mergeModels(...parts) : undefined;
223
+ }
224
+ /** An apply's own envelopes, and - for applies from before rechecks - its guard's runs: `guard/<attempt>/<case>/<try>/`. */
225
+ export function applyTokens(dir) {
226
+ const parts = [envelopesIn(dir, 'apply')];
227
+ const under = (d) => {
228
+ try {
229
+ return readdirSync(d, { withFileTypes: true })
230
+ .filter((e) => e.isDirectory())
231
+ .map((e) => join(d, e.name));
232
+ }
233
+ catch {
234
+ return [];
235
+ }
236
+ };
237
+ for (const tryDir of under(join(dir, 'guard')).flatMap(under).flatMap(under)) {
238
+ parts.push(envelopesIn(tryDir, 'analyze'));
239
+ try {
240
+ const run = JSON.parse(readFileSync(join(tryDir, 'run.json'), 'utf8'));
241
+ parts.push(run.tokens);
242
+ }
243
+ catch {
244
+ // A void run wrote none.
245
+ }
246
+ }
247
+ const found = parts.filter((p) => !!p);
248
+ return found.length > 0 ? mergeModels(...found) : undefined;
249
+ }
250
+ /** 1234 → 1.2k, 2184341 → 2.2M. */
251
+ export function short(n) {
252
+ if (n >= 1_000_000) {
253
+ return `${(n / 1_000_000).toFixed(1)}M`;
254
+ }
255
+ if (n >= 1000) {
256
+ return `${Math.round(n / 1000)}k`;
257
+ }
258
+ return String(n);
259
+ }
260
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1,9 @@
1
+ export interface Viewing {
2
+ url: string;
3
+ close(): Promise<void>;
4
+ }
5
+ /** The address another live process serves the tuning on, if one does. */
6
+ export declare function liveView(projectDir: string): string | undefined;
7
+ /** Serves the project, opening at STATUS.md, until `close`. */
8
+ export declare function startView(projectDir: string): Promise<Viewing>;
9
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { liveHolder, ownLock } from '@zenera/neo';
2
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { FINETUNE_DIR } from "./tuning.js";
5
+ const lockFile = (projectDir) => join(projectDir, FINETUNE_DIR, 'view.json');
6
+ /** The address another live process serves the tuning on, if one does. */
7
+ export function liveView(projectDir) {
8
+ return liveHolder(lockFile(projectDir))?.url;
9
+ }
10
+ /** Serves the project, opening at STATUS.md, until `close`. */
11
+ export async function startView(projectDir) {
12
+ const { serveView } = await import('@zenera/view');
13
+ const server = await serveView({ root: projectDir, start: `/${FINETUNE_DIR}/STATUS.md` });
14
+ const file = lockFile(projectDir);
15
+ mkdirSync(dirname(file), { recursive: true });
16
+ writeFileSync(file, `${JSON.stringify(ownLock({ url: server.url }), null, 2)}\n`);
17
+ return {
18
+ url: server.url,
19
+ close: async () => {
20
+ if (liveHolder(file)?.pid === process.pid) {
21
+ rmSync(file, { force: true });
22
+ }
23
+ await server.close();
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1,15 @@
1
+ export { invokedAs, parse } from '../args.ts';
2
+ export type { EventLine } from '../eventlog.ts';
3
+ export { paths, readJson, writeJson } from '../home.ts';
4
+ export { envOf, type KeyEntry, type KeyStore, type Provider } from '../keys.ts';
5
+ export { PROVIDER_NAMES, splitRef } from '../modelref.ts';
6
+ export { duration } from '../narrate.ts';
7
+ export { copyTree, SHARED_RULES, TEMPLATES } from '../scaffold.ts';
8
+ export { bold, CliError, confirm, credentialError, cut, cyan, dim, EXIT, green, invalidError, json, note, pad, plain, red, styled, table, usageError, write, writeAll, yellow, } from '../term.ts';
9
+ export { safe } from '../trace.ts';
10
+ export { formatInline, formatMarkdown } from '../tui/markdown.ts';
11
+ export { resolveTheme, type Theme } from '../tui/theme.ts';
12
+ export { boxWidth } from '../tui/wrap.ts';
13
+ export { META_PROMPT_ENV, META_SESSION_ENV } from '../usage.ts';
14
+ export { validateProject, type Report as ProjectReport } from '../validate.ts';
15
+ //# sourceMappingURL=host.d.ts.map