@zenera/cli 1.1.43 → 1.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/commands/completion.d.ts +1 -1
  2. package/dist/commands/completion.js +4 -4
  3. package/dist/commands/meta.js +10 -10
  4. package/dist/completion.js +1 -1
  5. package/dist/meta/editor.js +43 -3
  6. package/dist/meta/finetune/command.js +2 -2
  7. package/dist/meta/finetune/improvements.js +1 -1
  8. package/dist/meta/prompts.d.ts +8 -3
  9. package/dist/meta/prompts.js +42 -19
  10. package/dist/usage.d.ts +1 -1
  11. package/dist/usage.js +1 -1
  12. package/package.json +4 -4
  13. package/templates/editor/.github/copilot-instructions.md +35 -22
  14. package/templates/editor/.github/{prompts/analyze.prompt.md → skills/analyze/SKILL.md} +6 -3
  15. package/templates/editor/.github/{prompts/dataset.prompt.md → skills/dataset/SKILL.md} +6 -3
  16. package/templates/editor/.github/{prompts/finetune.prompt.md → skills/finetune/SKILL.md} +7 -4
  17. package/templates/editor/.github/{prompts/new-agent.prompt.md → skills/new-agent/SKILL.md} +3 -1
  18. package/templates/editor/.github/{prompts/new-skill.prompt.md → skills/new-skill/SKILL.md} +3 -1
  19. package/templates/editor/.github/{prompts/project-review.prompt.md → skills/project-review/SKILL.md} +6 -0
  20. package/templates/editor/.github/{prompts/spec-apply-feedback.prompt.md → skills/spec-apply-feedback/SKILL.md} +2 -0
  21. package/templates/editor/.github/skills/spec-edit/SKILL.md +324 -0
  22. package/templates/editor/.github/{prompts/spec-sync-project.prompt.md → skills/spec-sync-project/SKILL.md} +41 -15
  23. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +1 -0
  24. package/templates/editor/.github/skills/zen-cli/SKILL.md +21 -20
  25. package/templates/editor/.github/skills/zen-cli/references/finetune.md +1 -1
  26. package/templates/editor/.github/skills/zen-cli/references/meta.md +2 -2
  27. package/templates/editor/.github/skills/zen-code-python/SKILL.md +1 -0
  28. package/templates/editor/.github/skills/zen-dataset/SKILL.md +1 -0
  29. package/templates/editor/.github/skills/zen-finetune/SKILL.md +2 -1
  30. package/templates/editor/.github/{prompts/finetune-apply.prompt.md → skills/zen-finetune-apply/SKILL.md} +7 -4
  31. package/templates/editor/.github/skills/zen-inspect/SKILL.md +1 -0
  32. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +1 -0
  33. package/templates/editor/.github/skills/zen-instructions/SKILL.md +1 -0
  34. package/templates/editor/.github/skills/zen-memory/SKILL.md +1 -0
  35. package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +1 -0
  36. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +1 -0
  37. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +1 -0
  38. package/templates/editor/.github/skills/zen-review/SKILL.md +34 -0
  39. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +1 -1
  40. package/templates/editor/.github/skills/zen-review/scripts/check-receipt.mjs +95 -0
  41. package/templates/editor/.github/skills/zen-review/scripts/review.sh +27 -2
  42. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +2 -1
  43. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -1
  44. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +115 -20
  45. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +83 -2
  46. package/templates/editor/.github/skills/zen-topology/SKILL.md +1 -0
  47. package/templates/project/agents/prompts/default.md.tmpl +3 -3
  48. package/templates/editor/.github/prompts/spec-edit.prompt.md +0 -250
