@zenera/cli 1.1.15 → 1.1.17

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 (57) hide show
  1. package/README.md +201 -129
  2. package/dist/banner.d.ts +5 -1
  3. package/dist/banner.js +2 -2
  4. package/dist/catalog.js +19 -20
  5. package/dist/command.d.ts +5 -0
  6. package/dist/commands/check.js +5 -1
  7. package/dist/commands/init.js +41 -6
  8. package/dist/commands/run.js +4 -0
  9. package/dist/engine.js +6 -0
  10. package/dist/env.d.ts +20 -0
  11. package/dist/env.js +65 -0
  12. package/dist/keys.d.ts +2 -0
  13. package/dist/keys.js +2 -0
  14. package/dist/main.js +10 -6
  15. package/dist/sandbox.d.ts +9 -0
  16. package/dist/sandbox.js +23 -3
  17. package/dist/scaffold.d.ts +6 -0
  18. package/dist/scaffold.js +56 -15
  19. package/dist/validate.js +57 -15
  20. package/package.json +2 -2
  21. package/templates/editor/.github/copilot-instructions.md +501 -301
  22. package/templates/editor/.github/prompts/new-agent.prompt.md +6 -6
  23. package/templates/editor/.github/prompts/new-skill.prompt.md +7 -7
  24. package/templates/editor/.github/prompts/review-project.prompt.md +54 -15
  25. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +229 -42
  26. package/templates/editor/.github/prompts/update-spec-from-feedback.prompt.md +142 -0
  27. package/templates/editor/.github/skills/zen-cli/SKILL.md +62 -6
  28. package/templates/editor/.github/skills/zen-cli/references/check.md +9 -6
  29. package/templates/editor/.github/skills/zen-cli/references/faker.md +14 -14
  30. package/templates/editor/.github/skills/zen-cli/references/frame.md +11 -9
  31. package/templates/editor/.github/skills/zen-cli/references/inspect.md +3 -3
  32. package/templates/editor/.github/skills/zen-cli/references/keys.md +9 -9
  33. package/templates/editor/.github/skills/zen-cli/references/memory.md +11 -11
  34. package/templates/editor/.github/skills/zen-cli/references/models.md +8 -8
  35. package/templates/editor/.github/skills/zen-cli/references/projects.md +23 -6
  36. package/templates/editor/.github/skills/zen-cli/references/rag.md +191 -56
  37. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -3
  38. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +14 -8
  39. package/templates/editor/.github/skills/zen-memory/SKILL.md +144 -72
  40. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
  41. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +71 -48
  42. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +67 -67
  43. package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
  44. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
  45. package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
  46. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
  47. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
  48. package/templates/editor/.vscode/settings.json +46 -3
  49. package/templates/parts/memory-terms.yaml.tmpl +16 -0
  50. package/templates/parts/memory.yaml.tmpl +12 -0
  51. package/templates/project/SPECIFICATION.md.tmpl +7 -0
  52. package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +1 -1
  53. package/templates/project/agents/memory-instructions.md +278 -0
  54. package/templates/project/agents.yaml.tmpl +9 -0
  55. package/templates/project/assets/README.md +1 -1
  56. package/templates/project/env +22 -0
  57. package/templates/project/sandbox/Dockerfile.tmpl +3 -0
@@ -0,0 +1,12 @@
1
+ # What the agents work out, kept with the project rather than in the session
2
+ # that found it out. `agents/memory-instructions.md` is what they read about
3
+ # using it, and `zen memory` is how you look at it from the outside.
4
+ memory:
5
+ dir: memory
6
+ # Recall by meaning rather than by the words a question happened to reuse.
7
+ # The width is learned from the first response, so swapping this model
8
+ # later is refused rather than quietly mixing two vector spaces.
9
+ embedding: main
10
+
11
+ embeddings:
12
+ main: {{embedding}}
@@ -18,18 +18,25 @@ something in it — and report what was done in enough detail to be checked.
18
18
  - `default` — the entry point and the only agent; nobody to hand work to.
