@zenera/cli 1.1.42 → 1.1.44
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 +6 -4
- package/dist/commands/check.js +7 -5
- package/dist/commands/completion.d.ts +1 -1
- package/dist/commands/completion.js +4 -4
- package/dist/commands/meta.js +10 -10
- package/dist/commands/run.js +18 -6
- package/dist/completion.js +1 -1
- package/dist/meta/editor.js +43 -3
- package/dist/meta/finetune/command.js +2 -2
- package/dist/meta/finetune/improvements.js +1 -1
- package/dist/meta/prompts.d.ts +8 -3
- package/dist/meta/prompts.js +42 -19
- package/dist/resolve.d.ts +2 -0
- package/dist/resolve.js +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +1 -1
- package/dist/validate.d.ts +11 -0
- package/dist/validate.js +46 -3
- package/package.json +4 -4
- package/templates/editor/.github/copilot-instructions.md +35 -22
- package/templates/editor/.github/{prompts/analyze.prompt.md → skills/analyze/SKILL.md} +6 -3
- package/templates/editor/.github/{prompts/dataset.prompt.md → skills/dataset/SKILL.md} +6 -3
- package/templates/editor/.github/{prompts/finetune.prompt.md → skills/finetune/SKILL.md} +7 -4
- package/templates/editor/.github/{prompts/new-agent.prompt.md → skills/new-agent/SKILL.md} +3 -1
- package/templates/editor/.github/{prompts/new-skill.prompt.md → skills/new-skill/SKILL.md} +3 -1
- package/templates/editor/.github/{prompts/project-review.prompt.md → skills/project-review/SKILL.md} +6 -0
- package/templates/editor/.github/{prompts/spec-apply-feedback.prompt.md → skills/spec-apply-feedback/SKILL.md} +2 -0
- package/templates/editor/.github/{prompts/spec-edit.prompt.md → skills/spec-edit/SKILL.md} +26 -23
- package/templates/editor/.github/{prompts/spec-sync-project.prompt.md → skills/spec-sync-project/SKILL.md} +40 -16
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +23 -21
- package/templates/editor/.github/skills/zen-cli/references/check.md +2 -1
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +1 -1
- package/templates/editor/.github/skills/zen-cli/references/meta.md +2 -2
- package/templates/editor/.github/skills/zen-cli/references/run.md +12 -4
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +2 -1
- package/templates/editor/.github/{prompts/finetune-apply.prompt.md → skills/zen-finetune-apply/SKILL.md} +7 -4
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +1 -0
- package/templates/editor/.github/skills/zen-review/SKILL.md +34 -0
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +1 -1
- package/templates/editor/.github/skills/zen-review/scripts/check-receipt.mjs +95 -0
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +27 -2
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +2 -1
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -1
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +115 -20
- package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +83 -2
- package/templates/editor/.github/skills/zen-topology/SKILL.md +1 -0
- package/templates/project/agents/prompts/default.md.tmpl +3 -3
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ in line with it, validate the setup, and run it:
|
|
|
115
115
|
```sh
|
|
116
116
|
zen meta run my-project /spec-sync-project
|
|
117
117
|
zen check my-project
|
|
118
|
-
zen run my-project "Summarize this repository and write NOTES.md"
|
|
118
|
+
zen run my-project --here "Summarize this repository and write NOTES.md"
|
|
119
119
|
zen inspect
|
|
120
120
|
```
|
|
121
121
|
|
|
@@ -127,13 +127,15 @@ than silently guessing. After you answer, apply that feedback with
|
|
|
127
127
|
## Run it your way
|
|
128
128
|
|
|
129
129
|
With no prompt, `zen run` opens the terminal UI. Provide a prompt for a
|
|
130
|
-
one-shot run, or use JSON and batch mode to integrate with other tools
|
|
130
|
+
one-shot run, or use JSON and batch mode to integrate with other tools. A
|
|
131
|
+
one-shot run works in a fresh directory inside its session; `--here` points it
|
|
132
|
+
at the directory you are in instead:
|
|
131
133
|
|
|
132
134
|
```sh
|
|
133
135
|
zen run my-project # interactive terminal UI
|
|
134
|
-
zen run my-project "Review the latest changes"
|
|
136
|
+
zen run my-project --here "Review the latest changes"
|
|
135
137
|
git diff | zen run my-project
|
|
136
|
-
zen run my-project "Summarize the changes" --read-only
|
|
138
|
+
zen run my-project --here "Summarize the changes" --read-only
|
|
137
139
|
zen run my-project "Triage these alerts" --json | jq
|
|
138
140
|
zen run batch --input cases.json # run a set of requests
|
|
139
141
|
```
|
package/dist/commands/check.js
CHANGED
|
@@ -6,7 +6,7 @@ import { duration } from "../narrate.js";
|
|
|
6
6
|
import { project as resolveProject } from "../resolve.js";
|
|
7
7
|
import { refreshShared } from "../scaffold.js";
|
|
8
8
|
import { bold, count, cyan, dim, green, invalidError, json, progress, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
9
|
-
import { validateProject, } from "../validate.js";
|
|
9
|
+
import { repinDockerfile, validateProject, } from "../validate.js";
|
|
10
10
|
const USAGE = 'zen check [name|dir] [--project <name|dir>] [--fix] [--no-sandbox] [--no-models] [--strict] [--quiet]';
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
// zen check
|
|
@@ -57,8 +57,10 @@ export const check = {
|
|
|
57
57
|
'(.vscode/ and .github/). They restate how this version of zen behaves,',
|
|
58
58
|
'so a copy left behind by an upgrade is worse than none — they are',
|
|
59
59
|
'replaced whether or not they were edited, and the report that follows is',
|
|
60
|
-
'of the project as repaired.
|
|
61
|
-
'
|
|
60
|
+
'of the project as repaired. It also pins the @zenera/* installs in the',
|
|
61
|
+
'sandbox Dockerfile to this zen\u2019s version, changing nothing else in it.',
|
|
62
|
+
'Nothing else is touched: agents.yaml, the prompts, the specification and',
|
|
63
|
+
'agents/instructions.md are yours.',
|
|
62
64
|
'',
|
|
63
65
|
'The argument is a directory if one is there and a registered project',
|
|
64
66
|
'name otherwise; with neither, the project you are standing in.',
|
|
@@ -87,7 +89,7 @@ export const check = {
|
|
|
87
89
|
// the files that are there now and the exit code answers the repaired
|
|
88
90
|
// project. A fix that left an error behind in the output would be read
|
|
89
91
|
// as a fix that failed.
|
|
90
|
-
const fixed = values.fix ? refreshShared(dir) : [];
|
|
92
|
+
const fixed = values.fix ? [...refreshShared(dir), ...repinDockerfile(dir)] : [];
|
|
91
93
|
// Materialised first, so the credential verdicts are the ones a run
|
|
92
94
|
// would reach: a key in the environment and a key in the keyring are
|
|
93
95
|
// the same key by the time the library asks. The project's own `.env`
|
|
@@ -329,7 +331,7 @@ function fixedLines(fixed) {
|
|
|
329
331
|
return [];
|
|
330
332
|
}
|
|
331
333
|
return [
|
|
332
|
-
`${bold('
|
|
334
|
+
`${bold('Fixed')} ${dim(count(fixed.length, 'file'))}`,
|
|
333
335
|
...fixed.map((f) => ` ${green('wrote')} ${f}`),
|
|
334
336
|
'',
|
|
335
337
|
];
|
|
@@ -2,7 +2,7 @@ import type { Command } from '../command.ts';
|
|
|
2
2
|
export { install, installed, loader, rcFile } from '../completion-install.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Prints the shell's side of tab completion. The script is a few lines that
|
|
5
|
-
* call back into `zen __complete`, so the project names and
|
|
5
|
+
* call back into `zen __complete`, so the project names and commands it
|
|
6
6
|
* offers are always read from the machine it runs on, never baked in.
|
|
7
7
|
*/
|
|
8
8
|
export declare const completion: Command;
|
|
@@ -8,7 +8,7 @@ export { install, installed, loader, rcFile } from "../completion-install.js";
|
|
|
8
8
|
const USAGE = 'zen completion [<zsh|bash|fish>] [--install [--yes]]';
|
|
9
9
|
/**
|
|
10
10
|
* Prints the shell's side of tab completion. The script is a few lines that
|
|
11
|
-
* call back into `zen __complete`, so the project names and
|
|
11
|
+
* call back into `zen __complete`, so the project names and commands it
|
|
12
12
|
* offers are always read from the machine it runs on, never baked in.
|
|
13
13
|
*/
|
|
14
14
|
export const completion = {
|
|
@@ -29,12 +29,12 @@ export const completion = {
|
|
|
29
29
|
' zen completion zsh --install --yes without asking',
|
|
30
30
|
'',
|
|
31
31
|
'Then, in a new shell:',
|
|
32
|
-
' zen meta run my-project /<Tab> the project’s
|
|
32
|
+
' zen meta run my-project /<Tab> the project’s commands',
|
|
33
33
|
' zen meta <Tab> run, prompts, resume, model …',
|
|
34
34
|
' zen run <Tab> your projects',
|
|
35
35
|
'',
|
|
36
|
-
'The
|
|
37
|
-
'
|
|
36
|
+
'The commands are the skills in .github/skills/ an editor offers as slash',
|
|
37
|
+
'commands - not the zen-* ones, which are the model’s. Where there is nothing to offer, file names are.',
|
|
38
38
|
],
|
|
39
39
|
run: async (ctx) => {
|
|
40
40
|
const { values, positionals } = parse(ctx.args, { install: { type: 'boolean' }, yes: { type: 'boolean' } }, USAGE);
|
package/dist/commands/meta.js
CHANGED
|
@@ -10,7 +10,7 @@ import { parse } from "../args.js";
|
|
|
10
10
|
import { matching, projectNames } from "../candidates.js";
|
|
11
11
|
import { copilotEventLog } from "../eventlog.js";
|
|
12
12
|
import { probeChat, probeModel } from "../liveness.js";
|
|
13
|
-
import { answerBox, chooseModel, DATASET_DETAILS, DATASET_SUMMARY, DATASET_USAGE, datasetProjectFlag, DEFAULT_RESUMES, editorFiles, fallbackRef, FINETUNE_DETAILS, FINETUNE_SUMMARY, FINETUNE_USAGE, finetuneProjectFlag, lastSession, launch, listPrompts, loadPrompt, locate, masked, metaCompatible, misspelledProvider, MODEL_ENV, openLog, ORDER,
|
|
13
|
+
import { answerBox, chooseModel, DATASET_DETAILS, DATASET_SUMMARY, DATASET_USAGE, datasetProjectFlag, DEFAULT_RESUMES, editorFiles, fallbackRef, FINETUNE_DETAILS, FINETUNE_SUMMARY, FINETUNE_USAGE, finetuneProjectFlag, lastSession, launch, listPrompts, loadPrompt, locate, masked, metaCompatible, misspelledProvider, MODEL_ENV, openLog, ORDER, SKILL_DIR, providersWarning, readMeta, recordSession, RESUME_PROMPT, resumeDelayMs, runDataset, runFinetune, sameModel, sessionModel, sessionTotals, SOURCE_LABELS, startVertexRelay, tailSpans, Tally, terminalSink, transient, wire, wireGateway, writeMeta, } from "../meta/index.js";
|
|
14
14
|
import { splitRef } from "../modelref.js";
|
|
15
15
|
import { duration } from "../narrate.js";
|
|
16
16
|
import { project as resolveProject } from "../resolve.js";
|
|
@@ -84,16 +84,16 @@ const OPTIONS = {
|
|
|
84
84
|
};
|
|
85
85
|
const RUN_FORMS = [
|
|
86
86
|
' zen meta run [project] "<question>" ask it something',
|
|
87
|
-
' zen meta run [project] /<name> [words] run
|
|
88
|
-
' zen meta run [project] pick one of its
|
|
87
|
+
' zen meta run [project] /<name> [words] run one of its commands',
|
|
88
|
+
' zen meta run [project] pick one of its commands',
|
|
89
89
|
];
|
|
90
90
|
const RUN_ARGUMENTS = [
|
|
91
91
|
'Arguments:',
|
|
92
92
|
' [project] Name of a project. Default: the one you are in.',
|
|
93
93
|
' [prompt] Your question, in quotes. Or --prompt, or piped in when neither',
|
|
94
94
|
' it nor --prompt-file is given.',
|
|
95
|
-
' /<name> A
|
|
96
|
-
' [words]
|
|
95
|
+
' /<name> A command: the skill in .github/skills/<name>/SKILL.md.',
|
|
96
|
+
' [words] Given to that command, after its text.',
|
|
97
97
|
];
|
|
98
98
|
const RUN_OPTIONS = [
|
|
99
99
|
'Options:',
|
|
@@ -126,7 +126,7 @@ export const meta = {
|
|
|
126
126
|
details: [
|
|
127
127
|
'Forms:',
|
|
128
128
|
...RUN_FORMS,
|
|
129
|
-
' zen meta prompts [project] list
|
|
129
|
+
' zen meta prompts [project] list its commands',
|
|
130
130
|
' zen meta resume [project] [session] carry on a run that stopped',
|
|
131
131
|
' zen meta model [ref] show or set the model it uses',
|
|
132
132
|
' zen meta dataset <verb> the cases it tunes against',
|
|
@@ -134,7 +134,7 @@ export const meta = {
|
|
|
134
134
|
'',
|
|
135
135
|
'The project may come before the verb instead: `zen meta acme run`.',
|
|
136
136
|
'`zen meta dataset --help`, `zen meta finetune --help` and `zen meta run --help`',
|
|
137
|
-
'have their own pages; the last lists the
|
|
137
|
+
'have their own pages; the last lists the commands.',
|
|
138
138
|
'',
|
|
139
139
|
...RUN_ARGUMENTS,
|
|
140
140
|
'',
|
|
@@ -281,7 +281,7 @@ async function runHelp(ctx, words) {
|
|
|
281
281
|
lines.push('', ...RUN_ARGUMENTS, '', ...RUN_OPTIONS, '', 'Examples:', ' zen meta run "what does this project do?"', ' zen meta run acme /project-review', ' zen meta run acme /spec-sync-project agents/triage.md', ' zen meta run acme /analyze --json');
|
|
282
282
|
page({
|
|
283
283
|
usage: 'zen meta run [project] [/<name> [words] | "<question>"] [options]',
|
|
284
|
-
summary: 'Ask the meta agent a question, or run one of the
|
|
284
|
+
summary: 'Ask the meta agent a question, or run one of the commands.',
|
|
285
285
|
details: lines,
|
|
286
286
|
});
|
|
287
287
|
}
|
|
@@ -359,7 +359,7 @@ async function stored(ctx, values, args) {
|
|
|
359
359
|
if (question) {
|
|
360
360
|
return await go(ctx, values, project, question);
|
|
361
361
|
}
|
|
362
|
-
// Nothing said and nothing piped: the
|
|
362
|
+
// Nothing said and nothing piped: the commands are what is left to offer.
|
|
363
363
|
return await runPrompt(ctx, values, project, await pickPrompt(ctx, project), []);
|
|
364
364
|
}
|
|
365
365
|
async function runPrompt(ctx, values, project, name, extra) {
|
|
@@ -508,7 +508,7 @@ async function prompts(ctx, values, args) {
|
|
|
508
508
|
return json(found.map(({ name, description, path }) => ({ name, description, path })));
|
|
509
509
|
}
|
|
510
510
|
if (found.length === 0) {
|
|
511
|
-
note(dim(`no
|
|
511
|
+
note(dim(`no commands in ${project.name}/${SKILL_DIR}`));
|
|
512
512
|
return;
|
|
513
513
|
}
|
|
514
514
|
writeAll(table(found.map((p) => [`/${p.name}`, p.description ? dim(p.description) : ''])));
|
package/dist/commands/run.js
CHANGED
|
@@ -33,6 +33,8 @@ export const run = {
|
|
|
33
33
|
' --new Start a new session without asking which one.',
|
|
34
34
|
' --input <file> Read the whole request from JSON. `-` is stdin.',
|
|
35
35
|
' --workspace <dir> Directory the agent can read and write.',
|
|
36
|
+
' Default: a fresh one inside the new session.',
|
|
37
|
+
' --here Same as --workspace with the directory you are in.',
|
|
36
38
|
' --memory <dir> Directory the agents remember into.',
|
|
37
39
|
' --memory-read-only Recall from it, write nothing back.',
|
|
38
40
|
' --model <ref> Use this model instead of the default.',
|
|
@@ -59,7 +61,9 @@ export const run = {
|
|
|
59
61
|
'the answer alone and `2>/dev/null` hides the progress.',
|
|
60
62
|
'',
|
|
61
63
|
'A prompt always starts a new session, so --new is for the TUI, where the',
|
|
62
|
-
'alternative is being asked which session to continue.',
|
|
64
|
+
'alternative is being asked which session to continue. Its workspace is a',
|
|
65
|
+
'fresh, empty directory inside that session; to ask about the files where',
|
|
66
|
+
'you are, say --here.',
|
|
63
67
|
'',
|
|
64
68
|
'--input names a file holding the whole request - project, input, workspace,',
|
|
65
69
|
'memory - and what it says wins over the same flag on the line. Its paths are',
|
|
@@ -98,6 +102,7 @@ export const run = {
|
|
|
98
102
|
' zen run open the TUI in this project',
|
|
99
103
|
' zen run acme open the TUI in the acme project',
|
|
100
104
|
' zen run "what changed?" ask that, print the answer, exit',
|
|
105
|
+
' zen run --here "what changed?" ... about the files in this directory',
|
|
101
106
|
' git diff | zen run take the prompt from stdin',
|
|
102
107
|
' zen run "what changed?" > out.md redirect the answer into a file',
|
|
103
108
|
' zen run --out out.md "what changed?" write the answer to out.md only',
|
|
@@ -118,6 +123,7 @@ export const run = {
|
|
|
118
123
|
new: { type: 'boolean' },
|
|
119
124
|
input: { type: 'string' },
|
|
120
125
|
workspace: { type: 'string' },
|
|
126
|
+
here: { type: 'boolean' },
|
|
121
127
|
memory: { type: 'string' },
|
|
122
128
|
'memory-read-only': { type: 'boolean' },
|
|
123
129
|
model: { type: 'string' },
|
|
@@ -155,16 +161,20 @@ export const run = {
|
|
|
155
161
|
if (values.theme !== undefined && !parseChoice(values.theme)) {
|
|
156
162
|
throw usageError(`unknown theme: ${values.theme}`, 'dark, light or auto');
|
|
157
163
|
}
|
|
164
|
+
if (values.here && values.workspace !== undefined) {
|
|
165
|
+
throw usageError('--here and --workspace both name the workspace', 'keep one');
|
|
166
|
+
}
|
|
158
167
|
// With `--input -` the pipe carries the request, not the prompt, so it
|
|
159
168
|
// is read here and never looked at again below.
|
|
160
169
|
const request = values.input ? await readRequest(values.input, ctx.cwd, piped) : undefined;
|
|
161
170
|
const prompt = typed || (request ? '' : (piped ?? ''));
|
|
162
171
|
// A prompt on the command line is a request for an answer, not a
|
|
163
172
|
// conversation to pick up. So it answers the three questions itself:
|
|
164
|
-
// a fresh session,
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
173
|
+
// a fresh session, a fresh directory inside it as the workspace, and
|
|
174
|
+
// no confirmation for any of it. Reading the files where it was typed
|
|
175
|
+
// takes --here: an agent that can write should not land in whatever
|
|
176
|
+
// directory the shell happened to be in. Every flag still wins, and
|
|
177
|
+
// the TUI, where there is someone to ask, still asks.
|
|
168
178
|
const input = request ? request.input : prompt;
|
|
169
179
|
const shot = Boolean(prompt) || Boolean(request);
|
|
170
180
|
if (values.events && !shot) {
|
|
@@ -175,7 +185,8 @@ export const run = {
|
|
|
175
185
|
project: request?.project ?? values.project ?? (named ? head : undefined),
|
|
176
186
|
session: values.session,
|
|
177
187
|
fresh: values.new || (shot && !values.session),
|
|
178
|
-
workspace: request?.workspace ?? values.workspace ?? (
|
|
188
|
+
workspace: request?.workspace ?? values.workspace ?? (values.here ? ctx.cwd : undefined),
|
|
189
|
+
ask: !shot,
|
|
179
190
|
yes: values.yes || shot,
|
|
180
191
|
});
|
|
181
192
|
const engine = await Engine.open({
|
|
@@ -234,6 +245,7 @@ const NOT_IN_A_BATCH = [
|
|
|
234
245
|
['session', 'every item is a new session of its own'],
|
|
235
246
|
['new', 'every item is new already'],
|
|
236
247
|
['workspace', 'they cannot share one; each item gets its own, or names one in the file'],
|
|
248
|
+
['here', 'they cannot share one; each item gets its own, or names one in the file'],
|
|
237
249
|
['plain', 'a batch never draws the TUI'],
|
|
238
250
|
['theme', 'a batch never draws the TUI'],
|
|
239
251
|
['events', 'each item already has its own output.json'],
|
package/dist/completion.js
CHANGED
|
@@ -14,7 +14,7 @@ import { completeMeta } from "./commands/meta.js";
|
|
|
14
14
|
// One candidate per line, `value<TAB>description`. Nothing at all means "no
|
|
15
15
|
// opinion", and the shell falls back to completing file names — which is the
|
|
16
16
|
// right answer for `-C <dir>`, for `--events <file>`, and for the files that
|
|
17
|
-
// follow a stored
|
|
17
|
+
// follow a stored command.
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
/** Flags the frame owns, offered where a command name would be. */
|
|
20
20
|
const GLOBAL_FLAGS = [
|
package/dist/meta/editor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmdirSync, rmSync } from 'node:fs';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
3
|
import { copyTree, TEMPLATES } from "./host.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
@@ -27,8 +28,8 @@ import { copyTree, TEMPLATES } from "./host.js";
|
|
|
27
28
|
// assistant still needs a brief of its own, and what it needs to know is how
|
|
28
29
|
// this kind of project is put together — the file formats, how a prompt is
|
|
29
30
|
// written, when to add a skill rather than an agent. That is what the
|
|
30
|
-
// `.github/` tree is: the standing brief, plus the
|
|
31
|
-
//
|
|
31
|
+
// `.github/` tree is: the standing brief, plus the skills the editor picks up
|
|
32
|
+
// from the same place - `zen-*` for the model to load, the rest as `/commands`.
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
/**
|
|
34
35
|
* Writes the editor's files under `dir`, replacing what is there. They are
|
|
@@ -38,6 +39,45 @@ import { copyTree, TEMPLATES } from "./host.js";
|
|
|
38
39
|
* none. Returns the relative paths that actually changed.
|
|
39
40
|
*/
|
|
40
41
|
export function editorFiles(dir) {
|
|
41
|
-
return copyTree(join(TEMPLATES, 'editor'), dir, '', {});
|
|
42
|
+
return [...copyTree(join(TEMPLATES, 'editor'), dir, '', {}), ...retirePrompts(dir)];
|
|
43
|
+
}
|
|
44
|
+
// Every `.github/prompts/*.prompt.md` `zen` has ever shipped. Copilot dropped
|
|
45
|
+
// prompt files for skills, and these are now `.github/skills/<name>/` - so the
|
|
46
|
+
// old copy would only offer a second, stale `/name`. A prompt file the project
|
|
47
|
+
// wrote itself is not on this list, and stays.
|
|
48
|
+
const RETIRED_PROMPTS = [
|
|
49
|
+
'analyze',
|
|
50
|
+
'dataset',
|
|
51
|
+
'finetune',
|
|
52
|
+
'finetune-apply',
|
|
53
|
+
'finetune-consolidate',
|
|
54
|
+
'inspect',
|
|
55
|
+
'new-agent',
|
|
56
|
+
'new-skill',
|
|
57
|
+
'project-review',
|
|
58
|
+
'review-project',
|
|
59
|
+
'spec-apply-feedback',
|
|
60
|
+
'spec-edit',
|
|
61
|
+
'spec-sync-project',
|
|
62
|
+
'sync-with-spec',
|
|
63
|
+
'update-spec-from-feedback',
|
|
64
|
+
];
|
|
65
|
+
function retirePrompts(dir) {
|
|
66
|
+
const folder = join(dir, '.github', 'prompts');
|
|
67
|
+
if (!existsSync(folder)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const removed = [];
|
|
71
|
+
for (const name of RETIRED_PROMPTS) {
|
|
72
|
+
const file = join(folder, `${name}.prompt.md`);
|
|
73
|
+
if (existsSync(file)) {
|
|
74
|
+
rmSync(file);
|
|
75
|
+
removed.push(join('.github', 'prompts', `${name}.prompt.md`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (readdirSync(folder).length === 0) {
|
|
79
|
+
rmdirSync(folder);
|
|
80
|
+
}
|
|
81
|
+
return removed;
|
|
42
82
|
}
|
|
43
83
|
//# sourceMappingURL=editor.js.map
|
|
@@ -89,7 +89,7 @@ export const FINETUNE_DETAILS = [
|
|
|
89
89
|
'has its own private memory; passing memories are merged into finetune/memories/.',
|
|
90
90
|
'',
|
|
91
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',
|
|
92
|
+
'/zen-finetune-apply fixes the parked cases together: when -M wait, or once nothing',
|
|
93
93
|
'else can run. No run starts during an apply. What it may do:',
|
|
94
94
|
' - Every call reads the house rules, the prompt, preloaded skills and one line per',
|
|
95
95
|
' skill, so that is what --budget counts. A skill body is paid only once loaded:',
|
|
@@ -461,7 +461,7 @@ async function gate(ctx, project, rest, options) {
|
|
|
461
461
|
const dir = resolve(ctx.cwd, at);
|
|
462
462
|
const g = readJson(join(dir, 'gate.json'));
|
|
463
463
|
if (!g || existsSync(join(dir, 'applied.json'))) {
|
|
464
|
-
throw invalidError(`${at} is not an apply in progress`, 'pass the apply directory named
|
|
464
|
+
throw invalidError(`${at} is not an apply in progress`, 'pass the apply directory named after /zen-finetune-apply');
|
|
465
465
|
}
|
|
466
466
|
const after = await measure(project.dir);
|
|
467
467
|
const over = overLimits(g.before, after, g.limits);
|
|
@@ -401,7 +401,7 @@ export class Improvements {
|
|
|
401
401
|
join(dir, 'apply.events.jsonl'),
|
|
402
402
|
'--prompt-file',
|
|
403
403
|
brief,
|
|
404
|
-
'/finetune-apply',
|
|
404
|
+
'/zen-finetune-apply',
|
|
405
405
|
dir,
|
|
406
406
|
], applyLog);
|
|
407
407
|
writeFileSync(join(dir, attempt === 1 ? 'apply.json' : `apply-${attempt}.json`), res.stdout);
|
package/dist/meta/prompts.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const SKILL_DIR = ".github/skills";
|
|
1
2
|
export declare const PROMPT_DIR = ".github/prompts";
|
|
2
3
|
/** `/project-review`, `project-review`, `project-review.prompt.md` or a path. */
|
|
3
4
|
export declare function promptPath(dir: string, name: string): string;
|
|
@@ -5,14 +6,18 @@ export interface StoredPrompt {
|
|
|
5
6
|
name: string;
|
|
6
7
|
path: string;
|
|
7
8
|
description?: string;
|
|
9
|
+
/** False for a skill marked `user-invocable: false` - the model's, not a command. */
|
|
10
|
+
invocable: boolean;
|
|
8
11
|
body: string;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
|
-
* Frontmatter is the editor's business — `
|
|
12
|
-
* none of it means anything
|
|
13
|
-
*
|
|
14
|
+
* Frontmatter is the editor's business — `name`, `argument-hint`, the
|
|
15
|
+
* invocation switches — and none of it means anything once the body is the
|
|
16
|
+
* prompt, so only the body is sent. `description` is kept for the one line of
|
|
17
|
+
* narration that says which command is running.
|
|
14
18
|
*/
|
|
15
19
|
export declare function readPrompt(path: string, name: string, text: string): StoredPrompt;
|
|
16
20
|
export declare function loadPrompt(dir: string, name: string): Promise<StoredPrompt>;
|
|
21
|
+
/** The commands a person may type: every skill not marked for the model only, and any legacy prompt file. */
|
|
17
22
|
export declare function listPrompts(dir: string): Promise<string[]>;
|
|
18
23
|
//# sourceMappingURL=prompts.d.ts.map
|
package/dist/meta/prompts.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { usageError } from "./host.js";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
|
-
// Stored
|
|
5
|
+
// Stored commands
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// `zen meta run
|
|
7
|
+
// The editor offers `.github/skills/<name>/SKILL.md` as `/name`. The `zen-*`
|
|
8
|
+
// skills are reference the model loads on its own (`user-invocable: false`);
|
|
9
|
+
// the rest are commands somebody types (`disable-model-invocation: true`), and
|
|
10
|
+
// those are what `zen meta run /<name>` runs. Copilot would expand `/name`
|
|
11
|
+
// itself, but reading the file and handing over its body works the same with
|
|
12
|
+
// every model and leaves room for `--prompt-file` after it.
|
|
13
|
+
//
|
|
14
|
+
// `.github/prompts/*.prompt.md` is the format these used to be in. Copilot has
|
|
15
|
+
// dropped it, but a project may still have prompt files of its own, so one
|
|
16
|
+
// whose name no skill takes is still run.
|
|
11
17
|
// ---------------------------------------------------------------------------
|
|
18
|
+
export const SKILL_DIR = '.github/skills';
|
|
12
19
|
export const PROMPT_DIR = '.github/prompts';
|
|
13
20
|
const PROMPT_SUFFIX = '.prompt.md';
|
|
14
21
|
/** `/project-review`, `project-review`, `project-review.prompt.md` or a path. */
|
|
@@ -17,42 +24,58 @@ export function promptPath(dir, name) {
|
|
|
17
24
|
if (bare.includes('/') || bare.endsWith('.md')) {
|
|
18
25
|
return `${dir}/${bare}`;
|
|
19
26
|
}
|
|
20
|
-
|
|
27
|
+
const skill = `${dir}/${SKILL_DIR}/${bare}/SKILL.md`;
|
|
28
|
+
const legacy = `${dir}/${PROMPT_DIR}/${bare}${PROMPT_SUFFIX}`;
|
|
29
|
+
return !existsSync(skill) && existsSync(legacy) ? legacy : skill;
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
|
-
* Frontmatter is the editor's business — `
|
|
24
|
-
* none of it means anything
|
|
25
|
-
*
|
|
32
|
+
* Frontmatter is the editor's business — `name`, `argument-hint`, the
|
|
33
|
+
* invocation switches — and none of it means anything once the body is the
|
|
34
|
+
* prompt, so only the body is sent. `description` is kept for the one line of
|
|
35
|
+
* narration that says which command is running.
|
|
26
36
|
*/
|
|
27
37
|
export function readPrompt(path, name, text) {
|
|
28
38
|
let body = text;
|
|
29
39
|
let description;
|
|
40
|
+
let invocable = true;
|
|
30
41
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
31
42
|
if (match) {
|
|
32
43
|
body = text.slice(match[0].length);
|
|
33
44
|
const found = /^description:\s*(.+)$/m.exec(match[1]);
|
|
34
45
|
description = found?.[1].trim().replace(/^['"]|['"]$/g, '');
|
|
46
|
+
invocable = !/^user-invocable:\s*['"]?false['"]?\s*$/m.test(match[1]);
|
|
35
47
|
}
|
|
36
|
-
return { name, path, description, body: body.trim() };
|
|
48
|
+
return { name, path, description, invocable, body: body.trim() };
|
|
37
49
|
}
|
|
38
50
|
export async function loadPrompt(dir, name) {
|
|
39
51
|
const path = promptPath(dir, name);
|
|
40
52
|
if (!existsSync(path)) {
|
|
41
53
|
const known = await listPrompts(dir);
|
|
42
|
-
throw usageError(`no
|
|
54
|
+
throw usageError(`no command named ${name.replace(/^\//, '')}`, known.length > 0
|
|
43
55
|
? `try: ${known.map((p) => `/${p}`).join(', ')}`
|
|
44
|
-
: `put one in ${
|
|
56
|
+
: `put one in ${SKILL_DIR}/<name>/SKILL.md`);
|
|
45
57
|
}
|
|
46
58
|
return readPrompt(path, name.replace(/^\//, ''), await readFile(path, 'utf8'));
|
|
47
59
|
}
|
|
60
|
+
/** The commands a person may type: every skill not marked for the model only, and any legacy prompt file. */
|
|
48
61
|
export async function listPrompts(dir) {
|
|
49
|
-
const
|
|
62
|
+
const names = new Set();
|
|
63
|
+
for (const entry of entries(`${dir}/${SKILL_DIR}`)) {
|
|
64
|
+
const path = `${dir}/${SKILL_DIR}/${entry}/SKILL.md`;
|
|
65
|
+
if (existsSync(path) && readPrompt(path, entry, readFileSync(path, 'utf8')).invocable) {
|
|
66
|
+
names.add(entry);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries(`${dir}/${PROMPT_DIR}`)) {
|
|
70
|
+
if (entry.endsWith(PROMPT_SUFFIX)) {
|
|
71
|
+
names.add(entry.slice(0, -PROMPT_SUFFIX.length));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...names].sort();
|
|
75
|
+
}
|
|
76
|
+
function entries(path) {
|
|
50
77
|
try {
|
|
51
|
-
|
|
52
|
-
return names
|
|
53
|
-
.filter((n) => n.endsWith(PROMPT_SUFFIX))
|
|
54
|
-
.map((n) => n.slice(0, -PROMPT_SUFFIX.length))
|
|
55
|
-
.sort();
|
|
78
|
+
return readdirSync(path);
|
|
56
79
|
}
|
|
57
80
|
catch {
|
|
58
81
|
return [];
|
package/dist/resolve.d.ts
CHANGED
package/dist/resolve.js
CHANGED
|
@@ -137,7 +137,7 @@ export async function chooseWorkspace(session, want) {
|
|
|
137
137
|
await approve(at, session, want);
|
|
138
138
|
return at;
|
|
139
139
|
}
|
|
140
|
-
if (!term.isInteractive()) {
|
|
140
|
+
if (want.ask === false || !term.isInteractive()) {
|
|
141
141
|
return own;
|
|
142
142
|
}
|
|
143
143
|
const chosen = await term.choose('Workspace — what the agent can read and write', [
|
package/dist/usage.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ModelUsage, TokenUsage } from '@zenera/neo';
|
|
|
3
3
|
export declare const LEDGER_ENV = "ZENERA_USAGE_LEDGER";
|
|
4
4
|
/** The meta session whose agent started this process, if one did. */
|
|
5
5
|
export declare const META_SESSION_ENV = "ZENERA_META_SESSION";
|
|
6
|
-
/** The stored
|
|
6
|
+
/** The stored command that meta session was started with, if any. */
|
|
7
7
|
export declare const META_PROMPT_ENV = "ZENERA_META_PROMPT";
|
|
8
8
|
/**
|
|
9
9
|
* Where this project's usage goes: the variable when set, else the fine-tuning's
|
package/dist/usage.js
CHANGED
|
@@ -13,7 +13,7 @@ import { warn } from "./term.js";
|
|
|
13
13
|
export const LEDGER_ENV = 'ZENERA_USAGE_LEDGER';
|
|
14
14
|
/** The meta session whose agent started this process, if one did. */
|
|
15
15
|
export const META_SESSION_ENV = 'ZENERA_META_SESSION';
|
|
16
|
-
/** The stored
|
|
16
|
+
/** The stored command that meta session was started with, if any. */
|
|
17
17
|
export const META_PROMPT_ENV = 'ZENERA_META_PROMPT';
|
|
18
18
|
/**
|
|
19
19
|
* Where this project's usage goes: the variable when set, else the fine-tuning's
|
package/dist/validate.d.ts
CHANGED
|
@@ -176,4 +176,15 @@ export interface ValidateOptions {
|
|
|
176
176
|
}
|
|
177
177
|
export declare function validateProject(opts: ValidateOptions): Promise<Report>;
|
|
178
178
|
export declare function availableTools(root: string, config: ProjectConfig): AnyTool<unknown>[];
|
|
179
|
+
/**
|
|
180
|
+
* Pins every `@zenera/*` install in the project's Dockerfile to this `zen`,
|
|
181
|
+
* which is what `zen check --fix` does about `sandbox.pin.stale` and
|
|
182
|
+
* `sandbox.pin.absent`. Only the version after each name is touched, and only
|
|
183
|
+
* on lines `checkPin` reads, so whatever else the file holds is left alone.
|
|
184
|
+
*
|
|
185
|
+
* Returns the project-relative path when the file was rewritten, and nothing
|
|
186
|
+
* when there is no Dockerfile, it resolves outside the project, or it already
|
|
187
|
+
* pins this version — a project that does not load is the report's to explain.
|
|
188
|
+
*/
|
|
189
|
+
export declare function repinDockerfile(root: string): string[];
|
|
179
190
|
//# sourceMappingURL=validate.d.ts.map
|
package/dist/validate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SHAPES, auditModels, credentialFor, envNames, form, resolveBuild, } from '@zenera/engine';
|
|
2
2
|
import { EXA_GROUP, FileSkillProvider, SANDBOX_MOUNT, Sandbox, exaTools, fileTools, frontmatter, projectRegistry, readProjectConfig, sandboxTools, selectTools, toList, } from '@zenera/neo';
|
|
3
|
-
import { existsSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { probeModels } from "./liveness.js";
|
|
7
7
|
import { BuildError, ensurePodmanReady } from "./podman.js";
|
|
8
8
|
import { ownVersion, staleShared } from "./scaffold.js";
|
|
@@ -1315,7 +1315,7 @@ function checkPin(path, rel, add) {
|
|
|
1315
1315
|
}
|
|
1316
1316
|
}
|
|
1317
1317
|
}
|
|
1318
|
-
const fix = `pin them to ${mine}, the version \`zen --version\` reports`;
|
|
1318
|
+
const fix = `pin them to ${mine}, the version \`zen --version\` reports — \`zen check --fix\` does it`;
|
|
1319
1319
|
if (stale.size > 0) {
|
|
1320
1320
|
add({
|
|
1321
1321
|
severity: 'warning',
|
|
@@ -1337,6 +1337,49 @@ function checkPin(path, rel, add) {
|
|
|
1337
1337
|
});
|
|
1338
1338
|
}
|
|
1339
1339
|
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Pins every `@zenera/*` install in the project's Dockerfile to this `zen`,
|
|
1342
|
+
* which is what `zen check --fix` does about `sandbox.pin.stale` and
|
|
1343
|
+
* `sandbox.pin.absent`. Only the version after each name is touched, and only
|
|
1344
|
+
* on lines `checkPin` reads, so whatever else the file holds is left alone.
|
|
1345
|
+
*
|
|
1346
|
+
* Returns the project-relative path when the file was rewritten, and nothing
|
|
1347
|
+
* when there is no Dockerfile, it resolves outside the project, or it already
|
|
1348
|
+
* pins this version — a project that does not load is the report's to explain.
|
|
1349
|
+
*/
|
|
1350
|
+
export function repinDockerfile(root) {
|
|
1351
|
+
let rel;
|
|
1352
|
+
try {
|
|
1353
|
+
const ref = readProjectConfig(root).config.sandbox?.build?.dockerfile;
|
|
1354
|
+
rel = ref === undefined ? undefined : normalise(root, ref);
|
|
1355
|
+
}
|
|
1356
|
+
catch {
|
|
1357
|
+
return [];
|
|
1358
|
+
}
|
|
1359
|
+
if (rel === undefined) {
|
|
1360
|
+
return [];
|
|
1361
|
+
}
|
|
1362
|
+
const path = join(root, rel);
|
|
1363
|
+
let text;
|
|
1364
|
+
try {
|
|
1365
|
+
text = readFileSync(path, 'utf8');
|
|
1366
|
+
}
|
|
1367
|
+
catch {
|
|
1368
|
+
return [];
|
|
1369
|
+
}
|
|
1370
|
+
const mine = ownVersion();
|
|
1371
|
+
const next = text
|
|
1372
|
+
.split('\n')
|
|
1373
|
+
.map((line) => /^\s*#/.test(line)
|
|
1374
|
+
? line
|
|
1375
|
+
: line.replace(ZENERA_INSTALL, (_, name) => `@zenera/${name}@${mine}`))
|
|
1376
|
+
.join('\n');
|
|
1377
|
+
if (next === text) {
|
|
1378
|
+
return [];
|
|
1379
|
+
}
|
|
1380
|
+
writeFileSync(path, next);
|
|
1381
|
+
return [rel.split(sep).join('/')];
|
|
1382
|
+
}
|
|
1340
1383
|
/** Written by the smoke test and read back, to prove the mount is real. */
|
|
1341
1384
|
const SMOKE = 'zen-check';
|
|
1342
1385
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.44",
|
|
4
4
|
"description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"@google/genai": "^2.30.0",
|
|
55
55
|
"@inkjs/ui": "^2.0.0",
|
|
56
56
|
"@openrouter/sdk": "^1.4.25",
|
|
57
|
-
"@zenera/engine": "^1.1.
|
|
58
|
-
"@zenera/neo": "^1.1.
|
|
59
|
-
"@zenera/view": "^1.1.
|
|
57
|
+
"@zenera/engine": "^1.1.44",
|
|
58
|
+
"@zenera/neo": "^1.1.44",
|
|
59
|
+
"@zenera/view": "^1.1.44",
|
|
60
60
|
"google-auth-library": "^10.0.0",
|
|
61
61
|
"ink": "^7.1.1",
|
|
62
62
|
"openai": "^6.0.0",
|