@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
@@ -1,4 +1,4 @@
1
- # Projects — `init`, `list`, `open`, `version`
1
+ # Projects - `init`, `list`, `open`, `version`
2
2
 
3
3
  ## `zen init`
4
4
 
@@ -19,28 +19,45 @@ What it writes:
19
19
 
20
20
  ```
21
21
  zenera.json { version, name }
22
- INSTRUCTIONS.md house rules
23
22
  agents.yaml the configuration
23
+ agents/instructions.md house rules, prepended to every agent
24
+ agents/memory-instructions.md more of them, about the memory store
24
25
  agents/prompts/default.md the default agent's prompt
25
26
  agents/skills/ empty, for skills
26
27
  assets/README.md
27
28
  sandbox/Dockerfile the image the sandbox builds
28
29
  sessions/ empty
30
+ .env this project's environment, git-ignored
29
31
  .gitignore
30
32
  .vscode/settings.json editor files
31
33
  .github/ copilot instructions, prompts, this skill
32
34
  ```
33
35
 
34
- The project's own files are never overwritten — `--force` is what allows
36
+ More house rules can be added by hand: any `agents/<topic>-instructions.md` is
37
+ read too, in filename order, and prepended to every agent. A capability that
38
+ needs standing rules - memory, say - gets its own file rather than another
39
+ section of `agents/instructions.md`. Memory's are not written from scratch:
40
+ `agents/memory-instructions.md` is the same file as
41
+ `.github/skills/zen-memory/references/memory-instructions.md`, which init puts
42
+ in the project as well, so a copy that was lost or went stale is restored rather
43
+ than rewritten - `.github/skills/zen-memory/scripts/check-instructions.sh` says
44
+ which of the two it is, and `… fix` does the copy.
45
+
46
+ The project's own files are never overwritten - `--force` is what allows
35
47
  writing into an occupied directory, and the files already there stay. The
36
48
  editor files are the exception: `.vscode/settings.json` and the `.github/` tree
37
49
  are ours and are replaced on every `init` and every `zen open`, so edits to them
38
50
  do not survive.
39
51
 
40
52
  The default agent gets the file tools and a sandboxed shell, plus `exa:*` when
41
- the keyring holds an Exa key.
42
-
43
- **Choosing the model.** Without `--model`, the keyring is asked — not counted.
53
+ the keyring holds an Exa key. It also gets memory: the store is scaffolded in
54
+ `memory/`, the agent is bound to it with `memory: true`, and it is vectorised by
55
+ the chosen provider's embedding model. A provider that publishes none -
56
+ Anthropic - gets the store without an `embeddings:` entry, and recall ranks by
57
+ term overlap until one is added; an entry naming a provider with no embeddings
58
+ API is a project that fails to load, not one that degrades.
59
+
60
+ **Choosing the model.** Without `--model`, the keyring is asked - not counted.
44
61
  Stored credentials are probed, because holding a key is not the same as holding
45
62
  a working one, and the model is picked from a provider that answers. A key from
46
63
  the environment is taken at its word. Each provider has its own scaffolded
@@ -1,14 +1,14 @@
1
- # API search — `zen rag`
1
+ # API search - `zen rag`
2
2
 
3
3
  ```
4
4
  zen rag schema <index|search|list|grep|trace|show|stats> [spec...]
5
5
  ```
6
6
 
7
- Provided by `@zenera/rag` — `npm i -g @zenera/rag` if `zen rag` says it is not
7
+ Provided by `@zenera/rag` - `npm i -g @zenera/rag` if `zen rag` says it is not
8
8
  installed.
9
9
 
10
- Reads an OpenAPI/Swagger document as a **graph** — operations, schemas and the
11
- fields inside them, joined by the `$ref`s between them — and makes it
10
+ Reads an OpenAPI/Swagger document as a **graph** - operations, schemas and the
11
+ fields inside them, joined by the `$ref`s between them - and makes it
12
12
  searchable. The answer to a search is not a list of matches but the connected
13
13
  piece of the API that matched: the operations, the schemas they carry and the
14
14
  fields inside them, printed as text, a diagram, or TypeScript that compiles.
@@ -24,18 +24,18 @@ zen rag schema index <spec...> [--embedding <ref>] [-o <dir>] [--batch <n>]
24
24
 