@@ -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 stored prompts it
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 stored prompts it
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 stored prompts',
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 prompts are the files in .github/prompts/ — the same ones an editor',
37
- 'offers as slash commands. Where there is nothing to offer, file names are.',
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);
@@ -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, PROMPT_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";
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 a stored prompt',
88
- ' zen meta run [project] pick one of its stored prompts',
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 prompt file in .github/prompts/<name>.prompt.md.',
96
- ' [words] Appended to that prompt as a final line.',
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 the stored prompts',
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 stored prompts.',
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 stored prompts.',
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 stored prompts are what is left to offer.
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 prompts in ${project.name}/${PROMPT_DIR}`));
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) : ''])));
@@ -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 prompt.
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 = [
@@ -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 prompt files and skills the
31
- // editor picks up from the same place.
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 on the line after /finetune-apply');
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);
@@ -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 — `mode`, `tools`, `description` — and
12
- * none of it means anything to copilot, so only the body is sent. `description`
13
- * is kept for the one line of narration that says which prompt is running.
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
@@ -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 prompts
5
+ // Stored commands
6
6
  //
7
- // Copilot reads `AGENTS.md`, `.github/skills/` and `.github/agents/`, but it
8
- // has no notion of `.github/prompts/*.prompt.md` — the files an editor offers
9
- // as slash commands. Reading one and handing over its body is the whole of
10
- // `zen meta run`, and it is why that subcommand exists.
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
- return `${dir}/${PROMPT_DIR}/${bare}${PROMPT_SUFFIX}`;
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 — `mode`, `tools`, `description` — and
24
- * none of it means anything to copilot, so only the body is sent. `description`
25
- * is kept for the one line of narration that says which prompt is running.
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 prompt named ${name.replace(/^\//, '')}`, known.length > 0
54
+ throw usageError(`no command named ${name.replace(/^\//, '')}`, known.length > 0
43
55
  ? `try: ${known.map((p) => `/${p}`).join(', ')}`
44
- : `put one in ${PROMPT_DIR}/`);
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 { readdir } = await import('node:fs/promises');
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
- const names = await readdir(`${dir}/${PROMPT_DIR}`);
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/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 prompt that meta session was started with, if any. */
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 prompt that meta session was started with, if any. */
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.43",
3
+ "version": "1.1.45",
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.43",
58
- "@zenera/neo": "^1.1.43",
59
- "@zenera/view": "^1.1.43",
57
+ "@zenera/engine": "^1.1.45",
58
+ "@zenera/neo": "^1.1.45",
59
+ "@zenera/view": "^1.1.45",
60
60
  "google-auth-library": "^10.0.0",
61
61
  "ink": "^7.1.1",
62
62
  "openai": "^6.0.0",
@@ -1,7 +1,7 @@
1
1
  # Copilot instructions - agent projects
2
2
 
3
3
  > Written by `zen init` into `.github/copilot-instructions.md`, alongside the
4
- > prompt files and skills in the same tree (§2.4). VS Code loads it
4
+ > skills and commands in the same tree (§2.4). VS Code loads it
5
5
  > automatically for every request in this folder, so it is the standing brief
6
6
  > for anyone - human or agent - editing this project.
7
7
  >
@@ -35,12 +35,15 @@ programmed: what an agent knows, which model answers, and which of the tools
35
35
  6. Every change must still load: `agents.yaml` is validated strictly at load,
36
36
  and `zen check` says so before a model is ever called.
37
37
 
38
- ### 0.1 Copilot is the meta-agent: load the skill for every capability in use
38
+ ### 0.1 Copilot is the meta-agent: load skills for the capabilities in scope
39
39
 
40
40
  Copilot is always the meta-agent that creates and changes target projects. Load
41
41
  the editor's `zen-cli` skill before deciding how a target project should run,
42
42
  be validated, or use command-line capabilities. It is the command contract; do
43
43
  not reconstruct `zen` arguments from memory or call `--help` to discover them.
44
+ When the task is to bring a project into compliance with `SPECIFICATION.md`,
45
+ load `zen-spec-sync` first; its narrow tiers contain the exact validation
46
+ commands and do not require loading `zen-cli` as well.
44
47
 
45
48
  **Every runtime capability a project turns on carries obligations that are not
46
49
  visible in `agents.yaml` and that `zen check` does not report.** Before
@@ -54,20 +57,23 @@ code, `zen-topology` for `handoffs:` or `fork:` on any agent,
54
57
  `zen-cli` always. A capability that is already
55
58
  configured and already passing `zen check` is exactly the case that looks
56
59
  finished and is not: the obligation lives in the skill, so a review that never
