dreamteamer 0.20.0 → 0.22.0

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.
package/bin/dt-hook.sh ADDED
@@ -0,0 +1,54 @@
1
+ #!/bin/sh
2
+ # dt-hook.sh — run the engine from a harness hook, where there is no PATH to speak of.
3
+ #
4
+ # ⚠ THIS FILE EXISTS BECAUSE OF A MEASUREMENT, not a worry. A harness hook is `sh -c`, and `sh`
5
+ # reads NO startup file — not .zshenv, not .bash_profile, not .bashrc. On the machine where the
6
+ # first live worktree spawn was watched:
7
+ #
8
+ # env -i zsh -c 'command -v node' -> ~/.nvm/versions/node/<v>/bin/node
9
+ # env -i sh -c 'command -v node' -> NOT FOUND
10
+ #
11
+ # So a hook line beginning with a bare `npm`, `npx` or `node` fails, and fails SILENTLY: a fresh
12
+ # worktree has no node_modules either, so there is nothing to fall back to, and a hook's stderr goes
13
+ # to a log the session never reads. A shell-profile fix cannot reach this (`bash -c` and `sh -c`
14
+ # were both measured failing after one was applied) and would not travel with the workspace anyway.
15
+ # Hence an absolute resolution, here, in the one language a hook is guaranteed to have.
16
+ #
17
+ # sh /path/to/dt-hook.sh <verb> [args...]
18
+ #
19
+ # Resolution order: $DREAMTEAMER_NODE · node on PATH · the highest ~/.nvm install · /opt/homebrew ·
20
+ # /usr/local. The last two are where the two common non-nvm installers put it.
21
+ set -eu
22
+
23
+ node=""
24
+ if [ -n "${DREAMTEAMER_NODE:-}" ] && [ -x "${DREAMTEAMER_NODE}" ]; then
25
+ node="${DREAMTEAMER_NODE}"
26
+ elif command -v node >/dev/null 2>&1; then
27
+ node="$(command -v node)"
28
+ else
29
+ # `sort -V`, never a plain sort: v9.9.9 sorts AFTER v10.10.0 lexicographically, which would hand
30
+ # every hook the oldest install on the disk. A glob that matches nothing expands to itself, so
31
+ # the whole pipeline is allowed to come back empty rather than being trusted — and EVERY stage
32
+ # of it is silenced, because `-V` is not in POSIX and a sort that lacks it would otherwise write
33
+ # a usage error into the one stream a hook must leave clean.
34
+ nvm=""
35
+ if [ -n "${HOME:-}" ]; then
36
+ nvm="$(ls -d "${HOME}"/.nvm/versions/node/*/bin/node 2>/dev/null | sort -V 2>/dev/null | tail -1 2>/dev/null)" || nvm=""
37
+ fi
38
+ for candidate in "${nvm}" /opt/homebrew/bin/node /usr/local/bin/node; do
39
+ if [ -n "${candidate}" ] && [ -x "${candidate}" ]; then
40
+ node="${candidate}"
41
+ break
42
+ fi
43
+ done
44
+ fi
45
+
46
+ # ⚠ STDOUT, NOT STDERR, and that is the whole point of failing loudly here: a hook's stdout is added
47
+ # to the session's context, so this line is read by the agent that is about to work in a checkout
48
+ # nothing has made ready. Its stderr is not.
49
+ if [ -z "${node}" ]; then
50
+ echo "✖ dreamteamer hook: node not found — set DREAMTEAMER_NODE=/path/to/node in the harness environment, or install node under ~/.nvm, /opt/homebrew or /usr/local"
51
+ exit 1
52
+ fi
53
+
54
+ exec "${node}" "$(dirname "$0")/dreamteamer.js" "$@"
@@ -120,6 +120,15 @@ schema:
120
120
  opened on its own. It is stated here as DATA precisely so the record layer never has to
121
121
  learn what a module is; a bare string list rather than `x-reference: collections`, because
122
122
  the whole point is that the target is absent.