25
25
  | Flag | Default | Meaning |
26
26
  | ------------------- | ------------- | ------------------------------------------------------------ |
27
- | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
27
+ | `--embedding <ref>` | - | Which embedder makes the vectors. Omit it to see the choices |
28
28
  | `-o`, `--out <dir>` | `./schema-db` | Where the index goes; `$ZEN_SCHEMA_DB` if set |
29
29
  | `--batch <n>` | `96` | Texts per embedding request, and how often progress prints |
30
- | `--no-sources` | — | Keep no copy of the documents in the index |
31
- | `--quiet` | — | No narration |
30
+ | `--no-sources` | - | Keep no copy of the documents in the index |
31
+ | `--quiet` | - | No narration |
32
32
 
33
33
  ```
34
34
  zen rag schema index openapi.yaml --embedding openai:text-embedding-3-small
35
35
  ```
36
36
 
37
37
  Unlike the faker, `$ref`s are **bundled, not dereferenced**: component names are
38
- the node ids and the cycles between them are the edges. `discriminator` is kept —
38
+ the node ids and the cycles between them are the edges. `discriminator` is kept -
39
39
  it is what turns a `oneOf` into a tagged union a compiler can narrow.
40
40
 
41
41
  What lands in `<out>`:
@@ -56,14 +56,14 @@ search with a different model is refused rather than quietly returning nonsense.
56
56
  Every reading command takes `-d`, `--dir`. Without one:
57
57
 
58
58
  1. `$ZEN_SCHEMA_DB`, if it is set.
59
- 2. Otherwise the **nearest index** to the working directory — here, then a
59
+ 2. Otherwise the **nearest index** to the working directory - here, then a
60
60
  short way down, then up a level and again, stopping at your home directory.
61
61
  The one used is named on stderr.
62
62
  3. Otherwise `./schema-db`, so the error names the directory you expected.
63
63
 
64
64
  What is looked for is a `manifest.json`, not a directory called `schema-db`, so
65
65
  an index called anything else is found the same way. Two the same distance away
66
- is refused rather than guessed at — name one with `-d`, or set `ZEN_SCHEMA_DB`
66
+ is refused rather than guessed at - name one with `-d`, or set `ZEN_SCHEMA_DB`
67
67
  once and stop typing it.
68
68
 
69
69
  ## `search`
@@ -80,7 +80,7 @@ methods` searches for the phrase _"list methods"_ and returns ranked guesses;
80
80
  `zen rag schema list methods` is the listing. `search` is also the only read
81
81
  command that embeds, so it is the only slow one.
82
82
 
83
- ### Terms — repeatable, and the field is the point
83
+ ### Terms - repeatable, and the field is the point
84
84
 
85
85
  | Term | Searches |
86
86
  | ----------------------- | ---------------------------------------- |
@@ -103,22 +103,22 @@ search good. A request field belongs in `--input-property`, a response field in
103
103
 
104
104
  | Flag | Default | Meaning |
105
105
  | --------------------------- | ----------- | ------------------------------------------------------- |
106
- | `-d`, `--dir <dir>` | found | Which index — see "Which index gets read" |
106
+ | `-d`, `--dir <dir>` | found | Which index - see "Which index gets read" |
107
107
  | `--embedding <ref>` | the index's | Must be the one the index was built with |
108
108
  | `--direction <d>` | `any` | `input`, `output` or `any` |
109
109
  | `--method-type <t>` | `any` | `read_only`, `read_write` or `any` |
110
- | `--exclude-id <id>` | — | Drop a node. Repeatable |
111
- | `--exclude-method <name>` | — | Drop an operation by name. Repeatable |
112
- | `--exclude-type <name>` | — | Drop a schema by name. Repeatable |
113
- | `--exclude-property <name>` | — | Drop a field by name. Repeatable |
110
+ | `--exclude-id <id>` | - | Drop a node. Repeatable |
111
+ | `--exclude-method <name>` | - | Drop an operation by name. Repeatable |
112
+ | `--exclude-type <name>` | - | Drop a schema by name. Repeatable |
113
+ | `--exclude-property <name>` | - | Drop a field by name. Repeatable |
114
114
  | `--limit <n>` | `5` | Seeds kept per term |
115
115
  | `--max-hops <n>` | `3` | How far apart two hits may be |
116
116
  | `--max-nodes <n>` | `200` | Nodes per result |
117
117
  | `--format <f>` | `text` | `text`, `mermaid`, `mermaid-flowchart`, `ts`, `openapi` |
118
- | `--show-source` | — | Tag each operation and schema with its document |
119
- | `--no-docs` | — | Leave the descriptions out |
120
- | `--interactive` | — | Prompt, search, refine. Needs a terminal |
121
- | `--quiet` | — | No narration |
118
+ | `--show-source` | - | Tag each operation and schema with its document |
119
+ | `--no-docs` | - | Leave the descriptions out |
120
+ | `--interactive` | - | Prompt, search, refine. Needs a terminal |
121
+ | `--quiet` | - | No narration |
122
122
 
123
123
  ```