57
- opens the skill cannot find what is missing. This applies whatever prompted the
58
- change - a task scoped to `SPECIFICATION.md` does not narrow it. There is one
59
- exception: a `/spec-sync-project` pass that the `zen-spec-sync` skill classes as
60
- **spec-only**. It touches no agent, prompt, skill or wiring, so there is no
61
- obligation for it to meet.
60
+ opens the skill cannot find what is missing. Outside a specification-sync pass,
61
+ this applies whatever prompted the change. During an incremental sync, load only
62
+ skills for capabilities its changed requirements, files, keys or unresolved
63
+ work touch. A full pass still loads every enabled capability's skill. The
64
+ **spec-only** and **model-reference-only** tiers load no capability skill unless
65
+ the work expands beyond their eligibility rules.
62
66
 
63
67
  **Load `zen-instructions` before writing, editing, reviewing or deleting any
64
68
  `agents/*instructions.md`.** They are the house rules, prepended to every
65
- agent's prompt, and three of them are `zen`'s own copies rather than the
69
+ agent's prompt, and four of them are `zen`'s own copies rather than the
66
70
  project's: an edit made inside one is lost at the next `zen check --fix`, `zen
67
71
  init` or `zen open`, with no diff to show for it. The skill is what says which
68
- three those are, where a project's own rules on the same subjects go instead,
72
+ four those are, where a project's own rules on the same subjects go instead,
69
73
  how `requires:` conditions a document, and what filename order decides. None of
70
74
  that is visible in the file being edited.
75
+ Replacing generated copies with `zen check --fix` is maintenance, not manual
76
+ house-rule editing, and does not by itself require loading `zen-instructions`.
71
77
 
72
78
  **Load `zen-code-python` before writing, editing or reviewing any prompt, skill
73
79
  or house rule for an agent that writes or runs code** - any agent holding
@@ -251,8 +257,7 @@ Two consequences that drive nearly every design rule in this document:
251
257
  my-project/
252
258
  ├── .github/ the editor's brief - §2.4
253
259
  │ ├── copilot-instructions.md this file
254
- │ ├── prompts/*.prompt.md tasks you invoke by name
255
- │ └── skills/*/SKILL.md reference the editor loads on demand
260
+ │ └── skills/*/SKILL.md zen-* reference loaded on demand; the rest /commands
256
261
  ├── .env this project's environment - NEVER committed
257
262
  ├── .spec-sync/ what the last /spec-sync-project applied - §0.1
258
263
  ├── agents.yaml who exists, what they may reach for
@@ -349,15 +354,18 @@ agents do, for the same reason - it is a prefix somebody pays for:
349
354
 
350
355
  - **`copilot-instructions.md`** is always on. Everything in it is loaded for
351
356
  every request in this folder, so it holds only what is true of every task.
352
- - **`.github/skills/<name>/SKILL.md`** is reference the editor loads when its
353
- `description` matches what you asked. Put long, occasional material here -
354
- a command surface, a vendor's quirks, a format spec - not in the file above.
355
- The `description` is the routing key; §3.4 applies to these as much as to the
356
- project's own skills.
357
- - **`.github/prompts/<name>.prompt.md`** is a task you invoke by name (`/name`),
358
- with `mode: agent` and a `description` in its frontmatter. Write one when a
359
- job is done repeatedly and has a right order - adding an agent, adding a
360
- skill, reviewing before a commit.
357
+ - **`.github/skills/zen-<name>/SKILL.md`** is reference the editor loads when
358
+ its `description` matches what you asked. Put long, occasional material here
359
+ - a command surface, a vendor's quirks, a format spec - not in the file above.
360
+ The `description` is the routing key; §3.4 applies to these as much as to the
361
+ project's own skills. `user-invocable: false` keeps them out of the `/` menu.
362
+ - **`.github/skills/<name>/SKILL.md`** without the `zen-` prefix is a command
363
+ you invoke by name (`/name`, or `zen meta run /name`), with
364
+ `disable-model-invocation: true` so the model never starts one on a hunch,
365
+ and an `argument-hint` when it takes words. Write one when a job is done
366
+ repeatedly and has a right order - adding an agent, adding a skill, reviewing
367
+ before a commit. It holds the order of the work and loads the `zen-*` skills
368
+ that hold the rules.
361
369
 
362
370
  `zen init` and `zen open` rewrite this whole tree from the version of `zen` in
363
371
  hand, so **edits inside `.github/` do not survive**. Project-specific conventions
@@ -1885,6 +1893,11 @@ the spec-sync record, in that order:
1885
1893
 
1886
1894
  Run it first and read what it says. Everything else below is judgement, which is
1887
1895
  why it is still a list. The `zen-review` skill has the detail.
1896
+ Within a specification-sync pass, reuse its final check with
1897
+ `review.sh --skip-check` instead of probing again, and apply the sync skill's
1898
+ explicit tier scope to this checklist. Use `--skip-check no-sandbox` only for an
1899
+ eligible model-reference-only pass. Independent reviews still cover the whole
1900
+ checklist.
1888
1901
 
1889
1902
  Before finishing any change here:
1890
1903
 
@@ -2086,8 +2099,8 @@ install` is an `image:` that was never set - §3.7.
2086
2099
 
