dreamteamer 0.6.4 → 0.7.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/README.md +123 -51
- package/package.json +62 -58
- package/skills/building-dreamteamer/references/collections.md +32 -2
- package/skills/using-dreamteamer/references/records.md +17 -0
- package/src/check.js +12 -6
- package/src/cli.js +7 -1
- package/src/collections-cli.js +28 -2
- package/src/compile.js +61 -3
- package/src/harnesses.js +29 -8
- package/src/namespace.js +181 -0
- package/src/runtime.js +29 -3
- package/src/schema-ops.js +260 -11
- package/src/server.js +9 -0
- package/src/store.js +56 -20
package/README.md
CHANGED
|
@@ -1,82 +1,154 @@
|
|
|
1
1
|
# dreamteamer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
**Structured, modular memory for coding agents.**
|
|
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.
|
|
9
|
+
|
|
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.
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
npm i dreamteamer
|
|
14
|
+
npm i dreamteamer
|
|
11
15
|
npx dreamteamer init # scaffold a workspace
|
|
12
16
|
npx dreamteamer compile # sources → .dreamteamer (+ harness adapters)
|
|
13
|
-
npx dreamteamer check #
|
|
17
|
+
npx dreamteamer check # prove every record and every link is intact
|
|
14
18
|
npx dreamteamer help # the full command surface
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
which loads the engine **your workspace pins**, so the editor, the CLI and any agent session are
|
|
19
|
-
provably running the same code.
|
|
21
|
+
Apache-2.0. No server, no account, no telemetry.
|
|
20
22
|
|
|
21
|
-
##
|
|
23
|
+
## Structured
|
|
22
24
|
|
|
23
|
-
A
|
|
24
|
-
collection templates. Modules are discovered over three channels, in precedence order: inline
|
|
25
|
-
`modules/*`, then `git_modules/*`, then npm dependencies. Sources live **flat at a module root** —
|
|
26
|
-
`modules/crm/skills/`, beside `package.json` — and an unknown folder at a module root is a compile
|
|
27
|
-
error rather than a silent skip.
|
|
25
|
+
A record is a file. That's the whole trick.
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
```
|
|
28
|
+
data/meetings/2026/07/kickoff.meeting.md
|
|
29
|
+
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
```yaml
|
|
32
|
+
---
|
|
33
|
+
title: Kickoff
|
|
34
|
+
date: 2026-07-14
|
|
35
|
+
attendees: [contacts/ada, contacts/lin]
|
|
36
|
+
project: projects/apollo
|
|
37
|
+
---
|
|
38
|
+
Ada walked through the constraints. Lin owns the spec by Friday.
|
|
39
|
+
```
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
genuine bootstrap ordering: a fresh clone has no `.dreamteamer`, therefore no compiled schemas,
|
|
36
|
-
therefore no readable records — so module clones have to be restorable before anything can be read.
|
|
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.
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
inventing its own url/ref/identity fields. Because they are not needed at compile time, they get to
|
|
42
|
-
be data — which buys hard validation, the record CLI verbs, and history for free.
|
|
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**.
|
|
43
47
|
|
|
44
|
-
|
|
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:
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
## Modular
|
|
67
|
+
|
|
68
|
+
**Data and skills are the new app structure.** A coding agent with the right skills over the right
|
|
69
|
+
data is arbitrary functionality — but composing that with no module system is where most setups stall.
|
|
70
|
+
|
|
71
|
+
So dreamteamer doesn't invent one. **It uses npm.**
|
|
72
|
+
|
|
73
|
+
`node_modules` is battle-tested, universally adopted, and already sitting in nearly every
|
|
74
|
+
coding-agent setup. A module contributes collections, skills, agents, commands, command-bindings and
|
|
75
|
+
UI views — and skills and agents are treated as exactly what they are: **memory that loads into
|
|
76
|
+
context**, living in the same module structure as everything else, in a standard your tooling already
|
|
77
|
+
understands.
|
|
78
|
+
|
|
79
|
+
Three channels, one shape:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
modules/<name>/ # lives in this repo
|
|
83
|
+
git_modules/<name>/ # lives in its own repo
|
|
84
|
+
node_modules/<name>/ # published package
|
|
49
85
|
```
|
|
50
86
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
Precedence runs top to bottom, so a local copy shadows a published one — which is how you develop a
|
|
88
|
+
module and use it in the same workspace at the same time.
|
|
89
|
+
|
|
90
|
+
Sources live **flat at a module root** — `modules/crm/skills/`, beside `package.json` — and a folder
|
|
91
|
+
at a module root that isn't a known kind is a compile error rather than a silent skip.
|
|
92
|
+
|
|
93
|
+
### Modules are not rigid
|
|
94
|
+
|
|
95
|
+
This is the part that differs from npm on purpose.
|
|
96
|
+
|
|
97
|
+
Installing a module into a workspace that already has opinions — its own idea of what a `contact` is —
|
|
98
|
+
is a **negotiation, not an overwrite**. Four workspaces wanted a CRM and all four wanted a different
|
|
99
|
+
`contacts`. A hard import would force one answer and make every divergence a fork.
|
|
100
|
+
|
|
101
|
+
Two same-name collections is a compile error that names both descriptors and tells you the move:
|
|
102
|
+
declare `extends: <module>/<collection>` and overlay only what differs. Because every schema is one
|
|
103
|
+
small YAML file, adapting is cheap — read it, change what doesn't fit, and the diff shows exactly what
|
|
104
|
+
you agreed to.
|
|
55
105
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`<repos-path>/<name>`). A record's `path` field overrides the derivation entirely.
|
|
106
|
+
So domain modules are **recipes you copy and adapt, not packages you install**, and divergence is the
|
|
107
|
+
normal case rather than a failure.
|
|
59
108
|
|
|
60
|
-
|
|
61
|
-
identity — via `~/.gitconfig` `includeIf` rules keyed on the path, for example — but that resolution
|
|
62
|
-
happens outside the engine, which only joins it into a path.
|
|
109
|
+
## Every harness, one source
|
|
63
110
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
irrelevant to referential integrity. Presence is reported by `dreamteamer status`.
|
|
111
|
+
`compile` writes `.dreamteamer/` — the single runtime read surface — and from there into per-harness
|
|
112
|
+
adapters: Claude Code, Codex, Pi, Gemini CLI, Cursor. Author a skill once; every agent you run sees it.
|
|
67
113
|
|
|
68
|
-
##
|
|
114
|
+
## The editor
|
|
69
115
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
[dreamteamer-vscode](https://github.com/dreamteamer/dreamteamer-vscode) gives you tables, boards,
|
|
117
|
+
calendars, maps, forms and a data-model designer over the same files — and it loads **the engine your
|
|
118
|
+
workspace pins**, so the editor, the CLI and any agent session are provably running the same code.
|
|
119
|
+
|
|
120
|
+
## Docs
|
|
121
|
+
|
|
122
|
+
This is an agent-native tool, so its documentation is shipped as skills the agent loads on demand —
|
|
123
|
+
and you can read them like any other file:
|
|
124
|
+
|
|
125
|
+
- [`skills/using-dreamteamer`](skills/using-dreamteamer) — the map: collections, conventions, the CLI,
|
|
126
|
+
how records work
|
|
127
|
+
- [`skills/building-dreamteamer`](skills/building-dreamteamer) — authoring: collections, skills,
|
|
128
|
+
agents, commands, UI views, and which of those a given request should become
|
|
129
|
+
- [`docs/repos-and-modules.md`](docs/repos-and-modules.md) — attached repos vs modules, and why they
|
|
130
|
+
have different homes
|
|
131
|
+
- [`docs/namespaces-blast-radius.md`](docs/namespaces-blast-radius.md) — scoping collections under a
|
|
132
|
+
namespace (`health/doctors`), what it costs consumers, and why the default namespace is transparent
|
|
133
|
+
- [`UPDATING.md`](UPDATING.md) — what to do when upgrading, one section per release
|
|
134
|
+
|
|
135
|
+
## What it isn't
|
|
136
|
+
|
|
137
|
+
Not a database — records are files and git is the history. Not a cloud service — there is no server
|
|
138
|
+
and no account. Not a note-taking app — it's the layer underneath one.
|
|
139
|
+
|
|
140
|
+
And it is **not** for data that needs row-level access control, field-level encryption, or provable
|
|
141
|
+
erasure. Git cannot do those, and pretending otherwise is how people get hurt. This is for
|
|
142
|
+
human-scale structured knowledge: thousands of records, not millions.
|
|
74
143
|
|
|
75
144
|
## Contributing
|
|
76
145
|
|
|
77
146
|
Issues are welcome. For anything larger than a typo, please open a discussion before a pull request —
|
|
78
|
-
this is a small, deliberately lean codebase (`npm run metrics` enforces size budgets), and it's
|
|
79
|
-
|
|
147
|
+
this is a small, deliberately lean codebase (`npm run metrics` enforces size budgets), and it's better
|
|
148
|
+
to agree on the shape first.
|
|
149
|
+
|
|
150
|
+
`npm run verify` is the gate: import-layer direction, size budgets, and the test suite (tiers 1+2,
|
|
151
|
+
zero dependencies, a few seconds). See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
80
152
|
|
|
81
153
|
## License
|
|
82
154
|
|
package/package.json
CHANGED
|
@@ -1,60 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
2
|
+
"name": "dreamteamer",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "A workspace compiler for coding agents \u2014 schema-validated records as plain files over git, compiled into every harness",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Gilad Khen <giladkhen@gmail.com>",
|
|
7
|
+
"homepage": "https://github.com/dreamteamer/dreamteamer#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dreamteamer/dreamteamer.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/dreamteamer/dreamteamer/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"agent",
|
|
17
|
+
"coding-agent",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"workspace",
|
|
20
|
+
"compiler",
|
|
21
|
+
"cli",
|
|
22
|
+
"yaml",
|
|
23
|
+
"markdown",
|
|
24
|
+
"json-schema",
|
|
25
|
+
"git"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"dreamteamer": "./bin/dreamteamer.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"NOTICE",
|
|
36
|
+
"bin",
|
|
37
|
+
"src",
|
|
38
|
+
"collections",
|
|
39
|
+
"skills",
|
|
40
|
+
"agents",
|
|
41
|
+
"commands",
|
|
42
|
+
"command-bindings",
|
|
43
|
+
"ui-views",
|
|
44
|
+
"collection-templates"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"ajv": "^8.17.1",
|
|
48
|
+
"ajv-formats": "^3.0.1",
|
|
49
|
+
"express": "^5.2.1",
|
|
50
|
+
"js-yaml": "^4.1.0"
|
|
51
|
+
},
|
|
52
|
+
"dreamteamer": {
|
|
53
|
+
"title": "System"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "node scripts/test.mjs",
|
|
57
|
+
"test:unit": "node scripts/test.mjs --unit",
|
|
58
|
+
"test:watch": "node --watch scripts/test.mjs --unit",
|
|
59
|
+
"metrics": "node scripts/metrics.mjs",
|
|
60
|
+
"metrics:check": "node scripts/metrics.mjs --check",
|
|
61
|
+
"layers": "node scripts/layers.mjs",
|
|
62
|
+
"verify": "node scripts/layers.mjs && node scripts/metrics.mjs --check && node scripts/test.mjs"
|
|
63
|
+
}
|
|
60
64
|
}
|
|
@@ -8,6 +8,7 @@ One descriptor file: `modules/<module>/collections/<name>.collection.yaml`. The
|
|
|
8
8
|
| goal | how |
|
|
9
9
|
|---|---|
|
|
10
10
|
| new collection from a template | `dt collections add --name research-docs --template docs` |
|
|
11
|
+
| move one into a namespace | `dt collections rename doctors health/doctors` (or `doctors --namespace health`) |
|
|
11
12
|
| templateless | `dt collections add --name <n>` — emits a minimal compilable schema |
|
|
12
13
|
| add a field | `dt <collection> add-field --name urgent --type boolean --default-value false` |
|
|
13
14
|
| change / drop a field | `dt <collection> update-field …` · `remove-field --name <f>` |
|
|
@@ -20,8 +21,37 @@ or a bare collection name for a reference into it. `--required true` widens `req
|
|
|
20
21
|
|
|
21
22
|
⚠ **The meta verbs write the WORKSPACE module only.** To change a field on a collection another
|
|
22
23
|
module owns, either edit that module's descriptor by hand or add an `extends:` overlay.
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
**`dt collections rename <old> <new>`** moves the descriptor, the records, the record filenames and
|
|
25
|
+
every inbound reference in ONE commit — including `x-reference` targets in other descriptors and any
|
|
26
|
+
ui-view pointing at it. `<old> --namespace <ns>` is sugar for moving it into a namespace under the same
|
|
27
|
+
bare name. It refuses a compiled source, a module-owned collection, a taken name, and an undeclared
|
|
28
|
+
target namespace; a refusal leaves nothing half-moved. Two things it deliberately does NOT overrule,
|
|
29
|
+
because both are authored choices: a `storage.path` you set by hand (the records stay put, and it says
|
|
30
|
+
so) and a `storage.suffix` that is not the singular of the old name.
|
|
31
|
+
|
|
32
|
+
## namespaces — scoping a collection under a folder
|
|
33
|
+
|
|
34
|
+
A collection name may carry a slash-delimited namespace, and it becomes real directory nesting:
|
|
35
|
+
|
|
36
|
+
| declare in the workspace `package.json` | create it | lands in | referenced as |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `"namespaces": ["health"]` | `dt collections add --namespace health --name doctors` | `data/health/doctors/` | `health/doctors/dana-levi` |
|
|
39
|
+
|
|
40
|
+
- **The default namespace is the empty prefix.** `tasks` stays `data/tasks/` and `tasks/kickoff`, so
|
|
41
|
+
common entities need no prefix and adopting namespaces migrates nothing. `default` is RESERVED —
|
|
42
|
+
there is never a second spelling for one collection.
|
|
43
|
+
- ⚠ **The namespace MUST be declared before the collection compiles.** An id is also a slash path
|
|
44
|
+
(`meetings/2026/07/kickoff`), so `a/b/c` is ambiguous without the declared set; an undeclared prefix
|
|
45
|
+
is a compile error rather than a reference that silently names a different collection.
|
|
46
|
+
- `--namespace health --name doctors` and `--name health/doctors` are the same thing. The descriptor
|
|
47
|
+
lands at `collections/health/doctors.collection.yaml`, mirroring the runtime; the `suffix` comes off
|
|
48
|
+
the bare name (`<id>.doctor.md`).
|
|
49
|
+
- `x-reference: health/doctors`, `disable: "<module>/health/doctors"` and every record verb all take the
|
|
50
|
+
QUALIFIED name — it is the collection's identity everywhere.
|
|
51
|
+
- Nested namespaces work (`work/clients`), longest declared prefix wins.
|
|
52
|
+
- ⚠ **No collection may store records inside another's folder** — a namespace folder cannot itself be a
|
|
53
|
+
collection root. compile refuses it, because the outer collection would index the inner one's records
|
|
54
|
+
as its own.
|
|
25
55
|
|
|
26
56
|
## `templates:` — a live shared field set
|
|
27
57
|
|
|
@@ -74,6 +74,23 @@ due: '2026-07-28'
|
|
|
74
74
|
Users report the login button does nothing on mobile.
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## namespaced collections
|
|
78
|
+
|
|
79
|
+
A collection may be scoped under a namespace declared in the workspace `package.json`
|
|
80
|
+
(`dreamteamer.namespaces`). Everything about working with its records is unchanged except that the
|
|
81
|
+
QUALIFIED name is the collection's name everywhere:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
dt health/doctors add --name "Dana Levi" # → data/health/doctors/dana-levi.doctor.md
|
|
85
|
+
dt health/visits add --name Checkup --date 2026-03-04 --doctor health/doctors/dana-levi
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- a reference is still `<collection>/<id>` — `health/doctors/dana-levi` is the collection
|
|
89
|
+
`health/doctors` and the id `dana-levi`.
|
|
90
|
+
- the **default namespace has no prefix**: `tasks/kickoff` in `data/tasks/`, exactly as always.
|
|
91
|
+
- ⚠ a namespace only exists if it is DECLARED. Without the declaration the same string reads as the
|
|
92
|
+
collection `health` with a nested id, so it dangles — `dt check` says so.
|
|
93
|
+
|
|
77
94
|
## the hard rules
|
|
78
95
|
|
|
79
96
|
**never hand-rename or `mv` a record file** — the id IS the path, so a rename silently dangles
|
package/src/check.js
CHANGED
|
@@ -6,7 +6,8 @@ import path from 'node:path';
|
|
|
6
6
|
import Ajv from 'ajv';
|
|
7
7
|
import addFormats from 'ajv-formats';
|
|
8
8
|
import { parseRecord, patternRe, fmtAjvError, unknownFields, walk, EXT } from './records.js';
|
|
9
|
-
import { NO_RUNTIME, loadDescriptors, runtimeDir } from './runtime.js';
|
|
9
|
+
import { NO_RUNTIME, loadDescriptors, runtimeDir, namespaces as compiledNamespaces } from './runtime.js';
|
|
10
|
+
import { parseRef } from './namespace.js';
|
|
10
11
|
|
|
11
12
|
export function check({ root }) {
|
|
12
13
|
const RUNTIME = runtimeDir(root);
|
|
@@ -25,6 +26,9 @@ export function check({ root }) {
|
|
|
25
26
|
console.error(`✖ ${NO_RUNTIME}`);
|
|
26
27
|
return 2;
|
|
27
28
|
}
|
|
29
|
+
// Off the manifest, like the descriptors themselves — `check` is in the record layer and must not
|
|
30
|
+
// learn what a workspace package.json is (see the split in CLAUDE.md).
|
|
31
|
+
const namespaces = compiledNamespaces(root);
|
|
28
32
|
|
|
29
33
|
// ---- index all records: collection -> Map<id, filePath> ------------------------
|
|
30
34
|
const index = new Map();
|
|
@@ -122,7 +126,8 @@ export function check({ root }) {
|
|
|
122
126
|
const self = `${name}/${id}`;
|
|
123
127
|
for (const value of valuesAt(fields, fieldPath)) {
|
|
124
128
|
if (typeof value !== 'string' || value.startsWith('@')) continue;
|
|
125
|
-
const targetId =
|
|
129
|
+
const targetId = parseRef(value, namespaces)?.id;
|
|
130
|
+
if (targetId === undefined) continue; // already flagged as malformed
|
|
126
131
|
const targetFields = parsed.get(target)?.get(targetId);
|
|
127
132
|
if (!targetFields) continue; // already flagged as dangling
|
|
128
133
|
const back = [...valuesAt(targetFields, [inverse])];
|
|
@@ -137,10 +142,11 @@ export function check({ root }) {
|
|
|
137
142
|
function checkRef(file, fieldPath, value, target, softTargets) {
|
|
138
143
|
if (typeof value !== 'string') return;
|
|
139
144
|
if (value.startsWith('@')) return; // runtime tokens (@me, @initiator) are legal
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
145
|
+
// The SAME parser the store writes through (src/namespace.js) — `check` disagreeing with the
|
|
146
|
+
// write path about where a namespace ends would flag valid records and pass invalid ones.
|
|
147
|
+
const ref = parseRef(value, namespaces);
|
|
148
|
+
if (!ref) return flag(file, `${fieldPath.join('.')}: reference "${value}" is not <collection>/<id>`);
|
|
149
|
+
const { collection: coll, id } = ref;
|
|
144
150
|
if (target !== '*' && coll !== target) {
|
|
145
151
|
return flag(file, `${fieldPath.join('.')}: reference "${value}" should target collection "${target}"`);
|
|
146
152
|
}
|
package/src/cli.js
CHANGED
|
@@ -55,8 +55,14 @@ repo attachment (working trees are materialized ON DEMAND, never at install):
|
|
|
55
55
|
repos ensure --all [--json] (explicit opt-in: everything, e.g. before going offline)
|
|
56
56
|
|
|
57
57
|
meta verbs (schema operations — write SOURCES through a compile gate, never the runtime):
|
|
58
|
-
collections add --name <name> [--template docs|entity]
|
|
58
|
+
collections add --name <name> [--namespace <ns>] [--template docs|entity]
|
|
59
|
+
(--namespace health --name doctors === --name health/doctors; the
|
|
60
|
+
namespace must already be declared in dreamteamer.namespaces, and
|
|
61
|
+
records land in data/<ns>/<name>/)
|
|
59
62
|
collections rm <name> [--force] (--force required if it still has records)
|
|
63
|
+
collections rename <old> <new> (or <old> --namespace <ns> to move it into one)
|
|
64
|
+
moves the descriptor AND the records, re-suffixes files when the
|
|
65
|
+
suffix was derived, rewrites every inbound reference, ONE commit
|
|
60
66
|
<collection> add-field --name <field> --type <type> [--options a,b] [--default-value v] [--required true]
|
|
61
67
|
[--description "what this field means"]
|
|
62
68
|
types: string text markdown boolean number integer date datetime
|
package/src/collections-cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Store, bodyField } from './store.js';
|
|
|
8
8
|
import { load, dump } from './yaml.js';
|
|
9
9
|
import { slug } from './template.js';
|
|
10
10
|
import {
|
|
11
|
-
createCollection, removeCollection, addField, updateField, removeField, fieldDef, saveUiView, removeUiView,
|
|
11
|
+
createCollection, removeCollection, renameCollection, addField, updateField, removeField, fieldDef, saveUiView, removeUiView,
|
|
12
12
|
// was copy-pasted here, and the copy went stale the moment the source layout gained a second
|
|
13
13
|
// spelling — one implementation, two callers
|
|
14
14
|
workspaceSystemDir,
|
|
@@ -17,6 +17,7 @@ import { history, historyDiff } from './history.js';
|
|
|
17
17
|
import { commandsFor, recordResolver } from './record-commands.js';
|
|
18
18
|
import { distinctValues } from './field-values.js';
|
|
19
19
|
import { matchesFilter } from './filter.js';
|
|
20
|
+
import { baseNameOf, normalizeNamespaces } from './namespace.js';
|
|
20
21
|
import { sortRows } from './temporal.js';
|
|
21
22
|
import { ensureRepo, ensureAllRepos } from './init.js';
|
|
22
23
|
|
|
@@ -69,6 +70,7 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
69
70
|
// ordinary record path refuses them ("… are system sources") and always would.
|
|
70
71
|
if (collection === 'collections' && verb === 'add') return metaCollectionsAdd(ws, store, flags);
|
|
71
72
|
if (collection === 'collections' && verb === 'rm') return metaCollectionsRm(ws, store, flags, pos);
|
|
73
|
+
if (collection === 'collections' && verb === 'rename') return metaCollectionsRename(ws, store, flags, pos);
|
|
72
74
|
if (collection === 'commands' && verb === 'for') return metaCommandsFor(ws, store, flags, pos);
|
|
73
75
|
if (collection === 'ui-views' && ['add', 'set', 'rm'].includes(verb)) return metaUiView(ws, store, verb, flags, pos);
|
|
74
76
|
if (collection === 'repos' && verb === 'ensure') return metaReposEnsure(ws, flags, pos);
|
|
@@ -228,12 +230,36 @@ function metaReposEnsure(ws, flags, pos) {
|
|
|
228
230
|
|
|
229
231
|
// `dreamteamer collections add --name research-docs --template docs`
|
|
230
232
|
function metaCollectionsAdd(ws, store, flags) {
|
|
231
|
-
const { file } = createCollection(ws, store, { name: flags.name, template: flags.template });
|
|
233
|
+
const { file } = createCollection(ws, store, { name: flags.name, template: flags.template, namespace: flags.namespace });
|
|
232
234
|
console.log(`✔ ${rel(ws.root, file)}`);
|
|
233
235
|
console.log('✔ compiled — the collection is live (schema ops prove sources with a real compile)');
|
|
234
236
|
return 0;
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
// `dreamteamer collections rename doctors health/doctors`, or `… doctors --namespace health`.
|
|
240
|
+
// The whole point is that namespacing EXISTING data is one command instead of a six-step hand
|
|
241
|
+
// migration whose last step (rewriting references) dangles everything when forgotten.
|
|
242
|
+
function metaCollectionsRename(ws, store, flags, pos) {
|
|
243
|
+
const [oldName, explicitNew] = pos;
|
|
244
|
+
if (!oldName) throw new Error('usage: collections rename <old-name> <new-name> | <old-name> --namespace <ns>');
|
|
245
|
+
// `--namespace health` on its own moves the collection INTO that namespace keeping its bare name,
|
|
246
|
+
// which is the common case and saves retyping it.
|
|
247
|
+
const newName = explicitNew
|
|
248
|
+
?? (flags.namespace ? `${String(flags.namespace).replace(/^\/+|\/+$/g, '')}/${baseNameOf(oldName, normalizeNamespaces(ws.pkg.dreamteamer?.namespaces))}` : null);
|
|
249
|
+
if (!newName) throw new Error('missing new name — give it positionally or with --namespace <ns>');
|
|
250
|
+
|
|
251
|
+
const out = renameCollection(ws, store, oldName, newName);
|
|
252
|
+
if (flags.json) { emit(JSON.stringify(out)); return 0; }
|
|
253
|
+
if (!out.renamed) { console.log(`✔ ${oldName} — already named that, nothing to do`); return 0; }
|
|
254
|
+
console.log(`✔ ${oldName} → ${out.name}`);
|
|
255
|
+
if (out.from !== out.to) console.log(` records ${out.from} → ${out.to} (${out.records})`);
|
|
256
|
+
if (out.suffix) console.log(` suffix .${out.suffix.from}.md → .${out.suffix.to}.md`);
|
|
257
|
+
if (out.pathKept) console.log(` ⚠ storage.path kept as "${out.pathKept}" — it was authored, so the rename did not overrule it`);
|
|
258
|
+
console.log(` refs ${out.rewrites} rewritten`);
|
|
259
|
+
console.log('✔ compiled — the rename is live, in ONE commit');
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
237
263
|
// `dreamteamer collections rm widgets [--force]` — --force is required to drop a collection
|
|
238
264
|
// that still has records (removeCollection refuses otherwise, and says so).
|
|
239
265
|
function metaCollectionsRm(ws, store, flags, pos) {
|