@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
package/dist/cli.js CHANGED
@@ -2,10 +2,11 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { Command } from "commander";
5
+ import { Command, Option } from "commander";
6
6
  import { acceptanceTestCommand } from "./commands/acceptance-test.js";
7
7
  import { aiSetupCommand } from "./commands/ai-setup.js";
8
8
  import { pullAlias, pushAlias } from "./commands/aliases.js";
9
+ import { registerBoard } from "./commands/board/index.js";
9
10
  import { registerCodebaseToSpec } from "./commands/codebase-to-spec/index.js";
10
11
  import { createRequirementDocumentCommand } from "./commands/create-requirement-document.js";
11
12
  import { diffCommand } from "./commands/diff.js";
@@ -16,17 +17,22 @@ import { initCommand } from "./commands/init.js";
16
17
  import { linkCommand } from "./commands/link.js";
17
18
  import { listCommand } from "./commands/list.js";
18
19
  import { mcpCommand } from "./commands/mcp.js";
20
+ import { registerMockup } from "./commands/mockup/index.js";
19
21
  import { prepareCommand } from "./commands/prepare.js";
20
22
  import { reportCommand } from "./commands/report.js";
21
23
  import { requirementsForCommand } from "./commands/requirements-for.js";
22
24
  import { reviewTestCommand } from "./commands/review-test.js";
25
+ import { registerScores } from "./commands/scores/index.js";
23
26
  import { searchCommand } from "./commands/search.js";
27
+ import { registerSolution } from "./commands/solution/index.js";
24
28
  import { styleCheckCommand } from "./commands/style-check.js";
25
29
  import { syncCliAction } from "./commands/sync.js";
26
30
  import { testsForCommand } from "./commands/tests-for.js";
27
31
  import { validateCommand } from "./commands/validate.js";
32
+ import { commandErrorMessage } from "./utils/command-error.js";
28
33
  import { loadEnvFile } from "./utils/env.js";
29
34
  import { setPreferEnvCredentials } from "./utils/project-settings.js";
35
+ import { enableVersionMessages, formatVersionMessages, markVersionMessagesShown, versionMessages, } from "./version-messages.js";
30
36
  // Read version from package.json
31
37
  const __filename = fileURLToPath(import.meta.url);
32
38
  const __dirname = dirname(__filename);
@@ -34,6 +40,20 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"),
34
40
  const VERSION = packageJson.version;
35
41
  // Load environment variables from .env.local if it exists
36
42
  loadEnvFile();
