dreamteamer 0.30.0 → 0.32.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 (43) hide show
  1. package/README.md +47 -153
  2. package/collections/collections.collection.yaml +26 -9
  3. package/package.json +14 -2
  4. package/skills/using-dreamteamer/SKILL.md +16 -10
  5. package/skills/using-dreamteamer/references/before-you-build.md +4 -3
  6. package/skills/using-dreamteamer/references/collections.md +71 -1
  7. package/skills/using-dreamteamer/references/commands.md +3 -3
  8. package/skills/using-dreamteamer/references/data-modeling.md +9 -1
  9. package/skills/using-dreamteamer/references/extensions.md +60 -0
  10. package/skills/using-dreamteamer/references/records.md +17 -0
  11. package/skills/using-dreamteamer/references/sessions.md +4 -5
  12. package/skills/using-dreamteamer/references/skills.md +3 -5
  13. package/src/api.d.ts +109 -0
  14. package/src/api.js +70 -0
  15. package/src/check.js +57 -2
  16. package/src/checkout.js +105 -330
  17. package/src/cli.js +90 -274
  18. package/src/collections-cli.js +16 -165
  19. package/src/commit.js +7 -0
  20. package/src/compile.js +211 -155
  21. package/src/events.js +7 -2
  22. package/src/extensions.js +135 -0
  23. package/src/filter.js +1 -1
  24. package/src/harnesses.js +82 -135
  25. package/src/init.js +9 -3
  26. package/src/placement.js +209 -0
  27. package/src/records-api.d.ts +103 -0
  28. package/src/records-api.js +40 -0
  29. package/src/runtime.js +2 -2
  30. package/src/schema-ops.js +26 -9
  31. package/src/store.js +396 -35
  32. package/collections/containers.collection.yaml +0 -81
  33. package/collections/images.collection.yaml +0 -46
  34. package/collections/proofs.collection.yaml +0 -96
  35. package/skills/using-dreamteamer/references/exporting.md +0 -53
  36. package/skills/using-dreamteamer/references/proofs.md +0 -435
  37. package/skills/using-dreamteamer/references/worktrees.md +0 -235
  38. package/src/container-archive.js +0 -356
  39. package/src/containers.js +0 -635
  40. package/src/export-notebooklm.js +0 -502
  41. package/src/land.js +0 -743
  42. package/src/prove.js +0 -1922
  43. package/src/server.js +0 -481
@@ -6,7 +6,7 @@ refusals that keep that from looping, lying, or carrying private data somewhere
6
6
 
7
7
  The whole design turns on one asymmetry. Two sessions in the same tree are conflict-BLIND, and so are
8
8
  two sessions in one conversation: **the second write wins and nobody is told.** Sessions are
9
- `worktrees`' twin — observed, never stored, and the registry is the authority rather than anyone's
9
+ git worktrees' twin — observed, never stored, and the registry is the authority rather than anyone's
10
10
  memory of it.
11
11
 
12
12
  | the question | read |
@@ -175,10 +175,9 @@ A coordinator spans repos by construction, and one of them may publish.
175
175
  records it may not message a publishing session at all, whatever it means to say. This is what
176
176
  keeps rule 1 safe after compaction, when it can no longer recall precisely what it read.
177
177
  6. ⚠ **`cwd` is not the repo, and one repo is not one tree.** Harnesses put worktrees *inside* the
178
- repo or *under the home directory* depending on the harness (`references/worktrees.md`). A `cwd`
179
- under a harness's own worktree root is still that repo and carries its full boundary — so a
180
- path-prefix test against the primary root gets it wrong in the dangerous direction. `dt list
181
- worktrees` is the instrument.
178
+ repo or *under the home directory* depending on the harness. A `cwd` under a harness's own
179
+ worktree root is still that repo and carries its full boundary — so a path-prefix test against
180
+ the primary root gets it wrong in the dangerous direction. `git worktree list` is the instrument.
182
181
 
183
182
  ## not stepping on your own toes
184
183
 
