@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.
Files changed (135) hide show
  1. package/dist/cli.js +32 -7
  2. package/dist/cloud.d.ts +18 -0
  3. package/dist/cloud.js +38 -0
  4. package/dist/commands/board/index.d.ts +13 -0
  5. package/dist/commands/board/index.js +96 -0
  6. package/dist/commands/branch-merge.d.ts +78 -0
  7. package/dist/commands/branch-merge.js +129 -0
  8. package/dist/commands/create-requirement-document.js +2 -2
  9. package/dist/commands/diff.js +15 -5
  10. package/dist/commands/init.js +7 -6
  11. package/dist/commands/link.js +27 -21
  12. package/dist/commands/mockup/index.d.ts +11 -0
  13. package/dist/commands/mockup/index.js +82 -0
  14. package/dist/commands/report.js +22 -9
  15. package/dist/commands/review-test.js +3 -2
  16. package/dist/commands/scores/index.d.ts +50 -0
  17. package/dist/commands/scores/index.js +190 -0
  18. package/dist/commands/solution/ask.d.ts +9 -0
  19. package/dist/commands/solution/ask.js +27 -0
  20. package/dist/commands/solution/author.d.ts +19 -0
  21. package/dist/commands/solution/author.js +202 -0
  22. package/dist/commands/solution/branch.d.ts +103 -0
  23. package/dist/commands/solution/branch.js +341 -0
  24. package/dist/commands/solution/brief.d.ts +81 -0
  25. package/dist/commands/solution/brief.js +47 -0
  26. package/dist/commands/solution/checkout.d.ts +27 -0
  27. package/dist/commands/solution/checkout.js +66 -0
  28. package/dist/commands/solution/client.d.ts +16 -0
  29. package/dist/commands/solution/client.js +46 -0
  30. package/dist/commands/solution/contract.d.ts +9 -0
  31. package/dist/commands/solution/contract.js +23 -0
  32. package/dist/commands/solution/create.d.ts +14 -0
  33. package/dist/commands/solution/create.js +52 -0
  34. package/dist/commands/solution/demo-script.d.ts +28 -0
  35. package/dist/commands/solution/demo-script.js +43 -0
  36. package/dist/commands/solution/image.d.ts +60 -0
  37. package/dist/commands/solution/image.js +148 -0
  38. package/dist/commands/solution/index.d.ts +12 -0
  39. package/dist/commands/solution/index.js +281 -0
  40. package/dist/commands/solution/look-up.d.ts +9 -0
  41. package/dist/commands/solution/look-up.js +69 -0
  42. package/dist/commands/solution/next.d.ts +12 -0
  43. package/dist/commands/solution/next.js +48 -0
  44. package/dist/commands/solution/plan-file.d.ts +22 -0
  45. package/dist/commands/solution/plan-file.js +33 -0
  46. package/dist/commands/solution/preview-screenshot.d.ts +21 -0
  47. package/dist/commands/solution/preview-screenshot.js +56 -0
  48. package/dist/commands/solution/pull-request.d.ts +25 -0
  49. package/dist/commands/solution/pull-request.js +117 -0
  50. package/dist/commands/solution/pushing.d.ts +66 -0
  51. package/dist/commands/solution/pushing.js +185 -0
  52. package/dist/commands/solution/rehearse.d.ts +10 -0
  53. package/dist/commands/solution/rehearse.js +24 -0
  54. package/dist/commands/solution/request.d.ts +3 -0
  55. package/dist/commands/solution/request.js +13 -0
  56. package/dist/commands/solution/review.d.ts +27 -0
  57. package/dist/commands/solution/review.js +104 -0
  58. package/dist/commands/solution/run-file.d.ts +42 -0
  59. package/dist/commands/solution/run-file.js +63 -0
  60. package/dist/commands/solution/share.d.ts +62 -0
  61. package/dist/commands/solution/share.js +346 -0
  62. package/dist/commands/solution/stall.d.ts +14 -0
  63. package/dist/commands/solution/stall.js +41 -0
  64. package/dist/commands/solution/start.d.ts +11 -0
  65. package/dist/commands/solution/start.js +20 -0
  66. package/dist/commands/solution/status.d.ts +39 -0
  67. package/dist/commands/solution/status.js +145 -0
  68. package/dist/commands/solution/summary.d.ts +13 -0
  69. package/dist/commands/solution/summary.js +5 -0
  70. package/dist/commands/solution/things.d.ts +118 -0
  71. package/dist/commands/solution/things.js +78 -0
  72. package/dist/commands/solution/thread.d.ts +13 -0
  73. package/dist/commands/solution/thread.js +53 -0
  74. package/dist/commands/solution/upload.d.ts +24 -0
  75. package/dist/commands/solution/upload.js +27 -0
  76. package/dist/commands/solution/video.d.ts +88 -0
  77. package/dist/commands/solution/video.js +364 -0
  78. package/dist/commands/solution/working-folder.d.ts +44 -0
  79. package/dist/commands/solution/working-folder.js +64 -0
  80. package/dist/commands/style-check.js +4 -4
  81. package/dist/commands/sync.d.ts +2 -1
  82. package/dist/commands/sync.js +21 -5
  83. package/dist/convex.d.ts +64 -10
  84. package/dist/convex.js +75 -10
  85. package/dist/harness/finalize.js +16 -8
  86. package/dist/harness/reportingStatus.js +2 -11
  87. package/dist/push/core.js +7 -2
  88. package/dist/rehearsal/derive.d.ts +23 -0
  89. package/dist/rehearsal/derive.js +112 -0
  90. package/dist/rehearsal/driver.d.ts +44 -0
  91. package/dist/rehearsal/driver.js +337 -0
  92. package/dist/rehearsal/earlier.d.ts +25 -0
  93. package/dist/rehearsal/earlier.js +138 -0
  94. package/dist/rehearsal/index.d.ts +5 -0
  95. package/dist/rehearsal/index.js +4 -0
  96. package/dist/rehearsal/live.d.ts +72 -0
  97. package/dist/rehearsal/live.js +301 -0
  98. package/dist/rehearsal/report.d.ts +4 -0
  99. package/dist/rehearsal/report.js +69 -0
  100. package/dist/rehearsal/types.d.ts +377 -0
  101. package/dist/rehearsal/types.js +8 -0
  102. package/dist/requirements/cloud-ai.d.ts +0 -6
  103. package/dist/requirements/cloud-ai.js +2 -11
  104. package/dist/requirements/cloud-coverage.js +4 -4
  105. package/dist/requirements/index.d.ts +6 -0
  106. package/dist/requirements/index.js +24 -0
  107. package/dist/schema/browser.d.ts +1 -1
  108. package/dist/schema/browser.js +3 -1
  109. package/dist/schema/file-writer.d.ts +5 -0
  110. package/dist/schema/file-writer.js +4 -0
  111. package/dist/schema/parser.d.ts +11 -2
  112. package/dist/schema/parser.js +11 -7
  113. package/dist/schema/schemas.d.ts +40 -0
  114. package/dist/schema/schemas.js +4 -0
  115. package/dist/sync/execute.d.ts +10 -1
  116. package/dist/sync/execute.js +76 -24
  117. package/dist/sync/index.d.ts +1 -1
  118. package/dist/sync/index.js +1 -1
  119. package/dist/sync/publish.d.ts +7 -0
  120. package/dist/sync/publish.js +2 -2
  121. package/dist/sync/segment.js +4 -1
  122. package/dist/sync/snapshot.d.ts +27 -5
  123. package/dist/sync/snapshot.js +92 -15
  124. package/dist/sync/solution-documents.d.ts +64 -0
  125. package/dist/sync/solution-documents.js +97 -0
  126. package/dist/sync/types.d.ts +19 -1
  127. package/dist/utils/command-error.d.ts +19 -0
  128. package/dist/utils/command-error.js +44 -0
  129. package/dist/utils/oauth-flow.js +2 -2
  130. package/dist/utils/project-selector.js +4 -4
  131. package/dist/version-messages.d.ts +28 -0
  132. package/dist/version-messages.js +87 -0
  133. package/package.json +3 -2
  134. package/dist/config.d.ts +0 -5
  135. package/dist/config.js +0 -12
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Which solution and agent a verb is for (VERB-4). Inside a runner the run
3
+ * itself says — the runner writes a run file and names it in
4
+ * `DOTREQ_RUN_FILE`: `{ solutionId, agent, branch }` — so an agent types no
5
+ * ids. Outside one, the agent names them with `--solution` and `--agent`,
6
+ * which win wherever they are given. The outcome is the backend's to work
7
+ * out — the one the agent is at — unless `--outcome` names it, as an agent
8
+ * at more than one does.
9
+ */
10
+ /** The options every solution verb an agent runs carries. */
11
+ export type WhereOptions = {
12
+ solution?: string;
13
+ agent?: string;
14
+ outcome?: string;
15
+ /** The outcome under its old name: a hidden alias of `--outcome`. */
16
+ step?: string;
17
+ };
18
+ export type Where = {
19
+ solutionId: string;
20
+ /** The agent working: a plan's named agent, or one dedicated to an outcome ("Pitch/drafter"). */
21
+ agent: string;
22
+ /** The outcome, when the agent named it; otherwise the one the agent is at. */
23
+ outcome?: string;
24
+ /**
25
+ * The solution's branch, which the runner has checked out (SOLBRANCH-1):
26
+ * only a run says it, and a verb that has it pushes to it before its
27
+ * record is kept (SOLBRANCH-3). Absent outside a runner, where a person
28
+ * pushes their own branches.
29
+ */
30
+ branch?: string;
31
+ };
32
+ /** The run's own, with anything the agent named winning over it. */
33
+ export declare function whereOf(options: WhereOptions): Partial<Where>;
34
+ /** For a verb an agent runs: both the solution and the agent, or a refusal saying how to give them. */
35
+ export declare function requireWhere(options: WhereOptions): Where;
36
+ /** For a verb that needs only the solution, as `checkout`: the solution, or a refusal saying how to give it. */
37
+ export declare function requireSolution(options: WhereOptions): Partial<Where> & {
38
+ solutionId: string;
39
+ };
40
+ /** `"Pitch"` when the outcome is known, for what a verb prints; empty when it isn't. */
41
+ export declare function onOutcome(outcome: string | undefined): string;
42
+ //# sourceMappingURL=run-file.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { readFileSync } from "node:fs";
2
+ const SAY_BOTH = 'Nothing says which solution and which agent this is for. Name them: --solution <id> --agent "<agent>". Inside a runner they come from the run itself.';
3
+ const SAY_SOLUTION = "Nothing says which solution this is for. Name it: --solution <id>. Inside a runner it comes from the run itself.";
4
+ /** What the runner wrote for this run, or nothing when there is no run file to read. */
5
+ function fromRun() {
6
+ const path = process.env.DOTREQ_RUN_FILE;
7
+ if (!path)
8
+ return {};
9
+ let parsed;
10
+ try {
11
+ parsed = JSON.parse(readFileSync(path, "utf8"));
12
+ }
13
+ catch {
14
+ // A run file that cannot be read is no worse than none: the options,
15
+ // or the refusal, decide.
16
+ return {};
17
+ }
18
+ if (!parsed || typeof parsed !== "object")
19
+ return {};
20
+ const { solutionId, agent, branch } = parsed;
21
+ return {
22
+ solutionId: typeof solutionId === "string" ? solutionId : undefined,
23
+ agent: typeof agent === "string" && agent ? agent : undefined,
24
+ branch: typeof branch === "string" && branch ? branch : undefined,
25
+ };
26
+ }
27
+ /** The outcome named, by `--outcome` or its old name `--step`; blank is none. */
28
+ function outcomeNamed(options) {
29
+ return (options.outcome ?? options.step)?.trim() || undefined;
30
+ }
31
+ /** The run's own, with anything the agent named winning over it. */
32
+ export function whereOf(options) {
33
+ const run = fromRun();
34
+ return {
35
+ solutionId: options.solution ?? run.solutionId,
36
+ agent: options.agent?.trim() || run.agent,
37
+ outcome: outcomeNamed(options),
38
+ // The run's branch is the run's solution's: naming another solution
39
+ // is working outside the run.
40
+ branch: options.solution === undefined || options.solution === run.solutionId
41
+ ? run.branch
42
+ : undefined,
43
+ };
44
+ }
45
+ /** For a verb an agent runs: both the solution and the agent, or a refusal saying how to give them. */
46
+ export function requireWhere(options) {
47
+ const where = whereOf(options);
48
+ if (!where.solutionId || !where.agent)
49
+ throw new Error(SAY_BOTH);
50
+ return { ...where, solutionId: where.solutionId, agent: where.agent };
51
+ }
52
+ /** For a verb that needs only the solution, as `checkout`: the solution, or a refusal saying how to give it. */
53
+ export function requireSolution(options) {
54
+ const where = whereOf(options);
55
+ if (!where.solutionId)
56
+ throw new Error(SAY_SOLUTION);
57
+ return { ...where, solutionId: where.solutionId };
58
+ }
59
+ /** `"Pitch"` when the outcome is known, for what a verb prints; empty when it isn't. */
60
+ export function onOutcome(outcome) {
61
+ return outcome ? ` on "${outcome}"` : "";
62
+ }
63
+ //# sourceMappingURL=run-file.js.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The verbs as the share command runs them, with no install: a developer
3
+ * who ran `npx` has no `dotreq` on their path.
4
+ */
5
+ export declare const SHARE_CLI = "npx @popoverai/dotrequirements@latest solution";
6
+ type RecordedFile = {
7
+ documentId: string;
8
+ path: string;
9
+ version: number;
10
+ onBranch: boolean;
11
+ hash: string;
12
+ };
13
+ /** What a checkout leaves in the repository, for push and done to act on. */
14
+ export type ShareRecord = {
15
+ token: string;
16
+ /** The outcome checked out: one someone outside the product creates. */
17
+ outcome: string;
18
+ round: number | null;
19
+ solution: {
20
+ id: string;
21
+ title: string;
22
+ directory: string;
23
+ };
24
+ files: RecordedFile[];
25
+ };
26
+ /** Every solution checked out into this repository, by its folder. */
27
+ export declare function checkedOut(): ShareRecord[];
28
+ /**
29
+ * `dotreq solution checkout --share <token> --outcome "<name>"`
30
+ * (SOLSHARE-2): every document of the project into `.requirements/`, the
31
+ * solution's branch's versions where it has them, each at the file this
32
+ * repository already has for it or where the project's sync would put it;
33
+ * the solution's mockups as pages to open; the record push and done act
34
+ * on; and the outcome's prompt, printed for whoever or whatever reads the
35
+ * output.
36
+ */
37
+ export declare function shareCheckout(token: string,
38
+ /** The outcome handed over: one someone outside the product creates. */
39
+ outcome: string | undefined,
40
+ /** SOLSHARE-2.10: the branch's version replaces an edit it has a newer version than. */
41
+ branchWins?: boolean): Promise<void>;
42
+ /**
43
+ * `dotreq solution push`, in a repository a solution was checked out into
44
+ * (SOLSHARE-3): each document changed since the checkout, and each one
45
+ * added under `.requirements/`, to the solution's branch. False when no
46
+ * solution is checked out here, for the runner's push to take over.
47
+ */
48
+ export declare function sharePush(solution?: string,
49
+ /** SOLSHARE-3.8: the repository's version wins over a newer one on the branch. */
50
+ repoWins?: boolean): Promise<boolean>;
51
+ export type DoneOptions = {
52
+ comment?: string;
53
+ solution?: string;
54
+ };
55
+ /**
56
+ * `dotreq solution done [<where it is>...] [--comment "..."]` (SOLSHARE-4):
57
+ * close the round of the outcome the repository was checked out for, with
58
+ * nothing to paste.
59
+ */
60
+ export declare function doneCommand(made: string[], options: DoneOptions): Promise<void>;
61
+ export {};
62
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1,346 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { ConvexHttpClient } from "convex/browser";
6
+ import { ConvexError } from "convex/values";
7
+ import { parse } from "yaml";
8
+ import { convexUrl, webApiUrl } from "../../cloud.js";
9
+ import { api } from "../../convex.js";
10
+ import { extractFrontmatterBlock, stripFrontmatterBlock, } from "../../schema/parser-core.js";
11
+ import { refusal } from "./client.js";
12
+ import { layOut, recordSavedIds, } from "./working-folder.js";
13
+ /**
14
+ * A solution's share command, from any repository (SOLSHARE; design in
15
+ * docs/working/solution-branches.md, "Handing the build to the people who
16
+ * do it"): `dotreq solution checkout --share <token> --outcome "<name>"`
17
+ * lays the solution's work out and records, under the solution's folder,
18
+ * which solution, outcome and round it was for; `dotreq solution push` sends the
19
+ * changed documents to the solution's branch; `dotreq solution done` closes
20
+ * the round it was checked out for. The token is the only credential, and
21
+ * the record stays out of git.
22
+ */
23
+ const RECORD = ".checkout.json";
24
+ const SOLUTIONS = path.join(".requirements", "solutions");
25
+ /** What stays out of git in a solution's folder: the record, which holds the token, and the rendered mockups. */
26
+ const IGNORED = [RECORD, "mockups/"];
27
+ /**
28
+ * The verbs as the share command runs them, with no install: a developer
29
+ * who ran `npx` has no `dotreq` on their path.
30
+ */
31
+ export const SHARE_CLI = "npx @popoverai/dotrequirements@latest solution";
32
+ /**
33
+ * Work from the repository's root, whichever folder the command was run
34
+ * in, so that a checkout from a subfolder and a push from the root see the
35
+ * same record. Outside a git repository, where it was run.
36
+ */
37
+ function atRepositoryRoot() {
38
+ try {
39
+ const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
40
+ encoding: "utf-8",
41
+ stdio: ["ignore", "pipe", "ignore"],
42
+ }).trim();
43
+ if (root)
44
+ process.chdir(root);
45
+ }
46
+ catch {
47
+ // Not a git repository: the folder it was run in is the repository.
48
+ }
49
+ }
50
+ /** Calls on the share command's token alone: no project is linked, and none is needed. */
51
+ function shareClient() {
52
+ const client = new ConvexHttpClient(convexUrl());
53
+ const inWords = async (call) => {
54
+ try {
55
+ return await call();
56
+ }
57
+ catch (error) {
58
+ if (error instanceof ConvexError)
59
+ throw new Error(refusal(error.data));
60
+ throw error;
61
+ }
62
+ };
63
+ return {
64
+ query: (fn, args) => inWords(() => client.query(fn, args)),
65
+ mutation: (fn, args) => inWords(() => client.mutation(fn, args)),
66
+ };
67
+ }
68
+ function hashOf(file) {
69
+ return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
70
+ }
71
+ function recordPath(directory) {
72
+ return path.join(SOLUTIONS, directory, RECORD);
73
+ }
74
+ /** Every solution checked out into this repository, by its folder. */
75
+ export function checkedOut() {
76
+ if (!fs.existsSync(SOLUTIONS))
77
+ return [];
78
+ return fs
79
+ .readdirSync(SOLUTIONS)
80
+ .map((directory) => recordPath(directory))
81
+ .filter((file) => fs.existsSync(file))
82
+ .map((file) => JSON.parse(fs.readFileSync(file, "utf-8")));
83
+ }
84
+ /** The one solution a push or done is for, or why it can't tell (SOLSHARE-3.7, 4.6, 4.7). */
85
+ function oneCheckedOut(solution) {
86
+ const records = checkedOut();
87
+ if (records.length === 0) {
88
+ throw new Error("No solution is checked out here: run the share command you were given first.");
89
+ }
90
+ if (solution) {
91
+ const found = records.find((record) => record.solution.id === solution ||
92
+ record.solution.directory === solution);
93
+ if (!found) {
94
+ throw new Error(`"${solution}" isn't checked out here. Checked out: ${records.map((record) => record.solution.directory).join(", ")}.`);
95
+ }
96
+ return found;
97
+ }
98
+ if (records.length > 1) {
99
+ throw new Error(`More than one solution is checked out here; say which with --solution: ${records.map((record) => record.solution.directory).join(", ")}.`);
100
+ }
101
+ return records[0];
102
+ }
103
+ function writeRecord(record) {
104
+ const directory = path.join(SOLUTIONS, record.solution.directory);
105
+ fs.mkdirSync(directory, { recursive: true });
106
+ fs.writeFileSync(recordPath(record.solution.directory), `${JSON.stringify(record, null, 2)}\n`);
107
+ // The record holds the token, and the mockups are renders: both stay out
108
+ // of the repository's history. Lines the developer added are kept.
109
+ const ignore = path.join(directory, ".gitignore");
110
+ const lines = fs.existsSync(ignore)
111
+ ? fs.readFileSync(ignore, "utf-8").split("\n").filter(Boolean)
112
+ : [];
113
+ const missing = IGNORED.filter((line) => !lines.includes(line));
114
+ if (missing.length > 0) {
115
+ fs.writeFileSync(ignore, `${[...lines, ...missing].join("\n")}\n`);
116
+ }
117
+ }
118
+ /**
119
+ * Markdown files under `.requirements/`, with the document id each one's
120
+ * frontmatter names. A file whose frontmatter can't be read is set aside,
121
+ * with a warning, rather than stopping the command.
122
+ */
123
+ function requirementsFiles() {
124
+ const found = [];
125
+ const walk = (directory) => {
126
+ if (!fs.existsSync(directory))
127
+ return;
128
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
129
+ const full = path.join(directory, entry.name);
130
+ if (entry.isDirectory())
131
+ walk(full);
132
+ else if (entry.name.endsWith(".md")) {
133
+ let id;
134
+ try {
135
+ const yaml = extractFrontmatterBlock(fs.readFileSync(full, "utf-8"));
136
+ id = (yaml ? parse(yaml) : null)?.document?.id;
137
+ }
138
+ catch {
139
+ console.warn(`Left ${full} alone: its frontmatter isn't YAML the command can read.`);
140
+ continue;
141
+ }
142
+ found.push({
143
+ file: full,
144
+ ...(typeof id === "string" ? { documentId: id } : {}),
145
+ });
146
+ }
147
+ }
148
+ };
149
+ walk(".requirements");
150
+ return found;
151
+ }
152
+ /** A file as a push submits it: its text without frontmatter, and what the frontmatter says of its document. */
153
+ function submitted(file) {
154
+ const content = fs.readFileSync(file, "utf-8");
155
+ const yaml = extractFrontmatterBlock(content);
156
+ const document = (yaml ? parse(yaml) : null)?.document;
157
+ return {
158
+ path: file,
159
+ markdownContent: stripFrontmatterBlock(content),
160
+ ...(typeof document?.id === "string" ? { documentId: document.id } : {}),
161
+ ...(typeof document?.defaultPrefix === "string"
162
+ ? { defaultPrefix: document.defaultPrefix }
163
+ : {}),
164
+ };
165
+ }
166
+ /** The app the share command renders mockups through. */
167
+ function appUrl() {
168
+ return webApiUrl().replace(/\/+$/, "");
169
+ }
170
+ /**
171
+ * `dotreq solution checkout --share <token> --outcome "<name>"`
172
+ * (SOLSHARE-2): every document of the project into `.requirements/`, the
173
+ * solution's branch's versions where it has them, each at the file this
174
+ * repository already has for it or where the project's sync would put it;
175
+ * the solution's mockups as pages to open; the record push and done act
176
+ * on; and the outcome's prompt, printed for whoever or whatever reads the
177
+ * output.
178
+ */
179
+ export async function shareCheckout(token,
180
+ /** The outcome handed over: one someone outside the product creates. */
181
+ outcome,
182
+ /** SOLSHARE-2.10: the branch's version replaces an edit it has a newer version than. */
183
+ branchWins = false) {
184
+ if (!outcome)
185
+ throw new Error('Say which outcome: --outcome "<name>", as the command you were given does.');
186
+ atRepositoryRoot();
187
+ const client = shareClient();
188
+ const got = await client.query(api.solutions.shares.forCheckout, {
189
+ token,
190
+ outcome,
191
+ });
192
+ // SOLSHARE-2.6, 2.10, 2.11: an edit that wasn't pushed is left as it is,
193
+ // unless the branch has saved a newer version of its document since the
194
+ // checkout: then the checkout stops, or with --branch-wins takes the
195
+ // branch's version.
196
+ const earlier = checkedOut().find((record) => record.solution.id === got.solution.id);
197
+ const unpushed = (earlier?.files ?? []).filter((file) => fs.existsSync(file.path) && hashOf(file.path) !== file.hash);
198
+ const current = new Map(got.files.map((file) => [file.documentId, file]));
199
+ const newer = (file) => {
200
+ const now = current.get(file.documentId);
201
+ return !!now?.onBranch && (!file.onBranch || now.version > file.version);
202
+ };
203
+ const conflicting = unpushed.filter(newer);
204
+ const kept = unpushed.filter((file) => !newer(file));
205
+ if (conflicting.length > 0 && !branchWins) {
206
+ throw new Error(`Nothing was written: the solution's branch has saved a newer version of ${conflicting.map((file) => file.path).join(", ")} since you checked ${conflicting.length === 1 ? "it" : "them"} out, and your changes to ${conflicting.length === 1 ? "it" : "them"} weren't pushed. Push them first with \`${SHARE_CLI} push --repo-wins\` to keep yours, or run this again with --branch-wins to take the branch's.`);
207
+ }
208
+ const keptIds = new Set(kept.map((file) => file.documentId));
209
+ // Each document goes where this repository already keeps it, when it does.
210
+ const here = new Map(requirementsFiles()
211
+ .filter((found) => found.documentId)
212
+ .map((found) => [found.documentId, found.file]));
213
+ const files = got.files.map((file) => ({
214
+ ...file,
215
+ path: here.get(file.documentId) ?? file.path,
216
+ }));
217
+ layOut(files.filter((file) => !keptIds.has(file.documentId)));
218
+ const mockupDirectory = path.join(SOLUTIONS, got.solution.directory, "mockups");
219
+ const rendered = [];
220
+ const failed = [];
221
+ for (const mockupId of got.mockups) {
222
+ const response = await fetch(`${appUrl()}/api/solutions/share/mockup`, {
223
+ method: "POST",
224
+ headers: { "Content-Type": "application/json" },
225
+ body: JSON.stringify({ token, mockupId }),
226
+ }).catch(() => null);
227
+ if (!response?.ok) {
228
+ failed.push(mockupId);
229
+ continue;
230
+ }
231
+ fs.mkdirSync(mockupDirectory, { recursive: true });
232
+ fs.writeFileSync(path.join(mockupDirectory, `${mockupId}.html`), await response.text());
233
+ rendered.push(mockupId);
234
+ }
235
+ writeRecord({
236
+ token,
237
+ outcome: got.outcome,
238
+ round: got.round,
239
+ solution: got.solution,
240
+ // An edit left as it is keeps the version it was made from, so a push
241
+ // still sends it.
242
+ files: files.map((file) => kept.find((left) => left.documentId === file.documentId) ?? {
243
+ documentId: file.documentId,
244
+ path: file.path,
245
+ version: file.version,
246
+ onBranch: file.onBranch,
247
+ hash: hashOf(file.path),
248
+ }),
249
+ });
250
+ console.log(`Checked out ${got.solution.title}: ${files.length} ${files.length === 1 ? "document" : "documents"} under .requirements/${rendered.length ? `, and ${rendered.length} ${rendered.length === 1 ? "mockup" : "mockups"} under ${mockupDirectory}/` : ""}.`);
251
+ if (kept.length > 0) {
252
+ console.log(`Left your changes to ${kept.map((file) => file.path).join(", ")} as they are: the branch hasn't changed ${kept.length === 1 ? "that document" : "those documents"} since.`);
253
+ }
254
+ if (conflicting.length > 0) {
255
+ console.log(`Replaced your changes to ${conflicting.map((file) => file.path).join(", ")} with the branch's newer ${conflicting.length === 1 ? "version" : "versions"}.`);
256
+ }
257
+ if (failed.length > 0) {
258
+ console.log(`Couldn't render ${failed.length === 1 ? "mockup" : "mockups"} ${failed.join(", ")}: run the share command again to try again.`);
259
+ }
260
+ if (got.prompt) {
261
+ console.log(`\n${got.prompt}`);
262
+ }
263
+ else {
264
+ console.log(`\n"${got.outcome}" isn't waiting on anyone right now, so there's nothing to hand back yet.`);
265
+ }
266
+ }
267
+ /**
268
+ * `dotreq solution push`, in a repository a solution was checked out into
269
+ * (SOLSHARE-3): each document changed since the checkout, and each one
270
+ * added under `.requirements/`, to the solution's branch. False when no
271
+ * solution is checked out here, for the runner's push to take over.
272
+ */
273
+ export async function sharePush(solution,
274
+ /** SOLSHARE-3.8: the repository's version wins over a newer one on the branch. */
275
+ repoWins = false) {
276
+ atRepositoryRoot();
277
+ if (checkedOut().length === 0)
278
+ return false;
279
+ const record = oneCheckedOut(solution);
280
+ const changed = record.files
281
+ .filter((file) => fs.existsSync(file.path) && hashOf(file.path) !== file.hash)
282
+ .map((file) => file.path);
283
+ // A new document is a requirements file, or any Markdown in a solution's
284
+ // folder, that names no document yet; other notes under .requirements/
285
+ // stay where they are.
286
+ const added = requirementsFiles()
287
+ .filter((found) => !found.documentId &&
288
+ (found.file.endsWith(".requirements.md") ||
289
+ found.file.startsWith(`${SOLUTIONS}${path.sep}`)))
290
+ .map((found) => found.file);
291
+ const files = [...changed, ...added].map(submitted);
292
+ if (files.length === 0) {
293
+ console.log("Nothing to push: no document changed since the checkout.");
294
+ return true;
295
+ }
296
+ const saved = await shareClient().mutation(api.solutions.shares.push, {
297
+ token: record.token,
298
+ files,
299
+ base: record.files.map(({ documentId, version, onBranch }) => ({
300
+ documentId,
301
+ version,
302
+ onBranch,
303
+ })),
304
+ ...(repoWins ? { repoWins: true } : {}),
305
+ });
306
+ recordSavedIds(files, saved);
307
+ const byId = new Map(record.files.map((file) => [file.documentId, file]));
308
+ for (const entry of saved) {
309
+ byId.set(entry.documentId, {
310
+ documentId: entry.documentId,
311
+ path: entry.path,
312
+ version: entry.version,
313
+ onBranch: true,
314
+ hash: hashOf(entry.path),
315
+ });
316
+ }
317
+ writeRecord({ ...record, files: [...byId.values()] });
318
+ for (const entry of saved) {
319
+ console.log(`Saved ${entry.path} on the solution's branch as version ${entry.version}.${entry.replaced ? ` It replaced version ${entry.replaced.version}, saved ${new Date(entry.replaced.savedAt).toLocaleString()}, which the branch keeps.` : ""}`);
320
+ }
321
+ return true;
322
+ }
323
+ /**
324
+ * `dotreq solution done [<where it is>...] [--comment "..."]` (SOLSHARE-4):
325
+ * close the round of the outcome the repository was checked out for, with
326
+ * nothing to paste.
327
+ */
328
+ export async function doneCommand(made, options) {
329
+ atRepositoryRoot();
330
+ const record = oneCheckedOut(options.solution);
331
+ if (record.round === null) {
332
+ throw new Error(`"${record.outcome}" wasn't waiting on anyone when it was checked out: run the share command again once it is.`);
333
+ }
334
+ const said = await shareClient().mutation(api.solutions.shares.done, {
335
+ token: record.token,
336
+ outcome: record.outcome,
337
+ round: record.round,
338
+ ...(made.length ? { made } : {}),
339
+ ...(options.comment?.trim() ? { comment: options.comment.trim() } : {}),
340
+ });
341
+ // STEP-REVIEW-18.7.0: nothing was recorded, and the message says why.
342
+ if (said.recorded === false)
343
+ throw new Error(said.message);
344
+ console.log(said.message);
345
+ }
346
+ //# sourceMappingURL=share.js.map
@@ -0,0 +1,14 @@
1
+ import { type WhereOptions } from "./run-file.js";
2
+ /**
3
+ * `dotreq solution stall "<what has to change>"`: the agent cannot go on
4
+ * with the outcome it is at, or the one `--outcome` names, and no decision
5
+ * would fix that. The reason is what has to change before it can be
6
+ * started again (VERB-3.2).
7
+ */
8
+ export declare function stallCommand(reason: string, options: WhereOptions & {
9
+ /** False from the runner, which pushed already and says why it couldn't in the reason. */
10
+ push?: boolean;
11
+ }): Promise<void>;
12
+ /** A stall's reason, and that the work could not be pushed, and why (SOLBRANCH-3.4, 4.1). */
13
+ export declare function withUnpushed(reason: string, why: string): string;
14
+ //# sourceMappingURL=stall.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { api } from "../../convex.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { pushWork } from "./pushing.js";
4
+ import { onOutcome, requireWhere } from "./run-file.js";
5
+ /**
6
+ * `dotreq solution stall "<what has to change>"`: the agent cannot go on
7
+ * with the outcome it is at, or the one `--outcome` names, and no decision
8
+ * would fix that. The reason is what has to change before it can be
9
+ * started again (VERB-3.2).
10
+ */
11
+ export async function stallCommand(reason, options) {
12
+ if (!reason.trim()) {
13
+ throw new Error("Say what has to change before your work can be started again.");
14
+ }
15
+ const where = requireWhere(options);
16
+ const { solutionId, agent } = where;
17
+ const client = solutionClient();
18
+ // SOLBRANCH-3.4: a stall is recorded whether or not the work can be
19
+ // pushed; when it can't, the reason Quinn reads says so, and why.
20
+ let said = reason.trim();
21
+ if (where.branch && options.push !== false) {
22
+ const pushed = await pushWork(client, {
23
+ solutionId,
24
+ branch: where.branch,
25
+ });
26
+ if (!pushed.ok)
27
+ said = withUnpushed(said, pushed.why);
28
+ }
29
+ await client.mutation(api.solutions.mutations.stall, {
30
+ solutionId,
31
+ agent,
32
+ ...(where.outcome ? { outcome: where.outcome } : {}),
33
+ reason: said,
34
+ });
35
+ console.log(`Stalled${onOutcome(where.outcome)}. It can be started again once that is fixed.`);
36
+ }
37
+ /** A stall's reason, and that the work could not be pushed, and why (SOLBRANCH-3.4, 4.1). */
38
+ export function withUnpushed(reason, why) {
39
+ return `${reason.replace(/\s+$/, "")}${/[.!?]$/.test(reason.trim()) ? "" : "."} The work could not be pushed: ${why.replace(/[.\s]+$/, "")}.`;
40
+ }
41
+ //# sourceMappingURL=stall.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `dotreq solution start <solution-id> <agent>`: begin an agent's work by
3
+ * hand. A stalled agent is started again, and it takes the part of an
4
+ * outcome's work it has due: the one `--outcome` names, or its first in the
5
+ * plan's order.
6
+ */
7
+ export declare function startCommand(solutionId: string, agent: string, options: {
8
+ outcome?: string;
9
+ step?: string;
10
+ }): Promise<void>;
11
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { api } from "../../convex.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { onOutcome } from "./run-file.js";
4
+ /**
5
+ * `dotreq solution start <solution-id> <agent>`: begin an agent's work by
6
+ * hand. A stalled agent is started again, and it takes the part of an
7
+ * outcome's work it has due: the one `--outcome` names, or its first in the
8
+ * plan's order.
9
+ */
10
+ export async function startCommand(solutionId, agent, options) {
11
+ const outcome = (options.outcome ?? options.step)?.trim() || undefined;
12
+ const client = solutionClient();
13
+ await client.mutation(api.solutions.mutations.start, {
14
+ solutionId,
15
+ agent,
16
+ ...(outcome ? { outcome } : {}),
17
+ });
18
+ console.log(`Started ${agent}${onOutcome(outcome)}.`);
19
+ }
20
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1,39 @@
1
+ import { type Panel, type PanelOutcome } from "./brief.js";
2
+ import { solutionClient } from "./client.js";
3
+ import { type WhereOptions } from "./run-file.js";
4
+ /** What an agent is at: an outcome, and whether it drafts or reviews it there. */
5
+ export type AgentAt = {
6
+ outcome: PanelOutcome;
7
+ part: "draft" | "review";
8
+ };
9
+ /**
10
+ * The outcome an agent is at, read off the plan panel: the one named, when
11
+ * `--outcome` names it; otherwise the one it is drafting, else one it
12
+ * reviews that stands reviewing, else one it stalled or asked on, else one
13
+ * ready for it to draft. Undefined when it is at none, as after it recorded
14
+ * its work; refused when it is at more than one and nothing says which.
15
+ */
16
+ export declare function agentAt(panel: Pick<Panel, "phases">, agent: string, named?: string): AgentAt | undefined;
17
+ /** The plan panel, or a refusal when the solution isn't there. */
18
+ export declare function panelOf(client: ReturnType<typeof solutionClient>, solutionId: string): Promise<Panel>;
19
+ /**
20
+ * The outcome a verb is about: the one named, else the one the agent is at
21
+ * on the panel. Refused, saying how to name it, when it is at none.
22
+ */
23
+ export declare function outcomeFor(client: ReturnType<typeof solutionClient>, where: {
24
+ solutionId: string;
25
+ agent: string;
26
+ outcome?: string;
27
+ }, what: string): Promise<string>;
28
+ /**
29
+ * `dotreq solution status`: where the outcome the agent is at stands, as
30
+ * recorded — its one word, with the reason when stalled. With `--json`,
31
+ * also which outcome that is, whether the agent drafts or reviews it there,
32
+ * and whether it still owes its part: what the Stop hook and the runner's
33
+ * read-back judge an agent's session by (STEP-REVIEW-9). An agent at no
34
+ * outcome, as after it recorded its work, owes nothing.
35
+ */
36
+ export declare function statusCommand(options: WhereOptions & {
37
+ json?: boolean;
38
+ }): Promise<void>;
39
+ //# sourceMappingURL=status.d.ts.map