124
124
  zen rag schema search --method "reset a user password" --format ts
@@ -130,7 +130,7 @@ The exclusions are what turn one search into a session: pass back the ids of
130
130
  what you have already been shown to be shown something else instead of the same
131
131
  thing again.
132
132
 
133
- `--format ts` emits TypeScript closed over its own `$ref`s — everything named is
133
+ `--format ts` emits TypeScript closed over its own `$ref`s - everything named is
134
134
  also declared, so the output compiles on its own.
135
135
 
136
136
  ### `--interactive`
@@ -157,7 +157,7 @@ zen rag schema grep <pattern> [-d <dir>] [--name <p>] [--path <p>] [--kind <k>]
157
157
  ```
158
158
 
159
159
  Exact, and therefore complete. `search` ranks, so it can only hand back the top
160
- of a list — it cannot tell you that something is _not_ there. These can: they
160
+ of a list - it cannot tell you that something is _not_ there. These can: they
161
161
  read `graph.json` directly, with no embedder, no credential and no network.
162
162
 
163
163
  | Flag | For | Meaning |
@@ -176,13 +176,13 @@ read `graph.json` directly, with no embedder, no credential and no network.
176
176
  | `--quiet` | both | No narration |
177
177
 
178
178
  Under `--json`: `{found, truncated, rows}` from `list`, `{found, truncated,
179
- matches}` from `grep`. `grep` takes **one** pattern — quote it if it has
180
- spaces — and `list` **one** subject.
179
+ matches}` from `grep`. `grep` takes **one** pattern - quote it if it has
180
+ spaces - and `list` **one** subject.
181
181
 
182
182
  A pattern with `*` or `?` is a glob matched against the whole string; a plain
183
183
  word is a substring. So `--name password` finds `ResetPasswordPayload`, and
184
184
  `--name "Password*"` finds nothing, because nothing starts with it. `--regex`
185
- makes it a regular expression instead — the only way to say "one of these". On
185
+ makes it a regular expression instead - the only way to say "one of these". On
186
186
  `list` it applies to every pattern; on `grep` it applies to the pattern, while
187
187
  `--name` and `--path` stay globs-or-substrings:
188
188
 
@@ -203,7 +203,7 @@ zen rag schema grep status --path "/invoices/*" --kind property
203
203
  zen rag schema grep token --ids-only | xargs zen rag schema show --format ts
204
204
  ```
205
205
 
206
- No match exits 0 with nothing on stdout — that is the answer, and unlike an
206
+ No match exits 0 with nothing on stdout - that is the answer, and unlike an
207
207
  empty search it is a reliable one. Under `--limit`, `found` is still the true
208
208
  total, so a shortened answer never misreports how much there is.
209
209
 
@@ -219,7 +219,7 @@ zen rag schema trace <pattern|id...> [-d <dir>] [--kind <k>] [--direction <d>]
219
219
  The question `list` and `grep` leave you holding: you have found the field, so
220
220
  **which call can reach it?** `trace` walks up the `$ref`s from every node of
221
221
  that name to the operations that accept or return it, and prints the chain in
222
- between. Also a plain graph read — no embedder, no credential.
222
+ between. Also a plain graph read - no embedder, no credential.
223
223
 
224
224
  ```
225
225
  zen rag schema trace city
@@ -233,7 +233,7 @@ Property:Address.city
233
233
  By hand that is three lookups and a guess: find the field, find what holds
234
234
  `Address`, find what accepts _that_, and hope you followed every branch.
235
235
 