123
+ sensitive:
124
+ type: boolean
125
+ description: >-
126
+ This collection's records must not leave the workspace through an export — `dt export
127
+ <target>` writes none of them and names the omission in what it does write (the schema
128
+ source, the persona), so a reader knows the gap is deliberate. Set with `dt set
129
+ collections/<c> sensitive=true`. For ONE field rather than the collection, mark the field:
130
+ `x-sensitive: true` on its property (`dt add-field … --sensitive`). Nothing is inferred
131
+ from a name — the mark is the decision.
123
132
  module:
124
133
  type: string
125
134
  description: >-
@@ -19,7 +19,7 @@ schema:
19
19
  collection:
20
20
  type: string
21
21
  x-reference: collections
22
- description: The collection the command applies TO — which is what makes `dt commands for <ref>` able to answer "what can I do with this record".
22
+ description: The collection the command applies TO — which is what makes `dt next <ref>` able to answer "what can I do with this record".
23
23
  target:
24
24
  type: string
25
25
  enum:
@@ -0,0 +1,96 @@
1
+ name: proofs
2
+ storage:
3
+ path: proofs
4
+ codec: yaml
5
+ shape: file
6
+ suffix: proof
7
+ id:
8
+ generate: '{{ name | slug }}'
9
+ schema:
10
+ type: object
11
+ required:
12
+ - name
13
+ - about
14
+ - kind
15
+ properties:
16
+ name:
17
+ type: string
18
+ description: The proof id — must equal the filename.
19
+ about:
20
+ type: array
21
+ items:
22
+ type: string
23
+ description: What this proof is ABOUT — one or more artifact refs it proves something about. An artifact ref is `skills/<id>` · `commands/<id>` · `command-bindings/<id>` · `<module-id>/bin/<file>`.
24
+ kind:
25
+ type: string
26
+ enum:
27
+ - gate
28
+ - live
29
+ description: '`gate` — a static check with no fixture record; `live` — runs against a real record and is judged by `expect`.'
30
+ mode:
31
+ type: string
32
+ enum:
33
+ - readonly
34
+ - writes
35
+ description: Whether a `live` proof only reads the workspace or actually writes to it. Required when kind is live, forbidden on a gate.
36
+ description:
37
+ type: string
38
+ description: What this proof asserts, in one line.
39
+ external:
40
+ type: boolean
41
+ description: Excludes this proof from a bare `--all` run — it needs a network call, a credential or a mount that is not always present. `--external` still runs it.
42
+ requires:
43
+ type: object
44
+ description: What must already be present for this proof to be runnable at all — missing ones make it UNAVAILABLE rather than FAIL.
45
+ properties:
46
+ env:
47
+ type: array
48
+ items:
49
+ type: string
50
+ description: '.env keys (names only, never values) that must be set.'
51
+ bin:
52
+ type: array
53
+ items:
54
+ type: string
55
+ description: Binaries that must be on PATH.
56
+ given:
57
+ type: object
58
+ description: The record a `live` proof runs against — either a live filter over a collection or a checked-in fixture. Exactly one of `where`/`fixture`.
59
+ properties:
60
+ collection:
61
+ type: string
62
+ description: A collection NAME (not a reference) the record is picked from — e.g. `notes`, never `collections/notes`.
63
+ where:
64
+ type: object
65
+ description: 'A filter narrowing which record is picked — the ordinary filter grammar, one reference hop deep. ⚠ It may NOT use `{record}`: the given is what PICKS the record, so nothing substitutes here and compile refuses the literal.'
66
+ pick:
67
+ type: string
68
+ description: '`latest` (the collection''s own `sort_field`, descending — refused when the collection declares none) or an explicit record id. `any` is refused. A `fixture` given REQUIRES a `pick: <id>` naming one of its records.'
69
+ fixture:
70
+ type: boolean
71
+ description: Read the record from this proof's own fixtures folder instead of the live workspace.
72
+ steps:
73
+ type: array
74
+ items:
75
+ type: object
76
+ description: What actually runs, in order — a shell command (`run`) or a bound command (`perform`). Required; a gate takes one or more `run` steps, a live proof one or more of either.
77
+ expect:
78
+ type: array
79
+ items:
80
+ type: object
81
+ description: 'What must hold for a `live` proof to PASS. Live only, and every row is exactly ONE of four forms — `collection`+`where`+`count` · `record`+`where` · `step` (`exit`/`stdout`/`stdout_json`) · `path`+`exists`. A row whose keys span two forms is a compile error, because compile and the judge would otherwise read it as different shapes. `count` takes its own closed operator set (`_eq _neq _gt _gte _lt _lte _delta`) and every operand must be an INTEGER; a `record:` row REQUIRES a non-empty `where` and takes only the literal `{record}` as its target; a `step:` index past the last step is refused; `stdout` and `stdout_json` are FILTER MAPS (`{ _contains: ''…'' }` · `{ ''a.b'': { _gte: 1 } }`) and a scalar, a list or a bare key is refused, because the judge reads only a map and a row it skips asserts nothing. A `where`''s string literals substitute `{record}` and `{record.<field>}` against the picked record before the filter runs — in an expectation only, never in `given.where`.'
82
+ timeout:
83
+ type: integer
84
+ description: Seconds allowed per `run` step before it is killed.
85
+ order: 46
86
+ # ⚠ NO `last-modified` HERE (M12). Every other list_fields carries it, and on a proof it renders as
87
+ # a bare `-` in the MIDDLE of the row: `dt list proofs` appends two computed columns (availability
88
+ # and the ledger tail) after the declared ones, so the empty cell sits between the about list and
89
+ # the answer the reader came for. A proof's meaningful date is its LAST RUN, which is the `last`
90
+ # column, not the mtime of a file git already tracks.
91
+ list_fields:
92
+ - name
93
+ - kind
94
+ - about
95
+ icon: verified
96
+ group: system
@@ -6,12 +6,13 @@ use_when: >-
6
6
  a repository this workspace depends on is named — its own engine or extension clone, a client's
7
7
  repo — read the record here for WHERE it lives (`path`, a `${env:…}` template `dt resolve` renders
8
8
  per machine) before assuming a path or concluding a clone is unreachable; `dt status` reports
9
- each one and `dt ensure` materializes it
9
+ each one and `dt install repos/<id>` materializes it
10
10
  # An external git repo attached to this workspace. Owns CLONE LIFECYCLE ONLY — a repo record
11
11
  # never contributes schema, skills or UI (that is what a module is, declared in package.json
12
12
  # `dreamteamer.git-modules`, because modules must be restorable BEFORE compile can run).
13
- # Working trees are materialized ON DEMAND via `dreamteamer repos ensure <id>`, never at install:
14
- # the record count only grows and any given session needs almost none of them.
13
+ # Working trees are materialized ON DEMAND via `dreamteamer install repos/<id>`, and never as part
14
+ # of making a checkout ready: the record count only grows and any given session needs almost none
15
+ # of them.
15
16
  storage: { path: data/repos, codec: yaml, shape: file, suffix: repo }
16
17
  id:
17
18
  generate: "{{ name | slug }}"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dreamteamer",
3
- "version": "0.20.0",
4
- "description": "A workspace compiler for coding agents schema-validated records as plain files over git, compiled into every harness",
3
+ "version": "0.22.0",
4
+ "description": "A workspace compiler for coding agents \u2014 schema-validated records as plain files over git, compiled into every harness",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Gilad Khen <giladkhen@gmail.com>",
7
7
  "homepage": "https://github.com/dreamteamer/dreamteamer#readme",
@@ -59,10 +59,12 @@ their flags, on one page (there is no per-verb `--help`).
59
59
  the verb names, as a map (semantics and flags live in `help`; a test holds this list to the
60
60
  dispatch, so it cannot drift):
61
61
 
62
- - read & measure — `list` `get` `values` `history` `diff` `commands` `relations` `resolve`
63
- - write & publish — `add` `set` `rm` `rename` `move` `revert` `commit` `ensure`
64
- - fields (sources, through the compile gate) — `add-field` `update-field` `remove-field` `rename-field` (system entities — modules, collections, skills, ui-views… — take the RECORD verbs above)
65
- - workspace — `init` `install` `update` `compile` `check` `status` `start` `changes` `help` don't learn syntax from prose, this skill included: prose drifts, and `help` ships in
62
+ - read & measure — `list` `get` `values` `history` `diff` `next` `relations` `resolve`
63
+ - write & publish — `add` `set` `rm` `rename` `move` `revert` `commit`
64
+ - fields (sources, through the compile gate) — `add-field` `set-field` `rm-field` `rename-field` (system entities — modules, collections, skills, ui-views… — take the RECORD verbs above)
65
+ - workspace — `init` `install` `land` `update` `compile` `check` `prove` `status` `start` `changes` `export` `help`
66
+
67
+ don't learn syntax from prose, this skill included: prose drifts, and `help` ships in
66
68
  the same file as the dispatch it documents. run it once before your first write of a session.
67
69
  what prose adds is judgment — *when* a verb is the right move, and the guarantees you can lean
68
70
  on: **validation is hard** (unknown fields included; an invalid write is rejected before disk
@@ -80,14 +82,17 @@ Load by the map; nothing here is loaded "just in case".
80
82
  | a brand-new or empty workspace, dreamteamer over an existing pile of files, "help me set this up" | `references/getting-started.md` |
81
83
  | read, create, update, rename, delete, commit — or UNDO — a record | `references/records.md` |
82
84
  | "what changed while I was away" | `references/changes.md` |
85
+ | the workspace has to reach a reader that is not a coding agent — a NotebookLM notebook; "which fields are sensitive" | `references/exporting.md` |
83
86
  | the workspace seems unable to do something — a new kind of thing, a missing capability, "don't we already have this?" | `references/before-you-build.md` (look first); a new model then continues `references/data-modeling.md` (decide) → `references/collections.md` (write it) |
84
87
  | a collection or field, mechanically — the descriptor, the system and field verbs, `templates:`/`extends:`, a compile or check message | `references/collections.md` |
85
88
  | knowledge a session should find on its own | `references/skills.md` |
86
89
  | "let me type one word and have this done" | `references/commands.md` |
87
90
  | "which command applies to this record?" — a binding, a gate | `references/commands.md` |
91
+ | "how would anyone know this still works?" — a proof of a skill, a command or a script, and the exit code `dt prove` answers with | `references/proofs.md` |
88
92
  | a job needing a fresh context and its own tools | `references/agents.md` |
89
93
  | a route, a nav entry, a board / calendar / map over records | `references/ui-views.md` |
90
94
  | a rendering or editing behaviour nothing registered has | `references/ui-components.md` |
95
+ | a second checkout — making one ready, landing its records, a harness that cuts them for you | `references/worktrees.md` |
91
96
 
92
97
  three act-two tie-breakers, because they are the ones that go wrong:
93
98
 
@@ -107,8 +112,8 @@ workspace's decision log (where one exists) wins over older documents.
107
112
 
108
113
  ## system entities take the RECORD verbs
109
114
 
110
- Modules, collections, skills, agents, commands, command-bindings, ui-views and collection-templates
111
- are collections in the runtime, and since 0.19.0 the ordinary verbs write them:
115
+ Modules, collections, skills, agents, commands, command-bindings, ui-views, collection-templates
116
+ and proofs are collections in the runtime, and since 0.19.0 the ordinary verbs write them:
112
117
 
113
118
  ```
