dreamteamer 0.20.0 → 0.22.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/bin/dt-hook.sh +54 -0
- package/collections/collections.collection.yaml +9 -0
- package/collections/command-bindings.collection.yaml +1 -1
- package/collections/proofs.collection.yaml +96 -0
- package/collections/repos.collection.yaml +4 -3
- package/package.json +2 -2
- package/skills/using-dreamteamer/SKILL.md +16 -7
- package/skills/using-dreamteamer/references/before-you-build.md +16 -2
- package/skills/using-dreamteamer/references/collections.md +50 -9
- package/skills/using-dreamteamer/references/commands.md +16 -3
- package/skills/using-dreamteamer/references/data-modeling.md +2 -2
- package/skills/using-dreamteamer/references/exporting.md +53 -0
- package/skills/using-dreamteamer/references/proofs.md +435 -0
- package/skills/using-dreamteamer/references/skills.md +8 -0
- package/skills/using-dreamteamer/references/worktrees.md +235 -0
- package/src/checkout.js +666 -0
- package/src/cli.js +301 -40
- package/src/collections-cli.js +237 -63
- package/src/commit.js +1 -1
- package/src/compile.js +190 -15
- package/src/export-notebooklm.js +502 -0
- package/src/filter.js +4 -1
- package/src/harnesses.js +25 -10
- package/src/init.js +2 -0
- package/src/land.js +736 -0
- package/src/presentation.js +28 -1
- package/src/prove.js +1922 -0
- package/src/runtime.js +26 -0
- package/src/schema-ops.js +48 -25
package/src/cli.js
CHANGED
|
@@ -15,13 +15,17 @@ import { execFileSync } from 'node:child_process';
|
|
|
15
15
|
import { findWorkspace } from './workspace.js';
|
|
16
16
|
import { compile, staleness, warnIfStale, discoverModules, CHANNEL_LABEL, locationOf, KINDS } from './compile.js';
|
|
17
17
|
import { check } from './check.js';
|
|
18
|
-
import { collectionCommand, emit, relationsCommand } from './collections-cli.js';
|
|
19
|
-
import { init,
|
|
18
|
+
import { collectionCommand, emit, relationsCommand, parseArgs, refuseUnknownFlags } from './collections-cli.js';
|
|
19
|
+
import { init, installClone, update, listRepos } from './init.js';
|
|
20
|
+
import { installCommand, describeCheckout, listWorktrees, worktreeCommand } from './checkout.js';
|
|
21
|
+
import { proveCommand, readLedger, flagEnabled } from './prove.js';
|
|
22
|
+
import { landCommand } from './land.js';
|
|
20
23
|
import { deriveEvents } from './events.js';
|
|
21
24
|
import { commitPending } from './commit.js';
|
|
22
25
|
import { Store } from './store.js';
|
|
23
26
|
import { splitRef } from './ref.js';
|
|
24
27
|
import { envContext, renderTemplate } from './env-vars.js';
|
|
28
|
+
import { exportCommand, EXPORT_FLAGS } from './export-notebooklm.js';
|
|
25
29
|
|
|
26
30
|
// git calls whose failure we CATCH must not print git's own error: execFileSync forwards the
|
|
27
31
|
// child's stderr to ours unless told otherwise, so a handled "not a git repository" still
|
|
@@ -35,6 +39,10 @@ record verbs (hard validation — invalid writes are rejected before disk).
|
|
|
35
39
|
A <target> is either a collection name or a <collection>/<id> reference; the reference splits at
|
|
36
40
|
the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee is ONE argument:
|
|
37
41
|
list <collection> [--filter k=v] [--where <json>] [--sort [-]<field>] [--json]
|
|
42
|
+
list proofs [--missing] (a proof listing appends two COMPUTED columns:
|
|
43
|
+
availability on THIS machine, and the last verdict
|
|
44
|
+
from its ledger. --missing inverts it — one line per
|
|
45
|
+
artifact no proof is about, so it takes no filter)
|
|
38
46
|
(--filter is ONE condition — repeat it to AND more
|
|
39
47
|
(--filter a=1 --filter b=2 wants both);
|
|
40
48
|
anything compound goes in one --where, operator
|
|
@@ -64,14 +72,12 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
|
|
|
64
72
|
history <collection>/<id> [--json] (git revisions of this record, newest first)
|
|
65
73
|
diff <collection>/<id> [--hash <sha>] (the patch one revision applied; defaults to HEAD)
|
|
66
74
|
revert <collection>/<id> --hash <sha> (restore the content at <sha> as a pending write)
|
|
67
|
-
|
|
68
|
-
available / done / not-applicable
|
|
75
|
+
next <collection>[/<id>] [--ids <id>,…] (which bound commands apply, and in which state:
|
|
76
|
+
available / done / not-applicable — what can
|
|
77
|
+
happen to this record next)
|
|
69
78
|
relations [<collection>] (every two-way pair: owner.field → target.mirror)
|
|
70
79
|
relations rebuild <collection> [--drop <f>] (regenerate mirror VALUES from the owning side;
|
|
71
80
|
--drop removes a stale ex-mirror key from records)
|
|
72
|
-
ensure <repos-id> | --all [--json] (materialize an attached repo's working tree ON
|
|
73
|
-
DEMAND — never at install; --all is the explicit
|
|
74
|
-
opt-in, e.g. before going offline)
|
|
75
81
|
resolve '<string>' | <collection>/<id> <field>
|
|
76
82
|
(render \${env:NAME} · \${workspaceFolder} ·
|
|
77
83
|
\${userHome} — the ONLY substitution point; a
|
|
@@ -79,10 +85,11 @@ the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee i
|
|
|
79
85
|
field prints one item per line)
|
|
80
86
|
|
|
81
87
|
system verbs — the SAME verbs, on the entities the compiler materializes (modules, collections,
|
|
82
|
-
skills, agents, commands, command-bindings, ui-views, collection-templates). ⚠ ONE
|
|
83
|
-
POLICY, not in spelling: a SYSTEM write commits itself, because an uncompilable or
|
|
84
|
-
schema is not a state a workspace should sit in; a RECORD write does not — \`commit\`
|
|
85
|
-
The commit lands in the repo that holds the source, so a write into a git module
|
|
88
|
+
skills, agents, commands, command-bindings, ui-views, collection-templates, proofs). ⚠ ONE
|
|
89
|
+
difference in POLICY, not in spelling: a SYSTEM write commits itself, because an uncompilable or
|
|
90
|
+
unpublished schema is not a state a workspace should sit in; a RECORD write does not — \`commit\`
|
|
91
|
+
publishes it. The commit lands in the repo that holds the source, so a write into a git module
|
|
92
|
+
commits there.
|
|
86
93
|
add collections --name <name> [--module <m>] [--namespace <ns>] [--template docs|entity]
|
|
87
94
|
[--description "…"] [--suffix <s>] [--id-shape dated|slug]
|
|
88
95
|
(--namespace health --name doctors === --name
|
|
@@ -122,7 +129,7 @@ field verbs — a field is the one sub-entity, and it has verbs of its own (ther
|
|
|
122
129
|
collection: the ENGINE does not read one, and \`rename-field\` was the only capability it would buy):
|
|
123
130
|
add-field <collection> --name <field> --type <type> [--options a,b] [--default-value v]
|
|
124
131
|
[--required true] [--description "…"] [--many] [--inverse [name]]
|
|
125
|
-
[--inverse-description "…"] [--unique] [--body] [--module <m>]
|
|
132
|
+
[--inverse-description "…"] [--unique] [--body] [--sensitive] [--module <m>]
|
|
126
133
|
[--on-delete restrict|set-null] [--mirror-of <collection>.<field>]
|
|
127
134
|
types: string text markdown boolean number integer date datetime
|
|
128
135
|
enum tags <collection> — a date-time may be written as
|
|
@@ -135,8 +142,8 @@ collection: the ENGINE does not read one, and \`rename-field\` was the only capa
|
|
|
135
142
|
target to have one.
|
|
136
143
|
--module writes an OVERLAY in that module (it must declare the base's
|
|
137
144
|
module in dreamteamer.dependencies).
|
|
138
|
-
|
|
139
|
-
[--required true|false] [--description "…"] [--body true|false]
|
|
145
|
+
set-field <collection> --name <field> [--type <type>] [--options a,b] [--default-value v]
|
|
146
|
+
[--required true|false] [--description "…"] [--body true|false] [--sensitive true|false]
|
|
140
147
|
[--many] [--inverse [name]] [--unique] [--module <m>]
|
|
141
148
|
[--on-delete restrict|set-null] [--mirror-of <collection>.<field>]
|
|
142
149
|
(an existing description survives a retype, and so does every relation
|
|
@@ -145,7 +152,7 @@ collection: the ENGINE does not read one, and \`rename-field\` was the only capa
|
|
|
145
152
|
restating --type. --inverse= drops the mirror; --unique false clears
|
|
146
153
|
the one-to-one. Records written before the mirror existed are counted
|
|
147
154
|
for you, with the "relations rebuild" that repairs them.)
|
|
148
|
-
|
|
155
|
+
rm-field <collection> --name <field> [--module <m>] [--dry-run]
|
|
149
156
|
(clears the field's VALUES in the same write, and reports the count)
|
|
150
157
|
rename-field <collection> --name <field> --to <new-name> [--module <m>] [--dry-run]
|
|
151
158
|
(rewrites the key in every record AND everywhere a descriptor or view
|
|
@@ -159,13 +166,66 @@ Every verb that MOVES records or CLEARS values takes --dry-run and prints its pl
|
|
|
159
166
|
workspace verbs:
|
|
160
167
|
init write the workspace skeleton into the current directory (never compiles)
|
|
161
168
|
--version print the engine version (works anywhere)
|
|
162
|
-
install
|
|
169
|
+
install make THIS checkout ready — the engine, .env (linked from the primary when this is a
|
|
170
|
+
worktree), declared local assets, git modules, compile, and a declared postinstall.
|
|
171
|
+
Idempotent: it prints a board of what it found and what it did
|
|
172
|
+
[--dry-run] plan only [--json] the board as data
|
|
173
|
+
[--link-env] link .env even into a worktree OUTSIDE the primary root
|
|
174
|
+
install --hook | --print-adapters
|
|
175
|
+
--hook: read a harness hook's JSON payload from stdin and install the checkout its
|
|
176
|
+
\`cwd\` names — the process cwd is the harness's, never the worktree's. In a linked
|
|
177
|
+
worktree the board's last line is the landing instruction.
|
|
178
|
+
--print-adapters: print the hook snippet for each declared harness. Merging it into
|
|
179
|
+
the harness's own settings file is the operator's act — the engine never writes one
|
|
180
|
+
install repos/<id> | repos --all [--json]
|
|
181
|
+
materialize an attached repo's working tree ON DEMAND — never as part of making a
|
|
182
|
+
checkout ready; --all is the explicit opt-in, e.g. before going offline
|
|
183
|
+
install --clone <url> [name] attach a git module to this workspace
|
|
184
|
+
add worktrees --name <n> [--path <dir>] [--base <ref>] [--temp] | --hook
|
|
185
|
+
cut a linked git worktree on branch worktree-<n> and \`install\` it, so it is ready
|
|
186
|
+
to work in; it prints its absolute path LAST, which is what a creation hook echoes.
|
|
187
|
+
--temp: detached, no branch, under .worktrees/.tmp-* inside this root — a sandbox
|
|
188
|
+
--hook: take the name from a WorktreeCreate payload on stdin; implies .worktrees/<n>
|
|
189
|
+
list worktrees | get worktrees/<n> | rm worktrees/<n> [--force]
|
|
190
|
+
observed from \`git worktree list\`, never stored. rm refuses a worktree holding
|
|
191
|
+
dirty records or commits not on the primary branch — neither is visible from here
|
|
192
|
+
land worktrees/<name|path> [--keep] [--dry-run] [--branch <n>] [--json]
|
|
193
|
+
land a worktree's commits onto the primary branch — the one MOVEMENT verb. It refuses
|
|
194
|
+
while the primary holds pending record writes, rebases a COPY of the branch under one
|
|
195
|
+
engine-owned lock, resolves ONLY the generated harness block, fast-forwards, recompiles
|
|
196
|
+
the primary, then removes the worktree and its branch. A conflict in a record aborts
|
|
197
|
+
and leaves every tree exactly as it was
|
|
198
|
+
[--keep] keep the worktree, reset onto what landed
|
|
199
|
+
[--branch <n>] give a DETACHED worktree the branch worktree-<n> first
|
|
200
|
+
[--dry-run] print the plan and change nothing
|
|
201
|
+
land --hook [--dry-run] [--json]
|
|
202
|
+
read a WorktreeRemove payload on stdin and report what that worktree still holds.
|
|
203
|
+
ALWAYS a dry run — the engine never lands while a harness is deleting the tree
|
|
163
204
|
update pull git_modules clones forward (ff-only on the lockfile ref), rebuild,
|
|
164
205
|
then compile; [<name>] updates just one. dirty clones are skipped
|
|
165
206
|
compile materialize modules + workspace sources into .dreamteamer (+ harness adapters)
|
|
166
207
|
[--watch] recompile on source changes
|
|
167
208
|
check validate every record against the compiled descriptors (report-only)
|
|
168
|
-
|
|
209
|
+
prove <proof> | <artifact-ref> | --all
|
|
210
|
+
run a proof and answer with an EXIT CODE: 0 pass · 1 fail · 3 unavailable (this
|
|
211
|
+
machine lacks a required var or binary) · 4 no-fixture (the given matched no record)
|
|
212
|
+
· 5 a \`perform\` step is owed a human/agent · 6 vacuous (every expectation already
|
|
213
|
+
held, so the proof cannot fail). Evidence lands in .dreamteamer/.proofs/<id>.jsonl
|
|
214
|
+
<proof> [--record <c>/<id>] finish the pending run for that record
|
|
215
|
+
[--restart] discard a pending run and start over
|
|
216
|
+
[--here] run a \`writes\` proof in this workspace, not a sandbox
|
|
217
|
+
[--keep] keep the sandbox afterwards — under --json that is
|
|
218
|
+
\`kept: true\` beside the \`sandbox\` path [--json] one object on stdout
|
|
219
|
+
<artifact-ref> every proof whose \`about\` names skills/<id>, commands/<id>,
|
|
220
|
+
command-bindings/<id> or <module>/bin/<file> — board semantics, so
|
|
221
|
+
it takes the same [--kind gate|live] [--external] [--strict] [--json]
|
|
222
|
+
--all every proof; a \`perform\` one is LISTED, never started, so this
|
|
223
|
+
never exits 5. ONE line per proof — a step transcript is what a
|
|
224
|
+
single-proof run is for [--kind gate|live] [--external] include
|
|
225
|
+
external proofs [--strict] make an unavailable fatal [--json]
|
|
226
|
+
status workspace status: compiled runtime freshness, per-module channel/ref, staleness,
|
|
227
|
+
and one \`proofs:\` line counting each proof's LAST verdict on this machine
|
|
228
|
+
[--strict] exit 1 when any proof's ledger tail is a FAIL
|
|
169
229
|
start serve the clean REST api at /api [--port <n>]
|
|
170
230
|
changes what changed in every repo that holds records, as record events
|
|
171
231
|
[--since <sha|YYYY-MM-DD>] (default: HEAD~1 — the last commit's own changes) [--json]
|
|
@@ -175,10 +235,20 @@ workspace verbs:
|
|
|
175
235
|
<collection> or one <collection>/<id> — the record form is what keeps a
|
|
176
236
|
concurrent session's pending records out of your commit.
|
|
177
237
|
[<collection>|<collection>/<id> …] [-m <subject>] [--dry-run] [--json]
|
|
238
|
+
export render the workspace for a consumer that is not a coding agent, and optionally sync it
|
|
239
|
+
export notebooklm [--out <dir>] [--plan standard|plus|pro|ultra|<n>] [--max-words <n>]
|
|
240
|
+
[--collections a,b] [--instructions <template.md>]
|
|
241
|
+
[--notebook <id> | --create "<title>"] [--response-length default|longer|shorter]
|
|
242
|
+
[--mode default|learning-guide|concise|detailed] [--wait] [--json]
|
|
243
|
+
writes one schema source (workspace → module → collection → field), one source per
|
|
244
|
+
collection (sharded by --max-words, titled \`dt · <c> [n/m]\`), and the persona from a
|
|
245
|
+
template; a collection marked \`sensitive: true\` and a field marked \`x-sensitive: true\`
|
|
246
|
+
never travel. Refuses when the sources exceed the plan. With --notebook/--create it
|
|
247
|
+
adds, replaces and removes its own sources to match and applies the persona.
|
|
178
248
|
help this text
|
|
179
249
|
`;
|
|
180
250
|
|
|
181
|
-
// Record verbs, split by what their <target> means. `move` and `
|
|
251
|
+
// Record verbs, split by what their <target> means. `move` and `next` are in NEITHER set: both
|
|
182
252
|
// accept either shape, and which one it is has to be decided against the declared collections.
|
|
183
253
|
// How many rows a per-record listing prints before it summarises. ONE number, because a report that
|
|
184
254
|
// caps one of its lists and not the next reads as a bug in whichever list ran long.
|
|
@@ -186,22 +256,29 @@ const ROWS_SHOWN = 20;
|
|
|
186
256
|
|
|
187
257
|
const REF_VERBS = new Set(['get', 'set', 'rm', 'rename', 'history', 'diff', 'revert']);
|
|
188
258
|
const COLLECTION_VERBS = new Set(['list', 'add', 'values']);
|
|
189
|
-
const EITHER_VERBS = new Set(['move', '
|
|
259
|
+
const EITHER_VERBS = new Set(['move', 'next']);
|
|
190
260
|
|
|
191
261
|
// FIELD VERBS. Their <target> is a collection and everything else is flags, which is the one shape
|
|
192
262
|
// that differs from the record verbs — so they get their own case arm rather than being folded into
|
|
193
263
|
// `dispatchRecordVerb`. There is no `schema <op>` table any more: system entities take the record
|
|
194
264
|
// verbs, and `collectionCommand`'s interceptors are the whole dispatch (§4).
|
|
195
|
-
const FIELD_VERBS = ['add-field', '
|
|
265
|
+
const FIELD_VERBS = ['add-field', 'set-field', 'rm-field', 'rename-field'];
|
|
196
266
|
|
|
197
267
|
// WORKSPACE VERBS take a closed set of options, and nothing downstream of here would notice a
|
|
198
268
|
// misspelling — `dt commit --dryrun` COMMITTED, because `rest.includes('--dry-run')` is false for a
|
|
199
269
|
// flag nobody typed correctly. The record/system/field verbs are checked in `collections-cli.js`,
|
|
200
270
|
// beside the parser they share; these nine have no shared parser, so the table is here.
|
|
201
271
|
export const WORKSPACE_FLAGS = {
|
|
202
|
-
init: ['name', 'data-path', 'harnesses', 'workspace-module'],
|
|
203
|
-
|
|
272
|
+
init: ['name', 'data-path', 'harnesses', 'workspace-module'], update: [],
|
|
273
|
+
install: ['clone', 'dry-run', 'json', 'link-env', 'all', 'hook', 'print-adapters'],
|
|
274
|
+
start: ['port'], compile: ['watch'], check: [], status: ['strict'],
|
|
204
275
|
changes: ['since', 'json'], commit: ['dry-run', 'json'],
|
|
276
|
+
export: EXPORT_FLAGS,
|
|
277
|
+
// the UNION of every form's flags — the outer typo gate. Which flags each FORM takes is refused
|
|
278
|
+
// inside `proveCommand`, where the target has been resolved against the compiled proofs.
|
|
279
|
+
prove: ['all', 'kind', 'record', 'restart', 'json', 'keep', 'here', 'external', 'strict'],
|
|
280
|
+
// same shape: the union of both forms, with `--hook`'s vocabulary refused in the arm below
|
|
281
|
+
land: ['keep', 'dry-run', 'branch', 'hook', 'json'],
|
|
205
282
|
};
|
|
206
283
|
|
|
207
284
|
export function run(argv) {
|
|
@@ -229,10 +306,86 @@ export function run(argv) {
|
|
|
229
306
|
}
|
|
230
307
|
const ws = findWorkspace();
|
|
231
308
|
switch (cmd) {
|
|
309
|
+
// ONE verb makes a thing present and ready: this checkout, or an attached repo's
|
|
310
|
+
// working tree. `ensure` was the second spelling of the same idea and is retired in the
|
|
311
|
+
// `default` arm below — no alias, per the 0.12.0 policy.
|
|
312
|
+
//
|
|
313
|
+
// ⚠ THREE FORMS, ONE FLAG TABLE — so each form REFUSES the other's vocabulary itself.
|
|
314
|
+
// `WORKSPACE_FLAGS.install` can only say which flags the verb has; it cannot know that
|
|
315
|
+
// `--dry-run` is meaningless once a target is named. Forwarding just the flags a form
|
|
316
|
+
// understands DROPS the rest in silence, and that is not a cosmetic loss:
|
|
317
|
+
// `dt install repos/x --dry-run` materialized the repo for real, driven past a flag
|
|
318
|
+
// whose entire meaning is "do nothing". A bare positional had the same shape of bug —
|
|
319
|
+
// `dt install <repo-id>`, the muscle memory `dt ensure <id>` taught for nine releases,
|
|
320
|
+
// matched neither branch, so the target was ignored and a whole checkout install ran.
|
|
321
|
+
// Same policy as the retired verbs below: a stale invocation fails loudly.
|
|
232
322
|
case 'install': {
|
|
323
|
+
const given = rest.filter((a) => a.startsWith('--'));
|
|
324
|
+
const refuse = (form, allowed) => {
|
|
325
|
+
const stray = given.filter((f) => !allowed.includes(f));
|
|
326
|
+
if (!stray.length) return;
|
|
327
|
+
throw new Error(`${stray.join(' ')} ${stray.length > 1 ? 'are not flags' : 'is not a flag'} of \`${form}\` — that form takes ${allowed.join(' ')}`);
|
|
328
|
+
};
|
|
233
329
|
const ci = rest.indexOf('--clone');
|
|
234
|
-
if (ci > -1)
|
|
235
|
-
|
|
330
|
+
if (ci > -1) {
|
|
331
|
+
refuse('dt install --clone <url> [name]', ['--clone']);
|
|
332
|
+
process.exit(installClone(ws, rest[ci + 1], rest[ci + 2]));
|
|
333
|
+
}
|
|
334
|
+
const target = rest.find((a) => !a.startsWith('--'));
|
|
335
|
+
if (target === 'repos' || target?.startsWith('repos/')) {
|
|
336
|
+
refuse(`dt install ${target}`, ['--all', '--json']);
|
|
337
|
+
warnIfStale(ws.root);
|
|
338
|
+
const args = target === 'repos' ? given : [target.slice('repos/'.length), ...given];
|
|
339
|
+
process.exit(collectionCommand(ws, 'repos', 'ensure', args));
|
|
340
|
+
}
|
|
341
|
+
if (target) {
|
|
342
|
+
throw new Error(`dt install takes no target "${target}" — \`dt install\` makes THIS checkout ready, and \`dt install repos/${target}\` materializes an attached repo`);
|
|
343
|
+
}
|
|
344
|
+
// ⚠ TWO MORE FORMS, EACH ITS OWN VOCABULARY. `--hook` reads a JSON payload off stdin
|
|
345
|
+
// and installs the checkout that payload names; `--print-adapters` installs nothing
|
|
346
|
+
// at all. Folding either into `dt install`'s flag list would let `--hook --dry-run`
|
|
347
|
+
// through as a plan of a checkout nobody named, and `--print-adapters --link-env`
|
|
348
|
+
// as a symlink placed by a verb whose whole job is to print.
|
|
349
|
+
if (given.includes('--hook')) {
|
|
350
|
+
refuse('dt install --hook', ['--hook']);
|
|
351
|
+
process.exit(installCommand(ws, rest));
|
|
352
|
+
}
|
|
353
|
+
if (given.includes('--print-adapters')) {
|
|
354
|
+
refuse('dt install --print-adapters', ['--print-adapters']);
|
|
355
|
+
process.exit(installCommand(ws, rest));
|
|
356
|
+
}
|
|
357
|
+
refuse('dt install', ['--dry-run', '--json', '--link-env']);
|
|
358
|
+
process.exit(installCommand(ws, rest));
|
|
359
|
+
}
|
|
360
|
+
// ⚠ THE EXIT CODE IS THE WHOLE POINT OF THIS VERB, so `proveCommand` RETURNS it and this
|
|
361
|
+
// line is the only place it becomes a process exit. Six codes are a contract — 0 pass ·
|
|
362
|
+
// 1 fail · 3 unavailable · 4 no-fixture · 5 an actor is owed a step · 6 vacuous — and a
|
|
363
|
+
// `throw` inside `prove` becomes 1 through the shared catch below, which is right for
|
|
364
|
+
// every refusal it makes (a target that names nothing, a resume with nothing to resume,
|
|
365
|
+
// a stray flag of the other form): those are errors about the INVOCATION, not verdicts
|
|
366
|
+
// about an artifact, and 2 is already spoken for by "you typed a verb that is gone".
|
|
367
|
+
//
|
|
368
|
+
// The per-form flag refusal lives in `proveCommand` rather than here, unlike `install`'s:
|
|
369
|
+
// deciding which form was typed means resolving the target against the compiled proofs
|
|
370
|
+
// and artifacts, and a second copy of that resolution in this file — purely to choose
|
|
371
|
+
// which message to print — is the drift the comment above `case 'install':` describes.
|
|
372
|
+
case 'prove':
|
|
373
|
+
warnIfStale(ws.root);
|
|
374
|
+
process.exit(proveCommand(ws, rest).code);
|
|
375
|
+
// THE ONE MOVEMENT VERB (decision 309). Two forms and, like `install`'s, each refuses the
|
|
376
|
+
// other's vocabulary here: the flag table can only say which flags `land` HAS, and a
|
|
377
|
+
// `--hook --keep` accepted-and-dropped would promise a worktree kept by a form that is
|
|
378
|
+
// always a dry run. A refusal or a conflict throws or returns 1; 2 stays what it is
|
|
379
|
+
// everywhere else — "you typed a verb that is gone".
|
|
380
|
+
case 'land': {
|
|
381
|
+
const given = rest.filter((a) => a.startsWith('--'));
|
|
382
|
+
const stray = given.filter((f) => !(given.includes('--hook') ? ['--hook', '--dry-run', '--json'] : ['--keep', '--dry-run', '--branch', '--json']).includes(f));
|
|
383
|
+
if (stray.length) {
|
|
384
|
+
const form = given.includes('--hook') ? 'dt land --hook' : 'dt land worktrees/<name>';
|
|
385
|
+
throw new Error(`${stray.join(' ')} ${stray.length > 1 ? 'are not flags' : 'is not a flag'} of \`${form}\` — that form takes ${(given.includes('--hook') ? ['--hook', '--dry-run', '--json'] : ['--keep', '--dry-run', '--branch', '--json']).join(' ')}`);
|
|
386
|
+
}
|
|
387
|
+
warnIfStale(ws.root);
|
|
388
|
+
process.exit(landCommand(ws, rest));
|
|
236
389
|
}
|
|
237
390
|
case 'update': {
|
|
238
391
|
const code = update(ws, rest.find((a) => !a.startsWith('--')));
|
|
@@ -360,6 +513,52 @@ export function run(argv) {
|
|
|
360
513
|
if (sh) line += ` — shadows ${CHANNEL_LABEL[sh.loser]} copy`;
|
|
361
514
|
console.log(line);
|
|
362
515
|
}
|
|
516
|
+
// ⚠ WHICH CHECKOUT AM I. Everything `install` decides turns on this, and a linked
|
|
517
|
+
// worktree is indistinguishable from the primary by eye — so it is stated, with the
|
|
518
|
+
// count of sibling worktrees holding records and commits that are invisible from
|
|
519
|
+
// here. Wrapped like every other block below: `status` is the command run when
|
|
520
|
+
// things are already wrong, and it must still print.
|
|
521
|
+
try {
|
|
522
|
+
const co = describeCheckout(ws.root);
|
|
523
|
+
console.log(`checkout: ${co.kind === 'primary' ? 'primary' : `linked worktree of ${co.primary}`}`);
|
|
524
|
+
const wts = listWorktrees(ws).filter((w) => !w.primary);
|
|
525
|
+
console.log(`worktrees: ${wts.length} · ${wts.filter((w) => w.dirtyRecords).length} with dirty records · ${wts.filter((w) => w.ahead).length} ahead`);
|
|
526
|
+
} catch { /* not a git checkout — nothing to report about worktrees */ }
|
|
527
|
+
// ⚠ WHAT THIS MACHINE HAS ACTUALLY PROVED. A ledger is per-machine and gitignored, so
|
|
528
|
+
// this line cannot be derived from the repo — and it is the only place a FAIL from
|
|
529
|
+
// last week surfaces without being asked for. The TAIL per proof, not every row: a
|
|
530
|
+
// proof that failed on Monday and passed on Tuesday is passing. Wrapped like every
|
|
531
|
+
// block here — an older runtime has no `proofs` descriptor, and status must still print.
|
|
532
|
+
// ⚠ M5 — COUNTED AS THE WALK GOES, NEVER ASSIGNED AT THE END OF IT. `proofsFailed =
|
|
533
|
+
// tally.FAIL` sat below the loop, inside the try — so a throw partway through (an
|
|
534
|
+
// unreadable ledger, a record that will not parse) left the gate reading ZERO failures
|
|
535
|
+
// and `--strict` exiting 0 BECAUSE the count broke. That is the one direction a gate
|
|
536
|
+
// must never fail: a silent green bought with a swallowed exception.
|
|
537
|
+
let proofsFailed = 0;
|
|
538
|
+
try {
|
|
539
|
+
const tally = { PASS: 0, FAIL: 0, UNAVAILABLE: 0 };
|
|
540
|
+
let declared = 0; let never = 0; let other = 0;
|
|
541
|
+
// ⚠ A SANDBOX NOTHING WILL COME BACK FOR. `.worktrees/` is gitignored, so a kept or
|
|
542
|
+
// un-removable one accumulates in silence and the ledger is the only thing that
|
|
543
|
+
// knows the directory exists. `kept` is not a row field: a terminal row with a
|
|
544
|
+
// sandbox and NO removal attempted (`null`) is exactly what `--keep` leaves behind.
|
|
545
|
+
// `existsSync` because a count nothing can clear is a lie.
|
|
546
|
+
const left = new Set();
|
|
547
|
+
for (const { id } of new Store(ws).readAll('proofs')) {
|
|
548
|
+
declared++;
|
|
549
|
+
const rows = readLedger(ws.root, id);
|
|
550
|
+
const t = rows[rows.length - 1];
|
|
551
|
+
if (!t) never++;
|
|
552
|
+
else if (t.verdict in tally) { tally[t.verdict]++; if (t.verdict === 'FAIL') proofsFailed++; }
|
|
553
|
+
else other++;
|
|
554
|
+
for (const r of rows) {
|
|
555
|
+
const behind = r.sandbox_removed === false || (r.verdict !== 'PENDING' && r.sandbox_removed === null);
|
|
556
|
+
if (r.sandbox && behind && fs.existsSync(r.sandbox)) left.add(r.sandbox);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
console.log(`proofs: ${declared} declared · ${tally.PASS} passed · ${tally.FAIL} failed · ${tally.UNAVAILABLE} unavailable · ${never} never${other ? ` · ${other} other` : ''}`);
|
|
560
|
+
if (left.size) console.log(` sandboxes left behind: ${left.size} — dt list worktrees`);
|
|
561
|
+
} catch { /* no proofs descriptor compiled — nothing to report */ }
|
|
363
562
|
console.log(`entries: ${Object.keys(s.manifest.entries).length}`);
|
|
364
563
|
// repos materialize LAZILY, so presence is REPORTED here rather than stored on the
|
|
365
564
|
// record. Wrapped: an older workspace may predate the repos descriptor, and status
|
|
@@ -370,8 +569,8 @@ export function run(argv) {
|
|
|
370
569
|
const here = repos.filter((r) => r.present).length;
|
|
371
570
|
console.log(`repos: ${here}/${repos.length} materialized`);
|
|
372
571
|
// an UNRESOLVED path is not the same absence as a repo simply not cloned yet, and
|
|
373
|
-
// `dreamteamer
|
|
374
|
-
for (const r of repos) if (!r.present) console.log(` absent: ${r.id} → ${r.path}${r.unresolved ? ` — ${r.unresolved}` : ` (dreamteamer
|
|
572
|
+
// `dreamteamer install repos/<id>` is not the fix for it — say which one this is.
|
|
573
|
+
for (const r of repos) if (!r.present) console.log(` absent: ${r.id} → ${r.path}${r.unresolved ? ` — ${r.unresolved}` : ` (dreamteamer install repos/${r.id})`}`);
|
|
375
574
|
}
|
|
376
575
|
} catch { /* no repos descriptor compiled — nothing to report */ }
|
|
377
576
|
// Uncommitted records are invisible to `dt changes` (it diffs commits), so the
|
|
@@ -390,25 +589,58 @@ export function run(argv) {
|
|
|
390
589
|
process.exit(1);
|
|
391
590
|
}
|
|
392
591
|
console.log('✔ .dreamteamer is fresh');
|
|
592
|
+
// ⚠ THE FAIL IS FATAL ONLY WHEN ASKED. `status` is the command you run when things are
|
|
593
|
+
// already wrong, so it prints EVERYTHING first and gates last — the same shape the
|
|
594
|
+
// staleness exit above has.
|
|
595
|
+
// ⚠ R38/R46 — `--strict=true` IS THE SAME FLAG, AND `--strict=false` IS OFF. The
|
|
596
|
+
// unknown-flag gate above splits on `=`, so the `=` spelling was ACCEPTED and then read
|
|
597
|
+
// as "no --strict at all" — a CI step written that way stayed green over a failing
|
|
598
|
+
// proof, for a reason nothing printed. `flagEnabled` is the one reader of that shape,
|
|
599
|
+
// shared with `dt prove`, so the two verbs cannot disagree about what was typed.
|
|
600
|
+
if (flagEnabled(rest, 'strict') && proofsFailed) {
|
|
601
|
+
console.log(`✖ ${proofsFailed} proof(s) FAILED on this machine — dt list proofs`);
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
393
604
|
process.exit(0);
|
|
394
605
|
}
|
|
606
|
+
// `export <target>` — the workspace rendered for a consumer that is not a coding agent. One
|
|
607
|
+
// target today; the map in export-notebooklm.js is where a second one would register, the way
|
|
608
|
+
// harnesses do. Without --notebook/--create it is a pure render and touches no network.
|
|
609
|
+
case 'export': {
|
|
610
|
+
warnIfStale(ws.root);
|
|
611
|
+
const { flags, pos } = parseArgs(rest);
|
|
612
|
+
process.exit(exportCommand(ws, pos[0], flags));
|
|
613
|
+
}
|
|
395
614
|
case 'help':
|
|
396
615
|
emit(USAGE);
|
|
397
616
|
process.exit(0);
|
|
398
617
|
case 'list': case 'add': case 'values':
|
|
399
618
|
case 'get': case 'set': case 'rm': case 'rename': case 'history': case 'diff': case 'revert':
|
|
400
|
-
case 'move': case '
|
|
619
|
+
case 'move': case 'next': {
|
|
620
|
+
// ⚠ `worktrees` IS NOT A COLLECTION — it is observed from git — so it is intercepted
|
|
621
|
+
// here rather than being dispatched. Which means it never reaches
|
|
622
|
+
// `collectionCommand`, where every other verb's flags are refused: the parse and the
|
|
623
|
+
// refusal have to be done HERE or `--tmep` is swallowed and a request for a
|
|
624
|
+
// throwaway sandbox silently becomes a permanent branch worktree.
|
|
625
|
+
const target = rest[0];
|
|
626
|
+
if (target === 'worktrees' || target?.startsWith('worktrees/')) {
|
|
627
|
+
const { flags } = parseArgs(rest.slice(1));
|
|
628
|
+
refuseUnknownFlags(null, 'worktrees', cmd, flags);
|
|
629
|
+
process.exit(worktreeCommand(ws, cmd, target, flags));
|
|
630
|
+
}
|
|
401
631
|
warnIfStale(ws.root);
|
|
402
632
|
process.exit(dispatchRecordVerb(ws, cmd, rest));
|
|
633
|
+
}
|
|
403
634
|
// The verb `check`'s stale-mirror message names. It reads the compiled relations, and
|
|
404
635
|
// rebuild WRITES records, so both want the same staleness warning every record verb gets.
|
|
405
636
|
// FIELD VERBS — see FIELD_VERBS. Their <target> is a collection and everything else is
|
|
406
637
|
// flags, so they are their own case rather than being folded into `dispatchRecordVerb`.
|
|
407
638
|
//
|
|
408
|
-
// ⚠
|
|
409
|
-
// loudly, because a half-working grammar teaches the wrong shape without ever
|
|
410
|
-
// The `default` arm below names `schema`
|
|
411
|
-
|
|
639
|
+
// ⚠ THE RETIRED SPELLINGS ARE GONE, not aliased. The 0.12.0 policy: a stale invocation
|
|
640
|
+
// must fail loudly, because a half-working grammar teaches the wrong shape without ever
|
|
641
|
+
// saying so. The `default` arm below names each one — `schema`, `ensure`, `update-field`,
|
|
642
|
+
// `remove-field`, `commands` — and carries its replacement.
|
|
643
|
+
case 'add-field': case 'set-field': case 'rm-field': case 'rename-field': {
|
|
412
644
|
warnIfStale(ws.root);
|
|
413
645
|
const [target, ...flagArgs] = rest;
|
|
414
646
|
if (!target || target.startsWith('--')) {
|
|
@@ -419,28 +651,51 @@ export function run(argv) {
|
|
|
419
651
|
case 'relations':
|
|
420
652
|
warnIfStale(ws.root);
|
|
421
653
|
process.exit(relationsCommand(ws, rest));
|
|
422
|
-
// `repos ensure` lost its noun: the repos collection is still where the declaration lives,
|
|
423
|
-
// but materializing one is a verb the operator types, not a record write.
|
|
424
|
-
case 'ensure':
|
|
425
|
-
warnIfStale(ws.root);
|
|
426
|
-
process.exit(collectionCommand(ws, 'repos', 'ensure', rest));
|
|
427
654
|
case 'resolve':
|
|
428
655
|
process.exit(resolveVariables(ws, rest));
|
|
429
656
|
default:
|
|
430
657
|
// ⚠ NAMED, not just unknown. Every doc, skill and downstream script spelled these
|
|
431
658
|
// `dt schema <op>` for seven releases, so the failure has to carry the translation —
|
|
432
|
-
// an "unknown verb" alone sends the reader to `help` to guess which of
|
|
659
|
+
// an "unknown verb" alone sends the reader to `help` to guess which of thirty verbs
|
|
433
660
|
// replaced the one they typed. No alias layer and no deprecation window: 0.12.0's
|
|
434
661
|
// policy, and the reason it is the right one is that `dt contacts list` failing
|
|
435
662
|
// loudly is what taught the verb-first grammar in one command.
|
|
663
|
+
//
|
|
664
|
+
// ⚠ ONE EXIT CODE FOR ALL OF THEM: 2, the usage code. "You typed a verb that is
|
|
665
|
+
// gone" is one answer, so a script (and an operator) asks it once rather than
|
|
666
|
+
// learning which retirement exits 1 and which exits 2.
|
|
436
667
|
if (cmd === 'schema') {
|
|
437
668
|
console.error('✖ unknown verb "schema" — schema verbs are gone since 0.19.0. System entities take the RECORD verbs now:');
|
|
438
669
|
console.error(' dt add collections --name <c> [--module <m>] · dt rm collections/<c> · dt rename collections/<old> <new>');
|
|
439
670
|
console.error(' dt set collections/<c> module=<m> | <scalar>=<v> · dt get collections/<c> [--module <m>]');
|
|
440
|
-
console.error(' dt add-field <c> … · dt
|
|
671
|
+
console.error(' dt add-field <c> … · dt set-field <c> … · dt rm-field <c> … · dt rename-field <c> --name <f> --to <g>');
|
|
441
672
|
console.error(' dt add|set|rm|rename modules/<id> … · dt add|set|rm|rename ui-views/<id> …');
|
|
442
673
|
console.error(' the full mapping table is in UPDATING.md (0.18.0 → 0.19.0), and `dt help` has the current spellings.');
|
|
443
|
-
process.exit(
|
|
674
|
+
process.exit(2);
|
|
675
|
+
}
|
|
676
|
+
if (cmd === 'ensure') {
|
|
677
|
+
console.error('✖ unknown verb "ensure" — gone since 0.22.0 (decision 309: install is the one verb that makes a thing present and ready):');
|
|
678
|
+
console.error(' dt install this checkout — engine, .env, local assets, git modules, compile, postinstall');
|
|
679
|
+
console.error(' dt install repos/<id> materialize one attached repo · dt install repos --all');
|
|
680
|
+
process.exit(2);
|
|
681
|
+
}
|
|
682
|
+
// The change verbs spell one way. `add · set · rm · rename` are the record verbs, so
|
|
683
|
+
// `update-field`/`remove-field` were a second spelling for one action inside one
|
|
684
|
+
// grammar — the field verbs now say `add-field · set-field · rm-field · rename-field`.
|
|
685
|
+
if (cmd === 'update-field' || cmd === 'remove-field') {
|
|
686
|
+
const to = cmd === 'update-field' ? 'set-field' : 'rm-field';
|
|
687
|
+
console.error(`✖ unknown verb "${cmd}" — gone since 0.22.0 (decision 309: a field verb spells its action the way the record verbs do):`);
|
|
688
|
+
console.error(` dt ${to} <collection> --name <field> …`);
|
|
689
|
+
console.error(' the field verbs are: dt add-field · dt set-field · dt rm-field · dt rename-field');
|
|
690
|
+
process.exit(2);
|
|
691
|
+
}
|
|
692
|
+
// `dt commands <ref>` and `dt list commands` were one word for two things — the read
|
|
693
|
+
// verb and the system entity. `next` also says what it answers.
|
|
694
|
+
if (cmd === 'commands') {
|
|
695
|
+
console.error('✖ unknown verb "commands" — gone since 0.22.0 (decision 309: it collided with the `commands` ENTITY, which `dt list commands` reads):');
|
|
696
|
+
console.error(' dt next <collection>[/<id>] which bound commands apply to this record, and in which state');
|
|
697
|
+
console.error(' dt list commands the command entities this workspace ships');
|
|
698
|
+
process.exit(2);
|
|
444
699
|
}
|
|
445
700
|
console.error(`✖ unknown verb "${cmd}" — dreamteamer is verb-first since 0.12.0: dt <verb> [<target>]`);
|
|
446
701
|
emit(USAGE, 2);
|
|
@@ -448,6 +703,12 @@ export function run(argv) {
|
|
|
448
703
|
}
|
|
449
704
|
} catch (e) {
|
|
450
705
|
console.error(`✖ ${e.message}`);
|
|
706
|
+
// ⚠ AND ON STDOUT UNDER `--hook`, because that is the whole reason the shim exists.
|
|
707
|
+
// `bin/dt-hook.sh` fails onto stdout with a comment saying why: a hook's stdout is added to
|
|
708
|
+
// the session's context and its stderr is nobody's problem. Every success path here already
|
|
709
|
+
// prints to stdout; every FAILURE went to stderr alone, so a mis-wired hook, a bad payload, a
|
|
710
|
+
// TTY invocation or a refusal was invisible to exactly the reader the shim was built for.
|
|
711
|
+
if (process.argv.includes('--hook')) console.log(`✖ ${e.message}`);
|
|
451
712
|
process.exit(1);
|
|
452
713
|
}
|
|
453
714
|
}
|
|
@@ -465,7 +726,7 @@ function dispatchRecordVerb(ws, verb, args) {
|
|
|
465
726
|
return collectionCommand(ws, collection, verb, [id, ...rest]);
|
|
466
727
|
}
|
|
467
728
|
// EITHER_VERBS from here: a bare collection is legal for both — `move <collection> --init`,
|
|
468
|
-
// `
|
|
729
|
+
// `next <collection>`.
|
|
469
730
|
const { descriptors } = new Store(ws);
|
|
470
731
|
if (descriptors.has(target)) {
|
|
471
732
|
return verb === 'move'
|