dreamteamer 0.16.0 → 0.17.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/collections/collections.collection.yaml +10 -0
- package/collections/modules.collection.yaml +8 -0
- package/collections/repos.collection.yaml +5 -1
- package/package.json +1 -1
- package/skills/using-dreamteamer/SKILL.md +21 -5
- package/skills/using-dreamteamer/references/collections.md +14 -3
- package/skills/using-dreamteamer/references/data-modeling.md +8 -2
- package/skills/using-dreamteamer/references/getting-started.md +113 -0
- package/skills/using-dreamteamer/references/records.md +29 -11
- package/skills/using-dreamteamer/references/ui-views.md +11 -4
- package/src/check.js +15 -9
- package/src/cli.js +22 -12
- package/src/collections-cli.js +158 -26
- package/src/commit.js +9 -1
- package/src/compile.js +95 -14
- package/src/env-vars.js +15 -0
- package/src/init.js +27 -8
- package/src/namespace.js +15 -0
- package/src/ref.js +19 -0
- package/src/schema-ops.js +115 -43
- package/src/store.js +273 -52
|
@@ -105,6 +105,16 @@ schema:
|
|
|
105
105
|
icon:
|
|
106
106
|
type: string
|
|
107
107
|
description: material-symbols-outlined icon name, drawn in the nav and page header. The VS Code tree maps it to the nearest codicon — an unmapped name falls back to a generic cylinder, so pick one that is already mapped or add the row.
|
|
108
|
+
unresolved_peers:
|
|
109
|
+
type: array
|
|
110
|
+
items: { type: string }
|
|
111
|
+
description: >-
|
|
112
|
+
DERIVED by compile, never authored — the collections THIS one references that its module
|
|
113
|
+
declared as a `peerDependencies` peer and nothing installed provides. `check` reads it to
|
|
114
|
+
excuse those references as unresolvable rather than wrong, which is what lets a module be
|
|
115
|
+
opened on its own. It is stated here as DATA precisely so the record layer never has to
|
|
116
|
+
learn what a module is; a bare string list rather than `x-reference: collections`, because
|
|
117
|
+
the whole point is that the target is absent.
|
|
108
118
|
owner:
|
|
109
119
|
type: string
|
|
110
120
|
x-reference: modules
|
|
@@ -66,6 +66,14 @@ schema:
|
|
|
66
66
|
items:
|
|
67
67
|
type: string
|
|
68
68
|
x-reference: collections
|
|
69
|
+
# SOFT, because the normal state of a module opened on its own is that its peers are NOT
|
|
70
|
+
# installed — that is the whole reason a peer is declared rather than depended on. Validated
|
|
71
|
+
# hard, this field made the record it is projected onto fail `check` for saying exactly what
|
|
72
|
+
# it exists to say, and there was no state in which an optional cross-module reference
|
|
73
|
+
# passed: dropping the declaration made `compile` fail instead, naming peerDependencies as
|
|
74
|
+
# the remedy. Present peers still resolve, and a peer that names a collection nobody ever
|
|
75
|
+
# installs is reported by `check` as a warning off `unresolved_peers`, not silently.
|
|
76
|
+
x-reference-soft: true
|
|
69
77
|
collections:
|
|
70
78
|
type: array
|
|
71
79
|
description: >-
|
|
@@ -33,7 +33,11 @@ schema:
|
|
|
33
33
|
description: 'Opaque path segment grouping clones by the account that owns them: <repos-path>/<identity>/<name>.'
|
|
34
34
|
path:
|
|
35
35
|
type: string
|
|
36
|
-
description:
|
|
36
|
+
description: >-
|
|
37
|
+
Where the working tree goes, winning over the derived <repos-path>/<identity>/<name>. Takes
|
|
38
|
+
the ${env:NAME} / ${workspaceFolder} / ${userHome} templates, rendered when the path is
|
|
39
|
+
needed — which is how a repo that lives OUTSIDE this workspace is expressed, since an
|
|
40
|
+
absolute rendering escapes the root while a plain relative one still anchors to it.
|
|
37
41
|
visibility:
|
|
38
42
|
type: string
|
|
39
43
|
enum: [private, public]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dreamteamer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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>",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: using-dreamteamer
|
|
3
|
-
description: always load first in a dreamteamer workspace — reading, writing and committing records, and changing what the workspace keeps or does (collections and fields, skills, commands, agents, ui-views, component code). Also when deciding which of those a request should become, when a compile or check error names a source file, or when a request names a new kind of thing to keep.
|
|
3
|
+
description: always load first in a dreamteamer workspace — reading, writing and committing records, and changing what the workspace keeps or does (collections and fields, skills, commands, agents, ui-views, component code). Also when deciding which of those a request should become, when a compile or check error names a source file, or when a request names a new kind of thing to keep. Also for a brand-new or just-installed workspace, or dreamteamer over existing data.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# using dreamteamer
|
|
@@ -39,7 +39,8 @@ unsure which skill owns the job in front of you.
|
|
|
39
39
|
and the harness folders are gitignored build output, so a clone has no runtime until compile
|
|
40
40
|
writes one — and `.env` is per-machine (declared keys: `references/records.md`). `dt status` says
|
|
41
41
|
whether the runtime is fresh. the workspace's own switches live in `package.json`'s `dreamteamer`
|
|
42
|
-
block (`references/collections.md`, the workspace manifest)
|
|
42
|
+
block (`references/collections.md`, the workspace manifest) — and the guided path from nothing,
|
|
43
|
+
or from existing data, is `references/getting-started.md`.
|
|
43
44
|
|
|
44
45
|
## the CLI is the front door
|
|
45
46
|
|
|
@@ -47,8 +48,21 @@ block (`references/collections.md`, the workspace manifest).
|
|
|
47
48
|
workspace may alias it as an npm script — check `scripts` in `package.json` (the common spelling
|
|
48
49
|
is `npm run --silent dt -- <verb> …`).
|
|
49
50
|
|
|
51
|
+
**default to the CLI for every record read and write.** it validates before disk, generates the
|
|
52
|
+
id, materializes defaults, and its writes are pathspec-scoped; touch a record file by hand only
|
|
53
|
+
when the CLI cannot express the change (a long body, a nested map) — and then you owe `dt check`
|
|
54
|
+
(`references/records.md`).
|
|
55
|
+
|
|
50
56
|
**`dt help` is the complete command surface** — record verbs, schema verbs, workspace verbs, and
|
|
51
|
-
their flags, on one page (there is no per-verb `--help`).
|
|
57
|
+
their flags, on one page (there is no per-verb `--help`).
|
|
58
|
+
|
|
59
|
+
the verb names, as a map (semantics and flags live in `help`; a test holds this list to the
|
|
60
|
+
dispatch, so it cannot drift):
|
|
61
|
+
|
|
62
|
+
- read & measure — `list` `get` `values` `history` `diff` `commands` `relations` `resolve`
|
|
63
|
+
- write & publish — `add` `set` `rm` `rename` `move` `revert` `commit` `ensure`
|
|
64
|
+
- schema (sources, through the compile gate) — `schema` `add-collection` `rm-collection` `rename-collection` `add-field` `update-field` `remove-field` `add-view` `set-view` `rm-view`
|
|
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
|
|
52
66
|
the same file as the dispatch it documents. run it once before your first write of a session.
|
|
53
67
|
what prose adds is judgment — *when* a verb is the right move, and the guarantees you can lean
|
|
54
68
|
on: **validation is hard** (unknown fields included; an invalid write is rejected before disk
|
|
@@ -63,6 +77,7 @@ Load by the map; nothing here is loaded "just in case".
|
|
|
63
77
|
|
|
64
78
|
| the job | load |
|
|
65
79
|
|---|---|
|
|
80
|
+
| a brand-new or empty workspace, dreamteamer over an existing pile of files, "help me set this up" | `references/getting-started.md` |
|
|
66
81
|
| read, create, update, rename, delete, commit — or UNDO — a record | `references/records.md` |
|
|
67
82
|
| "what changed while I was away" | `references/changes.md` |
|
|
68
83
|
| 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) |
|
|
@@ -98,8 +113,9 @@ workspace's decision log (where one exists) wins over older documents.
|
|
|
98
113
|
2. **the filename is the id.** where a record also carries a frontmatter `name` (agents,
|
|
99
114
|
commands), the two must agree, or the id lies and dispatch misses.
|
|
100
115
|
3. **the meta-descriptor is the spec.** every source kind is itself a collection — read
|
|
101
|
-
`.dreamteamer/collections/<kind>.collection.yaml
|
|
102
|
-
|
|
116
|
+
`.dreamteamer/collections/<kind>.collection.yaml`; its field descriptions are the contract.
|
|
117
|
+
prose drifts, and a sample record is one arbitrary instance — when a schema underdocuments a
|
|
118
|
+
convention, fixing the schema IS the task (`references/records.md`).
|
|
103
119
|
4. **`dt compile`, then `dt check`**, after any source change. compile fails closed — a bad
|
|
104
120
|
source is rejected and the previous runtime stands; check reports and never modifies.
|
|
105
121
|
5. **a running session does not see new sources.** a new skill, command or agent is live in the
|
|
@@ -71,8 +71,19 @@ unpublished schema is not a state the workspace should sit in. The verbs and eve
|
|
|
71
71
|
or **edit the owning module's descriptor by hand** and compile (the change ships with the
|
|
72
72
|
module) — the only exit for a removal. Pick by who should own the field — `data-modeling.md`
|
|
73
73
|
Part III.
|
|
74
|
+
- **`--type <collection>` beats the type sugar, always.** A type that names a collection in the
|
|
75
|
+
runtime is a reference to it, whatever `string`/`enum`/`date`/`tags`/… would otherwise mean — so
|
|
76
|
+
in a workspace that ships a `tags` collection, `--type tags` points at it and the relation flags
|
|
77
|
+
work on it. Only a stated `--type` resolves this way; omitting it still means a plain string.
|
|
74
78
|
- `remove-field` on a populated field **clears the values in the same write and reports the
|
|
75
|
-
count** — a leftover key would make every later write to those records fail as unknown.
|
|
79
|
+
count** — a leftover key would make every later write to those records fail as unknown. It also
|
|
80
|
+
prunes the field out of **the same descriptor's `list_fields` and `sort_field`** (that is the
|
|
81
|
+
field's own presentation, and a dangling `sort_field` is a compile error), and **warns, by id,**
|
|
82
|
+
about any ui-view whose `options.columns` still names it — a different source, so it is named
|
|
83
|
+
rather than edited.
|
|
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
|
|
86
|
+
reorders — an existing field keeps the place its author gave it.
|
|
76
87
|
- **`schema rename-collection <old> <new>`** moves the descriptor **in the module that ships it**
|
|
77
88
|
(its guard is against writes an `npm install` would erase, not against modules), plus the
|
|
78
89
|
records, the filenames and every inbound reference — `x-reference` targets in other descriptors
|
|
@@ -82,8 +93,8 @@ unpublished schema is not a state the workspace should sit in. The verbs and eve
|
|
|
82
93
|
does NOT overrule two authored choices: a hand-set `storage.path` (records stay put, and it
|
|
83
94
|
says so) and a `storage.suffix` that is not the singular of the old name.
|
|
84
95
|
- An **empty value removes** in dotted `set-view` writes just as it does in `dt set` — so a
|
|
85
|
-
setting whose meaningful value IS empty (`options.sort: ''`, see `ui-views.md`)
|
|
86
|
-
|
|
96
|
+
setting whose meaningful value IS empty (`options.sort: ''`, see `ui-views.md`) is written
|
|
97
|
+
QUOTED, which is what says "the empty string is the value": `'options.sort=""'`.
|
|
87
98
|
|
|
88
99
|
## declaring a module
|
|
89
100
|
|
|
@@ -420,6 +420,9 @@ descriptions are what an agent reads before writing a value. So:
|
|
|
420
420
|
- A field's `description` says what the value MEANS, names the source when the value is copied
|
|
421
421
|
from elsewhere, and states the convention an agent must follow ("empty means unmatched — the
|
|
422
422
|
matching command's queue").
|
|
423
|
+
- A field whose valid value has a non-obvious SHAPE carries an `examples:` annotation — standard
|
|
424
|
+
JSON Schema, passed through to the compiled descriptor — so the canonical value lives in the
|
|
425
|
+
contract, not in whichever record a writer happens to open.
|
|
423
426
|
- `use_when` is authored **only** when an agent that fully understood the description would still
|
|
424
427
|
not reach for the collection — a search-here-first trigger, a write-here-when situation. It is
|
|
425
428
|
prose; nothing fires on it; and a `use_when` restating the description costs every session
|
|
@@ -814,8 +817,11 @@ useless until the flood is drained.
|
|
|
814
817
|
message being the ledger); `schema remove-field` the old one — which clears any leftovers and
|
|
815
818
|
reports the count.
|
|
816
819
|
- **Values** (an id, a reference target): `dt rename <collection>/<old> <new>` rewrites inbound
|
|
817
|
-
references
|
|
818
|
-
|
|
820
|
+
references. Prose wikilinks are followed in both spellings — `[[collection/id]]` always, and a
|
|
821
|
+
bare `[[id]]` when that basename names exactly ONE record in the workspace; when something else
|
|
822
|
+
claims it, the link is left alone and the rename says so, naming the file. A `#anchor` and a
|
|
823
|
+
`|label` both ride through untouched (`[[id#heading|see here]]`) — only the record moved. Raw
|
|
824
|
+
prose that is not a wikilink is counted and reported, never rewritten.
|
|
819
825
|
|
|
820
826
|
### 42. Migrations are scripts, run once, committed with their effects
|
|
821
827
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# getting started — from nothing, or from a pile, to a working workspace
|
|
2
|
+
|
|
3
|
+
Three arrivals land here, and they end in the same place. **The empty folder**: someone ran
|
|
4
|
+
`npm install dreamteamer` (or is about to) and has nothing else. **The existing pile**: a repo or
|
|
5
|
+
folder that already holds real files — notes, exports, spreadsheets — that should become records.
|
|
6
|
+
**The operator who is technical enough to run npm and not much more** — for whom the git section
|
|
7
|
+
below is written in plain words. The finish line for all three is identical: a compiled runtime,
|
|
8
|
+
ONE honest collection with real records in it, published — not an architecture.
|
|
9
|
+
|
|
10
|
+
No UI is assumed anywhere below. The CLI and the records are complete on their own; any surface
|
|
11
|
+
that reads the compiled runtime can render the same workspace later, unchanged.
|
|
12
|
+
|
|
13
|
+
| the situation | read |
|
|
14
|
+
|---|---|
|
|
15
|
+
| nothing exists yet | the skeleton, then path A |
|
|
16
|
+
| real files already exist | the skeleton, then path B |
|
|
17
|
+
| dreamteamer joins a repo that also holds code | the skeleton, then path C |
|
|
18
|
+
| "do I need git? a server? an account?" | git, in plain words |
|
|
19
|
+
|
|
20
|
+
## the skeleton — five commands, one minute
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm init -y # only if there is no package.json yet
|
|
24
|
+
npm install dreamteamer
|
|
25
|
+
git init # strongly recommended, not required to start — see below. first,
|
|
26
|
+
# so init can commit its own skeleton (it does, when git is there)
|
|
27
|
+
npx dreamteamer init # writes the workspace skeleton; never compiles
|
|
28
|
+
npx dreamteamer compile # materializes the runtime; writes the orientation block
|
|
29
|
+
npx dreamteamer check # should report 0 violations over the starter collection
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`init` writes the `dreamteamer` block into `package.json` (the workspace manifest —
|
|
33
|
+
`collections.md`), a workspace module at `modules/default/` with a starter `notes` collection,
|
|
34
|
+
and `.env.example`. `compile` is what makes it real: the runtime under `.dreamteamer/`, the
|
|
35
|
+
harness folders, and the orientation block every future agent session reads — which is how the
|
|
36
|
+
next session finds this skill without being told.
|
|
37
|
+
|
|
38
|
+
## git, in plain words
|
|
39
|
+
|
|
40
|
+
git is three things to this workspace, and none of them needs a server or an account:
|
|
41
|
+
|
|
42
|
+
- **the history** — every change to every record, forever, answerable (`dt history`, `dt changes`);
|
|
43
|
+
- **the undo** — any record restorable to any prior state (`records.md`, undoing);
|
|
44
|
+
- **the publish step** — `dt commit` is what turns "written to disk" into "on the record".
|
|
45
|
+
|
|
46
|
+
Verified capability split: **without git, everything still reads, writes, validates and compiles**
|
|
47
|
+
— only `dt commit`, `dt changes` and `dt history`/`diff`/`revert` are unavailable. `git init` is
|
|
48
|
+
one command, local, free, and turns all of them on. A **remote** (GitHub or anywhere) is a
|
|
49
|
+
separate, optional, later decision — it buys backup and a second machine, and nothing here needs
|
|
50
|
+
it. If the operator is unsure: run `git init`, skip the remote, move on.
|
|
51
|
+
|
|
52
|
+
## path A — nothing yet: model from an interview
|
|
53
|
+
|
|
54
|
+
The first hour's goal is **one collection the operator will actually touch tomorrow**, holding
|
|
55
|
+
real records. Not five collections, not a namespace plan, not a module layout.
|
|
56
|
+
|
|
57
|
+
1. **Find the daily thing.** Ask what they keep re-finding, re-asking or losing — the answer names
|
|
58
|
+
the first collection. When the requirement is vague, `data-modeling.md` Part II is the
|
|
59
|
+
interview: twelve questions, most answered by the requirement itself.
|
|
60
|
+
2. **Propose small, then stop.** The proposal contract (`data-modeling.md` §7): the descriptor
|
|
61
|
+
YAML, one sample record as its `dt add` line, and what is deliberately NOT modelled. The
|
|
62
|
+
operator says yes before anything lands.
|
|
63
|
+
3. **Create it in the workspace module** — `dt schema add-collection` (it compiles and publishes
|
|
64
|
+
itself). The workspace module is the right first home for everything (`data-modeling.md` §8);
|
|
65
|
+
extraction is a decision for the second consumer, months away.
|
|
66
|
+
4. **Seed three to five REAL records immediately** with `dt add` — not test data. Seeding real
|
|
67
|
+
records before declaring the model finished catches half the field mistakes: the missing unit,
|
|
68
|
+
the enum the domain spells differently, the id that comes out wrong.
|
|
69
|
+
5. **Iterate on evidence**: `dt values` shows what the fields actually hold, `dt check` what
|
|
70
|
+
disagrees. Adjust, then — only then — consider the second collection.
|
|
71
|
+
|
|
72
|
+
Deliberately deferred, each until its trigger: a **namespace** (first second-DOMAIN collection,
|
|
73
|
+
not before — `collections.md`) · a **module of its own** (a second consumer or a closed reference
|
|
74
|
+
graph — `data-modeling.md` §8) · **skills, commands, views** (after records exist — behaviour
|
|
75
|
+
follows shape, and a view needs a recurring question to encode).
|
|
76
|
+
|
|
77
|
+
## path B — dreamteamer over an existing pile
|
|
78
|
+
|
|
79
|
+
The pile is evidence; read it before modeling anything.
|
|
80
|
+
|
|
81
|
+
1. **Survey**: what file kinds, how many, what dates and names they carry, and — most useful —
|
|
82
|
+
what questions the operator actually asks of this pile. Read a handful of files end to end.
|
|
83
|
+
2. **Split the pile in two.** Files that already look like records — one thing per file, with
|
|
84
|
+
discoverable fields — get **registered where they stand**: `collections.md`, "registering an
|
|
85
|
+
existing data folder" (derive the descriptor from reality; an authored `storage.path` points at
|
|
86
|
+
the existing folder; **never rewrite records to fit an inferred schema**). Tabular exports
|
|
87
|
+
(CSVs, spreadsheets) get **imported**: `records.md`, the bulk-write recipe — one script, one
|
|
88
|
+
check, one collection-scoped commit.
|
|
89
|
+
3. **Big and binary files never become records.** They stay where they are (or move to a files
|
|
90
|
+
folder), and a record points at each via a `${env:VAR}` template — the filing convention and
|
|
91
|
+
the mechanics are in `records.md`.
|
|
92
|
+
4. **Restructure later, with the tools that keep references true** — `dt rename`,
|
|
93
|
+
`dt schema rename-collection` — not during the first pass. Model reality first; `check` will
|
|
94
|
+
tell you what reality violates, and that list, not taste, drives the cleanup.
|
|
95
|
+
|
|
96
|
+
## path C — into a repo that already holds code
|
|
97
|
+
|
|
98
|
+
The workspace root is wherever `package.json` carries the `dreamteamer` block — a repo can be
|
|
99
|
+
both a codebase and a workspace. `init`'s runtime folders are gitignored, records default to
|
|
100
|
+
`data/` beside the code, and an authored `storage.path` can put any collection anywhere the repo
|
|
101
|
+
prefers. Nothing about the code moves; `check` and `compile` read only what descriptors name.
|
|
102
|
+
|
|
103
|
+
## the first hour's mistakes
|
|
104
|
+
|
|
105
|
+
| mistake | reality |
|
|
106
|
+
|---|---|
|
|
107
|
+
| modeling the whole domain up front | one collection used tomorrow beats five perfect ones — widening is always safe later (`data-modeling.md` Part IX) |
|
|
108
|
+
| test data in the seed records | real records are what catch the model's mistakes |
|
|
109
|
+
| skipping git as "too technical" | one local command, no account — and it is the history, the undo and the publish step |
|
|
110
|
+
| inventing ids by hand | `id.generate` owns identity — pass `--id` only when the operator named one |
|
|
111
|
+
| hand-writing the first descriptor | `dt schema add-collection` is compile-gated and publishes itself; hand-written sources owe `dt compile` |
|
|
112
|
+
| rewriting existing files to fit a guessed schema | describe reality, compile, `check` — then decide which violations are worth fixing in the data |
|
|
113
|
+
| waiting for a UI before starting | the CLI and the records are the complete system; any surface renders them later, unchanged |
|
|
@@ -18,13 +18,13 @@ commands, ui-views, collections) are *sources*: edit the file under the owning m
|
|
|
18
18
|
|
|
19
19
|
the verbs and flags are `dt help`'s job; what to know *about* them:
|
|
20
20
|
|
|
21
|
-
- narrowing a `list`: `--filter k=v`
|
|
22
|
-
`--
|
|
21
|
+
- narrowing a `list`: `--filter k=v` per condition, **repeated to AND more of them**
|
|
22
|
+
(`--filter status=todo --filter owner=ana` wants both); **anything an equality cannot say goes
|
|
23
|
+
in one `--where`** — its operator grammar is enumerated in `dt help`, and it is
|
|
23
24
|
the same one views and gates use — e.g.
|
|
24
25
|
`dt list health/prescriptions --where '{"_and":[{"patient":{"_eq":"health/patients/dana-levi"}},{"status":{"_eq":"active"}}]}'`.
|
|
25
|
-
⚠
|
|
26
|
-
|
|
27
|
-
code.
|
|
26
|
+
⚠ an unknown field or a dangling ref **narrows to nothing** rather than erroring — filter field
|
|
27
|
+
names deserve the same care as code.
|
|
28
28
|
- ⚠ **there is no `@me` and no `users` collection** (both removed in 0.8.0). when a person is
|
|
29
29
|
needed, read `git config user.name`; filter on a person only when this workspace ships its own
|
|
30
30
|
collection of people.
|
|
@@ -42,8 +42,12 @@ the verbs and flags are `dt help`'s job; what to know *about* them:
|
|
|
42
42
|
nothing written. a rejected write leaves no partial state.
|
|
43
43
|
- **a write puts the record on disk; `dt commit` publishes it** — committing is workspace policy
|
|
44
44
|
(`auto-commit` in `package.json`, default off), never part of the write.
|
|
45
|
-
- `set <collection>/<id> <field>=` with an empty value **removes** the field;
|
|
46
|
-
|
|
45
|
+
- `set <collection>/<id> <field>=` with an empty value **removes** the field; the `x-body` field is
|
|
46
|
+
set like any other field.
|
|
47
|
+
- an **array field** takes a comma-separated value (`--tags a,b`, `tags=a,b`) — or the flag/pair
|
|
48
|
+
**repeated**, one element per sighting (`--tags a --tags b`), which is how a value that itself
|
|
49
|
+
contains a comma gets written. ⚠ repeating a **scalar** field is refused, naming it: it used to
|
|
50
|
+
keep the last value silently, so the first one never reached disk.
|
|
47
51
|
- ids generate from the record's own creation-time values — pass `--id` only when the operator
|
|
48
52
|
named one.
|
|
49
53
|
|
|
@@ -54,14 +58,27 @@ read the compiled descriptor: `.dreamteamer/collections/<collection>.collection.
|
|
|
54
58
|
Schema; the `x-` keywords carry the domain semantics — `x-reference`, `x-body`, `x-inverse`). it
|
|
55
59
|
also carries `title` (what to call the collection) and `title_template` (how to label one record).
|
|
56
60
|
|
|
61
|
+
the schema is the CONTRACT, and it is required to be sufficient: each field's `description`
|
|
62
|
+
carries its conventions, an `examples:` annotation (standard JSON Schema — compile passes it
|
|
63
|
+
through to the compiled descriptor) carries a canonical value where the shape is non-obvious, and
|
|
64
|
+
`dt values` shows a vocabulary's real spread. so **with a sufficient schema, do not open sibling
|
|
65
|
+
records for shape** — the descriptor answers faster than a peek, the validator rejects a wrong
|
|
66
|
+
write before disk, and a sibling is a bet on whichever record you grabbed being representative
|
|
67
|
+
(measured blind: the peek route bought nothing the descriptor already said, and it only works
|
|
68
|
+
while consistent siblings happen to exist). a schema that makes you peek to write correctly is a
|
|
69
|
+
defect in the schema — fix it.
|
|
70
|
+
|
|
57
71
|
## writing a record by hand
|
|
58
72
|
|
|
59
73
|
**default to `dt add <collection>`** — id, defaults and validation in one line. hand-write only
|
|
60
74
|
when the CLI can't express the value: a nested map, or a long structured body.
|
|
61
75
|
|
|
62
|
-
when you do,
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
when you do, the descriptor is still the contract — required, defaults, each field's
|
|
77
|
+
`description` and `examples:`, `dt values` for vocabularies. peek at a sibling
|
|
78
|
+
(`dt get <collection>/<id> --json`) only when the schema underdocuments a convention you need — a
|
|
79
|
+
sibling is ONE arbitrary instance, possibly written before the schema last moved — and the miss
|
|
80
|
+
itself is the finding: put the convention into the field's `description` (or an `examples:`) in
|
|
81
|
+
the same breath, so the next writer needs no peek. then:
|
|
65
82
|
|
|
66
83
|
- put the file where the id says: the id IS its path inside `storage.path`, minus suffix and
|
|
67
84
|
extension (folder-shape records are a folder named `<id>` holding the descriptor's `entry`).
|
|
@@ -192,7 +209,8 @@ two hundred.
|
|
|
192
209
|
|---|---|
|
|
193
210
|
| `mv data/tasks/old.task.md …/new.task.md` | every inbound ref now dangles. `dt rename`. |
|
|
194
211
|
| renaming a record because its title changed | the id is not a display name — edit the field |
|
|
195
|
-
|
|
|
212
|
+
| a `--filter` per condition | right — they AND; only what equality cannot express needs `--where` |
|
|
213
|
+
| `--tags a --tags b` on a scalar field | refused, naming the field — a repeat is an array ELEMENT, and a scalar has no room for two |
|
|
196
214
|
| `--force` to get past an `rm` refusal | it leaves inbound refs dangling — retarget them first (unless the refusal named a *prose* mention, which isn't a real reference) |
|
|
197
215
|
| omitting schema defaults from a hand-written file | the file stops being legible without the schema |
|
|
198
216
|
| unquoted `due: 2026-07-28` in hand-written YAML | dreamteamer parses CORE_SCHEMA so it stays a string *here*, but a default-schema YAML reader turns it into a timestamp — quote dates |
|
|
@@ -121,9 +121,16 @@ same record. A misspelled key is read by nobody, silently. The two edges that bi
|
|
|
121
121
|
naming a field the schema lacks is **dropped, not fallen back from** — which is how a core inbox
|
|
122
122
|
view asking for `title` on a collection whose field is `name` rendered every row nameless with no
|
|
123
123
|
error. Check the descriptor's real field names against every column you write.
|
|
124
|
+
- **A list-valued option takes the comma spelling**: `set-view <id> options.columns=name,status`
|
|
125
|
+
writes a real list, as does the JSON form `'["name","status"]'`. The keys that split are
|
|
126
|
+
`columns`, `ref_fields` and `value_fields` — `options` has no schema, so a key not on that list
|
|
127
|
+
keeps its commas as characters (`options.template=a, b` is one string), and a list of OBJECTS
|
|
128
|
+
(`options.arrangement`) has only the JSON spelling.
|
|
124
129
|
- ⚠ **`options.sort` must be written even when empty** (`sort: ''`), or "unsorted" cannot
|
|
125
|
-
round-trip and silently reverts to a fallback ordering on the next load. The
|
|
126
|
-
|
|
130
|
+
round-trip and silently reverts to a fallback ordering on the next load. The spelling is a
|
|
131
|
+
QUOTED value — `dt schema set-view <id> 'options.sort=""'` — because a bare `options.sort=`
|
|
132
|
+
removes the key, as an empty value does everywhere else. Quoting any value makes it a literal
|
|
133
|
+
string (`options.sort='"-date"'` is the same as `options.sort=-date`).
|
|
127
134
|
|
|
128
135
|
Views are live records: a changed `filter` or `options` reaches an open tab on the next compile;
|
|
129
136
|
an unchanged view re-renders nothing.
|
|
@@ -152,7 +159,7 @@ kind with full CLI write support, because it goes through the same compile gate.
|
|
|
152
159
|
`add-view` writes the **workspace module** — right for an operator's own daily surface; a view
|
|
153
160
|
that is part of a module's canonical shape belongs in that module's `ui-views/`, hand-written.
|
|
154
161
|
And a dotted `key=` with an **empty value removes the key**, so the one setting whose meaningful
|
|
155
|
-
value IS empty — `sort: ''` —
|
|
162
|
+
value IS empty — `sort: ''` — is written QUOTED: `'options.sort=""'` (see options).
|
|
156
163
|
|
|
157
164
|
## common mistakes
|
|
158
165
|
|
|
@@ -164,7 +171,7 @@ value IS empty — `sort: ''` — must be hand-written in the source file (see o
|
|
|
164
171
|
| a filter using `@me` | gone in 0.8.0 with `users` — compile refuses it by name |
|
|
165
172
|
| `filter` written inside `options` | accepted, saved, read by nobody — compile warns; move it up |
|
|
166
173
|
| a column the schema does not have | dropped silently — the row loses that value with no error |
|
|
167
|
-
| omitting `sort` to mean unsorted | write `sort: ''` or the ordering reverts on reload |
|
|
174
|
+
| omitting `sort` to mean unsorted | write `sort: ''` (`'options.sort=""'`) or the ordering reverts on reload |
|
|
168
175
|
| a ui-view that restates the built-in fallback | a record to maintain for zero gain |
|
|
169
176
|
| a `default: true` view with its own `path`/`nav` | the default IS the collection page — it never routes itself |
|
|
170
177
|
| a module ui-view naming one person | a hard-coded id resolves in no other workspace |
|
package/src/check.js
CHANGED
|
@@ -8,7 +8,7 @@ import addFormats from 'ajv-formats';
|
|
|
8
8
|
import { parseRecord, patternRe, fmtAjvError, unknownFields, walk, idFromRecordPath, MAX_RECORD_BYTES } from './records.js';
|
|
9
9
|
import { NO_RUNTIME, loadDescriptors, runtimeDir, namespaces as compiledNamespaces } from './runtime.js';
|
|
10
10
|
import { parseRef } from './namespace.js';
|
|
11
|
-
import { refTargetsOf } from './ref.js';
|
|
11
|
+
import { refTargetsOf, refIsSoft } from './ref.js';
|
|
12
12
|
import { relationsOf, expectedMirrors } from './relations.js';
|
|
13
13
|
|
|
14
14
|
export function check({ root }) {
|
|
@@ -122,9 +122,9 @@ export function check({ root }) {
|
|
|
122
122
|
for (const k of unknownFields(d.schema, fields)) {
|
|
123
123
|
flag(file, `unknown field "${k}" (not in the ${name} schema)`);
|
|
124
124
|
}
|
|
125
|
-
for (const [fieldPath, target] of refFields) {
|
|
125
|
+
for (const [fieldPath, target, soft] of refFields) {
|
|
126
126
|
for (const value of valuesAt(fields, fieldPath)) {
|
|
127
|
-
checkRef(file, fieldPath, value, target, softTargets);
|
|
127
|
+
checkRef(file, fieldPath, value, target, softTargets, soft);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
parsed.get(name).set(id, fields);
|
|
@@ -168,7 +168,7 @@ export function check({ root }) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
function checkRef(file, fieldPath, value, targets, softTargets) {
|
|
171
|
+
function checkRef(file, fieldPath, value, targets, softTargets, soft = false) {
|
|
172
172
|
if (typeof value !== 'string') return;
|
|
173
173
|
if (value.startsWith('@')) return; // runtime tokens (@me, @initiator) are legal
|
|
174
174
|
// The SAME parser the store writes through (src/namespace.js) — `check` disagreeing with the
|
|
@@ -189,9 +189,14 @@ export function check({ root }) {
|
|
|
189
189
|
softRefs.set(coll, (softRefs.get(coll) ?? 0) + 1);
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
|
+
// A SOFT field (`x-reference-soft`) is a declaration, not a resolved link — see ref.js.
|
|
193
|
+
if (soft) return;
|
|
192
194
|
return flag(file, `${fieldPath.join('.')}: reference "${value}" targets unknown collection "${coll}"`);
|
|
193
195
|
}
|
|
194
|
-
if (!index.get(coll).has(id))
|
|
196
|
+
if (!index.get(coll).has(id)) {
|
|
197
|
+
if (soft) return; // resolve if present, ignore if absent
|
|
198
|
+
return flag(file, `${fieldPath.join('.')}: dangling reference "${value}" — no such record`);
|
|
199
|
+
}
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
// ---- report ----------------------------------------------------------------------
|
|
@@ -218,16 +223,17 @@ export function check({ root }) {
|
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
|
|
221
|
-
// collect [fieldPath, targets] for every x-reference in the schema, where `targets` is '*' or
|
|
222
|
-
// normalized array of declared collections (see refTargetsOf)
|
|
223
|
-
// are decoded once, from the
|
|
226
|
+
// collect [fieldPath, targets, soft] for every x-reference in the schema, where `targets` is '*' or
|
|
227
|
+
// the normalized array of declared collections (see refTargetsOf) and `soft` says whether an absent
|
|
228
|
+
// target is a finding (see refIsSoft). Relations are NOT read here — they are decoded once, from the
|
|
229
|
+
// compiled descriptors, by src/relations.js.
|
|
224
230
|
function collectRefFields(schema, prefix = []) {
|
|
225
231
|
const out = [];
|
|
226
232
|
for (const [key, s] of Object.entries(schema.properties ?? {})) {
|
|
227
233
|
if (!s || typeof s !== 'object') continue;
|
|
228
234
|
const p = [...prefix, key];
|
|
229
235
|
const targets = refTargetsOf(s);
|
|
230
|
-
if (targets) out.push([p, targets]);
|
|
236
|
+
if (targets) out.push([p, targets, refIsSoft(s)]);
|
|
231
237
|
if (s.properties) out.push(...collectRefFields(s, p));
|
|
232
238
|
if (s.items?.properties) out.push(...collectRefFields(s.items, p));
|
|
233
239
|
}
|
package/src/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ import { deriveEvents } from './events.js';
|
|
|
21
21
|
import { commitPending } from './commit.js';
|
|
22
22
|
import { Store } from './store.js';
|
|
23
23
|
import { splitRef } from './ref.js';
|
|
24
|
-
import {
|
|
24
|
+
import { envContext, renderTemplate } from './env-vars.js';
|
|
25
25
|
|
|
26
26
|
// git calls whose failure we CATCH must not print git's own error: execFileSync forwards the
|
|
27
27
|
// child's stderr to ours unless told otherwise, so a handled "not a git repository" still
|
|
@@ -35,7 +35,8 @@ record verbs (hard validation — invalid writes are rejected before disk).
|
|
|
35
35
|
A <target> is either a collection name or a <collection>/<id> reference; the reference splits at
|
|
36
36
|
the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee is ONE argument:
|
|
37
37
|
list <collection> [--filter k=v] [--where <json>] [--sort [-]<field>] [--json]
|
|
38
|
-
(--filter is ONE condition —
|
|
38
|
+
(--filter is ONE condition — repeat it to AND more
|
|
39
|
+
(--filter a=1 --filter b=2 wants both);
|
|
39
40
|
anything compound goes in one --where, operator
|
|
40
41
|
objects e.g. '{"starts":{"_gte":"2026-07-01"}}' —
|
|
41
42
|
operators: _eq _neq _lt _lte _gt _gte _in _nin
|
|
@@ -47,8 +48,12 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
|
|
|
47
48
|
add <collection> --<field> <value> … [--id <explicit-id>]
|
|
48
49
|
(a codec-file collection takes --from <path>
|
|
49
50
|
instead — the file IS the record, fields derive;
|
|
50
|
-
--force replaces an existing file record
|
|
51
|
-
|
|
51
|
+
--force replaces an existing file record.
|
|
52
|
+
A repeated --<field> is one ELEMENT of an array
|
|
53
|
+
field — refused on a scalar one; a single value
|
|
54
|
+
still splits on commas)
|
|
55
|
+
set <collection>/<id> <field>=<value> … (repeating a pair adds an element, exactly as a
|
|
56
|
+
repeated --<field> does)
|
|
52
57
|
rm <collection>/<id> [--force]
|
|
53
58
|
rename <collection>/<id> <new-id> (rewrites all inbound refs in one WRITE —
|
|
54
59
|
commit publishes the set together)
|
|
@@ -109,7 +114,10 @@ different word in front of it):
|
|
|
109
114
|
schema remove-field <collection> --name <field>
|
|
110
115
|
schema add-view --path </route> --target list --collection collections/<c> --layout <id>
|
|
111
116
|
[--id <id>] [k.v=…]
|
|
112
|
-
schema set-view <id> <key>=<value> … (dotted keys: options.sort=-date, nav.label=Recent
|
|
117
|
+
schema set-view <id> <key>=<value> … (dotted keys: options.sort=-date, nav.label=Recent.
|
|
118
|
+
A list option takes commas — options.columns=name,status — or JSON.
|
|
119
|
+
An empty value REMOVES the key; quote it to write the empty string
|
|
120
|
+
itself: 'options.sort=""' is the "unsorted" the surface needs.)
|
|
113
121
|
schema rm-view <id>
|
|
114
122
|
|
|
115
123
|
workspace verbs:
|
|
@@ -315,7 +323,9 @@ export function run(argv) {
|
|
|
315
323
|
if (repos.length) {
|
|
316
324
|
const here = repos.filter((r) => r.present).length;
|
|
317
325
|
console.log(`repos: ${here}/${repos.length} materialized`);
|
|
318
|
-
|
|
326
|
+
// an UNRESOLVED path is not the same absence as a repo simply not cloned yet, and
|
|
327
|
+
// `dreamteamer ensure` is not the fix for it — say which one this is.
|
|
328
|
+
for (const r of repos) if (!r.present) console.log(` absent: ${r.id} → ${r.path}${r.unresolved ? ` — ${r.unresolved}` : ` (dreamteamer ensure ${r.id})`}`);
|
|
319
329
|
}
|
|
320
330
|
} catch { /* no repos descriptor compiled — nothing to report */ }
|
|
321
331
|
// Uncommitted records are invisible to `dt changes` (it diffs commits), so the
|
|
@@ -395,8 +405,11 @@ function dispatchRecordVerb(ws, verb, args) {
|
|
|
395
405
|
// `commands for <c>/<id>` split its own target at the FIRST slash, which cannot name a
|
|
396
406
|
// namespaced collection. splitRef can, so the id is handed over as `--ids` — the same
|
|
397
407
|
// `commandsFor(store, collection, ids)` call, reached without re-encoding the reference.
|
|
398
|
-
//
|
|
399
|
-
|
|
408
|
+
// An explicit `--ids` from the caller still wins — by NOT injecting ours, not by ordering. It
|
|
409
|
+
// used to rely on "last flag parsed wins", which stopped being true when a repeated flag started
|
|
410
|
+
// promoting to an array instead of overwriting: the pair would now be refused as a double.
|
|
411
|
+
const ours = rest.some((a) => a === '--ids' || a.startsWith('--ids=')) ? [] : ['--ids', id];
|
|
412
|
+
return collectionCommand(ws, 'commands', 'for', [collection, ...ours, ...rest]);
|
|
400
413
|
}
|
|
401
414
|
|
|
402
415
|
/**
|
|
@@ -419,10 +432,7 @@ function resolveVariables(ws, args) {
|
|
|
419
432
|
// resolve has no flags, so a flag-shaped target is a mistake — and the one that costs is
|
|
420
433
|
// `dt resolve --help`, which would otherwise print `--help` back and exit 0.
|
|
421
434
|
if (target.startsWith('--')) throw new Error(`dt resolve takes a string or a <collection>/<id>, not a flag ("${target}") — see \`dreamteamer help\``);
|
|
422
|
-
const
|
|
423
|
-
const envFile = path.join(ws.root, '.env');
|
|
424
|
-
const env = parseEnvValues(fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '');
|
|
425
|
-
const ctx = { env, workspaceFolder: ws.root, declared };
|
|
435
|
+
const ctx = envContext(ws);
|
|
426
436
|
|
|
427
437
|
let ref = null;
|
|
428
438
|
let store = null;
|