114
119
  dt add modules --name core --description "The shared nouns."
@@ -120,6 +125,10 @@ dt set modules/hr namespaces=hr dependencies=modules/core
120
125
  dt rm modules/hr --force # --dry-run first; it prints its plan
121
126
  ```
122
127
 
128
+ ⚠ **`proofs` is the one exception, and only to `add`:** a proof is hand-authored like a skill or a
129
+ command, so `dt add proofs` is refused, naming the file to write
130
+ (`modules/<module>/proofs/<id>.proof.yaml`, `references/proofs.md`). Every other verb works on it.
131
+
123
132
  `dt schema <op>` is **gone** since 0.19.0 and fails with the translation printed. `UPDATING.md` has
124
133
  the complete mapping table.
125
134
 
@@ -130,7 +139,7 @@ holds the source**, so a write into a `git_modules/` module commits there and sa
130
139
  `ahead 1 — push when ready`. A record write lands on disk and `dt commit` publishes it.
131
140
 
132
141
  ⚠ **Every verb that moves records or clears values takes `--dry-run` and prints its plan first:**
133
- `rename collections/…`, `rename-field`, `remove-field`, `set collections/… module=`,
142
+ `rename collections/…`, `rename-field`, `rm-field`, `set collections/… module=`,
134
143
  `rm modules/… --force`. The plan line is one shape — `records N · refs M · descriptors K · values