43
+ // CLI-MSG-1: a run that reaches the cloud hears what it has to say to this
44
+ // version.
45
+ enableVersionMessages();
46
+ /**
47
+ * The cloud's messages for this version, after the command's own output. On
48
+ * stderr, so a command's piped output stays clean.
49
+ */
50
+ async function printVersionMessages() {
51
+ const messages = await versionMessages();
52
+ if (messages.length > 0) {
53
+ console.error(`${formatVersionMessages(messages)}\n`);
54
+ }
55
+ markVersionMessagesShown();
56
+ }
37
57
  /**
38
58
  * Wrap command handlers to catch errors and display them cleanly
39
59
  * without showing stack traces to users
@@ -44,14 +64,11 @@ function wrapCommand(fn) {
44
64
  await fn(...args);
45
65
  }
46
66
  catch (error) {
47
- if (error instanceof Error) {
48
- console.error(`\nError: ${error.message}\n`);
49
- }
50
- else {
51
- console.error(`\nError: ${String(error)}\n`);
52
- }
67
+ console.error(`\nError: ${commandErrorMessage(error)}\n`);
68
+ await printVersionMessages();
53
69
  process.exit(1);
54
70
  }
71
+ await printVersionMessages();
55
72
  };
56
73
  }
57
74
  const program = new Command();
@@ -102,6 +119,10 @@ program
102
119
  .option("-s, --share <token>", "Read-only share token (no setup required)")
103
120
  .option("-p, --project <id>", "Override the project slug")
104
121
  .option("-y, --yes", "Skip the deletion confirmation prompt")
122
+ // BRANCH-8: merging a solution's branch into main. Kept out of --help, as
123
+ // the solution verbs are, until the solution flow is offered.
124
+ .addOption(new Option("--branch-contributes <solution-id>", "Merge the solution's branch into main: its additions only").hideHelp())
125
+ .addOption(new Option("--branch-wins <solution-id>", "Merge the solution's branch into main: the branch overwrites what differs").hideHelp())
105
126
  .action(wrapCommand(syncCliAction));
106
127
  // Deprecated aliases into `sync`, kept for commands already in the wild
107
128
  // (SYNC-ALIAS-1). Each prints a deprecation notice naming its new spelling.
@@ -212,5 +233,9 @@ program
212
233
  .description("Print the greenfield interview protocol — for working out what something new should do before writing its spec")
213
234
  .action(wrapCommand(greenfieldDiscoveryCommand));
214
235
  registerCodebaseToSpec(program);
236
+ registerSolution(program);
237
+ registerMockup(program);
238
+ registerBoard(program);
239
+ registerScores(program);
215
240
  program.parse();
216
241
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The one way into the dotrequirements cloud. Every place the CLI reaches the
3
+ * cloud asks here for the address, so the first use in a run is visible — the
4
+ * version-message check (CLI-MSG-1) starts there.
5
+ */
6
+ /** The web app, home of the hosted AI endpoints and publishing. */
7
+ export declare const DEFAULT_API_BASE_URL = "https://app.dotrequirements.io";
8
+ /**
9
+ * The Convex backend's address, for a ConvexHttpClient or its HTTP API:
10
+ * production, unless DOTREQ_CONVEX_URL points somewhere else, such as a dev
11
+ * or preview deployment.
12
+ */
13
+ export declare function convexUrl(): string;
14
+ /** The web app's address; `DOTREQUIREMENTS_API_URL` overrides it. */
15
+ export declare function webApiUrl(): string;
16
+ /** Run `callback` the first time this process reaches the cloud. */
17
+ export declare function whenCloudFirstUsed(callback: () => void): void;
18
+ //# sourceMappingURL=cloud.d.ts.map
package/dist/cloud.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The one way into the dotrequirements cloud. Every place the CLI reaches the
3
+ * cloud asks here for the address, so the first use in a run is visible — the
4
+ * version-message check (CLI-MSG-1) starts there.
5
+ */
6
+ /** The Convex backend every user's CLI talks to. */
7
+ const CONVEX_URL = "https://data.dotrequirements.io";
8
+ /** The web app, home of the hosted AI endpoints and publishing. */
9
+ export const DEFAULT_API_BASE_URL = "https://app.dotrequirements.io";
10
+ let onFirstUse = null;
11
+ let used = false;
12
+ function noteUse() {
13
+ if (used)
14
+ return;
15
+ used = true;
16
+ onFirstUse?.();
17
+ }
18
+ /**
19
+ * The Convex backend's address, for a ConvexHttpClient or its HTTP API:
20
+ * production, unless DOTREQ_CONVEX_URL points somewhere else, such as a dev
21
+ * or preview deployment.
22
+ */
23
+ export function convexUrl() {
24
+ noteUse();
25
+ return process.env.DOTREQ_CONVEX_URL?.trim() || CONVEX_URL;
26
+ }
27
+ /** The web app's address; `DOTREQUIREMENTS_API_URL` overrides it. */
28
+ export function webApiUrl() {
29
+ noteUse();
30
+ return process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
31
+ }
32
+ /** Run `callback` the first time this process reaches the cloud. */
33
+ export function whenCloudFirstUsed(callback) {
34
+ onFirstUse = callback;
35
+ if (used)
36
+ callback();
37
+ }
38
+ //# sourceMappingURL=cloud.js.map
@@ -0,0 +1,13 @@
1
+ import type { Command } from "commander";
2
+ export declare function guideCommand(): Promise<void>;
3
+ export declare function createCommand(options: {
4
+ title: string;
5
+ description?: string;
6
+ file: string;
7
+ }): Promise<void>;
8
+ export declare function updateCommand(boardId: string, options: {
9
+ file: string;
10
+ }): Promise<void>;
11
+ export declare function showCommand(boardId: string): Promise<void>;
12
+ export declare function registerBoard(program: Command): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { webApiUrl } from "../../cloud.js";
4
+ import { getProjectCredentials } from "../../utils/project-settings.js";
5
+ /**
6
+ * Hidden from help until solutions launch: today's users see the CLI they
7
+ * have now, and the server refuses these commands for a team without the
8
+ * solutions switch (SOLACCESS-2).
9
+ */
10
+ const HIDDEN_UNTIL_LAUNCH = { hidden: true };
11
+ async function throughApp(path, extra) {
12
+ const { projectId, projectSecret } = getProjectCredentials();
13
+ const base = webApiUrl();
14
+ const response = await fetch(`${base}${path}`, {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ body: JSON.stringify({ projectId, projectSecret, ...extra }),
18
+ });
19
+ if (!response.ok) {
20
+ const body = (await response.json().catch(() => ({})));
21
+ // BOARD-CLI-3: every problem at once, each on its line — an agent that
22
+ // gets one problem per attempt spends a turn per typo.
23
+ const problems = (body.problems ?? [])
24
+ .map((problem) => problem.line === undefined
25
+ ? ` ${problem.message}`
26
+ : ` Line ${problem.line}: ${problem.message}`)
27
+ .join("\n");
28
+ throw new Error([body.error || response.statusText, problems]
29
+ .filter(Boolean)
30
+ .join("\n"));
31
+ }
32
+ return response;
33
+ }
34
+ export async function guideCommand() {
35
+ const response = await throughApp("/api/boards/guide", {});
36
+ const { guide } = (await response.json());
37
+ process.stdout.write(`${guide}\n`);
38
+ }
39
+ export async function createCommand(options) {
40
+ const yaml = readFileSync(resolve(options.file), "utf8");
41
+ const response = await throughApp("/api/boards/create", {
42
+ title: options.title,
43
+ description: options.description,
44
+ yaml,
45
+ });
46
+ const { boardId, message } = (await response.json());
47
+ console.log(`Created board ${boardId}. ${message} Submit it as board:${boardId}; read it back with: dotreq board show ${boardId}`);
48
+ }
49
+ export async function updateCommand(boardId, options) {
50
+ const yaml = readFileSync(resolve(options.file), "utf8");
51
+ const response = await throughApp("/api/boards/update", { boardId, yaml });
52
+ const { message } = (await response.json());
53
+ console.log(message);
54
+ }
55
+ export async function showCommand(boardId) {
56
+ const response = await throughApp("/api/boards/show", { boardId });
57
+ const { yaml } = (await response.json());
58
+ process.stdout.write(yaml.endsWith("\n") ? yaml : `${yaml}\n`);
59
+ }
60
+ export function registerBoard(program) {
61
+ const board = program
62
+ .command("board", HIDDEN_UNTIL_LAUNCH)
63
+ .description("Make and read discovery boards");
64
+ board
65
+ .command("guide")
66
+ .description("Print the board authoring guide: the YAML a board is written in, what each verb does, a complete example")
67
+ .action(wrap(guideCommand));
68
+ board
69
+ .command("create")
70
+ .description("Create a board in the project from a YAML file")
71
+ .requiredOption("--title <title>", "The board's title")
72
+ .option("--description <text>", "What the board explores")
73
+ .requiredOption("--file <path>", "The board file")
74
+ .action(wrap(createCommand));
75
+ board
76
+ .command("update <board-id>")
77
+ .description("Make a board say what a YAML file says")
78
+ .requiredOption("--file <path>", "The board file")
79
+ .action(wrap(updateCommand));
80
+ board
81
+ .command("show <board-id>")
82
+ .description("Print a board as the YAML it is written in")
83
+ .action(wrap(showCommand));
84
+ }
85
+ function wrap(fn) {
86
+ return async (...args) => {
87
+ try {
88
+ await fn(...args);
89
+ }
90
+ catch (error) {
91
+ console.error(`\nError: ${error instanceof Error ? error.message : String(error)}\n`);
92
+ process.exitCode = 1;
93
+ }
94
+ };
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `dotreq sync --branch-contributes <solution-id>` and `--branch-wins
3
+ * <solution-id>`: merge a solution's branch into main (BRANCH-8). Both sides
4
+ * are in the cloud, so no local file is read or written; the web app's merge
5
+ * route does it, as it does for the solution page's Merge action.
6
+ */
7
+ type MergeDocument = {
8
+ title: string;
9
+ action: string;
10
+ because?: string;
11
+ };
12
+ type MergeRefusal = {
13
+ kind: "conflicts";
14
+ documents: string[];
15
+ } | {
16
+ kind: "key";
17
+ key: string;
18
+ holder: string;
19
+ document: string;
20
+ } | {
21
+ kind: "limit";
22
+ message: string;
23
+ } | {
24
+ kind: "swap";
25
+ documents: string[];
26
+ } | {
27
+ kind: "nothing";
28
+ };
29
+ type MergePlan = {
30
+ solution: {
31
+ title: string;
32
+ };
33
+ documents: MergeDocument[];
34
+ refusal: MergeRefusal | null;
35
+ };
36
+ type MergeOutcome = {
37
+ kind: "merged";
38
+ landed: string[];
39
+ plan: MergePlan;
40
+ } | {
41
+ kind: "refused";
42
+ plan: MergePlan;
43
+ } | {
44
+ kind: "no branch";
45
+ } | {
46
+ kind: "stopped";
47
+ landed: string[];
48
+ at: string;
49
+ reason: string;
50
+ plan: MergePlan;
51
+ };
52
+ export type BranchMergeFlags = {
53
+ branchContributes?: string;
54
+ branchWins?: string;
55
+ };
56
+ /** The flags a branch merge can't be combined with (BRANCH-8.3). */
57
+ declare const OTHER_FLAGS: readonly ["cloudContributes", "repoContributes", "cloudWins", "repoWins", "share", "project"];
58
+ /**
59
+ * The merge a sync's flags ask for, or null when they ask for none. Throws,
60
+ * saying so, when they combine a merge with anything else (BRANCH-8.3).
61
+ */
62
+ export declare function branchMergeOf(scope: string[], options: BranchMergeFlags & {
63
+ [K in (typeof OTHER_FLAGS)[number]]?: unknown;
64
+ }): {
65
+ solutionId: string;
66
+ mode: "contributes" | "wins";
67
+ } | null;
68
+ /** What a merge says it did, line by line; and whether it did it. */
69
+ export declare function describeMerge(outcome: MergeOutcome): {
70
+ lines: string[];
71
+ ok: boolean;
72
+ };
73
+ export declare function branchMergeCommand(merge: {
74
+ solutionId: string;
75
+ mode: "contributes" | "wins";
76
+ }): Promise<void>;
77
+ export {};
78
+ //# sourceMappingURL=branch-merge.d.ts.map
@@ -0,0 +1,129 @@
1
+ import { webApiUrl } from "../cloud.js";
2
+ import { getProjectCredentials } from "../utils/project-settings.js";
3
+ import { checkedOut } from "./solution/share.js";
4
+ /** The flags a branch merge can't be combined with (BRANCH-8.3). */
5
+ const OTHER_FLAGS = [
6
+ "cloudContributes",
7
+ "repoContributes",
8
+ "cloudWins",
9
+ "repoWins",
10
+ "share",
11
+ // The merge runs on the linked project's credential; a project named here
12
+ // would be quietly ignored, so it is refused instead.
13
+ "project",
14
+ ];
15
+ /**
16
+ * The merge a sync's flags ask for, or null when they ask for none. Throws,
17
+ * saying so, when they combine a merge with anything else (BRANCH-8.3).
18
+ */
19
+ export function branchMergeOf(scope, options) {
20
+ const contributes = options.branchContributes;
21
+ const wins = options.branchWins;
22
+ if (contributes === undefined && wins === undefined)
23
+ return null;
24
+ const combined = (contributes !== undefined && wins !== undefined) ||
25
+ scope.length > 0 ||
26
+ OTHER_FLAGS.some((flag) => options[flag]);
27
+ if (combined) {
28
+ throw new Error("A branch merge can't be combined with other sync flags or a scope: run `dotreq sync --branch-contributes <solution-id>` or `dotreq sync --branch-wins <solution-id>` on its own.");
29
+ }
30
+ return contributes !== undefined
31
+ ? { solutionId: contributes, mode: "contributes" }
32
+ : { solutionId: wins, mode: "wins" };
33
+ }
34
+ /** What a merge says it did, line by line; and whether it did it. */
35
+ export function describeMerge(outcome) {
36
+ if (outcome.kind === "no branch") {
37
+ return {
38
+ lines: ["That solution has nothing on a branch to merge."],
39
+ ok: false,
40
+ };
41
+ }
42
+ const title = outcome.plan.solution.title;
43
+ if (outcome.kind === "merged") {
44
+ return {
45
+ lines: [
46
+ `Merged the "${title}" branch into main.`,
47
+ ...outcome.landed.map((doc) => ` landed: ${doc}`),
48
+ ],
49
+ ok: true,
50
+ };
51
+ }
52
+ if (outcome.kind === "stopped") {
53
+ return {
54
+ lines: [
55
+ `The "${title}" merge stopped at ${outcome.at}: ${outcome.reason}`,
56
+ ...outcome.landed.map((doc) => ` landed: ${doc}`),
57
+ ],
58
+ ok: false,
59
+ };
60
+ }
61
+ const refusal = outcome.plan.refusal;
62
+ switch (refusal.kind) {
63
+ case "conflicts":
64
+ return {
65
+ lines: [
66
+ `Nothing merged: these documents on the "${title}" branch conflict with main.`,
67
+ ...refusal.documents.map((doc) => ` conflict: ${doc}`),
68
+ "Resolve them on the branch and merge again, or let the branch win with `dotreq sync --branch-wins <solution-id>`.",
69
+ ],
70
+ ok: false,
71
+ };
72
+ case "key":
73
+ return {
74
+ lines: [
75
+ `Nothing merged: ${refusal.document} uses the requirement key ${refusal.key}, which "${refusal.holder}" on main has.`,
76
+ ],
77
+ ok: false,
78
+ };
79
+ case "limit":
80
+ return { lines: [`Nothing merged: ${refusal.message}`], ok: false };
81
+ case "swap":
82
+ return {
83
+ lines: [
84
+ `Nothing merged: ${refusal.documents.join(" and ")} take each other's requirement keys, which can't land one at a time. Give one of them new keys on the branch.`,
85
+ ],
86
+ ok: false,
87
+ };
88
+ case "nothing":
89
+ return {
90
+ lines: [
91
+ `Nothing to merge: every document on the "${title}" branch is already on main.`,
92
+ ],
93
+ ok: true,
94
+ };
95
+ }
96
+ }
97
+ export async function branchMergeCommand(merge) {
98
+ // SOLSHARE-5.0: a repository a solution's share command checked out holds
99
+ // no credential that merges; say so rather than asking for a link.
100
+ let credentials;
101
+ try {
102
+ credentials = getProjectCredentials();
103
+ }
104
+ catch (error) {
105
+ if (checkedOut().length > 0) {
106
+ throw new Error("Nothing merged: the share command you checked out with can't merge a solution's branch. Whoever runs the solution merges it, from the app.");
107
+ }
108
+ throw error;
109
+ }
110
+ const { projectId, projectSecret } = credentials;
111
+ const apiBaseUrl = webApiUrl();
112
+ const response = await fetch(`${apiBaseUrl}/api/branches/merge`, {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({ ...merge, projectId, projectSecret }),
116
+ });
117
+ const body = (await response.json().catch(() => null));
118
+ if (!body || !("kind" in body)) {
119
+ throw new Error(body?.error ??
120
+ `The merge failed (${response.status}).`);
121
+ }
122
+ const { lines, ok } = describeMerge(body);
123
+ for (const line of lines)
124
+ console.log(line);
125
+ // BRANCH-8.4: a merge that finds conflicts exits non-zero.
126
+ if (!ok)
127
+ process.exitCode = 1;
128
+ }
129
+ //# sourceMappingURL=branch-merge.js.map
@@ -1,9 +1,9 @@
1
+ import { convexUrl } from "../cloud.js";
1
2
  import { getProjectContext } from "../requirements/cloud-ai.js";
2
3
  import { loadAllRequirements } from "../requirements/index.js";
3
4
  import { generateStyleGuide } from "../requirements/style-guide.js";
4
5
  import { readLocalStyleGuide } from "../requirements/style-guide-file.js";
5
6
  import { findProjectRoot, getProjectCredentials, } from "../utils/project-settings.js";
6
- const CONVEX_URL = "https://data.dotrequirements.io";
7
7
  export async function createRequirementDocumentCommand(filePath) {
8
8
  const workspaceRoot = process.cwd();
9
9
  // CLI-CREATE-DOC-1.1 / .2: discover existing patterns from the workspace.
@@ -24,7 +24,7 @@ export async function createRequirementDocumentCommand(filePath) {
24
24
  if (projectRoot) {
25
25
  try {
26
26
  const { projectId, projectSecret } = getProjectCredentials(workspaceRoot);
27
- const contextData = await getProjectContext(projectId, projectSecret, CONVEX_URL);
27
+ const contextData = await getProjectContext(projectId, projectSecret, convexUrl());
28
28
  customStyleGuidance = contextData?.requirementsStyleContext ?? null;
29
29
  }
30
30
  catch {
@@ -5,16 +5,17 @@
5
5
  * the detail one level. Exits 0 regardless of verdicts unless `--exit-code` is
6
6
  * passed, which makes any non-in-sync document exit 1 (the CI drift gate).
7
7
  */
8
- import { acquireCloudSnapshot, acquireLocalSnapshot, compareSnapshots, filterByScope, formatUnitDetail, formatVerdictLine, resolutionHint, } from "../sync/index.js";
8
+ import { acquireCloudSnapshot, acquireLocalSnapshot, compareSnapshots, filterByScope, foreignDocumentNotes, formatUnitDetail, formatVerdictLine, resolutionHint, } from "../sync/index.js";
9
9
  import { brand } from "../utils/brand.js";
10
10
  import { resolveCloudAuth } from "./sync-common.js";
11
11
  export async function diffCommand(scope, options) {
12
12
  const workspaceRoot = process.cwd();
13
13
  const auth = resolveCloudAuth(options);
14
- const [local, cloud] = await Promise.all([
15
- acquireLocalSnapshot(workspaceRoot),
16
- acquireCloudSnapshot(auth),
17
- ]);
14
+ // SOLACCESS-2.2: as the sync carries them.
15
+ const cloud = await acquireCloudSnapshot(auth);
16
+ const local = await acquireLocalSnapshot(workspaceRoot, undefined, {
17
+ solutions: cloud.solutions === true,
18
+ });
18
19
  const comparison = compareSnapshots(local, cloud);
19
20
  const { documents, unmatched } = filterByScope(comparison, scope, workspaceRoot);
20
21
  for (const token of unmatched) {
@@ -38,12 +39,21 @@ export async function diffCommand(scope, options) {
38
39
  console.log("No documents to compare.");
39
40
  return;
40
41
  }
42
+ // SYNC-FAIL-5: a file whose id names another project's document is only
43
+ // in the repo, and a sync will refuse it; say so here as the sync will.
44
+ const foreign = await foreignDocumentNotes(auth, documents.flatMap((d) => d.verdict === "only_in_repo" && d.documentId ? [d.documentId] : []));
41
45
  console.log(`\n=== Repo vs ${brand} cloud ===\n`);
42
46
  for (const doc of documents) {
43
47
  console.log(formatVerdictLine(doc));
44
48
  if (doc.verdict === "invalid_file" && doc.error) {
45
49
  console.log(` ${doc.error}`);
46
50
  }
51
+ const foreignNote = doc.verdict === "only_in_repo" && doc.documentId
52
+ ? foreign.get(doc.documentId)
53
+ : undefined;
54
+ if (foreignNote) {
55
+ console.log(` ${foreignNote}`);
56
+ }
47
57
  if (verbose && doc.details && doc.details.length > 0) {
48
58
  for (const detail of doc.details) {
49
59
  console.log(formatUnitDetail(detail));
@@ -1,12 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { ConvexHttpClient } from "convex/browser";
4
- import { getConvexUrl } from "../config.js";
4
+ import { convexUrl } from "../cloud.js";
5
5
  import { api } from "../convex.js";
6
6
  import { generateStyleGuideBody } from "../requirements/style-guide.js";
7
7
  import { generateExampleRequirements } from "../templates/example-requirements.js";
8
8
  import { generateRequirementsReadme } from "../templates/requirements-readme.js";
9
9
  import { brand } from "../utils/brand.js";
10
+ import { commandErrorMessage } from "../utils/command-error.js";
10
11
  import { ensureGitignore } from "../utils/gitignore.js";
11
12
  import { executeOAuthFlow } from "../utils/oauth-flow.js";
12
13
  import { detectProjectName } from "../utils/project-name.js";
@@ -69,7 +70,7 @@ export async function initCommand(options = {}) {
69
70
  }
70
71
  }
71
72
  catch (error) {
72
- console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
73
+ console.error(`\n✗ Error: ${commandErrorMessage(error)}`);
73
74
  // INIT-11: Network error guidance
74
75
  if (error instanceof Error && error.message.includes("fetch")) {
75
76
  console.error("\nCheck your internet connection and try again.");
@@ -130,7 +131,7 @@ async function authenticatedFlow(cwd, options) {
130
131
  return false;
131
132
  }
132
133
  // Set up authenticated client using the session token
133
- const client = new ConvexHttpClient(getConvexUrl());
134
+ const client = new ConvexHttpClient(convexUrl());
134
135
  client.setAuth(oauthResult.accessToken);
135
136
  // INIT-18: Select team
136
137
  const selectedTeam = await selectTeam(client);
@@ -216,9 +217,9 @@ async function selectTeam(client) {
216
217
  */
217
218
  async function chooseProjectAction(client, teamId) {
218
219
  // INIT-7.3: Check if team has existing projects the user can access
219
- const projectsWithSecrets = (await client.query(api.projectSecrets.queries.listOwnSecretsForTeam,
220
+ const projectsWithSecrets = (await client.query(api.projectSecrets.queries.listOwnForTeam,
220
221
  // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id<"teams"> type; the runtime value is a plain string the convex server validates.
221
- { target: { type: "team", id: teamId } }));
222
+ { teamId: teamId }));
222
223
  if (projectsWithSecrets.length === 0) {
223
224
  // No existing projects, only offer create
224
225
  const confirmed = await promptConfirm("This team has no projects. Create a new one?", true);
@@ -315,7 +316,7 @@ async function connectExistingProjectFlow(cwd, client, team) {
315
316
  */
316
317
  async function inviteFlow(cwd, token) {
317
318
  // MEMBERSHIP-18.0: Validate token before auth (no auth needed for this query)
318
- const client = new ConvexHttpClient(getConvexUrl());
319
+ const client = new ConvexHttpClient(convexUrl());
319
320
  console.log("Validating invite token...");
320
321
  const inviteInfo = await client.query(api.teamInvites.queries.getInviteInfo, {
321
322
  token,