@zenera/cli 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/catalog.d.ts +20 -0
- package/dist/catalog.js +60 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/inspect.d.ts +5 -1
- package/dist/commands/inspect.js +54 -14
- package/dist/commands/memory.js +28 -3
- package/dist/commands/meta.js +24 -5
- package/dist/commands/models.js +2 -1
- package/dist/commands/sandbox.js +73 -3
- package/dist/commands/view.d.ts +8 -0
- package/dist/commands/view.js +114 -0
- package/dist/engine.d.ts +3 -1
- package/dist/engine.js +6 -0
- package/dist/eventlog.d.ts +5 -0
- package/dist/eventlog.js +25 -1
- package/dist/liveness.js +20 -2
- package/dist/main.js +0 -0
- package/dist/meta/editor.d.ts +1 -1
- package/dist/meta/editor.js +4 -21
- package/dist/meta/finetune/analyze-brief.d.ts +26 -0
- package/dist/meta/finetune/analyze-brief.js +131 -0
- package/dist/meta/finetune/apply-brief.d.ts +23 -0
- package/dist/meta/finetune/apply-brief.js +247 -0
- package/dist/meta/finetune/brief.d.ts +45 -0
- package/dist/meta/finetune/brief.js +158 -0
- package/dist/meta/finetune/command.d.ts +1 -1
- package/dist/meta/finetune/command.js +224 -46
- package/dist/meta/finetune/cost.d.ts +46 -0
- package/dist/meta/finetune/cost.js +105 -0
- package/dist/meta/finetune/dataset/types.d.ts +3 -1
- package/dist/meta/finetune/feedback.d.ts +14 -2
- package/dist/meta/finetune/feedback.js +22 -3
- package/dist/meta/finetune/guard.d.ts +29 -0
- package/dist/meta/finetune/guard.js +104 -0
- package/dist/meta/finetune/improvements.d.ts +50 -3
- package/dist/meta/finetune/improvements.js +290 -33
- package/dist/meta/finetune/live.d.ts +17 -7
- package/dist/meta/finetune/live.js +56 -10
- package/dist/meta/finetune/loop.js +59 -10
- package/dist/meta/finetune/recheck.d.ts +58 -0
- package/dist/meta/finetune/recheck.js +311 -0
- package/dist/meta/finetune/report.d.ts +9 -2
- package/dist/meta/finetune/report.js +551 -438
- package/dist/meta/finetune/sampler.js +8 -2
- package/dist/meta/finetune/train.d.ts +6 -2
- package/dist/meta/finetune/train.js +55 -21
- package/dist/meta/finetune/tui.d.ts +3 -3
- package/dist/meta/finetune/tui.js +37 -12
- package/dist/meta/finetune/tuning.d.ts +27 -1
- package/dist/meta/finetune/tuning.js +21 -2
- package/dist/meta/finetune/usage.d.ts +4 -0
- package/dist/meta/finetune/usage.js +35 -0
- package/dist/meta/finetune/view.d.ts +9 -0
- package/dist/meta/finetune/view.js +27 -0
- package/dist/meta/host.d.ts +2 -1
- package/dist/meta/host.js +2 -1
- package/dist/meta.d.ts +291 -0
- package/dist/meta.js +981 -0
- package/dist/resources.d.ts +215 -0
- package/dist/resources.js +856 -0
- package/dist/scaffold.d.ts +3 -4
- package/dist/scaffold.js +21 -9
- package/dist/trace.d.ts +5 -0
- package/dist/trace.js +54 -5
- package/dist/tui/app.js +19 -5
- package/dist/tui/links.d.ts +27 -0
- package/dist/tui/links.js +142 -0
- package/dist/tui/models.d.ts +3 -1
- package/dist/tui/models.js +65 -7
- package/dist/tui/sandbox.d.ts +10 -0
- package/dist/tui/sandbox.js +131 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +29 -43
- package/templates/editor/.github/prompts/analyze.prompt.md +3 -1
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +221 -42
- package/templates/editor/.github/prompts/project-review.prompt.md +1 -1
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +162 -57
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +74 -14
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +16 -2
- package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +25 -13
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +74 -33
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +14 -1
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +4 -3
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
- package/templates/editor/.github/skills/zen-memory/SKILL.md +64 -39
- package/templates/editor/.github/skills/zen-review/SKILL.md +6 -31
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +1 -2
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +2 -1
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
- package/templates/project/agents/fork-instructions.md +78 -28
- package/templates/project/agents/memory-instructions.md +222 -493
- package/templates/project/agents/tools-instructions.md +7 -0
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { VERDICTS } from "./dataset/types.js";
|
|
3
|
+
/** A summary's first paragraph - the verdict, for a table cell or a one-line note. */
|
|
4
|
+
export const lead = (summary) => summary.trim().split(/\n\s*\n/)[0] ?? '';
|
|
5
|
+
/** A `specification` improvement is for the dataset, never for an apply: prose cannot fix a rubric. */
|
|
6
|
+
export const forDataset = (i) => i.kind === 'specification';
|
|
7
|
+
/** The improvements an apply can act on. */
|
|
8
|
+
export const forProse = (f) => f.improvements.filter((i) => !forDataset(i));
|
|
3
9
|
// Round 1 keeps the bare form, so the ids applies already took still match.
|
|
4
10
|
export const feedbackId = (caseId, phase, attempt, round = 1) => `${caseId}${round > 1 ? `#${round}` : ''}@${phase}-${attempt}`;
|
|
5
11
|
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
@@ -47,7 +53,7 @@ export function readFeedback(file, known, rubricIds = []) {
|
|
|
47
53
|
const rubric = {};
|
|
48
54
|
if (isObject(raw.rubric)) {
|
|
49
55
|
for (const [id, result] of Object.entries(raw.rubric)) {
|
|
50
|
-
if (result === 'pass' || result === 'fail') {
|
|
56
|
+
if (result === 'pass' || result === 'fail' || result === 'invalid') {
|
|
51
57
|
rubric[id] = result;
|
|
52
58
|
}
|
|
53
59
|
}
|
|
@@ -64,10 +70,23 @@ export function readFeedback(file, known, rubricIds = []) {
|
|
|
64
70
|
if (verdict !== 'void' && ungraded.length > 0) {
|
|
65
71
|
return {
|
|
66
72
|
problem: `missing grades for rubric lines ${ungraded.join(', ')} - grade every line of ` +
|
|
67
|
-
`\`zen meta dataset show ${known.case}@${known.caseRev}\` "pass" or "
|
|
73
|
+
`\`zen meta dataset show ${known.case}@${known.caseRev}\` "pass", "fail" or "invalid" under "rubric"`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const unexplained = Object.entries(rubric)
|
|
77
|
+
.filter(([, g]) => g === 'invalid')
|
|
78
|
+
.map(([id]) => id)
|
|
79
|
+
.filter((id) => !improvements.some((i) => forDataset(i) && `${i.change} ${i.why ?? ''}`.split(/[^\w-]+/).includes(id)));
|
|
80
|
+
if (unexplained.length > 0) {
|
|
81
|
+
return {
|
|
82
|
+
problem: `rubric lines ${unexplained.join(', ')} are graded "invalid" with no ` +
|
|
83
|
+
'"specification" improvement naming them - add one per line saying what is wrong ' +
|
|
84
|
+
'with it and how it should read',
|
|
68
85
|
};
|
|
69
86
|
}
|
|
70
87
|
const failed = Object.values(rubric).includes('fail');
|
|
88
|
+
// A right run whose only findings are about the dataset has nothing left for the prose.
|
|
89
|
+
const onlyDataset = improvements.length > 0 && forProse({ improvements }).length === 0;
|
|
71
90
|
return {
|
|
72
91
|
feedback: {
|
|
73
92
|
id: feedbackId(known.case, known.phase, known.attempt, known.round),
|
|
@@ -75,7 +94,7 @@ export function readFeedback(file, known, rubricIds = []) {
|
|
|
75
94
|
verdict,
|
|
76
95
|
rubric,
|
|
77
96
|
// `done` on a wrong answer or a failed rubric line is a contradiction; those win.
|
|
78
|
-
done: raw.done && verdict === 'right' && !failed,
|
|
97
|
+
done: (raw.done || onlyDataset) && verdict === 'right' && !failed,
|
|
79
98
|
summary: typeof raw.summary === 'string' ? raw.summary : '',
|
|
80
99
|
improvements,
|
|
81
100
|
...(verdict === 'void' ? { infra } : {}),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Case } from './dataset/types.ts';
|
|
2
|
+
import { type Tuning } from './tuning.ts';
|
|
3
|
+
export interface GuardTry {
|
|
4
|
+
dir: string;
|
|
5
|
+
verdict: string;
|
|
6
|
+
passed: boolean;
|
|
7
|
+
summary?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GuardResult {
|
|
10
|
+
case: string;
|
|
11
|
+
tries: GuardTry[];
|
|
12
|
+
broken: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** A guard case as the live view and the status page show it, updated as it goes. */
|
|
15
|
+
export interface GuardSeat {
|
|
16
|
+
case: string;
|
|
17
|
+
try: number;
|
|
18
|
+
step: 'run' | 'analyze' | 'passed' | 'broken';
|
|
19
|
+
/** the try's directory */
|
|
20
|
+
dir: string;
|
|
21
|
+
since: number;
|
|
22
|
+
}
|
|
23
|
+
/** A case is broken only after failing this many tries in a row. */
|
|
24
|
+
export declare const GUARD_TRIES = 2;
|
|
25
|
+
/** Completed cases outside `exclude`, in an order fixed by the seed and the apply. */
|
|
26
|
+
export declare function guardCases(t: Tuning, apply: string, exclude: Set<string>): Case[];
|
|
27
|
+
/** Runs the guard for one attempt of an apply; `patch` is that attempt's diff. */
|
|
28
|
+
export declare function guard(t: Tuning, dir: string, attempt: number, exclude: Set<string>, patch: string): Promise<GuardResult[]>;
|
|
29
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
import { readFeedback } from "./feedback.js";
|
|
5
|
+
import { resultOf, selection } from "./sampler.js";
|
|
6
|
+
import { keepAnalysis, runOnce } from "./train.js";
|
|
7
|
+
import { readJson } from "./tuning.js";
|
|
8
|
+
/** A case is broken only after failing this many tries in a row. */
|
|
9
|
+
export const GUARD_TRIES = 2;
|
|
10
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
11
|
+
/** Completed cases outside `exclude`, in an order fixed by the seed and the apply. */
|
|
12
|
+
export function guardCases(t, apply, exclude) {
|
|
13
|
+
const key = (id) => createHash('sha256').update(`${t.config.seed}:${apply}:${id}`).digest('hex');
|
|
14
|
+
return selection(t)
|
|
15
|
+
.filter((c) => !exclude.has(c.id) && resultOf(t, c)?.state === 'completed')
|
|
16
|
+
.sort((a, b) => key(a.id).localeCompare(key(b.id)))
|
|
17
|
+
.slice(0, t.config.guard);
|
|
18
|
+
}
|
|
19
|
+
/** Runs the guard for one attempt of an apply; `patch` is that attempt's diff. */
|
|
20
|
+
export async function guard(t, dir, attempt, exclude, patch) {
|
|
21
|
+
const cases = guardCases(t, basename(dir), exclude);
|
|
22
|
+
if (cases.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const seats = cases.map((c) => ({
|
|
26
|
+
case: c.id,
|
|
27
|
+
try: 1,
|
|
28
|
+
step: 'run',
|
|
29
|
+
dir: join(dir, 'guard', `a${attempt}`, c.id),
|
|
30
|
+
since: Date.now(),
|
|
31
|
+
}));
|
|
32
|
+
if (t.improvements.current) {
|
|
33
|
+
t.improvements.current.guard = seats;
|
|
34
|
+
}
|
|
35
|
+
t.event({
|
|
36
|
+
what: 'guard',
|
|
37
|
+
detail: `${basename(dir)}: ${cases.map((c) => c.id).join(', ')} run again on the edit`,
|
|
38
|
+
});
|
|
39
|
+
const results = await Promise.all(cases.map((c, i) => guardOne(t, join(dir, 'guard', `a${attempt}`, c.id), c, patch, seats[i])));
|
|
40
|
+
writeFileSync(join(dir, 'guard.json'), `${JSON.stringify(results, null, 2)}\n`);
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
async function guardOne(t, dir, c, patch, seat) {
|
|
44
|
+
const tries = [];
|
|
45
|
+
for (let n = 1; n <= GUARD_TRIES; n++) {
|
|
46
|
+
const at = join(dir, `${pad(n)}-nomem`);
|
|
47
|
+
const one = await tryOne(t, c, at, n, patch, seat);
|
|
48
|
+
tries.push(one);
|
|
49
|
+
if (one.passed) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const broken = tries.length === GUARD_TRIES && tries.every((x) => !x.passed);
|
|
54
|
+
Object.assign(seat, { step: broken ? 'broken' : 'passed', since: Date.now() });
|
|
55
|
+
t.event({
|
|
56
|
+
what: broken ? 'guard broken' : 'guard passed',
|
|
57
|
+
case: c.id,
|
|
58
|
+
rev: c.rev,
|
|
59
|
+
detail: tries.map((x) => x.verdict).join(', '),
|
|
60
|
+
});
|
|
61
|
+
return { case: c.id, tries, broken };
|
|
62
|
+
}
|
|
63
|
+
/** A run or an analysis that says nothing about the prose counts as a pass: only a graded failure undoes an apply. */
|
|
64
|
+
async function tryOne(t, c, dir, n, patch, seat) {
|
|
65
|
+
mkdirSync(dir, { recursive: true });
|
|
66
|
+
Object.assign(seat, { try: n, step: 'run', dir, since: Date.now() });
|
|
67
|
+
const why = await runOnce(t, c, dir);
|
|
68
|
+
if (why) {
|
|
69
|
+
return { dir, verdict: 'void', passed: true, summary: why };
|
|
70
|
+
}
|
|
71
|
+
const run = readJson(join(dir, 'run.json'));
|
|
72
|
+
const file = join(dir, 'feedback.json');
|
|
73
|
+
Object.assign(seat, { step: 'analyze', since: Date.now() });
|
|
74
|
+
const res = await t.zen([
|
|
75
|
+
'meta',
|
|
76
|
+
'run',
|
|
77
|
+
'--no-refresh',
|
|
78
|
+
'--json',
|
|
79
|
+
'--events',
|
|
80
|
+
join(dir, 'analyze.events.jsonl'),
|
|
81
|
+
'--session-id',
|
|
82
|
+
randomUUID(),
|
|
83
|
+
'/analyze',
|
|
84
|
+
run.dir,
|
|
85
|
+
`case=${c.id}`,
|
|
86
|
+
`rev=${c.rev}`,
|
|
87
|
+
'phase=nomem',
|
|
88
|
+
`attempt=${n}`,
|
|
89
|
+
`feedback=${file}`,
|
|
90
|
+
`guard=${patch}`,
|
|
91
|
+
], join(dir, 'analyze.log'));
|
|
92
|
+
keepAnalysis(dir, res.stdout);
|
|
93
|
+
const read = readFeedback(file, { case: c.id, caseRev: c.rev, phase: 'nomem', attempt: n }, c.rubric.map((r) => r.id));
|
|
94
|
+
if (!('feedback' in read)) {
|
|
95
|
+
return { dir, verdict: 'unread', passed: true, summary: read.problem };
|
|
96
|
+
}
|
|
97
|
+
const f = read.feedback;
|
|
98
|
+
if (f.verdict === 'void') {
|
|
99
|
+
return { dir, verdict: 'void', passed: true, summary: f.infra };
|
|
100
|
+
}
|
|
101
|
+
const passed = f.verdict === 'right' && !Object.values(f.rubric).includes('fail');
|
|
102
|
+
return { dir, verdict: f.verdict, passed, summary: f.summary };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Cost } from './cost.ts';
|
|
2
|
+
import { type Feedback, type Improvement } from './feedback.ts';
|
|
2
3
|
import { type Seat, type Tuning } from './tuning.ts';
|
|
3
4
|
export interface AppliedFile {
|
|
4
5
|
ids: string[];
|
|
@@ -7,7 +8,33 @@ export interface AppliedFile {
|
|
|
7
8
|
at: string;
|
|
8
9
|
startedAt?: string;
|
|
9
10
|
skipped?: boolean;
|
|
11
|
+
/** why the edit was undone: it failed `zen check`, or went over a limit */
|
|
12
|
+
discarded?: string;
|
|
13
|
+
/** a consolidation, from before an apply tidied up after itself */
|
|
14
|
+
kind?: 'consolidate';
|
|
15
|
+
/** what the agents always read, before and after */
|
|
16
|
+
bytes?: {
|
|
17
|
+
before: number;
|
|
18
|
+
after: number;
|
|
19
|
+
};
|
|
10
20
|
}
|
|
21
|
+
export interface Decision {
|
|
22
|
+
decision?: string;
|
|
23
|
+
note?: string;
|
|
24
|
+
disposition?: string;
|
|
25
|
+
/** the skill the improvement created or changed */
|
|
26
|
+
skill?: string;
|
|
27
|
+
}
|
|
28
|
+
export type Decisions = Record<string, Record<string, Decision>>;
|
|
29
|
+
export interface Deferred {
|
|
30
|
+
request: string;
|
|
31
|
+
case: string;
|
|
32
|
+
apply: string;
|
|
33
|
+
improvement: Improvement;
|
|
34
|
+
note?: string;
|
|
35
|
+
}
|
|
36
|
+
/** `<request> <improvement>` for each improvement of inputs.json that decisions.json gives no valid decision. */
|
|
37
|
+
export declare function undecided(dir: string): string[];
|
|
11
38
|
export declare class Improvements {
|
|
12
39
|
#private;
|
|
13
40
|
readonly pending: Feedback[];
|
|
@@ -24,6 +51,8 @@ export declare class Improvements {
|
|
|
24
51
|
name?: string;
|
|
25
52
|
dir?: string;
|
|
26
53
|
requests?: number;
|
|
54
|
+
/** which gate the edit is at; absent while it waits for runs */
|
|
55
|
+
step?: 'edit' | 'check';
|
|
27
56
|
} | undefined;
|
|
28
57
|
/** applies started in this process */
|
|
29
58
|
started: number;
|
|
@@ -34,12 +63,30 @@ export declare class Improvements {
|
|
|
34
63
|
reload(): void;
|
|
35
64
|
get dir(): string;
|
|
36
65
|
isApplied(id: string): boolean;
|
|
37
|
-
/**
|
|
38
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Every apply's decisions, later ones over earlier, per improvement. An edit
|
|
68
|
+
* that was undone kept nothing, so what it applied or merged reads `discarded`.
|
|
69
|
+
*/
|
|
70
|
+
decisions(): Decisions;
|
|
71
|
+
/** Improvements held back for want of a second case, and not taken up since. */
|
|
72
|
+
deferred(): Deferred[];
|
|
73
|
+
/** The skills an apply made or changed for this request: the case's next run should load them. */
|
|
74
|
+
skillsFor(id: string): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Parks until an apply has taken this feedback, the worker free meanwhile. Taken already - after a restart - returns at once.
|
|
77
|
+
* `structural` when the apply found nothing prose can fix.
|
|
78
|
+
*/
|
|
79
|
+
apply(feedback: Feedback, s: Seat): Promise<{
|
|
80
|
+
structural?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** A recheck's request: queued like a parked case's, with no case waiting on it. */
|
|
83
|
+
regressed(feedback: Feedback): void;
|
|
39
84
|
/** Before a run starts: wait out an apply, or stop. */
|
|
40
85
|
notApplying(): Promise<void>;
|
|
41
86
|
/** The applier: runs until the tuning is finished. */
|
|
42
87
|
loop(): Promise<void>;
|
|
88
|
+
/** What the agents read when the tuning began - its first version, even for a tuning begun before this was kept. */
|
|
89
|
+
baseline(): Promise<Cost>;
|
|
43
90
|
/** Requests every apply so far took, for the report. */
|
|
44
91
|
applies(): {
|
|
45
92
|
name: string;
|