135
144
  cleared V` — so two dry runs are comparable, and a term that reads 0 means zero rather than
136
145
  unmeasured (where a number genuinely cannot be known before the run, the plan says so in words).
@@ -40,13 +40,27 @@ install. The looking still matters; the taking changed shape.
40
40
 
41
41
  | # | look at | how, concretely | what you get |
42
42
  |---|---|---|---|
43
- | 1 | **this workspace's own modules** | the orientation block's MODULES AND THEIR COLLECTIONS list is already in your context — find the module whose sentence owns the concept, then reread its collections with the concept's *synonyms* in mind, `use when` clauses included; then read the candidate's DESCRIPTOR, not its records; `dt help` shows the verbs and `dt commands <collection>` the bound ones; the module's skills line names the techniques | the thing may already exist under a name you didn't guess. This is where misses actually happen, because it is the place you assume you already know |
43
+ | 1 | **this workspace's own modules** | the orientation block's MODULES AND THEIR COLLECTIONS list is already in your context — find the module whose sentence owns the concept, then reread its collections with the concept's *synonyms* in mind, `use when` clauses included; then read the candidate's DESCRIPTOR, not its records; `dt help` shows the verbs and `dt next <collection>` the bound ones; the module's skills line names the techniques | the thing may already exist under a name you didn't guess. This is where misses actually happen, because it is the place you assume you already know |
44
44
  | 2 | **a recipes repo — where this workspace's own CLAUDE.md names one** (reference modules maintained to be **copied and adapted**) | read that repo's own instructions first; they ARE the adoption procedure. no recipes repo declared → skip this row | a working module with its reasoning attached — descriptors, skills, and the trap notes that each cost someone a day |
45
45
  | 3 | **a sibling workspace — where the operator has named one** | read-only; grep its `modules/` for the concept. none named → skip | another vault may have solved it concretely. That is a *reference*, not a source — it holds real personal data, so read the shape, never lift the content |
46
- | 4 | **the engine's own surface** | `dt help` — the complete verb surface, system and field verbs included; read it rather than recalling it | the capability may already be a verb (`relations rebuild`, `resolve`, `ensure`) rather than a missing module |
46
+ | 4 | **the engine's own surface** | `dt help` — the complete verb surface, system and field verbs included; read it rather than recalling it | the capability may already be a verb (`relations rebuild`, `resolve`, `install repos/<id>`) rather than a missing module |
47
47
 
48
48
  Only after all four: build it, in the module that owns the concept.
49
49
 
50
+ ## name the proof before you build it
51
+
52
+ **Before writing the thing, say how anyone would know it works** — one sentence, in the shape a
53
+ `proofs` record takes: *this record, in this state, after this step, must look like this*
54
+ (`proofs.md`). It costs a minute and it is the cheapest design review there is.
55
+
56
+ ⚠ **When you cannot name one, the artifact has no observable post-state, and THAT is the first
57
+ thing to change** — not something to note and carry on past. A skill nothing can check is a skill
58
+ whose only evidence is that a session read it; a command whose completion leaves no trace in any
59
+ record is a command nobody can tell ran. Give it a post-state — a field it sets, a record it
60
+ writes, a file it leaves, an exit code it returns — and the proof follows for free. Do it after the
61
+ proposal is accepted and before the build, so the assertion is what you build toward rather than
62
+ what you retrofit.
63
+
50
64
  ## what "found" actually means
51
65
 
52
66
  Rarely the exact thing. Four outcomes, each with its own move:
@@ -63,26 +63,26 @@ unpublished schema is not a state the workspace should sit in. The verbs and eve
63
63
  `dt help` under "system verbs" and "field verbs" — read that, not prose. What help cannot tell you:
64
64
 
65
65
  - **The field verbs write the WORKSPACE module.** On a collection another module owns, `add-field`
66
- and `update-field` author an `extends:` overlay in the workspace module — which compiles only
66
+ and `set-field` author an `extends:` overlay in the workspace module — which compiles only
67
67
  if the workspace module declares the owning module in `dreamteamer.dependencies` (the extends
68
- gate exempts nobody). `remove-field` has no overlay form at all — an overlay cannot remove an
68
+ gate exempts nobody). `rm-field` has no overlay form at all — an overlay cannot remove an
69
69
  inherited field, so it refuses a module-shipped field by name. So on a module-owned collection:
70
- declare the dependency and let add/update write the overlay (the change stays workspace-local),
71
- or **edit the owning module's descriptor by hand** and compile (the change ships with the
72
- module) — the only exit for a removal. Pick by who should own the field — `data-modeling.md`
73
- Part III.
70
+ declare the dependency and let `add-field`/`set-field` write the overlay (the change stays
71
+ workspace-local), or **edit the owning module's descriptor by hand** and compile (the change
72
+ ships with the module) — the only exit for a removal. Pick by who should own the field —
73
+ `data-modeling.md` Part III.
74
74
  - **`--type <collection>` beats the type sugar, always.** A type that names a collection in the
75
75
  runtime is a reference to it, whatever `string`/`enum`/`date`/`tags`/… would otherwise mean — so
76
76
  in a workspace that ships a `tags` collection, `--type tags` points at it and the relation flags
77
77
  work on it. Only a stated `--type` resolves this way; omitting it still means a plain string.
78
- - `remove-field` on a populated field **clears the values in the same write and reports the
78
+ - `rm-field` on a populated field **clears the values in the same write and reports the
79
79
  count** — a leftover key would make every later write to those records fail as unknown. It also
80
80
  prunes the field out of **the same descriptor's `list_fields` and `sort_field`** (that is the
81
81
  field's own presentation, and a dangling `sort_field` is a compile error), and **warns, by id,**
82
82
  about any ui-view whose `options.columns` still names it — a different source, so it is named
83
83
  rather than edited.
84
84
  - **`add-field` inserts before the `x-body` field**, on the same rule as a `templates:` merge
85
- below: property order is form order, and a record's body belongs last. `update-field` never
85
+ below: property order is form order, and a record's body belongs last. `set-field` never
86
86
  reorders — an existing field keeps the place its author gave it.
87
87
  - **`dt rename collections/<old> <new>`** moves the descriptor **in the module that ships it**
88
88
  (its guard is against writes an `npm install` would erase, not against modules), plus the
@@ -227,6 +227,45 @@ Two gates around it:
227
227
  - ⚠ **An overlay can add fields but cannot remove an inherited one.** If the shape is wrong for
228
228
  the module rather than just for this workspace, fix the base.
229
229
 
230
+ ## `x-choices` — what an enum VALUE looks like
231
+
232
+ An enum value carries a label and nothing else by default: a surface gets `{ text, value }` and
233
+ draws the value. `x-choices` is an OPTIONAL sparse map, keyed by the value, that gives a surface
234
+ more to draw with — a board grouping by the field, a dropdown in a form, anything reading
235
+ `edit_options.choices`.
236
+
237
+ ```yaml
238
+ lane:
239
+ type: string
240
+ enum: [alpha, bravo, charlie]
241
+ x-choices:
242
+ alpha:
243
+ label: Alpha team # what a surface shows; the stored VALUE is still `alpha`
244
+ description: the one that ships
245
+ icon: rocket # a codicon name …
246
+ color: charts.blue # a theme colour id — the accent
247
+ background: charts.blue # … and the fill
248
+ bravo:
249
+ icon: assets/icons/lucide/anchor # … OR a reference to a `codec: file` record
250
+ ```
251
+
252
+ - **Sparse and additive.** Decorate one value, or none. A value with no entry projects exactly as it
253
+ did before this keyword existed, so adding it changes nothing that already works.
254
+ - **`enum` still owns the value set AND its order.** A map key cannot add, remove or reorder a
255
+ value — which matters, because a grouped view takes its band order from the enum.
256
+ - **Five keys, and only five** — `label` · `description` · `icon` · `color` · `background`, each an
257
+ optional string. Anything else in an entry is dropped: the projection copies by name, so a
258
+ descriptor cannot inject keys into a contract every surface reads.
259
+ - **`label` becomes `text`.** So a workspace can relabel a value without touching the value, and no
260
+ stored record moves.
261
+ - **`icon` is a codicon name or a reference to a record of a `codec: file` collection.** A codicon
262
+ name never contains a slash and a record reference always does, so the surface decides which
263
+ without a second keyword.
264
+ - **Colours are theme colour ids, not hex.** A hex is authored against one theme and wrong in the
265
+ other.
266
+ - **Both mistakes warn rather than fail** — a key that is not one of the enum's values, and the
267
+ keyword on a field with no enum. See the message catalog below.
268
+
230
269
  ## the reference contract — `x-reference` across the module graph
231
270
 
232
271
  Every `x-reference` target must be one of: a **core** collection (the entity kinds plus `repos`)
@@ -291,6 +330,8 @@ collection author actually meets. (⚠ = warning: it compiled, and you should st
291
330
  | `cyclic module dependencies: a → b → a` | concept-level links declared as module deps | the collection belongs in `peerDependencies` |
292
331
  | relation refusals (`stamps a mirror onto…`, `declared on both sides…`) | the relation rules | `data-modeling.md` Part VI |
293
332
  | ⚠ `x-unique on "f" is inert` | a relation keyword with no relation — nothing enforces it | declare the inverse, or drop it |
333
+ | ⚠ `x-choices on "f" has an entry for "k"` | it decorates enum VALUES and `k` is not one — a typo, or a value since removed | fix the spelling, or drop the entry |
334
+ | ⚠ `x-choices on "f" is inert` | the keyword on a field that declares no enum — nothing reads it | give the field an enum, or drop the keyword |
294
335
  | ⚠ `collection … has no description` | it renders as a bare name in the orientation block every session loads | write the sentence (`data-modeling.md` §18) |
295
336
  | ⚠ `module "…" contributed no recognised sources` | its folders match no kind and it ships no UI bundle | usually a layout or naming mistake |
296
337
  | ⚠ `module X: <channel> copy shadows <channel> copy` | the same module delivered twice — the more local wins (npm-link semantics) | intended for dev; otherwise remove one |
@@ -316,7 +357,7 @@ silently not discovered — see declaring a module.)*
316
357
  | a second same-name descriptor without `extends` | compile error by design |
317
358
  | a plain string where a ref belongs | use `x-reference` so `check` and `rename` can follow it |
318
359
  | a `templates:` ref pointing at another module | that module can no longer be copied or installed alone |
319
- | a field verb aimed at a module-owned collection, retried verbatim | add/update write a workspace overlay behind a dependency gate — declare the dependency or edit the owning module; remove-field refuses outright (edit the module) |
360
+ | a field verb aimed at a module-owned collection, retried verbatim | add-field/set-field write a workspace overlay behind a dependency gate — declare the dependency or edit the owning module; rm-field refuses outright (edit the module) |
320
361
  | authoring `storage.path` under an entity-kind name | it compiles as a runtime collection and becomes unwritable |
321
362
  | hand-editing schema when a schema verb could express it | the verbs are compile-gated and commit their write; a hand edit can land uncompilable and sit unpublished |
322
363
  | ignoring a ⚠ because compile said ✔ | every warning above is a defect with a deferred bill |
@@ -8,7 +8,7 @@ queue without any run records.
8
8
  What you write is consumed by three readers with different needs: the **operator** scanning the
9
9
  `/` picker, who sees the `description` and `argument-hint` and nothing else; the **session** that
10
10
  receives the body verbatim as its turn, and needs instructions, not documentation; and the
11
- **surfaces** that render bindings — `dt commands <ref>`, the studio's Commands tab, and the
11
+ **surfaces** that render bindings — `dt next <ref>`, the studio's Commands tab, and the
12
12
  orientation block, which prints every binding's gates so each new session knows the queue exists
13
13
  before any skill is loaded.
14
14
 
@@ -107,7 +107,7 @@ can-exit: { transcript: { _nempty: true } }
107
107
  description: audio present, not yet transcribed
108
108
  ```
