@popoverai/dotrequirements 0.38.4 → 0.39.1

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.
@@ -4,6 +4,7 @@ import { api } from "../../convex.js";
4
4
  import { currentBranch, headCommit, repositoryRoot } from "./branch.js";
5
5
  import { solutionClient } from "./client.js";
6
6
  import { readImage, uploadImage } from "./image.js";
7
+ import { ownChangesOf } from "./own-changes.js";
7
8
  import { screenshotPreview } from "./preview-screenshot.js";
8
9
  import { checkPullRequest } from "./pull-request.js";
9
10
  import { pushBeforeRecord, refuseUncommitted } from "./pushing.js";
@@ -76,6 +77,12 @@ export async function authorCommand(references, options) {
76
77
  // SOLDOC-12.2.0: a file from the checkout's own branch keeps the commit
77
78
  // it was submitted at, so the page can tell when it changes after.
78
79
  const commit = checkout ? headCommit(cwd) : null;
80
+ // CHECKPOINT-12.4 to 12.6: what the pull request would merge, taken after
81
+ // the push, so the checkpoint can tell a branch brought up to date from
82
+ // changed code. Without one, as when the fetch fails, the work is
83
+ // recorded all the same and reads as changed.
84
+ const merging = [...pullRequests.values()].find((made) => made.base);
85
+ const ownChanges = checkout && merging?.base ? ownChangesOf(cwd, merging.base) : null;
79
86
  /** A reference as it is submitted: where the thing is, as the backend keeps it. */
80
87
  const submissionOf = async (thing) => {
81
88
  const recording = videos.get(thing);
@@ -99,8 +106,12 @@ export async function authorCommand(references, options) {
99
106
  return { kind: "image", storageId, name: image.name };
100
107
  }
101
108
  const pullRequest = pullRequests.get(thing);
102
- if (pullRequest)
103
- return { kind: "pull request", ...pullRequest };
109
+ if (pullRequest) {
110
+ // The branch it merges into is the CLI's, for `ownChanges`; the
111
+ // backend keeps the pull request by its number and address.
112
+ const { base: _base, ...kept } = pullRequest;
113
+ return { kind: "pull request", ...kept };
114
+ }
104
115
  if (thing.kind === "preview") {
105
116
  // SIGHT-12.0, 12.2: a screenshot of the page it opens on, kept as an
106
117
  // image is; without one, it is submitted all the same.
@@ -141,6 +152,7 @@ export async function authorCommand(references, options) {
141
152
  comment,
142
153
  ...(summary ? { summary } : {}),
143
154
  ...(files.length ? { files } : {}),
155
+ ...(ownChanges ? { ownChanges } : {}),
144
156
  };
145
157
  const result = await client.mutation(api.solutions.mutations.complete, args);
146
158
  const saved = result?.saved ?? [];
@@ -12,7 +12,8 @@
12
12
  * images and demo scripts `next` saves in the checkout (SIGHT-9.3, 13.1).
13
13
  */
14
14
  export type Brief = {
15
- kind: "step" | "review";
15
+ /** `request`: what the solution's agent asked it, which it answers (CONVO-11). */
16
+ kind: "step" | "review" | "request";
16
17
  /** The outcome the agent took; `label` is its name too. */
17
18
  step: {
18
19
  label: string;
@@ -0,0 +1,12 @@
1
+ import { type WhereOptions } from "./run-file.js";
2
+ /**
3
+ * `dotreq solution change --comment "<what changed and why>"`: a made
4
+ * outcome's drafter, asked by the solution's agent to change it, records
5
+ * the change in place (VERB-8). Its commits are pushed first, as before any
6
+ * record (VERB-8.1, SOLBRANCH-3); the outcome stays made, and its history
7
+ * says what changed and why, at the solution's agent's request.
8
+ */
9
+ export declare function changeCommand(options: WhereOptions & {
10
+ comment?: string;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=change.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { api } from "../../convex.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { pushBeforeRecord } from "./pushing.js";
4
+ import { requireWhere } from "./run-file.js";
5
+ /**
6
+ * `dotreq solution change --comment "<what changed and why>"`: a made
7
+ * outcome's drafter, asked by the solution's agent to change it, records
8
+ * the change in place (VERB-8). Its commits are pushed first, as before any
9
+ * record (VERB-8.1, SOLBRANCH-3); the outcome stays made, and its history
10
+ * says what changed and why, at the solution's agent's request.
11
+ */
12
+ export async function changeCommand(options) {
13
+ const comment = options.comment?.trim();
14
+ // VERB-8.3: what it says is all the history shows of the change.
15
+ if (!comment) {
16
+ throw new Error("Say what changed and why with --comment: what you say is what the outcome's history shows of the change.");
17
+ }
18
+ const where = requireWhere(options);
19
+ const client = solutionClient();
20
+ await pushBeforeRecord(client, where);
21
+ const { done } = await client.mutation(api.solutions.mutations.changeInPlace, {
22
+ solutionId: where.solutionId,
23
+ agent: where.agent,
24
+ ...(where.outcome ? { outcome: where.outcome } : {}),
25
+ comment,
26
+ });
27
+ console.log(done);
28
+ }
29
+ //# sourceMappingURL=change.js.map
@@ -2,6 +2,7 @@ import { Option } from "commander";
2
2
  import { approvalCompleteCommand } from "./approval.js";
3
3
  import { askCommand } from "./ask.js";
4
4
  import { authorCommand } from "./author.js";
5
+ import { changeCommand } from "./change.js";
5
6
  import { checkoutCommand, pushCommand } from "./checkout.js";
6
7
  import { REQUEST_CONTRACT } from "./contract.js";
7
8
  import { createCommand } from "./create.js";
@@ -219,7 +220,7 @@ export function registerSolution(program) {
219
220
  whereOptions(solution
220
221
  .command("author <reference...>", HIDDEN)
221
222
  .description("Record what you made, and what changed and why"))
222
- .option("--comment <text>", "What changed and why: the next reader sees it first (required)")
223
+ .option("--comment <text>", "What changed and why: the next reader sees it first, so open on a sentence that stands on its own and says what changed (required)")
223
224
  .option("--executive-summary <text>", "For the owner, when your work moves on before anyone reviews it (required there)")
224
225
  .option("--summary <text>", "Short for --executive-summary")
225
226
  .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])
@@ -269,6 +270,11 @@ export function registerSolution(program) {
269
270
  whereOptions(solution
270
271
  .command("things <name>", HIDDEN)
271
272
  .description("What an outcome or an input holds now, or the outcomes a checkpoint presents: one reference a line")).action(wrap(thingsCommand));
273
+ whereOptions(solution
274
+ .command("change", HIDDEN)
275
+ .description("Record a change you made in place to a made outcome, as the solution's agent asked: what changed and why. The outcome stays made"))
276
+ .option("--comment <text>", "What changed and why: what the outcome's history shows of the change (required)")
277
+ .action(wrap(changeCommand));
272
278
  whereOptions(solution
273
279
  .command("stall <reason>", HIDDEN)
274
280
  .description("Your work cannot go on, and no decision would fix that: what has to change before it can be started again"))
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The pull request's own changes, fingerprinted from the checkout: what it
3
+ * would merge into `base`, as `git patch-id` sees it (CHECKPOINT-12.4 to
4
+ * 12.6). Merging a newer `base` in, or rebasing onto it, leaves the
5
+ * fingerprint as it was; resolving a conflict, a whitespace change or a new
6
+ * commit of the branch's own gives a new one.
7
+ *
8
+ * `--verbatim` keeps whitespace, and `-U0` drops the context lines and
9
+ * leaves line numbers only in the hunk headers, which `patch-id` ignores, so
10
+ * what moved under the change doesn't count as changing it. The cost, an
11
+ * accepted limit (12.4.0): a fix that only moves the branch's own lines
12
+ * elsewhere in the same file keeps the fingerprint too.
13
+ *
14
+ * Null when any step fails, the fetch included: the work is then recorded
15
+ * without one, and the checkpoint counts the pull request as changed
16
+ * (12.6). Never throws.
17
+ */
18
+ export declare function ownChangesOf(cwd: string, base: string): {
19
+ commit: string;
20
+ patchId: string;
21
+ } | null;
22
+ //# sourceMappingURL=own-changes.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { spawnSync } from "node:child_process";
2
+ /**
3
+ * The pull request's own changes, fingerprinted from the checkout: what it
4
+ * would merge into `base`, as `git patch-id` sees it (CHECKPOINT-12.4 to
5
+ * 12.6). Merging a newer `base` in, or rebasing onto it, leaves the
6
+ * fingerprint as it was; resolving a conflict, a whitespace change or a new
7
+ * commit of the branch's own gives a new one.
8
+ *
9
+ * `--verbatim` keeps whitespace, and `-U0` drops the context lines and
10
+ * leaves line numbers only in the hunk headers, which `patch-id` ignores, so
11
+ * what moved under the change doesn't count as changing it. The cost, an
12
+ * accepted limit (12.4.0): a fix that only moves the branch's own lines
13
+ * elsewhere in the same file keeps the fingerprint too.
14
+ *
15
+ * Null when any step fails, the fetch included: the work is then recorded
16
+ * without one, and the checkpoint counts the pull request as changed
17
+ * (12.6). Never throws.
18
+ */
19
+ export function ownChangesOf(cwd, base) {
20
+ try {
21
+ // The base as the repository has it now: an older one would put its
22
+ // newer commits, merged into the branch, among the branch's own.
23
+ const fetched = run(cwd, [
24
+ "fetch",
25
+ "--quiet",
26
+ "origin",
27
+ `+refs/heads/${base}:refs/remotes/origin/${base}`,
28
+ ]);
29
+ if (!fetched)
30
+ return null;
31
+ const commit = text(run(cwd, ["rev-parse", "--verify", "--quiet", "HEAD"]));
32
+ if (!commit)
33
+ return null;
34
+ const mergeBase = text(run(cwd, ["merge-base", "HEAD", `refs/remotes/origin/${base}`]));
35
+ if (!mergeBase)
36
+ return null;
37
+ const diff = run(cwd, [
38
+ "diff",
39
+ "--no-ext-diff",
40
+ "--no-textconv",
41
+ "--no-color",
42
+ "--binary",
43
+ "-U0",
44
+ mergeBase,
45
+ commit,
46
+ ]);
47
+ if (!diff)
48
+ return null;
49
+ // `patch-id` prints nothing for an empty diff.
50
+ if (diff.length === 0)
51
+ return { commit, patchId: "empty" };
52
+ const id = run(cwd, ["patch-id", "--verbatim"], diff);
53
+ if (!id)
54
+ return null;
55
+ // A diff it gives no id for, as one only changing a file's mode, can't
56
+ // be told from another: no fingerprint.
57
+ const patchId = text(id)?.split(/\s+/)[0];
58
+ return patchId ? { commit, patchId } : null;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Git's stdout, as bytes, or null when it failed. */
65
+ function run(cwd, args, input) {
66
+ const ran = spawnSync("git", args, {
67
+ cwd,
68
+ input,
69
+ maxBuffer: 1024 * 1024 * 1024,
70
+ // A fetch that wants a password fails rather than waiting for one.
71
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
72
+ });
73
+ return ran.status === 0 && !ran.error ? ran.stdout : null;
74
+ }
75
+ function text(out) {
76
+ return out?.toString("utf8").trim() || null;
77
+ }
78
+ //# sourceMappingURL=own-changes.js.map
@@ -1,8 +1,12 @@
1
1
  import { type Where, type WhereOptions } from "./run-file.js";
2
- /** The solution's pull request: its number and where it opens. */
2
+ /**
3
+ * The solution's pull request: its number and where it opens, and, when
4
+ * checked by its number, the branch it merges into (CHECKPOINT-12.4).
5
+ */
3
6
  export type PullRequest = {
4
7
  url: string;
5
8
  number: number;
9
+ base?: string;
6
10
  };
7
11
  /** The command that opens the solution's pull request and prints its reference. */
8
12
  export declare const OPEN_IT = "dotreq solution pr open";
@@ -91,9 +91,13 @@ export async function checkPullRequest(where, number) {
91
91
  return pullRequest;
92
92
  }
93
93
  function pullRequestIn(said) {
94
- return said.url && typeof said.number === "number"
95
- ? { url: said.url, number: said.number }
96
- : null;
94
+ if (!said.url || typeof said.number !== "number")
95
+ return null;
96
+ return {
97
+ url: said.url,
98
+ number: said.number,
99
+ ...(typeof said.base === "string" && said.base ? { base: said.base } : {}),
100
+ };
97
101
  }
98
102
  /**
99
103
  * Ask the app's pull-request route, on the project's credentials: with the
package/dist/convex.d.ts CHANGED
@@ -101,6 +101,7 @@ export declare const api: {
101
101
  ask: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
102
102
  completeApproval: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
103
103
  stall: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
104
+ changeInPlace: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
104
105
  answer: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
105
106
  approveCheckpoint: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
106
107
  removeRehearsal: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
package/dist/convex.js CHANGED
@@ -115,6 +115,8 @@ export const api = {
115
115
  // DECIDE-4: the action on the option the owner chose, taken once.
116
116
  completeApproval: mutation("solutions/mutations:completeApproval"),
117
117
  stall: mutation("solutions/mutations:stall"),
118
+ // VERB-8: a made outcome's drafter's change in place.
119
+ changeInPlace: mutation("solutions/mutations:changeInPlace"),
118
120
  answer: mutation("solutions/mutations:answer"),
119
121
  // A rehearsal's scripted approval of a checkpoint (CHECKPOINT-1.1).
120
122
  approveCheckpoint: mutation("solutions/mutations:approveCheckpoint"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.38.4",
3
+ "version": "0.39.1",
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": {