@popoverai/dotrequirements 0.33.0 → 0.34.1

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 (119) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +13 -1
  3. package/dist/cloud.d.ts +5 -1
  4. package/dist/cloud.js +6 -2
  5. package/dist/commands/acceptance-test.js +1 -1
  6. package/dist/commands/board/index.d.ts +13 -0
  7. package/dist/commands/board/index.js +96 -0
  8. package/dist/commands/branch-merge.d.ts +78 -0
  9. package/dist/commands/branch-merge.js +129 -0
  10. package/dist/commands/diff.js +15 -5
  11. package/dist/commands/mockup/index.d.ts +11 -0
  12. package/dist/commands/mockup/index.js +82 -0
  13. package/dist/commands/scores/index.d.ts +50 -0
  14. package/dist/commands/scores/index.js +190 -0
  15. package/dist/commands/solution/ask.d.ts +9 -0
  16. package/dist/commands/solution/ask.js +27 -0
  17. package/dist/commands/solution/author.d.ts +19 -0
  18. package/dist/commands/solution/author.js +202 -0
  19. package/dist/commands/solution/branch.d.ts +103 -0
  20. package/dist/commands/solution/branch.js +341 -0
  21. package/dist/commands/solution/brief.d.ts +81 -0
  22. package/dist/commands/solution/brief.js +47 -0
  23. package/dist/commands/solution/checkout.d.ts +27 -0
  24. package/dist/commands/solution/checkout.js +66 -0
  25. package/dist/commands/solution/client.d.ts +16 -0
  26. package/dist/commands/solution/client.js +46 -0
  27. package/dist/commands/solution/contract.d.ts +9 -0
  28. package/dist/commands/solution/contract.js +23 -0
  29. package/dist/commands/solution/create.d.ts +14 -0
  30. package/dist/commands/solution/create.js +52 -0
  31. package/dist/commands/solution/demo-script.d.ts +28 -0
  32. package/dist/commands/solution/demo-script.js +43 -0
  33. package/dist/commands/solution/image.d.ts +60 -0
  34. package/dist/commands/solution/image.js +148 -0
  35. package/dist/commands/solution/index.d.ts +12 -0
  36. package/dist/commands/solution/index.js +282 -0
  37. package/dist/commands/solution/look-up.d.ts +9 -0
  38. package/dist/commands/solution/look-up.js +69 -0
  39. package/dist/commands/solution/next.d.ts +12 -0
  40. package/dist/commands/solution/next.js +48 -0
  41. package/dist/commands/solution/plan-file.d.ts +22 -0
  42. package/dist/commands/solution/plan-file.js +33 -0
  43. package/dist/commands/solution/preview-screenshot.d.ts +21 -0
  44. package/dist/commands/solution/preview-screenshot.js +56 -0
  45. package/dist/commands/solution/pull-request.d.ts +25 -0
  46. package/dist/commands/solution/pull-request.js +117 -0
  47. package/dist/commands/solution/pushing.d.ts +66 -0
  48. package/dist/commands/solution/pushing.js +185 -0
  49. package/dist/commands/solution/rehearse.d.ts +10 -0
  50. package/dist/commands/solution/rehearse.js +24 -0
  51. package/dist/commands/solution/request.d.ts +3 -0
  52. package/dist/commands/solution/request.js +13 -0
  53. package/dist/commands/solution/review.d.ts +27 -0
  54. package/dist/commands/solution/review.js +104 -0
  55. package/dist/commands/solution/run-file.d.ts +43 -0
  56. package/dist/commands/solution/run-file.js +69 -0
  57. package/dist/commands/solution/share.d.ts +62 -0
  58. package/dist/commands/solution/share.js +346 -0
  59. package/dist/commands/solution/stall.d.ts +14 -0
  60. package/dist/commands/solution/stall.js +41 -0
  61. package/dist/commands/solution/start.d.ts +14 -0
  62. package/dist/commands/solution/start.js +31 -0
  63. package/dist/commands/solution/status.d.ts +39 -0
  64. package/dist/commands/solution/status.js +145 -0
  65. package/dist/commands/solution/summary.d.ts +13 -0
  66. package/dist/commands/solution/summary.js +5 -0
  67. package/dist/commands/solution/things.d.ts +118 -0
  68. package/dist/commands/solution/things.js +78 -0
  69. package/dist/commands/solution/thread.d.ts +13 -0
  70. package/dist/commands/solution/thread.js +53 -0
  71. package/dist/commands/solution/upload.d.ts +24 -0
  72. package/dist/commands/solution/upload.js +27 -0
  73. package/dist/commands/solution/video.d.ts +88 -0
  74. package/dist/commands/solution/video.js +364 -0
  75. package/dist/commands/solution/working-folder.d.ts +44 -0
  76. package/dist/commands/solution/working-folder.js +64 -0
  77. package/dist/commands/sync.d.ts +2 -1
  78. package/dist/commands/sync.js +21 -5
  79. package/dist/convex.d.ts +54 -0
  80. package/dist/convex.js +64 -0
  81. package/dist/push/core.js +7 -2
  82. package/dist/rehearsal/derive.d.ts +23 -0
  83. package/dist/rehearsal/derive.js +112 -0
  84. package/dist/rehearsal/driver.d.ts +44 -0
  85. package/dist/rehearsal/driver.js +337 -0
  86. package/dist/rehearsal/earlier.d.ts +25 -0
  87. package/dist/rehearsal/earlier.js +138 -0
  88. package/dist/rehearsal/index.d.ts +5 -0
  89. package/dist/rehearsal/index.js +4 -0
  90. package/dist/rehearsal/live.d.ts +72 -0
  91. package/dist/rehearsal/live.js +301 -0
  92. package/dist/rehearsal/report.d.ts +4 -0
  93. package/dist/rehearsal/report.js +69 -0
  94. package/dist/rehearsal/types.d.ts +377 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/requirements/index.d.ts +6 -0
  97. package/dist/requirements/index.js +24 -0
  98. package/dist/schema/browser.d.ts +2 -1
  99. package/dist/schema/browser.js +6 -1
  100. package/dist/schema/file-writer.d.ts +5 -0
  101. package/dist/schema/file-writer.js +4 -0
  102. package/dist/schema/parser.d.ts +11 -2
  103. package/dist/schema/parser.js +11 -7
  104. package/dist/schema/provenance.d.ts +27 -0
  105. package/dist/schema/provenance.js +66 -0
  106. package/dist/schema/schemas.d.ts +40 -0
  107. package/dist/schema/schemas.js +4 -0
  108. package/dist/sync/execute.d.ts +10 -1
  109. package/dist/sync/execute.js +68 -5
  110. package/dist/sync/index.d.ts +1 -1
  111. package/dist/sync/index.js +1 -1
  112. package/dist/sync/publish.d.ts +7 -0
  113. package/dist/sync/segment.js +4 -1
  114. package/dist/sync/snapshot.d.ts +23 -1
  115. package/dist/sync/snapshot.js +75 -3
  116. package/dist/sync/solution-documents.d.ts +74 -0
  117. package/dist/sync/solution-documents.js +135 -0
  118. package/dist/sync/types.d.ts +20 -0
  119. package/package.json +3 -2