109
109
 
110
- What a binding buys: `dt commands <collection>[/<id>] [--ids a,b] [--json]` answers "what can I do
110
+ What a binding buys: `dt next <collection>[/<id>] [--ids a,b] [--json]` answers "what can I do
111
111
  with this record right now"; the studio draws the same answer as buttons; and the orientation block
112
112
  renders every binding with its gates **literally** (`/transcribe-visit (enter: recording_file set ·
113
113
  exit: transcript set)`) — so the gate you write is also documentation every session reads without
@@ -151,8 +151,21 @@ they read stay honest:
151
151
  `{ summary: { _nempty: true } }` works the moment `summary` is a mirror — or ship the binding
152
152
  without a `can-exit` and accept that it never shows done. What is not honest is a proxy field a
153
153
  human must remember to set.
154
+ - **A `can-exit` and a proof's `count` answer different questions — put each expectation on its own
155
+ side.** A gate is a filter over ONE record, evaluated on every render of `dt next` and every board
156
+ the studio draws, so it can only ever read that record's own fields (plus one outbound hop) — which
157
+ is exactly the gap the bullet above names: "a summary referencing this record exists" is
158
+ inexpressible there. A **proof** (`proofs.md`) is evaluated on demand and is collection-scoped, so
159
+ it says the thing a gate cannot: `{ collection: summaries, where: { about: { _eq: '{record}' } },
160
+ count: { _delta: 1 } }` — *running this command left one more summary behind*. (`{record}` inside a
161
+ proof's `where` is SUBSTITUTED with the picked record's reference before the filter runs, which is
162
+ what makes that line count the summaries about THIS record rather than all of them.) The rule of thumb:
163
+ **the record's own post-state is the binding's** (it has to be, or the queue cannot advance);
164
+ **what the command left elsewhere is the proof's**. Writing the second one as a gate needs a mirror
165
+ field or a proxy a human must remember to set; writing the first one only as a proof leaves every
166
+ record reading `available` forever.
154
167
  - **The binding's `description` is the state pair in words** ("audio present, not yet
