@popoverai/dotrequirements 0.33.0 → 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 (115) hide show
  1. package/dist/cli.js +13 -1
  2. package/dist/cloud.d.ts +5 -1
  3. package/dist/cloud.js +6 -2
  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/diff.js +15 -5
  9. package/dist/commands/mockup/index.d.ts +11 -0
  10. package/dist/commands/mockup/index.js +82 -0
  11. package/dist/commands/scores/index.d.ts +50 -0
  12. package/dist/commands/scores/index.js +190 -0
  13. package/dist/commands/solution/ask.d.ts +9 -0
  14. package/dist/commands/solution/ask.js +27 -0
  15. package/dist/commands/solution/author.d.ts +19 -0
  16. package/dist/commands/solution/author.js +202 -0
  17. package/dist/commands/solution/branch.d.ts +103 -0
  18. package/dist/commands/solution/branch.js +341 -0
  19. package/dist/commands/solution/brief.d.ts +81 -0
  20. package/dist/commands/solution/brief.js +47 -0
  21. package/dist/commands/solution/checkout.d.ts +27 -0
  22. package/dist/commands/solution/checkout.js +66 -0
  23. package/dist/commands/solution/client.d.ts +16 -0
  24. package/dist/commands/solution/client.js +46 -0
  25. package/dist/commands/solution/contract.d.ts +9 -0
  26. package/dist/commands/solution/contract.js +23 -0
  27. package/dist/commands/solution/create.d.ts +14 -0
  28. package/dist/commands/solution/create.js +52 -0
  29. package/dist/commands/solution/demo-script.d.ts +28 -0
  30. package/dist/commands/solution/demo-script.js +43 -0
  31. package/dist/commands/solution/image.d.ts +60 -0
  32. package/dist/commands/solution/image.js +148 -0
  33. package/dist/commands/solution/index.d.ts +12 -0
  34. package/dist/commands/solution/index.js +281 -0
  35. package/dist/commands/solution/look-up.d.ts +9 -0
  36. package/dist/commands/solution/look-up.js +69 -0
  37. package/dist/commands/solution/next.d.ts +12 -0
  38. package/dist/commands/solution/next.js +48 -0
  39. package/dist/commands/solution/plan-file.d.ts +22 -0
  40. package/dist/commands/solution/plan-file.js +33 -0
  41. package/dist/commands/solution/preview-screenshot.d.ts +21 -0
  42. package/dist/commands/solution/preview-screenshot.js +56 -0
  43. package/dist/commands/solution/pull-request.d.ts +25 -0
  44. package/dist/commands/solution/pull-request.js +117 -0
  45. package/dist/commands/solution/pushing.d.ts +66 -0
  46. package/dist/commands/solution/pushing.js +185 -0
  47. package/dist/commands/solution/rehearse.d.ts +10 -0
  48. package/dist/commands/solution/rehearse.js +24 -0
  49. package/dist/commands/solution/request.d.ts +3 -0
  50. package/dist/commands/solution/request.js +13 -0
  51. package/dist/commands/solution/review.d.ts +27 -0
  52. package/dist/commands/solution/review.js +104 -0
  53. package/dist/commands/solution/run-file.d.ts +42 -0
  54. package/dist/commands/solution/run-file.js +63 -0
  55. package/dist/commands/solution/share.d.ts +62 -0
  56. package/dist/commands/solution/share.js +346 -0
  57. package/dist/commands/solution/stall.d.ts +14 -0
  58. package/dist/commands/solution/stall.js +41 -0
  59. package/dist/commands/solution/start.d.ts +11 -0
  60. package/dist/commands/solution/start.js +20 -0
  61. package/dist/commands/solution/status.d.ts +39 -0
  62. package/dist/commands/solution/status.js +145 -0
  63. package/dist/commands/solution/summary.d.ts +13 -0
  64. package/dist/commands/solution/summary.js +5 -0
  65. package/dist/commands/solution/things.d.ts +118 -0
  66. package/dist/commands/solution/things.js +78 -0
  67. package/dist/commands/solution/thread.d.ts +13 -0
  68. package/dist/commands/solution/thread.js +53 -0
  69. package/dist/commands/solution/upload.d.ts +24 -0
  70. package/dist/commands/solution/upload.js +27 -0
  71. package/dist/commands/solution/video.d.ts +88 -0
  72. package/dist/commands/solution/video.js +364 -0
  73. package/dist/commands/solution/working-folder.d.ts +44 -0
  74. package/dist/commands/solution/working-folder.js +64 -0
  75. package/dist/commands/sync.d.ts +2 -1
  76. package/dist/commands/sync.js +21 -5
  77. package/dist/convex.d.ts +54 -0
  78. package/dist/convex.js +64 -0
  79. package/dist/push/core.js +7 -2
  80. package/dist/rehearsal/derive.d.ts +23 -0
  81. package/dist/rehearsal/derive.js +112 -0
  82. package/dist/rehearsal/driver.d.ts +44 -0
  83. package/dist/rehearsal/driver.js +337 -0
  84. package/dist/rehearsal/earlier.d.ts +25 -0
  85. package/dist/rehearsal/earlier.js +138 -0
  86. package/dist/rehearsal/index.d.ts +5 -0
  87. package/dist/rehearsal/index.js +4 -0
  88. package/dist/rehearsal/live.d.ts +72 -0
  89. package/dist/rehearsal/live.js +301 -0
  90. package/dist/rehearsal/report.d.ts +4 -0
  91. package/dist/rehearsal/report.js +69 -0
  92. package/dist/rehearsal/types.d.ts +377 -0
  93. package/dist/rehearsal/types.js +8 -0
  94. package/dist/requirements/index.d.ts +6 -0
  95. package/dist/requirements/index.js +24 -0
  96. package/dist/schema/browser.d.ts +1 -1
  97. package/dist/schema/browser.js +3 -1
  98. package/dist/schema/file-writer.d.ts +5 -0
  99. package/dist/schema/file-writer.js +4 -0
  100. package/dist/schema/parser.d.ts +11 -2
  101. package/dist/schema/parser.js +11 -7
  102. package/dist/schema/schemas.d.ts +40 -0
  103. package/dist/schema/schemas.js +4 -0
  104. package/dist/sync/execute.d.ts +10 -1
  105. package/dist/sync/execute.js +69 -5
  106. package/dist/sync/index.d.ts +1 -1
  107. package/dist/sync/index.js +1 -1
  108. package/dist/sync/publish.d.ts +7 -0
  109. package/dist/sync/segment.js +4 -1
  110. package/dist/sync/snapshot.d.ts +23 -1
  111. package/dist/sync/snapshot.js +75 -3
  112. package/dist/sync/solution-documents.d.ts +64 -0
  113. package/dist/sync/solution-documents.js +97 -0
  114. package/dist/sync/types.d.ts +18 -0
  115. package/package.json +3 -2
