dreamteamer 0.6.1 → 0.6.3
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/package.json +1 -1
- package/src/collections-cli.js +20 -3
- package/src/compile.js +42 -33
- package/src/init.js +17 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dreamteamer",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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>",
|
package/src/collections-cli.js
CHANGED
|
@@ -295,15 +295,32 @@ function parseViewValue(raw) {
|
|
|
295
295
|
return raw;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Assign `a.b.c` into a nested object, creating plain objects on the way down.
|
|
300
|
+
*
|
|
301
|
+
* An empty or null value REMOVES the key rather than writing `''` — the same convention
|
|
302
|
+
* `store.set` has always used for top-level fields (`if (v === null || v === '') delete next[k]`),
|
|
303
|
+
* extended to the nested paths the meta verbs address. Without it there was no way to take a key
|
|
304
|
+
* back out of a `ui-view`'s `options`, so a superseded option lingered as `provider: ''` and the
|
|
305
|
+
* only cure was `rm` + `add`.
|
|
306
|
+
*
|
|
307
|
+
* Intermediate objects are not created on the way to a delete: unsetting `a.b` on a record with no
|
|
308
|
+
* `a` should leave the record alone, not grow an empty `a: {}`.
|
|
309
|
+
*/
|
|
299
310
|
function assignPath(target, dotted, value) {
|
|
300
311
|
const keys = dotted.split('.');
|
|
312
|
+
const leaf = keys[keys.length - 1];
|
|
313
|
+
const unset = value === null || value === '';
|
|
301
314
|
let node = target;
|
|
302
315
|
for (const k of keys.slice(0, -1)) {
|
|
303
|
-
if (node[k] == null || typeof node[k] !== 'object' || Array.isArray(node[k]))
|
|
316
|
+
if (node[k] == null || typeof node[k] !== 'object' || Array.isArray(node[k])) {
|
|
317
|
+
if (unset) return;
|
|
318
|
+
node[k] = {};
|
|
319
|
+
}
|
|
304
320
|
node = node[k];
|
|
305
321
|
}
|
|
306
|
-
node[
|
|
322
|
+
if (unset) delete node[leaf];
|
|
323
|
+
else node[leaf] = value;
|
|
307
324
|
}
|
|
308
325
|
|
|
309
326
|
const VIEW_META_FLAGS = new Set(['id', 'json', 'force']);
|
package/src/compile.js
CHANGED
|
@@ -100,7 +100,8 @@ function bothLayouts(root, kind) {
|
|
|
100
100
|
* has `data/` — not a layout knob every module would set identically.
|
|
101
101
|
*/
|
|
102
102
|
const NON_SOURCE_DIRS = new Set([
|
|
103
|
-
'node_modules', 'data', 'state', 'media', 'bin', 'src', 'lib', 'scripts',
|
|
103
|
+
'node_modules', 'data', 'state', 'media', 'bin', 'src', 'lib', 'scripts',
|
|
104
|
+
'ui', 'studio', // the module's UI bundle — 'studio' is the pre-archive name, kept as a fallback
|
|
104
105
|
'docs', 'dist', 'build', 'test', 'tests', 'coverage', 'system', // 'system': the pre-flatten layout
|
|
105
106
|
]);
|
|
106
107
|
|
|
@@ -357,23 +358,21 @@ export function compile({ root, pkg }) {
|
|
|
357
358
|
}
|
|
358
359
|
}
|
|
359
360
|
|
|
360
|
-
// A module that ships only folders the engine does not recognise compiles ✔ and contributes
|
|
361
|
-
// NOTHING. Warn; do not fail, since a module that is temporarily source-free is the
|
|
362
|
-
// operator's business, not the compiler's.
|
|
363
|
-
for (const source of sources) {
|
|
364
|
-
if (contributed.has(source.name)) continue;
|
|
365
|
-
console.warn(`⚠ module "${source.name}" (${rel(source.root)}) contributed no recognised sources — its folder names must match a known kind (${KINDS.join(', ')})`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
361
|
// ---- stage module UI bundles ---------------------------------------------------
|
|
369
|
-
// modules ship a PRE-BUILT app.js that registers components/layouts against the
|
|
362
|
+
// modules ship a PRE-BUILT app.js that registers components/layouts against the surface's
|
|
370
363
|
// registry (design "the UI": components are module code, never records). staged under
|
|
371
|
-
// .dreamteamer/ui/<module>/app.js; the
|
|
372
|
-
//
|
|
364
|
+
// .dreamteamer/ui/<module>/app.js; the VS Code extension reads it off disk (decision 48) and
|
|
365
|
+
// the legacy server served it at /ui. `dist/app.js` (a built bundle) wins over `app.js`
|
|
366
|
+
// (plain-JS, host-provided Vue).
|
|
367
|
+
//
|
|
368
|
+
// `ui/` is the name — it matches where the bundle STAGES and what it is. `studio/` is the
|
|
369
|
+
// original name and stays a fallback: the studio it referred to is archived (decisions 51, 93),
|
|
370
|
+
// so the folder was named after a surface that no longer exists. Both are in NON_SOURCE_DIRS,
|
|
371
|
+
// so neither trips the unknown-folder gate (decision 179).
|
|
373
372
|
const uiModules = [];
|
|
374
373
|
const uiOwners = new Map(); // shortName -> module name, for a readable collision error
|
|
375
374
|
for (const source of sources) {
|
|
376
|
-
const cand = ['studio/dist/app.js', 'studio/app.js']
|
|
375
|
+
const cand = ['ui/dist/app.js', 'ui/app.js', 'studio/dist/app.js', 'studio/app.js']
|
|
377
376
|
.map((p) => path.join(source.root, p))
|
|
378
377
|
.find((p) => fs.existsSync(p));
|
|
379
378
|
if (!cand) continue;
|
|
@@ -386,6 +385,18 @@ export function compile({ root, pkg }) {
|
|
|
386
385
|
uiOwners.set(shortName, source.name);
|
|
387
386
|
addEntry(path.join('ui', shortName, 'app.js'), cand);
|
|
388
387
|
uiModules.push(shortName);
|
|
388
|
+
// A UI bundle IS a contribution. Counting it here is what keeps the warning below honest —
|
|
389
|
+
// a module whose whole purpose is a layout used to be told it "contributed no recognised
|
|
390
|
+
// sources" while its layout was rendering in the app.
|
|
391
|
+
contributed.add(source.name);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// A module that ships only folders the engine does not recognise compiles ✔ and contributes
|
|
395
|
+
// NOTHING. Warn; do not fail, since a module that is temporarily source-free is the
|
|
396
|
+
// operator's business, not the compiler's. Runs AFTER UI staging so a UI-only module counts.
|
|
397
|
+
for (const source of sources) {
|
|
398
|
+
if (contributed.has(source.name)) continue;
|
|
399
|
+
console.warn(`⚠ module "${source.name}" (${rel(source.root)}) contributed no recognised sources — its folder names must match a known kind (${KINDS.join(', ')}) or it must ship a UI bundle at ui/app.js`);
|
|
389
400
|
}
|
|
390
401
|
|
|
391
402
|
// ---- collection-templates, for `templates:` merging ----------------------------
|
|
@@ -512,29 +523,27 @@ export function compile({ root, pkg }) {
|
|
|
512
523
|
}
|
|
513
524
|
|
|
514
525
|
// ---- ui-view layout validation --------------------------------------------------
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
// `
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
526
|
+
// ⚠ `layout` is NOT validated here, deliberately. The rule: the engine validates a value if and
|
|
527
|
+
// only if the ENGINE INTERPRETS it. It interprets filter operators (`matchesFilter`, and the
|
|
528
|
+
// CLI's `--where`), so a typo'd operator is a real bug it can catch — hence the check below.
|
|
529
|
+
// It interprets `layout` nowhere: the value is opaque payload forwarded to whichever surface
|
|
530
|
+
// renders, and only that surface's registry knows which ids exist.
|
|
531
|
+
//
|
|
532
|
+
// There used to be an allowlist here, hardcoded to mirror dreamteamer-vscode's
|
|
533
|
+
// `lists.register(...)` calls in a DIFFERENT REPO. It was wrong both times it was tested:
|
|
534
|
+
// kanban/calendar/map (2026-07-29) and erd/graph (2026-08-10), each costing an engine edit to
|
|
535
|
+
// add a UI feature. Worse, it BLOCKED the sanctioned extension path — a module's `app.js` gets
|
|
536
|
+
// a `registerList({ id, ... })` API, so it can contribute a layout with no engine involvement,
|
|
537
|
+
// and this check then rejected the very view naming it unless the module also duplicated the id
|
|
538
|
+
// into a `dreamteamer.studio.layouts` key (zero users, in any repo, ever). Proven 2026-08-11 by
|
|
539
|
+
// modules/ui-smoke: the layout rendered in the app while compile refused the view.
|
|
540
|
+
//
|
|
541
|
+
// The descriptor already documented the correct behaviour — ui-views.collection.yaml: "An
|
|
542
|
+
// unregistered id degrades visibly rather than erroring" — and the surface already implements
|
|
543
|
+
// it (presets.ts#resolveRendererEntry falls back to table). Decision 195.
|
|
532
544
|
for (const [rt, e] of entries) {
|
|
533
545
|
if (!rt.startsWith('ui-views/')) continue;
|
|
534
546
|
const view = load(e.bytes.toString('utf8'));
|
|
535
|
-
if (view?.target === 'list' && view?.layout && !registeredLayouts.has(view.layout)) {
|
|
536
|
-
fail(`${rt}: layout "${view.layout}" is not registered (registered: ${[...registeredLayouts].sort().join(', ')}).\n a module registers layouts in its studio app.js AND declares them in package.json under dreamteamer.studio.layouts.`);
|
|
537
|
-
}
|
|
538
547
|
// filters are load-bearing (they narrow what the operator SEES) — typo'd operators
|
|
539
548
|
// fail at compile, not silently at render (review finding 5)
|
|
540
549
|
const badOps = view?.filter ? [...unknownOperators(view.filter)] : [];
|
package/src/init.js
CHANGED
|
@@ -143,6 +143,7 @@ export function install({ root, pkg }) {
|
|
|
143
143
|
const names = Object.keys(map);
|
|
144
144
|
if (!names.length) { console.log('✔ no git-modules declared — nothing to restore'); return 0; }
|
|
145
145
|
fs.mkdirSync(path.join(root, 'git_modules'), { recursive: true });
|
|
146
|
+
const unreachable = [];
|
|
146
147
|
for (const name of names) {
|
|
147
148
|
const { url, ref = 'main' } = map[name];
|
|
148
149
|
const dest = path.join(root, 'git_modules', name);
|
|
@@ -154,10 +155,24 @@ export function install({ root, pkg }) {
|
|
|
154
155
|
continue;
|
|
155
156
|
}
|
|
156
157
|
console.log(`… cloning ${url} → git_modules/${name} (${ref})`);
|
|
157
|
-
|
|
158
|
+
try {
|
|
159
|
+
execFileSync('git', ['clone', '--branch', ref, url, dest], { stdio: 'inherit' });
|
|
160
|
+
} catch {
|
|
161
|
+
// one unreachable clone must not abandon the rest. the lockfile is a map of PRIVATE
|
|
162
|
+
// repos as often as public ones, so "the current credentials cannot see this one" is
|
|
163
|
+
// ordinary — a fresh machine, a collaborator, a cloud sandbox. aborting there left a
|
|
164
|
+
// half-restored workspace and named only the first failure.
|
|
165
|
+
fs.rmSync(dest, { recursive: true, force: true }); // git leaves the partial dir behind
|
|
166
|
+
unreachable.push(name);
|
|
167
|
+
console.warn(`⚠ git_modules/${name}: clone failed — skipped (${url})`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
158
170
|
buildClone(dest, name);
|
|
159
171
|
}
|
|
160
|
-
|
|
172
|
+
// non-zero, because the workspace is NOT what the lockfile describes: modules are missing and
|
|
173
|
+
// `check` will report references into them as unknown collections.
|
|
174
|
+
if (unreachable.length) console.error(`✖ ${unreachable.length} module(s) could not be cloned: ${unreachable.join(', ')}`);
|
|
175
|
+
return unreachable.length ? 1 : 0;
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
// dreamteamer update [<name>] — pull each lockfile-declared git_modules clone forward
|