@zenera/cli 1.1.16 → 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 (39) hide show
  1. package/README.md +20 -12
  2. package/dist/catalog.js +19 -20
  3. package/dist/commands/check.js +5 -1
  4. package/dist/commands/init.js +41 -6
  5. package/dist/commands/run.js +4 -0
  6. package/dist/engine.js +6 -0
  7. package/dist/env.d.ts +20 -0
  8. package/dist/env.js +65 -0
  9. package/dist/keys.d.ts +2 -0
  10. package/dist/keys.js +2 -0
  11. package/dist/sandbox.d.ts +9 -0
  12. package/dist/sandbox.js +23 -3
  13. package/dist/scaffold.d.ts +6 -0
  14. package/dist/scaffold.js +56 -15
  15. package/dist/validate.js +57 -15
  16. package/package.json +2 -2
  17. package/templates/editor/.github/copilot-instructions.md +256 -91
  18. package/templates/editor/.github/prompts/review-project.prompt.md +53 -14
  19. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +208 -21
  20. package/templates/editor/.github/prompts/update-spec-from-feedback.prompt.md +142 -0
  21. package/templates/editor/.github/skills/zen-cli/references/check.md +6 -3
  22. package/templates/editor/.github/skills/zen-cli/references/frame.md +5 -3
  23. package/templates/editor/.github/skills/zen-cli/references/projects.md +19 -2
  24. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +6 -0
  25. package/templates/editor/.github/skills/zen-memory/SKILL.md +102 -30
  26. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
  27. package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
  28. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
  29. package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
  30. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
  31. package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
  32. package/templates/editor/.vscode/settings.json +44 -4
  33. package/templates/parts/memory-terms.yaml.tmpl +16 -0
  34. package/templates/parts/memory.yaml.tmpl +12 -0
  35. package/templates/project/SPECIFICATION.md.tmpl +7 -0
  36. package/templates/project/agents/memory-instructions.md +278 -0
  37. package/templates/project/agents.yaml.tmpl +9 -0
  38. package/templates/project/env +22 -0
  39. /package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: zen-memory
3
- description: How agent memory is organised and how to configure it in `agents.yaml` - the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes what to commit and what never to, how to keep a working file under `/memory` and re-run it later, and how to put memory in front of a `zen rag schema` or `zen rag docs` index so a search that already succeeded once is not paid for again.
3
+ description: How agent memory is organised and how to configure it in `agents.yaml` - the `memory:` block, per-agent `access`/`sees`/`writes`/`autoRecall`, the four `memory_*` tools, kinds and relations, how recall ranks and stitches a subgraph, and how to design a memory strategy for a project. Includes the usage rules every memory-enabled project must carry in `agents/memory-instructions.md` (references/memory-instructions.md) and the script that says whether that copy is still current (scripts/check-instructions.sh), what to commit and what never to, how to keep a working file under `/memory` and re-run it later, and how to put memory in front of a `zen rag schema` or `zen rag docs` index so a search that already succeeded once is not paid for again.
4
4
  ---
5
5
 
6
6
  # Memory
@@ -379,11 +379,14 @@ node explaining what it is and the graph cannot acquire orphans. To change a
379
379
  remembered file: copy it into the workspace, edit it there, commit the new one
380
380
  and link it with `SUPERSEDES` to the old.
381
381
 
382
- | Commit as | When |
383
- | --------- | ------------------------------------------------------------------------- |
384
- | `file` | It is runnable, or it is the artifact itself: a script, a config, a query |
385
- | `snippet` | It is a few lines that only make sense read: a request body, a fragment |
386
- | `fact` | It is a sentence. A file containing one sentence is a fact in a costume |
382
+ The node text describes and locates; the file carries the content. Reach for a
383
+ node with no file only when the whole of what you know fits on one line.
384
+
385
+ | Commit as | When |
386
+ | --------- | --------------------------------------------------------------------------- |
387
+ | `file` | It is content - a script, a config, a query, a passage, an assembled answer |
388
+ | `snippet` | It is a few lines that only make sense read, and nothing will ever run them |
389
+ | `fact` | It is one line. A file containing one sentence is a fact in a costume |
387
390
 
388
391
  ## Memory in front of a `zen rag` index
389
392
 
@@ -400,18 +403,26 @@ it is entirely a matter of **what you commit**.
400
403
  | Freshness | as fresh as the last `index` | as stale as it was allowed to get |