2087
2100
  This file is loaded on every request in this repository, so it is subject to its
2088
2101
  own rules: stable, factual, no hedging. Before adding a section, ask whether it
2089
- is true of every task - if it is not, it is a skill under `.github/skills/`, or
2090
- a prompt file under `.github/prompts/` if it is a procedure (§2.4).
2102
+ is true of every task - if it is not, it is a `zen-*` skill under
2103
+ `.github/skills/`, or a command skill there if it is a procedure (§2.4).
2091
2104
 
2092
2105
  Update it when:
2093
2106
 
@@ -1,5 +1,8 @@
1
1
  ---
2
+ name: analyze
2
3
  description: Analyze one recorded run of this project - its health, what it wasted, and what to change in the instructions. Name the run (directory or id) after the command, or get the most recent one.
4
+ argument-hint: '[run directory or run id]'
5
+ disable-model-invocation: true
3
6
  ---
4
7
 
5
8
  Load the **zen-analyze-run** skill and follow it to analyze one run of this
@@ -10,10 +13,10 @@ audit cannot be graded without it.
10
13
 
11
14
  ## Which run
12
15
 
13
- The line after this prompt may name the run - a run directory or a run id.
14
- If it does, use it. Words after it like `case=<id> ... feedback=<file>` mean
16
+ The words given with the command may name the run - a run directory or a run
17
+ id. If they do, use it. Words after it like `case=<id> ... feedback=<file>` mean
15
18
  the tuning loop sent you: follow section 7 of the skill and write that file.
16
- A `## The brief` section below that line is what the loop already ran for
19
+ A `## The brief` section below them is what the loop already ran for
17
20
  you: section 1 of the skill says how to use it.
18
21
 
19
22
  If it does not, list this project's most recent runs, newest first:
@@ -1,17 +1,20 @@
1
1
  ---
2
+ name: dataset
2
3
  description: Build or refresh this project's dataset - every case read from its sources with a class, complexity, tags, a verbatim rubric and an anchor - re-reading only the sections that changed since the last revision. Name a source after the command to add it, `rebuild` to read everything again, or a case and what to fix.
4
+ argument-hint: '[source file | rebuild | <case id> <what to fix>]'
5
+ disable-model-invocation: true
3
6
  ---
4
7
 
5
8
  Keep `dataset/` in step with its sources. **Load the `zen-dataset` skill before
6
9
  anything else**, and `zen-cli` for the command reference. The skill holds the
7
- extraction rules and every verb; this prompt is the order of the work. If
10
+ extraction rules and every verb; this command is the order of the work. If
8
11
  loading a skill fails, read `.github/skills/<name>/SKILL.md` with your file tool
9
12
  before going on.
10
13
 
11
14
  You never run cases, never edit `agents/`, and never edit a source. Only
12
15
  `zen meta dataset` writes `dataset/` - never a file tool, never a script.
13
16
 
