@popoverai/dotrequirements 0.34.1 → 0.35.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.
@@ -49,6 +49,13 @@ export type PanelOutcome = {
49
49
  waitingFor?: string[];
50
50
  /** Its drafter's work was set aside and its new draft isn't taken yet: a session at it owes nothing more (CHECKPOINT-6.3.2.3). */
51
51
  setAside?: boolean;
52
+ /** The agents at it now, each drafting it or reviewing it; absent from a backend from before it said. */
53
+ atWork?: Array<{
54
+ agent: string;
55
+ part: "draft" | "review";
56
+ }>;
57
+ /** The reviewers waiting on its pull request, their sessions ended (PULL-5). */
58
+ waiting?: string[];
52
59
  };
53
60
  /** A checkpoint on the plan panel, as far as the CLI reads it. */
54
61
  export type PanelCheckpoint = {
@@ -6,7 +6,7 @@ import { REQUEST_CONTRACT } from "./contract.js";
6
6
  import { createCommand } from "./create.js";
7
7
  import { thingsCommand } from "./look-up.js";
8
8
  import { nextCommand } from "./next.js";
9
- import { pullRequestCommand } from "./pull-request.js";
9
+ import { prStatusCommand, prWaitCommand, pullRequestCommand, } from "./pull-request.js";
10
10
  import { rehearseCommand } from "./rehearse.js";
11
11
  import { reviewCommand } from "./review.js";
12
12
  import { doneCommand } from "./share.js";
@@ -41,7 +41,7 @@ Where what you made is, one or more:
41
41
  mockup:<id> a mockup in dotrequirements
42
42
  scores:<id> scores in dotrequirements, made with dotreq scores create
43
43
  pr:<number> the solution's pull request, opened or found with
44
- dotreq solution pull-request. An outcome kept on a pull
44
+ dotreq solution pr open. An outcome kept on a pull
45
45
  request is submitted with it, with the rest of its work
46
46
  image:<path> an image, such as a screenshot: a PNG, JPEG, GIF, WebP or
47
47
  AVIF. The app keeps a copy; don't commit it
@@ -91,11 +91,27 @@ Examples:
91
91
  dotreq solution author file:src/checkout.ts@guest pr:12 image:checkout.png --comment "Guest path, behind the flag."
92
92
  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
93
  `;
94
- /** What `pull-request` does, on its `--help`, where `author --help` sends the agent (PULL-2). */
94
+ /** What `pr status` says, on its `--help` (PULL-4). */
95
+ const PR_STATUS_HELP = `
96
+ Reads the pull request of the outcome you are at, or of the outcome it awaits that
97
+ is kept on one, as GitHub reports it. The merge state is GitHub's word for whether
98
+ it can merge: clean means it can, with every required check passing and no
99
+ conflict. Failed checks come with a link to them; checks still running are named.
100
+ Reading it changes nothing.
101
+ `;
102
+ /** What `pr wait` does, on its `--help` (PULL-5, PULL-6). */
103
+ const PR_WAIT_HELP = `
104
+ For a reviewer, when the pull request has to change before you can give a result,
105
+ as while its checks run. Records where it stands now; then end your session
106
+ without a result. You are started again, in this session, when GitHub reports it
107
+ changed and nothing on it is still running, or once it is merged or closed. A new
108
+ submission ends the wait too, and you review that as you would any other.
109
+ `;
110
+ /** What `pr open` does, on its `--help`, where `author --help` sends the agent (PULL-2). */
95
111
  const PULL_REQUEST_HELP = `
96
112
  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:
113
+ into the branch it was made from, as a draft, or finds the one already open. The
114
+ plan says when it becomes ready for review. Prints its reference and its address:
99
115
 
100
116
  The solution's pull request is pr:12: https://github.com/<owner>/<repository>/pull/12
101
117
 
@@ -216,11 +232,25 @@ export function registerSolution(program) {
216
232
  .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
233
  .addHelpText("after", REFERENCE_FORMS)
218
234
  .action(wrap(authorCommand));
219
- whereOptions(solution
220
- .command("pull-request", HIDDEN)
235
+ const pr = solution
236
+ .command("pr", HIDDEN)
237
+ .description("The solution's pull request: open it, read it, wait on it");
238
+ whereOptions(pr
239
+ .command("open")
221
240
  .description("Push the solution's branch and open its pull request, or find the one open; prints its reference, pr:<number>, to submit"))
222
241
  .addHelpText("after", PULL_REQUEST_HELP)
223
242
  .action(wrap(pullRequestCommand));
243
+ whereOptions(pr
244
+ .command("status")
245
+ .description("Where the pull request of the outcome you are at stands, in GitHub's words: its merge state, conflicts, review decision and checks"))
246
+ .option("--json", "Print as JSON")
247
+ .addHelpText("after", PR_STATUS_HELP)
248
+ .action(wrap(prStatusCommand));
249
+ whereOptions(pr
250
+ .command("wait")
251
+ .description("Wait on the pull request with your session ended: you start again when it changes and nothing on it is still running"))
252
+ .addHelpText("after", PR_WAIT_HELP)
253
+ .action(wrap(prWaitCommand));
224
254
  whereOptions(solution
225
255
  .command("review <result> [video...]", HIDDEN)
226
256
  .description("Record the review's result: approved, approved-with-revisions, needs-revision or escalate"))
@@ -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();
@@ -23,6 +23,11 @@ export function agentAt(panel, agent, named) {
23
23
  if (!drafting && !reviewing) {
24
24
  throw new Error(`${agent} neither drafts nor reviews "${named}".`);
25
25
  }
26
+ // At it now: the part it is at, as for an agent that both drafts and
27
+ // reviews the outcome, whatever word the outcome stands in.
28
+ const at = outcome.atWork?.find((each) => each.agent === agent);
29
+ if (at)
30
+ return { outcome, part: at.part };
26
31
  return {
27
32
  outcome,
28
33
  part: reviewing && (!drafting || outcome.status === "reviewing")
@@ -129,10 +134,19 @@ export async function statusCommand(options) {
129
134
  // CHECKPOINT-6.3.2.3: a drafter whose work was set aside owes nothing
130
135
  // more there; the new draft is another session's.
131
136
  const setAside = part === "draft" && outcome.setAside === true;
137
+ // STEP-REVIEW-9.8: an agent still at its part owes it, whatever word the
138
+ // outcome stands in because of its other agents, as stalled when the
139
+ // other reviewer stalled. Asking, stalling and recording all take the
140
+ // agent off it.
141
+ const atIt = outcome.atWork?.some((each) => each.agent === where.agent && each.part === part) === true;
142
+ // PULL-5.2: a reviewer waiting on the pull request owes nothing until it
143
+ // starts again.
144
+ const waiting = part === "review" && outcome.waiting?.includes(where.agent) === true;
132
145
  const owes = part === "draft"
133
146
  ? !setAside &&
134
- (outcome.status === "drafting" || outcome.status === "ready")
135
- : outcome.status === "reviewing" &&
147
+ (atIt || outcome.status === "drafting" || outcome.status === "ready")
148
+ : !waiting &&
149
+ (atIt || outcome.status === "reviewing") &&
136
150
  (await reviewerOwes(client, where.solutionId, outcome.name, where.agent));
137
151
  if (options.json) {
138
152
  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
  }
package/dist/convex.d.ts CHANGED
@@ -121,6 +121,9 @@ export declare const api: {
121
121
  recordPush: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
122
122
  removal: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
123
123
  };
124
+ waits: {
125
+ waitOnPullRequest: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
126
+ };
124
127
  };
125
128
  lib: {
126
129
  authActions: {
package/dist/convex.js CHANGED
@@ -141,6 +141,9 @@ export const api = {
141
141
  recordPush: mutation("solutions/branches:recordPush"),
142
142
  removal: query("solutions/branches:removal"),
143
143
  },
144
+ waits: {
145
+ waitOnPullRequest: mutation("solutions/waits:waitOnPullRequest"),
146
+ },
144
147
  },
145
148
  lib: {
146
149
  authActions: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.34.1",
3
+ "version": "0.35.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": {
@@ -26,9 +26,9 @@
26
26
  "test": "vitest",
27
27
  "test:run": "vitest run",
28
28
  "test:coverage": "vitest run --coverage",
29
- "publish:patch": "echo 'ERROR: publish:patch is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a production PR with a CLI version bump merges. Use the /release Claude Code skill to queue a bump and open the production PR. See docs/working/cli-publish-workflow.md for details.' && exit 1",
30
- "publish:minor": "echo 'ERROR: publish:minor is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a production PR with a CLI version bump merges. Use the /release Claude Code skill to queue a bump and open the production PR. See docs/working/cli-publish-workflow.md for details.' && exit 1",
31
- "publish:major": "echo 'ERROR: publish:major is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a production PR with a CLI version bump merges. Use the /release Claude Code skill to queue a bump and open the production PR. See docs/working/cli-publish-workflow.md for details.' && exit 1"
29
+ "publish:patch": "echo 'ERROR: publish:patch is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a PR that bumps the CLI version merges to main. See docs/working/cli-publish-workflow.md for details.' && exit 1",
30
+ "publish:minor": "echo 'ERROR: publish:minor is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a PR that bumps the CLI version merges to main. See docs/working/cli-publish-workflow.md for details.' && exit 1",
31
+ "publish:major": "echo 'ERROR: publish:major is decommissioned. CLI npm publishes now happen via the cli-publish GitHub Action when a PR that bumps the CLI version merges to main. See docs/working/cli-publish-workflow.md for details.' && exit 1"
32
32
  },
33
33
  "keywords": [
34
34
  "requirements",