19
19
  - Establishes what it is looking at before it acts: reads a file before
20
20
  changing it, runs a command rather than predicting its output.
21
+ - Remembers what it worked out, so a later session starts from it rather
22
+ than from nothing.
21
23
 
22
24
  ## Tools and boundaries
23
25
 
24
26
  - Files — read, search, create, edit, move, delete. The workspace is the only
25
27
  writable thing, symlinks included.
26
28
  - Shell — commands run in the container, on that same workspace.
29
+ - Memory — a graph of what earlier runs established, recalled before new
30
+ questions and added to deliberately. What goes in it, and what does not, is
31
+ `agents/memory-instructions.md`.
27
32
  - `assets/` is reference material, and read-only.
28
33
  {{web}}
29
34
 
30
35
  ## Environment
31
36
 
32
37
  - Model: `{{modelRef}}`, one for the whole project.
38
+ - Memory: `memory/`, kept with the project and committed except for its
39
+ vectors, which are derived.
33
40
  - Sandbox: built from `sandbox/Dockerfile`, persists between runs, and can
34
41
  reach the network. What the work always needs belongs in the image, not in an
35
42
  instruction that reinstalls it on every run.
@@ -12,4 +12,4 @@ Say what you did, file by file, and say what you deliberately did not do.
12
12
 
13
13
  Every claim about what a command did has to be one you can make because you ran
14
14
  it and read the output. When something cannot be established, say so and say
