dreamteamer 0.12.0 → 0.12.1
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 +2 -2
- package/collections/collections.collection.yaml +1 -1
- package/collections/modules.collection.yaml +1 -1
- package/package.json +2 -1
- package/skills/using-dreamteamer/SKILL.md +1 -1
- package/src/compile.js +12 -5
- package/src/env-vars.js +5 -1
- package/src/init.js +4 -4
- package/src/schema-ops.js +17 -8
package/README.md
CHANGED
|
@@ -73,8 +73,8 @@ source_file: ${env:FILES_FOLDER}/2026/q3.pdf
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Three variables, borrowing VS Code's grammar: `${env:NAME}` — declared in `dreamteamer.vars` in
|
|
76
|
-
`package.json`, valued in the gitignored `.env`
|
|
77
|
-
One verb renders them:
|
|
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:
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
npx dreamteamer resolve '${env:FILES_FOLDER}/x' # → /Volumes/annex/x
|
|
@@ -93,7 +93,7 @@ schema:
|
|
|
93
93
|
description: >-
|
|
94
94
|
The module that OWNS this concept — DERIVED by compile from the base source, never authored.
|
|
95
95
|
An overlay adds fields to somebody else's collection and does not take it over, so `meetings`
|
|
96
|
-
stays owned by crm even though
|
|
96
|
+
stays owned by crm even though the workspace module overlays it. This is the workspace's real partition, and
|
|
97
97
|
what the nav groups by.
|
|
98
98
|
group:
|
|
99
99
|
type: string
|
|
@@ -30,7 +30,7 @@ schema:
|
|
|
30
30
|
properties:
|
|
31
31
|
name:
|
|
32
32
|
type: string
|
|
33
|
-
description: The module's package name, verbatim — `@dreamteamer/crm`, `
|
|
33
|
+
description: The module's package name, verbatim — `@dreamteamer/crm`, `acme-workspace`.
|
|
34
34
|
title:
|
|
35
35
|
type: string
|
|
36
36
|
description: >-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dreamteamer",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
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>",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"test": "node scripts/test.mjs",
|
|
58
58
|
"test:unit": "node scripts/test.mjs --unit",
|
|
59
59
|
"test:watch": "node --watch scripts/test.mjs --unit",
|
|
60
|
+
"perf": "node test/perf/run.mjs",
|
|
60
61
|
"metrics": "node scripts/metrics.mjs",
|
|
61
62
|
"metrics:check": "node scripts/metrics.mjs --check",
|
|
62
63
|
"layers": "node scripts/layers.mjs",
|
|
@@ -100,7 +100,7 @@ source_file: ${env:FILES_FOLDER}/2026/q3.pdf
|
|
|
100
100
|
|
|
101
101
|
| variable | renders to |
|
|
102
102
|
|---|---|
|
|
103
|
-
| `${env:NAME}` | `NAME`'s value in the workspace's `.env` — and only if `NAME` is listed in `dreamteamer.vars` in `package.json` |
|
|
103
|
+
| `${env:NAME}` | `NAME`'s value in the workspace's `.env` — and only if `NAME` is listed in `dreamteamer.vars` in `package.json` AND has a non-empty value there (an empty or whitespace-only value fails exactly like an unset key) |
|
|
104
104
|
| `${workspaceFolder}` | the workspace root, absolute |
|
|
105
105
|
| `${userHome}` | the current user's home directory |
|
|
106
106
|
|
package/src/compile.js
CHANGED
|
@@ -49,7 +49,7 @@ export function titleCase(id) {
|
|
|
49
49
|
* Every `x-display` left in a schema, as `[fieldPath, template, referenceTarget|null]`.
|
|
50
50
|
*
|
|
51
51
|
* The keyword was renamed to `x-title-template` and mostly DELETED — its value is inherited from
|
|
52
|
-
* the target collection's `title_template`. There is deliberately no alias:
|
|
52
|
+
* the target collection's `title_template`. There is deliberately no alias: real workspaces pin
|
|
53
53
|
* this engine by SHA, so nothing breaks until someone bumps a pin, and that person needs a message
|
|
54
54
|
* rather than silence. JSON Schema IGNORES unknown keywords, so the alternative to failing here is
|
|
55
55
|
* a label that quietly stops working and regresses to a raw id.
|
|
@@ -318,7 +318,11 @@ export function compile({ root, pkg }) {
|
|
|
318
318
|
// pairing there is: compile says nothing and then `dt resolve` answers "no value in
|
|
319
319
|
// .env" about a line the operator is looking straight at. Values are read here and
|
|
320
320
|
// never printed — the warnings below name keys only.
|
|
321
|
-
|
|
321
|
+
// A key present with an EMPTY (or whitespace-only) value is treated as absent, same as
|
|
322
|
+
// resolve's renderTemplate — `FILES_FOLDER=` must warn here exactly as `FILES_FOLDER`
|
|
323
|
+
// missing entirely would, or compile says nothing and resolve fails on the same line.
|
|
324
|
+
const parsedEnv = parseEnvValues(fs.readFileSync(envPath, 'utf8'));
|
|
325
|
+
const present = new Set([...parsedEnv].filter(([, v]) => v.trim() !== '').map(([k]) => k));
|
|
322
326
|
for (const [k, mods] of declaredEnv) {
|
|
323
327
|
if (present.has(k)) continue;
|
|
324
328
|
for (const mod of mods) console.warn(`⚠ module ${mod} declares env key ${k} — missing from .env (see .env.example)`);
|
|
@@ -662,7 +666,8 @@ export function compile({ root, pkg }) {
|
|
|
662
666
|
// judged against the BASE module's declarations, which it never wrote.
|
|
663
667
|
const groupModules = [...new Set(group.map((g) => g.moduleName))];
|
|
664
668
|
// WHO OWNS the concept — the module whose source is the base, not the ones overlaying it.
|
|
665
|
-
// An overlay adds fields to somebody else's collection (
|
|
669
|
+
// An overlay adds fields to somebody else's collection (a workspace module adding its own
|
|
670
|
+
// `tags` to the `crm` module's `contacts`);
|
|
666
671
|
// it does not take the concept over. Measured 2026-08-11: letting the overlay win moves
|
|
667
672
|
// `contacts` and `meetings` out of CRM, and a CRM without contacts reads as broken.
|
|
668
673
|
//
|
|
@@ -672,7 +677,8 @@ export function compile({ root, pkg }) {
|
|
|
672
677
|
// IS the answer, and it exists to REPLACE `group:` as the workspace's partition rather than
|
|
673
678
|
// to sit beside it.
|
|
674
679
|
merged.owner = `modules/${moduleId(base?.moduleName ?? groupModules[0])}`;
|
|
675
|
-
// EVERY contributing module, not just the base — a collection merged from crm
|
|
680
|
+
// EVERY contributing module, not just the base — a collection merged from `crm` and the
|
|
681
|
+
// workspace module that overlays it belongs
|
|
676
682
|
// to both, and saying otherwise is what made a flat "which module owns this" field wrong.
|
|
677
683
|
for (const m of groupModules) {
|
|
678
684
|
if (!moduleColls.has(m)) moduleColls.set(m, new Set());
|
|
@@ -881,7 +887,8 @@ export function compile({ root, pkg }) {
|
|
|
881
887
|
// in this loop and never got cleared — a module that was RENAMED or REMOVED left its old record
|
|
882
888
|
// behind forever, listing collections that no longer exist. `check` reads those records like any
|
|
883
889
|
// other, so it surfaced as a dangling reference in a file nobody had touched, twice in one day
|
|
884
|
-
// (
|
|
890
|
+
// (the workspace module's own record after it was renamed, and a domain module's after it was
|
|
891
|
+
// folded into another). The
|
|
885
892
|
// runtime is build output; stale build output is the compiler's problem, not the reader's.
|
|
886
893
|
for (const kind of [...KINDS, ...DERIVED_KINDS]) fs.rmSync(path.join(RUNTIME, kind), { recursive: true, force: true });
|
|
887
894
|
fs.rmSync(path.join(RUNTIME, 'system'), { recursive: true, force: true });
|
package/src/env-vars.js
CHANGED
|
@@ -34,7 +34,11 @@ export function renderTemplate(str, { env, workspaceFolder, declared }) {
|
|
|
34
34
|
if (ns !== 'env') throw new Error(`\${${ns}:${arg}} is not a dreamteamer variable — ${SUPPORTED}`);
|
|
35
35
|
if (!arg) throw new Error(`\${env:} needs a key name — ${SUPPORTED}`);
|
|
36
36
|
if (!declared.includes(arg)) throw new Error(`\${env:${arg}}: "${arg}" is not declared in dreamteamer.vars (workspace package.json) — declared: ${declared.join(', ') || '(none)'}`);
|
|
37
|
-
|
|
37
|
+
// An empty or whitespace-only value is indistinguishable from unset to anyone reading the
|
|
38
|
+
// rendered output — `FILES_FOLDER=` passes `env.has()` and silently renders to '', producing
|
|
39
|
+
// a plausible-looking but wrong path. Same failure, same message: the operator can't tell
|
|
40
|
+
// the two states apart from outside and doesn't care which one it is.
|
|
41
|
+
if (!env.has(arg) || env.get(arg).trim() === '') throw new Error(`\${env:${arg}} is declared but has no value in .env on this machine`);
|
|
38
42
|
return env.get(arg);
|
|
39
43
|
});
|
|
40
44
|
}
|
package/src/init.js
CHANGED
|
@@ -75,10 +75,10 @@ export function init({ flags = {} } = {}) {
|
|
|
75
75
|
harnesses,
|
|
76
76
|
'gitignore-runtime-folder': true,
|
|
77
77
|
// The workspace's own sources live in `modules/default/`, and the folder is named for its ROLE,
|
|
78
|
-
// not for the vault. It used to be named after the workspace, and that name went stale
|
|
79
|
-
// one repo (
|
|
80
|
-
//
|
|
81
|
-
//
|
|
78
|
+
// not for the vault. It used to be named after the workspace, and that name went stale TWICE in
|
|
79
|
+
// one repo (decision 213, reversed by 224) — each rename rewriting every path that RESOLVES
|
|
80
|
+
// while the historical documents deliberately kept the old spelling, so the old `modules/<vault>`
|
|
81
|
+
// was correct in prose and a bug in a path. A role name cannot go stale.
|
|
82
82
|
//
|
|
83
83
|
// `default` is deliberately the same word `RESERVED_NAMESPACES` holds (namespace.js): this module
|
|
84
84
|
// owns the DEFAULT-namespace collections, and the default namespace is the empty prefix. The one
|
package/src/schema-ops.js
CHANGED
|
@@ -219,14 +219,23 @@ export function removeCollection(ws, store, name, { force = false } = {}) {
|
|
|
219
219
|
* relearn both, and would corrupt `data/tasks/` in a path or a URL on its first outing. N passes over
|
|
220
220
|
* the record files is the price, and at human scale it is worth paying for reusing the correct code.
|
|
221
221
|
*
|
|
222
|
-
* ⚠ MEASURED 2026-08-17, so the cost is a number rather than a hope: a 2,291-record collection
|
|
223
|
-
* 3,391-file workspace
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
222
|
+
* ⚠ MEASURED 2026-08-17, so the cost is a number rather than a hope: a real 2,291-record collection
|
|
223
|
+
* in a 3,391-file workspace takes **3 minutes**, of which 142s is system time — 7.7M file reads to
|
|
224
|
+
* rewrite ZERO references, because the pass runs per id whether or not anything points at the
|
|
225
|
+
* collection. Tolerable for a one-time migration and left alone on that basis; it is
|
|
226
|
+
* O(records x files), so a workspace 3x larger pays 27 minutes.
|
|
227
|
+
*
|
|
228
|
+
* ⚠ REPRODUCED 2026-08-22 by `npm run perf -- --records=2291 --filler=1100`, which generates a
|
|
229
|
+
* workspace that shape — 271s wall, 203s of it system, and **15.6M reads, not 7.7M**. The original
|
|
230
|
+
* figure counted ONE pass per id; there are TWO, because `captureRefs` walks every record file for
|
|
231
|
+
* the rollback snapshot before `rewriteRefs` walks them all again. `7.7M` is the per-pass number.
|
|
232
|
+
* That is what a generated fixture is for: the finding was right about the shape and off by 2x on
|
|
233
|
+
* the count, and no comment could have told you.
|
|
234
|
+
*
|
|
235
|
+
* The fix when it is needed is a batch entry point on the store that reads each file ONCE and loops
|
|
236
|
+
* the ref set in memory, with `text.includes(oldName + '/')` as a cheap NEGATIVE filter only — never
|
|
237
|
+
* as the matcher, for the reason above. Halving it is cheaper still: the snapshot pass and the
|
|
238
|
+
* rewrite pass read the same bytes.
|
|
230
239
|
*/
|
|
231
240
|
export function renameCollection(ws, store, oldName, newName) {
|
|
232
241
|
const d = store.descriptor(oldName); // throws with the known-collection list if absent
|