@@ -0,0 +1,138 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { parse } from "yaml";
4
+ import { parseThing } from "../commands/solution/things.js";
5
+ import { api } from "../convex.js";
6
+ /**
7
+ * A new rehearsal replaces the earlier runs of its scenario (REHEARSE-6):
8
+ * the rehearsal solutions in the project recorded as runs of the same
9
+ * scenario file are removed before it starts, with what they made. What any scenario in the
10
+ * same folder names as an input stays, so that scenario can still start
11
+ * from it; so does an earlier run whose history a scenario there starts
12
+ * from (6.2.0), whole.
13
+ */
14
+ /**
15
+ * What the scenarios in this scenario's folder name: the documents, boards
16
+ * and mockups they name as inputs, and the earlier rehearsals' solutions
17
+ * whose history they start from (REHEARSE-6.2.0), each by id, with the files
18
+ * that name it. Refuses when one of those files can't be read, since then
19
+ * what it names can't be known.
20
+ */
21
+ export function namedInputs(scenarioPath) {
22
+ return namedInFolder(scenarioPath).inputs;
23
+ }
24
+ function namedInFolder(scenarioPath) {
25
+ const folder = dirname(resolve(scenarioPath));
26
+ const named = new Map();
27
+ const histories = new Map();
28
+ for (const name of readdirSync(folder).sort()) {
29
+ if (!name.endsWith(".yaml"))
30
+ continue;
31
+ let inputs;
32
+ try {
33
+ const file = parse(readFileSync(join(folder, name), "utf8"));
34
+ inputs = file?.inputs;
35
+ const source = file?.history?.solution;
36
+ if (typeof source === "string" && source.trim()) {
37
+ const id = source.trim();
38
+ histories.set(id, [...(histories.get(id) ?? []), name]);
39
+ }
40
+ for (const entries of Object.values((inputs ?? {}))) {
41
+ for (const entry of Array.isArray(entries) ? entries : []) {
42
+ if (typeof entry !== "string")
43
+ continue;
44
+ const thing = parseThing(entry);
45
+ const id = thing.kind === "document"
46
+ ? thing.documentId
47
+ : thing.kind === "board"
48
+ ? thing.boardId
49
+ : thing.kind === "mockup"
50
+ ? thing.mockupId
51
+ : null;
52
+ if (id)
53
+ named.set(id, [...(named.get(id) ?? []), name]);
54
+ }
55
+ }
56
+ }
57
+ catch (error) {
58
+ throw new Error(`Can't tell what ${name} names as its inputs, so no earlier run was removed: ${error instanceof Error ? error.message : String(error)} Fix it, or rehearse with --keep.`);
59
+ }
60
+ }
61
+ return { inputs: named, histories };
62
+ }
63
+ /**
64
+ * How long a rehearsal waits for an earlier run's branch to be deleted
65
+ * before it starts anyway: the deletion is the backend's, scheduled as the
66
+ * run is removed.
67
+ */
68
+ const BRANCH_WAIT_MS = 60_000;
69
+ /**
70
+ * Wait for the backend to delete an earlier run's branch (REHEARSE-6.7):
71
+ * it must be gone before the new run makes its own, or that one takes a
72
+ * suffixed name. What became of it, or pending when it took too long.
73
+ */
74
+ async function branchOutcome(client, removalId, pause) {
75
+ const deadline = Date.now() + BRANCH_WAIT_MS;
76
+ for (;;) {
77
+ const removal = await client.query(api.solutions.branches.removal, { removalId });
78
+ if (!removal)
79
+ return null;
80
+ if (removal.outcome) {
81
+ return {
82
+ name: removal.branch,
83
+ outcome: removal.outcome,
84
+ ...(removal.why ? { why: removal.why } : {}),
85
+ };
86
+ }
87
+ if (Date.now() >= deadline) {
88
+ return { name: removal.branch, outcome: "pending" };
89
+ }
90
+ await pause(1000);
91
+ }
92
+ }
93
+ /**
94
+ * Remove the earlier runs of the scenario, keeping what a scenario names
95
+ * (REHEARSE-6.0, 6.2), and keeping whole an earlier run whose history a
96
+ * scenario starts from (6.2.0).
97
+ */
98
+ export async function removeEarlierRuns(scenarioPath, client, pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
99
+ const { inputs: named, histories } = namedInFolder(scenarioPath);
100
+ const scenario = basename(scenarioPath);
101
+ const earlier = await client.query(api.solutions.queries.rehearsalsOf, { project: client.project, scenario });
102
+ const kept = [];
103
+ const keptRuns = [];
104
+ const branches = [];
105
+ let removedRuns = 0;
106
+ for (const solutionId of earlier) {
107
+ const startFromIt = histories.get(solutionId);
108
+ if (startFromIt) {
109
+ keptRuns.push({ solutionId, namedBy: startFromIt });
110
+ continue;
111
+ }
112
+ removedRuns++;
113
+ const removed = await client.mutation(api.solutions.mutations.removeRehearsal, {
114
+ solutionId,
115
+ keep: [...named.keys()],
116
+ });
117
+ // Each of its branches, one per pull-request surface (SOLBRANCH-1.4).
118
+ for (const removalId of removed.branchRemovals ?? []) {
119
+ const branch = await branchOutcome(client, removalId, pause);
120
+ if (branch)
121
+ branches.push(branch);
122
+ }
123
+ for (const thing of removed.kept) {
124
+ kept.push({
125
+ reference: `${thing.kind}:${thing.id}`,
126
+ namedBy: thing.because === "named" ? (named.get(thing.id) ?? []) : [],
127
+ });
128
+ }
129
+ }
130
+ return {
131
+ scenario,
132
+ removed: removedRuns,
133
+ kept,
134
+ ...(keptRuns.length > 0 ? { keptRuns } : {}),
135
+ ...(branches.length > 0 ? { branches } : {}),
136
+ };
137
+ }
138
+ //# sourceMappingURL=earlier.js.map
@@ -0,0 +1,5 @@
1
+ export { planForOutcomes, planOf } from "./derive.js";
2
+ export { partiesOf, rehearse } from "./driver.js";
3
+ export { renderReport } from "./report.js";
4
+ export type * from "./types.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { planForOutcomes, planOf } from "./derive.js";
2
+ export { partiesOf, rehearse } from "./driver.js";
3
+ export { renderReport } from "./report.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,72 @@
1
+ import { solutionClient } from "../commands/solution/client.js";
2
+ import type { Party, PersonScript, RehearsalClient, Scenario } from "./types.js";
3
+ /**
4
+ * The live mode (REHEARSE-4): the driver over the CLI's client against the
5
+ * linked project. Scripted agents run through the same verbs an agent
6
+ * runs; real agents are the runner's, awaited; the person's decisions are
7
+ * answered from the scenario's own script, and the rehearsal stops only at
8
+ * a decision nobody scripted.
9
+ */
10
+ /** The scenario as a file: plans and artifacts by name, scripts as data. */
11
+ export type ScenarioFile = {
12
+ title: string;
13
+ /** What the person wrote about the solution beyond its title, as Markdown. */
14
+ description?: string;
15
+ /**
16
+ * "default" for the project's plan; a path to a plan file; or some of
17
+ * either's outcomes, run on their own: `{ from: default, outcomes: [Code,
18
+ * Demo] }`, or `outcome: Pitch` for one. `step` and `steps` are read as
19
+ * `outcome` and `outcomes`.
20
+ */
21
+ plan: "default" | string | {
22
+ from?: "default" | string;
23
+ outcome?: string;
24
+ outcomes?: string[];
25
+ step?: string;
26
+ steps?: string[];
27
+ };
28
+ /**
29
+ * The branch the solution's branch is made from, rather than the
30
+ * repository's default branch (REHEARSE-4.5): `branch: rehearsal-fixtures/spec`.
31
+ */
32
+ branch?: string;
33
+ /**
34
+ * An earlier rehearsal's solution and an outcome it made, whose history
35
+ * the solution starts with (REHEARSE-4.8):
36
+ * `history: { solution: q97fe7s7…, after: Code }`. Each input `inputs`
37
+ * doesn't name is taken from that solution as it stood when that outcome
38
+ * was made, with its thread.
39
+ */
40
+ history?: {
41
+ solution?: unknown;
42
+ after?: unknown;
43
+ };
44
+ /**
45
+ * Artifacts by input name: references; `image:<path>` for an image, its
46
+ * path from the scenario's folder, to upload first (REHEARSE-4.7); or
47
+ * { file } to publish first.
48
+ */
49
+ inputs?: Record<string, Array<string | {
50
+ file: string;
51
+ title?: string;
52
+ }>>;
53
+ /**
54
+ * By agent (`Code Reviewer`, `Pitch/drafter`), by an outcome's name (its
55
+ * drafter), by "<outcome>/reviewer" (its reviewers), or by
56
+ * "<outcome>/<agent>": "real", or a script as data (`Scenario.parties`).
57
+ */
58
+ parties?: Record<string, "real" | Omit<Party, "expect">>;
59
+ /** What the person answers, by an outcome's name or a checkpoint's, in order asked. */
60
+ person?: PersonScript;
61
+ /** Minutes to wait for a real agent. */
62
+ wait?: number;
63
+ };
64
+ /** The scenario file read into a scenario the driver runs. */
65
+ export declare function scenarioFrom(path: string, client: ReturnType<typeof solutionClient>): Promise<Scenario>;
66
+ /**
67
+ * The driver's client over the CLI: the project secret for every call. The
68
+ * solution is created as a rehearsal's, which is what lets that same secret
69
+ * answer the decisions the scenario scripted.
70
+ */
71
+ export declare function liveClient(): RehearsalClient;
72
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1,301 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, resolve } from "node:path";
3
+ import { webApiUrl } from "../cloud.js";
4
+ import { solutionClient } from "../commands/solution/client.js";
5
+ import { ACCEPTED_IMAGE_FORMATS, imageFormat, } from "../commands/solution/image.js";
6
+ import { planPayload, readPlanFile } from "../commands/solution/plan-file.js";
7
+ import { parseThing } from "../commands/solution/things.js";
8
+ import { uploadToApp } from "../commands/solution/upload.js";
9
+ import { api } from "../convex.js";
10
+ import { publishToCloud } from "../sync/publish.js";
11
+ import { getProjectCredentials } from "../utils/project-settings.js";
12
+ async function planNamed(name, client, from) {
13
+ if (name === "default") {
14
+ const result = await client.query(api.solutions.queries.defaultPlan, { project: client.project });
15
+ return result.plan;
16
+ }
17
+ const spec = readPlanFile(readFileSync(resolve(from, name), "utf8"), name);
18
+ if (!spec?.plan)
19
+ throw new Error(`${name} has no plan in it.`);
20
+ // PLAN-1: the backend reads a plan as a file writes it.
21
+ return client.query(api.solutions.queries.readPlanFile, {
22
+ plan: planPayload(spec),
23
+ });
24
+ }
25
+ /**
26
+ * Artifacts named as files are published as documents first (REHEARSE-4.1),
27
+ * before the solution they are to belong to exists: the solution is created
28
+ * with them, and takes them as its own then (4.1.0). Whether one is a
29
+ * requirements document is its file's name, as at a push (SOLDOC-3). An
30
+ * image named by its path is uploaded first for the same reason, and kept
31
+ * on the input under its file name, as an image an agent submits is (4.7);
32
+ * `inputImages` read it beforehand.
33
+ */
34
+ async function artifact(entry, from, client, images) {
35
+ if (typeof entry === "string") {
36
+ const image = images.get(entry);
37
+ if (!image)
38
+ return { thing: parseThing(entry) };
39
+ const storageId = await uploadToApp(client, api.solutions.images.inputUploadUrl, { project: client.project }, image);
40
+ return { thing: { kind: "image", storageId, name: image.name } };
41
+ }
42
+ const { projectId, projectSecret } = getProjectCredentials();
43
+ const markdownContent = readFileSync(resolve(from, entry.file), "utf8");
44
+ const { documentId } = await publishToCloud({
45
+ projectId,
46
+ projectSecret,
47
+ markdownContent,
48
+ title: entry.title,
49
+ });
50
+ return {
51
+ thing: { kind: "document", documentId },
52
+ published: {
53
+ documentId,
54
+ isRequirementsDoc: entry.file.endsWith(".requirements.md"),
55
+ },
56
+ };
57
+ }
58
+ /**
59
+ * The images a scenario names for its inputs as `image:<path>`, a path from
60
+ * the scenario's folder, each read and checked as `dotreq solution author`
61
+ * checks one (SIGHT-9.0.0) before anything is published, removed or
62
+ * created (REHEARSE-4.7). By the entry that names each.
63
+ */
64
+ function inputImages(scenario, inputs, from) {
65
+ const images = new Map();
66
+ for (const entries of Object.values(inputs)) {
67
+ for (const entry of entries) {
68
+ if (typeof entry !== "string" || !entry.startsWith("image:"))
69
+ continue;
70
+ const path = entry.slice("image:".length);
71
+ const full = resolve(from, path);
72
+ if (!path || !existsSync(full) || !statSync(full).isFile()) {
73
+ throw new Error(`${scenario} names the image ${path || "(no path)"}, but there is no file there. Nothing was removed or created.`);
74
+ }
75
+ const bytes = new Uint8Array(readFileSync(full));
76
+ const format = imageFormat(bytes);
77
+ if ("refused" in format) {
78
+ throw new Error(`${scenario} names ${path} as an image, but it isn't one a browser can show (${format.refused}). Name ${ACCEPTED_IMAGE_FORMATS}. Nothing was removed or created.`);
79
+ }
80
+ images.set(entry, {
81
+ name: basename(full),
82
+ contentType: format.contentType,
83
+ bytes,
84
+ });
85
+ }
86
+ }
87
+ return images;
88
+ }
89
+ /**
90
+ * Refuse a scenario whose branch to start from the project's repository
91
+ * doesn't have (REHEARSE-4.6), before anything is published, removed or
92
+ * created. Asked of the app, which reads the repository with its own
93
+ * credential: a rehearsal runs from the project's connection sandbox,
94
+ * which has none.
95
+ */
96
+ async function checkBranch(name, branch) {
97
+ const { projectId, projectSecret } = getProjectCredentials();
98
+ const base = webApiUrl();
99
+ const response = await fetch(`${base}/api/rehearsals/branch`, {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({ projectId, projectSecret, branch }),
103
+ });
104
+ const said = (await response.json().catch(() => ({})));
105
+ if (!response.ok) {
106
+ throw new Error(`Couldn't find out whether the project's repository has the branch ${branch}, which ${name} starts from: ${said.error ?? `the app answered ${response.status}`}. Nothing was removed or created.`);
107
+ }
108
+ if (!said.repository) {
109
+ throw new Error(`${name} starts from the branch ${branch}, but the project has no repository to find it in. Nothing was removed or created.`);
110
+ }
111
+ if (!said.head) {
112
+ throw new Error(`${name} starts from the branch ${branch}, which ${said.repository} doesn't have. Nothing was removed or created.`);
113
+ }
114
+ }
115
+ /**
116
+ * The history a scenario starts from (REHEARSE-4.8), checked with the app
117
+ * before anything is published, removed or created (4.8.2): refused, saying
118
+ * which solution or outcome and why, when the solution isn't one of the
119
+ * project's rehearsals or hasn't made the outcome. What that solution
120
+ * holds, for the inputs the scenario leaves to it.
121
+ */
122
+ async function startingHistory(name, history, client) {
123
+ const solution = typeof history.solution === "string" ? history.solution.trim() : "";
124
+ const after = typeof history.after === "string" ? history.after.trim() : "";
125
+ if (!solution || !after) {
126
+ throw new Error(`${name}'s history names ${solution ? "no outcome" : "no solution"}: write it as history: { solution: <the earlier rehearsal's solution>, after: <an outcome it made> }. Nothing was removed or created.`);
127
+ }
128
+ const said = await client.query(api.solutions.queries.rehearsalHistory, { project: client.project, solution, after });
129
+ if ("refused" in said) {
130
+ throw new Error(`${name} can't start from that history. ${said.refused} Nothing was removed or created.`);
131
+ }
132
+ return { solution, after, outcomes: said.outcomes };
133
+ }
134
+ /** The outcomes a scenario's part of a plan names: `outcomes`, or one `outcome`; `steps` and `step` read the same. */
135
+ function outcomesNamed(path, part) {
136
+ const many = part.outcomes ?? part.steps;
137
+ const one = part.outcome ?? part.step;
138
+ const names = many ?? (one !== undefined ? [one] : []);
139
+ if (names.length === 0 || names.some((name) => typeof name !== "string")) {
140
+ throw new Error(`${path}'s plan names no outcomes to run: write it as plan: { from: default, outcomes: [<outcome>, ...] }.`);
141
+ }
142
+ return names;
143
+ }
144
+ /** The scenario file read into a scenario the driver runs. */
145
+ export async function scenarioFrom(path, client) {
146
+ const file = readPlanFile(readFileSync(resolve(path), "utf8"), path);
147
+ const from = resolve(path, "..");
148
+ if (!file?.title || !file.plan) {
149
+ throw new Error(`${path} needs a title and a plan.`);
150
+ }
151
+ const branch = typeof file.branch === "string" ? file.branch.trim() : "";
152
+ if (file.branch !== undefined && !branch) {
153
+ throw new Error(`${path}'s branch names no branch: give the name of the branch the solution's branch is to be made from, or leave it out.`);
154
+ }
155
+ if (branch)
156
+ await checkBranch(basename(path), branch);
157
+ const history = file.history === undefined || file.history === null
158
+ ? undefined
159
+ : await startingHistory(basename(path), typeof file.history === "object" ? file.history : {}, client);
160
+ const plan = typeof file.plan === "string"
161
+ ? await planNamed(file.plan, client, from)
162
+ : {
163
+ from: await planNamed(file.plan.from ?? "default", client, from),
164
+ outcomes: outcomesNamed(path, file.plan),
165
+ };
166
+ const images = inputImages(basename(path), file.inputs ?? {}, from);
167
+ const inputs = {};
168
+ const published = [];
169
+ for (const [name, entries] of Object.entries(file.inputs ?? {})) {
170
+ inputs[name] = [];
171
+ for (const entry of entries) {
172
+ const made = await artifact(entry, from, client, images);
173
+ inputs[name].push(made.thing);
174
+ if (made.published)
175
+ published.push(made.published);
176
+ }
177
+ }
178
+ const parties = {};
179
+ for (const [key, party] of Object.entries(file.parties ?? {})) {
180
+ parties[key] = party === "real" ? "real" : party;
181
+ }
182
+ return {
183
+ title: file.title,
184
+ file: basename(path),
185
+ description: file.description,
186
+ plan,
187
+ inputs,
188
+ ...(published.length > 0 ? { published } : {}),
189
+ ...(branch ? { branch } : {}),
190
+ ...(history ? { history } : {}),
191
+ parties,
192
+ person: file.person,
193
+ waitMs: file.wait ? file.wait * 60_000 : undefined,
194
+ };
195
+ }
196
+ /**
197
+ * The driver's client over the CLI: the project secret for every call. The
198
+ * solution is created as a rehearsal's, which is what lets that same secret
199
+ * answer the decisions the scenario scripted.
200
+ */
201
+ export function liveClient() {
202
+ const client = solutionClient();
203
+ return {
204
+ async create(args) {
205
+ return client.mutation(api.solutions.mutations.create, {
206
+ project: client.project,
207
+ title: args.title,
208
+ description: args.description,
209
+ plan: args.plan,
210
+ inputs: args.inputs,
211
+ rehearsal: true,
212
+ scenario: args.scenario,
213
+ baseBranch: args.branch,
214
+ published: args.published,
215
+ history: args.history,
216
+ });
217
+ },
218
+ async createScores(scores) {
219
+ const { scoresId } = await client.mutation(api.scores.mutations.create, { project: client.project, ...scores });
220
+ return scoresId;
221
+ },
222
+ async panel(solutionId) {
223
+ return client.query(api.solutions.queries.getPlan, { solutionId });
224
+ },
225
+ async next(args) {
226
+ return client.mutation(api.solutions.mutations.next, {
227
+ solutionId: args.solutionId,
228
+ agent: args.agent,
229
+ });
230
+ },
231
+ async complete(args) {
232
+ const said = await client.mutation(api.solutions.mutations.complete, {
233
+ solutionId: args.solutionId,
234
+ ...(args.agent !== undefined ? { agent: args.agent } : {}),
235
+ outcome: args.outcome,
236
+ made: args.made,
237
+ comment: args.comment,
238
+ summary: args.summary,
239
+ review: args.review,
240
+ });
241
+ // STEP-REVIEW-18.7.0: a result refused because the drafter submitted
242
+ // again is a refusal like any other to the script.
243
+ if (said?.refused)
244
+ throw new Error(said.refused);
245
+ },
246
+ async ask(args) {
247
+ await client.mutation(api.solutions.mutations.ask, {
248
+ solutionId: args.solutionId,
249
+ agent: args.agent,
250
+ outcome: args.outcome,
251
+ request: args.request,
252
+ });
253
+ },
254
+ async stall(args) {
255
+ await client.mutation(api.solutions.mutations.stall, {
256
+ solutionId: args.solutionId,
257
+ agent: args.agent,
258
+ outcome: args.outcome,
259
+ reason: args.reason,
260
+ });
261
+ },
262
+ async openDecision(solutionId, outcome) {
263
+ const page = await client.query(api.solutions.queries.getOutcomePage, { solutionId, outcome });
264
+ const decisionId = page?.panel?.decisionId;
265
+ return decisionId ? { id: decisionId } : null;
266
+ },
267
+ async answer(decisionId, answer) {
268
+ await client.mutation(api.solutions.mutations.answer, {
269
+ decisionId,
270
+ answer,
271
+ });
272
+ },
273
+ async approveCheckpoint(solutionId, checkpoint, words) {
274
+ await client.mutation(api.solutions.mutations.approveCheckpoint, {
275
+ solutionId,
276
+ checkpoint,
277
+ ...(words?.trim() ? { words: words.trim() } : {}),
278
+ });
279
+ },
280
+ async thread(solutionId, outcome) {
281
+ return ((await client.query(api.solutions.queries.getThread, {
282
+ solutionId,
283
+ outcome,
284
+ })) ?? []);
285
+ },
286
+ async things(solutionId, outcome) {
287
+ const things = await client.query(api.solutions.queries.getThings, { solutionId, name: outcome });
288
+ return things?.things ?? [];
289
+ },
290
+ async agentLog(solutionId, agent) {
291
+ const logs = await client.query(api.solutions.queries.getAgentLogs, { solutionId, agent });
292
+ if (logs.length === 0)
293
+ return null;
294
+ return logs
295
+ .map((log, i) => `Run ${logs.length - i} of ${logs.length}, ${new Date(log.at).toISOString()}:\n${log.summary}`)
296
+ .join("\n\n");
297
+ },
298
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
299
+ };
300
+ }
301
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1,4 @@
1
+ import type { RehearsalReport } from "./types.js";
2
+ /** The rehearsal as text: how it ended, then each outcome's thread and things. */
3
+ export declare function renderReport(report: RehearsalReport): string;
4
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1,69 @@
1
+ /** The rehearsal as text: how it ended, then each outcome's thread and things. */
2
+ export function renderReport(report) {
3
+ const lines = [`# Rehearsal ${report.solutionId}`, ""];
4
+ if (report.earlier)
5
+ lines.push(...earlierLines(report.earlier), "");
6
+ lines.push(`Ended: ${report.ended}`);
7
+ for (const outcome of report.outcomes) {
8
+ lines.push("", `## ${outcome.outcome} — ${outcome.status}${outcome.reason ? ` (${outcome.reason})` : ""}`);
9
+ if (outcome.things.length > 0) {
10
+ lines.push("", "Submitted:");
11
+ for (const thing of outcome.things) {
12
+ lines.push(`- ${JSON.stringify(thing)}`);
13
+ }
14
+ }
15
+ if (outcome.thread.length > 0) {
16
+ lines.push("", "Thread:");
17
+ for (const entry of outcome.thread) {
18
+ const who = entry.agent ?? entry.by ?? "?";
19
+ const result = entry.result ? ` — ${entry.result}` : "";
20
+ lines.push(`- ${entry.kind} by ${who}${result}: ${entry.text ?? ""}`);
21
+ }
22
+ }
23
+ for (const [agent, log] of Object.entries(outcome.agentLogs ?? {})) {
24
+ if (log)
25
+ lines.push("", `${agent}, from its log:`, "", log);
26
+ }
27
+ }
28
+ return `${lines.join("\n")}\n`;
29
+ }
30
+ /** Names as a person lists them: "a", "a and b", "a, b and c". */
31
+ function listed(names) {
32
+ return names.length <= 1
33
+ ? (names[0] ?? "")
34
+ : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
35
+ }
36
+ /** What became of the scenario's earlier runs (REHEARSE-6.1, 6.5). */
37
+ function earlierLines(earlier) {
38
+ const runs = earlier.removed === 1 ? "run" : "runs";
39
+ const lines = [
40
+ earlier.keep
41
+ ? `Removed no earlier runs of ${earlier.scenario}: --keep left them in place.`
42
+ : earlier.removed === 0 && !earlier.keptRuns?.length
43
+ ? `Removed no earlier runs of ${earlier.scenario}: there were none.`
44
+ : `Removed ${earlier.removed === 0 ? "no" : earlier.removed} earlier ${runs} of ${earlier.scenario}.`,
45
+ ];
46
+ // REHEARSE-6.2.0: an earlier run another scenario starts from stays whole.
47
+ for (const run of earlier.keptRuns ?? []) {
48
+ lines.push(`Kept the earlier run ${run.solutionId}, because ${listed(run.namedBy)} starts from its history.`);
49
+ }
50
+ for (const thing of earlier.kept) {
51
+ lines.push(thing.namedBy.length > 0
52
+ ? `Kept ${thing.reference}, which an earlier run made, because ${listed(thing.namedBy)} names it as an input.`
53
+ : `Kept ${thing.reference}, which an earlier run made, because another solution uses it.`);
54
+ }
55
+ // REHEARSE-6.7, 6.8, 6.10: the branch an earlier run's solution made.
56
+ for (const branch of earlier.branches ?? []) {
57
+ if (branch.outcome === "deleted") {
58
+ lines.push(`Deleted the branch ${branch.name}, which an earlier run's solution made.`);
59
+ }
60
+ else if (branch.outcome === "kept") {
61
+ lines.push(`Kept the branch ${branch.name}, which an earlier run's solution made, because ${branch.why ?? "the app didn't say why"}.`);
62
+ }
63
+ else if (branch.outcome === "pending") {
64
+ lines.push(`The branch ${branch.name}, which an earlier run's solution made, was still being deleted when this run started; if it is still there, this run's branch takes a suffixed name.`);
65
+ }
66
+ }
67
+ return lines;
68
+ }
69
+ //# sourceMappingURL=report.js.map