@popoverai/dotrequirements 0.32.1 → 0.34.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 (135) hide show
  1. package/dist/cli.js +32 -7
  2. package/dist/cloud.d.ts +18 -0
  3. package/dist/cloud.js +38 -0
  4. package/dist/commands/board/index.d.ts +13 -0
  5. package/dist/commands/board/index.js +96 -0
  6. package/dist/commands/branch-merge.d.ts +78 -0
  7. package/dist/commands/branch-merge.js +129 -0
  8. package/dist/commands/create-requirement-document.js +2 -2
  9. package/dist/commands/diff.js +15 -5
  10. package/dist/commands/init.js +7 -6
  11. package/dist/commands/link.js +27 -21
  12. package/dist/commands/mockup/index.d.ts +11 -0
  13. package/dist/commands/mockup/index.js +82 -0
  14. package/dist/commands/report.js +22 -9
  15. package/dist/commands/review-test.js +3 -2
  16. package/dist/commands/scores/index.d.ts +50 -0
  17. package/dist/commands/scores/index.js +190 -0
  18. package/dist/commands/solution/ask.d.ts +9 -0
  19. package/dist/commands/solution/ask.js +27 -0
  20. package/dist/commands/solution/author.d.ts +19 -0
  21. package/dist/commands/solution/author.js +202 -0
  22. package/dist/commands/solution/branch.d.ts +103 -0
  23. package/dist/commands/solution/branch.js +341 -0
  24. package/dist/commands/solution/brief.d.ts +81 -0
  25. package/dist/commands/solution/brief.js +47 -0
  26. package/dist/commands/solution/checkout.d.ts +27 -0
  27. package/dist/commands/solution/checkout.js +66 -0
  28. package/dist/commands/solution/client.d.ts +16 -0
  29. package/dist/commands/solution/client.js +46 -0
  30. package/dist/commands/solution/contract.d.ts +9 -0
  31. package/dist/commands/solution/contract.js +23 -0
  32. package/dist/commands/solution/create.d.ts +14 -0
  33. package/dist/commands/solution/create.js +52 -0
  34. package/dist/commands/solution/demo-script.d.ts +28 -0
  35. package/dist/commands/solution/demo-script.js +43 -0
  36. package/dist/commands/solution/image.d.ts +60 -0
  37. package/dist/commands/solution/image.js +148 -0
  38. package/dist/commands/solution/index.d.ts +12 -0
  39. package/dist/commands/solution/index.js +281 -0
  40. package/dist/commands/solution/look-up.d.ts +9 -0
  41. package/dist/commands/solution/look-up.js +69 -0
  42. package/dist/commands/solution/next.d.ts +12 -0
  43. package/dist/commands/solution/next.js +48 -0
  44. package/dist/commands/solution/plan-file.d.ts +22 -0
  45. package/dist/commands/solution/plan-file.js +33 -0
  46. package/dist/commands/solution/preview-screenshot.d.ts +21 -0
  47. package/dist/commands/solution/preview-screenshot.js +56 -0
  48. package/dist/commands/solution/pull-request.d.ts +25 -0
  49. package/dist/commands/solution/pull-request.js +117 -0
  50. package/dist/commands/solution/pushing.d.ts +66 -0
  51. package/dist/commands/solution/pushing.js +185 -0
  52. package/dist/commands/solution/rehearse.d.ts +10 -0
  53. package/dist/commands/solution/rehearse.js +24 -0
  54. package/dist/commands/solution/request.d.ts +3 -0
  55. package/dist/commands/solution/request.js +13 -0
  56. package/dist/commands/solution/review.d.ts +27 -0
  57. package/dist/commands/solution/review.js +104 -0
  58. package/dist/commands/solution/run-file.d.ts +42 -0
  59. package/dist/commands/solution/run-file.js +63 -0
  60. package/dist/commands/solution/share.d.ts +62 -0
  61. package/dist/commands/solution/share.js +346 -0
  62. package/dist/commands/solution/stall.d.ts +14 -0
  63. package/dist/commands/solution/stall.js +41 -0
  64. package/dist/commands/solution/start.d.ts +11 -0
  65. package/dist/commands/solution/start.js +20 -0
  66. package/dist/commands/solution/status.d.ts +39 -0
  67. package/dist/commands/solution/status.js +145 -0
  68. package/dist/commands/solution/summary.d.ts +13 -0
  69. package/dist/commands/solution/summary.js +5 -0
  70. package/dist/commands/solution/things.d.ts +118 -0
  71. package/dist/commands/solution/things.js +78 -0
  72. package/dist/commands/solution/thread.d.ts +13 -0
  73. package/dist/commands/solution/thread.js +53 -0
  74. package/dist/commands/solution/upload.d.ts +24 -0
  75. package/dist/commands/solution/upload.js +27 -0
  76. package/dist/commands/solution/video.d.ts +88 -0
  77. package/dist/commands/solution/video.js +364 -0
  78. package/dist/commands/solution/working-folder.d.ts +44 -0
  79. package/dist/commands/solution/working-folder.js +64 -0
  80. package/dist/commands/style-check.js +4 -4
  81. package/dist/commands/sync.d.ts +2 -1
  82. package/dist/commands/sync.js +21 -5
  83. package/dist/convex.d.ts +64 -10
  84. package/dist/convex.js +75 -10
  85. package/dist/harness/finalize.js +16 -8
  86. package/dist/harness/reportingStatus.js +2 -11
  87. package/dist/push/core.js +7 -2
  88. package/dist/rehearsal/derive.d.ts +23 -0
  89. package/dist/rehearsal/derive.js +112 -0
  90. package/dist/rehearsal/driver.d.ts +44 -0
  91. package/dist/rehearsal/driver.js +337 -0
  92. package/dist/rehearsal/earlier.d.ts +25 -0
  93. package/dist/rehearsal/earlier.js +138 -0
  94. package/dist/rehearsal/index.d.ts +5 -0
  95. package/dist/rehearsal/index.js +4 -0
  96. package/dist/rehearsal/live.d.ts +72 -0
  97. package/dist/rehearsal/live.js +301 -0
  98. package/dist/rehearsal/report.d.ts +4 -0
  99. package/dist/rehearsal/report.js +69 -0
  100. package/dist/rehearsal/types.d.ts +377 -0
  101. package/dist/rehearsal/types.js +8 -0
  102. package/dist/requirements/cloud-ai.d.ts +0 -6
  103. package/dist/requirements/cloud-ai.js +2 -11
  104. package/dist/requirements/cloud-coverage.js +4 -4
  105. package/dist/requirements/index.d.ts +6 -0
  106. package/dist/requirements/index.js +24 -0
  107. package/dist/schema/browser.d.ts +1 -1
  108. package/dist/schema/browser.js +3 -1
  109. package/dist/schema/file-writer.d.ts +5 -0
  110. package/dist/schema/file-writer.js +4 -0
  111. package/dist/schema/parser.d.ts +11 -2
  112. package/dist/schema/parser.js +11 -7
  113. package/dist/schema/schemas.d.ts +40 -0
  114. package/dist/schema/schemas.js +4 -0
  115. package/dist/sync/execute.d.ts +10 -1
  116. package/dist/sync/execute.js +76 -24
  117. package/dist/sync/index.d.ts +1 -1
  118. package/dist/sync/index.js +1 -1
  119. package/dist/sync/publish.d.ts +7 -0
  120. package/dist/sync/publish.js +2 -2
  121. package/dist/sync/segment.js +4 -1
  122. package/dist/sync/snapshot.d.ts +27 -5
  123. package/dist/sync/snapshot.js +92 -15
  124. package/dist/sync/solution-documents.d.ts +64 -0
  125. package/dist/sync/solution-documents.js +97 -0
  126. package/dist/sync/types.d.ts +19 -1
  127. package/dist/utils/command-error.d.ts +19 -0
  128. package/dist/utils/command-error.js +44 -0
  129. package/dist/utils/oauth-flow.js +2 -2
  130. package/dist/utils/project-selector.js +4 -4
  131. package/dist/version-messages.d.ts +28 -0
  132. package/dist/version-messages.js +87 -0
  133. package/package.json +3 -2
  134. package/dist/config.d.ts +0 -5
  135. package/dist/config.js +0 -12
