@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,76 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { setTimeout as sleep } from 'node:timers/promises';
5
+ const folder = (tuningDir) => join(tuningDir, 'children');
6
+ export function remember(tuningDir, pid, marker) {
7
+ mkdirSync(folder(tuningDir), { recursive: true });
8
+ const row = { pid, marker, at: new Date().toISOString() };
9
+ writeFileSync(join(folder(tuningDir), `${pid}.json`), `${JSON.stringify(row)}\n`);
10
+ }
11
+ export function forget(tuningDir, pid) {
12
+ rmSync(join(folder(tuningDir), `${pid}.json`), { force: true });
13
+ }
14
+ const alive = (pid) => {
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch (err) {
20
+ return err.code === 'EPERM';
21
+ }
22
+ };
23
+ const commandOf = (pid) => spawnSync('ps', ['-o', 'command=', '-p', String(pid)], { encoding: 'utf8' }).stdout ?? '';
24
+ const signal = (pid, sig) => {
25
+ try {
26
+ process.kill(-pid, sig);
27
+ }
28
+ catch {
29
+ try {
30
+ process.kill(pid, sig);
31
+ }
32
+ catch {
33
+ // Gone already.
34
+ }
35
+ }
36
+ };
37
+ /**
38
+ * Stops every child a dead loop left running, and waits until they are gone.
39
+ * Only under the loop's lock: a live loop's children look the same.
40
+ */
41
+ export async function reap(tuningDir, graceMs = 10_000) {
42
+ let names = [];
43
+ try {
44
+ names = readdirSync(folder(tuningDir)).filter((n) => n.endsWith('.json'));
45
+ }
46
+ catch {
47
+ return [];
48
+ }
49
+ const left = [];
50
+ for (const name of names) {
51
+ let child;
52
+ try {
53
+ child = JSON.parse(readFileSync(join(folder(tuningDir), name), 'utf8'));
54
+ }
55
+ catch {
56
+ // A row torn by the kill itself.
57
+ }
58
+ if (child && alive(child.pid) && commandOf(child.pid).includes(child.marker)) {
59
+ signal(child.pid, 'SIGTERM');
60
+ left.push(child.pid);
61
+ }
62
+ }
63
+ const until = Date.now() + graceMs;
64
+ while (left.some(alive) && Date.now() < until) {
65
+ await sleep(100);
66
+ }
67
+ for (const pid of left.filter(alive)) {
68
+ signal(pid, 'SIGKILL');
69
+ }
70
+ while (left.some(alive) && Date.now() < until + 2000) {
71
+ await sleep(50);
72
+ }
73
+ rmSync(folder(tuningDir), { recursive: true, force: true });
74
+ return left;
75
+ }
76
+ //# sourceMappingURL=children.js.map
@@ -0,0 +1,30 @@
1
+ import { type Config, type Zen } from './tuning.ts';
2
+ export declare const FINETUNE_USAGE = "zen meta finetune <start|status|stop|serve|retry|session|gate> [options]";
3
+ export declare const FINETUNE_HELP: string[];
4
+ export declare const FINETUNE_SUMMARY = "Tune the project's prose on its dataset, a case at a time, N cases at once.";
5
+ /** The page `zen meta finetune --help` prints. */
6
+ export declare const FINETUNE_DETAILS: string[];
7
+ export interface FinetuneContext {
8
+ readonly args: readonly string[];
9
+ readonly json: boolean;
10
+ readonly cwd: string;
11
+ }
12
+ export declare function finetuneProjectFlag(args: readonly string[]): string | undefined;
13
+ export interface FinetuneOptions {
14
+ /** stands in for the `zen` binary - tests */
15
+ zen?: Zen;
16
+ tickMs?: number;
17
+ }
18
+ export declare function runFinetune(ctx: FinetuneContext, project: {
19
+ dir: string;
20
+ name: string;
21
+ }, options?: FinetuneOptions): Promise<void>;
22
+ /**
23
+ * A tuning used to live in `.finetune/`. Moves it to `finetune/` once, so a
24
+ * project tuned before the rename resumes where it stopped. Refuses while a
25
+ * loop of the old version still holds its lock there: renaming its folder out
26
+ * from under it would split one tuning across two directories.
27
+ */
28
+ export declare function moveLegacyDir(projectDir: string): void;
29
+ export declare function readConfig(dir: string): Config;
30
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1,516 @@
1
+ import { liveHolder } from '@zenera/neo';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { editorFiles } from "../editor.js";
6
+ import { bold, CliError, cyan, dim, EXIT, green, invalidError, json, note, parse, red, usageError, write, yellow, } from "../host.js";
7
+ import { costChanges, measure, overLimits } from "./cost.js";
8
+ import { drift } from "./dataset/drift.js";
9
+ import { DatasetStore } from "./dataset/store.js";
10
+ import { runLoop } from "./loop.js";
11
+ import { analyzeSession, attemptsOf } from "./report.js";
12
+ import { retry } from "./retry.js";
13
+ import { resultOf, selection } from "./sampler.js";
14
+ import { DEFAULTS, FAILURES_IN_A_ROW, FINETUNE_DIR, LEGACY_FINETUNE_DIR, readJson, spawnZen, Tuning, } from "./tuning.js";
15
+ import { liveView, startView } from "./view.js";
16
+ // ---------------------------------------------------------------------------
17
+ // zen meta finetune
18
+ // ---------------------------------------------------------------------------
19
+ export const FINETUNE_USAGE = 'zen meta finetune <start|status|stop|serve|retry|session|gate> [options]';
20
+ export const FINETUNE_HELP = [
21
+ 'Tuning:',
22
+ ' zen meta finetune start [options] train every case: run, analyze, apply, run again',
23
+ ' zen meta finetune [status] where it stands; the detail is finetune/STATUS.md',
24
+ ' zen meta finetune stop finish the steps in flight, then stop',
25
+ ' zen meta finetune serve the tuning live in a browser, while it is not running',
26
+ ' zen meta finetune retry --difficult give difficult cases another round, then start',
27
+ " zen meta finetune session <case> resume the analyze session of that case's latest try",
28
+ ' zen meta finetune gate <apply dir> would this apply pass? - for the apply agent',
29
+ '',
30
+ ' start: -N <workers> (4), -M <apply at> (4), --tries <n> (4), --mem-tries <n> (3),',
31
+ ' --class <c>, --id <glob>, --rubric yes|no, --limit <n> and --more <n> (this start only);',
32
+ ' what an apply may do: --budget <bytes> (1500), --max-growth <pct> (50),',
33
+ ' --skill-cap <bytes> (12000), --max-skills <n> (10); after it, --recheck <n> (3).',
34
+ ' More - --merge-every, --seed, --force: zen meta finetune --help.',
35
+ ' In a terminal start draws what runs now, the tokens and the log; --plain does not.',
36
+ ' Settings are kept in finetune/loop.json; start again to resume where it stopped.',
37
+ ];
38
+ export const FINETUNE_SUMMARY = "Tune the project's prose on its dataset, a case at a time, N cases at once.";
39
+ /** The page `zen meta finetune --help` prints. */
40
+ export const FINETUNE_DETAILS = [
41
+ 'Verbs:',
42
+ ' start Train every case: run, analyze, apply, run again. Resumes.',
43
+ ' status Where it stands (the default). The detail: finetune/STATUS.md',
44
+ ' stop Let the steps in flight finish, then stop the running tuning.',
45
+ ' serve Show the tuning live in a browser until Ctrl-C. start does this',
46
+ ' itself, and prints the address; serve is for when it is not running.',
47
+ ' retry [ids...] Another round for difficult cases: --difficult takes them all,',
48
+ ' ids and globs add to it, --class narrows. Run them with start.',
49
+ " session <case> Print the command that resumes the analyze session of that case's",
50
+ ' latest try.',
51
+ ' gate <apply dir> Judge an apply in progress as the loop will: bytes moved per',
52
+ ' agent, skill and file, any limit broken, and zen check.',
53
+ ' Exits 1 when it would be undone. Read-only.',
54
+ '',
55
+ 'Which cases (start):',
56
+ ' --class <c> Only this class. Repeatable.',
57
+ ' --id <glob> Only cases whose id matches. Repeatable.',
58
+ ' --rubric yes|no Only cases with, or without, a rubric.',
59
+ ' --limit <n> Only the first n cases of the order. Not kept.',
60
+ ' --more <n> Take at most n cases - new, part-way or failed - then stop.',
61
+ ' Parked cases it took still finish. Not kept.',
62
+ '',
63
+ 'How it runs (start):',
64
+ ' -N, --workers <n> Cases trained at once. Default 4.',
65
+ ' -M, --apply-at <n> Apply once this many cases wait for a fix. Default 4, min 2.',
66
+ ' --tries <n> Tries without memory before a case is difficult. Default 4.',
67
+ ' --mem-tries <n> Tries with memory. Default 3.',
68
+ ' --merge-every <k> Merge the kept memories after k more arrive. Default 3.',
69
+ ' --seed <n> Order of cases inside a class. Default 1.',
70
+ '',
71
+ 'What an apply may do (start):',
72
+ ' --budget <bytes> Most one apply may add to what every call reads. Default 1500.',
73
+ ' --max-growth <pct> Once that is this much over its size when the tuning began,',
74
+ ' an apply may add nothing. Default 50.',
75
+ ' --skill-cap <bytes> Most one skill may hold. Default 12000.',
76
+ ' --max-skills <n> Most skills one agent may see. Default 10.',
77
+ ' --recheck <n> After each apply, run this many completed cases again on it;',
78
+ ' one that fails both tries sends its fixes to the next apply.',
79
+ ' Default 3, 0 none.',
80
+ '',
81
+ 'Other (start):',
82
+ ' --force Start past dataset drift, and allow -M 1.',
83
+ ' --plain No live view: print a line per start and stop, as in a pipe.',
84
+ ' --project <name|dir> Which project. Default: the one you are in.',
85
+ '',
86
+ 'One case: without memory, run it and /analyze it until the analysis says done -',
87
+ 'parking it until the next apply after each try that is not - then the same with',
88
+ 'the memory the passing run wrote. Tries used up: the case is difficult. Each try',
89
+ 'has its own private memory; passing memories are merged into finetune/memories/.',
90
+ '',
91
+ 'A parked case gives its worker back; woken cases take a worker before new ones.',
92
+ '/finetune-apply fixes the parked cases together: when -M wait, or once nothing',
93
+ 'else can run. No run starts during an apply. What it may do:',
94
+ ' - Every call reads the house rules, the prompt, preloaded skills and one line per',
95
+ ' skill, so that is what --budget counts. A skill body is paid only once loaded:',
96
+ ' moving a procedure from a prompt into a skill is a saving.',
97
+ ' - One case is an anecdote. A new rule needs two cases asking for it; one is',
98
+ ' deferred and offered to every later apply until a second asks.',
99
+ ' - A skill made for a case must be loaded by its next run, or the next apply',
100
+ ' fixes the description.',
101
+ ' - It tidies up after itself: the files it touched lose their duplicates and',
102
+ ' contradictions in the same edit.',
103
+ ' - zen check must pass and the budget hold, or the edit is undone and tried once',
104
+ ' more. A second zen check failure stops the tuning; a second budget failure only',
105
+ ' undoes the apply.',
106
+ 'Every apply is a new version of agents.yaml + agents/. Then --recheck completed',
107
+ 'cases run again on it, as ordinary work - nothing waits. One that fails both tries',
108
+ 'has regressed: its fixes join the queue, and the next apply repairs what broke.',
109
+ '',
110
+ 'A case is difficult when its tries run out and the analysis still says no, or at',
111
+ 'once when all an apply could say of it is structural - a tool, a skill binding,',
112
+ 'the dataset. That is final for its revision until `retry` gives it another round.',
113
+ 'A case fails instead when something outside the prose broke - the sandbox, a',
114
+ 'provider, a key: it keeps its tries and carries on from that step at the next start.',
115
+ `After ${FAILURES_IN_A_ROW} failures in a row the tuning stops, and says what broke;`,
116
+ 'a container engine that fails stops it at once. A run its analysis finds void -',
117
+ 'the network or a service under it down - is set aside and runs again.',
118
+ '',
119
+ 'Ctrl-C (or stop) finishes the steps in flight; Ctrl-C again kills them. Start',
120
+ 'again to carry on: every step is kept on disk, and nothing finished is repeated.',
121
+ 'Settings given to start are kept in finetune/loop.json.',
122
+ '',
123
+ 'Examples:',
124
+ ' zen meta finetune start -N 4',
125
+ ' zen meta finetune start --class search --limit 6 -N 2 -M 2',
126
+ ' zen meta finetune start --limit 4 --recheck 0 # quick, cheap',
127
+ ' zen meta finetune start --more 4',
128
+ ' zen meta finetune',
129
+ ' zen meta finetune retry --difficult --class merge',
130
+ ' zen meta finetune session plan-day',
131
+ ];
132
+ const OPTIONS = {
133
+ project: { type: 'string' },
134
+ workers: { type: 'string', short: 'N' },
135
+ 'apply-at': { type: 'string', short: 'M' },
136
+ tries: { type: 'string' },
137
+ 'mem-tries': { type: 'string' },
138
+ 'merge-every': { type: 'string' },
139
+ seed: { type: 'string' },
140
+ recheck: { type: 'string' },
141
+ budget: { type: 'string' },
142
+ 'max-growth': { type: 'string' },
143
+ 'skill-cap': { type: 'string' },
144
+ 'max-skills': { type: 'string' },
145
+ class: { type: 'string', multiple: true },
146
+ id: { type: 'string', multiple: true },
147
+ rubric: { type: 'string' },
148
+ limit: { type: 'string' },
149
+ more: { type: 'string' },
150
+ force: { type: 'boolean' },
151
+ plain: { type: 'boolean' },
152
+ difficult: { type: 'boolean' },
153
+ yes: { type: 'boolean' },
154
+ };
155
+ export function finetuneProjectFlag(args) {
156
+ return parse(args, OPTIONS, FINETUNE_USAGE).values.project;
157
+ }
158
+ export async function runFinetune(ctx, project, options = {}) {
159
+ const { values, positionals } = parse(ctx.args, OPTIONS, FINETUNE_USAGE);
160
+ const [verb = 'status', ...rest] = positionals;
161
+ moveLegacyDir(project.dir);
162
+ switch (verb) {
163
+ case 'start':
164
+ return start(ctx, project, values, options);
165
+ case 'status':
166
+ return status(ctx, project);
167
+ case 'stop':
168
+ return stop(project);
169
+ case 'serve':
170
+ return serve(ctx, project);
171
+ case 'retry':
172
+ return retryCases(ctx, project, values, rest);
173
+ case 'session':
174
+ return session(project, rest);
175
+ case 'gate':
176
+ return gate(ctx, project, rest, options);
177
+ default:
178
+ throw usageError(`unknown finetune verb: ${verb}`, 'one of: start, status, stop, serve, retry, session, gate');
179
+ }
180
+ }
181
+ /**
182
+ * A tuning used to live in `.finetune/`. Moves it to `finetune/` once, so a
183
+ * project tuned before the rename resumes where it stopped. Refuses while a
184
+ * loop of the old version still holds its lock there: renaming its folder out
185
+ * from under it would split one tuning across two directories.
186
+ */
187
+ export function moveLegacyDir(projectDir) {
188
+ const legacy = join(projectDir, LEGACY_FINETUNE_DIR);
189
+ const current = join(projectDir, FINETUNE_DIR);
190
+ if (!existsSync(legacy)) {
191
+ return;
192
+ }
193
+ if (existsSync(current)) {
194
+ note(yellow(`both ${LEGACY_FINETUNE_DIR}/ and ${FINETUNE_DIR}/ exist; using ${FINETUNE_DIR}/ - remove ${LEGACY_FINETUNE_DIR}/ once nothing in it is wanted`));
195
+ return;
196
+ }
197
+ const holder = liveHolder(join(legacy, 'loop.lock'));
198
+ if (holder) {
199
+ throw invalidError(`a tuning is still running in ${LEGACY_FINETUNE_DIR}/ (pid ${holder.pid})`, `let it finish or stop that process, then run this again to move it to ${FINETUNE_DIR}/`);
200
+ }
201
+ renameSync(legacy, current);
202
+ note(dim(`moved ${LEGACY_FINETUNE_DIR}/ to ${FINETUNE_DIR}/`));
203
+ }
204
+ function whole(text, flag, min = 1) {
205
+ if (text === undefined) {
206
+ return undefined;
207
+ }
208
+ const n = Number(text);
209
+ if (!Number.isInteger(n) || n < min) {
210
+ throw usageError(`${flag} takes a whole number of at least ${min}`, `got "${text}"`);
211
+ }
212
+ return n;
213
+ }
214
+ const configFile = (dir) => join(dir, FINETUNE_DIR, 'loop.json');
215
+ export function readConfig(dir) {
216
+ try {
217
+ // A limit is for one start; older versions kept it, and had a guard and consolidations.
218
+ const { limit: _, guard, consolidateEvery: __, ...saved } = JSON.parse(readFileSync(configFile(dir), 'utf8'));
219
+ return { ...DEFAULTS, ...(guard !== undefined ? { recheck: guard } : {}), ...saved };
220
+ }
221
+ catch {
222
+ return { ...DEFAULTS };
223
+ }
224
+ }
225
+ function configFrom(dir, v) {
226
+ const saved = readConfig(dir);
227
+ const workers = whole(v.workers, '-N') ?? saved.workers;
228
+ if (v.rubric !== undefined && v.rubric !== 'yes' && v.rubric !== 'no') {
229
+ throw usageError('--rubric takes yes or no');
230
+ }
231
+ const applyAt = whole(v['apply-at'], '-M');
232
+ if (applyAt === 1 && !v.force) {
233
+ throw usageError('-M 1 applies what one case asked for, alone', 'one case is an anecdote: use -M 2 or more, or pass --force');
234
+ }
235
+ const config = {
236
+ ...saved,
237
+ workers,
238
+ // An older version saved -N as -M, so a tuning on one worker kept applying one case at a time.
239
+ applyAt: applyAt ?? (saved.applyAt < 2 && !v.force ? 2 : saved.applyAt),
240
+ tries: whole(v.tries, '--tries') ?? saved.tries,
241
+ memTries: whole(v['mem-tries'], '--mem-tries') ?? saved.memTries,
242
+ mergeEvery: whole(v['merge-every'], '--merge-every') ?? saved.mergeEvery,
243
+ seed: whole(v.seed, '--seed', 0) ?? saved.seed,
244
+ recheck: whole(v.recheck, '--recheck', 0) ?? saved.recheck,
245
+ budget: whole(v.budget, '--budget', 0) ?? saved.budget,
246
+ maxGrowth: whole(v['max-growth'], '--max-growth', 0) ?? saved.maxGrowth,
247
+ skillCap: whole(v['skill-cap'], '--skill-cap') ?? saved.skillCap,
248
+ maxSkills: whole(v['max-skills'], '--max-skills') ?? saved.maxSkills,
249
+ ...(v.class ? { classes: v.class } : {}),
250
+ ...(v.id ? { ids: v.id } : {}),
251
+ ...(v.rubric ? { rubric: v.rubric === 'yes' } : {}),
252
+ };
253
+ const limit = whole(v.limit, '--limit');
254
+ mkdirSync(join(dir, FINETUNE_DIR), { recursive: true });
255
+ writeFileSync(configFile(dir), `${JSON.stringify(config, null, 2)}\n`);
256
+ return limit ? { ...config, limit } : config;
257
+ }
258
+ async function start(ctx, project, values, options) {
259
+ const store = DatasetStore.open(project.dir);
260
+ if (!store.exists()) {
261
+ throw invalidError('there is no dataset to tune on', 'build one: zen meta run /dataset');
262
+ }
263
+ const report = drift(store);
264
+ if (!report.clean && !values.force) {
265
+ throw invalidError(`the dataset has drifted from its sources (${report.cases.length} case(s), ${report.uncovered.length} section(s))`, 'refresh it: zen meta run /dataset - or start with --force');
266
+ }
267
+ const more = whole(values.more, '--more');
268
+ const config = configFrom(project.dir, values);
269
+ const children = new Set();
270
+ const t = new Tuning(project.dir, config, options.zen ?? spawnZen(project.dir, children), options.tickMs);
271
+ t.more = more;
272
+ if (!options.zen) {
273
+ editorFiles(project.dir);
274
+ }
275
+ let interrupts = 0;
276
+ let drawing = false;
277
+ const onInterrupt = () => {
278
+ interrupts++;
279
+ if (interrupts === 1) {
280
+ if (!drawing) {
281
+ note(yellow('stopping: the steps in flight finish first - Ctrl-C again to kill them'));
282
+ }
283
+ t.stop('interrupted');
284
+ return;
285
+ }
286
+ for (const child of children) {
287
+ try {
288
+ process.kill(-child.pid, 'SIGTERM');
289
+ }
290
+ catch {
291
+ // Already gone.
292
+ }
293
+ }
294
+ process.exit(130);
295
+ };
296
+ process.on('SIGINT', onInterrupt);
297
+ note(`${bold('tuning')} ${project.name} · ${config.workers} workers · apply at ${config.applyAt}`);
298
+ const viewing = options.zen ? undefined : await viewOf(project.dir);
299
+ const url = viewing?.url ?? liveView(project.dir);
300
+ if (url) {
301
+ note(dim(`follow it: ${url}`));
302
+ }
303
+ let close;
304
+ if (!ctx.json && !values.plain && !options.zen && process.stderr.isTTY && process.stdin.isTTY) {
305
+ const { watch } = await import("./tui.js");
306
+ close = await watch(t, onInterrupt, url);
307
+ drawing = true;
308
+ }
309
+ try {
310
+ await runLoop(t);
311
+ }
312
+ finally {
313
+ close?.();
314
+ process.off('SIGINT', onInterrupt);
315
+ await viewing?.close();
316
+ }
317
+ if (!ctx.json) {
318
+ await status(ctx, project);
319
+ }
320
+ }
321
+ function counts(dir) {
322
+ const t = new Tuning(dir, readConfig(dir), async () => ({ code: 1, stdout: '' }));
323
+ const out = { total: 0, queued: 0, stopped: 0, completed: 0, difficult: 0, failed: 0 };
324
+ for (const c of selection(t)) {
325
+ out.total++;
326
+ const r = resultOf(t, c);
327
+ if (r) {
328
+ out[r.state]++;
329
+ }
330
+ else if (attemptsOf(t, c).length > 0) {
331
+ out.stopped++;
332
+ }
333
+ else {
334
+ out.queued++;
335
+ }
336
+ }
337
+ return out;
338
+ }
339
+ function status(ctx, project) {
340
+ const dir = join(project.dir, FINETUNE_DIR);
341
+ if (!DatasetStore.open(project.dir).exists()) {
342
+ throw invalidError('there is no dataset to tune on', 'build one: zen meta run /dataset');
343
+ }
344
+ const holder = liveHolder(join(dir, 'loop.lock'));
345
+ const c = counts(project.dir);
346
+ const url = liveView(project.dir);
347
+ if (ctx.json) {
348
+ return json({
349
+ running: Boolean(holder),
350
+ ...c,
351
+ status: join(dir, 'STATUS.md'),
352
+ ...(url ? { url } : {}),
353
+ });
354
+ }
355
+ write(`${bold(project.name)} ${holder ? green(`running (pid ${holder.pid})`) : dim('not running')}`);
356
+ write(`${c.total} cases: ${green(`${c.completed} completed`)}, ${yellow(`${c.difficult} difficult`)}, ${c.failed ? `${red(`${c.failed} failed`)}, ` : ''}${c.stopped} part-way, ${c.queued} not started`);
357
+ if (!holder && c.queued + c.stopped + c.failed > 0) {
358
+ note(dim(`${c.stopped + c.failed > 0 ? 'resume it' : 'start it'}: ${cyan(`zen meta ${project.name} finetune start`)} [-N <workers>] - see zen meta finetune --help`));
359
+ }
360
+ if (!holder && c.difficult > 0) {
361
+ note(dim(`another round for the difficult ones: ${cyan(`zen meta ${project.name} finetune retry --difficult`)}`));
362
+ }
363
+ if (existsSync(join(dir, 'STATUS.md'))) {
364
+ note(dim(`the detail: ${cyan(join(dir, 'STATUS.md'))} · ${url ? cyan(url) : `live in a browser: ${cyan(`zen meta ${project.name} finetune serve`)}`}`));
365
+ }
366
+ }
367
+ /** The live view for a tuning, or a note saying why there is none - it never stops one. */
368
+ async function viewOf(projectDir) {
369
+ if (liveView(projectDir)) {
370
+ return undefined;
371
+ }
372
+ try {
373
+ return await startView(projectDir);
374
+ }
375
+ catch (err) {
376
+ note(yellow(`no live view: ${err instanceof Error ? err.message : String(err)}`));
377
+ return undefined;
378
+ }
379
+ }
380
+ async function serve(ctx, project) {
381
+ const live = liveView(project.dir);
382
+ if (live) {
383
+ if (ctx.json) {
384
+ return json({ url: live, served: false });
385
+ }
386
+ write(live);
387
+ note(dim('already served by another zen process'));
388
+ return;
389
+ }
390
+ const viewing = await startView(project.dir);
391
+ if (ctx.json) {
392
+ json({ url: viewing.url, served: true });
393
+ }
394
+ else {
395
+ write(viewing.url);
396
+ note(dim('serving the tuning · Ctrl-C stops it'));
397
+ }
398
+ await new Promise((settle) => {
399
+ const done = () => {
400
+ process.off('SIGINT', done);
401
+ process.off('SIGTERM', done);
402
+ settle();
403
+ };
404
+ process.once('SIGINT', done);
405
+ process.once('SIGTERM', done);
406
+ });
407
+ await viewing.close();
408
+ }
409
+ function stop(project) {
410
+ const dir = join(project.dir, FINETUNE_DIR);
411
+ if (!liveHolder(join(dir, 'loop.lock'))) {
412
+ note('no tuning is running here');
413
+ return;
414
+ }
415
+ writeFileSync(join(dir, 'stop'), `${new Date().toISOString()}\n`);
416
+ note('asked it to stop: the steps in flight finish first');
417
+ }
418
+ async function retryCases(ctx, project, values, ids) {
419
+ const t = new Tuning(project.dir, readConfig(project.dir), async () => ({
420
+ code: 1,
421
+ stdout: '',
422
+ }));
423
+ const plan = await retry(t, {
424
+ difficult: values.difficult,
425
+ classes: values.class,
426
+ ids: [...ids, ...(values.id ?? [])],
427
+ yes: values.yes,
428
+ });
429
+ if (ctx.json) {
430
+ json({ retried: plan.take.map((c) => c.id), skipped: plan.skip, tokens: plan.tokens });
431
+ }
432
+ }
433
+ function session(project, rest) {
434
+ const [id] = rest;
435
+ if (!id) {
436
+ throw usageError('session of which case?', 'zen meta finetune session <case>');
437
+ }
438
+ const c = DatasetStore.open(project.dir).get(id);
439
+ if (!c) {
440
+ throw invalidError(`no case ${id}`);
441
+ }
442
+ const t = new Tuning(project.dir, readConfig(project.dir), async () => ({
443
+ code: 1,
444
+ stdout: '',
445
+ }));
446
+ const latest = attemptsOf(t, c)
447
+ .reverse()
448
+ .map((a) => analyzeSession(a.dir))
449
+ .find(Boolean);
450
+ if (!latest) {
451
+ throw invalidError(`${id} has not been analyzed yet at revision ${c.rev}`);
452
+ }
453
+ write(`zen meta resume ${project.name} ${latest}`);
454
+ }
455
+ /** The gates `#edit` runs after the apply agent exits, run now against the same `before` and limits. */
456
+ async function gate(ctx, project, rest, options) {
457
+ const [at] = rest;
458
+ if (!at) {
459
+ throw usageError('gate of which apply?', 'zen meta finetune gate <apply dir>');
460
+ }
461
+ const dir = resolve(ctx.cwd, at);
462
+ const g = readJson(join(dir, 'gate.json'));
463
+ if (!g || existsSync(join(dir, 'applied.json'))) {
464
+ throw invalidError(`${at} is not an apply in progress`, 'pass the apply directory named on the line after /finetune-apply');
465
+ }
466
+ const after = await measure(project.dir);
467
+ const over = overLimits(g.before, after, g.limits);
468
+ const log = join(dir, 'gate.log');
469
+ const check = await (options.zen ?? checkOnce(project.dir))(['check', '--no-models', '--no-sandbox'], log);
470
+ const pass = over.length === 0 && check.code === 0;
471
+ const grew = after.total - g.before.total;
472
+ if (ctx.json) {
473
+ json({
474
+ pass,
475
+ alwaysRead: { before: g.before.total, after: after.total },
476
+ limits: g.limits,
477
+ over,
478
+ check: { code: check.code, log },
479
+ });
480
+ }
481
+ else {
482
+ const moved = costChanges(g.before, after);
483
+ write([
484
+ `always read, summed over the agents: ${g.before.total} -> ${after.total} bytes (${grew > 0 ? '+' : ''}${grew}); budget +${g.limits.budget}, ceiling ${g.limits.ceiling}`,
485
+ '',
486
+ ...(moved.length > 0 ? moved : ['nothing the agents read changed size']),
487
+ '',
488
+ ...(over.length > 0 ? ['over the limits:', ...over.map((o) => `- ${o}`), ''] : []),
489
+ check.code === 0
490
+ ? 'zen check: passed'
491
+ : `zen check: failed (exit ${check.code})\n${tail(log)}`,
492
+ pass ? 'gate: pass' : 'gate: fail - this apply would be undone',
493
+ ].join('\n'));
494
+ }
495
+ if (!pass) {
496
+ throw new CliError(over.length > 0 ? 'over the limits' : 'zen check failed', EXIT.failed, over.length > 0 ? 'cut, move or extract, then run gate again' : undefined);
497
+ }
498
+ }
499
+ const tail = (log) => existsSync(log) ? readFileSync(log, 'utf8').trimEnd().split('\n').slice(-30).join('\n') : '';
500
+ /** `zen check` once, its output to `log` - not the loop's spawn, which records its children. */
501
+ function checkOnce(root) {
502
+ return async (args, log) => {
503
+ const fd = openSync(log, 'w');
504
+ try {
505
+ const res = spawnSync(process.execPath, [process.argv[1], ...args], {
506
+ cwd: root,
507
+ stdio: ['ignore', fd, fd],
508
+ });
509
+ return { code: res.status ?? 1, stdout: '' };
510
+ }
511
+ finally {
512
+ closeSync(fd);
513
+ }
514
+ };
515
+ }
516
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1,46 @@
1
+ import { type ProjectReport } from '../host.ts';
2
+ import type { Config } from './tuning.ts';
3
+ export interface Cost {
4
+ /** per agent: house rules, prompt, preloaded skills, skill index lines */
5
+ alwaysRead: Record<string, number>;
6
+ /** `alwaysRead` summed over the agents */
7
+ total: number;
8
+ /** per skill: its SKILL.md */
9
+ skills: Record<string, number>;
10
+ /** per agent: the skills it can see */
11
+ visible: Record<string, number>;
12
+ /** every markdown file under agents/ */
13
+ files: Record<string, number>;
14
+ }
15
+ export interface Limits {
16
+ /** bytes one apply may add to `total` */
17
+ budget: number;
18
+ /** above this `total`, an apply may add nothing */
19
+ ceiling: number;
20
+ skillCap: number;
21
+ maxSkills: number;
22
+ }
23
+ /** `gate.json` in an apply's folder: what its budget gate compares against, kept for `finetune gate`. */
24
+ export interface Gate {
25
+ before: Cost;
26
+ limits: Limits;
27
+ }
28
+ export declare function measure(root: string): Promise<Cost>;
29
+ export interface Binding {
30
+ discovery: string;
31
+ allow?: string[];
32
+ preload?: string[];
33
+ }
34
+ /** No `skills:` key is the whole catalog as an index, the same as the runtime. */
35
+ export declare const bindingOf: (a: ProjectReport["agents"][number]) => Binding;
36
+ /** The skills a binding lets its agent load. */
37
+ export declare const visibleTo: <S extends {
38
+ name: string;
39
+ }>(b: Binding, entries: S[]) => S[];
40
+ export declare function costOf(root: string, report: ProjectReport): Cost;
41
+ export declare function limits(start: Cost, c: Config): Limits;
42
+ /** Every limit an edit broke, said so the next attempt can act on it; empty when none. */
43
+ export declare function overLimits(before: Cost, after: Cost, l: Limits): string[];
44
+ /** What moved between two measures, per agent, skill and file - for a person and the next attempt. */
45
+ export declare function costChanges(before: Cost, after: Cost): string[];
46
+ //# sourceMappingURL=cost.d.ts.map