155
- transcribed") — it renders beside the button and in `dt commands` output (the orientation block
168
+ transcribed") — it renders beside the button and in `dt next` output (the orientation block
156
169
  carries the gates themselves), so write it as the answer to "why is this available".
157
170
 
158
171
  ## the chain — multi-step processes
@@ -93,7 +93,7 @@ The useful half of object orientation maps cleanly onto a workspace; the inherit
93
93
  | OO idea | its shape here | its non-shape |
94
94
  |---|---|---|
95
95
  | an object owns its state | a record owns its fields; the collection is the class | fields about X scattered on Y "for convenience" |
96
- | methods | **command-bindings**: verbs bound to a collection, gated on field state (`can-enter`/`can-exit`) — `dt commands <ref>` lists what applies to a record right now | a workflow engine; procedures copied into descriptions |
96
+ | methods | **command-bindings**: verbs bound to a collection, gated on field state (`can-enter`/`can-exit`) — `dt next <ref>` lists what applies to a record right now | a workflow engine; procedures copied into descriptions |
97
97
  | interfaces / mixins | **`templates:`** — a shared field set stamped onto consumers, merged at compile | copy-pasting the same four fields into six descriptors |
98
98
  | encapsulation | **module ownership** — a concept's fields live with the module that owns the concept | the module that happened to need the field first |