401
404
  | Cost of a query | one embedding round trip (`search`), or nothing (`list`/`grep`) | already in the prompt |
402
405
 
403
- So: **never copy index content into memory.** A schema pasted into a node goes
404
- stale silently and the index already answers in milliseconds; a documentation
405
- passage quoted into a node outlives the release it was true for and will look
406
- exactly as authoritative when it is wrong.
406
+ So: **never copy content out of an index that still changes.** A schema pasted
407
+ into a node goes stale silently and the index already answers in milliseconds; a
408
+ documentation passage quoted into a node outlives the release it was true for
409
+ and will look exactly as authoritative when it is wrong.
410
+
411
+ A **pinned** index is the exception, and a real one. Docs indexed from a
412
+ released version or a versioned reference tree cannot change under the memory,
413
+ so a passage taken out of one stays true as long as the project stays on that
414
+ version. Keep it as a `file` node whose text names the documents and line ranges
415
+ and says which version it was. That is as well as the route, never instead of
416
+ it.
407
417
 
408
- What memory should hold is the **route** - the resolution, not the source:
418
+ What memory should hold first is the **route** - the resolution, not the source:
409
419
 
410
420
  | Commit | Kind | Because |
411
421
  | --------------------------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------- |
412
422
  | The endpoint that turned out to be the answer, and its shape | `operation` | A `search_api` costs a round trip and returns five ranked guesses; this names one |
413
423
  | The sequence of calls that was necessary | `plan` | No ranking finds an ordering. It is not in the document |
414
424
  | Where the answer lives in the docs - document name, heading, line range | `fact` | `zen rag docs show` reads it back verbatim and current |
425
+ | The passage itself, when the index is pinned to a released version | `file` | It cannot change under you, and the pointer alone makes the next run re-read it |
415
426
  | A convention the index cannot state - auth, casing, base path, the error envelope | `fact` | It is not in the graph, and it is wrong to re-derive it every run |
416
427
  | Where the index itself is | `fact` | `-d` or `$ZEN_SCHEMA_DB`. An agent cannot guess a path |
417
428
  | A working request body, or a script that drives the API | `snippet` / `file` | The thing you would otherwise write again |
@@ -451,8 +462,12 @@ cheap and settles it.
451
462
  ### The project skill is where this is written down
452
463
 
453
464
  An index used by a project needs a skill in that project describing it - see
454
- `zen-rag-schema` and `zen-rag-docs`. Memory policy belongs in the same skill,
455
- because it is the same subject:
465
+ `zen-rag-schema` and `zen-rag-docs`. The memory policy that is **about the
466
+ index** belongs in that skill, because it is the same subject and it is read at
467
+ the moment the index is. Policy that holds whatever the agent is doing -
468
+ audiences, what must never be written down - goes in
469
+ `agents/memory-policy-instructions.md` instead, which every agent gets whether
470
+ it loads the skill or not.
456
471
 
457
472
  ```md
458
473
  - Recalled routes are a shortcut, not authority. Confirm with `list_api --name`
@@ -460,10 +475,61 @@ because it is the same subject:
460
475
  - After solving something the index made you work for, commit it: the
461
476
  `operation` you ended on, linked to the `task` that asked and any `fact` you
462
477
  had to discover. Phrase the text as the question, not as the operation id.
463
- - Never commit schema text or documentation passages. Commit the pointer -
464
- document, heading, line range - and read it back with `zen rag docs show`.
478
+ - Never commit schema text or documentation passages copied out of a document
479
+ that still changes. Commit the pointer - document, heading, line range -
480
+ alongside what you concluded from it, and read the passage back with
481
+ `zen rag docs show` to check the conclusion still holds.
482
+ - Our docs are indexed from the pinned 1.4 tree, so a passage out of them is
483
+ safe to keep whole: commit it as a `file` node headed with the documents and
484
+ line ranges, next to the pointer and the conclusion.
485
+ ```
486
+
487
+ ## The house rules
488
+
489
+ **Every project that enables memory carries `references/memory-instructions.md`
490
+ in its own `agents/memory-instructions.md`**, as part of turning memory on. That
491
+ file is how the agent is told to use the store: read and search it only through
492
+ the tools, never through `/memory` itself, and what a node must carry to still be
493
+ worth having.
494
+
495
+ `zen init` writes it, because the projects it scaffolds have memory on from the
496
+ first run. Two cases are left to check by hand: a project that turned memory on
497
+ afterwards, and one whose copy has drifted from the reference or was deleted.
498
+
499
+ Neither is checked by eye. The skill ships the check, and it is one command:
500
+
501
+ ```sh
502
+ .github/skills/zen-memory/scripts/check-instructions.sh # the verdict
503
+ .github/skills/zen-memory/scripts/check-instructions.sh diff # every differing line
504
+ .github/skills/zen-memory/scripts/check-instructions.sh fix # copy the reference over
465
505
  ```
