dreamteamer 0.18.0 → 0.19.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/src/cli.js CHANGED
@@ -13,7 +13,7 @@ import fs from 'node:fs';
13
13
  import path from 'node:path';
14
14
  import { execFileSync } from 'node:child_process';
15
15
  import { findWorkspace } from './workspace.js';
16
- import { compile, staleness, warnIfStale, discoverModules, CHANNEL_LABEL, KINDS } from './compile.js';
16
+ import { compile, staleness, warnIfStale, discoverModules, CHANNEL_LABEL, locationOf, KINDS } from './compile.js';
17
17
  import { check } from './check.js';
18
18
  import { collectionCommand, emit, relationsCommand } from './collections-cli.js';
19
19
  import { init, install, installClone, update, listRepos } from './init.js';
@@ -78,19 +78,49 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
78
78
  record keeps the template verbatim. An array
79
79
  field prints one item per line)
80
80
 
81
- schema verbs (write SOURCES through a compile gate, never the runtime — a different act, so a
82
- different word in front of it):
83
- schema add-collection --name <name> [--namespace <ns>] [--template docs|entity]
84
- (--namespace health --name doctors === --name health/doctors; the
85
- namespace must already be declared in dreamteamer.namespaces, and
86
- records land in data/<ns>/<name>/)
87
- schema rm-collection <name> [--force] (--force required if it still has records)
88
- schema rename-collection <old> <new> (or <old> --namespace <ns> to move it into one)
89
- moves the descriptor AND the records, re-suffixes files when the
90
- suffix was derived, rewrites every inbound reference, ONE commit
91
- schema add-field <collection> --name <field> --type <type> [--options a,b] [--default-value v]
92
- [--required true] [--description "what this field means"]
93
- [--many] [--inverse [name]] [--unique] [--body]
81
+ system verbs — the SAME verbs, on the entities the compiler materializes (modules, collections,
82
+ skills, agents, commands, command-bindings, ui-views, collection-templates). ⚠ ONE difference in
83
+ POLICY, not in spelling: a SYSTEM write commits itself, because an uncompilable or unpublished
84
+ schema is not a state a workspace should sit in; a RECORD write does not — \`commit\` publishes it.
85
+ The commit lands in the repo that holds the source, so a write into a git module commits there.
86
+ add collections --name <name> [--module <m>] [--namespace <ns>] [--template docs|entity]
87
+ [--description "…"] [--suffix <s>] [--id-shape dated|slug]
88
+ (--namespace health --name doctors === --name
89
+ health/doctors; a module declaring exactly ONE
90
+ namespace infers it, and the resolved name is echoed.
91
+ --namespace '' means no namespace)
92
+ add modules --name <id> [--description "…"]
93
+ (modules/<id>/ + every kind folder + package.json.
94
+ folder = package name = id, so a module never forks.
95
+ the git shape is \`install --clone <url> [name]\`)
96
+ add skills --name <id> --description "…" (skills/<id>/SKILL.md — --description is required,
97
+ because an undescribed skill is undiscoverable)
98
+ add ui-views --path </route> --target list --collection collections/<c> --layout <id>
99
+ [--id <id>] [k.v=…]
100
+ set <system>/<id> <field>=<value> … (collections: description · use_when · title ·
101
+ title_template · icon · group · list_fields ·
102
+ sort_field · order, plus module=<m>, which MOVES it.
103
+ modules: description · namespaces · dependencies ·
104
+ peerDependencies, record-shaped (modules/core).
105
+ ui-views: dotted keys — options.sort=-date. An empty
106
+ value REMOVES the key; quote it to write the empty
107
+ string itself ('options.sort=""').
108
+ skills/agents/commands/…: frontmatter keys)
109
+ rm <system>/<id> [--force] [--dry-run]
110
+ rename <system>/<id> <new-id> (a collection's rename moves its records, re-suffixes
111
+ the files and rewrites every inbound ref, ONE commit)
112
+ move <system>/<id> --after|--before <id> (nav ordering — it writes \`order\`)
113
+ get collections/<c> [--module <m>] (--module prints ONE module's source contribution
114
+ rather than the merged descriptor)
115
+ list modules | collections | skills | … (id · location · path · namespaces · package name)
116
+ revert <system>/<id> (refused: its source is in git —
117
+ \`git checkout <sha> -- <path>\` then \`dt compile\`)
118
+
119
+ field verbs — a field is the one sub-entity, and it has verbs of its own (there is no \`fields\`
120
+ collection: the ENGINE does not read one, and \`rename-field\` was the only capability it would buy):
121
+ add-field <collection> --name <field> --type <type> [--options a,b] [--default-value v]
122
+ [--required true] [--description "…"] [--many] [--inverse [name]]
123
+ [--inverse-description "…"] [--unique] [--body] [--module <m>]
94
124
  [--on-delete restrict|set-null] [--mirror-of <collection>.<field>]
95
125
  types: string text markdown boolean number integer date datetime
96
126
  enum tags <collection> — a date-time may be written as
@@ -101,9 +131,11 @@ different word in front of it):
101
131
  --body marks the field a record's PROSE lands in (the text after the
102
132
  frontmatter). One per collection, and a relation mirror needs the
103
133
  target to have one.
104
- schema update-field <collection> --name <field> --type <type> [--options a,b] [--default-value v]
134
+ --module writes an OVERLAY in that module (it must declare the base's
135
+ module in dreamteamer.dependencies).
136
+ update-field <collection> --name <field> [--type <type>] [--options a,b] [--default-value v]
105
137
  [--required true|false] [--description "…"] [--body true|false]
106
- [--many] [--inverse [name]] [--unique]
138
+ [--many] [--inverse [name]] [--unique] [--module <m>]
107
139
  [--on-delete restrict|set-null] [--mirror-of <collection>.<field>]
108
140
  (an existing description survives a retype, and so does every relation
109
141
  keyword you do not restate. --inverse on an EXISTING reference is the
@@ -111,14 +143,16 @@ different word in front of it):
111
143
  restating --type. --inverse= drops the mirror; --unique false clears
112
144
  the one-to-one. Records written before the mirror existed are counted
113
145
  for you, with the "relations rebuild" that repairs them.)
114
- schema remove-field <collection> --name <field>
115
- schema add-view --path </route> --target list --collection collections/<c> --layout <id>
116
- [--id <id>] [k.v=…]
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.)
121
- schema rm-view <id>
146
+ remove-field <collection> --name <field> [--module <m>] [--dry-run]
147
+ (clears the field's VALUES in the same write, and reports the count)
148
+ rename-field <collection> --name <field> --to <new-name> [--module <m>] [--dry-run]
149
+ (rewrites the key in every record AND everywhere a descriptor or view
150
+ names the field: list_fields, sort_field, x-inverse, x-inverse-of,
151
+ title_template, id.generate, a ui-view's options.columns and filter,
152
+ and a command-binding's can-enter/can-exit. ONE commit)
153
+
154
+ Every verb that MOVES records or CLEARS values takes --dry-run and prints its plan first:
155
+ records N · refs M · descriptors K · values cleared V
122
156
 
123
157
  workspace verbs:
124
158
  init write the workspace skeleton into the current directory (never compiles)
@@ -152,21 +186,11 @@ const REF_VERBS = new Set(['get', 'set', 'rm', 'rename', 'history', 'diff', 'rev
152
186
  const COLLECTION_VERBS = new Set(['list', 'add', 'values']);
153
187
  const EITHER_VERBS = new Set(['move', 'commands']);
154
188
 
155
- // `schema <op>` → the (collection, verb) pair the implementation layer already answers to. The
156
- // collections are literals: `collections` and `ui-views` are SYSTEM-stored, which is precisely what
157
- // makes these a separate group in the grammar rather than records like any other.
158
- const SCHEMA_OPS = {
159
- 'add-collection': ['collections', 'add'],
160
- 'rm-collection': ['collections', 'rm'],
161
- 'rename-collection': ['collections', 'rename'],
162
- 'add-view': ['ui-views', 'add'],
163
- 'set-view': ['ui-views', 'set'],
164
- 'rm-view': ['ui-views', 'rm'],
165
- };
166
- // These three name their collection POSITIONALLY (`schema add-field contacts --name phone`) and keep
167
- // their existing verb spelling on it — the schema group is a prefix here, not a rename.
168
- const SCHEMA_FIELD_OPS = new Set(['add-field', 'update-field', 'remove-field']);
169
- const SCHEMA_OP_LIST = [...Object.keys(SCHEMA_OPS), ...SCHEMA_FIELD_OPS].join(' | ');
189
+ // FIELD VERBS. Their <target> is a collection and everything else is flags, which is the one shape
190
+ // that differs from the record verbs — so they get their own case arm rather than being folded into
191
+ // `dispatchRecordVerb`. There is no `schema <op>` table any more: system entities take the record
192
+ // verbs, and `collectionCommand`'s interceptors are the whole dispatch (§4).
193
+ const FIELD_VERBS = ['add-field', 'update-field', 'remove-field', 'rename-field'];
170
194
 
171
195
  export function run(argv) {
172
196
  const [cmd, ...rest] = argv;
@@ -184,7 +208,7 @@ export function run(argv) {
184
208
  process.exit(init({ flags }));
185
209
  }
186
210
  if (!cmd) {
187
- console.log(USAGE);
211
+ emit(USAGE);
188
212
  process.exit(0);
189
213
  }
190
214
  const ws = findWorkspace();
@@ -304,11 +328,17 @@ export function run(argv) {
304
328
  const shadowed = new Map(shadows.map((sh) => [sh.name, sh]));
305
329
  console.log('modules:');
306
330
  for (const m of modules) {
307
- let line = ` ${m.name} [${m.channel}]`;
331
+ // §10: the folder name IS the label. `hr git_modules @ 3f2a1c (dirty)` needs no
332
+ // legend, and `[inline]` needed one every single time.
333
+ let line = ` ${m.name.padEnd(20)} ${locationOf(m, ws.root)}`;
308
334
  if (m.channel === 'git') {
309
335
  const ref = tryGit(m.root, ['rev-parse', '--short', 'HEAD']);
310
336
  const dirty = tryGit(m.root, ['status', '--porcelain']);
311
- line += ` @ ${ref ?? '?'}${dirty ? ' (dirty)' : ''}`;
337
+ // ⚠ A SCHEMA WRITE NOW COMMITS HERE (§9), so a clone can be ahead of its remote
338
+ // with work the operator does not know they are holding. `status` is the command
339
+ // they run when something feels wrong, so it is where the count belongs.
340
+ const ahead = tryGit(m.root, ['rev-list', '--count', 'HEAD', '--not', '--remotes']);
341
+ line += ` @ ${ref ?? '?'}${dirty ? ' (dirty)' : ''}${Number(ahead) > 0 ? ` — ahead ${ahead}, push when ready` : ''}`;
312
342
  }
313
343
  const sh = shadowed.get(m.name);
314
344
  if (sh) line += ` — shadows ${CHANNEL_LABEL[sh.loser]} copy`;
@@ -347,7 +377,7 @@ export function run(argv) {
347
377
  process.exit(0);
348
378
  }
349
379
  case 'help':
350
- console.log(USAGE);
380
+ emit(USAGE);
351
381
  process.exit(0);
352
382
  case 'list': case 'add': case 'values':
353
383
  case 'get': case 'set': case 'rm': case 'rename': case 'history': case 'diff': case 'revert':
@@ -356,6 +386,20 @@ export function run(argv) {
356
386
  process.exit(dispatchRecordVerb(ws, cmd, rest));
357
387
  // The verb `check`'s stale-mirror message names. It reads the compiled relations, and
358
388
  // rebuild WRITES records, so both want the same staleness warning every record verb gets.
389
+ // FIELD VERBS — see FIELD_VERBS. Their <target> is a collection and everything else is
390
+ // flags, so they are their own case rather than being folded into `dispatchRecordVerb`.
391
+ //
392
+ // ⚠ `dt schema <op>` is GONE, not aliased. The 0.12.0 policy: a stale invocation must fail
393
+ // loudly, because a half-working grammar teaches the wrong shape without ever saying so.
394
+ // The `default` arm below names `schema` specifically.
395
+ case 'add-field': case 'update-field': case 'remove-field': case 'rename-field': {
396
+ warnIfStale(ws.root);
397
+ const [target, ...flagArgs] = rest;
398
+ if (!target || target.startsWith('--')) {
399
+ throw new Error(`dt ${cmd} needs a collection: dreamteamer ${cmd} <collection> --name <field> …`);
400
+ }
401
+ process.exit(collectionCommand(ws, target, cmd, flagArgs));
402
+ }
359
403
  case 'relations':
360
404
  warnIfStale(ws.root);
361
405
  process.exit(relationsCommand(ws, rest));
@@ -364,14 +408,26 @@ export function run(argv) {
364
408
  case 'ensure':
365
409
  warnIfStale(ws.root);
366
410
  process.exit(collectionCommand(ws, 'repos', 'ensure', rest));
367
- case 'schema':
368
- warnIfStale(ws.root);
369
- process.exit(dispatchSchemaVerb(ws, rest));
370
411
  case 'resolve':
371
412
  process.exit(resolveVariables(ws, rest));
372
413
  default:
414
+ // ⚠ NAMED, not just unknown. Every doc, skill and downstream script spelled these
415
+ // `dt schema <op>` for seven releases, so the failure has to carry the translation —
416
+ // an "unknown verb" alone sends the reader to `help` to guess which of nine verbs
417
+ // replaced the one they typed. No alias layer and no deprecation window: 0.12.0's
418
+ // policy, and the reason it is the right one is that `dt contacts list` failing
419
+ // loudly is what taught the verb-first grammar in one command.
420
+ if (cmd === 'schema') {
421
+ console.error('✖ unknown verb "schema" — schema verbs are gone since 0.19.0. System entities take the RECORD verbs now:');
422
+ console.error(' dt add collections --name <c> [--module <m>] · dt rm collections/<c> · dt rename collections/<old> <new>');
423
+ console.error(' dt set collections/<c> module=<m> | <scalar>=<v> · dt get collections/<c> [--module <m>]');
424
+ console.error(' dt add-field <c> … · dt update-field <c> … · dt remove-field <c> … · dt rename-field <c> --name <f> --to <g>');
425
+ console.error(' dt add|set|rm|rename modules/<id> … · dt add|set|rm|rename ui-views/<id> …');
426
+ console.error(' the full mapping table is in UPDATING.md (0.18.0 → 0.19.0), and `dt help` has the current spellings.');
427
+ process.exit(1);
428
+ }
373
429
  console.error(`✖ unknown verb "${cmd}" — dreamteamer is verb-first since 0.12.0: dt <verb> [<target>]`);
374
- console.error(USAGE);
430
+ emit(USAGE, 2);
375
431
  process.exit(1);
376
432
  }
377
433
  } catch (e) {
@@ -468,22 +524,6 @@ function resolveVariables(ws, args) {
468
524
  return 0;
469
525
  }
470
526
 
471
- /** Translate `dt schema <op> …` onto the same meta verbs `collectionCommand` already routes. */
472
- function dispatchSchemaVerb(ws, args) {
473
- const [op, ...rest] = args;
474
- if (!op) throw new Error(`dt schema needs an operation — use ${SCHEMA_OP_LIST}`);
475
- if (SCHEMA_FIELD_OPS.has(op)) {
476
- const [collection, ...flags] = rest;
477
- if (!collection || collection.startsWith('--')) {
478
- throw new Error(`dt schema ${op} needs a collection: dreamteamer schema ${op} <collection> --name <field> …`);
479
- }
480
- return collectionCommand(ws, collection, op, flags);
481
- }
482
- const pair = SCHEMA_OPS[op];
483
- if (!pair) throw new Error(`unknown schema operation "${op}" — use ${SCHEMA_OP_LIST}`);
484
- return collectionCommand(ws, pair[0], pair[1], rest);
485
- }
486
-
487
527
  function tryGit(cwd, args) {
488
528
  try { return execFileSync('git', args, { cwd, stdio: QUIET }).toString().trim() || null; } catch { return null; }
489
529
  }