14
- This prompt is **reentrant**: the state on disk says what to do, so running it
17
+ This command is **reentrant**: the state on disk says what to do, so running it
15
18
  twice in a row does nothing the second time.
16
19
 
17
20
  ## 1. Where things stand
@@ -27,7 +30,7 @@ sections are `changed`, `gone` or `uncovered`.
27
30
 
28
31
  ## 2. What to do
29
32
 
30
- The last line of this prompt may hold words. Then read the table top down and
33
+ Words may be given with the command. Then read the table top down and
31
34
  take the first row that fits:
32
35
 
33
36
  | State | Do |
@@ -1,5 +1,8 @@
1
1
  ---
2
+ name: finetune
2
3
  description: Fine-tune this project against a training set, a batch at a time - every batch first with no memory, fixing the instructions, then with the memory it just built, making the same work cheaper - starting from a 3-case smoke test and doubling each smooth batch, until every case is used and no difficult case is open.
4
+ argument-hint: '[training set] [limit]'
5
+ disable-model-invocation: true
3
6
  ---
4
7
 
5
8
  Tune this project against real queries. Nothing here touches model weights: what
@@ -9,7 +12,7 @@ queries produce.
9
12
 
10
13
  **Load the `zen-finetune` skill before anything else**, and load `zen-inspect`,
11
14
  `zen-analyze-run`, `zen-topology` and `zen-inspect-ask` before you grade, and
12
- `zen-instructions` before you write a policy file. This prompt is the order of the work; the skill
15
+ `zen-instructions` before you write a policy file. This command is the order of the work; the skill
13
16
  is how each step is done, and it holds the rules that are easy to get wrong. If
14
17
  loading a skill fails, read `.github/skills/<name>/SKILL.md` with your file tool
15
18
  before going on - never carry on without it.
@@ -28,7 +31,7 @@ command:
28
31
  | one memory node in full | `zen memory show <node-id> --dir "$RUN/batch/<id>/memory"` |
29
32
  | a whole memory, or what one added to another | `zen memory ls --dir <dir>`, `zen memory diff <base> --dir <dir>` |
30
33
 
31
- This prompt is **reentrant**. Tuning is long and gets interrupted - a run dies, a
34
+ This command is **reentrant**. Tuning is long and gets interrupted - a run dies, a
32
35
  session ends, I stop you mid-batch. Every step leaves its result on disk under
33
36
  `finetune/`, so a second invocation does not start over: it reads that state,
34
37
  works out where the work stopped, says so, and carries on from there unless I
@@ -199,7 +202,7 @@ in your reports instead of re-typing it.
199
202
 
200
203
  ## Where you are - work this out before anything else
201
204
 
202
- Do this every time this prompt runs, including the first:
205
+ Do this every time this command runs, including the first:
203
206
 
204
207
  ```sh
205
208
  .github/skills/zen-finetune/scripts/next.mjs
@@ -208,7 +211,7 @@ Do this every time this prompt runs, including the first:
208
211
  ```
209
212
 
210
213
  `next.mjs` reads the whole `finetune/` tree and prints the next step. Do not
211
- re-derive the state from an `ls`. Map what it says onto this prompt:
214
+ re-derive the state from an `ls`. Map what it says onto this command:
212
215
 
213
216
  | What `next.mjs` says | Go to |
214
217
  | ---------------------------------------------------------------- | --------------------------------------------------------------------------------- |
@@ -1,6 +1,8 @@
1
1
  ---
2
- mode: agent
2
+ name: new-agent
3
3
  description: Add an agent to agents.yaml, with its prompt, tools and handoffs.
4
+ argument-hint: "[the agent's job in one sentence]"
5
+ disable-model-invocation: true
4
6
  ---
5
7
 
6
8
  Add an agent to this project. Ask for the one sentence that states its job
@@ -1,6 +1,8 @@
1
1
  ---
2
- mode: agent
2
+ name: new-skill
3
3
  description: Add a skill under agents/skills, with a description that routes.
4
+ argument-hint: '[what the skill says and when it applies]'
5
+ disable-model-invocation: true
4
6
  ---
5
7
 
6
8
  Add a skill to this project. A skill is curated instruction content - and