@@ -86,6 +86,8 @@ export declare const MetadataSchema: z.ZodObject<{
86
86
  id: z.ZodOptional<z.ZodString>;
87
87
  title: z.ZodOptional<z.ZodString>;
88
88
  defaultPrefix: z.ZodOptional<z.ZodString>;
89
+ solution: z.ZodOptional<z.ZodString>;
90
+ outcome: z.ZodOptional<z.ZodString>;
89
91
  attachments: z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
90
92
  url: z.ZodString;
91
93
  title: z.ZodOptional<z.ZodString>;
@@ -112,6 +114,8 @@ export declare const MetadataSchema: z.ZodObject<{
112
114
  id?: string | undefined;
113
115
  title?: string | undefined;
114
116
  defaultPrefix?: string | undefined;
117
+ solution?: string | undefined;
118
+ outcome?: string | undefined;
115
119
  attachments?: {
116
120
  url: string;
117
121
  title?: string | undefined;
@@ -120,6 +124,8 @@ export declare const MetadataSchema: z.ZodObject<{
120
124
  id?: string | undefined;
121
125
  title?: string | undefined;
122
126
  defaultPrefix?: string | undefined;
127
+ solution?: string | undefined;
128
+ outcome?: string | undefined;
123
129
  attachments?: {
124
130
  url: string;
125
131
  title?: string | undefined;
@@ -133,6 +139,8 @@ export declare const MetadataSchema: z.ZodObject<{
133
139
  id?: string | undefined;
134
140
  title?: string | undefined;
135
141
  defaultPrefix?: string | undefined;
142
+ solution?: string | undefined;
143
+ outcome?: string | undefined;
136
144
  attachments?: {
137
145
  url: string;
138
146
  title?: string | undefined;
@@ -146,6 +154,8 @@ export declare const MetadataSchema: z.ZodObject<{
146
154
  id?: string | undefined;
147
155
  title?: string | undefined;
148
156
  defaultPrefix?: string | undefined;
157
+ solution?: string | undefined;
158
+ outcome?: string | undefined;
149
159
  attachments?: {
150
160
  url: string;
151
161
  title?: string | undefined;
@@ -213,6 +223,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
213
223
  id: z.ZodOptional<z.ZodString>;
214
224
  title: z.ZodOptional<z.ZodString>;
215
225
  defaultPrefix: z.ZodOptional<z.ZodString>;
226
+ solution: z.ZodOptional<z.ZodString>;
227
+ outcome: z.ZodOptional<z.ZodString>;
216
228
  attachments: z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
217
229
  url: z.ZodString;
218
230
  title: z.ZodOptional<z.ZodString>;
@@ -239,6 +251,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
239
251
  id?: string | undefined;
240
252
  title?: string | undefined;
241
253
  defaultPrefix?: string | undefined;
254
+ solution?: string | undefined;
255
+ outcome?: string | undefined;
242
256
  attachments?: {
243
257
  url: string;
244
258
  title?: string | undefined;
@@ -247,6 +261,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
247
261
  id?: string | undefined;
248
262
  title?: string | undefined;
249
263
  defaultPrefix?: string | undefined;
264
+ solution?: string | undefined;
265
+ outcome?: string | undefined;
250
266
  attachments?: {
251
267
  url: string;
252
268
  title?: string | undefined;
@@ -260,6 +276,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
260
276
  id?: string | undefined;
261
277
  title?: string | undefined;
262
278
  defaultPrefix?: string | undefined;
279
+ solution?: string | undefined;
280
+ outcome?: string | undefined;
263
281
  attachments?: {
264
282
  url: string;
265
283
  title?: string | undefined;
@@ -273,6 +291,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
273
291
  id?: string | undefined;
274
292
  title?: string | undefined;
275
293
  defaultPrefix?: string | undefined;
294
+ solution?: string | undefined;
295
+ outcome?: string | undefined;
276
296
  attachments?: {
277
297
  url: string;
278
298
  title?: string | undefined;
@@ -288,6 +308,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
288
308
  id: z.ZodOptional<z.ZodString>;
289
309
  title: z.ZodOptional<z.ZodString>;
290
310
  defaultPrefix: z.ZodOptional<z.ZodString>;
311
+ solution: z.ZodOptional<z.ZodString>;
312
+ outcome: z.ZodOptional<z.ZodString>;
291
313
  attachments: z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
292
314
  url: z.ZodString;
293
315
  title: z.ZodOptional<z.ZodString>;
@@ -314,6 +336,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
314
336
  id?: string | undefined;
315
337
  title?: string | undefined;
316
338
  defaultPrefix?: string | undefined;
339
+ solution?: string | undefined;
340
+ outcome?: string | undefined;
317
341
  attachments?: {
318
342
  url: string;
319
343
  title?: string | undefined;
@@ -322,6 +346,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
322
346
  id?: string | undefined;
323
347
  title?: string | undefined;
324
348
  defaultPrefix?: string | undefined;
349
+ solution?: string | undefined;
350
+ outcome?: string | undefined;
325
351
  attachments?: {
326
352
  url: string;
327
353
  title?: string | undefined;
@@ -335,6 +361,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
335
361
  id?: string | undefined;
336
362
  title?: string | undefined;
337
363
  defaultPrefix?: string | undefined;
364
+ solution?: string | undefined;
365
+ outcome?: string | undefined;
338
366
  attachments?: {
339
367
  url: string;
340
368
  title?: string | undefined;
@@ -348,6 +376,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
348
376
  id?: string | undefined;
349
377
  title?: string | undefined;
350
378
  defaultPrefix?: string | undefined;
379
+ solution?: string | undefined;
380
+ outcome?: string | undefined;
351
381
  attachments?: {
352
382
  url: string;
353
383
  title?: string | undefined;
@@ -363,6 +393,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
363
393
  id: z.ZodOptional<z.ZodString>;
364
394
  title: z.ZodOptional<z.ZodString>;
365
395
  defaultPrefix: z.ZodOptional<z.ZodString>;
396
+ solution: z.ZodOptional<z.ZodString>;
397
+ outcome: z.ZodOptional<z.ZodString>;
366
398
  attachments: z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
367
399
  url: z.ZodString;
368
400
  title: z.ZodOptional<z.ZodString>;
@@ -389,6 +421,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
389
421
  id?: string | undefined;
390
422
  title?: string | undefined;
391
423
  defaultPrefix?: string | undefined;
424
+ solution?: string | undefined;
425
+ outcome?: string | undefined;
392
426
  attachments?: {
393
427
  url: string;
394
428
  title?: string | undefined;
@@ -397,6 +431,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
397
431
  id?: string | undefined;
398
432
  title?: string | undefined;
399
433
  defaultPrefix?: string | undefined;
434
+ solution?: string | undefined;
435
+ outcome?: string | undefined;
400
436
  attachments?: {
401
437
  url: string;
402
438
  title?: string | undefined;
@@ -410,6 +446,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
410
446
  id?: string | undefined;
411
447
  title?: string | undefined;
412
448
  defaultPrefix?: string | undefined;
449
+ solution?: string | undefined;
450
+ outcome?: string | undefined;
413
451
  attachments?: {
414
452
  url: string;
415
453
  title?: string | undefined;
@@ -423,6 +461,8 @@ export declare const RequirementsFileSchema: z.ZodObject<{
423
461
  id?: string | undefined;
424
462
  title?: string | undefined;
425
463
  defaultPrefix?: string | undefined;
464
+ solution?: string | undefined;
465
+ outcome?: string | undefined;
426
466
  attachments?: {
427
467
  url: string;
428
468
  title?: string | undefined;
@@ -147,6 +147,10 @@ export const MetadataSchema = z.object({
147
147
  id: z.string().optional(), // Cloud document link - filled by push when creating
148
148
  title: z.string().optional(), // Legacy fallback — the body's leading H1 is the title
149
149
  defaultPrefix: z.string().optional(), // Editor hint for new requirement keys
150
+ // SOLDOC-5: the solution (by id) and the outcome (by name) this document
151
+ // belongs to. Written by the sync; the truth once present.
152
+ solution: z.string().optional(),
153
+ outcome: z.string().optional(),
150
154
  // ATTACH-10: links attached to the document (a Miro board, a reference
151
155
  // page). Round-trips through sync — the cloud stores them as document
152
156
  // metadata, the file carries them here.
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { PlannedAction, SyncMode } from "./plan.js";
11
11
  import type { CloudAuth } from "./snapshot.js";
12
+ import { type SolutionForSync } from "./solution-documents.js";
12
13
  import type { CloudSnapshot, DocumentComparison } from "./types.js";
13
14
  export interface SyncOutcome {
14
15
  cloudCreated: number;
@@ -59,6 +60,8 @@ export interface SyncOutcome {
59
60
  name: string;
60
61
  warning: string;
61
62
  }>;
63
+ /** SOLDOC-5.1/5.2: a file whose path and frontmatter disagree about where it belongs. Optional so an outcome built before the field existed still reads. */
64
+ belongingWarnings?: string[];
62
65
  /** SYNC-CLI-EDIT-3.1: files whose notation the cloud changed on storing —
63
66
  * list markers, emphasis characters, spacing. Counted so the sync can say
64
67
  * the reformatting happened and why, instead of leaving Jaime to discover
@@ -85,5 +88,11 @@ export interface SyncOutcome {
85
88
  * writes require a project slug in `auth`; a read-only share run has none and
86
89
  * its plan carries no cloud-writing actions.
87
90
  */
88
- export declare function executePlan(plan: PlannedAction[], cloud: CloudSnapshot, auth: CloudAuth, workspaceRoot: string, mode: SyncMode): Promise<SyncOutcome>;
91
+ export declare function executePlan(plan: PlannedAction[], cloud: CloudSnapshot, auth: CloudAuth, workspaceRoot: string, mode: SyncMode,
92
+ /**
93
+ * The project's solutions, as `projectSolutions` read them; null (the
94
+ * default) when the project's team doesn't have solutions
95
+ * (SOLACCESS-2.2), so no file is placed as a solution's.
96
+ */
97
+ solutions?: SolutionForSync[] | null): Promise<SyncOutcome>;
89
98
  //# sourceMappingURL=execute.d.ts.map
@@ -10,13 +10,15 @@
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
12
  import { ConvexHttpClient } from "convex/browser";
13
- import { getConvexUrl } from "../config.js";
13
+ import { convexUrl } from "../cloud.js";
14
14
  import { api } from "../convex.js";
15
15
  import { parseFilesForPushIndividually, WEB_APP_URL } from "../push/core.js";
16
16
  import { composeMarkdownWithTitle, splitLeadingH1, writeRequirementsFile, } from "../schema/index.js";
17
+ import { commandErrorMessage } from "../utils/command-error.js";
17
18
  import { cloudAttachmentRemovals, resolveAttachments } from "./attachments.js";
18
- import { resolveLocalPath } from "./local-files.js";
19
+ import { resolveLocalPath, sanitizeFileName } from "./local-files.js";
19
20
  import { publishToCloud } from "./publish.js";
21
+ import { requirementsDocByName, resolveBelonging, solutionDirOf, solutionDocumentPath, } from "./solution-documents.js";
20
22
  function emptyOutcome() {
21
23
  return {
22
24
  cloudCreated: 0,
@@ -34,6 +36,7 @@ function emptyOutcome() {
34
36
  writeBackWarnings: [],
35
37
  importWarnings: [],
36
38
  publishWarnings: [],
39
+ belongingWarnings: [],
37
40
  normalized: [],
38
41
  renames: [],
39
42
  inferredPrefixes: [],
@@ -44,9 +47,15 @@ function emptyOutcome() {
44
47
  * writes require a project slug in `auth`; a read-only share run has none and
45
48
  * its plan carries no cloud-writing actions.
46
49
  */
47
- export async function executePlan(plan, cloud, auth, workspaceRoot, mode) {
50
+ export async function executePlan(plan, cloud, auth, workspaceRoot, mode,
51
+ /**
52
+ * The project's solutions, as `projectSolutions` read them; null (the
53
+ * default) when the project's team doesn't have solutions
54
+ * (SOLACCESS-2.2), so no file is placed as a solution's.
55
+ */
56
+ solutions = null) {
48
57
  const outcome = emptyOutcome();
49
- const client = new ConvexHttpClient(getConvexUrl());
58
+ const client = new ConvexHttpClient(convexUrl());
50
59
  const cloudById = new Map(cloud.documents.map((d) => [d.documentId, d]));
51
60
  const requirementsDir = path.join(workspaceRoot, ".requirements");
52
61
  const usedPaths = new Set();
@@ -63,7 +72,7 @@ export async function executePlan(plan, cloud, auth, workspaceRoot, mode) {
63
72
  outcome.invalid.push(doc);
64
73
  break;
65
74
  case "cloud_write":
66
- await cloudWrite(auth, doc, cloudById, outcome, mode);
75
+ await cloudWrite(auth, doc, cloudById, outcome, mode, solutions);
67
76
  break;
68
77
  case "cloud_delete":
69
78
  await cloudDelete(client, auth, doc, outcome);
@@ -83,14 +92,14 @@ export async function executePlan(plan, cloud, auth, workspaceRoot, mode) {
83
92
  await syncResidualAttachments(client, auth, doc, action, outcome, mode);
84
93
  }
85
94
  catch (err) {
86
- outcome.errors.push({ name, error: errorDisplayMessage(err) });
95
+ outcome.errors.push({ name, error: commandErrorMessage(err) });
87
96
  }
88
97
  }
89
98
  return outcome;
90
99
  }
91
100
  /**
92
101
  * Attachment-only operations for whatever the body action didn't cover.
93
- * The push goes through setAttachmentsForSync — a metadata patch, never the
102
+ * The push goes through syncAttachments — a metadata patch, never the
94
103
  * publish path, so naming a link cannot displace anyone's working document.
95
104
  */
96
105
  async function syncResidualAttachments(client, auth, doc, action, outcome, mode) {
@@ -110,12 +119,11 @@ async function syncResidualAttachments(client, auth, doc, action, outcome, mode)
110
119
  if (!auth.projectSlug) {
111
120
  throw new Error("Cannot sync attachments without project credentials.");
112
121
  }
113
- await client.mutation(api.documents.mutations.setAttachmentsForSync, {
122
+ await client.mutation(api.documents.mutations.syncAttachments, {
114
123
  projectAuth: {
115
124
  projectSlug: auth.projectSlug,
116
125
  projectSecret: auth.projectSecret,
117
126
  },
118
- target: { type: "project", slug: auth.projectSlug },
119
127
  documentId: doc.documentId,
120
128
  attachments: res.cloudFinal,
121
129
  // Compare-and-set: the resolution was computed from the snapshot's
@@ -161,18 +169,7 @@ async function syncResidualAttachments(client, auth, doc, action, outcome, mode)
161
169
  outcome.attachmentWrites++;
162
170
  }
163
171
  }
164
- /**
165
- * Servers throw ConvexError({kind, message}) for expected failures; production
166
- * redacts the Error message to "Server Error" but preserves error.data.
167
- * Surface the human-readable message rather than the blob.
168
- */
169
- function errorDisplayMessage(err) {
170
- const data = err.data;
171
- if (data && typeof data.message === "string")
172
- return data.message;
173
- return err instanceof Error ? err.message : String(err);
174
- }
175
- async function cloudWrite(auth, doc, cloudById, outcome, mode) {
172
+ async function cloudWrite(auth, doc, cloudById, outcome, mode, solutions) {
176
173
  if (!auth.projectSlug || !doc.filePath) {
177
174
  throw new Error("Cannot write to the cloud without project credentials.");
178
175
  }
@@ -181,6 +178,24 @@ async function cloudWrite(auth, doc, cloudById, outcome, mode) {
181
178
  if (!parsed)
182
179
  throw new Error(`Could not parse ${path.basename(doc.filePath)}`);
183
180
  const meta = parsed.metadata.document;
181
+ // SOLDOC-3/4/5: what the file's name and place say about it. A file in a
182
+ // solution directory with no frontmatter declares its belonging by its
183
+ // path; one with frontmatter keeps that, and a disagreeing path is reported.
184
+ // SOLACCESS-2.2: without solutions, a file is placed as it was before them.
185
+ const withSolutions = solutions !== null;
186
+ const resolved = withSolutions
187
+ ? resolveBelonging(doc.filePath, { solution: meta?.solution, outcome: meta?.outcome }, solutionDirOf(doc.filePath), solutions)
188
+ : { belonging: undefined, warning: undefined };
189
+ if (resolved.warning) {
190
+ outcome.belongingWarnings = [
191
+ ...(outcome.belongingWarnings ?? []),
192
+ resolved.warning,
193
+ ];
194
+ }
195
+ const belonging = resolved.belonging;
196
+ const isRequirementsDoc = withSolutions
197
+ ? requirementsDocByName(doc.filePath)
198
+ : undefined;
184
199
  // SYNC-TITLE-1.1: a legacy file whose title lives only in frontmatter (no body
185
200
  // H1) must reach the cloud self-describing. Materialize the title as the body's
186
201
  // leading H1 BEFORE the push — so the cloud row carries the H1 on first write
@@ -225,6 +240,8 @@ async function cloudWrite(auth, doc, cloudById, outcome, mode) {
225
240
  : []
226
241
  : undefined,
227
242
  runMarker: parsed.metadata.ctsRun,
243
+ solution: belonging,
244
+ isRequirementsDoc,
228
245
  });
229
246
  const documentId = saveResult.documentId;
230
247
  // ATTACH-10.3.1.0: the publish above is the push that removes cloud
@@ -330,6 +347,9 @@ async function cloudWrite(auth, doc, cloudById, outcome, mode) {
330
347
  // the title.
331
348
  writeRequirementsFile(doc.filePath, {
332
349
  documentId,
350
+ // SOLDOC-4.1: the belonging the path declared is written into the
351
+ // frontmatter, where it is the truth from now on.
352
+ belonging,
333
353
  // The prefix just pushed is the file's own (or the one inferred from
334
354
  // its first key, DOC-HEADER-11.4, which the write-back materializes).
335
355
  defaultPrefix: meta?.defaultPrefix
@@ -361,12 +381,11 @@ async function cloudDelete(client, auth, doc, outcome) {
361
381
  if (!auth.projectSlug || !doc.documentId) {
362
382
  throw new Error("Cannot delete from the cloud without project credentials.");
363
383
  }
364
- await client.mutation(api.documents.mutations.deleteForCli, {
384
+ await client.mutation(api.documents.mutations.deleteForSync, {
365
385
  projectAuth: {
366
386
  projectSlug: auth.projectSlug,
367
387
  projectSecret: auth.projectSecret,
368
388
  },
369
- target: { type: "project", slug: auth.projectSlug },
370
389
  documentId: doc.documentId,
371
390
  });
372
391
  outcome.cloudDeleted++;
@@ -377,8 +396,9 @@ function localWrite(doc, cloudById, usedPaths, requirementsDir, outcome, mode) {
377
396
  throw new Error(`No cloud content for ${doc.title}`);
378
397
  if (!fs.existsSync(requirementsDir))
379
398
  fs.mkdirSync(requirementsDir, { recursive: true });
380
- const filePath = resolveLocalPath(cloudDoc, doc.filePath, usedPaths, requirementsDir);
399
+ const filePath = placeCloudDocument(cloudDoc, doc.filePath, usedPaths, requirementsDir);
381
400
  usedPaths.add(filePath);
401
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
382
402
  // DOC-HEADER-11.6/11.7: a cloud document with no prefix means "nothing to
383
403
  // impose" under an additive sync and "no prefix" under cloud-wins authority
384
404
  // — so a prefix Jaime wrote is only ever removed by the mode they ran to
@@ -402,10 +422,42 @@ function localWrite(doc, cloudById, usedPaths, requirementsDir, outcome, mode) {
402
422
  defaultPrefix,
403
423
  attachments,
404
424
  version: cloudDoc.version,
425
+ belonging: cloudDoc.solution
426
+ ? {
427
+ solutionId: cloudDoc.solution.solutionId,
428
+ outcome: cloudDoc.solution.outcome,
429
+ }
430
+ : undefined,
405
431
  }, cloudDoc.body);
406
432
  if (changed)
407
433
  outcome.localWritten++;
408
434
  }
435
+ /**
436
+ * SOLDOC-6: where a pulled document lands. A file it is already linked to
437
+ * keeps its path (SYNC-DISCOVERY-2.0); a document that belongs to a solution
438
+ * lands in that solution's directory, named for its outcome; a document
439
+ * that is not a requirements document and belongs to no solution has no
440
+ * place, and the pull says so rather than guessing.
441
+ */
442
+ function placeCloudDocument(cloudDoc, existingPath, usedPaths, requirementsDir) {
443
+ if (existingPath)
444
+ return existingPath;
445
+ const isRequirementsDoc = cloudDoc.isRequirementsDoc !== false;
446
+ if (cloudDoc.solution) {
447
+ // SOLDOC-9: the solution's own directory name; a backend from before it
448
+ // was sent leaves it out, and the title's slug stands in.
449
+ const candidate = solutionDocumentPath(requirementsDir, cloudDoc.solution.directory ??
450
+ sanitizeFileName(cloudDoc.solution.title ?? cloudDoc.solution.solutionId), cloudDoc.solution.outcome, isRequirementsDoc);
451
+ if (usedPaths.has(candidate) || fs.existsSync(candidate)) {
452
+ throw new Error(`"${cloudDoc.title}" belongs at ${path.relative(path.dirname(requirementsDir), candidate)}, but another file is already there.`);
453
+ }
454
+ return candidate;
455
+ }
456
+ if (!isRequirementsDoc) {
457
+ throw new Error(`"${cloudDoc.title}" is not a requirements document and belongs to no solution, so the pull has nowhere to put it.`);
458
+ }
459
+ return resolveLocalPath(cloudDoc, existingPath, usedPaths, requirementsDir);
460
+ }
409
461
  function localDelete(doc, outcome) {
410
462
  if (!doc.filePath)
411
463
  return;
@@ -6,7 +6,7 @@ import type { ComparisonResult, DocumentComparison } from "./types.js";
6
6
  export { compareSnapshots, duplicateIdAbortMessage, duplicateLocalDocumentIds, } from "./compare.js";
7
7
  export { displayName, formatUnitDetail, formatVerdictLine, resolutionHint, scopeToken, } from "./render.js";
8
8
  export { segmentBody } from "./segment.js";
9
- export { acquireCloudSnapshot, acquireLocalSnapshot, type CloudAuth, readLocalDocument, } from "./snapshot.js";
9
+ export { acquireCloudSnapshot, acquireLocalSnapshot, type CloudAuth, foreignDocumentNotes, projectSolutions, readLocalDocument, } from "./snapshot.js";
10
10
  export type { CloudDocument, CloudSnapshot, ComparisonResult, DocumentComparison, LocalDocument, LocalSnapshot, UnitDetail, Verdict, } from "./types.js";
11
11
  export { verdictLabel } from "./types.js";
12
12
  /**
@@ -6,7 +6,7 @@ import * as path from "node:path";
6
6
  export { compareSnapshots, duplicateIdAbortMessage, duplicateLocalDocumentIds, } from "./compare.js";
7
7
  export { displayName, formatUnitDetail, formatVerdictLine, resolutionHint, scopeToken, } from "./render.js";
8
8
  export { segmentBody } from "./segment.js";
9
- export { acquireCloudSnapshot, acquireLocalSnapshot, readLocalDocument, } from "./snapshot.js";
9
+ export { acquireCloudSnapshot, acquireLocalSnapshot, foreignDocumentNotes, projectSolutions, readLocalDocument, } from "./snapshot.js";
10
10
  export { verdictLabel } from "./types.js";
11
11
  /**
12
12
  * Does a document match one scope token? A token may name a local file (path or
@@ -32,6 +32,13 @@ export interface PublishParams {
32
32
  title?: string;
33
33
  }>;
34
34
  runMarker?: string;
35
+ /** SOLDOC-4/5: the belonging the file declares. */
36
+ solution?: {
37
+ solutionId: string;
38
+ outcome: string;
39
+ };
40
+ /** SOLDOC-3: what the file's name says; undefined where the rule doesn't reach. */
41
+ isRequirementsDoc?: boolean;
35
42
  }
36
43
  /**
37
44
  * What the server reports back: the document, any run-marker complaint, and any
@@ -11,7 +11,7 @@
11
11
  * repo unable to push. Recoverable rather than lossy — see
12
12
  * docs/working/publishing-and-unpublished-changes.md.
13
13
  */
14
- import { DEFAULT_API_BASE_URL } from "../requirements/cloud-ai.js";
14
+ import { webApiUrl } from "../cloud.js";
15
15
  /**
16
16
  * POST to `/api/publish`. Returns the saved document; throws with the server's
17
17
  * own message on failure, which is what Jaime reads in the sync output.
@@ -21,7 +21,7 @@ import { DEFAULT_API_BASE_URL } from "../requirements/cloud-ai.js";
21
21
  * apply next time.
22
22
  */
23
23
  export async function publishToCloud(params) {
24
- const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
24
+ const apiBaseUrl = webApiUrl();
25
25
  const response = await fetch(`${apiBaseUrl}/api/publish`, {
26
26
  method: "POST",
27
27
  headers: { "Content-Type": "application/json" },
@@ -6,7 +6,10 @@
6
6
  * normalized text (prose has no stable id, so a reworded passage reads as a
7
7
  * removal-plus-addition — a conflict, per DIFF-4.3).
8
8
  */
9
- import { canonicalRequirementKey, canonicalRequirementPath, getAllRequirements, parseRequirementBlock, splitRequirementFenceContent, } from "../schema/index.js";
9
+ // From the fs-free modules, not the schema index, so the backend can bundle
10
+ // the comparator for merging a solution's branch (BRANCH-7).
11
+ import { getAllRequirements, parseRequirementBlock, splitRequirementFenceContent, } from "../schema/parser-core.js";
12
+ import { canonicalRequirementKey, canonicalRequirementPath, } from "../schema/schemas.js";
10
13
  /** Matches a fenced dotrequirements block; group 1 is its inner content. */
11
14
  const FENCE = /```dotrequirements\n([\s\S]*?)```/gm;
12
15
  /**
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Snapshot acquisition for the comparator — kept separate from comparison so
3
3
  * the comparator stays a pure function (compare.ts). The local half reads the
4
- * workspace; the cloud half is one exportProjectForCli query.
4
+ * workspace; the cloud half is one export query.
5
5
  */
6
+ import type { SolutionForSync } from "./solution-documents.js";
6
7
  import type { CloudSnapshot, LocalDocument, LocalSnapshot } from "./types.js";
7
8
  /**
8
9
  * Read and classify one requirements file into a LocalDocument. A file whose
@@ -15,16 +16,37 @@ export declare function readLocalDocument(filePath: string): LocalDocument;
15
16
  * Build the local snapshot from the workspace, or from an explicit set of file
16
17
  * paths (scope). Files are always read fresh from disk.
17
18
  */
18
- export declare function acquireLocalSnapshot(workspaceRoot: string, filePaths?: string[]): Promise<LocalSnapshot>;
19
+ export declare function acquireLocalSnapshot(workspaceRoot: string, filePaths?: string[], options?: {
20
+ solutions?: boolean;
21
+ }): Promise<LocalSnapshot>;
22
+ /**
23
+ * The project's solutions as the sync places files by them (SOLDOC-4), or
24
+ * null when the project's team doesn't have solutions, so the sync behaves
25
+ * as it did before solutions (SOLACCESS-2.2). Whether it has them is the
26
+ * export's answer — the one the cloud side was read with — so the local
27
+ * side can't follow a different one. A share run places nothing, so it
28
+ * needs no list. A failure to fetch the list fails the sync, as does a
29
+ * switch flipped between the two reads: guessing would leave solution
30
+ * documents out of the repo's side, and a repo-wins sync would delete them
31
+ * from the cloud.
32
+ */
33
+ export declare function projectSolutions(auth: CloudAuth, cloud: CloudSnapshot): Promise<SolutionForSync[] | null>;
19
34
  export interface CloudAuth {
20
35
  /** projectSlug + projectSecret for full access, or a bare share token. */
21
36
  projectSlug?: string;
22
37
  projectSecret: string;
23
38
  }
24
39
  /**
25
- * Build the cloud snapshot with one exportProjectForCli query. A share token is
26
- * passed as the projectSecret with no slug (the read-only wrapper resolves the
27
- * project from the token) — the read-only path never writes back.
40
+ * Build the cloud snapshot with one export query: the project's own, with a
41
+ * project secret, or the share endpoint, with a bare share token (the token
42
+ * names the project) — the read-only path never writes back.
28
43
  */
29
44
  export declare function acquireCloudSnapshot(auth: CloudAuth): Promise<CloudSnapshot>;
45
+ /**
46
+ * SYNC-FAIL-5: of the ids the workspace's files carry, the ones that name
47
+ * another project's document, each with the words a push of that file gets,
48
+ * so `dotreq diff` says what `dotreq sync` will. A backend that can't answer
49
+ * leaves the diff as it was rather than failing it.
50
+ */
51
+ export declare function foreignDocumentNotes(auth: CloudAuth, documentIds: string[]): Promise<Map<string, string>>;
30
52
  //# sourceMappingURL=snapshot.d.ts.map