dreamteamer 0.22.0 → 0.23.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/package.json +2 -2
- package/src/cli.js +1 -0
- package/src/harnesses.js +101 -1
- package/src/land.js +8 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dreamteamer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A workspace compiler for coding agents
|
|
3
|
+
"version": "0.23.0",
|
|
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>",
|
|
7
7
|
"homepage": "https://github.com/dreamteamer/dreamteamer#readme",
|
package/src/cli.js
CHANGED
|
@@ -165,6 +165,7 @@ Every verb that MOVES records or CLEARS values takes --dry-run and prints its pl
|
|
|
165
165
|
|
|
166
166
|
workspace verbs:
|
|
167
167
|
init write the workspace skeleton into the current directory (never compiles)
|
|
168
|
+
[--harnesses claude-code,codex,pi,gemini-cli,cursor,notebooklm]
|
|
168
169
|
--version print the engine version (works anywhere)
|
|
169
170
|
install make THIS checkout ready — the engine, .env (linked from the primary when this is a
|
|
170
171
|
worktree), declared local assets, git modules, compile, and a declared postinstall.
|
package/src/harnesses.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { load, dump } from './yaml.js';
|
|
16
16
|
|
|
17
|
-
export const KNOWN_HARNESSES = ['claude-code', 'codex', 'pi', 'gemini-cli', 'cursor'];
|
|
17
|
+
export const KNOWN_HARNESSES = ['claude-code', 'codex', 'pi', 'gemini-cli', 'cursor', 'notebooklm'];
|
|
18
18
|
|
|
19
19
|
export const STAMP = '<!-- generated by dreamteamer compile — do not edit; source of truth lives in modules/<module>/<kind>/ -->';
|
|
20
20
|
|
|
@@ -99,6 +99,14 @@ export function runHarnessAdapters({ root, entries, harnesses, prevManifest, sou
|
|
|
99
99
|
summary.push('cursor → .cursor/rules/dreamteamer.mdc');
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// ---- notebooklm: NOTEBOOKLM.md carries the notebook's CONFIGURATION ---------------
|
|
103
|
+
// Not an orientation block: NotebookLM does not read a context file and has no skills. What it
|
|
104
|
+
// needs is a persona to paste, a response length to pick, and the limits that decide how the
|
|
105
|
+
// workspace has to be cut up — and all three go stale as collections come and go, which is why
|
|
106
|
+
// this is generated on compile rather than written once by hand.
|
|
107
|
+
block('NOTEBOOKLM.md', on('notebooklm') ? notebooklmBlock(entries, version) : null);
|
|
108
|
+
if (on('notebooklm')) summary.push('notebooklm → NOTEBOOKLM.md block');
|
|
109
|
+
|
|
102
110
|
// ---- prune: anything WE stamped that this compile didn't produce ------------------
|
|
103
111
|
const current = new Set(outputs);
|
|
104
112
|
for (const dir of ['.claude/skills', '.claude/agents', '.claude/commands', '.agents/skills', '.cursor/rules']) {
|
|
@@ -119,6 +127,94 @@ export function runHarnessAdapters({ root, entries, harnesses, prevManifest, sou
|
|
|
119
127
|
return { outputs, blocks, summary };
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
/** Sources a notebook holds, per plan — Google's published table, read 2026-09. Stated here because
|
|
131
|
+
* the number decides the SHAPE of the export: a workspace with more collections than the plan has
|
|
132
|
+
* slots has to be narrowed or packed, and that is a decision the operator makes before running
|
|
133
|
+
* anything. */
|
|
134
|
+
const NOTEBOOK_PLANS = [
|
|
135
|
+
['standard', 50, 50], ['plus', 100, 200], ['pro', 300, 500], ['ultra', 600, 2500],
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
/** The notebook's configuration, generated: what to paste as custom instructions, which settings to
|
|
139
|
+
* pick, and the limits that bind. Derived from the compiled DESCRIPTORS only — never from `data/`,
|
|
140
|
+
* for the same reason the orientation block carries no record counts: this lands in a committed file
|
|
141
|
+
* and a count would re-dirty it on every write. */
|
|
142
|
+
function notebooklmBlock(entries, version) {
|
|
143
|
+
const index = buildCollectionsIndex(entries);
|
|
144
|
+
const modules = buildModulesIndex(entries);
|
|
145
|
+
const data = index.filter((c) => !c.system);
|
|
146
|
+
const withheldCollections = data.filter((c) => c.sensitive).map((c) => c.name);
|
|
147
|
+
const withheldFields = data.flatMap((c) => c.sensitiveFields.map((f) => `${c.name}.${f}`));
|
|
148
|
+
const exported = data.filter((c) => !c.sensitive);
|
|
149
|
+
|
|
150
|
+
// grouped by module from the COLLECTIONS index, which carries each collection's owning module —
|
|
151
|
+
// `buildModulesIndex` deliberately reports skills, commands and bin, not collections
|
|
152
|
+
const title = new Map(modules.map((m) => [m.id, m.title]));
|
|
153
|
+
const byModule = new Map();
|
|
154
|
+
for (const c of data) {
|
|
155
|
+
const k = c.module || '';
|
|
156
|
+
if (!byModule.has(k)) byModule.set(k, []);
|
|
157
|
+
byModule.get(k).push(c.name);
|
|
158
|
+
}
|
|
159
|
+
const brief = [...byModule]
|
|
160
|
+
.sort((a, b) => (title.get(a[0]) || a[0]).localeCompare(title.get(b[0]) || b[0]))
|
|
161
|
+
.map(([mod, names]) => `- ${title.get(mod) || mod || 'the workspace'}: ${names.sort().map((c) => (withheldCollections.includes(c) ? `${c} (withheld)` : c)).join(', ')}`);
|
|
162
|
+
|
|
163
|
+
const persona = [
|
|
164
|
+
'You are the reference desk for this workspace. Every source is ONE COLLECTION of it, and every row or section is one record. Answer only from the sources.',
|
|
165
|
+
'',
|
|
166
|
+
'A record is cited as `<collection>/<id>`. A field holding a reference contains exactly that form, so `leads.company = companies/acme` means the record `acme` in the `companies` source — follow those links across sources rather than guessing. A source whose name ends `--01`, `--02` is one collection split across files purely for size; treat the parts as one collection.',
|
|
167
|
+
'',
|
|
168
|
+
'What this workspace keeps, by module:',
|
|
169
|
+
...brief,
|
|
170
|
+
'',
|
|
171
|
+
withheldCollections.length || withheldFields.length
|
|
172
|
+
? `Deliberately NOT here: ${[...withheldCollections.map((c) => `the whole collection ${c}`), ...withheldFields].join('; ')}. If asked about them, say they were withheld from the export rather than inferring.`
|
|
173
|
+
: 'Nothing has been withheld from this export.',
|
|
174
|
+
'',
|
|
175
|
+
'Ground every claim in a record and name it. When the question is a table question — which rows match, what is overdue, everything about one company — read down the column and give the matching records, not a summary. When the sources do not hold the answer, say so plainly: "this workspace does not record that" is a useful answer.',
|
|
176
|
+
].join('\n');
|
|
177
|
+
|
|
178
|
+
const lines = [
|
|
179
|
+
'NOTEBOOKLM — the configuration for a notebook over this workspace. NotebookLM reads no context',
|
|
180
|
+
'file and runs no skills, so unlike the other harnesses nothing here is injected: it is applied,',
|
|
181
|
+
`once per notebook, and re-pasted when the schema changes. Regenerated by every \`dreamteamer compile\` (engine ${version}).`,
|
|
182
|
+
'',
|
|
183
|
+
'## settings',
|
|
184
|
+
'',
|
|
185
|
+
'| setting | value | why |',
|
|
186
|
+
'|---|---|---|',
|
|
187
|
+
'| response length | **longer** | records are terse; the useful answer quotes several and says which |',
|
|
188
|
+
'| chat mode | **do not set one** | `--mode` REPLACES the persona — see the warning below |',
|
|
189
|
+
'',
|
|
190
|
+
'```bash',
|
|
191
|
+
'notebooklm configure -n <notebook-id> --response-length longer \\',
|
|
192
|
+
' --persona "$(sed -n \'/^## custom instructions/,/^## limits/p\' NOTEBOOKLM.md | sed \'1,2d;$d\')"',
|
|
193
|
+
'```',
|
|
194
|
+
'',
|
|
195
|
+
'⚠ **`configure` REPLACES the whole configuration on every call, and passing `--mode` with a persona silently discards the persona.** Measured: `--persona … --response-length longer --mode default` answers `{mode: "default", configured: true}` at exit 0, with the persona and the response length gone; the identical call without `--mode` returns both. There is also no read-only inspection — a bare `configure`, the obvious way to check the current settings, CLEARS them. So send every setting you want in ONE call, never `--mode` alongside a persona, and keep the persona in a file rather than only in the notebook.',
|
|
196
|
+
'',
|
|
197
|
+
'## custom instructions',
|
|
198
|
+
'',
|
|
199
|
+
persona,
|
|
200
|
+
'',
|
|
201
|
+
'## limits',
|
|
202
|
+
'',
|
|
203
|
+
'| plan | sources per notebook | chats per day |',
|
|
204
|
+
'|---|---|---|',
|
|
205
|
+
...NOTEBOOK_PLANS.map(([n, s, c]) => `| ${n} | ${s} | ${c} |`),
|
|
206
|
+
'',
|
|
207
|
+
`This workspace exports **${exported.length} collections**${withheldCollections.length ? ` (${withheldCollections.length} withheld)` : ''}, so one source per collection needs a plan with at least that many slots — before any collection is split.`,
|
|
208
|
+
'',
|
|
209
|
+
'- **Per source:** 500,000 words or 200 MB, per Google. ⚠ A CSV source fails well below that: measured on a large private workspace, files at or under 805,081 bytes indexed and files at or above 881,828 bytes did not, so 700,000 bytes is the working ceiling.',
|
|
210
|
+
'- **Persona:** 10,000 characters. The block above is generated to stay inside it; adding to it by hand can push it over, and `configure` refuses the whole call rather than truncating.',
|
|
211
|
+
'- **Auto-sync follows native Google Docs, Sheets and Slides only.** An uploaded file — CSV, PDF, Markdown — is a SNAPSHOT: re-export means re-adding those sources.',
|
|
212
|
+
'- ⚠ **`source add` reports success whether or not the file indexes.** Read `source list --json` back and require `type` to be the format you uploaded; a source that failed reads `type: unknown, status: error` with no message anywhere.',
|
|
213
|
+
'- ⚠ **An answer is real only with non-blank text AND at least one reference.** Exit 0 with an empty answer is a real outcome, reached by scoping a broad question with `-s`.',
|
|
214
|
+
];
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
122
218
|
// skill id → description one-liners from each SKILL.md's frontmatter; the orientation
|
|
123
219
|
// block carries this index so harnesses without native skill discovery still get triggers.
|
|
124
220
|
function buildSkillsIndex(entries) {
|
|
@@ -156,6 +252,10 @@ function buildCollectionsIndex(entries) {
|
|
|
156
252
|
description: flat(d.description),
|
|
157
253
|
useWhen: flat(d.use_when),
|
|
158
254
|
module: d.module ?? '',
|
|
255
|
+
// the sensitivity marks, so a generated persona can NAME what it was not given —
|
|
256
|
+
// "withheld" is a better answer than "I don't know"
|
|
257
|
+
sensitive: d.sensitive === true,
|
|
258
|
+
sensitiveFields: Object.entries(d.schema?.properties ?? {}).filter(([, p]) => p?.['x-sensitive'] === true).map(([k]) => k),
|
|
159
259
|
write: writeLine(d.schema),
|
|
160
260
|
});
|
|
161
261
|
}
|
package/src/land.js
CHANGED
|
@@ -43,12 +43,19 @@ export const LAND_LOCK = 'dreamteamer-land.lock';
|
|
|
43
43
|
* compile never touches one: matching on the basename would let a nested file that happens to carry
|
|
44
44
|
* a copied block be resolved `--ours` and discarded silently (ruling R32).
|
|
45
45
|
*
|
|
46
|
+
* ⚠ THIS LIST DRIFTS THE MOMENT A HARNESS IS ADDED ON A DIFFERENT BRANCH, and it did:
|
|
47
|
+
* `NOTEBOOKLM.md` was written by a harness developed in parallel with `land`, so neither branch's
|
|
48
|
+
* suite could see the gap and both were green. Landed together, `dt land` classified a conflict in
|
|
49
|
+
* that file as `other` — a foreign file that ABORTS the landing — instead of a managed block it can
|
|
50
|
+
* simply regenerate. The unit test below derives this list from `harnesses.js` itself for exactly
|
|
51
|
+
* this reason, and it is what caught it; keep it that way rather than asserting a literal.
|
|
52
|
+
*
|
|
46
53
|
* ⚠ `.cursor/rules/dreamteamer.mdc` is deliberately NOT here (ruling R33). Cursor's output is a
|
|
47
54
|
* WHOLE generated file — frontmatter, body and STAMP, no begin/end markers (harnesses.js:97) — so
|
|
48
55
|
* there is no block to take `--ours` on, and `init` gitignores `.cursor/` anyway. A conflict there
|
|
49
56
|
* is an ordinary non-records conflict and aborts the landing like any other.
|
|
50
57
|
*/
|
|
51
|
-
export const MANAGED_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
|
|
58
|
+
export const MANAGED_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', 'NOTEBOOKLM.md'];
|
|
52
59
|
|
|
53
60
|
/** Does compile write a managed block into this exact path? Task C's per-commit loop asks too. */
|
|
54
61
|
export const isManaged = (filePath) => MANAGED_FILES.includes(filePath);
|