dreamteamer 0.30.0 → 0.32.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.
Files changed (43) hide show
  1. package/README.md +47 -153
  2. package/collections/collections.collection.yaml +26 -9
  3. package/package.json +14 -2
  4. package/skills/using-dreamteamer/SKILL.md +16 -10
  5. package/skills/using-dreamteamer/references/before-you-build.md +4 -3
  6. package/skills/using-dreamteamer/references/collections.md +71 -1
  7. package/skills/using-dreamteamer/references/commands.md +3 -3
  8. package/skills/using-dreamteamer/references/data-modeling.md +9 -1
  9. package/skills/using-dreamteamer/references/extensions.md +60 -0
  10. package/skills/using-dreamteamer/references/records.md +17 -0
  11. package/skills/using-dreamteamer/references/sessions.md +4 -5
  12. package/skills/using-dreamteamer/references/skills.md +3 -5
  13. package/src/api.d.ts +109 -0
  14. package/src/api.js +70 -0
  15. package/src/check.js +57 -2
  16. package/src/checkout.js +105 -330
  17. package/src/cli.js +90 -274
  18. package/src/collections-cli.js +16 -165
  19. package/src/commit.js +7 -0
  20. package/src/compile.js +211 -155
  21. package/src/events.js +7 -2
  22. package/src/extensions.js +135 -0
  23. package/src/filter.js +1 -1
  24. package/src/harnesses.js +82 -135
  25. package/src/init.js +9 -3
  26. package/src/placement.js +209 -0
  27. package/src/records-api.d.ts +103 -0
  28. package/src/records-api.js +40 -0
  29. package/src/runtime.js +2 -2
  30. package/src/schema-ops.js +26 -9
  31. package/src/store.js +396 -35
  32. package/collections/containers.collection.yaml +0 -81
  33. package/collections/images.collection.yaml +0 -46
  34. package/collections/proofs.collection.yaml +0 -96
  35. package/skills/using-dreamteamer/references/exporting.md +0 -53
  36. package/skills/using-dreamteamer/references/proofs.md +0 -435
  37. package/skills/using-dreamteamer/references/worktrees.md +0 -235
  38. package/src/container-archive.js +0 -356
  39. package/src/containers.js +0 -635
  40. package/src/export-notebooklm.js +0 -502
  41. package/src/land.js +0 -743
  42. package/src/prove.js +0 -1922
  43. package/src/server.js +0 -481
package/README.md CHANGED
@@ -1,14 +1,24 @@
1
1
  # dreamteamer
2
2
 
3
- **Structured, modular memory for coding agents.**
3
+ **dreamteamer is a modular AI workspace builder.**
4
4
 
5
- Your agent already has a memory. You just can't see it — it's prose, in a black box somewhere,
6
- untracked and unshared. And memory *is* context, which is the single biggest lever on what your agent
7
- decides and how well it does it. So the most consequential thing in your setup is the one you have the
8
- least access to.
5
+ It gives your coding agents a structured, modular memory. Instead of hiding context in unmanaged prose, dreamteamer stores memory as **plain markdown files with a schema** directly in your git repo. Agents read it natively, and you can browse it as tables, boards, and forms.
9
6
 
