@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,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"
@@ -0,0 +1,197 @@
1
+ ---
2
+ name: zen-spec-sync
3
+ description: Running `/sync-with-spec` as an incremental pass - the committed `.spec-sync` baseline of what the last pass applied, how to scope a pass from the specification and feedback diffs and from what moved in the project itself, the four checks a narrow pass may never skip, the iteration history format, and why the baseline is written last. Load before every `/sync-with-spec`, including the first one on a project that has no baseline yet.
4
+ ---
5
+
6
+ # Syncing a project with its specification, one change at a time
7
+
8
+ `SPECIFICATION.md` is the intent and the files are the implementation, so
9
+ bringing the two together is a pass over the whole project. Done from nothing
10
+ every time, that pass re-reads every prompt, re-derives every decision the last
11
+ one already made, and re-raises questions someone has already answered.
12
+
13
+ A pass is worth repeating cheaply, so it keeps a record of **what it applied**.
14
+ The next pass diffs the specification against that record and works the
15
+ difference. What it must not do is confuse a narrow diff with a narrow
16
+ obligation: the record scopes what you _read and edit_, never what you _verify_.
17
+
18
+ ## What this owns
19
+
20
+ Everything is committed. `.spec-sync/` is a record, not scratch - `.tmp/` is
21
+ scratch.
22
+
23
+ ```
24
+ .spec-sync/
25
+ ├── baseline/
26
+ │ ├── SPECIFICATION.md the spec the last completed pass applied
27
+ │ ├── SPECIFICATION-FEEDBACK.md the questions it had open, if any
28
+ │ ├── agents.yaml the wiring it left behind
29
+ │ ├── agents/ and every instruction, prompt and skill
30
+ │ ├── applied.txt the history entry this baseline belongs to
31
+ │ └── manifest.txt sha256 of every file above
32
+ └── history/
33
+ └── 2026-09-13T104500Z.md one file per pass, never edited after
34
+ ```
35
+
36
+ The baseline mirrors the paths it records, so `baseline/agents/skills/x/SKILL.md`
37
+ is that skill as the last completed pass left it. Both sides are there on
38
+ purpose: the specification and the feedback say what was **asked for**, the
39
+ wiring and the `agents/` tree say what was **built**, and a pass has to know
40
+ which of the two moved.
41
+
42
+ The baseline is **not a copy of the current specification**. It is the
43
+ specification as the last completed pass left it, which is what makes the
44
+ difference between the two mean "work nobody has done yet".
45
+
46
+ `zen init` creates `baseline/` and `history/` empty, and git does not carry an
47
+ empty directory, so **the presence of `.spec-sync/` proves nothing**. The
48
+ manifest is the signal: no `manifest.txt` means no pass has ever completed.
49
+
50
+ ## Deciding the size of the pass
51
+
52
+ Run this first, from the project root:
53
+
54
+ ```sh
55
+ .github/skills/zen-spec-sync/scripts/snapshot.sh status
56
+ ```
57
+
58
+ It prints one `mode:` line, the `applied:` stamp the baseline belongs to, and a
59
+ `changed:` line per file that differs - specification, feedback, `agents.yaml`
60
+ or anything under `agents/`:
61
+
62
+ | Mode | Means | Do |
63
+ | ------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
64
+ | `full` | no manifest - no pass has ever completed | the whole prompt as written, then write the first baseline |
65
+ | `incremental` | a baseline is there and the files differ | scope from the four inputs below |
66
+ | `tampered` | a baseline file does not match its own sha | treat as `full`, and say in the chat which file and that the baseline was not trusted |
67
+
68
+ `tampered` is either a baseline edited by hand or a pass that died between
69
+ copying the files and writing the manifest. Both mean the record is not
70
+ evidence. Do not rewrite `manifest.txt` to make the message go away - that
71
+ asserts a pass happened that did not.
72
+
73
+ `snapshot.sh diff` then prints the unified diffs, and is where the pass
74
+ actually starts.
75
+
76
+ ## The four scope inputs
77
+
78
+ An incremental pass is scoped by the union of these. Write the union down before
79
+ editing anything, and report it in the chat as the pass's scope.
80
+
81
+ 1. **The `SPECIFICATION.md` diff.** The primary input: added, changed and
82
+ deleted requirements. A deleted requirement is work too - what implemented it
83
+ is now an **extra**, to be reported under `➕` rather than quietly deleted.
84
+ 2. **The `SPECIFICATION-FEEDBACK.md` diff.** The file is a questionnaire the
85
+ human answers by ticking one answer's `[ ]` box or deleting the answers they
86
+ do not want. A question that has moved under `✅ Answered` was folded into the
87
+ specification by `/update-spec-from-feedback`, and its **Still to implement**
88
+ line is the work. A question with a box marked `[x]`, or one answer left
89
+ under it, and no `✅` entry was answered and never folded in - implement it,
90
+ put its text into `SPECIFICATION.md`, and say so. A new question someone else
91
+ raised is work too. Close what this pass resolves rather than re-raising it.
92
+ 3. **What the last pass left open.** The `Still open` section of the entry named
93
+ by `applied:`, in `.spec-sync/history/`. A specification line that has not
94
+ changed since the baseline is **not therefore implemented**: the baseline
95
+ records what the pass applied, and a pass can complete with items open. This
96
+ is the only place that says which.
97
+ 4. **The implementation diff.** `agents.yaml` and every file under `agents/` -
98
+ instructions, prompts and skills. This side is what was built rather than
99
+ what was asked for, so it never narrows a pass: it says what moved under you
100
+ since the last one. An agent, tool grant, handoff, model, house rule, prompt
101
+ or skill edited outside a pass **widens** the pass to everything that names
102
+ what changed, and the edit is itself an item - either the specification
103
+ covers it, and you say which line, or it does not, and it is an **extra**
104
+ reported under `➕`.
105
+
106
+ ## The four checks a narrow pass may never skip
107
+
108
+ However small the diff:
109
+
110
+ 1. **`zen check` passes.** Run it before the first edit and again after the
111
+ last.
112
+ 2. **`scripts/_setup.sh` has run to completion in this session, and re-runs
113
+ clean.** A one-line specification change can still break a setup step, and a
114
+ pass that did not run it does not know.
115
+ 3. **§9 of the copilot instructions, worked as a checklist**, group by group.
116
+ 4. **The capability obligations in §0.1.** For every capability the project has
117
+ turned on, load that capability's skill and re-check what it requires - in
118
+ particular that `agents/memory-instructions.md` is still byte-identical to
119
+ `.github/skills/zen-memory/references/memory-instructions.md`. `zen init` and
120
+ `zen open` rewrite the reference, so this drifts without anybody editing the
121
+ project.
122
+
123
+ A narrow diff narrows reading and editing. It does not narrow verification.
124
+
125
+ ## What else an incremental pass still owes
126
+
127
+ The reverse pass of §2 - reading each line and naming the specification item it
128
+ serves - runs over every file this pass touched, plus every file the
129
+ implementation diff named. It is the only pass that finds a line contradicting
130
+ another file, and an edit made this session, or made by hand since the last
131
+ pass, is exactly where a new contradiction comes from.
132
+
133
+ ## The history entry
134
+
135
+ One file per pass, `.spec-sync/history/<stamp>.md`, where the stamp is UTC and
136
+ filename-safe: `date -u +%Y-%m-%dT%H%M%SZ`. Never edit a file that is already
137
+ there; a pass that did nothing still writes one saying so.
138
+
139
+ These headings, in this order, because the next pass reads them:
140
+
141
+ ```markdown
142
+ # Sync pass 2026-09-13T104500Z
143
+
144
+ - **Mode** - incremental
145
+ - **Spec sha** - 3f9a… (`shasum -a 256 SPECIFICATION.md`)
146
+
147
+ ## Scope
148
+
149
+ What drove the pass: the diff hunks, the feedback entries, what the last pass
150
+ left open. `full` if it was a full pass.
151
+
152
+ ## Changed
153
+
154
+ One row per file changed, and the specification item it closes.
155
+
156
+ ## Feedback
157
+
158
+ Entries opened and entries closed, by number.
159
+
160
+ ## Still open
161
+
162
+ Every specification item this pass did **not** close, with its
163
+ `SPECIFICATION-FEEDBACK.md` anchor. Empty means empty - this is what the next
164
+ pass trusts.
165
+
166
+ ## Checks
167
+
168
+ `zen check`, each `scripts/_setup.sh` step, and the §9 groups: pass or fail.
169
+ ```
170
+
171
+ ## Write the baseline last
172
+
173
+ The one rule that must not be got wrong.
174
+
175
+ ```sh
176
+ .github/skills/zen-spec-sync/scripts/snapshot.sh commit
177
+ ```
178
+
179
+ runs **after** `zen check` passes, **after** `scripts/_setup.sh` has completed,
180
+ and **after** the history file is on disk. It takes the newest entry in
181
+ `.spec-sync/history/` - the stamps sort chronologically by name - records it in
182
+ `applied.txt`, and writes `manifest.txt` last of all. It refuses to run when
183
+ there is no history file, and when the newest one is the entry `applied.txt`
184
+ already names: that means this pass wrote no entry of its own.
185
+
186
+ A pass that moves the baseline early and then fails records a specification as
187
+ applied that was never applied - and the next pass diffs against it, sees
188
+ nothing, and skips the work for good. Failing with the baseline unmoved costs
189
+ one repeated pass; failing with it moved costs a requirement that is never
190
+ implemented and that nothing will report. Say in the chat which history file was
191
+ written and that the baseline moved.
192
+
193
+ ## The first pass on a project that has no baseline
194
+
195
+ `mode: full`, so run the prompt as written, end to end. Then write the history
196
+ file and commit the baseline exactly as any other pass does - a project only
197
+ becomes incremental by having completed one full pass.
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # The committed record of what the last completed /sync-with-spec pass applied.
4
+ #
5
+ # snapshot.sh status full | incremental | tampered, and what differs
6
+ # snapshot.sh diff the difference the next pass has to work
7
+ # snapshot.sh commit move the baseline on, once the pass has finished
8
+ #
9
+ # Run it from anywhere; it finds the project root from its own location, which
10
+ # `zen init` and `zen open` fix. The baseline is written last and only last —
11
+ # see .github/skills/zen-spec-sync/SKILL.md for why that is the whole point.
12
+
13
+ set -eu
14
+ cd "$(dirname "$0")/../../../.."
15
+
16
+ STATE=.spec-sync
17
+ BASE="$STATE/baseline"
18
+ HISTORY="$STATE/history"
19
+ MANIFEST="$BASE/manifest.txt"
20
+ SCRATCH=.tmp/spec-sync
21
+
22
+ # The intent: what the project is meant to be, and what was asked back about it.
23
+ INTENT="SPECIFICATION.md SPECIFICATION-FEEDBACK.md"
24
+
25
+ # The implementation: the wiring, and every instruction, prompt and skill file
26
+ # under agents/. One of those edited since the last pass is a change the pass
27
+ # has to account for, the same as a changed requirement is.
28
+ WIRING=agents.yaml
29
+ TREE=agents
30
+
31
+ # The history entry this baseline belongs to, hashed like the rest of it.
32
+ APPLIED=applied.txt
33
+
34
+ if [ ! -f "$WIRING" ] && [ ! -f SPECIFICATION.md ]; then
35
+ echo "snapshot.sh: no agents.yaml or SPECIFICATION.md in $PWD" >&2
36
+ exit 2
37
+ fi
38
+
39
+ # Git cannot carry an empty directory, so these are made rather than expected.
40
+ mkdir -p "$BASE" "$HISTORY"
41
+
42
+ # Every path the record covers, on the working-tree side.
43
+ present() {
44
+ {
45
+ for file in $INTENT $WIRING; do
46
+ if [ -f "$file" ]; then echo "$file"; fi
47
+ done
48
+ if [ -d "$TREE" ]; then find "$TREE" -type f ! -name .DS_Store; fi
49
+ } | sort -u
50
+ }
51
+
52
+ # The same, from both sides, so a file added or deleted since the last pass is
53
+ # a difference rather than a path nobody walks.
54
+ scope() {
55
+ {
56
+ present
57
+ for file in $INTENT $WIRING; do
58
+ if [ -f "$BASE/$file" ]; then echo "$file"; fi
59
+ done
60
+ if [ -d "$BASE/$TREE" ]; then
61
+ (cd "$BASE" && find "$TREE" -type f ! -name .DS_Store)
62
+ fi
63
+ } | sort -u
64
+ }
65
+
66
+ # A file that is not there counts as an empty one, so adding or deleting it
67
+ # reads as a difference rather than as an error.
68
+ side() {
69
+ if [ -f "$1" ]; then echo "$1"; else echo /dev/null; fi
70
+ }
71
+
72
+ differs() {
73
+ ! cmp -s "$(side "$BASE/$1")" "$(side "$1")"
74
+ }
75
+
76
+ # Which baseline files no longer hash to what the manifest says they do.
77
+ # `FAILED` covers a changed file; `FAILED open or read` covers a missing one.
78
+ broken() {
79
+ (cd "$BASE" && shasum -a 256 -c manifest.txt 2>/dev/null || true) |
80
+ sed -n 's/: FAILED.*$//p'
81
+ }
82
+
83
+ # The stamps sort chronologically by name, which is the point of the format —
84
+ # an mtime would make a fast pass look like no pass at all.
85
+ newest() {
86
+ ls "$HISTORY"/*.md 2>/dev/null | sort | tail -1 || true
87
+ }
88
+
89
+ status() {
90
+ if [ ! -f "$MANIFEST" ]; then
91
+ echo "mode: full"
92
+ echo "reason: no baseline, so no pass has ever completed here"
93
+ return 0
94
+ fi
95
+ failed="$(broken)"
96
+ if [ -n "$failed" ]; then
97
+ echo "mode: tampered"
98
+ while IFS= read -r file; do
99
+ if [ -n "$file" ]; then echo "unsound: $BASE/$file"; fi
100
+ done <<EOF
101
+ $failed
102
+ EOF
103
+ echo "reason: the baseline does not match its own manifest"
104
+ return 0
105
+ fi
106
+ echo "mode: incremental"
107
+ if [ -f "$BASE/$APPLIED" ]; then
108
+ echo "applied: $(cat "$BASE/$APPLIED")"
109
+ fi
110
+ while IFS= read -r file; do
111
+ if [ -n "$file" ] && differs "$file"; then
112
+ echo "changed: $file"
113
+ fi
114
+ done <<EOF
115
+ $(scope)
116
+ EOF
117
+ }
118
+
119
+ show_diff() {
120
+ while IFS= read -r file; do
121
+ if [ -n "$file" ] && differs "$file"; then
122
+ diff -u -L "baseline/$file" -L "$file" \
123
+ "$(side "$BASE/$file")" "$(side "$file")" || true
124
+ fi
125
+ done <<EOF
126
+ $(scope)
127
+ EOF
128
+ }
129
+
130
+ commit() {
131
+ entry="$(newest)"
132
+ if [ -z "$entry" ]; then
133
+ echo "snapshot.sh: write $HISTORY/<stamp>.md before moving the baseline" >&2
134
+ exit 1
135
+ fi
136
+ stamp="$(basename "$entry" .md)"
137
+ if [ -f "$BASE/$APPLIED" ] && [ "$stamp" = "$(cat "$BASE/$APPLIED")" ]; then
138
+ echo "snapshot.sh: the baseline already names $stamp — this pass wrote no entry" >&2
139
+ exit 1
140
+ fi
141
+
142
+ staged="$(present)"
143
+ rm -rf "$SCRATCH"
144
+ mkdir -p "$SCRATCH"
145
+ echo "$stamp" >"$SCRATCH/$APPLIED"
146
+ : >"$SCRATCH/manifest.txt"
147
+ while IFS= read -r file; do
148
+ if [ -n "$file" ]; then
149
+ mkdir -p "$SCRATCH/$(dirname "$file")"
150
+ cp "$file" "$SCRATCH/$file"
151
+ fi
152
+ done <<EOF
153
+ $staged
154
+ EOF
155
+ while IFS= read -r file; do
156
+ if [ -n "$file" ]; then
157
+ (cd "$SCRATCH" && shasum -a 256 "$file") >>"$SCRATCH/manifest.txt"
158
+ fi
159
+ done <<EOF
160
+ $APPLIED
161
+ $staged
162
+ EOF
163
+
164
+ while IFS= read -r file; do
165
+ if [ -n "$file" ]; then
166
+ mkdir -p "$BASE/$(dirname "$file")"
167
+ mv "$SCRATCH/$file" "$BASE/$file"
168
+ fi
169
+ done <<EOF
170
+ $APPLIED
171
+ $staged
172
+ EOF
173
+ # Everything in scope that still exists has just been staged, so a baseline
174
+ # file with no working copy is one this pass deleted.
175
+ while IFS= read -r file; do
176
+ if [ -n "$file" ] && [ "$file" != "$APPLIED" ] && [ ! -f "$file" ]; then
177
+ rm -f "$BASE/$file"
178
+ fi
179
+ done <<EOF
180
+ $(cd "$BASE" && find . -type f ! -name manifest.txt | sed 's|^\./||')
181
+ EOF
182
+ find "$BASE" -mindepth 1 -type d -empty -delete
183
+ # Last, so an interrupted commit leaves a baseline that reports itself as
184
+ # tampered rather than one that claims a pass it never finished.
185
+ mv "$SCRATCH/manifest.txt" "$MANIFEST"
186
+ rm -rf "$SCRATCH"
187
+
188
+ echo "applied: $stamp"
189
+ echo "recorded: $(printf '%s\n' "$staged" | sed '/^$/d' | wc -l | tr -d ' ') files"
190
+ }
191
+
192
+ case "${1:-status}" in
193
+ status) status ;;
194
+ diff) show_diff ;;
195
+ commit) commit ;;
196
+ *)
197
+ echo "usage: snapshot.sh [status|diff|commit]" >&2
198
+ exit 2
199
+ ;;
200
+ esac
@@ -1,8 +1,51 @@
1
1
  {
2
2
  "chat.useNestedAgentsMdFiles": false,
3
3
  "chat.tools.terminal.enableAutoApprove": true,
4
- "chat.tools.terminal.ignoreDefaultAutoApproveRules": true,
5
- "chat.tools.edits.autoApprove": {
6
- "**": true
4
+ "chat.tools.edits.autoApprove": true,
5
+ "chat.tools.terminal.autoApprove": {
6
+ "awk": true,
7
+ "basename": true,
8
+ "cat": true,
9
+ "cd": true,
10
+ "comm": true,
11
+ "cut": true,
12
+ "date": true,
13
+ "diff": true,
14
+ "dirname": true,
15
+ "du": true,
16
+ "echo": true,
17
+ "env": true,
18
+ "file": true,
19
+ "find": true,
20
+ "grep": true,
21
+ "head": true,
22
+ "jq": true,
23
+ "ls": true,
24
+ "nl": true,
25
+ "printf": true,
26
+ "pwd": true,
27
+ "realpath": true,
28
+ "rg": true,
29
+ "sed": true,
30
+ "sort": true,
31
+ "stat": true,
32
+ "tail": true,
33
+ "tr": true,
34
+ "tree": true,
35
+ "uniq": true,
36
+ "wc": true,
37
+ "which": true,
38
+ "tsc": true,
39
+ "zen": true,
40
+ "zn": true,
41
+ "zenera": true,
42
+ "/^git (branch|diff|log|show|status)\\b/": true,
43
+ "/^npm (test|run (build|format:check|lint|test|typecheck))\\b/": true,
44
+ "/\\bfind\\b[^|;]*\\s-(exec|execdir|ok|okdir|delete)\\b/": false,
45
+ "/\\bsed\\b[^|;]*\\s-[A-Za-z]*i/": false,
46
+ "/\\bawk\\b[^|;]*\\b(system|print\\s*>|printf\\s*>)/": false,
47
+ "/^\\.?\\/?scripts\\/[A-Za-z0-9_.-]+\\.sh\\b/": true,
48
+ "/^\\.?\\/?\\.github\\/skills\\/[A-Za-z0-9_-]+\\/scripts\\/[A-Za-z0-9_.-]+\\.sh\\b/": true,
49
+ "podman": true
7
50
  }
8
51
  }
@@ -0,0 +1,16 @@
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
+ #
5
+ # Recall ranks by term overlap here, which finds a memory phrased the way the
6
+ # question was and misses the rest. This project's provider publishes no
7
+ # embeddings API; add one that does and point the store at it to rank by
8
+ # meaning instead:
9
+ #
10
+ # memory:
11
+ # embedding: main
12
+ #
13
+ # embeddings:
14
+ # main: openai:text-embedding-3-small
15
+ memory:
16
+ dir: memory