@popoverai/dotrequirements 0.34.2 → 0.36.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.
@@ -0,0 +1,9 @@
1
+ import { type WhereOptions } from "./run-file.js";
2
+ /**
3
+ * `dotreq solution approval complete <decision>`: the agent takes the
4
+ * action on the option the owner chose, exactly as the option carried it,
5
+ * once, and prints what came of it (DECIDE-4.1). Refused, saying why, when
6
+ * she hasn't chosen it, it was taken already, or another agent asked.
7
+ */
8
+ export declare function approvalCompleteCommand(decisionId: string, options: WhereOptions): Promise<void>;
9
+ //# sourceMappingURL=approval.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { api } from "../../convex.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { requireWhere } from "./run-file.js";
4
+ /**
5
+ * `dotreq solution approval complete <decision>`: the agent takes the
6
+ * action on the option the owner chose, exactly as the option carried it,
7
+ * once, and prints what came of it (DECIDE-4.1). Refused, saying why, when
8
+ * she hasn't chosen it, it was taken already, or another agent asked.
9
+ */
10
+ export async function approvalCompleteCommand(decisionId, options) {
11
+ const { solutionId, agent } = requireWhere(options);
12
+ const result = (await solutionClient().mutation(api.solutions.mutations.completeApproval, { solutionId, agent, decisionId }));
13
+ // DECIDE-4.7: refused because the work moved on, kept so the brief stops
14
+ // offering it, and said as any refusal is.
15
+ if ("refused" in result)
16
+ throw new Error(result.refused);
17
+ console.log(result.said);
18
+ }
19
+ //# sourceMappingURL=approval.js.map
@@ -1,9 +1,14 @@
1
1
  import { type WhereOptions } from "./run-file.js";
2
+ export type AskOptions = WhereOptions & {
3
+ /** One action as JSON, `{"tool": ..., "args": ...}`, to ask only whether it may be taken (DECIDE-3.4). */
4
+ approvalRequest?: string;
5
+ };
2
6
  /**
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
+ * `dotreq solution ask <request-file>`: the drafter or a reviewer puts a
8
+ * decision to the owner, on the outcome it is at or the one `--outcome`
9
+ * names (VERB-3.0). A reviewer gives its result once she has answered (3.1).
10
+ * `--approval-request '<action>'`, with no file, asks only whether it may
11
+ * take that one action (DECIDE-3.4).
7
12
  */
8
- export declare function askCommand(file: string, options: WhereOptions): Promise<void>;
13
+ export declare function askCommand(file: string | undefined, options: AskOptions): Promise<void>;
9
14
  //# sourceMappingURL=ask.d.ts.map
@@ -4,24 +4,39 @@ import { pushBeforeRecord } from "./pushing.js";
4
4
  import { readRequest } from "./request.js";
5
5
  import { onOutcome, requireWhere } from "./run-file.js";
6
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).
7
+ * `dotreq solution ask <request-file>`: the drafter or a reviewer puts a
8
+ * decision to the owner, on the outcome it is at or the one `--outcome`
9
+ * names (VERB-3.0). A reviewer gives its result once she has answered (3.1).
10
+ * `--approval-request '<action>'`, with no file, asks only whether it may
11
+ * take that one action (DECIDE-3.4).
11
12
  */