236
- `search` does some of this already — it stitches its seeds into one connected
236
+ `search` does some of this already - it stitches its seeds into one connected
237
237
  piece and prints what each operation accepts and returns. But it links only
238
238
  what **ranked**, only within `--max-hops`, and it never names the chain; and
239
239
  `getUser` and `city` share no word, so the field has to rank on its own and the
@@ -245,14 +245,14 @@ there and are certain.
245
245
  | `--kind <k>` | types + properties | `method`, `type` or `property`. Repeatable |
246
246
  | `--direction <d>` | `any` | Keep only the calls that accept it, or return it |
247
247
  | `--max-hops <n>` | `8` | How far up to walk |
248
- | `--limit <n>` | — | Trace at most n matching nodes |
249
- | `--routes <n>` | — | Operations printed per node; `found` counts them all |
250
- | `--ids-only` | — | Bare operation ids, one per line, for piping |
251
- | `--regex` | — | Read the pattern as a regex; `--case-sensitive` too |
252
- | `--source <name>` | — | Only nodes from one document |
253
- | `--show-source` | — | Print which document each operation came from |
254
-
255
- A bare word is matched the way `list --name` matches it — substring, or a glob
248
+ | `--limit <n>` | - | Trace at most n matching nodes |
249
+ | `--routes <n>` | - | Operations printed per node; `found` counts them all |
250
+ | `--ids-only` | - | Bare operation ids, one per line, for piping |
251
+ | `--regex` | - | Read the pattern as a regex; `--case-sensitive` too |
252
+ | `--source <name>` | - | Only nodes from one document |
253
+ | `--show-source` | - | Print which document each operation came from |
254
+
255
+ A bare word is matched the way `list --name` matches it - substring, or a glob
256
256
  when it has `*` or `?`. A node id (`Type:User`) is taken as that node and not
257
257
  as a pattern. Operations are left out of a name match on purpose: they are
258
258
  where a trace ends.
@@ -263,7 +263,7 @@ zen rag schema trace "*Settings" --kind type
263
263
  zen rag schema trace mfa_secret --ids-only | xargs zen rag schema show --format openapi
264
264
  ```
265
265
 
266
- A node nothing carries prints `no operation reaches it` — a real answer, and
266
+ A node nothing carries prints `no operation reaches it` - a real answer, and
267
267
  one worth having: it means the schema is unreachable in this document.
268
268
 
269
269
  ## `show`
@@ -275,13 +275,13 @@ zen rag schema show [id...] [-d <dir>] [--format <f>] [--exact]
275
275
  ```
276
276
 
277
277
  Prints named nodes with no search in between. Needs no embedder and no
278
- credential — it is a read of the graph.
278
+ credential - it is a read of the graph.
279
279
 
280
280
  Ids are one way in; `--method` and `--type` name things directly, which is
281
281
  usually what you have. A bare name means exactly that name; add `*` to select
282
282
  more than one. `--source <name>` takes a whole document, and `--show-source`
283
283
  tags each node with the document it came from. `--exact` prints only what was
284
- named instead of the neighbourhood around it — with `--format openapi` that is
284
+ named instead of the neighbourhood around it - with `--format openapi` that is
285
285
  a valid, self-contained slice of the specification. Without it the neighbours
286
286
  are stitched in, up to `--max-nodes`.
287
287
 
@@ -315,9 +315,9 @@ documents it came from. Also needs no embedder.
315
315
  | -------------------------- | ------------------------------------------------------------------- |
316
316
  | `search_api` | The search above, with the same fields |
317
317
  | `describe_types` | Named schemas as TypeScript, closed over what they refer to |
318
- | `find_types_with_property` | Every schema with a field of this name — exact lookup, no searching |
319
- | `list_api` | Methods, types or fields by name — complete, and counted in full |
320
- | `grep_api` | Every literal occurrence of a string — the way to prove absence |
318
+ | `find_types_with_property` | Every schema with a field of this name - exact lookup, no searching |
319
+ | `list_api` | Methods, types or fields by name - complete, and counted in full |
320
+ | `grep_api` | Every literal occurrence of a string - the way to prove absence |
321
321
  | `trace_api` | Up from a field or schema to the operations that carry it |
322
322
 
323
323
  Only `search_api` ranks. Reach for the others whenever the question is whether
