dreamteamer 0.8.0 → 0.9.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/schema-ops.js +54 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dreamteamer",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "A workspace compiler for coding agents \u2014 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/schema-ops.js CHANGED
@@ -64,6 +64,37 @@ export function workspaceSystemDir(ws, kind) {
64
64
  return kindDir(wm ? path.join(ws.root, 'modules', wm) : ws.root, kind);
65
65
  }
66
66
 
67
+ /**
68
+ * WHERE A COLLECTION'S DESCRIPTOR ACTUALLY LIVES — asked of the manifest, not assumed.
69
+ *
70
+ * `renameCollection` used to derive this from `workspaceSystemDir`, which silently meant "only the
71
+ * workspace module's own collections can be renamed". That is the wrong line. The guard exists to
72
+ * stop a write that will be ERASED, and the thing that erases writes is `npm install` — so the test
73
+ * is `node_modules/`, not "which module". A module whose sources are inline in the workspace repo is
74
+ * under the same git history as everything else and is perfectly safe to rewrite; refusing it made
75
+ * `collections rename` unusable for exactly the migration it was built for, because a workspace's
76
+ * domain collections almost always live in a module.
77
+ *
78
+ * Returns `{ dir, sources }` — the kind dir to write into (the SAME module the descriptor came from,
79
+ * so a rename never teleports a collection into the workspace module), and every descriptor source
80
+ * that contributed, so the caller can refuse the cases this cannot honestly do.
81
+ */
82
+ function descriptorSourceDir(ws, name) {
83
+ const entry = readManifest(ws.root)?.entries?.[`collections/${name}.collection.yaml`];
84
+ // `sources` mixes the descriptor with any collection-templates it merged, so match on the shape
85
+ // of a descriptor path for THIS collection. A namespaced name is nested, hence the full suffix.
86
+ const suffix = `collections/${name}.collection.yaml`;
87
+ const sources = (entry?.sources ?? [])
88
+ .map((s) => s.path)
89
+ .filter((p) => p.endsWith(suffix));
90
+ if (!sources.length) return { dir: null, sources };
91
+ // The BASE descriptor is the one to move. With an overlay present there are two, and the overlay's
92
+ // `extends` names the base by its old qualified id — rewriting that is a second, different
93
+ // migration, so the caller refuses rather than half-doing it.
94
+ const moduleRoot = path.join(ws.root, sources[0].slice(0, sources[0].length - suffix.length));
95
+ return { dir: kindDir(moduleRoot, 'collections'), sources };
96
+ }
97
+
67
98
  // ---- ops ------------------------------------------------------------------------
68
99
 
69
100
  export function createCollection(ws, store, { name, template, namespace }) {
@@ -143,6 +174,15 @@ export function removeCollection(ws, store, name, { force = false } = {}) {
143
174
  * already scopes prose to `[[wikilinks]]` (decision 7) — a fresh `oldName/` pattern would have to
144
175
  * relearn both, and would corrupt `data/tasks/` in a path or a URL on its first outing. N passes over
145
176
  * the record files is the price, and at human scale it is worth paying for reusing the correct code.
177
+ *
178
+ * ⚠ MEASURED 2026-08-17, so the cost is a number rather than a hope: a 2,291-record collection in a
179
+ * 3,391-file workspace — gk-brain's `finance-transactions` — takes **3 minutes**, of which 142s is
180
+ * system time. That is 7.7M file reads to rewrite ZERO references, because the pass runs per id
181
+ * whether or not anything points at the collection. Tolerable for a one-time migration and left
182
+ * alone on that basis; it is O(records x files), so a workspace 3x larger pays 27 minutes. The fix
183
+ * when it is needed is a batch entry point on the store that reads each file ONCE and loops the ref
184
+ * set in memory, with `text.includes(oldName + '/')` as a cheap NEGATIVE filter only — never as the
185
+ * matcher, for the reason above.
146
186
  */
147
187
  export function renameCollection(ws, store, oldName, newName) {
148
188
  const d = store.descriptor(oldName); // throws with the known-collection list if absent
@@ -156,10 +196,21 @@ export function renameCollection(ws, store, oldName, newName) {
156
196
  if (store.descriptors.has(newName)) throw new Error(`collection "${newName}" already exists`);
157
197
  if (d.storage.base === 'runtime') throw new Error(`"${oldName}" is a compiled source, not a data collection — it cannot be renamed`);
158
198
 
159
- const src = path.join(workspaceSystemDir(ws, 'collections'), `${oldName}.collection.yaml`);
160
- const dest = path.join(workspaceSystemDir(ws, 'collections'), `${newName}.collection.yaml`);
199
+ // The descriptor is renamed IN THE MODULE THAT SHIPS IT — see `descriptorSourceDir`. Two cases
200
+ // this refuses, both because doing them halfway is worse than not doing them:
201
+ const { dir: sourceDir, sources } = descriptorSourceDir(ws, oldName);
202
+ if (sources.length > 1) {
203
+ throw new Error(`"${oldName}" is overlaid — ${sources.length} modules contribute a descriptor (${sources.join(', ')}).\n the overlay's \`extends\` names the base by its current id, so renaming the base alone would break it. merge or remove the overlay first.`);
204
+ }
205
+ if (sources.some((p) => p.split(path.sep).includes('node_modules'))) {
206
+ throw new Error(`"${oldName}" ships from node_modules (${sources[0]}) — a write there is erased by the next \`npm install\`. rename it in its own repo and release, or overlay it with \`extends\`.`);
207
+ }
208
+ const src = sourceDir
209
+ ? path.join(sourceDir, `${oldName}.collection.yaml`)
210
+ : path.join(workspaceSystemDir(ws, 'collections'), `${oldName}.collection.yaml`);
211
+ const dest = path.join(sourceDir ?? workspaceSystemDir(ws, 'collections'), `${newName}.collection.yaml`);
161
212
  if (!fs.existsSync(src)) {
162
- throw new Error(`"${oldName}" is not workspace-ownedit ships with a module, so rename it there (or overlay it with \`extends\`)`);
213
+ throw new Error(`"${oldName}" has no writable descriptor source the manifest names none under a module in this workspace. it may be contributed by the engine itself; overlay it with \`extends\` instead.`);
163
214
  }
164
215
 
165
216
  const doc = load(fs.readFileSync(src, 'utf8'));