15
- what would settle it — a guess offered as an answer is worse than no answer.
15
+ what would settle it - a guess offered as an answer is worse than no answer.
@@ -0,0 +1,278 @@
1
+ # Memory
2
+
3
+ This project has a memory: a graph of what earlier runs worked out, kept with
4
+ the project rather than with the session. It is reached **only** through the
5
+ `memory_*` tools - `memory_search` to find, `memory_load` to read,
6
+ `memory_commit` to write.
7
+
8
+ It exists to make the next run cheaper. Work that cost something - a long
9
+ investigation, a fan-out of searches, a build, an invocation found by trial -
10
+ should be paid for once. A recollection is a cache hit: check it, do not
11
+ rebuild it.
12
+
13
+ ## What belongs in it
14
+
15
+ One question decides both what to keep and how far to trust it:
16
+
17
+ **Can the next run get this itself, cheaply and current?**
18
+
19
+ | When the answer is | Keep |
20
+ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
21
+ | one read, one call, one command away | the way to get it - the route, the call, the script, the invocation |
22
+ | assembled from several sources, with a judgement on top | what you concluded, and a pointer to every source it came from |
23
+ | out of something that cannot change - a released spec, a pinned or versioned document | the finding itself, whole, in a file that names where it came from |
24
+ | a reading of the moment - an API response, a query result, what a script printed | the call that produces it, never the reading |
25
+
26
+ Remember the **route** when the answer is cheap, the **answer** when the route
27
+ is expensive, and never a value that is stale by the next run.
28
+
29
+ ## How it is organised
30
+
31
+ A node is one thing worth keeping. It carries a `kind`, a `text` - the only
32
+ thing searched - and optionally a remembered file. An edge says how one node led
33
+ to another.
34
+
35
+ The two halves do different jobs, and the split is the same every time: the
36
+ **text is how the node is found** - what it is, what it establishes, where it
37
+ came from - and the **file is what the node holds**. Only the text is searched,
38
+ and only its first 160 characters are ever shown, so content written into it is
39
+ both unfindable and in the way. Once something is worth committing at all, the
40
+ usual shape is therefore a short description and a file behind it.
41
+
42
+ A node with no file is for what fits on **one line** - a path and a line range,
43
+ a command, a standing instruction, a single stated fact. If what you are keeping
44
+ runs past a line or two, it is content, and content goes in the file.
45
+
46
+ | Kind | Is |
47
+ | ------------ | ------------------------------------------------------------------------------------ |
48
+ | `task` | the request that started a piece of work |
49
+ | `plan` | an approach that worked and would work again |
50
+ | `fact` | a durable truth about this codebase, API or environment |
51
+ | `snippet` | a few lines of code or configuration |
52
+ | `file` | an artifact kept whole - a script to re-run, an answer or passage too big for a text |
53
+ | `operation` | an external call that was made - endpoint, command, query. The call, not its answer |
54
+ | `preference` | a standing instruction from the user |
55
+
56
+ | Relation | Reads as |
57
+ | ------------ | --------------------------------------------------- |
58
+ | `PRODUCED` | the source led to the target being made |
59
+ | `CALLS` | the source invokes the target |
60
+ | `SUPERSEDES` | the source replaces the target; the target is stale |
61
+ | `INFORMED` | the source was context the target was built from |
62
+
63
+ So a remembered piece of work is a subgraph, not a row:
64
+
65
+ ```
66
+ task the request that started this
67
+ PRODUCED → file | snippet | operation what came out of it
68
+ INFORMED ← fact what had to be known first
69
+ file
70
+ CALLS → operation what it calls
71
+ ```
72
+
73
+ What you are shown is ranked, masked and clipped. **Assume a recollection is
74
+ part of the answer, never all of it.**
75
+
76
+ Relation is what decides where the clipping falls. Everything reached across
77
+ `PRODUCED`, `CALLS` or `SUPERSEDES` is the work itself; everything reached
78
+ across `INFORMED` is context, and context is what a short recollection drops
79
+ first - so the answer degrades by losing its background rather than by being cut
80
+ in half. Attach with `INFORMED` what **explains** a node, never what the next run
81
+ needs in order to **use** it: the endpoint a script calls, or the request that
82
+ produced it, belongs on the spine, where it is still there when the budget is
83
+ tight.
84
+
85
+ `INFORMED` also reads in a direction - from the background to the work it fed,
86
+ so a `fact` informs an answer and never the other way round. Written backwards
87
+ it still traverses, and still renders; it just says the answer was context for
88
+ its own source.
89
+
90
+ ## Reading it
91
+
92
+ `memory_search` hands back an outline of a subgraph with each node's text cut to
93
+ 160 characters. `memory_load` is what reads a node whole, by id - load anything
94
+ you are going to rely on.
95
+
96
+ A remembered file appears at `/memory/<id>.<ext>`. Open it or run it, but arrive
97
+ at that path through `memory_load`, never by looking around in the directory.
98
+
99
+ **Never read the store directly.** `/memory` holds `graph.json`, `vectors.f32`,
100
+ `vectors.json` and `manifest.json` - its internal format. Do not open, list,
101
+ grep, `cat` or `find` them, and do not point a shell command at that directory:
102
+
103
+ - it is ranked by **meaning**, so substring matching misses the hits;
104
+ - `graph.json` is every node ever committed, as one line;
105
+ - a raw read bypasses the audience mask and serves **superseded** nodes -
106
+ corrections that were withdrawn - as if they were current;
107
+ - it records no use, so the ranking that decides what gets recalled next decays;
108
+ - another run may be writing, and the manifest is written last.
109
+
110
+ A memory is a record, not an authority - but checking one is not the same as
111
+ redoing it. Every node names where its content came from, so confirm the
112
+ recollection there: the named lines still say what the node says, the route
113
+ still resolves, the command still exists. Work it out from nothing only when
114
+ that check fails, when the source is one that changes under you, or when being
115
+ wrong is expensive. A check that costs what the original investigation cost is
116
+ not a check - it is the investigation again, and the memory bought nothing.
117
+
118
+ ## Searching it
119
+
120
+ Search **before** paying for anything: an investigation, an index query, a
121
+ build, a long command. The point is not to find a pointer - it is to arrive at
122
+ the answer without buying it twice.
123
+
124
+ One `memory_load` is not cheaper than re-reading the single file a node names.
125
+ It is far cheaper than redoing what produced that node: several documents read
126
+ in sequence, a fan-out of searches, a delegated lookup, a long run. Price a
127
+ recollection against what it **saves**, never against one read.
128
+
129
+ Phrase the query as the question you are actually asking, in the words a person
130
+ would use - node texts are written to be found that way. Narrow with `kinds`,
131
+ `newer_than`, `limit`, `max_hops` and `max_nodes` when a recollection comes back
132
+ broad rather than wrong.
133
+
134
+ Recalling nothing is not proof of anything: work it out normally, then commit
135
+ the result so the next run does not repeat it.
136
+
137
+ ## Updating it
138
+
139
+ Commit when **the next run would otherwise have to work this out again** and
140
+ working it out cost more than a `memory_load` will. The measure is what the
141
+ answer COST, not what kind of thing it is: one cheap tool call away, do not;
142
+ five reads, a fan-out and a judgement, commit the judgement and not only the
143
+ five paths.
144
+
145
+ | Worth committing | Not worth committing |
146
+ | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
147
+ | The route through the codebase to a recurring question | A verbatim copy of one living file, which a read gives current |
148
+ | What a schema or docs search resolved to, and what you made of it | The ranked guesses it returned on the way there |
149
+ | The call or command sequence that turned out to be necessary | A single tool call that answers in milliseconds |
150
+ | The exact invocation that finally worked, with its directory | The request restated, or notes on progress |
151
+ | A working script or request body, committed whole | What running it printed - a reading, which the script gives current |
152
+ | An interface that behaved differently from its documentation | What a call returned, which the call gives current |
153
+ | The passage itself, when its source cannot change under you | A passage quoted out of something that can |
154
+ | A proven absence - looked here and here, it is not there | Anything not actually verified |
155
+
156
+ A search over a schema or a documentation index costs a round trip and comes
157
+ back ranked, so it is the thing most worth not paying for twice. Commit what it
158
+ **resolved to** - the operation and its shape, or the document, heading and line
159
+ range - **and what you concluded from reading it**. The pointers alone make the
160
+ next run repeat the reading; the conclusion alone cannot be checked. Both, in
161
+ one subgraph, and the next run answers at once and verifies cheaply.
162
+
163
+ When the index is over something pinned - a released spec, a versioned reference
164
+ tree - go one further and carry the passage itself in a `file` node, headed with
165
+ the documents and line ranges it was taken from. The pointer says where to look
166
+ and the conclusion says what it meant, but only the passage saves the next run
167
+ from opening the sources to find out what they actually said.
168
+
169
+ Then the rules for writing it down:
170
+
171
+ **Commit the whole subgraph in one call.** A script with no record of what asked
172
+ for it, or a fact with nothing that acts on it, is a memory nobody can use.
173
+
174
+ **Say where the content came from.** This is what lets an incomplete
175
+ recollection be continued instead of believed:
176
+
177
+ | The node holds | Name |
178
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
179
+ | A fact about this codebase | the file and the line range it was read from - `src/models/factory.ts:120-168` |
180
+ | Anything spanning several files | every one of them, each with its range, in the order they are read |
181
+ | An API route or a behaviour | the document and operation id, or the request sent and the status that came back |
182
+ | Something a document states | document, heading and line range, next to what it states - the pointer is how the passage is checked, not a substitute for it |
183
+ | Something a command established | the exact command, its arguments, and the directory it ran in |
184
+ | A plan | the steps, and where each step's target lives |
185
+ | A negative result | where you looked, so the search is not repeated |
186
+
187
+ **Write the text as the question.** Not the way the system that produced it
188
+ names things, and with the identifying facts first - only the first 160
189
+ characters appear in a recollection.
190
+
191
+ | Instead of | Write |
192
+ | ----------------- | ------------------------------------------------------------------------------------------ |
193
+ | `fix for the 409` | `A duplicate customer email returns 409, not 422 - retry is wrong (src/billing.ts:88-104)` |
194
+ | `retry.ts` | `Where retries are configured: one policy for all adapters, src/models/factory.ts:40-96` |
195
+
196
+ **Put the content in a file, not in the text.** This is the usual shape, not a
197
+ special case: a script, a config, a query, a request body, a rendered table of
198
+ findings, a passage worth keeping whole, an answer that took several sources to
199
+ assemble - all of it is a `file` node split in two. The `text` describes the
200
+ content and says where it came from; the file holds the content itself, whole
201
+ and exactly as it was - the entire block, not a summary of it. Content in a file
202
+ costs nothing until a `memory_load` asks for it, and comes back at
203
+ `/memory/<id>.<ext>` to be read or run. Commit a node with no file only when the
204
+ whole of what you know is one line long.
205
+
206
+ **Name the sources inside the file as well as in the text.** The two halves
207
+ arrive separately - a recollection shows the text, and only a later
208
+ `memory_load` opens the file - so a file read on its own is an assertion with
209
+ nothing behind it. Open it with a header naming every document, heading and line
210
+ range the content came from, and when it was taken, then the content verbatim:
211
+
212
+ ```markdown
213
+ <!-- reference-docs-1.4/overview.html.md:112-160 and
214
+ reference-docs-1.4/architecture.html.md:38-91
215
+ pinned 1.4 reference tree, read 2026-04-09 -->
216
+
217
+ ...the passages, whole and unedited...
218
+ ```
219
+
220
+ Use whatever comment form the file's own format takes. A header costs two lines
221
+ and is the difference between a copy that can be re-checked against its source
222
+ and one that can only be believed.
223
+
224
+ Make the file self-contained - it will be opened in a different workspace, so it
225
+ takes its inputs as arguments and reads credentials from the environment - and
226
+ say so in the text. **Give it a configurable output path.** `/memory` is
227
+ read-only: a script that writes beside itself fails, while one that takes
228
+ `--out` writes into the workspace and is reusable unchanged. Use a `snippet`
229
+ only for the few lines that are read rather than run.
230
+
231
+ A remembered script runs from where it is - the mount is readable, so
232
+ `python3 /memory/<id>.py` or `node /memory/<id>.js` needs no copy first:
233
+
234
+ ```
235
+ python3 /memory/01JD9Q7X8N2K4M6P8R0T2V4W6Y.py --out ./report.csv
236
+ ```
237
+
238
+ **Read it before running it.** It is code an earlier run wrote, recalled by
239
+ similarity: `memory_load` returns its contents, or its path when it is large, in
240
+ which case read the file. Confirm it is the thing you want, and that its
241
+ arguments are what you think, before executing anything.
242
+
243
+ **Commit what produces the answer, not the material it was made from.** This is
244
+ about what to keep, not what form to keep it in - whatever survives the test
245
+ still goes in a file. A file the project holds: the pointer, never a copy - a
246
+ copy goes stale silently and looks exactly as authoritative when it is wrong. An
247
+ external call: the `operation` - route, auth, the parameters that mattered, the
248
+ status a case returns - never the body that came back. A script: the file and
249
+ the invocation that worked, never its output. What a run ESTABLISHED belongs in
250
+ the node's text - that it works, what it was run against, the verdict that was
251
+ the point; the rows it emitted do not.
252
+
253
+ Two things fall outside that. An answer you assembled from several sources is a
254
+ copy of none of them and exists nowhere in the project, so commit it, with a
255
+ pointer node per source `INFORMED`ing it. And a source that cannot change under
256
+ you (a released specification, a pinned dependency, a versioned reference tree)
257
+ does not go stale silently, so the finding may be kept whole.
258
+
259
+ When it is kept whole, keep it as a `file` node rather than as a longer `text`:
260
+ the text says what the passage establishes and names the documents it came from,
261
+ the file carries the whole block behind its provenance header. Say in the text
262
+ which kind of source it was, so a later run can see why a copy was allowed. A
263
+ pointer alone, with nothing of what the source said, makes the next run open all
264
+ of it again - which is the reading the memory was supposed to buy.
265
+
266
+ A body or a result set may still be worth keeping as a SPECIMEN - a shape to
267
+ write against, a case to reproduce. Commit it as a `file` node whose text says
268
+ what was asked, when, and that it is what came back then, never what the data
269
+ is. Keep credentials and personal data out of the store: it outlives the
270
+ session and every later run can read it.
271
+
272
+ **Correct by superseding, never by editing.** Commit the new node and link it to
273
+ the old one with `SUPERSEDES`. Recall then stops serving the wrong answer
274
+ without destroying the record of why it changed.
275
+
276
+ **Ask whether it will still be true next month.** Memory has no expiry and no
277
+ reviewer. A durable fact about this project survives; a fact about this
278
+ afternoon's state is how a graph becomes confidently wrong.
@@ -29,12 +29,21 @@ sandbox:
29
29
  build:
30
30
  dockerfile: sandbox/Dockerfile
31
31
 
32
+ {{memory}}
33
+
32
34
  agents:
33
35
  - name: default
34
36
  description: The entry point.
35
37
  # Instructions live in agents/prompts/<name>.md and are picked up by
36
38
  # convention — no need to name the file here.
37
39
  #
40
+ # The memory store above is opened for this agent: it can search, load
41
+ # and commit, and it recalls on its own before answering something new.
42
+ # The block form narrows that — `memory:` with `access: read` lets it use
43
+ # what is there without adding to it. Drop the line and it never sees the
44
+ # store at all.
45
+ memory: true
46
+ #
38
47
  # workspace:* is every file tool at once, sandbox:* is the shell. Name
39
48
  # them one by one to be narrower, or subtract: [workspace:*, -delete_file]
40
49
  #
@@ -2,7 +2,7 @@
2
2
 
3
3
  Everything in this folder is mounted at /assets when an agent runs. Every agent
4
4
  in this project can read, list and search it, and no tool of theirs can change
5
- it — so this is where reference material goes: handbooks, specifications,
5
+ it - so this is where reference material goes: handbooks, specifications,
6
6
  schemas, worked examples, the style guide the output is supposed to follow.
7
7
 
8
8
  It is the project's own files that agents get without being asked. The
@@ -0,0 +1,22 @@
1
+ # This project's environment.
2
+ #
3
+ # `zen run` reads this file before it loads the project, and forwards every
4
+ # name in it into the sandbox — so a command the agent runs sees the same
5
+ # variables the run does. Values are forwarded by name, never written onto a
6
+ # command line, and `--no-keys` withholds the whole file.
7
+ #
8
+ # Precedence: what is already set in your shell wins, then this file, then the
9
+ # keyring (`zen key add`). So this is the place for what belongs to *this*
10
+ # project — the credential one of its tools needs, the address of the service
11
+ # it talks to — and the keyring is the place for what belongs to you.
12
+ #
13
+ # It is in .gitignore, and should stay there. Everything else in the project is
14
+ # meant to be read, copied and shared; this is the one file that is not.
15
+ #
16
+ # A credential *file* — GOOGLE_APPLICATION_CREDENTIALS — may be named by a path
17
+ # relative to this directory. It is mounted into the sandbox where the agent
18
+ # can read it, which is worth knowing before you point at one.
19
+
20
+ # OPENAI_API_KEY=
21
+ # ACME_API_TOKEN=
22
+ # ACME_BASE_URL=https://api.example.test
@@ -18,6 +18,9 @@ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
18
18
  RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
19
19
  && ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
20
20
 
21
+ # Keep the heavyweight provider SDKs cached when the pinned CLI release changes.
22
+ RUN npm install -g @anthropic-ai/sdk @google/genai @openrouter/sdk openai
23
+
21
24
  # Pinned to the `zen` that wrote this file, so the tools in the container are
22
25
  # the ones the session outside it was built by. Bump both together.
23
26
  RUN npm install -g @zenera/cli@{{version}} @zenera/rag@{{version}}