dreamteamer 0.6.1 → 0.6.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dreamteamer",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
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/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', 'studio',
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 studio
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 server serves /ui, the studio imports and calls it.
372
- // studio/dist/app.js (a built bundle) wins over studio/app.js (plain-JS, host-provided Vue).
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
- // layouts are registered module code; a view naming an unregistered layout fails loudly
516
- // naming the registered set (design guardrail: "unknown layout = compile error").
517
- // core set = the studio's built-ins; modules declare theirs in package.json
518
- // dreamteamer.studio.layouts (the same file their app.js registration lives beside).
519
- // KEEP IN SYNC with the UI's `lists.register(...)` calls (dreamteamer-vscode
520
- // webview/src/registry/register-defaults.ts). kanban/calendar/map landed there as core Lists in
521
- // the 2026-07-27 layouts wave but this set was never widened, so the only way to get a
522
- // `layout: kanban` view past compile was for a module to CLAIM the layout it didn't own — which
523
- // is what a workspace module was once caught doing, shadowing the core board in the registry (a
524
- // module's app.js loads after the built-ins and Map.set wins). Fixed both ends 2026-07-29.
525
- const registeredLayouts = new Set(['table', 'cards', 'kanban', 'calendar', 'map']);
526
- for (const source of sources) {
527
- try {
528
- const mpkg = JSON.parse(fs.readFileSync(path.join(source.root, 'package.json'), 'utf8'));
529
- for (const l of mpkg.dreamteamer?.studio?.layouts ?? []) registeredLayouts.add(l);
530
- } catch { /* root-workspace source without package.json */ }
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
- execFileSync('git', ['clone', '--branch', ref, url, dest], { stdio: 'inherit' });
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
- return 0;
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