@@ -328,7 +328,7 @@ found and the endpoint to call is what is actually wanted.
328
328
  They share the group `schema`, so an agent takes them with `schema:*` in its
329
329
  `tools:`.
330
330
 
331
- # Document search — `zen rag docs`
331
+ # Document search - `zen rag docs`
332
332
 
333
333
  ```
334
334
  zen rag docs <index|search|list|grep|show|stats> [args...]
@@ -347,17 +347,17 @@ zen rag docs index <path...> [--embedding <ref>] [-o <dir>] [--chunk-tokens <n>]
347
347
 
348
348
  | Flag | Default | Meaning |
349
349
  | -------------------- | ----------- | ------------------------------------------------------------ |
350
- | `--embedding <ref>` | — | Which embedder makes the vectors. Omit it to see the choices |
350
+ | `--embedding <ref>` | - | Which embedder makes the vectors. Omit it to see the choices |
351
351
  | `-o`, `--out <dir>` | `./docs-db` | Where the index goes; `$ZEN_DOCS_DB` if set |
352
352
  | `--batch <n>` | `96` | Texts per embedding request |
353
353
  | `--chunk-tokens <n>` | `384` | Target chunk size |
354
- | `--quiet` | — | No narration |
354
+ | `--quiet` | - | No narration |
355
355
 
356
356
  Paths may be files, directories or globs. `.md`, `.markdown`, `.txt` and
357
357
  `.text` are read; hidden directories and `node_modules` are not.
358
358
 
359
359
  Every document is **copied into the index**, and every quoted line is read back
360
- out of that copy — so the index is one portable thing and what it quotes is the
360
+ out of that copy - so the index is one portable thing and what it quotes is the
361
361
  document rather than a reconstruction of it. Each document is named by its path
362
362
  relative to the common root of everything indexed, which is what keeps
363
363
  `acme_4.1.0/api/routing.md` and `acme_4.2.0/api/routing.md` apart.
@@ -378,17 +378,19 @@ zen rag docs search [text...] [-f <pattern>] [-s <section>] [--kind <k>]
378
378
 
379
379
  | Flag | Default | Meaning |
380
380
  | ------------------------ | -------- | ---------------------------------------------------- |
381
- | `-f`, `--file <pattern>` | — | Only documents whose name matches. Repeatable |
382
- | `--exclude-file <p>` | — | Drop documents whose name matches |
383
- | `-s`, `--section <name>` | — | Only under this heading, and what nests in it |
384
- | `--kind <k>` | — | `paragraph`, `list`, `table`, `table_row`, `code`, … |
381
+ | `-f`, `--file <pattern>` | - | Only documents whose name matches. Repeatable |
382
+ | `--exclude-file <p>` | - | Drop documents whose name matches |
383
+ | `-s`, `--section <name>` | - | Only under this heading, and what nests in it |
384
+ | `--kind <k>` | - | `paragraph`, `list`, `table`, `table_row`, `code`, … |
385
385
  | `--mode <m>` | `hybrid` | `hybrid`, `vector` or `text` for exact wording |
386
+ | `--text-query <text>` | - | Wording for the full-text leg, instead of `[text]` |
387
+ | `--vector-query <text>` | - | Wording for the vector leg, instead of `[text]` |
386
388
  | `--limit <n>` | `8` | Passages kept |
387
389
  | `-B`, `-A <n>` | `0` | Extra lines quoted before and after each passage |
388
390
  | `--max-lines <n>` | `400` | A ceiling on the whole answer |
389
- | `--exclude-id <id>` | — | Drop a passage already seen. Repeatable |
390
- | `--hits` | — | One line per passage instead of the text |
391
- | `--interactive` | — | Prompt, search, narrow, search again |
391
+ | `--exclude-id <id>` | - | Drop a passage already seen. Repeatable |
392
+ | `--hits` | - | One line per passage instead of the text |
393
+ | `--interactive` | - | Prompt, search, narrow, search again |
392
394
 
393
395
  **Narrowing is the interface.** Nobody finds the right paragraph on the first
394
396
  ask; the second call is the same question inside one part of the tree.
@@ -403,7 +405,114 @@ zen rag docs search --mode text "X-RateLimit-Remaining"
403
405
  A `--file` pattern with `*` or `?` is a glob over the whole document name and
404
406
  a substring otherwise.
405
407
 
406
- ## `list`, `grep`, `show` — no embedder, no credential
408
+ ### Limiting the search scope
409
+
410
+ `--file` selects the documents that are allowed to contribute passages. Repeat
411
+ it to include multiple document families; use `--exclude-file` to subtract a
412
+ family. Both are applied before ranking, so a returned passage cannot come from
413
+ a document outside the selected set. Start with `list files` when the corpus
414
+ names are unknown:
415
+
416
+ ```sh
417
+ zen rag docs list files -d <index-dir>
418
+ zen rag docs search -d <index-dir> --file "manual-*/**" "<question>"
419
+ zen rag docs search -d <index-dir> --file "guide-*/**" --file "reference-*/**" \
420
+ --exclude-file "*-archive/**" "<question>"
421
+ ```
422
+
423
+ `--section` narrows the selected documents again, retaining a named heading and
424
+ all headings below it. It accepts a heading title, structure id, or structure
425
+ path printed by `list sections`. Combine it with `--file` when the same heading
426
+ exists in more than one document. `--kind` scopes by content shape: use `table`
427
+ or `table_row` for limits and matrices, and `code` for commands or examples.
428
+
429
+ ```sh
430
+ zen rag docs list sections -d <index-dir> --file "manual-*/**"
431
+ zen rag docs search -d <index-dir> --file "manual-*/**" --section "Operations" \
432
+ --kind table_row "<question about a limit>"
433
+ ```
434
+
435
+ ### Search strategy
436
+
437
+ ### How retrieval modes work
438
+
439
+ Search has two independent retrieval legs, followed by reciprocal-rank fusion
440
+ and a diversity pass. It does **not** run full-text search and then use a vector
441
+ model to rerank those same results.
442
+
443
+ | Mode | What runs | Best for | Caution |
444
+ | -------- | ---------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
445
+ | `hybrid` | Full-text and vector legs, fused by rank | Default: concepts, explanations, broad questions | A broad corpus can still need `--file` or `--section` |
446
+ | `text` | Full-text only | Exact errors, headers, commands, identifiers, and phrases | Literal wording only; misses paraphrases |
447
+ | `vector` | Vector similarity only | A concept whose wording is unknown or differs from the corpus | Can retrieve topical but imprecise passages |
448
+
449
+ The full-text leg rewards words present in the chunk. The vector leg embeds the
450
+ question and retrieves similar meaning, even with different vocabulary. Hybrid
451
+ adds a rank-based contribution from each leg, so a passage supported by both
452
+ normally rises while a strong result from either leg can still appear. The final
453
+ diversity pass prevents near-identical neighbouring chunks from consuming the
454
+ whole answer.
455
+
456
+ **Use hybrid first.** Ask the user-facing question as a complete sentence. It
457
+ combines semantic similarity with the words in the corpus and is the strongest
458
+ default for concepts, explanations, and broad operational questions:
459
+
460
+ ```sh
461
+ zen rag docs search -d <index-dir> "How is a failed request retried?"
462
+ ```
463
+
464
+ Treat the answer as candidates, not confirmation. Search ranks a fixed number
465
+ of passages, so it cannot prove an exact phrase is absent and may return a
466
+ nearby version or an incident that happens to share terms. Read the returned
467
+ line-numbered passage; then use `show` for its surrounding section or `grep`
468
+ for an exact existence check.
469
+
470
+ **Narrow before rewriting the question.** For a corpus with releases, products,
471
+ or repeated manuals, a broad term can retrieve a relevant-looking answer from
472
+ the wrong part of the tree. Keep the question and progressively constrain it:
473
+
474
+ ```sh
475
+ zen rag docs list files -d <index-dir>
476
+ zen rag docs list sections -d <index-dir> --file "guide-*/**"
477
+ zen rag docs search -d <index-dir> --file "guide-*/**" --section "Operations" \
478
+ "How is a failed request retried?"
479
+ ```
480
+
481
+ Use `--kind table` or `--kind table_row` for a limit, entitlement, or matrix;
482
+ use `--kind code` for a command or configuration sample. Pass returned ids to
483
+ `--exclude-id` when the next answer must be new rather than a repeat.
484
+
485
+ **Use exact lookup for exact claims.** `--mode text` is for a known error,
486
+ header, command, identifier, or phrase. `grep` is stronger when the question is
487
+ whether that exact string appears anywhere, because it counts every match and
488
+ does not depend on vector ranking:
489
+
490
+ ```sh
491
+ zen rag docs search -d <index-dir> --mode text "exact identifier"
492
+ zen rag docs grep -d <index-dir> "exact identifier"
493
+ ```
494
+
495
+ **Split the wording only when it is genuinely two wordings.** The text leg
496
+ wants a distinctive string likely to occur verbatim; the vector leg wants the
497
+ full intent. This is useful for an exact identifier plus an explanatory
498
+ question:
499
+
500
+ ```sh
501
+ zen rag docs search -d <index-dir> --text-query "exact identifier" \
502
+ --vector-query "what it means, its constraints, and how to use it"
503
+ ```
504
+
505
+ Both flags run and fuse the two legs. One on its own runs only that leg.
506
+ `--text-query` and `--vector-query` replace positional `[text]` and settle the
507
+ mode themselves: do not combine them with `[text]` or `--mode`.
508
+
509
+ Split search is an expert control, not a stronger default. A generic text leg
510
+ such as a product name or common noun retrieves every incidental mention and
511
+ can displace the more useful semantic passages. Use it only when the text leg
512
+ is specific enough to add evidence rather than noise; otherwise stay with
513
+ ordinary hybrid search and narrow by file or section.
514
+
515
+ ## `list`, `grep`, `show` - no embedder, no credential
407
516
 
408
517
  ```