99
99
  | polymorphism | a **union reference** (`x-reference: [meetings, visits]`) or the open-world `'*'` for evidence/source fields | a `type` field plus fields that only apply to some rows |
@@ -845,7 +845,7 @@ useless until the flood is drained.
845
845
  command-binding's `can-enter`/`can-exit`. A field is referenced BY NAME rather than as a
846
846
  `<collection>/<id>` reference, so `store.rewriteRefs` can see none of those — which is exactly
847
847
  why this used to be "no rename verb, deliberately", with the honest sequence being add-new,
848
- script the values, `remove-field` the old. That sequence still works and is what you want when
848
+ script the values, `rm-field` the old. That sequence still works and is what you want when
849
849
  the values themselves have to CHANGE shape; when only the name changes, the verb is one command
850
850
  and takes `--dry-run`.
851
851
  - **Values** (an id, a reference target): `dt rename <collection>/<old> <new>` rewrites inbound
@@ -0,0 +1,53 @@
1
+ # exporting — the workspace for a reader that is not a coding agent
2
+
3
+ `compile` renders the runtime for harnesses that read files. `dt export <target>` renders it for a
4
+ consumer that does not: today **NotebookLM**, which holds a bounded number of *sources* per notebook
5
+ and takes its standing instructions as a 10,000-character *persona*. `dt help` has the flags; this
6
+ page has the judgment.
7
+
8
+ ## what travels, and what never does
9
+
10
+ Sensitivity is SCHEMA, decided once where the field is declared — never a flag on the export and
11
+ never inferred from a field's name (`email` travels unless somebody marked it):
12
+
13
+ - **a whole collection**: `dt set collections/<c> sensitive=true`. No record of it is written; the
14
+ schema source and the persona NAME the omission so the reader knows the gap is deliberate.
15
+ - **one field**: `dt add-field <c> --name <f> … --sensitive`, or `dt set-field <c> --name <f>
16
+ --sensitive` (`--sensitive false` clears). The field is projected out of every record.
17
+
18
+ Also never exported as records: the system collections (they ARE the schema source) and `codec: file`
19
+ collections (bytes a view draws). **Before the first export of a workspace, read the report** — it
20
+ prints every field that WAS exported per collection; that list is the review.
21
+
22
+ ## what a notebook gets
23
+
24
+ - **`dt · schema`** — one source: workspace → module → collection → field, one heading level per
25
+ step, with each collection's `use when`, id shape and every field's type, enum values and reference
26
+ target. The lexicon the persona cannot afford in full.
27
+ - **`dt · <collection>`** — one source per collection, `## <collection>/<id>` per record, references
28
+ rendered as `companies/acme (Acme Ltd)`. Over `--max-words` it shards into `dt · <c> [n/m]`.
29
+ - **the persona** — a Markdown template with `{{workspace}}` `{{schema_brief}}` `{{sources}}`
30
+ `{{omitted}}` `{{exported_at}}` `{{engine_version}}` `{{schema_title}}` `{{collections}}`; the
31
+ engine ships a default, `--instructions <file.md>` replaces it, an unknown placeholder is an
32
+ error. `--response-length` and `--mode` ride along to `notebooklm configure`.
33
+
34
+ **The budget is the plan's source cap** (`--plan standard|plus|pro|ultra|<n>`, default `standard` =
35
+ 50). Too many sources is a refusal that names the count and the remedies — nothing is dropped
36
+ quietly. A workspace with more collections than the plan has sources narrows with `--collections`.
37
+
38
+ ## syncing
39
+
40
+ Without `--notebook`/`--create` the export is a pure render into `--out` (default
41
+ `.cache/dreamteamer/notebooklm/`, gitignored) and touches no network. With one, it makes the notebook
42
+ match **by title**: adds new sources, replaces those whose content hash changed, removes its own stale
43
+ ones (the `dt · ` prefix marks ownership — a source added by hand is never touched), then applies the
44
+ persona. `notebook.json` in the out dir keeps the notebook id and per-source hashes, so the second run
45
+ is incremental and `--create` is needed once. Every vendor call names the notebook with `-n`;
46
+ `notebooklm use` is never run. **Preflight is `notebooklm auth check --test`** — the bare check
47
+ reports a stale session as valid — and an expired login is the operator's to renew (`notebooklm
48
+ login` opens a browser).
49
+
50
+ ## asking
51
+
52
+ Not a verb here. Ask with `notebooklm ask -n <id> --json`, and treat an answer as real only when it
53
+ has non-blank text AND at least one reference — exit 0 with an empty answer is a known shape.