@@ -0,0 +1,364 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { api } from "../../convex.js";
4
+ import { uploadToApp } from "./upload.js";
5
+ /**
6
+ * Videos on an outcome (SIGHT-7): the drafter submits a video file from its
7
+ * checkout with the script it was recorded from. The video is uploaded to
8
+ * the app and never committed; the script travels as text, and the outcome's
9
+ * reviewer reads it in its brief.
10
+ *
11
+ * A browser plays MP4 with H.264 video and WebM with VP8 or VP9 video
12
+ * everywhere we support (Chrome, Edge, Firefox, Safari on macOS and iOS).
13
+ * HEVC and AV1 play only on some hardware, and QuickTime and Matroska only
14
+ * in some browsers, so they are refused. The codec is read from the file
15
+ * itself, since an extension says nothing about it. Kept in step with the
16
+ * backend's `ACCEPTED_VIDEO_FORMATS` (convex/solutions/videos.ts).
17
+ */
18
+ export const ACCEPTED_VIDEO_FORMATS = "an MP4 with H.264 video, or a WebM with VP8 or VP9 video";
19
+ /** The video at a path, or a refusal naming the accepted formats (SIGHT-7.0.0). */
20
+ export function readVideo(path) {
21
+ const full = resolve(path);
22
+ if (!existsSync(full) || !statSync(full).isFile()) {
23
+ throw new Error(`There is no file at ${path}, so nothing was submitted.`);
24
+ }
25
+ const bytes = new Uint8Array(readFileSync(full));
26
+ const format = videoFormat(bytes);
27
+ if ("refused" in format) {
28
+ throw new Error(`${path} isn't a video a browser can play (${format.refused}), so nothing was submitted. Submit ${ACCEPTED_VIDEO_FORMATS}.`);
29
+ }
30
+ return { name: basename(full), contentType: format.contentType, bytes };
31
+ }
32
+ /**
33
+ * The script a video was recorded from (SIGHT-7.0, 7.0.1): agentic-demo's
34
+ * steps file, JSON holding the browser commands for each step and the
35
+ * sentence said over it, as its text. Anything else is refused, so a
36
+ * reviewer never reads the wrong file as the demo's script.
37
+ */
38
+ export function readScript(path) {
39
+ const full = resolve(path);
40
+ if (!existsSync(full) || !statSync(full).isFile()) {
41
+ throw new Error(`There is no script at ${path}, so nothing was submitted. Submit the steps file the video was recorded from with it.`);
42
+ }
43
+ const text = readFileSync(full, "utf8");
44
+ if (!isStepsFile(text)) {
45
+ throw new Error(`${path} isn't the steps file a video is recorded from, so nothing was submitted. Submit the steps file you recorded the video from, with its steps and what is said over each.`);
46
+ }
47
+ return text;
48
+ }
49
+ /**
50
+ * Where each step of the script begins in the video, in seconds
51
+ * (SIGHT-7.4): read from the summary `agentic-demo record` writes beside
52
+ * the video it records, `final.json` (@popoverai/browser-automation 0.15.4
53
+ * on), one segment per step: `index`, the step's 0-based place in the steps
54
+ * file, and `startSeconds`, where the step's first frame is in the final
55
+ * video, taken as given. The starts are kept in the steps file's order, so
56
+ * step n of a link, counted from 1 as the recorder prints it ("step 1/6"),
57
+ * is the start at n − 1 (SOLDOC-15.1.1). None when there is no such file,
58
+ * as beside a video recorded before the recorder wrote one, so a link to a
59
+ * step of the script opens the video at its start (SOLDOC-15.1.1.0); none,
60
+ * with a word on stderr, when the file isn't the recorder's summary of this
61
+ * script, one start for each of its steps.
62
+ */
63
+ export function readStepStarts(videoPath, script) {
64
+ const summary = join(dirname(resolve(videoPath)), "final.json");
65
+ if (!existsSync(summary))
66
+ return undefined;
67
+ let segments;
68
+ try {
69
+ segments = JSON.parse(readFileSync(summary, "utf8"))?.segments;
70
+ }
71
+ catch {
72
+ segments = undefined;
73
+ }
74
+ const starts = [];
75
+ if (Array.isArray(segments)) {
76
+ segments.forEach((segment, at) => {
77
+ const index = Number.isInteger(segment?.index)
78
+ ? segment.index
79
+ : at;
80
+ starts[index] = segment?.startSeconds;
81
+ });
82
+ }
83
+ const steps = JSON.parse(script).steps.length;
84
+ const fits = starts.length === steps &&
85
+ // Array.from gives a step no segment named `undefined`, which fails.
86
+ Array.from(starts).every((start) => typeof start === "number" && Number.isFinite(start) && start >= 0);
87
+ if (!fits) {
88
+ console.error(`${summary} doesn't give where each of the script's ${steps} steps begins, so the video is submitted without them; a link to a step of it opens at its start. Record it again from this script to keep them.`);
89
+ return undefined;
90
+ }
91
+ return starts;
92
+ }
93
+ /**
94
+ * A demo's script, as the drafter submits it (SIGHT-13.0): the steps file
95
+ * the agent reviewing it records the video from, as its text.
96
+ */
97
+ export function readDemoScript(path) {
98
+ const full = resolve(path);
99
+ if (!existsSync(full) || !statSync(full).isFile()) {
100
+ throw new Error(`There is no demo script at ${path}, so nothing was submitted. Submit the steps file the demo is recorded from, as demo-script:<steps file>.`);
101
+ }
102
+ const script = readFileSync(full, "utf8");
103
+ if (!isStepsFile(script)) {
104
+ throw new Error(`${path} isn't a steps file a demo is recorded from, so nothing was submitted. Submit the steps file with its steps and what is said over each; \`agentic-demo validate ${path}\` checks it.`);
105
+ }
106
+ return { name: basename(full), script };
107
+ }
108
+ /** JSON with a list of steps, each saying what is said over it. */
109
+ function isStepsFile(text) {
110
+ let parsed;
111
+ try {
112
+ parsed = JSON.parse(text);
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ const steps = parsed?.steps;
118
+ return (Array.isArray(steps) &&
119
+ steps.length > 0 &&
120
+ steps.every((step) => typeof step?.narrate === "string"));
121
+ }
122
+ /** What container and video codec the bytes hold, read from the file itself. */
123
+ export function videoFormat(bytes) {
124
+ if (fourcc(bytes, 4) === "ftyp")
125
+ return mp4Format(bytes);
126
+ if (readUint(bytes, 0, 4) === 0x1a45dfa3)
127
+ return webmFormat(bytes);
128
+ return { refused: "it isn't an MP4 or a WebM file" };
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // MP4: a tree of boxes, each a 32-bit size and a four-letter type. The video
132
+ // track's codec is the first sample entry of its `stsd`, under
133
+ // moov > trak > mdia > minf > stbl, and a track is video when its `hdlr`
134
+ // says `vide`.
135
+ const H264 = new Set(["avc1", "avc3"]);
136
+ const MP4_CODEC_NAMES = {
137
+ hvc1: "HEVC",
138
+ hev1: "HEVC",
139
+ av01: "AV1",
140
+ vp09: "VP9 in an MP4",
141
+ vp08: "VP8 in an MP4",
142
+ mp4v: "MPEG-4 Part 2",
143
+ };
144
+ function mp4Format(bytes) {
145
+ const top = boxes(bytes, 0, bytes.length);
146
+ if (!top)
147
+ return { refused: "its MP4 structure is damaged" };
148
+ const ftyp = top.find((box) => box.type === "ftyp");
149
+ if (ftyp && fourcc(bytes, ftyp.body) === "qt ") {
150
+ return { refused: "it is a QuickTime movie" };
151
+ }
152
+ const moov = top.find((box) => box.type === "moov");
153
+ if (!moov)
154
+ return { refused: "it has no movie header" };
155
+ const codecs = [];
156
+ for (const trak of children(bytes, moov, "trak")) {
157
+ const mdia = children(bytes, trak, "mdia")[0];
158
+ if (!mdia)
159
+ continue;
160
+ const hdlr = children(bytes, mdia, "hdlr")[0];
161
+ // hdlr: version and flags, pre_defined, then the handler type.
162
+ if (!hdlr || fourcc(bytes, hdlr.body + 8) !== "vide")
163
+ continue;
164
+ const minf = children(bytes, mdia, "minf")[0];
165
+ const stbl = minf && children(bytes, minf, "stbl")[0];
166
+ const stsd = stbl && children(bytes, stbl, "stsd")[0];
167
+ if (!stsd)
168
+ continue;
169
+ // stsd: version and flags, an entry count, then the entries as boxes.
170
+ const entries = boxes(bytes, stsd.body + 8, stsd.end);
171
+ if (entries?.[0])
172
+ codecs.push(entries[0].type);
173
+ }
174
+ if (codecs.length === 0)
175
+ return { refused: "it has no video track" };
176
+ const other = codecs.find((codec) => !H264.has(codec));
177
+ if (other) {
178
+ return { refused: `its video is ${MP4_CODEC_NAMES[other] ?? other}` };
179
+ }
180
+ return { contentType: "video/mp4" };
181
+ }
182
+ function children(bytes, parent, type) {
183
+ return (boxes(bytes, parent.body, parent.end) ?? []).filter((box) => box.type === type);
184
+ }
185
+ /** The boxes between two offsets, or null when a size runs past the end. */
186
+ function boxes(bytes, start, end) {
187
+ const found = [];
188
+ let at = start;
189
+ while (at + 8 <= end) {
190
+ let size = readUint(bytes, at, 4);
191
+ let header = 8;
192
+ if (size === 1) {
193
+ if (at + 16 > end)
194
+ return null;
195
+ size = readUint(bytes, at + 8, 8);
196
+ header = 16;
197
+ }
198
+ else if (size === 0) {
199
+ size = end - at;
200
+ }
201
+ if (size < header || at + size > end)
202
+ return null;
203
+ found.push({
204
+ type: fourcc(bytes, at + 4),
205
+ body: at + header,
206
+ end: at + size,
207
+ });
208
+ at += size;
209
+ }
210
+ return found;
211
+ }
212
+ // ---------------------------------------------------------------------------
213
+ // WebM: EBML elements, each an ID and a size written as variable-length
214
+ // integers. The header's DocType says webm or matroska; the video track's
215
+ // CodecID is under Segment > Tracks > TrackEntry, where TrackType 1 is video.
216
+ const EBML = 0x1a45dfa3;
217
+ const DOC_TYPE = 0x4282;
218
+ const SEGMENT = 0x18538067;
219
+ const CLUSTER = 0x1f43b675;
220
+ const TRACKS = 0x1654ae6b;
221
+ const TRACK_ENTRY = 0xae;
222
+ const TRACK_TYPE = 0x83;
223
+ const CODEC_ID = 0x86;
224
+ const WEBM_VIDEO = new Set(["V_VP8", "V_VP9"]);
225
+ const WEBM_CODEC_NAMES = {
226
+ V_AV1: "AV1",
227
+ "V_MPEG4/ISO/AVC": "H.264 in a Matroska file",
228
+ "V_MPEGH/ISO/HEVC": "HEVC",
229
+ };
230
+ function webmFormat(bytes) {
231
+ const top = elements(bytes, 0, bytes.length);
232
+ const header = top.find((element) => element.id === EBML);
233
+ const docType = header
234
+ ? elements(bytes, header.body, header.end).find((element) => element.id === DOC_TYPE)
235
+ : undefined;
236
+ const kind = docType ? text(bytes, docType) : "";
237
+ if (kind !== "webm") {
238
+ return {
239
+ refused: kind === "matroska"
240
+ ? "it is a Matroska file"
241
+ : "its WebM header is damaged",
242
+ };
243
+ }
244
+ const segment = top.find((element) => element.id === SEGMENT);
245
+ const tracks = segment
246
+ ? elements(bytes, segment.body, segment.end).find((element) => element.id === TRACKS)
247
+ : undefined;
248
+ if (!tracks)
249
+ return { refused: "it has no track list" };
250
+ const codecs = [];
251
+ for (const entry of elements(bytes, tracks.body, tracks.end)) {
252
+ if (entry.id !== TRACK_ENTRY)
253
+ continue;
254
+ const fields = elements(bytes, entry.body, entry.end);
255
+ const type = fields.find((field) => field.id === TRACK_TYPE);
256
+ if (!type || readUint(bytes, type.body, type.end - type.body) !== 1) {
257
+ continue;
258
+ }
259
+ const codec = fields.find((field) => field.id === CODEC_ID);
260
+ if (codec)
261
+ codecs.push(text(bytes, codec));
262
+ }
263
+ if (codecs.length === 0)
264
+ return { refused: "it has no video track" };
265
+ const other = codecs.find((codec) => !WEBM_VIDEO.has(codec));
266
+ if (other) {
267
+ return { refused: `its video is ${WEBM_CODEC_NAMES[other] ?? other}` };
268
+ }
269
+ return { contentType: "video/webm" };
270
+ }
271
+ /**
272
+ * The elements between two offsets. An element of unknown size, as a
273
+ * Segment written while recording is, runs to the end; a Cluster ends the
274
+ * walk, since the tracks come before the frames.
275
+ */
276
+ function elements(bytes, start, end) {
277
+ const found = [];
278
+ let at = start;
279
+ while (at < end) {
280
+ const id = vint(bytes, at, true);
281
+ if (!id || id.value === null)
282
+ break;
283
+ const size = vint(bytes, at + id.length, false);
284
+ if (!size)
285
+ break;
286
+ const body = at + id.length + size.length;
287
+ if (id.value === CLUSTER)
288
+ break;
289
+ const elementEnd = size.value === null ? end : Math.min(end, body + size.value);
290
+ found.push({ id: id.value, body, end: elementEnd });
291
+ at = elementEnd;
292
+ }
293
+ return found;
294
+ }
295
+ /**
296
+ * A variable-length integer: the leading zero bits say how many bytes
297
+ * follow. An ID keeps its length marker; a size drops it, and a size of all
298
+ * ones is unknown.
299
+ */
300
+ function vint(bytes, at, keepMarker) {
301
+ const first = bytes[at];
302
+ if (first === undefined || first === 0)
303
+ return null;
304
+ let length = 1;
305
+ while (length <= 8 && !(first & (0x80 >> (length - 1))))
306
+ length++;
307
+ if (at + length > bytes.length)
308
+ return null;
309
+ let value = keepMarker ? first : first & (0xff >> length);
310
+ let allOnes = value === 0xff >> length;
311
+ for (let i = 1; i < length; i++) {
312
+ value = value * 256 + bytes[at + i];
313
+ if (bytes[at + i] !== 0xff)
314
+ allOnes = false;
315
+ }
316
+ return { value: !keepMarker && allOnes ? null : value, length };
317
+ }
318
+ function text(bytes, element) {
319
+ return String.fromCharCode(...bytes.subarray(element.body, element.end))
320
+ .replace(/\0+$/, "")
321
+ .trim();
322
+ }
323
+ // ---------------------------------------------------------------------------
324
+ function fourcc(bytes, at) {
325
+ return at + 4 <= bytes.length
326
+ ? String.fromCharCode(...bytes.subarray(at, at + 4))
327
+ : "";
328
+ }
329
+ function readUint(bytes, at, length) {
330
+ let value = 0;
331
+ for (let i = 0; i < length; i++)
332
+ value = value * 256 + (bytes[at + i] ?? 0);
333
+ return value;
334
+ }
335
+ /**
336
+ * A video and its script, read and checked before anything is uploaded or
337
+ * recorded (SIGHT-7.0.0, 7.0.1), with its steps' starts (7.4).
338
+ */
339
+ export function readRecording(path, scriptPath) {
340
+ const video = readVideo(path);
341
+ const script = readScript(scriptPath);
342
+ const stepStarts = readStepStarts(path, script);
343
+ return { video, script, ...(stepStarts ? { stepStarts } : {}) };
344
+ }
345
+ /**
346
+ * A recording as it is submitted (SIGHT-7.0, 7.4, 13.2): the app's copy of
347
+ * the video, by the file's name, with the script's text and its steps'
348
+ * starts. Nothing goes on the branch.
349
+ */
350
+ export async function submittedRecording(client, solutionId, { video, script, stepStarts }) {
351
+ const storageId = await uploadVideo(client, solutionId, video);
352
+ return {
353
+ kind: "video",
354
+ storageId,
355
+ name: video.name,
356
+ script,
357
+ ...(stepStarts ? { stepStarts } : {}),
358
+ };
359
+ }
360
+ /** Upload a video to the app (SIGHT-7.0), and the stored copy's id back. */
361
+ export function uploadVideo(client, solutionId, video) {
362
+ return uploadToApp(client, api.solutions.mutations.videoUploadUrl, { solutionId }, video);
363
+ }
364
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1,44 @@
1
+ import type { Reference } from "./things.js";
2
+ /**
3
+ * A document submitted by its file, as `solutions/mutations:complete` takes
4
+ * it: on a project with no repository connected the backend saves it on the
5
+ * solution's branch (BRANCH-1.0); with one, the file is the thing submitted.
6
+ */
7
+ export type SubmittedFile = {
8
+ path: string;
9
+ markdownContent: string;
10
+ documentId?: string;
11
+ defaultPrefix?: string;
12
+ };
13
+ /** A document the backend saved on the branch, for its id to go into the file. */
14
+ export type SavedFile = {
15
+ path: string;
16
+ documentId: string;
17
+ version: number;
18
+ };
19
+ /** A document `next` lays out in the working folder (BRANCH-4.0). */
20
+ export type LaidOutFile = {
21
+ documentId: string;
22
+ path: string;
23
+ markdownContent: string;
24
+ defaultPrefix?: string;
25
+ version: number;
26
+ solution?: {
27
+ solutionId: string;
28
+ outcome: string;
29
+ };
30
+ };
31
+ /**
32
+ * The text of each document among the `file:` things submitted: a Markdown
33
+ * file that exists, with its frontmatter taken off and what it says about the
34
+ * document kept beside it. Anything else stays a plain file reference.
35
+ */
36
+ export declare function readSubmittedFiles(made: Reference[]): SubmittedFile[];
37
+ /**
38
+ * After a save on the branch, each file carries its document's id, so submitting
39
+ * the same file again saves that document's next version (BRANCH-4.1).
40
+ */
41
+ export declare function recordSavedIds(files: SubmittedFile[], saved: SavedFile[]): void;
42
+ /** Write the documents an outcome works from into the working folder (BRANCH-4.0). */
43
+ export declare function layOut(files: LaidOutFile[]): void;
44
+ //# sourceMappingURL=working-folder.d.ts.map
@@ -0,0 +1,64 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { parse } from "yaml";
4
+ import { writeRequirementsFile } from "../../schema/file-writer.js";
5
+ import { extractFrontmatterBlock, stripFrontmatterBlock, } from "../../schema/parser-core.js";
6
+ /**
7
+ * The text of each document among the `file:` things submitted: a Markdown
8
+ * file that exists, with its frontmatter taken off and what it says about the
9
+ * document kept beside it. Anything else stays a plain file reference.
10
+ */
11
+ export function readSubmittedFiles(made) {
12
+ const files = [];
13
+ for (const thing of made) {
14
+ if (thing.kind !== "file" || thing.branch !== undefined)
15
+ continue;
16
+ if (!thing.path.endsWith(".md") || !fs.existsSync(thing.path))
17
+ continue;
18
+ const content = fs.readFileSync(thing.path, "utf-8");
19
+ const yaml = extractFrontmatterBlock(content);
20
+ const frontmatter = (yaml ? parse(yaml) : null);
21
+ const id = frontmatter?.document?.id;
22
+ const prefix = frontmatter?.document?.defaultPrefix;
23
+ files.push({
24
+ path: thing.path,
25
+ markdownContent: stripFrontmatterBlock(content),
26
+ ...(typeof id === "string" && id ? { documentId: id } : {}),
27
+ ...(typeof prefix === "string" && prefix
28
+ ? { defaultPrefix: prefix }
29
+ : {}),
30
+ });
31
+ }
32
+ return files;
33
+ }
34
+ /**
35
+ * After a save on the branch, each file carries its document's id, so submitting
36
+ * the same file again saves that document's next version (BRANCH-4.1).
37
+ */
38
+ export function recordSavedIds(files, saved) {
39
+ for (const entry of saved) {
40
+ const file = files.find((f) => f.path === entry.path);
41
+ if (!file)
42
+ continue;
43
+ writeRequirementsFile(entry.path, {
44
+ documentId: entry.documentId,
45
+ defaultPrefix: "keep",
46
+ version: entry.version,
47
+ }, file.markdownContent);
48
+ }
49
+ }
50
+ /** Write the documents an outcome works from into the working folder (BRANCH-4.0). */
51
+ export function layOut(files) {
52
+ for (const file of files) {
53
+ fs.mkdirSync(path.dirname(file.path), { recursive: true });
54
+ writeRequirementsFile(file.path, {
55
+ documentId: file.documentId,
56
+ defaultPrefix: file.defaultPrefix
57
+ ? { set: file.defaultPrefix }
58
+ : "keep",
59
+ version: file.version,
60
+ ...(file.solution ? { belonging: file.solution } : {}),
61
+ }, file.markdownContent);
62
+ }
63
+ }
64
+ //# sourceMappingURL=working-folder.js.map
@@ -4,8 +4,9 @@
4
4
  * direction (`--cloud-contributes` / `--repo-contributes`) or assign authority
5
5
  * (`--repo-wins` / `--cloud-wins`).
6
6
  */
7
+ import { type BranchMergeFlags } from "./branch-merge.js";
7
8
  import { type SyncAuthOptions } from "./sync-common.js";
8
- interface SyncOptions extends SyncAuthOptions {
9
+ interface SyncOptions extends SyncAuthOptions, BranchMergeFlags {
9
10
  cloudContributes?: boolean;
10
11
  repoContributes?: boolean;
11
12
  cloudWins?: boolean;
@@ -8,9 +8,10 @@ import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
  import { attachmentSyncNeeded } from "../sync/attachments.js";
10
10
  import { executePlan } from "../sync/execute.js";
11
- import { acquireCloudSnapshot, acquireLocalSnapshot, compareSnapshots, duplicateIdAbortMessage, filterByScope, resolutionHint, } from "../sync/index.js";
11
+ import { acquireCloudSnapshot, acquireLocalSnapshot, compareSnapshots, duplicateIdAbortMessage, filterByScope, projectSolutions, resolutionHint, } from "../sync/index.js";
12
12
  import { buildPlan, resolveMode, } from "../sync/plan.js";
13
13
  import { brand } from "../utils/brand.js";
14
+ import { branchMergeCommand, branchMergeOf, } from "./branch-merge.js";
14
15
  import { resolveCloudAuth } from "./sync-common.js";
15
16
  /**
16
17
  * The CLI entry point: exits non-zero on unresolved trouble (SYNC-MODE-1.5,
@@ -19,6 +20,12 @@ import { resolveCloudAuth } from "./sync-common.js";
19
20
  * the whole command (the report lines still print).
20
21
  */
21
22
  export async function syncCliAction(scope, options) {
23
+ // BRANCH-8: a branch merge is a sync of its own, with no local files.
24
+ const merge = branchMergeOf(scope, options);
25
+ if (merge) {
26
+ await branchMergeCommand(merge);
27
+ return;
28
+ }
22
29
  const result = await syncCommand(scope, options);
23
30
  if (result === "trouble") {
24
31
  process.exitCode = 1;
@@ -38,9 +45,14 @@ export async function syncCommand(scope, options) {
38
45
  mode = resolveMode(options);
39
46
  }
40
47
  const auth = resolveCloudAuth(options);
41
- const [local, cloud] = await Promise.all([
42
- acquireLocalSnapshot(workspaceRoot),
43
- acquireCloudSnapshot(auth),
48
+ // SOLACCESS-2.2: whether the project's team has solutions decides which
49
+ // files the sync carries.
50
+ const cloud = await acquireCloudSnapshot(auth);
51
+ const [local, solutions] = await Promise.all([
52
+ acquireLocalSnapshot(workspaceRoot, undefined, {
53
+ solutions: cloud.solutions === true,
54
+ }),
55
+ projectSolutions(auth, cloud),
44
56
  ]);
45
57
  // SYNC-MODE-4.4 (and 4.4.0 for --yes): an empty repo is indistinguishable from a wrong working
46
58
  // directory or a failed checkout — never treat it as intent to delete every
@@ -122,7 +134,7 @@ export async function syncCommand(scope, options) {
122
134
  return "clean";
123
135
  }
124
136
  console.log(`\nSyncing with ${brand} cloud...`);
125
- const outcome = await executePlan(plan, cloud, auth, workspaceRoot, mode);
137
+ const outcome = await executePlan(plan, cloud, auth, workspaceRoot, mode, solutions);
126
138
  // Report.
127
139
  if (outcome.cloudCreated)
128
140
  console.log(` Created in cloud: ${outcome.cloudCreated}`);
@@ -179,6 +191,10 @@ export async function syncCommand(scope, options) {
179
191
  for (const { name, warning } of outcome.publishWarnings) {
180
192
  console.log(` ⚠ ${name}: ${warning}`);
181
193
  }
194
+ // SOLDOC-5: a file's path and frontmatter disagree about where it belongs.
195
+ for (const warning of outcome.belongingWarnings ?? []) {
196
+ console.log(` ⚠ ${warning}`);
197
+ }
182
198
  // SYNC-CLI-EDIT-3.1: the cloud stores the web editor's notation, and files
183
199
  // written another way converge to it on push. Each file NAMED, not counted —
184
200
  // a count leaves Jaime to find out which files changed from `git diff`,
package/dist/convex.d.ts CHANGED
@@ -20,6 +20,7 @@ export declare const api: {
20
20
  exportDocument: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
21
21
  exportProject: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
22
22
  exportProjectForShare: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
23
+ foreignDocumentIds: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
23
24
  };
24
25
  mutations: {
25
26
  deleteForSync: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
@@ -68,6 +69,59 @@ export declare const api: {
68
69
  ensureProjectShareToken: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
69
70
  };
70
71
  };
72
+ scores: {
73
+ mutations: {
74
+ create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
75
+ update: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
76
+ };
77
+ };
78
+ mockups: {
79
+ mutations: {
80
+ create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
81
+ };
82
+ };
83
+ solutions: {
84
+ queries: {
85
+ getPlan: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
86
+ getThread: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
87
+ getThings: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
88
+ defaultPlan: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
89
+ readPlanFile: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
90
+ getOutcomePage: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
91
+ getAgentLogs: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
92
+ forSync: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
93
+ rehearsalsOf: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
94
+ rehearsalHistory: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
95
+ };
96
+ mutations: {
97
+ create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
98
+ start: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
99
+ next: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
100
+ complete: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
101
+ ask: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
102
+ stall: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
103
+ answer: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
104
+ approveCheckpoint: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
105
+ removeRehearsal: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
106
+ videoUploadUrl: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
107
+ };
108
+ shares: {
109
+ forCheckout: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
110
+ push: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
111
+ done: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
112
+ };
113
+ images: {
114
+ uploadUrl: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
115
+ inputUploadUrl: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
116
+ copies: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
117
+ };
118
+ branches: {
119
+ branchOf: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
120
+ claim: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
121
+ recordPush: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
122
+ removal: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
123
+ };
124
+ };
71
125
  lib: {
72
126
  authActions: {
73
127
  exchangeAuthorizationCode: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;