466
506
 
507
+ It says whether this project turns memory on at all, exits non-zero on a copy
508
+ that is missing or stale, and prints the `cp` that repairs it. When the only
509
+ difference is trailing whitespace it says so - that is the usual drift, it is
510
+ invisible on screen, and it is the reason reading the two files side by side
511
+ never caught it.
512
+
513
+ It is a copy, not a transcription - this reference already sits in the project,
514
+ because `zen init` and `zen open` write the whole `.github/` tree, and they write
515
+ it from the same file the project's copy came from. What `fix` runs, from the
516
+ project root, is exactly:
517
+
518
+ ```sh
519
+ cp .github/skills/zen-memory/references/memory-instructions.md agents/memory-instructions.md
520
+ ```
521
+
522
+ Everything the project keeps under `agents/` named `<topic>-instructions.md` is
523
+ prepended to every agent, in filename order, so this belongs in its own document
524
+ rather than as another section of `agents/instructions.md` - it arrives with
525
+ memory and it leaves with it.
526
+
527
+ **Keep that file a pure copy.** Project-specific policy - audiences, what must
528
+ never be written down, what an agent commits at the end of a job - goes in
529
+ `agents/memory-policy-instructions.md`, which filename order puts directly after
530
+ it. Then keeping up with a changed reference is the same one command again,
531
+ rather than a hand-patch around prose that has to be preserved.
532
+
467
533
  ## Inspecting and repairing it
468
534
 
