@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/commands/link.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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(
|
|
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.
|
|
205
|
-
|
|
206
|
-
|
|
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.
|
|
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
|
-
{
|
|
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.
|
|
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
|
-
{
|
|
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.
|
|
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
|
-
{
|
|
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(
|
|
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: ${
|
|
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
|
package/dist/commands/report.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|