@zenera/cli 1.1.23 → 1.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -2
- package/dist/ask.d.ts +51 -0
- package/dist/ask.js +166 -0
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +83 -0
- package/dist/batch.js +413 -0
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/command.d.ts +6 -0
- package/dist/commands/inspect.d.ts +57 -0
- package/dist/commands/inspect.js +539 -91
- package/dist/commands/list.js +128 -19
- package/dist/commands/memory.js +461 -32
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +110 -14
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +175 -36
- package/dist/commands/sandbox.js +148 -15
- package/dist/engine.d.ts +24 -0
- package/dist/engine.js +61 -8
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +20 -3
- package/dist/main.js +5 -2
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -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 +338 -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 +125 -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 +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -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 +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -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 +41 -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 +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -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/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +42 -0
- package/dist/podman.js +198 -5
- package/dist/progress.d.ts +33 -0
- package/dist/progress.js +402 -0
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/request.d.ts +45 -0
- package/dist/request.js +300 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +20 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.d.ts +26 -1
- package/dist/session.js +46 -32
- package/dist/term.d.ts +5 -0
- package/dist/term.js +28 -3
- package/dist/trace.d.ts +84 -0
- package/dist/trace.js +737 -0
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -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/dist/validate.js +25 -3
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +41 -107
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +744 -0
- 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 +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +18 -4
- package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +300 -21
- package/templates/editor/.github/skills/zen-cli/references/memory.md +121 -28
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +6 -2
- package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- 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 +1791 -0
- 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 +431 -0
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +78 -29
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +25 -2
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/agents.schema.json +1 -1
- package/templates/editor/.vscode/settings.json +2 -0
- package/templates/project/agents/files-instructions.md +35 -13
- package/templates/project/agents/fork-instructions.md +27 -0
- package/templates/project/agents/memory-instructions.md +106 -34
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +5 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { liveHolder } from '@zenera/neo';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { editorFiles } from "../editor.js";
|
|
5
|
+
import { bold, cyan, dim, green, invalidError, json, note, parse, red, usageError, write, yellow, } from "../host.js";
|
|
6
|
+
import { drift } from "./dataset/drift.js";
|
|
7
|
+
import { DatasetStore } from "./dataset/store.js";
|
|
8
|
+
import { runLoop } from "./loop.js";
|
|
9
|
+
import { attemptsOf } from "./report.js";
|
|
10
|
+
import { retry } from "./retry.js";
|
|
11
|
+
import { resultOf, selection } from "./sampler.js";
|
|
12
|
+
import { DEFAULTS, FAILURES_IN_A_ROW, FINETUNE_DIR, LEGACY_FINETUNE_DIR, spawnZen, Tuning, } from "./tuning.js";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// zen meta finetune
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export const FINETUNE_USAGE = 'zen meta finetune <start|status|stop|retry|session> [options]';
|
|
17
|
+
export const FINETUNE_HELP = [
|
|
18
|
+
'Tuning:',
|
|
19
|
+
' zen meta finetune start [options] train every case: run, analyze, apply, run again',
|
|
20
|
+
' zen meta finetune [status] where it stands; the detail is finetune/STATUS.md',
|
|
21
|
+
' zen meta finetune stop finish the steps in flight, then stop',
|
|
22
|
+
' zen meta finetune retry --difficult give difficult cases another round, then start',
|
|
23
|
+
" zen meta finetune session <case> resume that case's analyze session",
|
|
24
|
+
'',
|
|
25
|
+
' start: -N <workers> (4), -M <apply at> (= workers), --tries <n> (4), --mem-tries <n> (3),',
|
|
26
|
+
' --merge-every <k> (3), --seed <n>, --class <c>, --id <glob>, --rubric yes|no, --limit <n>,',
|
|
27
|
+
' --force (past drift).',
|
|
28
|
+
' In a terminal start draws what runs now, the tokens and the log; --plain does not.',
|
|
29
|
+
' Settings are kept in finetune/loop.json; start again to resume where it stopped.',
|
|
30
|
+
];
|
|
31
|
+
export const FINETUNE_SUMMARY = "Tune the project's prose on its dataset, a case at a time, N cases at once.";
|
|
32
|
+
/** The page `zen meta finetune --help` prints. */
|
|
33
|
+
export const FINETUNE_DETAILS = [
|
|
34
|
+
'Verbs:',
|
|
35
|
+
' start Train every case: run, analyze, apply, run again. Resumes.',
|
|
36
|
+
' status Where it stands (the default). The detail: finetune/STATUS.md',
|
|
37
|
+
' stop Let the steps in flight finish, then stop the running tuning.',
|
|
38
|
+
' retry [ids...] Another round for difficult cases: --difficult takes them all,',
|
|
39
|
+
' ids and globs add to it, --class narrows. Run them with start.',
|
|
40
|
+
" session <case> Print the command that resumes that case's analyze session.",
|
|
41
|
+
'',
|
|
42
|
+
'Options for start:',
|
|
43
|
+
' -N, --workers <n> Cases trained at once. Default 4.',
|
|
44
|
+
' -M, --apply-at <n> Apply once this many requests wait. Default: the workers.',
|
|
45
|
+
' --tries <n> Tries without memory before a case is difficult. Default 4.',
|
|
46
|
+
' --mem-tries <n> Tries with memory. Default 3.',
|
|
47
|
+
' --merge-every <k> Merge the kept memories after k more arrive. Default 3.',
|
|
48
|
+
' --seed <n> Order of cases inside a class. Default 1.',
|
|
49
|
+
' --class <c> Only this class. Repeatable.',
|
|
50
|
+
' --id <glob> Only cases whose id matches. Repeatable.',
|
|
51
|
+
' --rubric yes|no Only cases with, or without, a rubric.',
|
|
52
|
+
' --limit <n> Only the first n cases of the order.',
|
|
53
|
+
' --force Start even though the dataset drifted from its sources.',
|
|
54
|
+
' --plain No live view: print a line per start and stop, as in a pipe.',
|
|
55
|
+
' --project <name|dir> Which project. Default: the one you are in.',
|
|
56
|
+
'',
|
|
57
|
+
'One case: without memory, run it and /analyze it until the analysis says done -',
|
|
58
|
+
'parking it until the next apply after each try that is not - then the same with',
|
|
59
|
+
'the memory the passing run wrote. Tries used up: the case is difficult.',
|
|
60
|
+
'',
|
|
61
|
+
'No run shares a memory: each try has its own private directory. A case whose',
|
|
62
|
+
'with-memory run passes submits a copy of that memory; every k submissions are',
|
|
63
|
+
'merged, with the last merge, into finetune/memories/merged/mNN - numbered,',
|
|
64
|
+
'never rewritten, each listing the cases it holds.',
|
|
65
|
+
'',
|
|
66
|
+
'A parked case gives its worker back, so another case runs meanwhile; woken cases',
|
|
67
|
+
'take a worker before new ones. Requests are applied in batches by /finetune-apply:',
|
|
68
|
+
'when -M cases are parked, or once nothing is running and no case is left to start.',
|
|
69
|
+
'No run starts during an apply. zen check must pass,',
|
|
70
|
+
'or the edit is undone. Every apply is a new version of agents.yaml + agents/.',
|
|
71
|
+
'',
|
|
72
|
+
'A case is difficult when its tries run out and the analysis still says no; that is',
|
|
73
|
+
'final for its revision until `retry` gives it another round on the prose of the day.',
|
|
74
|
+
'A case fails instead when something outside the prose broke - the sandbox, a',
|
|
75
|
+
'provider, a key: it keeps its tries and carries on from that step at the next start.',
|
|
76
|
+
`After ${FAILURES_IN_A_ROW} failures in a row the tuning stops, and says what broke;`,
|
|
77
|
+
'a container engine that fails stops it at once. A run its analysis finds void -',
|
|
78
|
+
'the network or a service under it down - is set aside and runs again.',
|
|
79
|
+
'',
|
|
80
|
+
'Ctrl-C (or stop) finishes the steps in flight; Ctrl-C again kills them. Start',
|
|
81
|
+
'again to carry on: every step is kept on disk, and nothing finished is repeated.',
|
|
82
|
+
'Settings given to start are kept in finetune/loop.json.',
|
|
83
|
+
'',
|
|
84
|
+
'Examples:',
|
|
85
|
+
' zen meta finetune start -N 4',
|
|
86
|
+
' zen meta finetune start --class search --limit 6 -N 2 -M 2',
|
|
87
|
+
' zen meta finetune',
|
|
88
|
+
' zen meta finetune retry --difficult --class merge',
|
|
89
|
+
' zen meta finetune session plan-day',
|
|
90
|
+
];
|
|
91
|
+
const OPTIONS = {
|
|
92
|
+
project: { type: 'string' },
|
|
93
|
+
workers: { type: 'string', short: 'N' },
|
|
94
|
+
'apply-at': { type: 'string', short: 'M' },
|
|
95
|
+
tries: { type: 'string' },
|
|
96
|
+
'mem-tries': { type: 'string' },
|
|
97
|
+
'merge-every': { type: 'string' },
|
|
98
|
+
seed: { type: 'string' },
|
|
99
|
+
class: { type: 'string', multiple: true },
|
|
100
|
+
id: { type: 'string', multiple: true },
|
|
101
|
+
rubric: { type: 'string' },
|
|
102
|
+
limit: { type: 'string' },
|
|
103
|
+
force: { type: 'boolean' },
|
|
104
|
+
plain: { type: 'boolean' },
|
|
105
|
+
difficult: { type: 'boolean' },
|
|
106
|
+
yes: { type: 'boolean' },
|
|
107
|
+
};
|
|
108
|
+
export function finetuneProjectFlag(args) {
|
|
109
|
+
return parse(args, OPTIONS, FINETUNE_USAGE).values.project;
|
|
110
|
+
}
|
|
111
|
+
export async function runFinetune(ctx, project, options = {}) {
|
|
112
|
+
const { values, positionals } = parse(ctx.args, OPTIONS, FINETUNE_USAGE);
|
|
113
|
+
const [verb = 'status', ...rest] = positionals;
|
|
114
|
+
moveLegacyDir(project.dir);
|
|
115
|
+
switch (verb) {
|
|
116
|
+
case 'start':
|
|
117
|
+
return start(ctx, project, values, options);
|
|
118
|
+
case 'status':
|
|
119
|
+
return status(ctx, project);
|
|
120
|
+
case 'stop':
|
|
121
|
+
return stop(project);
|
|
122
|
+
case 'retry':
|
|
123
|
+
return retryCases(ctx, project, values, rest);
|
|
124
|
+
case 'session':
|
|
125
|
+
return session(project, rest);
|
|
126
|
+
default:
|
|
127
|
+
throw usageError(`unknown finetune verb: ${verb}`, 'one of: start, status, stop, retry, session');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A tuning used to live in `.finetune/`. Moves it to `finetune/` once, so a
|
|
132
|
+
* project tuned before the rename resumes where it stopped. Refuses while a
|
|
133
|
+
* loop of the old version still holds its lock there: renaming its folder out
|
|
134
|
+
* from under it would split one tuning across two directories.
|
|
135
|
+
*/
|
|
136
|
+
export function moveLegacyDir(projectDir) {
|
|
137
|
+
const legacy = join(projectDir, LEGACY_FINETUNE_DIR);
|
|
138
|
+
const current = join(projectDir, FINETUNE_DIR);
|
|
139
|
+
if (!existsSync(legacy)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (existsSync(current)) {
|
|
143
|
+
note(yellow(`both ${LEGACY_FINETUNE_DIR}/ and ${FINETUNE_DIR}/ exist; using ${FINETUNE_DIR}/ - remove ${LEGACY_FINETUNE_DIR}/ once nothing in it is wanted`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const holder = liveHolder(join(legacy, 'loop.lock'));
|
|
147
|
+
if (holder) {
|
|
148
|
+
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}/`);
|
|
149
|
+
}
|
|
150
|
+
renameSync(legacy, current);
|
|
151
|
+
note(dim(`moved ${LEGACY_FINETUNE_DIR}/ to ${FINETUNE_DIR}/`));
|
|
152
|
+
}
|
|
153
|
+
function whole(text, flag, min = 1) {
|
|
154
|
+
if (text === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
const n = Number(text);
|
|
158
|
+
if (!Number.isInteger(n) || n < min) {
|
|
159
|
+
throw usageError(`${flag} takes a whole number of at least ${min}`, `got "${text}"`);
|
|
160
|
+
}
|
|
161
|
+
return n;
|
|
162
|
+
}
|
|
163
|
+
const configFile = (dir) => join(dir, FINETUNE_DIR, 'loop.json');
|
|
164
|
+
export function readConfig(dir) {
|
|
165
|
+
try {
|
|
166
|
+
return {
|
|
167
|
+
...DEFAULTS,
|
|
168
|
+
...JSON.parse(readFileSync(configFile(dir), 'utf8')),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { ...DEFAULTS };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function configFrom(dir, v) {
|
|
176
|
+
const saved = readConfig(dir);
|
|
177
|
+
const workers = whole(v.workers, '-N') ?? saved.workers;
|
|
178
|
+
if (v.rubric !== undefined && v.rubric !== 'yes' && v.rubric !== 'no') {
|
|
179
|
+
throw usageError('--rubric takes yes or no');
|
|
180
|
+
}
|
|
181
|
+
const config = {
|
|
182
|
+
...saved,
|
|
183
|
+
workers,
|
|
184
|
+
applyAt: whole(v['apply-at'], '-M') ?? (v.workers ? workers : saved.applyAt),
|
|
185
|
+
tries: whole(v.tries, '--tries') ?? saved.tries,
|
|
186
|
+
memTries: whole(v['mem-tries'], '--mem-tries') ?? saved.memTries,
|
|
187
|
+
mergeEvery: whole(v['merge-every'], '--merge-every') ?? saved.mergeEvery,
|
|
188
|
+
seed: whole(v.seed, '--seed', 0) ?? saved.seed,
|
|
189
|
+
...(v.class ? { classes: v.class } : {}),
|
|
190
|
+
...(v.id ? { ids: v.id } : {}),
|
|
191
|
+
...(v.rubric ? { rubric: v.rubric === 'yes' } : {}),
|
|
192
|
+
...(v.limit ? { limit: whole(v.limit, '--limit') } : {}),
|
|
193
|
+
};
|
|
194
|
+
mkdirSync(join(dir, FINETUNE_DIR), { recursive: true });
|
|
195
|
+
writeFileSync(configFile(dir), `${JSON.stringify(config, null, 2)}\n`);
|
|
196
|
+
return config;
|
|
197
|
+
}
|
|
198
|
+
async function start(ctx, project, values, options) {
|
|
199
|
+
const store = DatasetStore.open(project.dir);
|
|
200
|
+
if (!store.exists()) {
|
|
201
|
+
throw invalidError('there is no dataset to tune on', 'build one: zen meta run /dataset');
|
|
202
|
+
}
|
|
203
|
+
const report = drift(store);
|
|
204
|
+
if (!report.clean && !values.force) {
|
|
205
|
+
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');
|
|
206
|
+
}
|
|
207
|
+
const config = configFrom(project.dir, values);
|
|
208
|
+
const children = new Set();
|
|
209
|
+
const t = new Tuning(project.dir, config, options.zen ?? spawnZen(project.dir, children), options.tickMs);
|
|
210
|
+
if (!options.zen) {
|
|
211
|
+
editorFiles(project.dir);
|
|
212
|
+
}
|
|
213
|
+
let interrupts = 0;
|
|
214
|
+
let drawing = false;
|
|
215
|
+
const onInterrupt = () => {
|
|
216
|
+
interrupts++;
|
|
217
|
+
if (interrupts === 1) {
|
|
218
|
+
if (!drawing) {
|
|
219
|
+
note(yellow('stopping: the steps in flight finish first - Ctrl-C again to kill them'));
|
|
220
|
+
}
|
|
221
|
+
t.stop('interrupted');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const child of children) {
|
|
225
|
+
try {
|
|
226
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Already gone.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
process.exit(130);
|
|
233
|
+
};
|
|
234
|
+
process.on('SIGINT', onInterrupt);
|
|
235
|
+
note(`${bold('tuning')} ${project.name} · ${config.workers} workers · apply at ${config.applyAt}`);
|
|
236
|
+
note(dim(`follow it: file://${join(t.dir, 'STATUS.html')}`));
|
|
237
|
+
let close;
|
|
238
|
+
if (!ctx.json && !values.plain && !options.zen && process.stderr.isTTY && process.stdin.isTTY) {
|
|
239
|
+
const { watch } = await import("./tui.js");
|
|
240
|
+
close = await watch(t, onInterrupt);
|
|
241
|
+
drawing = true;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
await runLoop(t);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
close?.();
|
|
248
|
+
process.off('SIGINT', onInterrupt);
|
|
249
|
+
}
|
|
250
|
+
if (!ctx.json) {
|
|
251
|
+
await status(ctx, project);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function counts(dir) {
|
|
255
|
+
const t = new Tuning(dir, readConfig(dir), async () => ({ code: 1, stdout: '' }));
|
|
256
|
+
const out = { total: 0, queued: 0, stopped: 0, completed: 0, difficult: 0, failed: 0 };
|
|
257
|
+
for (const c of selection(t)) {
|
|
258
|
+
out.total++;
|
|
259
|
+
const r = resultOf(t, c);
|
|
260
|
+
if (r) {
|
|
261
|
+
out[r.state]++;
|
|
262
|
+
}
|
|
263
|
+
else if (attemptsOf(t, c).length > 0) {
|
|
264
|
+
out.stopped++;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
out.queued++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
function status(ctx, project) {
|
|
273
|
+
const dir = join(project.dir, FINETUNE_DIR);
|
|
274
|
+
if (!DatasetStore.open(project.dir).exists()) {
|
|
275
|
+
throw invalidError('there is no dataset to tune on', 'build one: zen meta run /dataset');
|
|
276
|
+
}
|
|
277
|
+
const holder = liveHolder(join(dir, 'loop.lock'));
|
|
278
|
+
const c = counts(project.dir);
|
|
279
|
+
if (ctx.json) {
|
|
280
|
+
return json({
|
|
281
|
+
running: Boolean(holder),
|
|
282
|
+
...c,
|
|
283
|
+
status: join(dir, 'STATUS.md'),
|
|
284
|
+
html: join(dir, 'STATUS.html'),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
write(`${bold(project.name)} ${holder ? green(`running (pid ${holder.pid})`) : dim('not running')}`);
|
|
288
|
+
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`);
|
|
289
|
+
if (!holder && c.queued + c.stopped + c.failed > 0) {
|
|
290
|
+
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`));
|
|
291
|
+
}
|
|
292
|
+
if (!holder && c.difficult > 0) {
|
|
293
|
+
note(dim(`another round for the difficult ones: ${cyan(`zen meta ${project.name} finetune retry --difficult`)}`));
|
|
294
|
+
}
|
|
295
|
+
if (existsSync(join(dir, 'STATUS.md'))) {
|
|
296
|
+
note(dim(`the detail: ${cyan(join(dir, 'STATUS.md'))} · ${cyan(`file://${join(dir, 'STATUS.html')}`)}`));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function stop(project) {
|
|
300
|
+
const dir = join(project.dir, FINETUNE_DIR);
|
|
301
|
+
if (!liveHolder(join(dir, 'loop.lock'))) {
|
|
302
|
+
note('no tuning is running here');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
writeFileSync(join(dir, 'stop'), `${new Date().toISOString()}\n`);
|
|
306
|
+
note('asked it to stop: the steps in flight finish first');
|
|
307
|
+
}
|
|
308
|
+
async function retryCases(ctx, project, values, ids) {
|
|
309
|
+
const t = new Tuning(project.dir, readConfig(project.dir), async () => ({
|
|
310
|
+
code: 1,
|
|
311
|
+
stdout: '',
|
|
312
|
+
}));
|
|
313
|
+
const plan = await retry(t, {
|
|
314
|
+
difficult: values.difficult,
|
|
315
|
+
classes: values.class,
|
|
316
|
+
ids: [...ids, ...(values.id ?? [])],
|
|
317
|
+
yes: values.yes,
|
|
318
|
+
});
|
|
319
|
+
if (ctx.json) {
|
|
320
|
+
json({ retried: plan.take.map((c) => c.id), skipped: plan.skip, tokens: plan.tokens });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function session(project, rest) {
|
|
324
|
+
const [id] = rest;
|
|
325
|
+
if (!id) {
|
|
326
|
+
throw usageError('session of which case?', 'zen meta finetune session <case>');
|
|
327
|
+
}
|
|
328
|
+
const c = DatasetStore.open(project.dir).get(id);
|
|
329
|
+
if (!c) {
|
|
330
|
+
throw invalidError(`no case ${id}`);
|
|
331
|
+
}
|
|
332
|
+
const file = join(project.dir, FINETUNE_DIR, 'cases', id, `r${c.rev}`, 'session');
|
|
333
|
+
if (!existsSync(file)) {
|
|
334
|
+
throw invalidError(`${id} has not been analyzed yet at revision ${c.rev}`);
|
|
335
|
+
}
|
|
336
|
+
write(`zen meta resume ${project.name} ${readFileSync(file, 'utf8').trim()}`);
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Anchor } from './types.ts';
|
|
2
|
+
export type Found = {
|
|
3
|
+
found: true;
|
|
4
|
+
text: string;
|
|
5
|
+
sha256: string;
|
|
6
|
+
} | {
|
|
7
|
+
found: false;
|
|
8
|
+
why: 'no file' | 'not found' | 'ambiguous' | 'unreadable';
|
|
9
|
+
};
|
|
10
|
+
interface Heading {
|
|
11
|
+
level: number;
|
|
12
|
+
title: string;
|
|
13
|
+
path: string[];
|
|
14
|
+
/** first line, and one past the last line of its section */
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const sha256: (text: string) => string;
|
|
19
|
+
/** Every ATX heading outside a code fence, with the extent of its section. */
|
|
20
|
+
export declare function headingsOf(text: string): Heading[];
|
|
21
|
+
/** Sorted keys, so reformatting or reordering a document is not a change. */
|
|
22
|
+
export declare function canonical(value: unknown): string;
|
|
23
|
+
/** One read of each file per instance: a drift check asks about many cases per file. */
|
|
24
|
+
export declare class Sources {
|
|
25
|
+
#private;
|
|
26
|
+
readonly root: string;
|
|
27
|
+
constructor(root: string);
|
|
28
|
+
text(file: string): string | undefined;
|
|
29
|
+
/** Of the bytes as they are: any edit at all, even one no case sees. */
|
|
30
|
+
hash(file: string): string | undefined;
|
|
31
|
+
section(file: string, anchor: Anchor | undefined): Found;
|
|
32
|
+
/**
|
|
33
|
+
* Sections of a file no case and no ignore accounts for: headings at the
|
|
34
|
+
* levels the covered ones sit at, or the siblings of covered pointers. A
|
|
35
|
+
* section inside a covered one is part of that case, and a section around one
|
|
36
|
+
* is its context, so neither is reported.
|
|
37
|
+
*/
|
|
38
|
+
uncovered(file: string, covered: readonly (Anchor | undefined)[]): Anchor[];
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=anchors.d.ts.map
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { extname, resolve } from 'node:path';
|
|
4
|
+
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import {} from "./types.js";
|
|
6
|
+
export const sha256 = (text) => createHash('sha256').update(text).digest('hex');
|
|
7
|
+
const normal = (text) => text
|
|
8
|
+
.replace(/\r\n?/g, '\n')
|
|
9
|
+
.split('\n')
|
|
10
|
+
.map((line) => line.trimEnd())
|
|
11
|
+
.join('\n')
|
|
12
|
+
.replace(/\n+$/, '');
|
|
13
|
+
const title = (text) => text.replace(/\s+/g, ' ').trim();
|
|
14
|
+
/** Every ATX heading outside a code fence, with the extent of its section. */
|
|
15
|
+
export function headingsOf(text) {
|
|
16
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
17
|
+
const found = [];
|
|
18
|
+
const stack = [];
|
|
19
|
+
let fence;
|
|
20
|
+
lines.forEach((line, i) => {
|
|
21
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
22
|
+
if (marker) {
|
|
23
|
+
const char = marker[1][0];
|
|
24
|
+
if (!fence) {
|
|
25
|
+
fence = { char, size: marker[1].length };
|
|
26
|
+
}
|
|
27
|
+
else if (fence.char === char && marker[1].length >= fence.size) {
|
|
28
|
+
fence = undefined;
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (fence) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const m = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/.exec(line);
|
|
36
|
+
if (!m || !m[2]?.trim()) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const level = m[1].length;
|
|
40
|
+
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
|
|
41
|
+
stack.pop();
|
|
42
|
+
}
|
|
43
|
+
const heading = {
|
|
44
|
+
level,
|
|
45
|
+
title: title(m[2]),
|
|
46
|
+
path: [...stack.map((h) => h.title), title(m[2])],
|
|
47
|
+
start: i,
|
|
48
|
+
end: lines.length,
|
|
49
|
+
};
|
|
50
|
+
stack.push(heading);
|
|
51
|
+
found.push(heading);
|
|
52
|
+
});
|
|
53
|
+
for (let i = 0; i < found.length; i++) {
|
|
54
|
+
const next = found.slice(i + 1).find((h) => h.level <= found[i].level);
|
|
55
|
+
found[i].end = next ? next.start : lines.length;
|
|
56
|
+
}
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The last name must be the heading's own; the names before it must appear, in
|
|
61
|
+
* order, among its ancestors. So `["Query: organize my day"]` is enough while it
|
|
62
|
+
* is unique, and an author adds a parent only to tell two apart.
|
|
63
|
+
*/
|
|
64
|
+
function matches(path, heading) {
|
|
65
|
+
const want = path.map(title);
|
|
66
|
+
if (want[want.length - 1] !== heading.title) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
let at = 0;
|
|
70
|
+
for (const name of heading.path.slice(0, -1)) {
|
|
71
|
+
if (at < want.length - 1 && name === want[at]) {
|
|
72
|
+
at++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return at === want.length - 1;
|
|
76
|
+
}
|
|
77
|
+
/** RFC 6901, over a parsed JSON or YAML document. */
|
|
78
|
+
function follow(doc, pointer) {
|
|
79
|
+
if (pointer === '') {
|
|
80
|
+
return { ok: true, value: doc };
|
|
81
|
+
}
|
|
82
|
+
let at = doc;
|
|
83
|
+
for (const raw of pointer.slice(1).split('/')) {
|
|
84
|
+
const key = raw.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
85
|
+
if (Array.isArray(at) && /^\d+$/.test(key) && Number(key) < at.length) {
|
|
86
|
+
at = at[Number(key)];
|
|
87
|
+
}
|
|
88
|
+
else if (at !== null && typeof at === 'object' && !Array.isArray(at) && key in at) {
|
|
89
|
+
at = at[key];
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return { ok: false };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, value: at };
|
|
96
|
+
}
|
|
97
|
+
const escapeKey = (key) => key.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
98
|
+
/** Sorted keys, so reformatting or reordering a document is not a change. */
|
|
99
|
+
export function canonical(value) {
|
|
100
|
+
if (Array.isArray(value)) {
|
|
101
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
102
|
+
}
|
|
103
|
+
if (value !== null && typeof value === 'object') {
|
|
104
|
+
const keys = Object.keys(value).sort();
|
|
105
|
+
return `{${keys
|
|
106
|
+
.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`)
|
|
107
|
+
.join(',')}}`;
|
|
108
|
+
}
|
|
109
|
+
return JSON.stringify(value) ?? 'null';
|
|
110
|
+
}
|
|
111
|
+
/** One read of each file per instance: a drift check asks about many cases per file. */
|
|
112
|
+
export class Sources {
|
|
113
|
+
root;
|
|
114
|
+
#text = new Map();
|
|
115
|
+
#headings = new Map();
|
|
116
|
+
#docs = new Map();
|
|
117
|
+
constructor(root) {
|
|
118
|
+
this.root = root;
|
|
119
|
+
}
|
|
120
|
+
text(file) {
|
|
121
|
+
if (!this.#text.has(file)) {
|
|
122
|
+
let text;
|
|
123
|
+
try {
|
|
124
|
+
text = readFileSync(resolve(this.root, file), 'utf8');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
text = undefined;
|
|
128
|
+
}
|
|
129
|
+
this.#text.set(file, text);
|
|
130
|
+
}
|
|
131
|
+
return this.#text.get(file);
|
|
132
|
+
}
|
|
133
|
+
/** Of the bytes as they are: any edit at all, even one no case sees. */
|
|
134
|
+
hash(file) {
|
|
135
|
+
const text = this.text(file);
|
|
136
|
+
return text === undefined ? undefined : sha256(text);
|
|
137
|
+
}
|
|
138
|
+
#headingsOf(file, text) {
|
|
139
|
+
let found = this.#headings.get(file);
|
|
140
|
+
if (!found) {
|
|
141
|
+
found = headingsOf(text);
|
|
142
|
+
this.#headings.set(file, found);
|
|
143
|
+
}
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
#doc(file, text) {
|
|
147
|
+
let parsed = this.#docs.get(file);
|
|
148
|
+
if (!parsed) {
|
|
149
|
+
try {
|
|
150
|
+
const ext = extname(file).toLowerCase();
|
|
151
|
+
parsed = { ok: true, doc: ext === '.json' ? JSON.parse(text) : parseYaml(text) };
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
parsed = { ok: false };
|
|
155
|
+
}
|
|
156
|
+
this.#docs.set(file, parsed);
|
|
157
|
+
}
|
|
158
|
+
return parsed;
|
|
159
|
+
}
|
|
160
|
+
section(file, anchor) {
|
|
161
|
+
const text = this.text(file);
|
|
162
|
+
if (text === undefined) {
|
|
163
|
+
return { found: false, why: 'no file' };
|
|
164
|
+
}
|
|
165
|
+
if (anchor?.heading) {
|
|
166
|
+
const hits = this.#headingsOf(file, text).filter((h) => matches(anchor.heading, h));
|
|
167
|
+
if (hits.length !== 1) {
|
|
168
|
+
return { found: false, why: hits.length === 0 ? 'not found' : 'ambiguous' };
|
|
169
|
+
}
|
|
170
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
171
|
+
const body = normal(lines.slice(hits[0].start, hits[0].end).join('\n'));
|
|
172
|
+
return { found: true, text: body, sha256: sha256(body) };
|
|
173
|
+
}
|
|
174
|
+
if (anchor?.pointer !== undefined) {
|
|
175
|
+
const parsed = this.#doc(file, text);
|
|
176
|
+
if (!parsed.ok) {
|
|
177
|
+
return { found: false, why: 'unreadable' };
|
|
178
|
+
}
|
|
179
|
+
const hit = follow(parsed.doc, anchor.pointer);
|
|
180
|
+
if (!hit.ok) {
|
|
181
|
+
return { found: false, why: 'not found' };
|
|
182
|
+
}
|
|
183
|
+
const body = canonical(hit.value);
|
|
184
|
+
return { found: true, text: body, sha256: sha256(body) };
|
|
185
|
+
}
|
|
186
|
+
const body = normal(text);
|
|
187
|
+
return { found: true, text: body, sha256: sha256(body) };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Sections of a file no case and no ignore accounts for: headings at the
|
|
191
|
+
* levels the covered ones sit at, or the siblings of covered pointers. A
|
|
192
|
+
* section inside a covered one is part of that case, and a section around one
|
|
193
|
+
* is its context, so neither is reported.
|
|
194
|
+
*/
|
|
195
|
+
uncovered(file, covered) {
|
|
196
|
+
const text = this.text(file);
|
|
197
|
+
if (text === undefined || covered.some((a) => !a?.heading && a?.pointer === undefined)) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const out = [];
|
|
201
|
+
const paths = covered.flatMap((a) => (a?.heading ? [a.heading] : []));
|
|
202
|
+
if (paths.length > 0) {
|
|
203
|
+
const all = this.#headingsOf(file, text);
|
|
204
|
+
const hit = all.filter((h) => paths.some((p) => matches(p, h)));
|
|
205
|
+
const levels = new Set(hit.map((h) => h.level));
|
|
206
|
+
const near = (h) => hit.some((c) => c === h ||
|
|
207
|
+
(c.start <= h.start && h.start < c.end) ||
|
|
208
|
+
(h.start <= c.start && c.start < h.end));
|
|
209
|
+
for (const h of all) {
|
|
210
|
+
if (levels.has(h.level) && !near(h)) {
|
|
211
|
+
out.push({ heading: h.path });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const pointers = covered.flatMap((a) => (a?.pointer ? [a.pointer] : []));
|
|
216
|
+
if (pointers.length > 0) {
|
|
217
|
+
const parsed = this.#doc(file, text);
|
|
218
|
+
const taken = new Set(pointers);
|
|
219
|
+
const parents = new Set(pointers.map((p) => p.slice(0, p.lastIndexOf('/'))));
|
|
220
|
+
for (const parent of parents) {
|
|
221
|
+
const hit = parsed.ok ? follow(parsed.doc, parent) : { ok: false };
|
|
222
|
+
if (!hit.ok || hit.value === null || typeof hit.value !== 'object') {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const keys = Array.isArray(hit.value)
|
|
226
|
+
? hit.value.map((_, i) => String(i))
|
|
227
|
+
: Object.keys(hit.value);
|
|
228
|
+
for (const key of keys) {
|
|
229
|
+
const pointer = `${parent}/${escapeKey(key)}`;
|
|
230
|
+
if (!taken.has(pointer)) {
|
|
231
|
+
out.push({ pointer });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=anchors.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Proposed, ProposedRubric } from './schema.ts';
|
|
2
|
+
import type { Commit, DatasetStore } from './store.ts';
|
|
3
|
+
import { type By, type Case, type Manifest, type RubricItem } from './types.ts';
|
|
4
|
+
export interface Plan extends Commit {
|
|
5
|
+
/** cases that were looked at and found the same */
|
|
6
|
+
unchanged: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Rubric items keep their ids across edits, so a note saying `r3 failed` still
|
|
10
|
+
* means the same line. An explicit id wins; otherwise the same words are the
|
|
11
|
+
* same item; anything else is new.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assignRubric(before: readonly RubricItem[], proposed: readonly ProposedRubric[]): RubricItem[];
|
|
14
|
+
/**
|
|
15
|
+
* A proposal, applied. Whole by default: an active case the proposal does not
|
|
16
|
+
* name is retired. `partial` touches only the cases it names — what a refresh
|
|
17
|
+
* of a few drifted sections writes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function planApply(store: DatasetStore, proposed: readonly Proposed[], options: {
|
|
20
|
+
partial: boolean;
|
|
21
|
+
why: string;
|
|
22
|
+
by: By;
|
|
23
|
+
}): Plan;
|
|
24
|
+
/** One case, changed by hand. `override` keeps the change through later re-extractions. */
|
|
25
|
+
export declare function planEdit(store: DatasetStore, id: string, edit: (c: Case) => Case, options: {
|
|
26
|
+
why: string;
|
|
27
|
+
by: By;
|
|
28
|
+
override?: readonly string[];
|
|
29
|
+
}): Plan;
|
|
30
|
+
export declare function planStatus(store: DatasetStore, ids: readonly string[], status: Case['status'], options: {
|
|
31
|
+
why: string;
|
|
32
|
+
by: By;
|
|
33
|
+
}): Plan;
|
|
34
|
+
/** The ignore list changed: no case moves, so no revision; the manifest is rewritten. */
|
|
35
|
+
export declare function planIgnored(store: DatasetStore, ignored: Manifest['ignored']): Plan;
|
|
36
|
+
//# sourceMappingURL=changes.d.ts.map
|