@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
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { planOf } from "./derive.js";
|
|
2
|
+
/**
|
|
3
|
+
* The plan a scenario rehearses, refused when it names an outcome the plan
|
|
4
|
+
* doesn't have or leaves one of its inputs holding nothing: what a live
|
|
5
|
+
* rehearsal checks before it removes the scenario's earlier runs, so a
|
|
6
|
+
* mistake in the scenario never costs the run under review. An input the
|
|
7
|
+
* scenario doesn't name holds something when the earlier rehearsal it
|
|
8
|
+
* starts from holds it (REHEARSE-4.8).
|
|
9
|
+
*/
|
|
10
|
+
export function checkScenario(scenario) {
|
|
11
|
+
const plan = planOf(scenario.plan);
|
|
12
|
+
for (const name of plan.inputs) {
|
|
13
|
+
if (scenario.inputs?.[name])
|
|
14
|
+
continue;
|
|
15
|
+
if (scenario.history?.outcomes.includes(name))
|
|
16
|
+
continue;
|
|
17
|
+
throw new Error(scenario.history
|
|
18
|
+
? `The plan's input "${name}" holds nothing: solution ${scenario.history.solution} held no "${name}" when it made "${scenario.history.after}", so name its artifacts in the scenario.`
|
|
19
|
+
: `The plan's input "${name}" holds nothing: name its artifacts in the scenario.`);
|
|
20
|
+
}
|
|
21
|
+
return plan;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Who plays an agent at a part of an outcome's work in a scenario: the key
|
|
25
|
+
* naming that agent on that outcome (`Code/Code Reviewer`), then the key
|
|
26
|
+
* naming the part — the outcome's name for its drafter or whoever makes it
|
|
27
|
+
* from outside (`Pitch`), "<outcome>/reviewer" for its reviewers
|
|
28
|
+
* (`Pitch/reviewer`) — then the agent's own name (`Acceptance Tester`, or
|
|
29
|
+
* `Pitch/drafter` for one dedicated to its outcome), then whoever outside
|
|
30
|
+
* makes it (`release manager`). Unnamed, an agent is real. The key that
|
|
31
|
+
* matched, for counting turns.
|
|
32
|
+
*/
|
|
33
|
+
export function partiesOf(parties) {
|
|
34
|
+
return (at) => {
|
|
35
|
+
const keys = [
|
|
36
|
+
...(at.agent ? [`${at.outcome}/${at.agent}`] : []),
|
|
37
|
+
at.part === "review" ? `${at.outcome}/reviewer` : at.outcome,
|
|
38
|
+
...(at.agent ? [at.agent] : []),
|
|
39
|
+
...(at.party ? [at.party] : []),
|
|
40
|
+
];
|
|
41
|
+
const key = keys.find((name) => parties[name] !== undefined);
|
|
42
|
+
return key ? { party: parties[key], key } : { party: "real", key: keys[0] };
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Every outcome on the panel, in the plan's order. */
|
|
46
|
+
const outcomesOn = (panel) => (panel?.phases ?? []).flatMap((phase) => phase.outcomes);
|
|
47
|
+
/**
|
|
48
|
+
* The parts of the work an agent, or someone outside, could take now or is
|
|
49
|
+
* at, in the plan's order: a ready or drafting outcome's draft, a review of
|
|
50
|
+
* an outcome standing reviewing (each of its reviewers, since the panel
|
|
51
|
+
* doesn't say which have given their result), and an outcome waiting on
|
|
52
|
+
* whoever makes it from outside.
|
|
53
|
+
*/
|
|
54
|
+
function partsDue(outcomes) {
|
|
55
|
+
return outcomes.flatMap((outcome) => {
|
|
56
|
+
if ((outcome.status === "ready" || outcome.status === "drafting") &&
|
|
57
|
+
outcome.drafter) {
|
|
58
|
+
return [{ outcome: outcome.name, part: "draft", agent: outcome.drafter }];
|
|
59
|
+
}
|
|
60
|
+
if (outcome.status === "reviewing") {
|
|
61
|
+
return (outcome.reviewers ?? []).map((agent) => ({
|
|
62
|
+
outcome: outcome.name,
|
|
63
|
+
part: "review",
|
|
64
|
+
agent,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if (outcome.status === "awaiting external" ||
|
|
68
|
+
outcome.status === "awaiting collaboration") {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
outcome: outcome.name,
|
|
72
|
+
part: "outside",
|
|
73
|
+
...(outcome.party ? { party: outcome.party } : {}),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The driver (docs/working/rehearsing-steps.md): the orchestrator the
|
|
82
|
+
* product has as events, as a loop. Look at where every outcome stands;
|
|
83
|
+
* for each part of the work due, find who plays its agent; play a script
|
|
84
|
+
* through the same verbs an agent uses — `next` for the agent, then what
|
|
85
|
+
* its script records — or wait for a real one; answer decisions and
|
|
86
|
+
* checkpoints from the person's script; stop when nothing is ready, or
|
|
87
|
+
* when something needs a person nobody scripted, or an agent stalls.
|
|
88
|
+
*/
|
|
89
|
+
export async function rehearse(client, scenario) {
|
|
90
|
+
const plan = checkScenario(scenario);
|
|
91
|
+
const inputs = Object.entries(scenario.inputs ?? {}).map(([name, things]) => ({ name, things }));
|
|
92
|
+
const solutionId = await client.create({
|
|
93
|
+
title: scenario.title,
|
|
94
|
+
scenario: scenario.file,
|
|
95
|
+
description: scenario.description,
|
|
96
|
+
plan,
|
|
97
|
+
inputs,
|
|
98
|
+
...(scenario.branch ? { branch: scenario.branch } : {}),
|
|
99
|
+
...(scenario.published?.length ? { published: scenario.published } : {}),
|
|
100
|
+
...(scenario.history
|
|
101
|
+
? {
|
|
102
|
+
history: {
|
|
103
|
+
solution: scenario.history.solution,
|
|
104
|
+
after: scenario.history.after,
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
: {}),
|
|
108
|
+
});
|
|
109
|
+
const partyFor = partiesOf(scenario.parties);
|
|
110
|
+
const played = {};
|
|
111
|
+
/** The script for this turn: the n-th of a list, the last repeating, counted by outcome and agent. */
|
|
112
|
+
const scriptFor = (at, party) => {
|
|
113
|
+
if (!Array.isArray(party))
|
|
114
|
+
return party;
|
|
115
|
+
const key = `${at.outcome}/${at.agent ?? at.part}`;
|
|
116
|
+
const n = played[key] ?? 0;
|
|
117
|
+
played[key] = n + 1;
|
|
118
|
+
return party[Math.min(n, party.length - 1)];
|
|
119
|
+
};
|
|
120
|
+
const asked = {};
|
|
121
|
+
/** The person's next scripted answer under a name, taken. */
|
|
122
|
+
const answerFor = (name) => {
|
|
123
|
+
const answers = scenario.person?.[name] ?? [];
|
|
124
|
+
const n = asked[name] ?? 0;
|
|
125
|
+
const answer = answers[n];
|
|
126
|
+
if (answer)
|
|
127
|
+
asked[name] = n + 1;
|
|
128
|
+
return answer;
|
|
129
|
+
};
|
|
130
|
+
const waited = {};
|
|
131
|
+
const waitMs = scenario.waitMs ?? 20 * 60 * 1000;
|
|
132
|
+
const pollMs = 15_000;
|
|
133
|
+
let ended = "nothing ready";
|
|
134
|
+
for (let turn = 0; turn < 500; turn++) {
|
|
135
|
+
const panel = await client.panel(solutionId);
|
|
136
|
+
const outcomes = outcomesOn(panel);
|
|
137
|
+
// A decision waiting: the person's script answers, or the rehearsal ends.
|
|
138
|
+
const deciding = outcomes.find((o) => o.status === "awaiting decision");
|
|
139
|
+
if (deciding) {
|
|
140
|
+
const decision = await client.openDecision?.(solutionId, deciding.name);
|
|
141
|
+
const answer = decision && client.answer ? answerFor(deciding.name) : undefined;
|
|
142
|
+
if (!answer || !decision || !client.answer) {
|
|
143
|
+
ended = `"${deciding.name}" waits on a decision nobody scripted`;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
await client.answer(decision.id, answer);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const stalled = outcomes.find((o) => o.status === "stalled");
|
|
150
|
+
if (stalled) {
|
|
151
|
+
ended = `"${stalled.name}" stalled: ${stalled.reason ?? "no reason given"}`;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
// A checkpoint waiting on the person: her scripted approval, or the end.
|
|
155
|
+
const checkpoint = panel?.checkpoints?.find((c) => c.stands === "deciding");
|
|
156
|
+
if (checkpoint) {
|
|
157
|
+
const answer = client.approveCheckpoint
|
|
158
|
+
? answerFor(checkpoint.name)
|
|
159
|
+
: undefined;
|
|
160
|
+
if (!answer || !client.approveCheckpoint) {
|
|
161
|
+
ended = `the ${checkpoint.name} checkpoint waits on a decision nobody scripted`;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (answer.kind !== "approval" || answer.outcome !== "approved") {
|
|
165
|
+
throw new Error(`The person's script answers the ${checkpoint.name} checkpoint with an approval: { kind: approval, outcome: approved }, with any comment as her words.`);
|
|
166
|
+
}
|
|
167
|
+
await client.approveCheckpoint(solutionId, checkpoint.name, answer.comment);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// The parts due, scripted ones first: a script takes its agent's work
|
|
171
|
+
// through `next`, as the agent would.
|
|
172
|
+
const due = partsDue(outcomes).map((at) => ({ at, ...partyFor(at) }));
|
|
173
|
+
let acted = false;
|
|
174
|
+
for (const { at, party } of due) {
|
|
175
|
+
if (party === "real")
|
|
176
|
+
continue;
|
|
177
|
+
if (at.part === "outside") {
|
|
178
|
+
await playOutside(client, solutionId, at, scriptFor(at, party));
|
|
179
|
+
acted = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
const brief = await client.next({ solutionId, agent: at.agent });
|
|
183
|
+
if (brief.kind === "nothing ready")
|
|
184
|
+
continue;
|
|
185
|
+
const taken = takenPart(brief, at.agent);
|
|
186
|
+
const script = partyFor(taken);
|
|
187
|
+
if (script.party === "real") {
|
|
188
|
+
throw new Error(`${at.agent} is scripted for "${at.outcome}" but real for "${taken.outcome}", which it took first: script it for both, or neither.`);
|
|
189
|
+
}
|
|
190
|
+
await play(client, solutionId, taken, scriptFor(taken, script.party), brief);
|
|
191
|
+
acted = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (acted)
|
|
195
|
+
continue;
|
|
196
|
+
// Nothing a script could take: wait for a real agent at work or due.
|
|
197
|
+
const real = due
|
|
198
|
+
.filter(({ at, party }) => party === "real" && at.part !== "outside")
|
|
199
|
+
.map(({ at }) => ({ outcome: at.outcome, agent: at.agent }))[0];
|
|
200
|
+
if (!real) {
|
|
201
|
+
ended = "nothing ready";
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (!client.sleep) {
|
|
205
|
+
throw new Error(`${real.agent} is real on "${real.outcome}", and only a live rehearsal can wait for a real agent.`);
|
|
206
|
+
}
|
|
207
|
+
const key = `${real.outcome}/${real.agent}`;
|
|
208
|
+
waited[key] = (waited[key] ?? 0) + pollMs;
|
|
209
|
+
if (waited[key] > waitMs) {
|
|
210
|
+
ended = `"${real.outcome}" waited ${Math.round(waitMs / 60000)} minutes for ${real.agent}`;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
await client.sleep(pollMs);
|
|
214
|
+
}
|
|
215
|
+
const reports = [];
|
|
216
|
+
for (const outcome of outcomesOn(await client.panel(solutionId))) {
|
|
217
|
+
const report = {
|
|
218
|
+
outcome: outcome.name,
|
|
219
|
+
status: outcome.status,
|
|
220
|
+
reason: outcome.reason,
|
|
221
|
+
things: await client.things(solutionId, outcome.name),
|
|
222
|
+
thread: await client.thread(solutionId, outcome.name),
|
|
223
|
+
};
|
|
224
|
+
if (client.agentLog) {
|
|
225
|
+
const agents = [
|
|
226
|
+
...(outcome.drafter ? [outcome.drafter] : []),
|
|
227
|
+
...(outcome.reviewers ?? []),
|
|
228
|
+
];
|
|
229
|
+
const logs = {};
|
|
230
|
+
for (const agent of agents) {
|
|
231
|
+
const part = {
|
|
232
|
+
outcome: outcome.name,
|
|
233
|
+
part: agent === outcome.drafter ? "draft" : "review",
|
|
234
|
+
agent,
|
|
235
|
+
};
|
|
236
|
+
if (partyFor(part).party === "real") {
|
|
237
|
+
logs[agent] = await client.agentLog(solutionId, agent);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (Object.keys(logs).length > 0)
|
|
241
|
+
report.agentLogs = logs;
|
|
242
|
+
}
|
|
243
|
+
reports.push(report);
|
|
244
|
+
}
|
|
245
|
+
return { solutionId, ended, outcomes: reports };
|
|
246
|
+
}
|
|
247
|
+
/** The part a brief hands the agent: the outcome it names, drafting or reviewing it. */
|
|
248
|
+
function takenPart(brief, agent) {
|
|
249
|
+
const step = brief.step;
|
|
250
|
+
const outcome = step?.outcome ?? step?.label;
|
|
251
|
+
if (!outcome) {
|
|
252
|
+
throw new Error(`${agent} was given a brief that names no outcome.`);
|
|
253
|
+
}
|
|
254
|
+
return { outcome, part: brief.kind === "review" ? "review" : "draft", agent };
|
|
255
|
+
}
|
|
256
|
+
/** A script for an agent: check the brief it was given as the agent would read it, then act. */
|
|
257
|
+
async function play(client, solutionId, at, script, brief) {
|
|
258
|
+
const agent = at.agent;
|
|
259
|
+
const { outcome } = at;
|
|
260
|
+
const where = `"${outcome}" (${agent}, ${at.part === "review" ? "reviewing" : "drafting"})`;
|
|
261
|
+
if (script.expect) {
|
|
262
|
+
try {
|
|
263
|
+
script.expect(brief);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw new Error(`${where}: the brief isn't what the script expected — ${error instanceof Error ? error.message : String(error)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (script.stall) {
|
|
270
|
+
await client.stall({ solutionId, agent, outcome, reason: script.stall });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (script.ask) {
|
|
274
|
+
await client.ask({ solutionId, agent, outcome, request: script.ask });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// REHEARSE-1.1: a script records exactly as a real agent does, so an
|
|
278
|
+
// approval carries the summary the script gives (VERB-2.5) and none
|
|
279
|
+
// stands in for a missing one; the backend refuses it as it would the
|
|
280
|
+
// agent's.
|
|
281
|
+
if (at.part === "review") {
|
|
282
|
+
if (!script.review) {
|
|
283
|
+
throw new Error(`${where}: the script gives no review — give it review, ask or stall.`);
|
|
284
|
+
}
|
|
285
|
+
await client.complete({
|
|
286
|
+
solutionId,
|
|
287
|
+
agent,
|
|
288
|
+
outcome,
|
|
289
|
+
review: script.review,
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const made = await madeBy(client, script);
|
|
294
|
+
if (!made) {
|
|
295
|
+
throw new Error(`${where}: the script says nothing to do — give it made, scores, ask or stall.`);
|
|
296
|
+
}
|
|
297
|
+
await client.complete({
|
|
298
|
+
solutionId,
|
|
299
|
+
agent,
|
|
300
|
+
outcome,
|
|
301
|
+
made,
|
|
302
|
+
comment: script.comment,
|
|
303
|
+
summary: script.summary,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/** What a script submits: its things, or the scores it makes first, as `dotreq scores create` would. */
|
|
307
|
+
async function madeBy(client, script) {
|
|
308
|
+
if (script.made)
|
|
309
|
+
return script.made;
|
|
310
|
+
if (script.scores) {
|
|
311
|
+
return [
|
|
312
|
+
{
|
|
313
|
+
kind: "scores",
|
|
314
|
+
scoresId: await client.createScores(script.scores),
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* A script for whoever makes an outcome from outside the product, or the
|
|
322
|
+
* owner with an agent: it closes the outcome with what it made, as their
|
|
323
|
+
* close would (AWAIT-7.1: a rehearsal's scripts play every party).
|
|
324
|
+
*/
|
|
325
|
+
async function playOutside(client, solutionId, at, script) {
|
|
326
|
+
const made = await madeBy(client, script);
|
|
327
|
+
if (!made) {
|
|
328
|
+
throw new Error(`"${at.outcome}" (${at.party ?? "made outside"}): the script says nothing to do — give it made or scores.`);
|
|
329
|
+
}
|
|
330
|
+
await client.complete({
|
|
331
|
+
solutionId,
|
|
332
|
+
outcome: at.outcome,
|
|
333
|
+
made,
|
|
334
|
+
comment: script.comment,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { solutionClient } from "../commands/solution/client.js";
|
|
2
|
+
import type { EarlierRuns } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A new rehearsal replaces the earlier runs of its scenario (REHEARSE-6):
|
|
5
|
+
* the rehearsal solutions in the project recorded as runs of the same
|
|
6
|
+
* scenario file are removed before it starts, with what they made. What any scenario in the
|
|
7
|
+
* same folder names as an input stays, so that scenario can still start
|
|
8
|
+
* from it; so does an earlier run whose history a scenario there starts
|
|
9
|
+
* from (6.2.0), whole.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* What the scenarios in this scenario's folder name: the documents, boards
|
|
13
|
+
* and mockups they name as inputs, and the earlier rehearsals' solutions
|
|
14
|
+
* whose history they start from (REHEARSE-6.2.0), each by id, with the files
|
|
15
|
+
* that name it. Refuses when one of those files can't be read, since then
|
|
16
|
+
* what it names can't be known.
|
|
17
|
+
*/
|
|
18
|
+
export declare function namedInputs(scenarioPath: string): Map<string, string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Remove the earlier runs of the scenario, keeping what a scenario names
|
|
21
|
+
* (REHEARSE-6.0, 6.2), and keeping whole an earlier run whose history a
|
|
22
|
+
* scenario starts from (6.2.0).
|
|
23
|
+
*/
|
|
24
|
+
export declare function removeEarlierRuns(scenarioPath: string, client: ReturnType<typeof solutionClient>, pause?: (ms: number) => Promise<void>): Promise<EarlierRuns>;
|
|
25
|
+
//# sourceMappingURL=earlier.d.ts.map
|
|
@@ -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,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
|