409
518
  zen rag docs list <files|sections|tables> [-f <pattern>] [-s <section>]
@@ -417,6 +526,32 @@ total even when `--limit` cuts the rows, so unlike a search it can answer
417
526
  whether a string appears at all. `show` prints a document, a named section, or
418
527
  a line range, verbatim.
419
528
 
529
+ ### Reading a complete section or table
530
+
531
+ Search finds relevant passages, not necessarily every row of a table. When the
532
+ whole section or table is needed, use the exact readers instead of increasing a
533
+ search limit.
534
+
535
+ Read a heading and everything nested beneath it verbatim:
536
+
537
+ ```sh
538
+ zen rag docs show -d <index-dir> "<document-name>" --section "<heading>"
539
+ ```
540
+
541
+ For one complete table, list its metadata first. Each row reports the document
542
+ name and the table's inclusive `line` and `end` range. Give that range to
543
+ `show --lines` to print the complete table, including its header, verbatim:
544
+
545
+ ```sh
546
+ zen rag docs list tables -d <index-dir> --file "<document-name>"
547
+ zen rag docs show -d <index-dir> "<document-name>" --lines <line>-<end>
548
+ ```
549
+
550
+ Narrow `list tables` with `--file` or `--section` when the corpus has many
551
+ tables. `search --kind table` and `search --kind table_row` are useful to find
552
+ a relevant table, but remain ranked retrieval; use the listed line span when
553
+ the answer needs every row.
554
+
420
555
  ## Giving it to an agent
421
556
 
422
557
  `@zenera/rag/docs/tools` exports four tools in the group `docs`:
@@ -424,8 +559,8 @@ a line range, verbatim.
424
559
  | Tool | For |
425
560
  | ------------- | --------------------------------------------------------------- |
426
561
  | `search_docs` | The search above, with the same narrowing parameters |
427
- | `list_docs` | The documents, their headings, or their tables — no ranking |
428
- | `grep_docs` | Every matching line, counted in full — the way to prove absence |
562
+ | `list_docs` | The documents, their headings, or their tables - no ranking |
563
+ | `grep_docs` | Every matching line, counted in full - the way to prove absence |
429
564
  | `read_docs` | A section or a line range, verbatim and complete |
430
565
 
431
566
  Only `search_docs` ranks. Every answer carries line numbers and `read_docs`
@@ -1,4 +1,4 @@
1
- # Running — `zen run`
1
+ # Running - `zen run`
2
2
 
3
3
  ```
4
4
  zen run [project] [prompt] [options]
@@ -49,8 +49,8 @@ git diff | zen run --quiet "summarise this diff"
49
49
 
50
50
  A prompt is a request for an answer, not a conversation to pick up, so it
51
51
  answers the three questions itself: **a fresh session**, **the directory you are
52
- in** as the workspace, and **no confirmation**. Every flag still wins —
53
- `--session`, `--workspace` and `--read-only` override it — and the TUI, where
52
+ in** as the workspace, and **no confirmation**. Every flag still wins -
53
+ `--session`, `--workspace` and `--read-only` override it - and the TUI, where
54
54
  there is someone to ask, still asks.
55
55
 
56
56
  There is no `resume`. A session continues itself, because its state is what it
@@ -1,4 +1,4 @@
1
- # The sandbox — `zen sandbox`
1
+ # The sandbox - `zen sandbox`
2
2
 
3
3
  ```