12
13
  export async function askCommand(file, options) {
13
- const request = readRequest(file);
14
+ if ((file === undefined) === (options.approvalRequest === undefined)) {
15
+ throw new Error("Ask with a request file, or with --approval-request '<action>' alone: one of them.");
16
+ }
17
+ const asking = file !== undefined
18
+ ? { request: readRequest(file) }
19
+ : { approvalRequest: actionFrom(options.approvalRequest ?? "") };
14
20
  const where = requireWhere(options);
15
21
  const { solutionId, agent } = where;
16
22
  const client = solutionClient();
17
23
  // SOLBRANCH-3.0: the drafter's commits reach the branch before Quinn is asked.
18
24
  await pushBeforeRecord(client, where);
19
- await client.mutation(api.solutions.mutations.ask, {
25
+ const decision = await client.mutation(api.solutions.mutations.ask, {
20
26
  solutionId,
21
27
  agent,
22
28
  ...(where.outcome ? { outcome: where.outcome } : {}),
23
- request,
29
+ ...asking,
24
30
  });
25
- console.log(`Asked${onOutcome(where.outcome)}. It waits on the owner's decision.`);
31
+ console.log(`Asked${onOutcome(where.outcome)}. It waits on the owner's decision${decision ? ` (${decision})` : ""}.`);
32
+ }
33
+ /** The action `--approval-request` names, as JSON. The backend checks it. */
34
+ function actionFrom(text) {
35
+ try {
36
+ return JSON.parse(text);
37
+ }
38
+ catch (error) {
39
+ throw new Error(`--approval-request takes the action as JSON, {"tool": ..., "args": ...}: ${error instanceof Error ? error.message : String(error)}`);
40
+ }
26
41
  }
27
42
  //# sourceMappingURL=ask.js.map
@@ -54,6 +54,8 @@ export type PanelOutcome = {
54
54
  agent: string;
55
55
  part: "draft" | "review";
56
56
  }>;
57
+ /** The reviewers waiting on its pull request, their sessions ended (PULL-5). */
58
+ waiting?: string[];
57
59
  };
58
60
  /** A checkpoint on the plan panel, as far as the CLI reads it. */
59
61
  export type PanelCheckpoint = {
@@ -5,5 +5,5 @@
5
5
  * request is validated against; the CLI can't import the backend, so this
6
6
  * is that text, and a test keeps the two the same.
7
7
  */
8
- export declare const REQUEST_CONTRACT = "Ask the person to decide something about the outcome you are drafting. A choice presents options with a recommendation. One decision per request: if two things need deciding, ask twice.\n\nA request is JSON with a \"kind\" and the fields of that kind:\n\nchoice:\n question (text, up to 200 characters): The question, phrased as a question in the words the person will read it, not as a topic.\n options (list, at least 2): The live alternatives. More than four makes deciding harder; give more only when each is a genuine choice. Leave out anything you have already ruled out.\n name (text, up to 200 characters): The option as you would say it to the person: a noun phrase they can repeat back, not a label.\n benefits (list of text, each up to 200 characters): What is better if this option is taken. One line each, in your own words.\n costs (list of text, each up to 200 characters): What is worse if this option is taken: what it would take to build, and what the person lives with afterwards. Effort is a cost like any other.\n note (text, up to 600 characters, optional): Anything you learned or checked about this option that is not a benefit or a cost. Leave it out when there is nothing to add.\n recommendation (object): Which option you would take, and the one reason why.\n option (text, up to 200 characters): The name of the option you recommend, exactly as it appears in options.\n because (text, up to 200 characters): The one reason that carries it, in one line. The full reasoning belongs in background.\n background (text, up to 4000 characters): What the person needs to know to decide well that the options do not already say. Include only context that would substantially change the quality of the decision. The person reads this after the options, so do not reiterate them.";
8
+ export declare const REQUEST_CONTRACT = "Ask the person to decide something about the outcome you are drafting or reviewing. A choice presents options with a recommendation. One decision per request: if two things need deciding, ask twice.\n\nA request is JSON with a \"kind\" and the fields of that kind:\n\nchoice:\n question (text, up to 200 characters): The question, phrased as a question in the words the person will read it, not as a topic.\n options (list, at least 2): The live alternatives. More than four makes deciding harder; give more only when each is a genuine choice. Leave out anything you have already ruled out.\n name (text, up to 200 characters): The option as you would say it to the person: a noun phrase they can repeat back, not a label.\n benefits (list of text, each up to 200 characters): What is better if this option is taken. One line each, in your own words.\n costs (list of text, each up to 200 characters): What is worse if this option is taken: what it would take to build, and what the person lives with afterwards. Effort is a cost like any other.\n note (text, up to 600 characters, optional): Anything you learned or checked about this option that is not a benefit or a cost. Leave it out when there is nothing to add.\n action (object, optional): An action you may take only with the person's permission, which choosing this option gives. Once they choose it, take it with `dotreq solution approval complete <decision>`. Leave it out for anything you may do yourself.\n tool (text, up to 200 characters): The action, by name. The one that needs the person's permission is \"fix\", starting a fix.\n args (object): The action's arguments, as it takes them: a fix's are below.\n recommendation (object): Which option you would take, and the one reason why.\n option (text, up to 200 characters): The name of the option you recommend, exactly as it appears in options.\n because (text, up to 200 characters): The one reason that carries it, in one line. The full reasoning belongs in background.\n background (text, up to 4000 characters): What the person needs to know to decide well that the options do not already say. Include only context that would substantially change the quality of the decision. The person reads this after the options, so do not reiterate them.\n\nThe args of a \"fix\" action:\n name (text, up to 120 characters): A short name for the fix, from what it puts right, as \"dice below the controls\".\n words (text, up to 2000 characters): What you found, in your own words.\n plan (text, up to 2000 characters): The fix as you would put it to the person, in a sentence or two.\n redo (list, at least 1): The outcomes the fix redoes, and what would change in each.\n outcome (text, up to 200 characters): An outcome to redo, by its name.\n change (text, up to 2000 characters): What would change in it.\n rerun (list of text, each up to 200 characters): The outcomes that await the ones redone and should run again from them. Empty when none should.";
9
9
  //# sourceMappingURL=contract.d.ts.map
@@ -5,7 +5,7 @@
5
5
  * request is validated against; the CLI can't import the backend, so this
6
6
  * is that text, and a test keeps the two the same.
7
7
  */
8
- export const REQUEST_CONTRACT = `Ask the person to decide something about the outcome you are drafting. A choice presents options with a recommendation. One decision per request: if two things need deciding, ask twice.
8
+ export const REQUEST_CONTRACT = `Ask the person to decide something about the outcome you are drafting or reviewing. A choice presents options with a recommendation. One decision per request: if two things need deciding, ask twice.
9
9
 
10
10
  A request is JSON with a "kind" and the fields of that kind:
11
11
 
@@ -16,8 +16,20 @@ choice:
16
16
  benefits (list of text, each up to 200 characters): What is better if this option is taken. One line each, in your own words.
17
17
  costs (list of text, each up to 200 characters): What is worse if this option is taken: what it would take to build, and what the person lives with afterwards. Effort is a cost like any other.
18
18
  note (text, up to 600 characters, optional): Anything you learned or checked about this option that is not a benefit or a cost. Leave it out when there is nothing to add.
19
+ action (object, optional): An action you may take only with the person's permission, which choosing this option gives. Once they choose it, take it with \`dotreq solution approval complete <decision>\`. Leave it out for anything you may do yourself.
20
+ tool (text, up to 200 characters): The action, by name. The one that needs the person's permission is "fix", starting a fix.
21
+ args (object): The action's arguments, as it takes them: a fix's are below.
19
22
  recommendation (object): Which option you would take, and the one reason why.
20
23
  option (text, up to 200 characters): The name of the option you recommend, exactly as it appears in options.
21
24
  because (text, up to 200 characters): The one reason that carries it, in one line. The full reasoning belongs in background.
22
- background (text, up to 4000 characters): What the person needs to know to decide well that the options do not already say. Include only context that would substantially change the quality of the decision. The person reads this after the options, so do not reiterate them.`;
25
+ background (text, up to 4000 characters): What the person needs to know to decide well that the options do not already say. Include only context that would substantially change the quality of the decision. The person reads this after the options, so do not reiterate them.
26
+
27
+ The args of a "fix" action:
28
+ name (text, up to 120 characters): A short name for the fix, from what it puts right, as "dice below the controls".
29
+ words (text, up to 2000 characters): What you found, in your own words.
30
+ plan (text, up to 2000 characters): The fix as you would put it to the person, in a sentence or two.
31
+ redo (list, at least 1): The outcomes the fix redoes, and what would change in each.
32
+ outcome (text, up to 200 characters): An outcome to redo, by its name.
33
+ change (text, up to 2000 characters): What would change in it.
34
+ rerun (list of text, each up to 200 characters): The outcomes that await the ones redone and should run again from them. Empty when none should.`;
23
35
  //# sourceMappingURL=contract.js.map
@@ -1,4 +1,5 @@
1
1
  import { Option } from "commander";
2
+ import { approvalCompleteCommand } from "./approval.js";
2
3
  import { askCommand } from "./ask.js";
3
4
  import { authorCommand } from "./author.js";
4
5
  import { checkoutCommand, pushCommand } from "./checkout.js";
@@ -6,7 +7,7 @@ import { REQUEST_CONTRACT } from "./contract.js";
6
7
  import { createCommand } from "./create.js";
7
8
  import { thingsCommand } from "./look-up.js";
8
9
  import { nextCommand } from "./next.js";
9
- import { pullRequestCommand } from "./pull-request.js";
10
+ import { prStatusCommand, prWaitCommand, pullRequestCommand, } from "./pull-request.js";
10
11
  import { rehearseCommand } from "./rehearse.js";
11
12
  import { reviewCommand } from "./review.js";
12
13
  import { doneCommand } from "./share.js";
@@ -41,7 +42,7 @@ Where what you made is, one or more:
41
42
  mockup:<id> a mockup in dotrequirements
42
43
  scores:<id> scores in dotrequirements, made with dotreq scores create
43
44
  pr:<number> the solution's pull request, opened or found with
44
- dotreq solution pull-request. An outcome kept on a pull
45
+ dotreq solution pr open. An outcome kept on a pull
45
46
  request is submitted with it, with the rest of its work
46
47
  image:<path> an image, such as a screenshot: a PNG, JPEG, GIF, WebP or
47
48
  AVIF. The app keeps a copy; don't commit it
@@ -91,11 +92,27 @@ Examples:
91
92
  dotreq solution author file:src/checkout.ts@guest pr:12 image:checkout.png --comment "Guest path, behind the flag."
92
93
  dotreq solution author "preview:8080/design/roll-count pnpm dev" file:src/RollCount.tsx --title "Roll count after three dice" --title "Roll count component" --comment "The roll count, as a dev-only page."
93
94
  `;
94
- /** What `pull-request` does, on its `--help`, where `author --help` sends the agent (PULL-2). */
95
+ /** What `pr status` says, on its `--help` (PULL-4). */
96
+ const PR_STATUS_HELP = `
97
+ Reads the pull request of the outcome you are at, or of the outcome it awaits that
98
+ is kept on one, as GitHub reports it. The merge state is GitHub's word for whether
99
+ it can merge: clean means it can, with every required check passing and no
100
+ conflict. Failed checks come with a link to them; checks still running are named.
101
+ Reading it changes nothing.
102
+ `;
103
+ /** What `pr wait` does, on its `--help` (PULL-5, PULL-6). */
104
+ const PR_WAIT_HELP = `
105
+ For a reviewer, when the pull request has to change before you can give a result,
106
+ as while its checks run. Records where it stands now; then end your session
107
+ without a result. You are started again, in this session, when GitHub reports it
108
+ changed and nothing on it is still running, or once it is merged or closed. A new
109
+ submission ends the wait too, and you review that as you would any other.
110
+ `;
111
+ /** What `pr open` does, on its `--help`, where `author --help` sends the agent (PULL-2). */
95
112
  const PULL_REQUEST_HELP = `
96
113
  Pushes your commits to the solution's branch, then opens a pull request from it
97
- into the branch it was made from, ready for review, or finds the one already open.
98
- Prints its reference and its address:
114
+ into the branch it was made from, as a draft, or finds the one already open. The
115
+ plan says when it becomes ready for review. Prints its reference and its address:
99
116
 
100
117
  The solution's pull request is pr:12: https://github.com/<owner>/<repository>/pull/12
101
118
 
@@ -123,8 +140,15 @@ The outcome waits until that is fixed and you are started again.
123
140
  const ASK_EXAMPLES = `
124
141
  Examples:
125
142
  dotreq solution ask question.json
143
+ dotreq solution ask --approval-request '{"tool": "fix", "args": {"name": "Receipt email required", "words": "...", "plan": "...", "redo": [{"outcome": "Code", "change": "..."}], "rerun": []}}'
144
+
145
+ A reviewer asks the same way, then gives its result once the owner has answered: it is
146
+ sent for again then.
126
147
 
127
- A reviewer doesn't ask: it escalates, with dotreq solution review escalate --comment "...".
148
+ An option may carry an action you may take only with the owner's permission: starting a
149
+ fix, as "fix". Choosing that option gives it; nothing is done until you take it, with
150
+ dotreq solution approval complete <decision>. --approval-request asks only whether you may
151
+ take one action.
128
152
  `;
129
153
  /**
130
154
  * What each result does, on `review --help` (STEP-REVIEW-1, 2), and the
@@ -135,8 +159,10 @@ const REVIEW_RESULTS = `
135
159
  Results:
136
160
  approved The work moves on.
137
161
  approved-with-revisions What remains is small: the drafter fixes it once, and it moves on without another review.
138
- needs-revision The drafter revises and you read it again. Not recorded from the twelfth round.
139
- escalate The owner decides: approve it, or send it back. Your comment is the note they read.
162
+ needs-revision The drafter revises and you read it again. From the twelfth round, give it only once the owner has chosen it.
163
+
164
+ When whether to approve turns on the owner's own judgement, put a decision to them first
165
+ with dotreq solution ask, and give your result once they have answered.
140
166
 
141
167
  When you approve, write an executive summary for the owner with --executive-summary (or
142
168
  --summary), describing the work as it will stand once any fixes you asked for are made. If
@@ -157,7 +183,6 @@ needs-revision, with no video. Only a plain approval carries a video.
157
183
 
158
184
  Examples:
159
185
  dotreq solution review approved --comment "Checked the pitch against the board; the customer is named." --executive-summary "A pitch for guest checkout, for first-time buyers. Look at the appetite: two weeks."
160
- dotreq solution review escalate --comment "Whether to name a segment is your call: the drafter and I disagree."
161
186
  dotreq solution review needs-revision --comment "The risk score gives no reason."
162
187
  dotreq solution review approved "video:demo/final.mp4 .dotreq/demo-scripts/steps.json" --comment "Recorded it; step 3 now waits for the receipt." --executive-summary "A two-minute demo of guest checkout. Look at step 4: the receipt."
163
188
  `;
@@ -216,15 +241,29 @@ export function registerSolution(program) {
216
241
  .option("--title <text>", "A reference's name on the outcome's tab, for the owner: one per reference, in the order the references are given, or none", (value, previous = []) => [...previous, value])
217
242
  .addHelpText("after", REFERENCE_FORMS)
218
243
  .action(wrap(authorCommand));
219
- whereOptions(solution
220
- .command("pull-request", HIDDEN)
244
+ const pr = solution
245
+ .command("pr", HIDDEN)
246
+ .description("The solution's pull request: open it, read it, wait on it");
247
+ whereOptions(pr
248
+ .command("open")
221
249
  .description("Push the solution's branch and open its pull request, or find the one open; prints its reference, pr:<number>, to submit"))
222
250
  .addHelpText("after", PULL_REQUEST_HELP)
223
251
  .action(wrap(pullRequestCommand));
252
+ whereOptions(pr
253
+ .command("status")
254
+ .description("Where the pull request of the outcome you are at stands, in GitHub's words: its merge state, conflicts, review decision and checks"))
255
+ .option("--json", "Print as JSON")
256
+ .addHelpText("after", PR_STATUS_HELP)
257
+ .action(wrap(prStatusCommand));
258
+ whereOptions(pr
259
+ .command("wait")
260
+ .description("Wait on the pull request with your session ended: you start again when it changes and nothing on it is still running"))
261
+ .addHelpText("after", PR_WAIT_HELP)
262
+ .action(wrap(prWaitCommand));
224
263
  whereOptions(solution
225
264
  .command("review <result> [video...]", HIDDEN)
226
- .description("Record the review's result: approved, approved-with-revisions, needs-revision or escalate"))
227
- .option("--comment <text>", "What should change, what you checked, or what the owner is to decide: required for every result")
265
+ .description("Record the review's result: approved, approved-with-revisions or needs-revision"))
266
+ .option("--comment <text>", "What should change, or what you checked: required for every result")
228
267
  .option("--executive-summary <text>", "For the owner, with every approval (required there)")
229
268
  .option("--summary <text>", "Short for --executive-summary")
230
269
  .addHelpText("after", REVIEW_RESULTS)
@@ -235,14 +274,24 @@ export function registerSolution(program) {
235
274
  .option("--keep", "Leave the scenario's earlier runs in place, to review the new run beside them")
236
275
  .action(wrap(rehearseCommand));
237
276
  whereOptions(solution
238
- .command("ask <request-file>", HIDDEN)
239
- .description("Put a decision to the owner, as JSON in the named file; the drafter's, since a reviewer escalates"))
277
+ .command("ask [request-file]", HIDDEN)
278
+ .description("Put a decision to the owner, as JSON in the named file, drafting or reviewing"))
279
+ .option("--approval-request <action>", 'Ask only whether you may take one action, given as JSON: {"tool": "fix", "args": {...}}. No request file then')
240
280
  .addHelpText("after", `\n${REQUEST_CONTRACT}\n${ASK_EXAMPLES}`)
241
281
  .action(wrap(askCommand));
282
+ const approval = solution
283
+ .command("approval", HIDDEN)
284
+ .description("An action the owner allowed by choosing the option that carries it");
285
+ approval
286
+ .command("complete <decision>")
287
+ .description("Take the action on the option the owner chose, exactly as offered, once, and print what came of it")
288
+ .option("--solution <id>", "Which solution, when the run does not say (inside a runner it does)")
289
+ .option("--agent <name>", "Which agent you are, when the run does not say (inside a runner it does)")
290
+ .action(wrap(approvalCompleteCommand));
242
291
  whereOptions(solution
243
292
  .command("thread", HIDDEN)
244
293
  .description("The outcome's thread, oldest first: every submission, review, decision, stall and restart on it"))
245
- .option("--latest [n]", "Only the newest n submission notes, review results, escalations and decisions, still oldest first; alone, the newest one. Stalls, restarts and redrafts are left out")
294
+ .option("--latest [n]", "Only the newest n submission notes, review results and decisions, still oldest first; alone, the newest one. Stalls, restarts and redrafts are left out")
246
295
  .action(wrap(threadCommand));
247
296
  whereOptions(solution
248
297
  .command("things <name>", HIDDEN)
@@ -5,9 +5,50 @@ export type PullRequest = {
5
5
  number: number;
6
6
  };
7
7
  /** The command that opens the solution's pull request and prints its reference. */
8
- export declare const OPEN_IT = "dotreq solution pull-request";
8
+ export declare const OPEN_IT = "dotreq solution pr open";
9
+ /** Where a pull request stands, in GitHub's words (web: lib/runner/github.ts). */
10
+ export type PullRequestState = {
11
+ number: number;
12
+ url: string;
13
+ mergeState: string;
14
+ mergeable: string;
15
+ reviewDecision: string | null;
16
+ checks: string | null;
17
+ failed: Array<{
18
+ name: string;
19
+ result: string;
20
+ link: string | null;
21
+ }>;
22
+ running: Array<{
23
+ name: string;
24
+ link: string | null;
25
+ }>;
26
+ head: string;
27
+ };
28
+ /**
29
+ * Where the pull request stood, as a wait records it (PULL-6.4): what a
30
+ * reviewer judges by, so a later read that differs is a change.
31
+ */
32
+ export declare function seenOf(state: PullRequestState): string;
33
+ /** Where the pull request stands, as a reviewer reads it (PULL-4). */
34
+ export declare function describeState(state: PullRequestState): string;
35
+ /**
36
+ * `dotreq solution pr status`: where the pull request of the outcome the
37
+ * agent is at stands, in GitHub's words (PULL-4). Changes nothing (4.6).
38
+ */
39
+ export declare function prStatusCommand(options: WhereOptions & {
40
+ json?: boolean;
41
+ }): Promise<void>;
42
+ /**
43
+ * `dotreq solution pr wait`: the reviewer waits on the pull request with
44
+ * no session running (PULL-5): it records where the pull request stands,
45
+ * and its session can end without a result. It starts again, in the same
46
+ * session, when the pull request changes and nothing on it is still running
47
+ * (PULL-6).
48
+ */
49
+ export declare function prWaitCommand(options: WhereOptions): Promise<void>;
9
50
  /**
10
- * `dotreq solution pull-request`: the solution's pull request, opened or
51
+ * `dotreq solution pr open`: the solution's pull request, opened or
11
52
  * found (PULL-2). Inside a runner the branch is pushed first, as a submission
12
53
  * pushes it (2.0, SOLBRANCH-3); then the app opens a pull request from it
13
54
  * into the branch it was made from, or finds the one already open (2.3,
@@ -1,13 +1,101 @@
1
1
  import { webApiUrl } from "../../cloud.js";
2
+ import { api } from "../../convex.js";
2
3
  import { getProjectCredentials } from "../../utils/project-settings.js";
3
4
  import { solutionClient } from "./client.js";
4
5
  import { pushWork } from "./pushing.js";
5
6
  import { requireWhere } from "./run-file.js";
6
7
  import { outcomeFor } from "./status.js";
7
8
  /** The command that opens the solution's pull request and prints its reference. */
8
- export const OPEN_IT = "dotreq solution pull-request";
9
+ export const OPEN_IT = "dotreq solution pr open";
9
10
  /**
10
- * `dotreq solution pull-request`: the solution's pull request, opened or
11
+ * Where the pull request stood, as a wait records it (PULL-6.4): what a
12
+ * reviewer judges by, so a later read that differs is a change.
13
+ */
14
+ export function seenOf(state) {
15
+ return [
16
+ state.head,
17
+ state.mergeState,
18
+ state.mergeable,
19
+ state.reviewDecision ?? "",
20
+ state.checks ?? "",
21
+ ].join(" ");
22
+ }
23
+ /** The pull request the outcome the agent is at reads, as the app finds it (PULL-4.0, 4.0.0). */
24
+ async function stateFor(where) {
25
+ const said = await ask({
26
+ solutionId: where.solutionId,
27
+ agent: where.agent,
28
+ ...(where.outcome ? { outcome: where.outcome } : {}),
29
+ op: "status",
30
+ });
31
+ if (said.noRepository) {
32
+ // PULL-4.5.
33
+ throw new Error("There's no pull request: a pull request needs a repository, and this project has none connected.");
34
+ }
35
+ if (said.noBranch || said.none) {
36
+ // PULL-4.4.
37
+ throw new Error(`There's no pull request open for it. Its drafter opens one with \`${OPEN_IT}\`.`);
38
+ }
39
+ if (said.refused) {
40
+ throw new Error(`Couldn't read the pull request from the repository: ${said.refused}`);
41
+ }
42
+ if (!said.state)
43
+ throw new Error("The app gave no pull request.");
44
+ return said.state;
45
+ }
46
+ /** Where the pull request stands, as a reviewer reads it (PULL-4). */
47
+ export function describeState(state) {
48
+ const lines = [
49
+ `pr:${state.number} (${state.url}), at ${state.head.slice(0, 7)}`,
50
+ `Merge state: ${state.mergeState}`,
51
+ `Conflicts with the branch it merges into: ${state.mergeable === "conflicting" ? "yes" : state.mergeable === "mergeable" ? "no" : "not worked out yet"}`,
52
+ `Review decision: ${state.reviewDecision ?? "none required"}`,
53
+ `Checks: ${state.checks ?? "none"}`,
54
+ ];
55
+ for (const check of state.failed) {
56
+ lines.push(` failed: ${check.name} (${check.result})${check.link ? ` ${check.link}` : ""}`);
57
+ }
58
+ for (const check of state.running) {
59
+ lines.push(` still running: ${check.name}${check.link ? ` ${check.link}` : ""}`);
60
+ }
61
+ if (state.mergeable === "unknown") {
62
+ // PULL-4.3.
63
+ lines.push("GitHub hasn't yet worked out whether it can merge: read it again shortly, or wait on it.");
64
+ }
65
+ return lines.join("\n");
66
+ }
67
+ /**
68
+ * `dotreq solution pr status`: where the pull request of the outcome the
69
+ * agent is at stands, in GitHub's words (PULL-4). Changes nothing (4.6).
70
+ */
71
+ export async function prStatusCommand(options) {
72
+ const state = await stateFor(requireWhere(options));
73
+ console.log(options.json ? JSON.stringify(state) : describeState(state));
74
+ }
75
+ /**
76
+ * `dotreq solution pr wait`: the reviewer waits on the pull request with
77
+ * no session running (PULL-5): it records where the pull request stands,
78
+ * and its session can end without a result. It starts again, in the same
79
+ * session, when the pull request changes and nothing on it is still running
80
+ * (PULL-6).
81
+ */
82
+ export async function prWaitCommand(options) {
83
+ const where = requireWhere(options);
84
+ const state = await stateFor(where);
85
+ await solutionClient().mutation(api.solutions.waits.waitOnPullRequest, {
86
+ solutionId: where.solutionId,
87
+ agent: where.agent,
88
+ ...(where.outcome ? { outcome: where.outcome } : {}),
89
+ seen: seenOf(state),
90
+ });
91
+ console.log([
92
+ describeState(state),
93
+ "",
94
+ "Waiting on it. End your session now: you'll be started again, here, when it changes and nothing on it is still running.",
95
+ ].join("\n"));
96
+ }
97
+ /**
98
+ * `dotreq solution pr open`: the solution's pull request, opened or
11
99
  * found (PULL-2). Inside a runner the branch is pushed first, as a submission
12
100
  * pushes it (2.0, SOLBRANCH-3); then the app opens a pull request from it
13
101
  * into the branch it was made from, or finds the one already open (2.3,
@@ -98,7 +186,8 @@ function pullRequestIn(said) {
98
186
  /**
99
187
  * Ask the app's pull-request route, on the project's credentials: with the
100
188
  * outcome, to open the pull request it is kept on; with a number, to check
101
- * that one is the solution's open pull request, which needs no outcome.
189
+ * that one is the solution's open pull request, which needs no outcome;
190
+ * with `op: "status"`, for where the open one stands.
102
191
  */
103
192
  async function ask(body) {
104
193
  const { projectId, projectSecret } = getProjectCredentials();
@@ -1,7 +1,7 @@
1
1
  import { type WhereOptions } from "./run-file.js";
2
2
  import { type SummaryOptions } from "./summary.js";
3
3
  export type ReviewOptions = WhereOptions & SummaryOptions & {
4
- /** What should change, what was checked, or what the owner is to decide: required for every result (VERB-2.0, 2.1, 2.4). */
4
+ /** What should change, or what was checked: required for every result (VERB-2.0, 2.1). */
5
5
  comment?: string;
6
6
  /**
7
7
  * With an approval of a demo's script: the video recorded from it and
@@ -14,9 +14,10 @@ export type ReviewOptions = WhereOptions & SummaryOptions & {
14
14
  * `dotreq solution review <result> --comment "..."`: the reviewer's
15
15
  * result on the outcome it is reviewing, or on the one `--outcome` names.
16
16
  * The comment is what the next reader of the thread sees first — the
17
- * drafter works from it, and on an escalation it is the note the owner
18
- * reads — so every result is refused without one before anything is
19
- * recorded (VERB-2.0, 2.1, 2.4). An approval also carries an executive
17
+ * drafter works from it — so every result is refused without one before
18
+ * anything is recorded (VERB-2.0, 2.1). When the reviewer needs the
19
+ * owner's judgement it asks first (`dotreq solution ask`), and gives its
20
+ * result once she has answered; escalate is refused, saying so (2.4). An approval also carries an executive
20
21
  * summary, `--executive-summary "..."`, which is what the owner reads with
21
22
  * the work (VERB-2.5); the outcome's own rules decide what follows. An
22
23
  * approval of a demo's script carries the video the reviewer recorded from
@@ -10,15 +10,17 @@ const RESULTS = {
10
10
  approved: "approved",
11
11
  "approved-with-revisions": "approved with revisions",
12
12
  "needs-revision": "needs revision",
13
- escalate: "escalate",
14
13
  };
14
+ /** What a reviewer is told when it gives escalate, which is no longer a result (VERB-2.4): in the backend's words. */
15
+ const NO_ESCALATING = "Nothing was recorded: escalate is no longer a result. When you need the owner's judgement, put a decision to them with `dotreq solution ask` (`dotreq solution ask --help` shows the form), and give your result once they have answered.";
15
16
  /**
16
17
  * `dotreq solution review <result> --comment "..."`: the reviewer's
17
18
  * result on the outcome it is reviewing, or on the one `--outcome` names.
18
19
  * The comment is what the next reader of the thread sees first — the
19
- * drafter works from it, and on an escalation it is the note the owner
20
- * reads — so every result is refused without one before anything is
21
- * recorded (VERB-2.0, 2.1, 2.4). An approval also carries an executive
20
+ * drafter works from it — so every result is refused without one before
21
+ * anything is recorded (VERB-2.0, 2.1). When the reviewer needs the
22
+ * owner's judgement it asks first (`dotreq solution ask`), and gives its
23
+ * result once she has answered; escalate is refused, saying so (2.4). An approval also carries an executive
22
24
  * summary, `--executive-summary "..."`, which is what the owner reads with
23
25
  * the work (VERB-2.5); the outcome's own rules decide what follows. An
24
26
  * approval of a demo's script carries the video the reviewer recorded from
@@ -26,17 +28,17 @@ const RESULTS = {
26
28
  * (SIGHT-13.2); no other result carries one (13.4.0).
27
29
  */
28
30
  export async function reviewCommand(result, options) {
31
+ if (result === "escalate")
32
+ throw new Error(NO_ESCALATING);
29
33
  const outcome = RESULTS[result];
30
34
  if (!outcome) {
31
- throw new Error(`A review result is approved, approved-with-revisions, needs-revision or escalate, not "${result}".`);
35
+ throw new Error(`A review result is approved, approved-with-revisions or needs-revision, not "${result}".`);
32
36
  }
33
37
  const comment = options.comment?.trim();
34
38
  if (!comment) {
35
39
  throw new Error(outcome === "approved"
36
40
  ? "Say what you checked: your comment is what the next reader sees first."
37
- : outcome === "escalate"
38
- ? "Say what the owner is to decide: your comment is the note they read with the work."
39
- : "Say what should change: the drafter works from your comment.");
41
+ : "Say what should change: the drafter works from your comment.");
40
42
  }
41
43
  // VERB-2.5, 2.5.0: an approval carries an executive summary for the
42
44
  // owner, and nothing else does; refused before anything is recorded, in
@@ -47,7 +49,7 @@ export async function reviewCommand(result, options) {
47
49
  throw new Error("Write an executive summary for the owner with --executive-summary: it's what they read first. Say what this is and what most needs their eye, as the work will stand once any fixes you asked for are made.");
48
50
  }
49
51
  if (!approving && summary) {
50
- throw new Error(`Only an approval carries an executive summary, so nothing was recorded. Leave --executive-summary off: ${outcome === "escalate" ? "your comment is the note the owner reads." : "the drafter works from your comment."}`);
52
+ throw new Error("Only an approval carries an executive summary, so nothing was recorded. Leave --executive-summary off: the drafter works from your comment.");
51
53
  }
52
54
  // SIGHT-13.2, 13.4.0: a video goes only with a plain approval, and is
53
55
  // read before anything is pushed, uploaded or recorded.
@@ -55,7 +57,7 @@ export async function reviewCommand(result, options) {
55
57
  if (recordings.length > 0 && outcome !== "approved") {
56
58
  throw new Error(outcome === "approved with revisions"
57
59
  ? "Nothing was recorded: a demo's script isn't approved with revisions, because its drafter can't change the script without the video going out of step. Fix anything small yourself, record the video, and approve with it, saying in your comment what you changed; or give needs-revision."
58
- : `Nothing was recorded: only an approval carries a video. Give your result without it: ${outcome === "escalate" ? "your comment is the note the owner reads" : "the drafter works from your comment"}, and no video is kept.`);
60
+ : "Nothing was recorded: only an approval carries a video. Give your result without it: the drafter works from your comment, and no video is kept.");
59
61
  }
60
62
  const where = requireWhere(options);
61
63
  const { solutionId, agent } = where;
@@ -83,9 +85,7 @@ export async function reviewCommand(result, options) {
83
85
  // again once the reviewer has looked.
84
86
  if (said?.refused)
85
87
  throw new Error(said.refused);
86
- console.log(outcome === "escalate"
87
- ? `Escalated${onOutcome(where.outcome)}. It waits on the owner's decision.`
88
- : `Recorded your review${onOutcome(where.outcome)}: ${outcome}.`);
88
+ console.log(`Recorded your review${onOutcome(where.outcome)}: ${outcome}.`);
89
89
  }
90
90
  /**
91
91
  * The videos a review carries (SIGHT-13.2): each recorded from a demo's
@@ -104,10 +104,11 @@ async function reviewerOwes(client, solutionId, outcome, agent) {
104
104
  if (entry.kind === "submission")
105
105
  since = at + 1;
106
106
  });
107
+ // Only a review is a result: a question it put to the owner, or an
108
+ // escalation withdrawn when escalating went, leaves it still owing one.
107
109
  return !thread
108
110
  .slice(since)
109
- .some((entry) => entry.agent === agent &&
110
- (entry.kind === "review" || entry.kind === "escalation"));
111
+ .some((entry) => entry.agent === agent && entry.kind === "review");
111
112
  }
112
113
  /**
113
114
  * `dotreq solution status`: where the outcome the agent is at stands, as
@@ -139,10 +140,14 @@ export async function statusCommand(options) {
139
140
  // other reviewer stalled. Asking, stalling and recording all take the
140
141
  // agent off it.
141
142
  const atIt = outcome.atWork?.some((each) => each.agent === where.agent && each.part === part) === true;
143
+ // PULL-5.2: a reviewer waiting on the pull request owes nothing until it
144
+ // starts again.
145
+ const waiting = part === "review" && outcome.waiting?.includes(where.agent) === true;
142
146
  const owes = part === "draft"
143
147
  ? !setAside &&
144
148
  (atIt || outcome.status === "drafting" || outcome.status === "ready")
145
- : (atIt || outcome.status === "reviewing") &&
149
+ : !waiting &&
150
+ (atIt || outcome.status === "reviewing") &&
146
151
  (await reviewerOwes(client, where.solutionId, outcome.name, where.agent));
147
152
  if (options.json) {
148
153
  process.stdout.write(`${JSON.stringify({ agent: where.agent, outcome: outcome.name, part, status: outcome.status, reason: outcome.reason ?? null, reviews: part === "review", ...(setAside ? { setAside } : {}), owes })}\n`);
@@ -22,10 +22,10 @@ export function parseThing(reference) {
22
22
  return { kind, scoresId: rest };
23
23
  case "pr": {
24
24
  // PULL-3: the solution's pull request, by the reference
25
- // `dotreq solution pull-request` printed.
25
+ // `dotreq solution pr open` printed.
26
26
  const number = Number(rest.replace(/^#/, ""));
27
27
  if (!Number.isInteger(number) || number <= 0) {
28
- throw new Error(`"${reference}" isn't a pull request's reference. It is pr: and the pull request's number, as \`dotreq solution pull-request\` prints it, e.g. pr:42.`);
28
+ throw new Error(`"${reference}" isn't a pull request's reference. It is pr: and the pull request's number, as \`dotreq solution pr open\` prints it, e.g. pr:42.`);
29
29
  }
30
30
  return { kind: "pull request", number };
31
31
  }
@@ -4,7 +4,7 @@ import { requireWhere } from "./run-file.js";
4
4
  import { outcomeFor } from "./status.js";
5
5
  /**
6
6
  * What `--latest` counts: what someone wrote — a submission note, a review
7
- * result, an escalation or a decision. A stall, a restart and a redraft
7
+ * result, a decision put to the person or their answer. A stall, a restart and a redraft
8
8
  * because the outcome it is on was redrafted are on the thread but not
9
9
  * counted, so a drafter revising after a restart reads the review it is
10
10
  * answering, not the restart (VERB-6.4, 6.5).
package/dist/convex.d.ts CHANGED
@@ -99,6 +99,7 @@ export declare const api: {
99
99
  next: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
100
100
  complete: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
101
101
  ask: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
102
+ completeApproval: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
102
103
  stall: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
103
104
  answer: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
104
105
  approveCheckpoint: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
@@ -121,6 +122,9 @@ export declare const api: {
121
122
  recordPush: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
122
123
  removal: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
123
124
  };
125
+ waits: {
126
+ waitOnPullRequest: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
127
+ };
124
128
  };
125
129
  lib: {
126
130
  authActions: {
package/dist/convex.js CHANGED
@@ -112,6 +112,8 @@ export const api = {
112
112
  next: mutation("solutions/mutations:next"),
113
113
  complete: mutation("solutions/mutations:complete"),
114
114
  ask: mutation("solutions/mutations:ask"),
115
+ // DECIDE-4: the action on the option the owner chose, taken once.
116
+ completeApproval: mutation("solutions/mutations:completeApproval"),
115
117
  stall: mutation("solutions/mutations:stall"),
116
118
  answer: mutation("solutions/mutations:answer"),
117
119
  // A rehearsal's scripted approval of a checkpoint (CHECKPOINT-1.1).
@@ -141,6 +143,9 @@ export const api = {
141
143
  recordPush: mutation("solutions/branches:recordPush"),
142
144
  removal: query("solutions/branches:removal"),
143
145
  },
146
+ waits: {
147
+ waitOnPullRequest: mutation("solutions/waits:waitOnPullRequest"),
148
+ },
144
149
  },
145
150
  lib: {
146
151
  authActions: {
@@ -142,16 +142,15 @@ export type Script = {
142
142
  /**
143
143
  * Give this review result (reviewing), with the comment every result
144
144
  * carries. An approval, with or without revisions, carries an executive
145
- * summary (VERB-2.5), and needs revision and escalate carry none; a
146
- * script that breaks either is refused, as the agent would be
147
- * (REHEARSE-1.1).
145
+ * summary (VERB-2.5), and needs revision carries none; a script that
146
+ * breaks either is refused, as the agent would be (REHEARSE-1.1).
148
147
  */
149
148
  review?: {
150
- outcome: "approved" | "approved with revisions" | "needs revision" | "escalate";
149
+ outcome: "approved" | "approved with revisions" | "needs revision";
151
150
  comment: string;
152
151
  summary?: string;
153
152
  };
154
- /** Put a decision to the person instead (drafting). */
153
+ /** Put a decision to the person instead, drafting or reviewing; a reviewer gives its result once she answers. */
155
154
  ask?: unknown;
156
155
  /** Stall instead. */
157
156
  stall?: string;
@@ -160,7 +159,7 @@ export type Script = {
160
159
  export type Party = "real" | Script | Script[];
161
160
  /**
162
161
  * What the person answers, in order asked: by an outcome's name, her answer
163
- * to a question or an escalation on it; by a checkpoint's name, her
162
+ * to a decision its drafter or a reviewer put to her; by a checkpoint's name, her
164
163
  * decision there, which a rehearsal gives only as an approval.
165
164
  */
166
165
  export type PersonScript = Record<string, Array<{
@@ -296,7 +295,7 @@ export type RehearsalClient = {
296
295
  /** With `made`: the drafter's executive summary (VERB-1.6). */
297
296
  summary?: string;
298
297
  review?: {
299
- outcome: "approved" | "approved with revisions" | "needs revision" | "escalate";
298
+ outcome: "approved" | "approved with revisions" | "needs revision";
300
299
  comment: string;
301
300
  /** With an approval: the executive summary (VERB-2.5). */
302
301
  summary?: string;
@@ -314,7 +313,7 @@ export type RehearsalClient = {
314
313
  outcome: string;
315
314
  reason: string;
316
315
  }): Promise<void>;
317
- /** The decision open on an outcome — a question or an escalation — or null. Absent when the client can't answer for the person. */
316
+ /** The decision open on an outcome, its drafter's or a reviewer's, or null. Absent when the client can't answer for the person. */
318
317
  openDecision?(solutionId: string, outcome: string): Promise<{
319
318
  id: string;
320
319
  } | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.34.2",
3
+ "version": "0.36.0",
4
4
  "description": "Requirements as testable, human-readable data — CLI and test harness for spec-driven development with AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {