@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
@@ -1,15 +1,17 @@
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 { WEB_APP_URL } from "../push/index.js";
7
7
  import { brand } from "../utils/brand.js";
8
+ import { commandErrorMessage } from "../utils/command-error.js";
8
9
  import { ensureGitignore } from "../utils/gitignore.js";
9
10
  import { executeOAuthFlow } from "../utils/oauth-flow.js";
10
11
  import { getOrCreateProjectSecret, promptExpiryDays, selectTeam, } from "../utils/project-selector.js";
11
12
  import { findProjectRoot, readProjectSettings, writeProjectSettings, } from "../utils/project-settings.js";
12
13
  import { promptChoice, promptConfirm } from "../utils/prompts.js";
14
+ import { markVersionMessagesShown, versionMessages, } from "../version-messages.js";
13
15
  import { hasCapacity, isNonInteractive, resolveProject, resolveProjectName, resolveTeam, } from "./link-resolution.js";
14
16
  import { syncCommand } from "./sync.js";
15
17
  /**
@@ -47,10 +49,13 @@ async function runNonInteractiveLink(options) {
47
49
  if (options.json) {
48
50
  console.log = (...args) => console.error(...args);
49
51
  }
52
+ // CLI-MSG-1.1: the JSON result carries the cloud's messages, so it is
53
+ // printed once the command is done and they are in.
54
+ let jsonOutcome = null;
50
55
  const finish = (outcome) => {
51
56
  console.log = originalLog;
52
57
  if (options.json) {
53
- originalLog(JSON.stringify(outcome));
58
+ jsonOutcome = outcome;
54
59
  }
55
60
  else if (outcome.status === "decision_needed") {
56
61
  console.log(`\n${outcome.message}`);
@@ -101,7 +106,7 @@ async function runNonInteractiveLink(options) {
101
106
  }
102
107
  // LINK-3 / LINK-10.7: browser auth is the only human step
103
108
  const oauthResult = await executeOAuthFlow();
104
- const client = new ConvexHttpClient(getConvexUrl());
109
+ const client = new ConvexHttpClient(convexUrl());
105
110
  client.setAuth(oauthResult.accessToken);
106
111
  await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
107
112
  const teams = (await client.query(api.teams.queries.listForUserWithUsage));
@@ -201,12 +206,9 @@ async function runNonInteractiveLink(options) {
201
206
  if (projectResolution.rename) {
202
207
  const newName = resolveProjectName(options, readPackageName(cwd), path.basename(cwd));
203
208
  try {
204
- await client.mutation(api.projects.mutations.update, {
205
- target: {
206
- type: "project",
207
- // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
208
- id: projectResolution.project.projectId,
209
- },
209
+ await client.mutation(api.projects.mutations.updateProject, {
210
+ // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
211
+ projectId: projectResolution.project.projectId,
210
212
  name: newName,
211
213
  });
212
214
  projectName = newName;
@@ -234,13 +236,17 @@ async function runNonInteractiveLink(options) {
234
236
  });
235
237
  }
236
238
  catch (error) {
237
- finish({
238
- status: "error",
239
- message: error instanceof Error ? error.message : String(error),
240
- });
239
+ finish({ status: "error", message: commandErrorMessage(error) });
241
240
  }
242
241
  finally {
243
242
  console.log = originalLog;
243
+ // Here, not after the block: every outcome above returns from inside
244
+ // `try`. Assigned inside `finish`, which TypeScript can't follow.
245
+ const outcome = jsonOutcome;
246
+ if (outcome) {
247
+ originalLog(JSON.stringify({ ...outcome, messages: await versionMessages() }));
248
+ markVersionMessagesShown();
249
+ }
244
250
  }
245
251
  }
246
252
  /**
@@ -253,18 +259,18 @@ async function runNonInteractiveLink(options) {
253
259
  async function mintShareArtifacts(client, teamId, projectId) {
254
260
  const artifacts = {};
255
261
  try {
256
- const invite = (await client.mutation(api.teamInvites.mutations.createInvite,
262
+ const invite = (await client.mutation(api.teamInvites.mutations.createInviteLink,
257
263
  // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
258
- { target: { type: "team", id: teamId } }));
264
+ { teamId: teamId }));
259
265
  artifacts.inviteUrl = `${WEB_APP_URL}/invite/${invite.token}`;
260
266
  }
261
267
  catch {
262
268
  // LINK-13.3: admin-gated — omit the invite URL for non-admin members
263
269
  }
264
270
  try {
265
- const share = (await client.mutation(api.projectSecrets.mutations.ensureShareToken,
271
+ const share = (await client.mutation(api.projectSecrets.mutations.ensureProjectShareToken,
266
272
  // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
267
- { target: { type: "project", id: projectId } }));
273
+ { projectId: projectId }));
268
274
  artifacts.shareSyncCommand = `npx -y @popoverai/dotrequirements@latest sync --share ${share.token}`;
269
275
  }
270
276
  catch {
@@ -273,9 +279,9 @@ async function mintShareArtifacts(client, teamId, projectId) {
273
279
  return artifacts;
274
280
  }
275
281
  async function listProjects(client, teamId) {
276
- const projects = (await client.query(api.projectSecrets.queries.listOwnSecretsForTeam,
282
+ const projects = (await client.query(api.projectSecrets.queries.listOwnForTeam,
277
283
  // biome-ignore lint/suspicious/noExplicitAny: CLI workspace doesn't import convex's branded Id type; the runtime value is a plain string the server validates.
278
- { target: { type: "team", id: teamId } }));
284
+ { teamId: teamId }));
279
285
  return projects.map((p) => ({
280
286
  projectId: p.projectId,
281
287
  projectName: p.projectName,
@@ -349,7 +355,7 @@ async function runInteractiveLink(options) {
349
355
  return;
350
356
  }
351
357
  // Set up authenticated client using the session token
352
- const client = new ConvexHttpClient(getConvexUrl());
358
+ const client = new ConvexHttpClient(convexUrl());
353
359
  client.setAuth(oauthResult.accessToken);
354
360
  // LINK-4: Select team
355
361
  const selectedTeam = await selectTeam(client);
@@ -417,7 +423,7 @@ async function runInteractiveLink(options) {
417
423
  console.log("\nDocumentation: https://docs.dotrequirements.io/getting-started");
418
424
  }
419
425
  catch (error) {
420
- console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
426
+ console.error(`\n✗ Error: ${commandErrorMessage(error)}`);
421
427
  // LINK-8: Network error guidance
422
428
  if (error instanceof Error && error.message.includes("fetch")) {
423
429
  console.error("\nCheck your internet connection and try again.");
@@ -0,0 +1,11 @@
1
+ import type { Command } from "commander";
2
+ export declare function guideCommand(): Promise<void>;
3
+ export declare function createCommand(options: {
4
+ title: string;
5
+ file: string;
6
+ }): Promise<void>;
7
+ export declare function previewCommand(mockupId: string, options: {
8
+ out?: string;
9
+ }): Promise<void>;
10
+ export declare function registerMockup(program: Command): void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,82 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { webApiUrl } from "../../cloud.js";
4
+ import { api } from "../../convex.js";
5
+ import { getProjectCredentials } from "../../utils/project-settings.js";
6
+ import { solutionClient } from "../solution/client.js";
7
+ /**
8
+ * Hidden from help until solutions launch: today's users see the CLI they
9
+ * have now, and the server refuses these commands for a team without the
10
+ * solutions switch (SOLACCESS-2).
11
+ */
12
+ const HIDDEN_UNTIL_LAUNCH = { hidden: true };
13
+ /**
14
+ * Mockups from the shell (SIGHT-5): the guide, a mockup made from a file,
15
+ * and a mockup rendered to a file an agent can open in its browser. The
16
+ * guide and the render come through the app, which owns the dialect's
17
+ * text and its renderer; the create goes to the backend.
18
+ */
19
+ async function throughApp(path, extra) {
20
+ const { projectId, projectSecret } = getProjectCredentials();
21
+ const base = webApiUrl();
22
+ const response = await fetch(`${base}${path}`, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify({ projectId, projectSecret, ...extra }),
26
+ });
27
+ if (!response.ok) {
28
+ const body = (await response.json().catch(() => ({})));
29
+ throw new Error(body.error || response.statusText);
30
+ }
31
+ return response;
32
+ }
33
+ export async function guideCommand() {
34
+ const response = await throughApp("/api/mockups/guide", {});
35
+ const { guide } = (await response.json());
36
+ process.stdout.write(`${guide}\n`);
37
+ }
38
+ export async function createCommand(options) {
39
+ const markup = readFileSync(resolve(options.file), "utf8");
40
+ const client = solutionClient();
41
+ const { mockupId } = await client.mutation(api.mockups.mutations.create, { project: client.project, title: options.title, markup });
42
+ console.log(`Created mockup ${mockupId}. Submit it as mockup:${mockupId}; render it with: dotreq mockup preview ${mockupId} --out mockup.html`);
43
+ }
44
+ export async function previewCommand(mockupId, options) {
45
+ const response = await throughApp("/api/mockups/frame", { mockupId });
46
+ const html = await response.text();
47
+ const out = resolve(options.out ?? `mockup-${mockupId}.html`);
48
+ writeFileSync(out, html);
49
+ console.log(`Wrote ${out}. Look at it: agent-browser open file://${out} && agent-browser screenshot mockup.png`);
50
+ }
51
+ export function registerMockup(program) {
52
+ const mockup = program
53
+ .command("mockup", HIDDEN_UNTIL_LAUNCH)
54
+ .description("Make and look at wireframe mockups");
55
+ mockup
56
+ .command("guide")
57
+ .description("Print the mockup authoring guide: the markup dialect, the components, a complete example")
58
+ .action(wrap(guideCommand));
59
+ mockup
60
+ .command("create")
61
+ .description("Create a mockup in the project from a markup file")
62
+ .requiredOption("--title <title>", "The mockup's title")
63
+ .requiredOption("--file <path>", "The markup file")
64
+ .action(wrap(createCommand));
65
+ mockup
66
+ .command("preview <mockup-id>")
67
+ .description("Render a mockup to a self-contained HTML file, to open in a browser")
68
+ .option("--out <path>", "Where to write it (default: mockup-<id>.html)")
69
+ .action(wrap(previewCommand));
70
+ }
71
+ function wrap(fn) {
72
+ return async (...args) => {
73
+ try {
74
+ await fn(...args);
75
+ }
76
+ catch (error) {
77
+ console.error(`\nError: ${error instanceof Error ? error.message : String(error)}\n`);
78
+ process.exitCode = 1;
79
+ }
80
+ };
81
+ }
82
+ //# sourceMappingURL=index.js.map
@@ -1,9 +1,10 @@
1
+ import { convexUrl } from "../cloud.js";
1
2
  import { findRequirementsDir, readCoverageCache } from "../harness/cache.js";
2
3
  import { formatUnrecordedNotice } from "../harness/reportingStatus.js";
3
4
  import { getProjectCoverage, getRequirementCoverage, } from "../requirements/cloud-coverage.js";
4
5
  import { buildLocalReport, } from "../requirements/coverage.js";
5
6
  import { findProjectRoot, getProjectCredentials, } from "../utils/project-settings.js";
6
- const CONVEX_URL = "https://data.dotrequirements.io";
7
+ import { markVersionMessagesShown, versionMessages, } from "../version-messages.js";
7
8
  export async function reportCommand(options) {
8
9
  const source = options.source ?? "local";
9
10
  const format = options.format ?? "console";
@@ -52,24 +53,28 @@ export async function reportCommand(options) {
52
53
  if (options.requirement) {
53
54
  // REPORT-CLOUD-1.7: --branch/--since apply to the requirement-scoped
54
55
  // output too, not just the project-wide report
55
- const record = await getRequirementCoverage(options.requirement, projectId, projectSecret, CONVEX_URL, {
56
+ const record = await getRequirementCoverage(options.requirement, projectId, projectSecret, convexUrl(), {
56
57
  branch: options.branch,
57
58
  sinceTimestamp: options.since,
58
59
  });
59
60
  console.log(printCloudRequirement(record, format, {
60
61
  branch: options.branch,
61
62
  since: options.since,
62
- }));
63
+ }, format === "json" ? await versionMessages() : []));
64
+ if (format === "json")
65
+ markVersionMessagesShown();
63
66
  return;
64
67
  }
65
- const record = await getProjectCoverage(projectId, projectSecret, CONVEX_URL, {
68
+ const record = await getProjectCoverage(projectId, projectSecret, convexUrl(), {
66
69
  branch: options.branch,
67
70
  sinceTimestamp: options.since,
68
71
  });
69
72
  console.log(printCloudProject(record, format, {
70
73
  branch: options.branch,
71
74
  since: options.since,
72
- }));
75
+ }, format === "json" ? await versionMessages() : []));
76
+ if (format === "json")
77
+ markVersionMessagesShown();
73
78
  }
74
79
  function printLocalReport(report, format) {
75
80
  switch (format) {
@@ -122,9 +127,10 @@ function formatLocalMarkdown(report) {
122
127
  }
123
128
  return out;
124
129
  }
125
- function printCloudRequirement(record, format, filters) {
130
+ function printCloudRequirement(record, format, filters, messages = []) {
126
131
  if (format === "json") {
127
- return JSON.stringify({ source: "cloud", requirement: record }, null, 2);
132
+ // CLI-MSG-1.1: the cloud's messages for this version travel in the JSON.
133
+ return JSON.stringify({ source: "cloud", requirement: record, messages }, null, 2);
128
134
  }
129
135
  if (!record.lastTestedAt) {
130
136
  // REPORT-CLOUD-1.7: with filters in play, an empty record means nothing
@@ -168,9 +174,16 @@ function printCloudRequirement(record, format, filters) {
168
174
  }
169
175
  return lines.join("\n");
170
176
  }
171
- function printCloudProject(record, format, filters) {
177
+ function printCloudProject(record, format, filters, messages = []) {
172
178
  if (format === "json") {
173
- return JSON.stringify({ source: "cloud", filters, summary: cloudSummary(record), ...record }, null, 2);
179
+ // CLI-MSG-1.1: the cloud's messages for this version travel in the JSON.
180
+ return JSON.stringify({
181
+ source: "cloud",
182
+ filters,
183
+ summary: cloudSummary(record),
184
+ ...record,
185
+ messages,
186
+ }, null, 2);
174
187
  }
175
188
  const summary = cloudSummary(record);
176
189
  const filterNote = formatCloudFilters(filters);
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { DEFAULT_API_BASE_URL, fetchReviewTestFeedback, } from "../requirements/cloud-ai.js";
3
+ import { webApiUrl } from "../cloud.js";
4
+ import { fetchReviewTestFeedback } from "../requirements/cloud-ai.js";
4
5
  import { findRequirementsInFile } from "../requirements/grep.js";
5
6
  import { formatRequirementTree, getRequirementTree, loadAllRequirements, } from "../requirements/index.js";
6
7
  import { getProjectCredentials } from "../utils/project-settings.js";
@@ -145,7 +146,7 @@ async function runCloudReview(params) {
145
146
  `(${error instanceof Error ? error.message : String(error)})`);
146
147
  }
147
148
  // CLI-REVIEW-3.0 / .1 / .3: dispatch to the hosted endpoint
148
- const apiBaseUrl = process.env.DOTREQUIREMENTS_API_URL ?? DEFAULT_API_BASE_URL;
149
+ const apiBaseUrl = webApiUrl();
149
150
  let feedback;
150
151
  let quotaWarning;
151
152
  try {
@@ -0,0 +1,50 @@
1
+ import type { Command } from "commander";
2
+ import type { Scores } from "../solution/things.js";
3
+ /**
4
+ * Scores from the shell (VERB-7, SCORES-1): a solution's price on CREATED's
5
+ * seven axes, made with `create` and revised with `update`, each axis a
6
+ * whole number from 1 to 5 with a reason. Like a board or a mockup, scores
7
+ * are a thing: `create` prints the `scores:<id>` a writer submits with
8
+ * `dotreq solution author`, whose comment says what changed and why.
9
+ * Nothing reaches the backend unless every axis is whole, and the backend
10
+ * refuses the same again.
11
+ */
12
+ /**
13
+ * The seven axes as `dotreq scores` takes them (VERB-7): the key the backend stores,
14
+ * the name a person reads, and the option, which is also how a line of the
15
+ * reasons file names the axis. The backend's own list is
16
+ * packages/convex/solutions/scores.ts; the CLI can't import it, so a test
17
+ * keeps the two equal.
18
+ */
19
+ export declare const AXES: ReadonlyArray<{
20
+ key: keyof Scores;
21
+ label: string;
22
+ option: string;
23
+ }>;
24
+ export type ScoresOptions = {
25
+ costOfDelay?: string;
26
+ relevance?: string;
27
+ entities?: string;
28
+ api?: string;
29
+ testing?: string;
30
+ experience?: string;
31
+ doubt?: string;
32
+ /** A file with one line per axis: `<axis>: <reason>`. */
33
+ reasons?: string;
34
+ };
35
+ /**
36
+ * Each axis's reason from the reasons file (VERB-7.0): one line an axis,
37
+ * `<axis>: <reason>`, the axis spelled as its option is, in any case; a
38
+ * leading "- " and blank lines are passed over. Refuses a line in another
39
+ * form, an axis that isn't one of the seven or is named twice (VERB-7.3),
40
+ * and an axis with no reason (VERB-7.2).
41
+ */
42
+ export declare function reasonsFrom(text: string, file: string): Record<keyof Scores, string>;
43
+ /** `dotreq scores create`: new scores, and the reference to record them by (VERB-7.0). */
44
+ export declare function createCommand(options: ScoresOptions): Promise<void>;
45
+ /** `dotreq scores update <id>`: all seven axes again, for a revision (VERB-7.1). */
46
+ export declare function updateCommand(scoresId: string, options: ScoresOptions): Promise<void>;
47
+ /** The axes, the reasons file and an example, on `scores --help` (VERB-7.4). */
48
+ export declare const SCORES_HELP = "\nScores are seven axes, each a whole number from 1 to 5 with one line of reasoning:\n suitability, whether to do this now:\n --cost-of-delay, --relevance\n appetite, what it takes to build and how likely the other six are wrong:\n --entities, --api, --testing, --experience, --doubt\n\nThe reasons file has one line per axis, the axis named as its option is,\nwithout the dashes:\n cost-of-delay: <why this score>\n relevance: <why>\n entities: <why>\n api: <why>\n testing: <why>\n experience: <why>\n doubt: <why>\n\ncreate prints the scores' reference; record them with it, as any work is\nrecorded. To revise them, update them with all seven axes again and record\nthem again.\n\nExamples:\n dotreq scores create --cost-of-delay 4 --relevance 3 --entities 2 --api 2 \\\n --testing 2 --experience 3 --doubt 2 --reasons reasons.txt\n dotreq solution author scores:<id> --comment \"Scored against the pitch's draft and the discovery board.\"\n dotreq scores update <id> --cost-of-delay 4 --relevance 3 --entities 3 --api 2 \\\n --testing 2 --experience 3 --doubt 3 --reasons reasons.txt\n";
49
+ export declare function registerScores(program: Command): void;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,190 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { api } from "../../convex.js";
3
+ import { solutionClient } from "../solution/client.js";
4
+ /**
5
+ * Hidden from help until solutions launch: today's users see the CLI they
6
+ * have now, and the server refuses these commands for a team without the
7
+ * solutions switch (SOLACCESS-2).
8
+ */
9
+ const HIDDEN_UNTIL_LAUNCH = { hidden: true };
10
+ /**
11
+ * Scores from the shell (VERB-7, SCORES-1): a solution's price on CREATED's
12
+ * seven axes, made with `create` and revised with `update`, each axis a
13
+ * whole number from 1 to 5 with a reason. Like a board or a mockup, scores
14
+ * are a thing: `create` prints the `scores:<id>` a writer submits with
15
+ * `dotreq solution author`, whose comment says what changed and why.
16
+ * Nothing reaches the backend unless every axis is whole, and the backend
17
+ * refuses the same again.
18
+ */
19
+ /**
20
+ * The seven axes as `dotreq scores` takes them (VERB-7): the key the backend stores,
21
+ * the name a person reads, and the option, which is also how a line of the
22
+ * reasons file names the axis. The backend's own list is
23
+ * packages/convex/solutions/scores.ts; the CLI can't import it, so a test
24
+ * keeps the two equal.
25
+ */
26
+ export const AXES = [
27
+ { key: "costOfDelay", label: "Cost of delay", option: "cost-of-delay" },
28
+ { key: "relevance", label: "Relevance", option: "relevance" },
29
+ { key: "entities", label: "Entities", option: "entities" },
30
+ { key: "api", label: "API", option: "api" },
31
+ { key: "testing", label: "Testing", option: "testing" },
32
+ { key: "experience", label: "Experience", option: "experience" },
33
+ { key: "doubt", label: "Doubt", option: "doubt" },
34
+ ];
35
+ const AXIS_NAMES = `The axes are ${AXES.map((axis) => axis.option).join(", ")}.`;
36
+ /** Each axis's score from its option, or a refusal naming the axis (VERB-7.2). */
37
+ function scoresFrom(options) {
38
+ const scores = {};
39
+ for (const axis of AXES) {
40
+ const given = options[axis.key]?.trim();
41
+ if (!given) {
42
+ throw new Error(`${axis.label} has no score: give it with --${axis.option} <1-5>.`);
43
+ }
44
+ const score = Number(given);
45
+ if (!/^\d+$/.test(given) || score < 1 || score > 5) {
46
+ throw new Error(`${axis.label} is scored "${given}": a score is a whole number from 1 to 5.`);
47
+ }
48
+ scores[axis.key] = score;
49
+ }
50
+ return scores;
51
+ }
52
+ /**
53
+ * Each axis's reason from the reasons file (VERB-7.0): one line an axis,
54
+ * `<axis>: <reason>`, the axis spelled as its option is, in any case; a
55
+ * leading "- " and blank lines are passed over. Refuses a line in another
56
+ * form, an axis that isn't one of the seven or is named twice (VERB-7.3),
57
+ * and an axis with no reason (VERB-7.2).
58
+ */
59
+ export function reasonsFrom(text, file) {
60
+ const reasons = {};
61
+ const lines = text.split(/\r?\n/);
62
+ lines.forEach((raw, index) => {
63
+ const line = raw.trim().replace(/^-\s+/, "");
64
+ if (!line)
65
+ return;
66
+ const colon = line.indexOf(":");
67
+ if (colon === -1) {
68
+ throw new Error(`Line ${index + 1} of ${file} isn't "<axis>: <reason>": "${raw.trim()}". ${AXIS_NAMES}`);
69
+ }
70
+ const name = line.slice(0, colon).trim().toLowerCase();
71
+ const axis = AXES.find((a) => a.option === name);
72
+ if (!axis) {
73
+ throw new Error(`"${name}" in ${file} isn't an axis. ${AXIS_NAMES}`);
74
+ }
75
+ if (reasons[axis.key] !== undefined) {
76
+ throw new Error(`"${name}" is named twice in ${file}. ${AXIS_NAMES}`);
77
+ }
78
+ reasons[axis.key] = line.slice(colon + 1).trim();
79
+ });
80
+ for (const axis of AXES) {
81
+ if (!reasons[axis.key]) {
82
+ throw new Error(`${axis.label} has no reason: add the line "${axis.option}: <why>" to ${file}.`);
83
+ }
84
+ }
85
+ return reasons;
86
+ }
87
+ /** The seven axes, checked, from the options and the reasons file. */
88
+ function axesFrom(options) {
89
+ const scores = scoresFrom(options);
90
+ const file = options.reasons?.trim();
91
+ if (!file) {
92
+ throw new Error(`Give each axis's reason in a file: --reasons <file>, one line per axis, "<axis>: <reason>". ${AXIS_NAMES}`);
93
+ }
94
+ let text;
95
+ try {
96
+ text = readFileSync(file, "utf8");
97
+ }
98
+ catch {
99
+ throw new Error(`Can't read the reasons file ${file}.`);
100
+ }
101
+ const reasons = reasonsFrom(text, file);
102
+ return Object.fromEntries(AXES.map((axis) => [
103
+ axis.key,
104
+ { score: scores[axis.key], reason: reasons[axis.key] },
105
+ ]));
106
+ }
107
+ /** `dotreq scores create`: new scores, and the reference to record them by (VERB-7.0). */
108
+ export async function createCommand(options) {
109
+ const axes = axesFrom(options);
110
+ const client = solutionClient();
111
+ const { scoresId } = await client.mutation(api.scores.mutations.create, { project: client.project, ...axes });
112
+ console.log(`Created scores ${scoresId}. Record them with: dotreq solution author scores:${scoresId} --comment "..."`);
113
+ }
114
+ /** `dotreq scores update <id>`: all seven axes again, for a revision (VERB-7.1). */
115
+ export async function updateCommand(scoresId, options) {
116
+ const axes = axesFrom(options);
117
+ // Named by its id or by the reference `create` printed.
118
+ const id = scoresId.replace(/^scores:/, "");
119
+ await solutionClient().mutation(api.scores.mutations.update, {
120
+ scoresId: id,
121
+ ...axes,
122
+ });
123
+ console.log(`Updated scores ${id}. Record the revision with: dotreq solution author scores:${id} --comment "..."`);
124
+ }
125
+ /** The axes, the reasons file and an example, on `scores --help` (VERB-7.4). */
126
+ export const SCORES_HELP = `
127
+ Scores are seven axes, each a whole number from 1 to 5 with one line of reasoning:
128
+ suitability, whether to do this now:
129
+ --cost-of-delay, --relevance
130
+ appetite, what it takes to build and how likely the other six are wrong:
131
+ --entities, --api, --testing, --experience, --doubt
132
+
133
+ The reasons file has one line per axis, the axis named as its option is,
134
+ without the dashes:
135
+ cost-of-delay: <why this score>
136
+ relevance: <why>
137
+ entities: <why>
138
+ api: <why>
139
+ testing: <why>
140
+ experience: <why>
141
+ doubt: <why>
142
+
143
+ create prints the scores' reference; record them with it, as any work is
144
+ recorded. To revise them, update them with all seven axes again and record
145
+ them again.
146
+
147
+ Examples:
148
+ dotreq scores create --cost-of-delay 4 --relevance 3 --entities 2 --api 2 \\
149
+ --testing 2 --experience 3 --doubt 2 --reasons reasons.txt
150
+ dotreq solution author scores:<id> --comment "Scored against the pitch's draft and the discovery board."
151
+ dotreq scores update <id> --cost-of-delay 4 --relevance 3 --entities 3 --api 2 \\
152
+ --testing 2 --experience 3 --doubt 3 --reasons reasons.txt
153
+ `;
154
+ /** The seven axis options and the reasons file, on both subcommands. */
155
+ function axisOptions(command) {
156
+ return command
157
+ .option("--cost-of-delay <n>", "Cost of delay, 1 to 5")
158
+ .option("--relevance <n>", "Relevance, 1 to 5")
159
+ .option("--entities <n>", "Entities, 1 to 5")
160
+ .option("--api <n>", "API, 1 to 5")
161
+ .option("--testing <n>", "Testing, 1 to 5")
162
+ .option("--experience <n>", "Experience, 1 to 5")
163
+ .option("--doubt <n>", "Doubt, 1 to 5")
164
+ .option("--reasons <file>", "A file with one line per axis, <axis>: <reason> (required)")
165
+ .addHelpText("after", SCORES_HELP);
166
+ }
167
+ export function registerScores(program) {
168
+ const scores = program
169
+ .command("scores", HIDDEN_UNTIL_LAUNCH)
170
+ .description("Make and revise a solution's scores: seven axes, each a whole number from 1 to 5 with a reason")
171
+ .addHelpText("after", SCORES_HELP);
172
+ axisOptions(scores
173
+ .command("create")
174
+ .description("Make scores from the seven axis options and a reasons file, and print the reference to record them by")).action(wrap(createCommand));
175
+ axisOptions(scores
176
+ .command("update <scores>")
177
+ .description("Revise scores, named by their id or scores:<id>, with all seven axes again; record them again with author")).action(wrap(updateCommand));
178
+ }
179
+ function wrap(fn) {
180
+ return async (...args) => {
181
+ try {
182
+ await fn(...args);
183
+ }
184
+ catch (error) {
185
+ console.error(`\nError: ${error instanceof Error ? error.message : String(error)}\n`);
186
+ process.exitCode = 1;
187
+ }
188
+ };
189
+ }
190
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,9 @@
1
+ import { type WhereOptions } from "./run-file.js";
2
+ /**
3
+ * `dotreq solution ask <request-file>`: the drafter puts a decision to the
4
+ * owner, on the outcome it is drafting or the one `--outcome` names. A
5
+ * reviewer escalates instead (`dotreq solution review escalate`), and the
6
+ * backend refuses its question (VERB-3.5).
7
+ */
8
+ export declare function askCommand(file: string, options: WhereOptions): Promise<void>;
9
+ //# sourceMappingURL=ask.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { api } from "../../convex.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { pushBeforeRecord } from "./pushing.js";
4
+ import { readRequest } from "./request.js";
5
+ import { onOutcome, requireWhere } from "./run-file.js";
6
+ /**
7
+ * `dotreq solution ask <request-file>`: the drafter puts a decision to the
8
+ * owner, on the outcome it is drafting or the one `--outcome` names. A
9
+ * reviewer escalates instead (`dotreq solution review escalate`), and the
10
+ * backend refuses its question (VERB-3.5).
11
+ */
12
+ export async function askCommand(file, options) {
13
+ const request = readRequest(file);
14
+ const where = requireWhere(options);
15
+ const { solutionId, agent } = where;
16
+ const client = solutionClient();
17
+ // SOLBRANCH-3.0: the drafter's commits reach the branch before Quinn is asked.
18
+ await pushBeforeRecord(client, where);
19
+ await client.mutation(api.solutions.mutations.ask, {
20
+ solutionId,
21
+ agent,
22
+ ...(where.outcome ? { outcome: where.outcome } : {}),
23
+ request,
24
+ });
25
+ console.log(`Asked${onOutcome(where.outcome)}. It waits on the owner's decision.`);
26
+ }
27
+ //# sourceMappingURL=ask.js.map
@@ -0,0 +1,19 @@
1
+ import { type WhereOptions } from "./run-file.js";
2
+ import { type SummaryOptions } from "./summary.js";
3
+ export type AuthorOptions = WhereOptions & SummaryOptions & {
4
+ /** What changed and why, for the outcome's thread (VERB-1.1, STEP-REVIEW-5.0). */
5
+ comment?: string;
6
+ /** Each reference's name on the outcome's tab: one per reference, in their order, or none (SIGHT-12.3). */
7
+ title?: string[];
8
+ };
9
+ /**
10
+ * `dotreq solution author <reference>... --comment "<what changed and why>"`:
11
+ * the drafter's submission of the outcome it is drafting, or of the one
12
+ * `--outcome` names. The references say where what it made is; the comment
13
+ * is what the next reader of the thread sees first, so nothing is recorded
14
+ * without it (VERB-1.2). On an outcome no agent reviews, the work goes to
15
+ * the owner with the executive summary, `--executive-summary "..."`, which
16
+ * the backend asks for there (VERB-1.6).
17
+ */
18
+ export declare function authorCommand(references: string[], options: AuthorOptions): Promise<void>;
19
+ //# sourceMappingURL=author.d.ts.map