@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.
- package/dist/cli.js +32 -7
- package/dist/cloud.d.ts +18 -0
- package/dist/cloud.js +38 -0
- package/dist/commands/board/index.d.ts +13 -0
- package/dist/commands/board/index.js +96 -0
- package/dist/commands/branch-merge.d.ts +78 -0
- package/dist/commands/branch-merge.js +129 -0
- package/dist/commands/create-requirement-document.js +2 -2
- package/dist/commands/diff.js +15 -5
- package/dist/commands/init.js +7 -6
- package/dist/commands/link.js +27 -21
- package/dist/commands/mockup/index.d.ts +11 -0
- package/dist/commands/mockup/index.js +82 -0
- package/dist/commands/report.js +22 -9
- package/dist/commands/review-test.js +3 -2
- package/dist/commands/scores/index.d.ts +50 -0
- package/dist/commands/scores/index.js +190 -0
- package/dist/commands/solution/ask.d.ts +9 -0
- package/dist/commands/solution/ask.js +27 -0
- package/dist/commands/solution/author.d.ts +19 -0
- package/dist/commands/solution/author.js +202 -0
- package/dist/commands/solution/branch.d.ts +103 -0
- package/dist/commands/solution/branch.js +341 -0
- package/dist/commands/solution/brief.d.ts +81 -0
- package/dist/commands/solution/brief.js +47 -0
- package/dist/commands/solution/checkout.d.ts +27 -0
- package/dist/commands/solution/checkout.js +66 -0
- package/dist/commands/solution/client.d.ts +16 -0
- package/dist/commands/solution/client.js +46 -0
- package/dist/commands/solution/contract.d.ts +9 -0
- package/dist/commands/solution/contract.js +23 -0
- package/dist/commands/solution/create.d.ts +14 -0
- package/dist/commands/solution/create.js +52 -0
- package/dist/commands/solution/demo-script.d.ts +28 -0
- package/dist/commands/solution/demo-script.js +43 -0
- package/dist/commands/solution/image.d.ts +60 -0
- package/dist/commands/solution/image.js +148 -0
- package/dist/commands/solution/index.d.ts +12 -0
- package/dist/commands/solution/index.js +281 -0
- package/dist/commands/solution/look-up.d.ts +9 -0
- package/dist/commands/solution/look-up.js +69 -0
- package/dist/commands/solution/next.d.ts +12 -0
- package/dist/commands/solution/next.js +48 -0
- package/dist/commands/solution/plan-file.d.ts +22 -0
- package/dist/commands/solution/plan-file.js +33 -0
- package/dist/commands/solution/preview-screenshot.d.ts +21 -0
- package/dist/commands/solution/preview-screenshot.js +56 -0
- package/dist/commands/solution/pull-request.d.ts +25 -0
- package/dist/commands/solution/pull-request.js +117 -0
- package/dist/commands/solution/pushing.d.ts +66 -0
- package/dist/commands/solution/pushing.js +185 -0
- package/dist/commands/solution/rehearse.d.ts +10 -0
- package/dist/commands/solution/rehearse.js +24 -0
- package/dist/commands/solution/request.d.ts +3 -0
- package/dist/commands/solution/request.js +13 -0
- package/dist/commands/solution/review.d.ts +27 -0
- package/dist/commands/solution/review.js +104 -0
- package/dist/commands/solution/run-file.d.ts +42 -0
- package/dist/commands/solution/run-file.js +63 -0
- package/dist/commands/solution/share.d.ts +62 -0
- package/dist/commands/solution/share.js +346 -0
- package/dist/commands/solution/stall.d.ts +14 -0
- package/dist/commands/solution/stall.js +41 -0
- package/dist/commands/solution/start.d.ts +11 -0
- package/dist/commands/solution/start.js +20 -0
- package/dist/commands/solution/status.d.ts +39 -0
- package/dist/commands/solution/status.js +145 -0
- package/dist/commands/solution/summary.d.ts +13 -0
- package/dist/commands/solution/summary.js +5 -0
- package/dist/commands/solution/things.d.ts +118 -0
- package/dist/commands/solution/things.js +78 -0
- package/dist/commands/solution/thread.d.ts +13 -0
- package/dist/commands/solution/thread.js +53 -0
- package/dist/commands/solution/upload.d.ts +24 -0
- package/dist/commands/solution/upload.js +27 -0
- package/dist/commands/solution/video.d.ts +88 -0
- package/dist/commands/solution/video.js +364 -0
- package/dist/commands/solution/working-folder.d.ts +44 -0
- package/dist/commands/solution/working-folder.js +64 -0
- package/dist/commands/style-check.js +4 -4
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.js +21 -5
- package/dist/convex.d.ts +64 -10
- package/dist/convex.js +75 -10
- package/dist/harness/finalize.js +16 -8
- package/dist/harness/reportingStatus.js +2 -11
- package/dist/push/core.js +7 -2
- package/dist/rehearsal/derive.d.ts +23 -0
- package/dist/rehearsal/derive.js +112 -0
- package/dist/rehearsal/driver.d.ts +44 -0
- package/dist/rehearsal/driver.js +337 -0
- package/dist/rehearsal/earlier.d.ts +25 -0
- package/dist/rehearsal/earlier.js +138 -0
- package/dist/rehearsal/index.d.ts +5 -0
- package/dist/rehearsal/index.js +4 -0
- package/dist/rehearsal/live.d.ts +72 -0
- package/dist/rehearsal/live.js +301 -0
- package/dist/rehearsal/report.d.ts +4 -0
- package/dist/rehearsal/report.js +69 -0
- package/dist/rehearsal/types.d.ts +377 -0
- package/dist/rehearsal/types.js +8 -0
- package/dist/requirements/cloud-ai.d.ts +0 -6
- package/dist/requirements/cloud-ai.js +2 -11
- package/dist/requirements/cloud-coverage.js +4 -4
- package/dist/requirements/index.d.ts +6 -0
- package/dist/requirements/index.js +24 -0
- package/dist/schema/browser.d.ts +1 -1
- package/dist/schema/browser.js +3 -1
- package/dist/schema/file-writer.d.ts +5 -0
- package/dist/schema/file-writer.js +4 -0
- package/dist/schema/parser.d.ts +11 -2
- package/dist/schema/parser.js +11 -7
- package/dist/schema/schemas.d.ts +40 -0
- package/dist/schema/schemas.js +4 -0
- package/dist/sync/execute.d.ts +10 -1
- package/dist/sync/execute.js +76 -24
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/publish.d.ts +7 -0
- package/dist/sync/publish.js +2 -2
- package/dist/sync/segment.js +4 -1
- package/dist/sync/snapshot.d.ts +27 -5
- package/dist/sync/snapshot.js +92 -15
- package/dist/sync/solution-documents.d.ts +64 -0
- package/dist/sync/solution-documents.js +97 -0
- package/dist/sync/types.d.ts +19 -1
- package/dist/utils/command-error.d.ts +19 -0
- package/dist/utils/command-error.js +44 -0
- package/dist/utils/oauth-flow.js +2 -2
- package/dist/utils/project-selector.js +4 -4
- package/dist/version-messages.d.ts +28 -0
- package/dist/version-messages.js +87 -0
- package/package.json +3 -2
- package/dist/config.d.ts +0 -5
- 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
|
-
|
|
48
|
-
|
|
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
|
package/dist/cloud.d.ts
ADDED
|
@@ -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,
|
|
27
|
+
const contextData = await getProjectContext(projectId, projectSecret, convexUrl());
|
|
28
28
|
customStyleGuidance = contextData?.requirementsStyleContext ?? null;
|
|
29
29
|
}
|
|
30
30
|
catch {
|
package/dist/commands/diff.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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));
|
package/dist/commands/init.js
CHANGED
|
@@ -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 {
|
|
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: ${
|
|
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(
|
|
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.
|
|
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
|
-
{
|
|
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(
|
|
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,
|