469
535
  ```
@@ -524,6 +590,9 @@ agents:
524
590
 
525
591
  ## Review checklist
526
592
 
593
+ - [ ] `.github/skills/zen-memory/scripts/check-instructions.sh` exits zero, so
594
+ `agents/memory-instructions.md` is the reference byte for byte - and
595
+ project policy lives in `agents/memory-policy-instructions.md`.
527
596
  - [ ] `memory.embedding` is set, and was set before the graph had content.
528
597
  - [ ] Every agent that should learn has a `memory:` binding - the top-level
529
598
  block alone enables nothing.
@@ -538,18 +607,21 @@ agents:
538
607
 
539
608
  ## When it goes wrong
540
609
 
541
- | Symptom | Cause |
542
- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
543
- | The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
544
- | `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made |
545
- | Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
546
- | Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Re-embed or change it back |
547
- | Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
548
- | The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test |
549
- | A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction - the **new** node is the source |
550
- | An agent cannot see a node you can | Its `audience` is not in that agent's `sees`. Invisible and missing are the same thing, on purpose |
551
- | "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
552
- | A remembered file will not run | It is not self-contained - it referenced the workspace it was written in |
553
- | Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
554
- | The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
555
- | A second run of the same project refuses to start | The directory lock. A lock whose process is gone is stale and is taken over |
610
+ | Symptom | Cause |
611
+ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
612
+ | The `memory_*` tools are missing | No `memory:` on the agent. They come from the binding, never from `tools:` |
613
+ | `zen memory` says the project has none | Neither a `memory:` block nor an agent binding - nothing is opened and no directory is made |
614
+ | Recall finds a memory only when reworded | No embedder. `zen memory stats` says `embedding none` |
615
+ | Recall finds nothing after changing the embedder | Refused rather than mixed - a manifest records the model. Re-embed or change it back |
616
+ | Vectors fewer than nodes | Some nodes were committed with no embedder; they are only reachable by term overlap |
617
+ | The graph fills with restated requests | The commit rule is not in a prompt or skill. State the "would a later run redo this" test |
618
+ | An agent greps `/memory` or reads `graph.json` | The house-rules block is missing from `agents/memory-instructions.md` - the system prompt never forbids it |
619
+ | A recalled fact cannot be checked or continued | Nodes were committed with no provenance. The block's "say where it came from" rule is what prevents it |
620
+ | Recall returns pointers and the run re-reads it all | Content was committed as `text` alone. A node's text describes and locates; the content belongs in a file |
621
+ | A wrong memory keeps coming back | It was edited instead of superseded, or superseded in the wrong direction - the **new** node is the source |
622
+ | An agent cannot see a node you can | Its `audience` is not in that agent's `sees`. Invisible and missing are the same thing, on purpose |
623
+ | "this agent cannot remember files" | The agent has no workspace, so a path cannot be resolved. Commit without `file` |
624
+ | A remembered file will not run | It is not self-contained - it referenced the workspace it was written in |
625
+ | Recollections are large and unhelpful | `autoRecall.limit` is too high, or node texts describe answers rather than questions |
626
+ | The model recalls a route the API no longer has | Nothing re-checks memory against a rebuilt index. Supersede, and require verification |
627
+ | A second run of the same project refuses to start | The directory lock. A lock whose process is gone is stale and is taken over |
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Whether this project's memory house rules are still the rules.
4
+ #
5
+ # check-instructions.sh diff the copy against the reference
6
+ # check-instructions.sh diff the same, showing every differing line
7
+ # check-instructions.sh fix overwrite the copy from the reference
8
+ #
9
+ # `agents/memory-instructions.md` is a *copy* of the `zen-memory` skill's
10
+ # `references/memory-instructions.md`, and the reference is rewritten by every
11
+ # `zen init` and `zen open`. So the copy goes stale silently, and it drifts by
12
+ # trailing whitespace inside tables, which no one can see by reading. That is
13
+ # the whole reason this is a script and not a checklist line.
14
+ #
15
+ # Run it from anywhere; it finds the project root from its own location.
16
+
17
+ set -eu
18
+ cd "$(dirname "$0")/../../../.."
19
+
20
+ COPY=agents/memory-instructions.md
21
+ REFERENCE=.github/skills/zen-memory/references/memory-instructions.md
22
+ REMEDY="cp $REFERENCE $COPY"
23
+
24
+ if [ ! -f "$REFERENCE" ]; then
25
+ echo "check-instructions.sh: no $REFERENCE in $PWD" >&2
26
+ echo "check-instructions.sh: run \`zen open\` to rewrite the .github tree" >&2
27
+ exit 2
28
+ fi
29
+
30
+ # The project turns memory on when something declares it — the top-level block
31
+ # or an agent's binding — so any `memory:` key in the configuration counts.
32
+ # The config is found by name, in the order a run finds it.
33
+ config() {
34
+ for file in agents.yaml agents.yml agents/agents.yaml agents/agents.yml; do
35
+ if [ -f "$file" ]; then
36
+ echo "$file"
37
+ return 0
38
+ fi
39
+ done
40
+ }
41
+
42
+ uses_memory() {
43
+ # `|| true` because no configuration at all is an answer, not a failure.
44
+ conf="$(config || true)"
45
+ if [ -z "$conf" ]; then
46
+ return 1
47
+ fi
48
+ grep -Eq '^[[:space:]]*(-[[:space:]]+)?memory:' "$conf"
49
+ }
50
+
51
+ # The file with every line's trailing whitespace removed. Two of these being
52
+ # equal is the drift nobody can see by reading, named as such.
53
+ trimmed() {
54
+ sed 's/[[:space:]]*$//' "$1"
55
+ }
56
+
57
+ report() {
58
+ if uses_memory; then
59
+ echo "memory: on"
60
+ else
61
+ echo "memory: off"
62
+ fi
63
+
64
+ if [ ! -f "$COPY" ]; then
65
+ if uses_memory; then
66
+ echo "missing: $COPY"
67
+ echo "remedy: $REMEDY"
68
+ return 1
69
+ fi
70
+ echo "ok: no copy, and nothing needs one"
71
+ return 0
72
+ fi
73
+
74
+ if cmp -s "$REFERENCE" "$COPY"; then
75
+ if ! uses_memory; then
76
+ # Harmless, but it is rules for a capability nothing turned on.
77
+ echo "note: the copy is current, but no agent has a memory: binding"
78
+ fi
79
+ echo "ok: $COPY is the reference, byte for byte"
80
+ return 0
81
+ fi
82
+
83
+ echo "drift: $COPY differs from the reference"
84
+ # Said out loud because it is invisible on screen, and is the usual cause:
85
+ # a trailing space inside a table, or a lost final newline.
86
+ if [ "$(trimmed "$REFERENCE")" = "$(trimmed "$COPY")" ]; then
87
+ echo "drift: trailing whitespace only — nothing you could have seen"
88
+ fi
89
+ echo "remedy: $REMEDY"
90
+ return 1
91
+ }
92
+
93
+ show_diff() {
94
+ report || true
95
+ # `-L` names the two sides, since both files are called the same thing.
96
+ diff -u -L reference -L "$COPY" "$REFERENCE" "$COPY" || true
97
+ # The report's verdict, not the diff's.
98
+ report >/dev/null
99
+ }
100
+
101
+ fix() {
102
+ cp "$REFERENCE" "$COPY"
103
+ echo "wrote: $COPY"
104
+ echo "note: project policy belongs in agents/memory-policy-instructions.md"
105
+ }
106
+
107
+ case "${1:-status}" in
108
+ status) report ;;
109
+ diff) show_diff ;;
110
+ fix) fix ;;
111
+ *)
112
+ echo "usage: check-instructions.sh [status|diff|fix]" >&2
113
+ exit 2
114
+ ;;
115
+ esac
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: zen-review
3
+ description: Checking a project before committing - the four mechanical checks `/review-project` makes before any judgement (`zen check`, the §2.5 path sweep, the memory house-rules copy, the spec-sync record), what each one owns, what their exit codes mean, and what deliberately has no check because no tool can make it. Load before running `/review-project`, before saying a project is ready, and whenever a review finding needs verifying rather than restating.
4
+ ---
5
+
6
+ # Reviewing a project
7
+
8
+ A review of an agent project is two jobs wearing one name. One half is
9
+ decidable - a schema is valid or it is not, a path resolves or it does not, a
10
+ copy matches its reference or it does not - and a human doing it by hand gets it
11
+ wrong in the same three places every time. The other half is judgement: whether
12
+ an agent has one job, whether a description routes, whether a number is in the
13
+ wrong file.
14
+
15
+ This skill owns the first half, so the prompt can be all of the second.
16
+
17
+ ## The one command
18
+
19
+ From the project root, or anywhere at all:
20
+
21
+ ```sh
22
+ .github/skills/zen-review/scripts/review.sh
23
+ ```
24
+
25
+ It runs four checks in the order a reviewer needs them and prints a summary:
26
+
27
+ | Step | Runs | Owns |
28
+ | ----------- | ------------------------------------------ | -------------------------------------------- |
29
+ | `zen check` | the CLI | everything decidable about the configuration |
30
+ | `paths` | `zen-review/scripts/check-paths.sh` | paths a running agent cannot open - §2.5 |
31
+ | `memory` | `zen-memory/scripts/check-instructions.sh` | the house-rules copy, byte for byte - §5.3 |
32
+ | `spec-sync` | `zen-spec-sync/scripts/snapshot.sh status` | whether the record of the last pass is sound |
33
+
34
+ Every step runs even when an earlier one fails, because a review that stops at
35
+ the first problem is one somebody has to run four times. The exit code is `1` if
36
+ any step wants attention and `0` if none does. `review.sh fast` passes
37
+ `--no-models --no-sandbox` to `zen check`, which is the right call while
38
+ iterating and the wrong one before committing - a key that authenticates says
39
+ nothing about the model id it is spent on.
40
+
41
+ Nothing below matters while `zen check` fails. Report what it said and fix that
42
+ first.
43
+
44
+ ## The two scripts this skill ships
45
+
46
+ ### `check-paths.sh` - the §2.5 sweep
47
+
48
+ A running agent's whole world is four paths: `/workspace`, `/assets`, `/skills`
49
+ and `/memory`. Every path-shaped token in `agents/*instructions.md`,
50
+ `agents/prompts/*.md` and a skill's markdown is read by a model as an
51
+ instruction to open something, so anything outside those four is an instruction
52
+ to open a file that is not there.
53
+
54
+ ```sh
55
+ .github/skills/zen-review/scripts/check-paths.sh # each candidate, with file and line
56
+ .github/skills/zen-review/scripts/check-paths.sh raw # the tokens alone, unfiltered
57
+ ```
58
+
59
+ It exits `1` whenever it prints a candidate, because the list is meant to be
60
+ emptied or explained rather than skimmed. It strips urls before tokenising, skips
61
+ a handful of English constructions that contain a slash, and leaves
62
+ `agents/memory-instructions.md` out entirely - that file is worked examples by
63
+ design, and `check-instructions.sh` is the check it actually needs.
64
+
65
+ Everything it does print is either noise the script has not learned yet or a
66
+ real finding. There is no third case. A candidate that is genuinely noise is a
67
+ one-line addition to `NOISE` in the script - **upstream**, see below, not in the
68
+ copy sitting in this project.
69
+
70
+ `raw` is the sweep with nothing filtered, for when you suspect the filter.
71
+
72
+ ### `check-instructions.sh` - the memory house rules
73
+
74
+ Lives in the `zen-memory` skill, because it is about memory and it is read at
75
+ the moment that skill is:
76
+
77
+ ```sh
78
+ .github/skills/zen-memory/scripts/check-instructions.sh # the verdict
79
+ .github/skills/zen-memory/scripts/check-instructions.sh diff # and every differing line
80
+ .github/skills/zen-memory/scripts/check-instructions.sh fix # copy the reference over
81
+ ```
82
+
83
+ `agents/memory-instructions.md` must be a byte-identical copy of the `zen-memory`
84
+ skill's `references/memory-instructions.md`, and the reference is rewritten by
85
+ every `zen init` and `zen open`, so the copy goes stale without anyone touching
86
+ it. It drifts by **trailing whitespace inside tables**, which is invisible on
87
+ screen - this is the check no one should be asked to make by eye. When the
88
+ difference is whitespace alone the script says so, because that is the usual
89
+ cause and the least believable finding.
90
+
91
+ `fix` is the one-line `cp` and nothing else. Project policy belongs in
92
+ `agents/memory-policy-instructions.md`, which filename order puts directly after
93
+ the copy - see the `zen-memory` skill.
94
+
95
+ ## What has no check, on purpose
96
+
97
+ Not because it would be hard, but because the answer would be wrong:
98
+
99
+ - **Whether an agent has one job.** Read the prompt.
100
+ - **Whether a description routes.** Two overlapping descriptions both parse.
101
+ - **Whether a fact belongs in a skill.** A threshold in a prompt is valid YAML.
102
+ - **Whether a tool grant is justified.** `zen check` confirms the selector
103
+ resolves, never that the agent needs it.
104
+ - **Whether the newest spec-sync history entry is honest.** A pass can claim
105
+ anything; only reading the diff says whether it did it.
106
+
107
+ That list is `/review-project`, and §9 of the copilot instructions is the long
108
+ form. Run the script first so the prompt is spent on this.
109
+
110
+ ## Where these scripts live, and why edits to them vanish
111
+
112
+ `zen init` and `zen open` rewrite the whole `.github/` tree from the version of
113
+ `zen` in hand. These scripts arrive with it, so a change made to the copy in a
114
+ project is gone at the next `open`. A filter that should hold for every project
115
+ belongs in the distribution - `packages/cli/templates/editor/.github/skills/` in
116
+ the @zenera/neo repository - and reaches this project the next time the tree is
117
+ written.
118
+
119
+ Do not confuse these with a project's own scripts. `agents/skills/<name>/scripts/`
120
+ is run by **the project's agents**, inside the sandbox, at
121
+ `/skills/<name>/scripts/…`. `.github/skills/<name>/scripts/` is run by **the
122
+ editor's assistant**, on the host, at a workspace path. The two have different
123
+ readers and neither can see the other.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Every path-shaped token a running agent would be told to open, minus the ones
4
+ # that exist. The §2.5 sweep, as a script rather than a command to retype.
5
+ #
6
+ # check-paths.sh each candidate, with the file and line it is on
7
+ # check-paths.sh raw the tokens alone, unfiltered — the sweep itself
8
+ #
9
+ # A running agent sees four paths: /workspace, /assets, /skills and /memory.
10
+ # Everything else printed here is either noise this script has not learned yet
11
+ # or an instruction to open a file that is not there. There is no third case,
12
+ # which is why this exits non-zero whenever it prints one: the list is meant to
13
+ # be emptied or explained, not skimmed.
14
+ #
15
+ # Run it from anywhere; it finds the project root from its own location.
16
+
17
+ set -eu
18
+ cd "$(dirname "$0")/../../../.."
19
+
20
+ # What a path looks like in prose. §2.5 of the copilot instructions describes
21
+ # the sweep and sends you here for it, so this is the only copy of the pattern.
22
+ # Every segment after the first is taken too: matched one segment at a time,
23
+ # `/skills/house_style/examples.md` comes back as a mount that resolves *and* a
24
+ # stray `/examples.md`, and that fragment is noise on every run of every project.
25
+ TOKEN='[~.]?/?[A-Za-z0-9_.-]*(/[A-Za-z0-9_.*-]+)+'
26
+
27
+ # The four mounts, and only those: a token under one of them is a path that
28
+ # resolves.
29
+ MOUNTS='^/(workspace|assets|skills|memory)(/|$)'
30
+
31
+ # English that happens to contain a slash, and the placeholder paths of a
32
+ # worked example. Add to this rather than teaching a reviewer to skip the same
33
+ # line every run.
34
+ NOISE='^(and/or|he/she|his/her|input/output|read/write|w/o|n/a|N/A|24/7|km/h)$|^(path|foo)/'
35
+
36
+ # A url, stripped before tokenising rather than filtered after it — the token
37
+ # regex reads `https://acme.dev/docs` as `//acme.dev` *and* `/docs`, and the
38
+ # second half is indistinguishable from a real absolute path once it is alone.
39
+ strip_urls() {
40
+ sed -E 's#[a-zA-Z][a-zA-Z0-9+.-]*://[^][ )"'"'"'`>]*##g' "$1"
41
+ }
42
+
43
+ # The three trees §2.5 names, prose only: a skill's own script is code the
44
+ # sandbox runs, not text rendered into a prompt, and its paths are its own.
45
+ #
46
+ # `agents/memory-instructions.md` is left out on purpose. It is a copy of the
47
+ # reference, full of worked examples, so it fails this sweep by design and
48
+ # every run of it — and it has a check of its own that a sweep cannot make:
49
+ # .github/skills/zen-memory/scripts/check-instructions.sh
50
+ files() {
51
+ {
52
+ if [ -d agents/prompts ]; then find agents/prompts -type f -name '*.md'; fi
53
+ if [ -d agents/skills ]; then find agents/skills -type f -name '*.md'; fi
54
+ for file in agents/*instructions.md; do
55
+ if [ -f "$file" ]; then echo "$file"; fi
56
+ done
57
+ } | grep -v '^agents/memory-instructions\.md$' | sort -u
58
+ }
59
+
60
+ # Every token in those files, urls removed first, and a sentence's full stop
61
+ # trimmed off the end — no path ends in a dot, and `/workspace.` would
62
+ # otherwise read as a mount that does not exist.
63
+ sweep() {
64
+ while IFS= read -r file; do
65
+ if [ -n "$file" ]; then
66
+ strip_urls "$file"
67
+ fi
68
+ done <<EOF
69
+ $(files)
70
+ EOF
71
+ }
72
+
73
+ tokens() {
74
+ sweep | grep -Eoh "$TOKEN" | sed 's/\.*$//' | sort -u
75
+ }
76
+
77
+ candidates() {
78
+ tokens | grep -Ev "$MOUNTS" | grep -Ev "$NOISE" || true
79
+ }
80
+
81
+ # Where a token came from, so a finding names a file and a line rather than
82
+ # sending someone back to grep for it.
83
+ where() {
84
+ while IFS= read -r file; do
85
+ if [ -n "$file" ]; then
86
+ grep -nF -- "$1" "$file" | sed "s|^| $file:|" | cut -d: -f1,2
87
+ fi
88
+ done <<EOF
89
+ $(files)
90
+ EOF
91
+ }
92
+
93
+ report() {
94
+ found=0
95
+ while IFS= read -r token; do
96
+ if [ -n "$token" ]; then
97
+ found=$((found + 1))
98
+ echo "path: $token"
99
+ where "$token"
100
+ fi
101
+ done <<EOF
102
+ $(candidates)
103
+ EOF
104
+ if [ "$found" -eq 0 ]; then
105
+ echo "ok: every path in the three trees is under a mount"
106
+ return 0
107
+ fi
108
+ echo "candidates: $found to explain or fix"
109
+ return 1
110
+ }
111
+
112
+ case "${1:-report}" in
113
+ report) report ;;
114
+ raw) tokens ;;
115
+ *)
116
+ echo "usage: check-paths.sh [report|raw]" >&2
117
+ exit 2
118
+ ;;
119
+ esac
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Every check a review can make without judgement, in the order it makes them.
4
+ #
5
+ # review.sh zen check, then paths, memory, and the spec-sync record
6
+ # review.sh fast the same, with `zen check --no-models --no-sandbox`
7
+ #
8
+ # What it prints is findings. What it cannot see — whether an agent has one job,
9
+ # whether a description is a condition, whether a fact belongs in a skill — is
10
+ # the rest of /review-project, and the reason that prompt still exists.
11
+ #
12
+ # Every step runs even when an earlier one fails: a review that stops at the
13
+ # first problem is one somebody has to run four times. The exit code is 1 if
14
+ # anything wants attention, 0 if nothing does.
15
+ #
16
+ # Run it from anywhere; it finds the project root from its own location.
17
+
18
+ set -u
19
+ cd "$(dirname "$0")/../../../.."
20
+
21
+ SKILLS=.github/skills
22
+ CHECK_FLAGS=""
23
+
24
+ case "${1:-full}" in
25
+ full) ;;
26
+ fast) CHECK_FLAGS="--no-models --no-sandbox" ;;
27
+ *)
28
+ echo "usage: review.sh [full|fast]" >&2
29
+ exit 2
30
+ ;;
31
+ esac
32
+
33
+ SUMMARY=""
34
+ WORST=0
35
+
36
+ # Records one step's verdict for the summary, and remembers the worst of them.
37
+ verdict() {
38
+ SUMMARY="$SUMMARY$1: $2
39
+ "
40
+ if [ "$3" -ne 0 ]; then
41
+ WORST=1
42
+ fi
43
+ }
44
+
45
+ step() {
46
+ printf '\n--- %s ---\n' "$1"
47
+ }
48
+
49
+ step 'zen check'
50
+ if command -v zen >/dev/null 2>&1; then
51
+ # Unquoted on purpose: empty means no flags rather than one empty argument.
52
+ # shellcheck disable=SC2086
53
+ zen check $CHECK_FLAGS
54
+ rc=$?
55
+ if [ "$rc" -eq 0 ]; then
56
+ verdict 'zen check' 'ok' 0
57
+ else
58
+ verdict 'zen check' "exit $rc — nothing below matters until this passes" 1
59
+ fi
60
+ else
61
+ echo "zen is not on PATH — install it, or run the other three alone"
62
+ verdict 'zen check' 'not run' 1
63
+ fi
64
+
65
+ step 'paths'
66
+ "$SKILLS/zen-review/scripts/check-paths.sh"
67
+ rc=$?
68
+ if [ "$rc" -eq 0 ]; then
69
+ verdict 'paths' 'ok' 0
70
+ else
71
+ verdict 'paths' 'candidates to explain or fix' 1
72
+ fi
73
+
74
+ step 'memory'
75
+ "$SKILLS/zen-memory/scripts/check-instructions.sh"
76
+ rc=$?
77
+ if [ "$rc" -eq 0 ]; then
78
+ verdict 'memory' 'ok' 0
79
+ else
80
+ verdict 'memory' 'the house rules are missing or stale' 1
81
+ fi
82
+
83
+ step 'spec-sync'
84
+ if [ -f SPECIFICATION.md ]; then
85
+ # `status` reports a tampered baseline rather than failing on one, so the
86
+ # verdict has to read what it said.
87
+ state="$("$SKILLS/zen-spec-sync/scripts/snapshot.sh" status)"
88
+ rc=$?
89
+ printf '%s\n' "$state"
90
+ if [ "$rc" -ne 0 ]; then
91
+ verdict 'spec-sync' "exit $rc" 1
92
+ elif printf '%s' "$state" | grep -q 'mode: tampered'; then
93
+ verdict 'spec-sync' 'the baseline does not match its own manifest' 1
94
+ else
95
+ verdict 'spec-sync' "$(printf '%s' "$state" | sed -n 's/^mode: /mode /p')" 0
96
+ fi
97
+ else
98
+ echo "no SPECIFICATION.md — this project keeps no spec-sync record"
99
+ verdict 'spec-sync' 'not applicable' 0
100
+ fi
101
+
102
+ step 'summary'
103
+ printf '%s' "$SUMMARY"
104
+ exit "$WORST"