4
4
  zen sandbox [status|up|pull|clean|disk] [options]
@@ -7,7 +7,7 @@ zen sandbox [status|up|pull|clean|disk] [options]
7
7
  Shell commands run in a container over the session workspace, never on the
8
8
  host. None of this is required: a run does all of it on its own, the first time
9
9
  an agent that can reach a shell is about to start one. These subcommands are for
10
- doing it deliberately — before a demo, in CI, or when diagnosing.
10
+ doing it deliberately - before a demo, in CI, or when diagnosing.
11
11
 
12
12
  | Subcommand | What it does |
13
13
  | ---------- | -------------------------------------------------------------- |
@@ -26,14 +26,14 @@ The engine is Podman. On macOS a machine has to exist and be running; `up`
26
26
  offers to install and start one, and its size follows the project's `cpus` and
27
27
  `memory`.
28
28
 
29
- ## Configuring it — `sandbox:` in `agents.yaml`
29
+ ## Configuring it - `sandbox:` in `agents.yaml`
30
30
 
31
31
  Top level, and again per agent, where an agent's block is merged over the base.
32
32
 
33
33
  | Key | Meaning |
34
34
  | --------- | ---------------------------------------------------------------- |
35
35
  | `image` | The image to run. Mutually exclusive with `build` |
36
- | `build` | `{ dockerfile, context? }` — build one instead. Excludes `image` |
36
+ | `build` | `{ dockerfile, context? }` - build one instead. Excludes `image` |
37
37
  | `cpus` | CPU limit |
38
38
  | `memory` | Memory limit, in MiB |
39
39
  | `network` | Whether the container has one |
@@ -47,11 +47,17 @@ Top level, and again per agent, where an agent's block is merged over the base.
47
47
  `CREDENTIAL`: the keyring has already materialised real credentials into the
48
48
  process environment, and a sandbox is the last place they should be forwarded to.
49
49
 
50
+ The door for those is the project's `.env`, which `zen init` writes and
51
+ `.gitignore` covers. `zen run` reads it before loading the project and forwards
52
+ every name in it into the container **by name**, so the value never reaches an
53
+ argv. `keys: false` and `zen run --no-keys` withhold it along with the API keys.
54
+ The shell's environment wins over the file; the keyring fills in the rest.
55
+
50
56
  ## `persist: true` is usually what you want
51
57
 
52
58
  Only `/workspace` and `/home/agent` survive an ephemeral container. A
53
59
  `pip install` as root, or an `apt-get install`, lands in the image's system
54
- paths and is gone by the next run — so the agent silently reinstalls its
60
+ paths and is gone by the next run - so the agent silently reinstalls its
55
61
  toolchain every single time. `zen init` scaffolds `persist: true` for that
56
62
  reason.
57
63
 
@@ -69,8 +75,8 @@ sandbox:
69
75
  persist: true
70
76
  ```
71
77
 
72
- The tag is content-addressed — a hash of the Dockerfile and every file in its
73
- context — so a changed Dockerfile is a different image and a `persist: true`
78
+ The tag is content-addressed - a hash of the Dockerfile and every file in its
79
+ context - so a changed Dockerfile is a different image and a `persist: true`
74
80
  container can never be left sitting on a stale rootfs. It is built only when
75
81
  that tag is absent, which is safe precisely because the tag follows the content.
76
82
  `zen sandbox pull` forces a rebuild.
@@ -87,5 +93,5 @@ prepared, and the message names the command to run for this platform. `zen check
87
93
  distinguishes the two cases: a broken Dockerfile is an error, a laptop with no
88
94
  Podman is a warning.
89
95
 
90
- `zen sandbox clean` removes every container this CLI created — the way out of a
96
+ `zen sandbox clean` removes every container this CLI created - the way out of a
91
97
  container left on a bad rootfs.