10
- dreamteamer makes it **files with a schema**: plain markdown in your git repo that your agent reads
11
- natively, and that you can browse as tables, boards and forms.
7
+ No server, no account, no telemetry. Just a lightweight npm package.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - **Structured Memory as Files**: A record is just a markdown file with YAML frontmatter. Your agent opens it like any other file.
14
+ - **Strict Validation**: Schemas ensure your agents use agreed-upon terminology. Invalid links, wrong types, or unknown fields are rejected before they touch the disk.
15
+ - **Agent Agnostic**: Author a skill or schema once, use it everywhere. Core compiles to Claude Code, Cursor, Gemini CLI, and more.
16
+ - **NPM Modularity**: Distribute domain knowledge, skills, and agents using the npm ecosystem you already know.
17
+ - **Visual Editor**: The [VS Code extension](https://github.com/dreamteamer/dreamteamer-vscode) gives you a powerful UI (tables, boards, forms) over your plain text files.
18
+
19
+ ## Getting Started
20
+
21
+ Scaffold your AI workspace in seconds:
12
22
 
13
23
  ```bash
14
24
  npm i dreamteamer
@@ -18,15 +28,10 @@ npx dreamteamer check # prove every record and every link is intact
18
28
  npx dreamteamer help # the full command surface
19
29
  ```
20
30
 
21
- Apache-2.0. No server, no account, no telemetry.
31
+ ## How It Works
22
32
 
23
- ## Structured
24
-
25
- A record is a file. That's the whole trick.
26
-
27
- ```
28
- data/meetings/2026/07/kickoff.meeting.md
29
- ```
33
+ ### 1. Define Records
34
+ A record is a file inside your collection directory (e.g., `data/meetings/2026/07/kickoff.meeting.md`):
30
35
 
31
36
  ```yaml
32
37
  ---
@@ -38,160 +43,49 @@ project: projects/apollo
38
43
  Ada walked through the constraints. Lin owns the spec by Friday.
39
44
  ```
40
45
 
41
- Your agent opens that file the way it opens any file. Nothing is intercepted, nothing is proxied,
42
- there is no API to learn.
43
-
44
- But `attendees` isn't a string — it's a link. `dreamteamer check` proves every one of them resolves,
45
- and renaming `contacts/ada` updates everything pointing at it. A write with an unknown field, a wrong
46
- type, or a reference to a record that doesn't exist is **rejected before it touches disk**.
47
-
48
- **A schema is an agreement about what things are called.** Shared terminology with guardrails — not a
49
- cage, because it stays negotiable. You change it by saying so:
50
-
51
- > *"From here on a client has a renewal date, and it's a date."*
52
-
53
- That's a schema update and a data migration, and it's an **explicit, reviewable event** rather than
54
- silent drift. Once it exists you get the column in a table, the field in a form, validation, sorting
55
- and aggregation — all of it falling out of having said what the thing is.
56
-
57
- The shape of a record is deliberately dull, because dull is what survives:
58
-
59
- - records are `<id>.<suffix>.<ext>` files; **the id is the path** inside the collection folder
60
- - references are `<collection>/<id>` — always qualified, greppable, never a bare name
61
- - a collection may be scoped under a **declared namespace** — `health/doctors/dana-levi`, stored in
62
- `data/health/doctors/`. The default namespace is the empty prefix, so `tasks/kickoff` is unchanged
63
- - a write lands on disk; `dreamteamer commit` publishes it, one commit per repo
64
- - schemas are JSON Schema in a YAML file, one per collection
65
-
66
- ### Machine-specific references
67
-
68
- Some things a record points at only exist on one machine — a synced Drive folder, an external disk,
69
- a checkout somewhere else. Those are written as **templates**, never as absolute paths:
70
-
71
- ```yaml
72
- source_file: ${env:FILES_FOLDER}/2026/q3.pdf
73
- ```
74
-
75
- Three variables, borrowing VS Code's grammar: `${env:NAME}` — declared in `dreamteamer.vars` in
76
- `package.json`, valued in the gitignored `.env` (an empty or whitespace-only value counts as no
77
- value at all) — plus `${workspaceFolder}` and `${userHome}`. One verb renders them:
46
+ ### 2. Enforce Schemas
47
+ Your agents read the file directly, but `dreamteamer check` ensures that every reference (like `contacts/ada`) actually resolves. **A schema is an agreement about what things are called.**
78
48
 
79
- ```bash
80
- npx dreamteamer resolve '${env:FILES_FOLDER}/x' # → /Volumes/annex/x
81
- npx dreamteamer resolve <collection>/<id> <field> # render what a record already holds
82
- ```
83
-
84
- **Templates are ordinary data — write them literally; nothing substitutes until `resolve` is
85
- called.** `get`, `list`, `check` and every harness see the template verbatim, which is exactly what
86
- makes the record mean the same thing on every machine instead of quietly meaning two things. An
87
- undeclared key and a declared-but-absent one are different errors, and `compile` warns — by name,
88
- never by value — when a declared var has nothing behind it here.
49
+ ### 3. Share & Compose
50
+ Because dreamteamer uses npm, you can install domain modules containing collections, skills, and agents. If a module doesn't perfectly fit your needs, you don't fork it—you adapt it locally by overriding just the schema fields you need to change.
89
51
 
90
- ## Modular
52
+ ## Programmatic Usage
91
53
 
92
- **Data and skills are the new app structure.** A coding agent with the right skills over the right
93
- data is arbitrary functionality — but composing that with no module system is where most setups stall.
54
+ Use dreamteamer in your own scripts or apps:
94
55
 
95
- So dreamteamer doesn't invent one. **It uses npm.**
56
+ ```javascript
57
+ import { openWorkspace, Store } from 'dreamteamer';
96
58
 
97
- `node_modules` is battle-tested, universally adopted, and already sitting in nearly every
98
- coding-agent setup. A module contributes collections, skills, agents, commands, command-bindings and
99
- UI views — and skills and agents are treated as exactly what they are: **memory that loads into
100
- context**, living in the same module structure as everything else, in a standard your tooling already
101
- understands.
59
+ const ws = await openWorkspace('.'); // no compile, no writes
60
+ const store = new Store(ws);
102
61
 
103
- Three channels, one shape:
104
-
105
- ```
106
- modules/<name>/ # lives in this repo
107
- git_modules/<name>/ # lives in its own repo
108
- node_modules/<name>/ # published package
62
+ for (const { id, fields } of store.readAll('notes')) {
63
+ console.log(id, fields.title);
64
+ }
109
65
  ```
110
66
 
111
- Precedence runs top to bottom, so a local copy shadows a published one — which is how you develop a
112
- module and use it in the same workspace at the same time.
113
-
114
- A published package or a git clone may carry **several** modules: when its root has a `modules/`
115
- folder, each `modules/<name>/` with a `dreamteamer` key in its `package.json` is a module on that
116
- channel, and the root itself is never compiled. One `npm install` then delivers a whole family, and the
117
- workspace keeps what it wants — a bare module name in `dreamteamer.disable` drops a module before
118
- compile looks at it (an entry with a slash, `<module>/<entity>`, still disables one entity):
119
-
120
- ```json
121
- "dreamteamer": { "disable": ["recordings", "introspection"] }
122
- ```
123
-
124
- Disabling a module another one declares in `dependencies` is refused, naming what is present. The
125
- workspace's own `modules/*` never nest.
126
-
127
- Sources live **flat at a module root** — `modules/crm/skills/`, beside `package.json` — and a folder
128
- at a module root that isn't a known kind is a compile error rather than a silent skip.
129
-
130
- ### Modules are not rigid
131
-
132
- This is the part that differs from npm on purpose.
133
-
134
- Installing a module into a workspace that already has opinions — its own idea of what a `contact` is —
135
- is a **negotiation, not an overwrite**. Four workspaces wanted a CRM and all four wanted a different
136
- `contacts`. A hard import would force one answer and make every divergence a fork.
137
-
138
- Two same-name collections is a compile error that names both descriptors and tells you the move:
139
- declare `extends: <module>/<collection>` and overlay only what differs. Because every schema is one
140
- small YAML file, adapting is cheap — read it, change what doesn't fit, and the diff shows exactly what
141
- you agreed to.
142
-
143
- So domain modules are **recipes you copy and adapt, not packages you install**, and divergence is the
144
- normal case rather than a failure.
145
-
146
- ## Every harness, one source
147
-
148
- `compile` writes `.dreamteamer/` — the single runtime read surface — and from there into per-harness
149
- adapters: Claude Code, Codex, Pi, Gemini CLI, Cursor. Author a skill once; every agent you run sees it.
150
-
151
- ## The editor
152
-
153
- Extension id `dreamteamer.dreamteamer-vscode` (Marketplace · Open VSX). `init` and `compile` write the
154
- `.vscode/extensions.json` recommendation, and `dt status` reports whether it is active.
155
-
156
- [dreamteamer-vscode](https://github.com/dreamteamer/dreamteamer-vscode) gives you tables, boards,
157
- calendars, maps, forms and a data-model designer over the same files — and it loads **the engine your
158
- workspace pins**, so the editor, the CLI and any agent session are provably running the same code.
159
-
160
- ## Docs
161
-
162
- This is an agent-native tool, so its documentation is shipped as skills the agent loads on demand —
163
- and you can read them like any other file:
67
+ ## Extensions
164
68
 
165
- - [`skills/using-dreamteamer`](skills/using-dreamteamer) — the one skill: working with records
166
- (the CLI, conventions, commits) and modeling the workspace (collections, skills, agents,
167
- commands, UI views — and which of those a given request should become), each topic a reference
168
- loaded on demand
169
- - [`docs/one-skill-blast-radius.md`](docs/one-skill-blast-radius.md) — the 0.16.0 skill
170
- consolidation: what breaks for a consumer, what to grep for, which claims were verified live
171
- - [`docs/repos-and-modules.md`](docs/repos-and-modules.md) — attached repos vs modules, and why they
172
- have different homes
173
- - [`docs/namespaces-blast-radius.md`](docs/namespaces-blast-radius.md) — scoping collections under a
174
- namespace (`health/doctors`), what it costs consumers, and why the default namespace is transparent
175
- - [`UPDATING.md`](UPDATING.md) — what to do when upgrading, one section per release
69
+ An extension adds verbs, source kinds or harnesses through one contract
70
+ ([`references/extensions.md`](skills/using-dreamteamer/references/extensions.md)). It can be a
71
+ workspace module (`modules/<id>/package.json` declaring `dreamteamer.extension`) or an installed
72
+ dependency.
176
73
 
177
- ## What it isn't
74
+ Behaviour proofs, worktrees, the REST API, the NotebookLM exporter and the local Docker host left core
75
+ in 0.31.0 and return as extensions. None is published yet.
178
76
 
179
- Not a database — records are files and git is the history. Not a cloud service — there is no server
180
- and no account. Not a note-taking app — it's the layer underneath one.
77
+ ## Agent-Native Documentation
181
78
 
182
- And it is **not** for data that needs row-level access control, field-level encryption, or provable
183
- erasure. Git cannot do those, and pretending otherwise is how people get hurt. This is for
184
- human-scale structured knowledge: thousands of records, not millions.
79
+ Because this is an agent-native tool, documentation is shipped as skills your agent loads on demand:
80
+ - [`skills/using-dreamteamer`](skills/using-dreamteamer) — Core skill for working with records and modeling the workspace.
81
+ - [`docs/`](docs/) — Deeper architectural context, upgrade guides, and rationale.
185
82
 
186
83
  ## Contributing
187
84
 
188
- Issues are welcome. For anything larger than a typo, please open a discussion before a pull request —
189
- this is a small, deliberately lean codebase (`npm run metrics` enforces size budgets), and it's better
190
- to agree on the shape first.
85
+ We welcome issues and discussions! For anything larger than a typo, please open a discussion before submitting a PR. This is a deliberately lean codebase and we prefer to agree on shape first. `npm run verify` enforces our size and test budgets.
191
86
 
192
- `npm run verify` is the gate: import-layer direction, size budgets, and the test suite (tiers 1+2,
193
- zero dependencies, a few seconds). See [CONTRIBUTING.md](CONTRIBUTING.md).
87
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
194
88
 
195
89
  ## License
196
90
 
197
- Apache-2.0 © 2026 Gilad Khen. See [LICENSE](LICENSE).
91
+ [Apache-2.0](LICENSE) © 2026 Gilad Khen.
@@ -67,15 +67,6 @@ schema:
67
67
  have lived in `modules/<module>/<kind>/` since the 2026-08-05 flatten, and a `system/`
68
68
  prefix is only how `runtime.js` recognises a RUNTIME collection in a descriptor compiled
69
69
  by a pre-flatten engine.
70
- driver:
71
- type: string
72
- description: >-
73
- The name of an engine DRIVER that answers this collection's verbs instead of a folder of
74
- files — `docker` is the one shipped (src/containers.js: `containers`, `images`). A driver
75
- collection has no records on disk: its derived `path` names a folder that never exists,
76
- so check, commit and the store read zero records and never write one; the CLI and the REST
77
- route dispatch to the driver first. A string checked against the drivers the engine has,
78
- not an enum — one implementation, and a second is a code change, not a vocabulary change.
79
70
  codec:
80
71
  type: string
81
72
  enum: [md, yaml, json, file]
@@ -100,6 +91,32 @@ schema:
100
91
  entry:
101
92
  type: string
102
93
  description: Folder shapes only — the file inside the folder that IS the record (e.g. SKILL.md).
94
+ under:
95
+ type: object
96
+ description: >-
97
+ RELATIONSHIP-BASED STORAGE — records live INSIDE the folder of the record they belong
98
+ to. `{ field: company, path: meetings }` puts a meeting whose `company` is
99
+ `companies/northwind` at `<companies root>/northwind/meetings/<id>.meeting.md`; one with
100
+ no `company` stays in this collection's own `path`, which remains its fallback root.
101
+ Still ONE logical collection: `list` is the union across every parent folder, a
102
+ reference is `<collection>/<id>` wherever the file sits, and the id never changes when
103
+ the owner does — `set <field>=…` MOVES the file. `field` must be a scalar `x-reference`
104
+ to exactly one collection, that collection must be `shape: folder`, and one level is
105
+ supported (a placed collection cannot be a parent). The field is the intended owner and
106
+ the folder is observed placement: `check` reports a disagreement, `dreamteamer relocate`
107
+ reconciles it, nothing infers an owner from where a file was found. Not for a record
108
+ many parents share equally — that is a plain reference.
109
+ required: [field, path]
110
+ properties:
111
+ field:
112
+ type: string
113
+ description: The scalar reference field on THIS collection that names the parent record.
114
+ path:
115
+ type: string
116
+ description: The folder INSIDE each parent record's folder that holds these records — relative, e.g. `meetings`.
117
+ collection:
118
+ type: string
119
+ description: DERIVED by compile, never authored — the parent collection, read off `field`'s x-reference so the record layer never opens a schema to find it.
103
120
  repo:
104
121
  type: string
105
122
  description: DERIVED by compile, never authored — the workspace-relative root of the git repo holding these records ('.' is the workspace). Set from the owning module's `owns-data`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dreamteamer",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "A workspace compiler for coding agents — 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>",
@@ -28,6 +28,19 @@
28
28
  "engines": {
29
29
  "node": ">=20"
30
30
  },
31
+ "main": "./src/api.js",
32
+ "types": "./src/api.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./src/api.d.ts",
36
+ "default": "./src/api.js"
37
+ },
38
+ "./records": {
39
+ "types": "./src/records-api.d.ts",
40
+ "default": "./src/records-api.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
31
44
  "bin": {
32
45
  "dreamteamer": "bin/dreamteamer.js",
33
46
  "dt": "bin/dreamteamer.js"
@@ -47,7 +60,6 @@
47
60
  "dependencies": {
48
61
  "ajv": "^8.17.1",
49
62
  "ajv-formats": "^3.0.1",
50
- "express": "^5.2.1",
51
63
  "fractional-indexing": "^4.0.0",
52
64
  "js-yaml": "^4.1.0",
53
65
  "yaml": "2.8.4"
@@ -30,7 +30,10 @@ unsure which skill owns the job in front of you.
30
30
 
31
31
  - a record is a `<id>.<suffix>.<ext>` file (or a folder, for folder-shape collections). **the id
32
32
  is the path** inside the collection folder minus suffix and extension — nested folders join in:
33
- `data/meetings/2026/07/standup.meeting.md` ⇒ id `2026/07/standup`.
33
+ `data/meetings/2026/07/standup.meeting.md` ⇒ id `2026/07/standup`. a collection may instead keep
34
+ its records INSIDE the folder of the record they belong to (`storage.under` — a company's
35
+ meetings in `data/companies/<company>/meetings/`): still ONE collection, the same id, the same
36
+ `meetings/<id>` reference; only the folder follows the owner field (`references/collections.md`).
34
37
  - **references are `<collection>/<id>`** strings — always qualified, greppable, never a bare name
35
38
  and never a file path.
36
39
 
@@ -61,9 +64,13 @@ dispatch, so it cannot drift):
61
64
 
62
65
  - a collection may be spelled in the SINGULAR on any of these (`dt add task "call the bank"` — one bare positional is the title); references inside records still spell the full name
63
66
  - read & measure — `list` `get` `values` `history` `diff` `next` `relations` `resolve`
64
- - write & publish — `add` `set` `rm` `rename` `move` `revert` `commit`
67
+ - write & publish — `add` `set` `rm` `rename` `move` `revert` `commit` `relocate`
65
68
  - 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)
66
- - workspace — `init` `install` `land` `update` `compile` `check` `prove` `status` `start` `changes` `export` `help`
69
+ - workspace — `init` `install` `update` `compile` `check` `status` `changes` `help`
70
+ - an EXTENSION (a workspace module or a dependency declaring `dreamteamer.extension`) adds verbs of
71
+ its own, and `dt help` lists them under its name; each ships the skill that teaches it. A verb that
72
+ answers "left core in 0.31.0" (`dt prove` · `dt land` · `dt worktree` · `dt serve` · `dt notebooklm` · the Docker
73
+ host) has no published extension yet — see `references/extensions.md`.
67
74
 
68
75
  don't learn syntax from prose, this skill included: prose drifts, and `help` ships in
69
76
  the same file as the dispatch it documents. run it once before your first write of a session.
@@ -99,17 +106,16 @@ Load by the map; nothing here is loaded "just in case".
99
106
  | a brand-new or empty workspace, dreamteamer over an existing pile of files, "help me set this up" | `references/getting-started.md` |
100
107
  | read, create, update, rename, delete, commit — or UNDO — a record | `references/records.md` |
101
108
  | "what changed while I was away" | `references/changes.md` |
102
- | the workspace has to reach a reader that is not a coding agent — a NotebookLM notebook; "which fields are sensitive" | `references/exporting.md` |
103
109
  | 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) |
104
110
  | a collection or field, mechanically — the descriptor, the system and field verbs, `templates:`/`extends:`, a compile or check message | `references/collections.md` |
111
+ | "keep a company's meetings in the company's folder" — records stored beside the record they belong to, a `placed … but` check report, `dt relocate` | `references/collections.md` (declaring it) · `references/records.md` (working with it) |
105
112
  | knowledge a session should find on its own | `references/skills.md` |
106
113
  | "let me type one word and have this done" | `references/commands.md` |
107
114
  | "which command applies to this record?" — a binding, a gate | `references/commands.md` |
108
- | "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` |
109
115
  | a job needing a fresh context and its own tools | `references/agents.md` |
110
116
  | a route, a nav entry, a board / calendar / map over records | `references/ui-views.md` |
111
117
  | a rendering or editing behaviour nothing registered has | `references/ui-components.md` |
112
- | a second checkout — making one ready, landing its records, a harness that cuts them for you | `references/worktrees.md` |
118
+ | an optional tool — behaviour proofs, worktrees, a REST server, an exporter, a new harness — and whether to write one | `references/extensions.md` |
113
119
  | other agent sessions are running on this machine — finding them, messaging one, coordinating several, and what may not cross between them | `references/sessions.md` |
114
120
 
115
121
  three act-two tie-breakers, because they are the ones that go wrong:
@@ -131,7 +137,8 @@ workspace's decision log (where one exists) wins over older documents.
131
137
  ## system entities take the RECORD verbs
132
138
 
133
139
  Modules, collections, skills, agents, commands, command-bindings, ui-views, collection-templates
134
- and proofs are collections in the runtime, and since 0.19.0 the ordinary verbs write them:
140
+ — and any kind an installed extension contributes — are collections in the runtime, and since
141
+ 0.19.0 the ordinary verbs write them:
135
142
 
136
143
  ```
137
144
  dt add modules --name core --description "The shared nouns."
@@ -143,9 +150,8 @@ dt set modules/hr namespaces=hr dependencies=modules/core
143
150
  dt rm modules/hr --force # --dry-run first; it prints its plan
144
151
  ```
145
152
 
146
- ⚠ **`proofs` is the one exception, and only to `add`:** a proof is hand-authored like a skill or a
147
- command, so `dt add proofs` is refused, naming the file to write
148
- (`modules/<module>/proofs/<id>.proof.yaml`, `references/proofs.md`). Every other verb works on it.
153
+ ⚠ **`add` scaffolds skills only.** Agents, commands, bindings, templates and contributed kinds are
154
+ hand-authored — `dt add agents` is refused, naming the file to write. Every other verb works on them.
149
155
 
150
156
  `dt schema <op>` is **gone** since 0.19.0 and fails with the translation printed. `UPDATING.md` has
151
157
  the complete mapping table.
@@ -49,9 +49,10 @@ Only after all four: build it, in the module that owns the concept.
49
49
 
50
50
  ## name the proof before you build it
51
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.
52
+ **Before writing the thing, say how anyone would know it works** — one sentence: *this record, in
53
+ this state, after this step, must look like this*. It costs a minute and it is the cheapest design
54
+ review there is. (With a proofs extension installed, that sentence is exactly the shape of a
55
+ `proofs` record, and `dt prove` runs it — its own skill says how.)
55
56
 
56
57
  ⚠ **When you cannot name one, the artifact has no observable post-state, and THAT is the first
57
58
  thing to change** — not something to note and carry on past. A skill nothing can check is a skill
@@ -172,7 +172,77 @@ is copied.
172
172
  - Nested namespaces work (`work/clients`); the longest declared prefix wins.
173
173
  - ⚠ **No collection may store records inside another's folder** — a namespace folder cannot
174
174
  itself be a collection root. Compile refuses it, because the outer collection would index the
175
- inner one's records as its own.
175
+ inner one's records as its own. The one DECLARED exception is the next section: a collection
176
+ stored under the records of a folder-shape parent, where compile knows exactly which files
177
+ belong to whom.
178
+
179
+ ## relationship-based storage — records beside the record they belong to
180
+
181
+ A collection can keep each record INSIDE the folder of the record it belongs to, so a company's
182
+ folder holds the company's meetings and a browse of `data/companies/northwind/` shows the whole
183
+ account. It is declared on the CHILD's `storage`, in one line, and changes nothing about what the
184
+ collection IS:
185
+
186
+ ```yaml
187
+ # modules/default/collections/meetings.collection.yaml
188
+ storage: { path: data/meetings, suffix: meeting, under: { field: company, path: meetings } }
189
+ ```
190
+
191
+ ```text
192
+ data/companies/northwind/company.md ← the parent: shape: folder, entry: company.md
193
+ data/companies/northwind/meetings/2026/10/kickoff.meeting.md ← meetings/2026/10/kickoff, company: companies/northwind
194
+ data/meetings/2026/10/offsite.meeting.md ← meetings/2026/10/offsite, no company: the FALLBACK root
195
+ ```
196
+
197
+ - **Still one logical collection.** `dt list meetings` is the union across every company folder
198
+ and the fallback root, ordered by id; `dt get meetings/2026/10/kickoff` finds the file wherever it
199
+ sits; a reference is `meetings/<id>` everywhere. Nothing is spelled per company — no descriptor,
200
+ no skill, no view.
201
+ - **The id is independent of placement.** `dt set meetings/<id> company=companies/harbor` MOVES
202
+ the file into Harbor's folder and changes nothing else: not the id, not one inbound reference.
203
+ Clearing the field moves it back to the fallback root. An id is unique across every root, and a
204
+ second file claiming one is a `check` violation and a write refusal, never last-one-wins.
205
+ - **`field`** is a scalar `x-reference` to exactly ONE collection (a list has no single folder; a
206
+ union has no single parent). **`path`** is a relative folder inside each parent record's folder.
207
+ compile derives `under.collection` from the field; nothing else is authored.
208
+ - **The parent must be `shape: folder`** (`storage: { shape: folder, entry: company.md }`) — only
209
+ a folder can hold anything beside the record. A file-shape collection that should become a
210
+ parent changes its descriptor to folder shape, and `dt relocate <collection>` then moves each
211
+ `<id>.<suffix>.md` into `<id>/<entry>` with its id unchanged (`check` names them until it runs).
212
+ - **One level.** A placed collection cannot itself be a parent; the child is a text record
213
+ (`md` · `yaml` · `json`, file shape) — an opaque or folder-shape child is refused for now. Two
214
+ children of one parent need two different paths, and a path never equals the parent's entry.
215
+ - **The field is the owner; the folder is observed placement.** A file found under the wrong
216
+ company — moved by hand, or sitting in the fallback root from before the declaration existed — is
217
+ `placed under … but <field> is …` in `check`, which changes nothing. `dt relocate <collection>`
218
+ (or `<collection>/<id>`, `--dry-run` first) moves files to where the compiled descriptor puts
219
+ them and refuses a source with unpublished changes or an occupied destination. Editing some OTHER
220
+ field never relocates as a side effect; nothing ever infers an owner from where a file was found.
221
+ - **A parent with records inside its folder cannot be removed** — not with `--force` either;
222
+ reassign or clear their owner first. Renaming the parent carries the folder with everything in
223
+ it and rewrites the children's owner field; their ids do not change.
224
+ - **Adopting it on existing data is three explicit steps**, each reviewable: make the parent folder
225
+ shape and `relocate` it; add `under` to the child and compile (no file moves at compile — `check`
226
+ reports every mismatch); `relocate` the child, `--dry-run` first. `dt commit` stages a moved
227
+ record's old and new path together; `dt revert` of an owner change moves the file back.
228
+ - **Removing or changing `under` is the same walk backwards, and compile holds the door.** While
229
+ records still sit inside parent folders, a compile that drops the declaration, changes its
230
+ `path`, or moves the PARENT collection's `storage.path` is REFUSED — the new descriptor would stop
231
+ every reader seeing them. The order is
232
+ `dt relocate <collection> --to-root` (every placed record back into the collection's own folder,
233
+ ids unchanged, under the still-current declaration) → edit the descriptor → compile → `dt relocate
234
+ <collection>` to place them under the new path. The same order renames a placed collection.
235
+ - **`relocate` refuses before it moves anything**: a dangling or malformed owner field (fix the
236
+ field first — it never makes a folder for a parent that does not exist), an occupied destination,
237
+ a source with unpublished changes, or a destination behind a symlink. One problem refuses the
238
+ whole plan; `--dry-run` reports it.
239
+ - **Inside a parent's folder, only real entries count.** A symlink at a child root, on the way to
240
+ one, or anywhere beneath one — a folder or a file — is never written through and never read as a
241
+ record; `check` names it. The collection's own roots (`data/`, `storage.path`) are not subject to
242
+ this; the rule is about what a record folder may contain.
243
+ - **When NOT to use it.** A record several parents share equally, a record whose owner is usually
244
+ unknown, or a collection nobody browses as a folder — keep conventional storage and a plain
245
+ reference. Folder grouping is a browsing convenience, never a permission boundary.
176
246
 
177
247
  ## `templates:` — a live shared field set
178
248
 
@@ -151,11 +151,11 @@ 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
154
+ - **A `can-exit` and a proof's `count` (a proofs extension) answer different questions — put
155
+ each expectation on its own side.** A gate is a filter over ONE record, evaluated on every render of `dt next` and every board
156
156
  the studio draws, so it can only ever read that record's own fields (plus one outbound hop) — which
157
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
158
+ inexpressible there. A **proof** is evaluated on demand and is collection-scoped, so
159
159
  it says the thing a gate cannot: `{ collection: summaries, where: { about: { _eq: '{record}' } },
160
160
  count: { _delta: 1 } }` — *running this command left one more summary behind*. (`{record}` inside a
161
161
  proof's `where` is SUBSTITUTED with the picked record's reference before the filter runs, which is
@@ -300,7 +300,15 @@ roadmap, not a model; wait for the second consumer.
300
300
  `max_bytes` (default 200 KB). A big binary is not a record: it lives outside the vault under a
301
301
  declared var, with an ordinary record carrying the `${env:...}` template that points at it.
302
302
  - **`shape: folder` when a record is intrinsically several files** (a skill with references beside
303
- it). Rare; prefer one file until the record itself demands companions.
303
+ it), or when OTHER collections' records should live inside it — see the next bullet. Otherwise
304
+ prefer one file until the record itself demands companions.
305
+ - **`storage.under` when people browse a parent as a unit** — a company folder holding that
306
+ company's meetings, a case folder holding its documents. It is one line on the CHILD
307
+ (`under: { field: company, path: meetings }`), the collection stays ONE collection with the same
308
+ ids and references, and the owner field is what moves a file (`collections.md`). Choose ONE
309
+ physical owner and leave every other relationship a plain reference; keep conventional storage
310
+ when no single owner is sensible, when the owner is usually unknown, or when nobody would open
311
+ the folder. It organises files; it grants nothing.
304
312
  - **Machine-specific paths are templates, never absolute paths.** `${env:FILES_FOLDER}/…` is inert
305
313
  data rendered per machine by `dt resolve`; an absolute path in a record is wrong on every other
306
314
  machine, silently. **A files folder is named after the collection or field that indexes it, and
@@ -0,0 +1,60 @@
1
+ # extensions — optional tools, and the one seam they plug into
2
+
3
+ Core is records plus the workspace compiler. Anything with a lifecycle of its own — a server, a
4
+ Docker host, a behaviour-test runner, an exporter to one vendor — is an **extension**: code the engine
5
+ calls, which a workspace has when it wants that capability and does without otherwise.
6
+
7
+ The verbs that left core in 0.31.0 — `prove` · `land` · `worktree`, `serve`, `notebooklm`, and the
8
+ Docker host — return as extensions, and **none is published yet**. Typed against core, each fails with
9
+ exit 2 and says so. A workspace that needs one now carries it as its own module (below), or stays on
10
+ 0.30.x.
11
+
12
+ `dt status` lists the extensions this workspace loaded, and `dt help` appends each one's usage.
13
+
14
+ ## how a workspace turns one on
15
+
16
+ Two places, one declaration — a `package.json` carrying `"dreamteamer": { "extension": "./entry.js" }`:
17
+
18
+ - **a workspace module**, `modules/<id>/package.json`. Its code is the workspace's own, like `bin/`,
19
+ so it needs no package and no npm. This is how a workspace carries an extension nobody has
20
+ published, and it shadows a dependency of the same name.
21
+ - **a direct dependency.** npm put the code there on purpose; a transitive package is never loaded
22
+ however it advertises.
23
+
24
+ A bare entry in `dreamteamer.disable` switches one off while leaving it in place. Two extensions
25
+ claiming the same verb, source kind or harness is a refusal at load, naming both — never "last one
26
+ wins".
27
+
28
+ An extension is ALSO a content module (its `dreamteamer` key makes it one): its collections and skills
29
+ travel with its code, and compile discovers them like any other module's.
30
+
31
+ ⚠ **Remove an extension and its kind goes with it.** A workspace holding `proofs/` folders with no
32
+ extension contributing `proofs` fails compile on the unknown folder — deliberately: a proof that
33
+ compiles with no validator would be a claim nothing checks. The next compile after a removal also
34
+ prunes the extension's compiled folder and its managed blocks.
35
+
36
+ ## writing one — the contract
37
+
38
+ The entry's default export is `activate(dt)`. `dt` is the RUNNING engine's public API
39
+ (`import('dreamteamer')`'s namespace), so the extension never imports an engine of its own and can
40
+ never disagree with the one the operator ran. It returns a contribution; every key is optional:
41
+
42
+ | key | shape | what core does with it |
43
+ |---|---|---|
44
+ | `commands` | `{ <verb>: { usage, run(ws, argv) } }` | `dt <verb> …` runs it in-process with the opened workspace; the return is the exit code |
45
+ | `sourceKinds` | `[{ kind, exclude?: [subtree] }]` | compile stages `<module>/<kind>/` like a built-in kind; `exclude` keeps fixtures out |
46
+ | `analyze` | `(draft) → { errors?, warnings?, notes? }` | runs after assembly, before any output is replaced; an error fails the compile (and every schema write, which compiles) |
47
+ | `harnesses` | `{ <id>: (ctx) → { blocks: { <file>: text }, summary } }` | a harness adapter writing managed blocks into user-owned files |
48
+ | `orientation` | a string | one paragraph appended to every orientation block |
49
+ | `hooks` | `{ <ClaudeHookEvent>: '<dt verb args>' }` | merged into `dt install --print-adapters` |
50
+
51
+ The `draft` is data only: the staged entries, the final merged descriptors, the modules, declared var
52
+ and env key NAMES, and the previous manifest. No writer, no Store, no environment values — an analysis
53
+ that needs one is a command, not an analysis.
54
+
55
+ ## a module, or an extension?
56
+
57
+ A **module** ships collections, skills, commands, agents, views and UI code — content. An
58
+ **extension** ships code the engine calls. Reach for an extension only when the capability needs one
59
+ of the contribution keys above; everything else is a module, and a module is copied and adapted per
60
+ workspace rather than installed (`references/before-you-build.md`).
@@ -111,6 +111,23 @@ first slash. the default namespace has no prefix (`tasks/kickoff`, exactly as al
111
111
  undeclared prefix reads as a nested id and dangles — `dt check` says so. declaring one is
112
112
  `collections.md`.
113
113
 
114
+ ## records stored under another record — the folder follows the owner
115
+
116
+ a collection may declare `storage.under` (`collections.md`): a meeting with a `company` lives in
117
+ that company's folder, one without stays in `data/meetings/`. working with it is unchanged in
118
+ every way that names a record — `list` is the whole collection, `get`/`set`/`rm`/`rename` take
119
+ `meetings/<id>` wherever the file sits, references never carry a folder — and different in one:
120
+ **the owner field moves the file.** `dt set meetings/<id> company=companies/harbor` relocates the
121
+ record into Harbor's folder with the same id and every inbound reference intact; `company=`
122
+ moves it back to the fallback root. so never `mv` one by hand, exactly as for a rename — a file
123
+ under the wrong company is what `check` reports as `placed under … but`, and `dt relocate
124
+ <collection>[/<id>]` (`--dry-run` first) is what moves it to where its field says — refusing whole
125
+ when an owner field dangles, a destination is taken or a source is unpublished. a parent
126
+ holding records in its folder refuses `rm` until they are reassigned; `dt commit <collection>/<id>`
127
+ after a move publishes both paths; `dt revert` of an owner change moves the file back too. before
128
+ the declaration is removed or its path changed: `dt relocate <collection> --to-root`
129
+ (`collections.md` has the order — compile refuses the edit while records would be stranded).
130
+
114
131
  ## two-way relations — the mirror is generated, and read-only
115
132
 
116
133
  a reference field may declare `x-inverse`: compile GENERATES the field it names on the TARGET