@@ -160,12 +160,10 @@ Skills ship with modules and are read by any operator on any machine:
160
160
  renamed verb or a changed limit in a skill sends every future session down the old path
161
161
  confidently. When you catch a skill lying, fixing it is part of the task you are on, not a
162
162
  follow-up.
163
- - **Give it a proof, and know what a proof cannot cover.** A `proofs` record (`proofs.md`) pins the
163
+ - **Give it a mechanical check, and know what one cannot cover.** Something must pin the
164
164
  mechanical half: the script the skill names runs, the record it promises appears, the path it
165
- files to exists. Write it in the same commit — `dt add skills` nudges you with the path the moment
166
- it writes the skill (compile's own nudge covers new commands and scripts, never skills), and
167
- `dt list proofs --missing` names every artifact nobody claimed anything about. ⚠ **A
168
- green proof is not evidence the skill TEACHES.** Whether a fresh session finds it, loads it and
165
+ files to exists — in a workspace with a proofs extension, that is a `proofs` record, written
166
+ in the same commit. ⚠ **A green check is not evidence the skill TEACHES.** Whether a fresh session finds it, loads it and
169
167
  does the job right is the eval layer: real tasks, blind sessions, a scoring sheet — a procedure,
170
168
  never something the engine runs.
171
169
  - **Retire what nothing loads.** A skill nobody uses still costs its line in every session's index.
package/src/api.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ // The typed contract of `import … from 'dreamteamer'` (src/api.js). It re-exports the record half
2
+ // (`dreamteamer/records`, src/records-api.d.ts) and adds the workspace half. A test pins each runtime
3
+ // export list to its declaration, so a name added to one and not the other fails the suite.
4
+
5
+ export * from './records-api.js';
6
+ import type { Fields, Descriptor, Descriptors, Manifest, Store } from './records-api.js';
7
+
8
+ // ---- the workspace and extensions ------------------------------------------------------------------
9
+
10
+ export interface Workspace {
11
+ /** absolute path of the workspace root */
12
+ root: string;
13
+ /** the workspace package.json */
14
+ pkg: { name?: string; dependencies?: Record<string, string>; devDependencies?: Record<string, string>; dreamteamer?: Record<string, any>; [k: string]: unknown };
15
+ /** the ACTIVATED extensions, present on a handle from `openWorkspace` */
16
+ extensions?: LoadedExtension[];
17
+ }
18
+
19
+ /** What an extension's `activate(dt)` returns. Every key is optional. */
20
+ export interface Contribution {
21
+ commands?: Record<string, { usage?: string; run(ws: Workspace, argv: string[]): number | void | Promise<number | void> }>;
22
+ sourceKinds?: (string | { kind: string; exclude?: string[] })[];
23
+ analyze?(draft: CompileDraft): { errors?: string[]; warnings?: string[]; notes?: string[] } | void;
24
+ harnesses?: Record<string, (ctx: HarnessContext) => { blocks?: Record<string, string | null>; summary?: string } | void>;
25
+ orientation?: string | ((ctx: { entries: Map<string, Entry> }) => string);
26
+ hooks?: Record<string, string>;
27
+ }
28
+ export type Activate = (dt: typeof import('./api.js')) => Contribution | Promise<Contribution>;
29
+ export interface LoadedExtension {
30
+ name: string;
31
+ version: string;
32
+ commands: NonNullable<Contribution['commands']>;
33
+ sourceKinds: { kind: string; exclude: string[]; extension: string }[];
34
+ analyze: Contribution['analyze'] | null;
35
+ harnesses: NonNullable<Contribution['harnesses']>;
36
+ orientation: Contribution['orientation'] | null;
37
+ hooks: Record<string, string>;
38
+ }
39
+ export type Entry = { sources: { path: string; hash: string }[]; bytes: Buffer };
40
+ export interface CompileDraft {
41
+ /** runtime-relative path → staged entry; read-only */
42
+ readonly entries: Map<string, Entry>;
43
+ /** the FINAL merged descriptors */
44
+ readonly descriptors: Descriptors;
45
+ readonly modules: { id: string; name: string; root: string; channel: 'inline' | 'git' | 'npm' }[];
46
+ /** names only — never values */
47
+ readonly declaredVars: string[];
48
+ readonly declaredEnv: string[];
49
+ readonly previousManifest: Manifest | null;
50
+ /** parse one staged YAML entry, with its source path in any error */
51
+ parse(runtimePath: string): any;
52
+ }
53
+ export interface HarnessContext {
54
+ entries: Map<string, Entry>;
55
+ version: string;
56
+ collections: { name: string; generated: boolean; systemGroup: boolean; description: string; module: string; sensitive: boolean; sensitiveFields: string[] }[];
57
+ modules: { id: string; title: string; description: string; path: string }[];
58
+ }
59
+
60
+ export const EXTENSION_API: 1;
61
+ export function openWorkspace(start?: string): Promise<Workspace & { extensions: LoadedExtension[] }>;
62
+ export function findWorkspace(start?: string): Workspace;
63
+ export function declaredExtensions(ws: Workspace): { name: string; version: string; dir: string; entry: string }[];
64
+ export const engineBin: string;
65
+ export const engineRoot: string;
66
+
67
+ export function envContext(ws: Workspace): unknown;
68
+ export function renderTemplate(template: string, ctx: unknown): string;
69
+ export function parseEnvValues(text: string): Map<string, string>;
70
+ export function satisfies(version: string, range: string): boolean | null;
71
+
72
+ // ---- the compiler, schema and module operations --------------------------------------------------
73
+
74
+ /** throws CompileError on a bad source; prints its summary; returns 0 */
75
+ export function compile(ws: Workspace): 0;
76
+ export function staleness(root: string): { compiled: boolean; stale: string[]; manifest?: Manifest; message?: string };
77
+ export function warnIfStale(root: string): ReturnType<typeof staleness>;
78
+ export function discoverModules(root: string, pkg: Workspace['pkg']): { modules: { name: string; root: string; channel: string }[]; shadows: unknown[]; disabledModules: string[] };
79
+ export class CompileError extends Error {}
80
+ export const KINDS: readonly string[];
81
+ export const MANAGED_BLOCKS: readonly { id: 'orientation' | 'instructions'; begin: string; end: string }[];
82
+ type SchemaOp = (ws: Workspace, store: Store, ...args: any[]) => any;
83
+ export const createCollection: SchemaOp, removeCollection: SchemaOp, renameCollection: SchemaOp, moveCollection: SchemaOp, setCollectionScalars: SchemaOp;
84
+ export const addField: SchemaOp, updateField: SchemaOp, removeField: SchemaOp, removeFieldPlan: SchemaOp, renameField: SchemaOp, renameFieldPlan: SchemaOp;
85
+ export function fieldDef(store: Store, flags: Record<string, unknown>, collection: string): any;
86
+ export function statedKeywords(flags: Record<string, unknown>): any;
87
+ export const saveUiView: SchemaOp, removeUiView: SchemaOp;
88
+ export const createModule: SchemaOp, setModule: SchemaOp, renameModule: SchemaOp, removeModule: SchemaOp;
89
+ export const createSkill: SchemaOp, refuseHandAuthored: SchemaOp, removeEntity: SchemaOp, renameEntity: SchemaOp, setEntityFrontmatter: SchemaOp;
90
+ export function init(opts?: { flags?: Record<string, string> }): 0;
91
+ export function ensureRepo(ws: Workspace, id: string): { path: string; cloned: boolean };
92
+ export function ensureAllRepos(ws: Workspace): { path: string; cloned: boolean }[];
93
+ export function listRepos(ws: Workspace): { id: string; path: string; present: boolean; unresolved?: string }[];
94
+ export function installClone(ws: Workspace, url: string, name?: string): number;
95
+
96
+ // ---- the checkout -------------------------------------------------------------------------------------
97
+
98
+ export function describeCheckout(root: string, git?: (args: string[], cwd: string) => string): { root: string; kind: 'primary' | 'linked'; gitDir: string; commonDir: string; primary: string; insideRoot: boolean };
99
+ export function installCommand(ws: Workspace, argv: string[], opts?: { open?: (at: string) => Promise<Workspace> }): Promise<number>;
100
+ export function resolveNpm(execPath?: string, env?: NodeJS.ProcessEnv): string | null;
101
+ export function childEnv(): NodeJS.ProcessEnv;
102
+ export function readHookInput(stdinText: string): { cwd: string | null; name: string | null; raw: Record<string, unknown> };
103
+ export function readStdin(isTTY?: boolean): string;
104
+
105
+ // ---- CLI helpers ------------------------------------------------------------------------------------------
106
+
107
+ /** write `text` plus a trailing newline, synchronously, looping on short writes — safe before process.exit */
108
+ export function emit(text: string, fd?: number): void;
109
+ export function parseArgs(argv: string[]): { flags: Record<string, string | boolean | string[]>; pos: string[] };
package/src/api.js ADDED
@@ -0,0 +1,70 @@
1
+ // The PUBLIC API — `import … from 'dreamteamer'`. Everything a surface (the VS Code extension, the
2
+ // mobile app) or an extension (http, workflows, notebooklm) may call, and nothing else: package.json
3
+ // `exports` hides `src/*`, so an internal file can be split, renamed or deleted without a cross-repo
4
+ // break. That break used to be the rule — the extension imported fifteen internal files by path, and
5
+ // deleting one took `activate()` down before the tree view existed.
6
+ //
7
+ // Grouped by task. Each name is the ONE implementation of its operation; nothing here wraps or
8
+ // re-implements. `api.d.ts` beside this file is the typed contract, and a test pins the two together.
9
+ //
10
+ // Importing this module prints nothing, writes nothing, binds no port and touches no network.
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import * as self from './api.js';
14
+ import { findWorkspace } from './workspace.js';
15
+ import { loadExtensions } from './extensions.js';
16
+ import { KINDS } from './compile.js';
17
+ import { DERIVED_KINDS } from './runtime.js';
18
+ import { KNOWN_HARNESSES } from './harnesses.js';
19
+ import { CORE_VERBS } from './cli.js';
20
+ import { installCommand as installCheckout } from './checkout.js';
21
+
22
+ // Everything the record half exports (the browser-safe entry, `dreamteamer/records`), then the rest.
23
+ export * from './records-api.js';
24
+
25
+ // ---- the workspace ------------------------------------------------------------------------------
26
+
27
+ /**
28
+ * The workspace at (or above) `start`, with its declared extensions ACTIVATED against this engine.
29
+ * The handle every other call takes: `{ root, pkg, extensions }`. Performs no compile, install,
30
+ * network call or write, and never changes the process cwd.
31
+ */
32
+ export async function openWorkspace(start = process.cwd()) {
33
+ const ws = findWorkspace(start);
34
+ ws.extensions = await loadExtensions(ws, self, { verbs: CORE_VERBS, kinds: [...KINDS, ...DERIVED_KINDS], harnesses: KNOWN_HARNESSES });
35
+ return ws;
36
+ }
37
+ export { findWorkspace };
38
+ export { EXTENSION_API, declaredExtensions } from './extensions.js';
39
+
40
+ /** The engine's own CLI entry — what a tool spawns to run `dt` in another checkout. */
41
+ export const engineBin = fileURLToPath(new URL('../bin/dreamteamer.js', import.meta.url));
42
+ export const engineRoot = path.dirname(path.dirname(engineBin));
43
+
44
+ // ---- values the workspace half adds ---------------------------------------------------------------
45
+ export { envContext, renderTemplate, parseEnvValues } from './env-vars.js';
46
+ export { satisfies } from './semver.js';
47
+
48
+ // ---- the compiler, schema and module operations ---------------------------------------------------
49
+ export { compile, staleness, warnIfStale, discoverModules, CompileError, KINDS } from './compile.js';
50
+ export { MANAGED_BLOCKS } from './harnesses.js';
51
+ export {
52
+ createCollection, removeCollection, renameCollection, moveCollection, setCollectionScalars,
53
+ addField, updateField, removeField, removeFieldPlan, renameField, renameFieldPlan, fieldDef, statedKeywords,
54
+ saveUiView, removeUiView,
55
+ createModule, setModule, renameModule, removeModule,
56
+ createSkill, refuseHandAuthored, removeEntity, renameEntity, setEntityFrontmatter,
57
+ } from './schema-ops.js';
58
+ export { init, ensureRepo, ensureAllRepos, listRepos, installClone } from './init.js';
59
+
60
+ // ---- the checkout: which one this is, and how it becomes ready ---------------------------------
61
+ export { describeCheckout, resolveNpm, childEnv, readHookInput, readStdin } from './checkout.js';
62
+
63
+ /** `dt install` on the checkout `ws`, as the CLI runs it. The compile step reopens the workspace with
64
+ * `opts.open` — `openWorkspace` unless a caller injects another — so the extensions npm just
65
+ * installed take part in that compile. The one name here that supplies a default rather than
66
+ * re-exporting: the checkout layer cannot import the opener that activates extensions. */
67
+ export const installCommand = (ws, argv, opts = {}) => installCheckout(ws, argv, { open: openWorkspace, ...opts });
68
+
69
+ // ---- CLI helpers an extension command reuses -----------------------------------------------------
70
+ export { emit, parseArgs } from './collections-cli.js';
package/src/check.js CHANGED
@@ -10,6 +10,7 @@ import { NO_RUNTIME, loadDescriptors, runtimeDir, namespaces as compiledNamespac
10
10
  import { parseRef } from './namespace.js';
11
11
  import { refTargetsOf, refIsSoft } from './ref.js';
12
12
  import { relationsOf, expectedMirrors } from './relations.js';
13
+ import { placementOf, placedRecords, ownerIdOf, symlinkedChildRoots } from './placement.js';
13
14
 
14
15
  export function check({ root }) {
15
16
  const RUNTIME = runtimeDir(root);
@@ -34,16 +35,19 @@ export function check({ root }) {
34
35
 
35
36
  // ---- index all records: collection -> Map<id, filePath> ------------------------
36
37
  const index = new Map();
38
+ // placed collections only: id -> the parent id its file was FOUND under (null = the fallback root)
39
+ const observed = new Map();
37
40
  const strays = [];
38
41
  // declared here rather than beside the validation pass: indexing can itself produce a
39
42
  // finding (an unreachable data root, below) before a single record is read.
40
43
  const violations = [];
44
+ const dirOf = (d) => path.join(d.storage.base === 'runtime' ? RUNTIME : root, d.storage.path);
41
45
  for (const [name, d] of descriptors) {
42
46
  const ids = new Map();
43
47
  index.set(name, ids);
44
48
  // runtime-based (knowhow/meta) collections are read from the COMPILED runtime —
45
49
  // their sources may live in any module; .dreamteamer is the merged read surface
46
- const dir = path.join(d.storage.base === 'runtime' ? RUNTIME : root, d.storage.path);
50
+ const dir = dirOf(d);
47
51
  // An unreachable data ROOT is a finding, not a skip: a collection whose module clone is
48
52
  // missing otherwise reports zero records and a clean check — a silent success. An EMPTY
49
53
  // directory stays fine (a module with no records yet is normal); only a missing owning
@@ -53,13 +57,46 @@ export function check({ root }) {
53
57
  violations.push({ file: d.storage.path, msg: `collection "${name}" is owned by ${d.storage.repo}, which is not present — every record in it is unreadable` });
54
58
  continue;
55
59
  }
60
+ const under = placementOf(d);
61
+ if (under) {
62
+ // ONE logical collection across the fallback root and every parent folder — the same walk
63
+ // the store indexes with (src/placement.js), so the two cannot disagree about which files
64
+ // are records. The first file to claim an id keeps it; every later one is a violation,
65
+ // because a `get` that silently answered from whichever folder sorted later is the
66
+ // failure this report exists to make visible.
67
+ const seen = new Map();
68
+ observed.set(name, seen);
69
+ const parentDir = dirOf(descriptors.get(under.collection));
70
+ // a child root that is (or sits behind) a symlink is not read — whatever it points at is not
71
+ // this parent's folder — and it is named here rather than silently skipped
72
+ for (const link of symlinkedChildRoots(under, parentDir)) {
73
+ violations.push({ file: rel(link), msg: `is a symlink — ${name} records are read only from real folders inside ${under.collection} records; whatever this points at is not indexed. Replace it with a real folder.` });
74
+ }
75
+ const onLink = (p) => violations.push({ file: rel(p), msg: `is a symlink inside a ${under.collection} record's folder — nothing behind it is read as a ${name} record, and nothing is written through it. Replace it with a real folder or file.` });
76
+ for (const r of placedRecords(d, dir, parentDir, onLink)) {
77
+ if (ids.has(r.id)) {
78
+ violations.push({ file: rel(r.file), msg: `collection "${name}" holds the id "${r.id}" twice — ${rel(ids.get(r.id))} and ${rel(r.file)}. Remove one.` });
79
+ continue;
80
+ }
81
+ ids.set(r.id, r.file);
82
+ seen.set(r.id, r.parentId);
83
+ }
84
+ continue;
85
+ }
56
86
  if (!fs.existsSync(dir)) continue;
57
87
  const shape = d.storage.shape ?? 'file';
58
88
  if (shape === 'folder') {
89
+ // a FILE record at the root of a folder-shape collection is the state a collection is in
90
+ // right after its shape changed — named as such, with the verb that finishes the change
91
+ const asFile = { ...d, storage: { ...d.storage, shape: 'file' } };
59
92
  for (const entry of fs.readdirSync(dir).sort()) {
60
93
  if (entry.startsWith('.')) continue;
61
94
  const p = path.join(dir, entry);
62
- if (!fs.statSync(p).isDirectory()) { strays.push({ collection: name, file: rel(p) }); continue; }
95
+ if (!fs.statSync(p).isDirectory()) {
96
+ const legacy = idFromRecordPath(asFile, entry) !== null;
97
+ strays.push({ collection: name, file: rel(p), note: legacy ? `a file-shape record in a folder-shape collection — dreamteamer relocate ${name} moves it to ${entry.split('.')[0]}/${d.storage.entry}` : undefined });
98
+ continue;
99
+ }
63
100
  const main = path.join(p, d.storage.entry ?? 'SKILL.md');
64
101
  if (fs.existsSync(main)) ids.set(entry, main);
65
102
  else strays.push({ collection: name, file: rel(p), note: `missing entry file ${d.storage.entry}` });
@@ -127,6 +164,24 @@ export function check({ root }) {
127
164
  checkRef(file, fieldPath, value, target, softTargets, soft);
128
165
  }
129
166
  }
167
+ // ---- placement: the FIELD is the intended owner, the FOLDER is observed placement ---
168
+ // Reported, never repaired: a record found under the wrong company is either a hand move
169
+ // (the field is right, run relocate) or a hand edit of the field (the folder is right, set
170
+ // it back) and only a person knows which. A malformed owner value is skipped here — the
171
+ // reference check above has already named it, and "placed under X but owner is empty"
172
+ // on top of that would be a second report of one typo.
173
+ const under = placementOf(d);
174
+ if (under) {
175
+ const raw = fields[under.field];
176
+ const wellFormed = raw == null || raw === '' || parseRef(raw, namespaces);
177
+ const want = ownerIdOf(fields, under, (v) => parseRef(v, namespaces));
178
+ const got = observed.get(name).get(id);
179
+ if (wellFormed && want !== got) {
180
+ const where = got ? `under ${under.collection}/${got}` : `in its own root (${d.storage.path})`;
181
+ const should = want ? `${under.field} is ${under.collection}/${want}` : `${under.field} is empty`;
182
+ flag(file, `placed ${where} but ${should} — the file is not where its owner puts it. Run: dreamteamer relocate ${name}/${id}`);
183
+ }
184
+ }
130
185
  parsed.get(name).set(id, fields);
131
186
  }
132
187
  }