@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (75) hide show
  1. package/README.md +12 -7
  2. package/dist/__build_info__-0wqQ-rtf.mjs +180 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/argument-error-CihH46Le.mjs +27 -0
  5. package/dist/attach-C0-YyNoY.mjs +92 -0
  6. package/dist/attach-CAKNqnUK.mjs +1259 -0
  7. package/dist/bin.mjs +1885 -1609
  8. package/dist/brief-C2hew8cS.mjs +116 -0
  9. package/dist/{decide-CGyx9rv7.mjs → builtin-files-YYAnbZGD.mjs} +3 -51
  10. package/dist/chips-CP_pgKlj.mjs +48 -0
  11. package/dist/command-tree-CEkzWNy6.mjs +63 -0
  12. package/dist/{completion-CSU5vkEt.mjs → completion-K4rAVsrf.mjs} +200 -30
  13. package/dist/context-B-fjPJky.mjs +106 -0
  14. package/dist/context-DMK0U1w0.mjs +160 -0
  15. package/dist/detached-q0AYG0Bs.mjs +39 -0
  16. package/dist/extra-arguments-BxQMXARO.mjs +13 -0
  17. package/dist/{find-D6Gj1sxg.mjs → find-Ct5CNl1J.mjs} +2 -2
  18. package/dist/flags-C90N4J1i.mjs +145 -0
  19. package/dist/follow-ups-D3HPyNYM.mjs +568 -0
  20. package/dist/format-CiPhQqbX.mjs +260 -0
  21. package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BQvcKQ9H.mjs} +49 -78
  22. package/dist/in-a-run-D_wg4iJi.mjs +27 -0
  23. package/dist/install-Hji6LIei.mjs +259 -0
  24. package/dist/{limits-B1-DYc3d.mjs → limits-BD6RZRCS.mjs} +2 -2
  25. package/dist/{mcp-DE_EmyVm.mjs → mcp-CpbDmBLG.mjs} +6 -6
  26. package/dist/once-J8Qn5fTG.mjs +112 -0
  27. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  28. package/dist/output-DXZbnFt_.mjs +458 -0
  29. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  30. package/dist/{pause-CL6N_Xrj.mjs → pause-Cl4ZYA35.mjs} +7 -4
  31. package/dist/print-DzugGZQB.mjs +19 -0
  32. package/dist/project-records-CtkmlsHN.mjs +233 -0
  33. package/dist/refusals-DQQX3AIb.mjs +116 -0
  34. package/dist/{relay-0SD0erwf.mjs → relay-CiP77vga.mjs} +2 -2
  35. package/dist/run-CtuNY3mE.mjs +234 -0
  36. package/dist/send-DRYGks8K.mjs +104 -0
  37. package/dist/short-flags-DkJLWVqh.mjs +1 -0
  38. package/dist/short-flags-Qf21as0E.mjs +83 -0
  39. package/dist/src-BMFQKHz4.mjs +3122 -0
  40. package/dist/{src-BbyEoMn4.mjs → src-BrFfDKux.mjs} +800 -235
  41. package/dist/{src-Dujmvv4s.mjs → src-BuF9Lbow.mjs} +12580 -5505
  42. package/dist/{src-DOcK8Jfp.mjs → src-k0A_QpST.mjs} +1024 -590
  43. package/dist/store-rTgRu8xy.mjs +25 -0
  44. package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
  45. package/dist/ui/assets/app-BYxpKJRh.css +1 -0
  46. package/dist/ui/assets/app-SEFEJLkF.js +112 -0
  47. package/dist/ui/assets/{index-9dFha9Qn.js → index-Cb6Voq_W.js} +3 -3
  48. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  49. package/dist/ui/index.html +2 -2
  50. package/dist/{ui-CTmO2Xzu.mjs → ui-C2f37TSb.mjs} +57 -25
  51. package/dist/usage-BuvGcQSk.mjs +256 -0
  52. package/dist/validate-ObP5lt29.mjs +1 -0
  53. package/dist/validate-_HjACtk9.mjs +632 -0
  54. package/dist/verdict-B592j1W3.mjs +84 -0
  55. package/dist/{watch-CtbgDAcz.mjs → watch-DV3ixCYj.mjs} +45 -17
  56. package/dist/{workflows-D1_WVukk.mjs → workflows-QLjBhGNS.mjs} +30 -5
  57. package/dist/worktrees-D3ubG_Qw.mjs +302 -0
  58. package/dist/yolo-BSH1E_kE.mjs +33 -0
  59. package/package.json +10 -8
  60. package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
  61. package/dist/clean-DaHjs__2.mjs +0 -88
  62. package/dist/context-DT8MfEIR.mjs +0 -274
  63. package/dist/format-Dnfv8zMT.mjs +0 -427
  64. package/dist/in-a-run-pLAZeNys.mjs +0 -22
  65. package/dist/list-BrH8KRSJ.mjs +0 -110
  66. package/dist/output-BLQWD6MJ.mjs +0 -950
  67. package/dist/remove-BeGdqroy.mjs +0 -194
  68. package/dist/report-DK4LGqwF.mjs +0 -129
  69. package/dist/send-C8tunWhv.mjs +0 -163
  70. package/dist/src-CCUKRX51.mjs +0 -1204
  71. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  72. package/dist/ui/assets/app-T7wVAhgx.js +0 -67
  73. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
  74. package/dist/usage-DYUmP0i4.mjs +0 -419
  75. package/dist/worktrees-DdMYoNOC.mjs +0 -212
@@ -0,0 +1,568 @@
1
+ import { $n as findFollowUp, Cn as followUpProblem, Kt as followUpListTable, Sn as deliverFollowUp, Zn as changeFollowUp, _n as postFollowUpMessage, bn as recordPublished, er as followUpActions, gr as runGh, qr as placeFor, tr as isGithubIssueUrl, yn as markDue } from "./src-BuF9Lbow.mjs";
2
+ import { a as refreshDue, i as followUpsRule } from "./context-DMK0U1w0.mjs";
3
+ import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
4
+ import { n as unknownProjectRefusal } from "./refusals-DQQX3AIb.mjs";
5
+ import { t as findRun } from "./find-Ct5CNl1J.mjs";
6
+ import { a as plain, c as write, l as writeErr, s as style } from "./output-DXZbnFt_.mjs";
7
+ import { n as flagValues } from "./short-flags-Qf21as0E.mjs";
8
+ import { t as refuseExtraArguments } from "./extra-arguments-BxQMXARO.mjs";
9
+ import { c as refuseUnknownFlags, i as readListArgs, n as filterArgs } from "./flags-C90N4J1i.mjs";
10
+ import { t as printList } from "./print-DzugGZQB.mjs";
11
+ import { t as withStore } from "./store-rTgRu8xy.mjs";
12
+ import { homedir } from "node:os";
13
+ import { defineCommand } from "citty";
14
+ // src/follow-ups/command.ts
15
+ /**
16
+ * @file What the `ork follow-ups` subcommands share: the flags, the project a
17
+ * call is about, and the due check every call runs first, in the user's
18
+ * store. A `gh` that fails only warns.
19
+ */
20
+ const json = {
21
+ type: "boolean",
22
+ description: "Print JSON",
23
+ default: false
24
+ };
25
+ const project = {
26
+ type: "string",
27
+ description: "A registered project's id (default: the one you are in)"
28
+ };
29
+ const idArg = {
30
+ type: "positional",
31
+ description: "A follow-up's id, or a prefix of one",
32
+ required: true
33
+ };
34
+ /** Says why, and makes the command exit 1. */
35
+ const fail = (message) => {
36
+ writeErr(message);
37
+ process.exitCode = 1;
38
+ };
39
+ /** The project a command is about, from `--project` or the folder. */
40
+ const placeOf = (store, key) => {
41
+ const projects = store.projects();
42
+ if (key === void 0) return placeFor(process.cwd(), projects);
43
+ const found = projects.find((p) => p.id === key || p.root === key);
44
+ if (found === void 0) {
45
+ fail(unknownProjectRefusal(key, projects.map((p) => p.id)));
46
+ return;
47
+ }
48
+ return placeFor(found.root, projects);
49
+ };
50
+ /** Runs `body` in the project, after the due check, in the user's store. */
51
+ const inProject = (key, body) => withStore(async (store) => {
52
+ const place = placeOf(store, key);
53
+ if (place === void 0) return;
54
+ await refreshDue(store, place.root, (line) => {
55
+ writeErr(style("dim", line));
56
+ });
57
+ await body(store, place);
58
+ });
59
+ /**
60
+ * Like `inProject`, for a list: `key` may be `all`, which gives every
61
+ * registered project's place. The due check runs for each.
62
+ */
63
+ const inProjects = (key, body) => withStore(async (store) => {
64
+ const projects = store.projects();
65
+ const places = key === "all" ? projects.map((p) => placeFor(p.root, projects)) : [placeOf(store, key)];
66
+ if (places.includes(void 0)) return;
67
+ const found = places.filter((place) => place !== void 0);
68
+ await Promise.all(found.map((place) => refreshDue(store, place.root, (line) => {
69
+ writeErr(style("dim", line));
70
+ })));
71
+ await body(store, found);
72
+ });
73
+ /** Runs `body` on the follow-up `id` names, or says why there is none. */
74
+ const withFollowUp = (args, body) => inProject(args.project, async (store, place) => {
75
+ const found = findFollowUp(store, place.root, args.id);
76
+ if (!found.ok) {
77
+ fail(found.error);
78
+ return;
79
+ }
80
+ await body(store, place, found.followUp);
81
+ });
82
+ /**
83
+ * The follow-ups several words name (`done a b`, `drop a,b`: a comma splits a
84
+ * word too), each once, or `undefined` after saying every word that failed and
85
+ * making the command exit 1; the caller then changes nothing.
86
+ */
87
+ const eachTarget = (words, resolve) => {
88
+ const named = [...new Set(words.flatMap((word) => word.split(",")).map((word) => word.trim()).filter((word) => word !== ""))];
89
+ if (named.length === 0) {
90
+ fail("Give a follow-up id.");
91
+ return;
92
+ }
93
+ const found = /* @__PURE__ */ new Map();
94
+ let failed = false;
95
+ for (const word of named) {
96
+ const one = resolve(word);
97
+ if (one.ok) for (const f of one.followUps) found.set(f.id, f);
98
+ else {
99
+ fail(one.error);
100
+ failed = true;
101
+ }
102
+ }
103
+ return failed ? void 0 : [...found.values()];
104
+ };
105
+ /**
106
+ * Like `withFollowUp`, for every id in `args._` (space or comma separated):
107
+ * `refused` says why one may not be changed, and any refusal stops the call.
108
+ */
109
+ const withFollowUps = (args, refused, body) => inProject(args.project, async (store, place) => {
110
+ const followUps = eachTarget(args._, (word) => {
111
+ const found = findFollowUp(store, place.root, word);
112
+ if (!found.ok) return found;
113
+ const why = refused(found.followUp);
114
+ return why === void 0 ? {
115
+ ok: true,
116
+ followUps: [found.followUp]
117
+ } : {
118
+ ok: false,
119
+ error: why
120
+ };
121
+ });
122
+ if (followUps !== void 0) await body(store, place, followUps);
123
+ });
124
+ // src/follow-ups/format.ts
125
+ /** The groups of the list, in the order they are shown. */
126
+ const GROUPS = [
127
+ {
128
+ status: "due",
129
+ title: "Due"
130
+ },
131
+ {
132
+ status: "waiting",
133
+ title: "Waiting"
134
+ },
135
+ {
136
+ status: "done",
137
+ title: "Done"
138
+ },
139
+ {
140
+ status: "dropped",
141
+ title: "Dropped"
142
+ }
143
+ ];
144
+ /** `agent` first, then the maintainer, other projects, then people. */
145
+ const FOR_ORDER = [
146
+ "agent",
147
+ "me",
148
+ "project:",
149
+ "person:"
150
+ ];
151
+ const forOrder = (value) => FOR_ORDER.findIndex((prefix) => value.startsWith(prefix));
152
+ /** The list's order within a status: by who it is for. */
153
+ const byFor = (a, b) => forOrder(a.for) - forOrder(b.for) || a.for.localeCompare(b.for);
154
+ /** A multi-line text, each line safe to print. */
155
+ const linesOf = (text) => text.split("\n").map((line) => plain(line));
156
+ const when = (f) => f.ref === void 0 ? f.due : `${f.due} ${f.ref}`;
157
+ /** One follow-up on one line; a short note when a run has taken it. */
158
+ const followUpLine = (f) => `${style("dim", f.id)} ${f.kind.padEnd(7)} ${plain(f.title)} ${style("dim", `(${plain(when(f))})`)}${f.takenBy === void 0 ? "" : style("dim", ` taken by ${plain(f.takenBy)}`)}`;
159
+ /** What to do with a draft: copy and send it, unless it is closed already. */
160
+ const draftHeading = (f, to) => f.status === "done" || f.status === "dropped" ? `Draft to ${to} (${f.status})` : `Draft to ${to}: copy it, send it, then \`ork follow-ups done ${f.id}\``;
161
+ /**
162
+ * The follow-ups grouped by status, then by `for`; the closed ones only with
163
+ * `all`. A line each, under a heading each.
164
+ */
165
+ const formatFollowUps = (followUps, all) => {
166
+ const lines = [];
167
+ for (const group of GROUPS) {
168
+ if (!all && group.status !== "due" && group.status !== "waiting") continue;
169
+ const inGroup = followUps.filter((f) => f.status === group.status).toSorted(byFor);
170
+ if (inGroup.length === 0) continue;
171
+ if (lines.length > 0) lines.push("");
172
+ lines.push(style("bold", `${group.title} (${String(inGroup.length)})`));
173
+ for (const kind of new Set(inGroup.map((f) => f.for))) {
174
+ lines.push(` ${style("cyan", `for ${plain(kind)}`)}`);
175
+ for (const f of inGroup.filter((one) => one.for === kind)) lines.push(` ${followUpLine(f)}`);
176
+ }
177
+ }
178
+ return lines.length === 0 ? [style("dim", "No follow-ups.")] : lines;
179
+ };
180
+ const fromLine = (f) => f.from.run === void 0 ? [] : [`from run ${plain(f.from.run)}${f.from.node === void 0 ? "" : `, node ${plain(f.from.node)}`}`];
181
+ /** One follow-up whole: its record, its draft and its steps, ready to copy. */
182
+ const formatFollowUp = (f) => [
183
+ `${style("bold", plain(f.title))} ${style("dim", f.id)}`,
184
+ `status ${f.status}${f.note === void 0 ? "" : ` (${plain(f.note)})`}, for ${plain(f.for)}, ${f.kind}, due ${plain(f.due)}`,
185
+ ...f.ref === void 0 ? [] : [`waits on ${plain(f.ref)}`],
186
+ ...f.takenBy === void 0 ? [] : [`taken by run ${plain(f.takenBy)}`],
187
+ ...fromLine(f),
188
+ ...f.where.length === 0 ? [] : [`files ${f.where.map((file) => plain(file)).join(", ")}`],
189
+ ...f.url === void 0 ? [] : [`kept at ${plain(f.url)}`],
190
+ ...f.body === "" ? [] : ["", ...linesOf(f.body)],
191
+ ...f.draft === void 0 ? [] : [
192
+ "",
193
+ style("bold", draftHeading(f, `${plain(f.draft.to)}${f.draft.channel === void 0 ? "" : ` (${plain(f.draft.channel)})`}`)),
194
+ ...linesOf(f.draft.text).map((line) => ` ${line}`)
195
+ ],
196
+ ...f.steps === void 0 ? [] : [
197
+ "",
198
+ style("bold", "Steps (Ork never runs them):"),
199
+ ...f.steps.flatMap((step, index) => [` ${String(index + 1)}. ${plain(step.text)}`, ...step.command === void 0 ? [] : [` ${plain(step.command)}`]])
200
+ ]
201
+ ];
202
+ // src/commands/follow-ups-act.ts
203
+ /**
204
+ * @file What `ork follow-ups` does to one follow-up: done (a message: sent),
205
+ * drop, reopen, make due, post a drafted GitHub comment, and `published`,
206
+ * which a workflow's step calls once its change is out. Posting goes through
207
+ * the person's own `gh` and only with `--confirm`, after showing the exact
208
+ * text; Ork never runs a task's commands.
209
+ */
210
+ /**
211
+ * `done`, `drop` or `reopen`: one change of status for each id given (space or
212
+ * comma separated), or why one can't be; any refusal changes none.
213
+ */
214
+ const closing = (action, description, verb) => defineCommand({
215
+ meta: {
216
+ name: action,
217
+ description
218
+ },
219
+ args: {
220
+ id: {
221
+ ...idArg,
222
+ description: "A follow-up's id, or a prefix of one; several allowed, space or comma separated"
223
+ },
224
+ project
225
+ },
226
+ run: ({ args }) => withFollowUps(args, (followUp) => {
227
+ const allowed = followUpActions(followUp)[action];
228
+ return allowed.ok ? void 0 : allowed.error;
229
+ }, (store, _place, followUps) => {
230
+ for (const followUp of followUps) {
231
+ const changed = changeFollowUp(store, followUp, action);
232
+ if (!changed.ok) {
233
+ fail(changed.error);
234
+ continue;
235
+ }
236
+ write(`${style("green", "✓")} ${verb} ${changed.followUp.id}: ${plain(changed.followUp.title)}`);
237
+ }
238
+ })
239
+ });
240
+ const due = defineCommand({
241
+ meta: {
242
+ name: "due",
243
+ description: "Make a waiting follow-up due now, or all of a run's (after a deploy, say)"
244
+ },
245
+ args: {
246
+ target: {
247
+ type: "positional",
248
+ description: "A follow-up's id (or a prefix), or a run id; several allowed, space or comma separated",
249
+ required: true
250
+ },
251
+ project
252
+ },
253
+ run: ({ args }) => inProject(args.project, (store, place) => {
254
+ const waiting = eachTarget(args._, (target) => {
255
+ const one = findFollowUp(store, place.root, target);
256
+ const run = findRun(store, target, "");
257
+ if (!one.ok && !run.ok) return {
258
+ ok: false,
259
+ error: one.error
260
+ };
261
+ const runId = run.ok ? run.run.id : target;
262
+ const ready = (one.ok ? [one.followUp] : store.followUps(place.root).filter((f) => f.from.run === runId)).filter((f) => f.status === "waiting");
263
+ return ready.length === 0 ? {
264
+ ok: false,
265
+ error: `Nothing waiting under \`${target}\`: give a waiting follow-up's id, or a run that left some.`
266
+ } : {
267
+ ok: true,
268
+ followUps: ready
269
+ };
270
+ });
271
+ for (const f of waiting ?? []) for (const marked of markDue(store, place.root, f.id)) write(`${style("green", "✓")} due ${marked.id}: ${plain(marked.title)}`);
272
+ })
273
+ });
274
+ const post = defineCommand({
275
+ meta: {
276
+ name: "post",
277
+ description: "Post a message drafted for GitHub, as a comment through your own gh, with --confirm; without it, show what would be posted"
278
+ },
279
+ plugins: [refuseExtraArguments],
280
+ args: {
281
+ id: idArg,
282
+ confirm: {
283
+ type: "boolean",
284
+ description: "Yes: post exactly this text",
285
+ default: false
286
+ },
287
+ project
288
+ },
289
+ run: ({ args }) => withFollowUp(args, async (store, _place, followUp) => {
290
+ const allowed = followUpActions(followUp).post;
291
+ if (!allowed.ok) {
292
+ fail(allowed.error);
293
+ return;
294
+ }
295
+ const draft = followUp.draft;
296
+ if (!args.confirm && draft !== void 0) {
297
+ write(`Would post to ${plain(draft.to)} through your gh:`);
298
+ for (const line of draft.text.split("\n")) write(` ${plain(line)}`);
299
+ writeErr("Run again with --confirm to post it.");
300
+ return;
301
+ }
302
+ const posted = await postFollowUpMessage(followUp, {
303
+ rows: store,
304
+ gh: runGh,
305
+ confirmed: args.confirm,
306
+ home: homedir()
307
+ });
308
+ if (!posted.ok) {
309
+ fail(posted.error);
310
+ return;
311
+ }
312
+ write(`${style("green", "✓")} posted${posted.url === void 0 ? "" : ` ${posted.url}`}; done ${followUp.id}`);
313
+ })
314
+ });
315
+ const published = defineCommand({
316
+ meta: {
317
+ name: "published",
318
+ description: "Record that this run published a pull request or branch (a workflow step calls it): its follow-ups wait on it, and the ones it took are done"
319
+ },
320
+ plugins: [refuseExtraArguments],
321
+ args: { ref: {
322
+ type: "positional",
323
+ description: "The pull request URL or the branch",
324
+ required: true
325
+ } },
326
+ run: ({ args }) => withStore((store) => {
327
+ const runId = process.env["ORK_RUN"];
328
+ if (runId === void 0 || runId === "") {
329
+ fail("ork follow-ups published runs inside a run: ORK_RUN is not set.");
330
+ return;
331
+ }
332
+ const found = findRun(store, runId, "");
333
+ if (!found.ok) {
334
+ fail(found.error);
335
+ return;
336
+ }
337
+ const callers = [];
338
+ for (let parent = found.run.parent; parent !== void 0 && !callers.includes(parent); parent = store.lookup(parent).find((r) => r.id === parent)?.parent) callers.push(parent);
339
+ const recorded = recordPublished(store, {
340
+ project: found.run.project,
341
+ run: found.run.id,
342
+ ref: args.ref,
343
+ callers
344
+ });
345
+ write(`${style("green", "✓")} ${String(recorded.referenced.length)} follow-up(s) now wait on ${plain(args.ref)}; ${String(recorded.done.length)} done`);
346
+ })
347
+ });
348
+ // src/commands/follow-ups-add.ts
349
+ /**
350
+ * @file `ork follow-ups add`: a follow-up the maintainer adds by hand, with its
351
+ * `for`, `kind` and `due`, a message's draft or a task's steps. It is kept
352
+ * where the project's rule says, and one for another project goes into that
353
+ * project's list and, when a run is live there, to its session.
354
+ */
355
+ /** `text :: command` as a step; the command is optional. */
356
+ const stepOf = (raw) => {
357
+ const [text = "", command] = raw.split(" :: ", 2);
358
+ return command === void 0 ? { text: text.trim() } : {
359
+ text: text.trim(),
360
+ command: command.trim()
361
+ };
362
+ };
363
+ const defaultKind = (value) => {
364
+ if (value.startsWith("person:")) return "message";
365
+ return value === "agent" || value.startsWith("project:") ? "change" : "task";
366
+ };
367
+ const isKind = (value) => [
368
+ "change",
369
+ "message",
370
+ "task",
371
+ "check"
372
+ ].includes(value);
373
+ const isFor = (value) => /^(?:agent|me|project:.+|person:.+)$/u.test(value);
374
+ const isDue = (value) => /^(?:now|after-merge|after-deploy|\d+-\d+-\d+)$/u.test(value);
375
+ const add = defineCommand({
376
+ meta: {
377
+ name: "add",
378
+ description: "Add a follow-up: --for agent|me|project:<name>|person:<name>, --kind, --due"
379
+ },
380
+ args: {
381
+ title: {
382
+ type: "positional",
383
+ description: "What it is",
384
+ required: true
385
+ },
386
+ for: {
387
+ type: "string",
388
+ description: "agent, me, project:<name> or person:<name> (default: me)"
389
+ },
390
+ kind: {
391
+ type: "string",
392
+ description: "change, message, task or check (default: by --for)"
393
+ },
394
+ due: {
395
+ type: "string",
396
+ description: "now, after-merge, after-deploy or a YYYY-MM-DD date (default: now)"
397
+ },
398
+ body: {
399
+ type: "string",
400
+ description: "What and why"
401
+ },
402
+ where: {
403
+ type: "string",
404
+ description: "A file it is about (repeatable)"
405
+ },
406
+ ref: {
407
+ type: "string",
408
+ description: "The PR or branch it waits on"
409
+ },
410
+ to: {
411
+ type: "string",
412
+ description: "A message: who it is to (default: the person); for --channel github, the issue or PR URL"
413
+ },
414
+ channel: {
415
+ type: "string",
416
+ description: "A message: slack, email, github…"
417
+ },
418
+ text: {
419
+ type: "string",
420
+ description: "A message: its text, to send"
421
+ },
422
+ step: {
423
+ type: "string",
424
+ description: "A task's step, `text` or `text :: command` (repeatable); Ork never runs it"
425
+ },
426
+ project,
427
+ json
428
+ },
429
+ run: ({ args, rawArgs }) => inProject(args.project, async (store, place) => {
430
+ const recipient = args.for ?? "me";
431
+ const kind = args.kind ?? defaultKind(recipient);
432
+ if (!isKind(kind)) {
433
+ fail("--kind is change, message, task or check.");
434
+ return;
435
+ }
436
+ const due = args.due ?? "now";
437
+ if (!isFor(recipient) || !isDue(due)) {
438
+ fail(isFor(recipient) ? "--due is now, after-merge, after-deploy or a YYYY-MM-DD date." : "--for is agent, me, project:<name> or person:<name>.");
439
+ return;
440
+ }
441
+ if (kind === "message" && args.text === void 0) {
442
+ fail("a message needs --text.");
443
+ return;
444
+ }
445
+ if (args.channel === "github" && !isGithubIssueUrl(args.to ?? "")) {
446
+ fail("--channel github needs --to <the issue or pull request URL>, https://github.com/<owner>/<repo>/(issues|pull)/<n>.");
447
+ return;
448
+ }
449
+ const steps = flagValues(rawArgs, "step").map((raw) => stepOf(raw));
450
+ const input = {
451
+ title: args.title,
452
+ for: recipient,
453
+ kind,
454
+ due,
455
+ where: flagValues(rawArgs, "where"),
456
+ ...args.body === void 0 ? {} : { body: args.body },
457
+ ...args.ref === void 0 ? {} : { ref: args.ref },
458
+ ...args.text === void 0 ? {} : { draft: {
459
+ to: args.to ?? recipient.replace(/^person:/u, ""),
460
+ ...args.channel === void 0 ? {} : { channel: args.channel },
461
+ text: args.text
462
+ } },
463
+ ...steps.length === 0 ? {} : { steps }
464
+ };
465
+ const problem = followUpProblem(input);
466
+ if (problem !== null) {
467
+ fail(`${problem}.`);
468
+ return;
469
+ }
470
+ const rule = followUpsRule(place);
471
+ const delivered = await deliverFollowUp(input, {
472
+ store,
473
+ project: place.root,
474
+ ...rule === void 0 ? {} : { rule }
475
+ });
476
+ if (!delivered.ok) {
477
+ fail(delivered.error);
478
+ return;
479
+ }
480
+ if (args.json) {
481
+ write(JSON.stringify(delivered, void 0, 2));
482
+ return;
483
+ }
484
+ const { added, relayed } = delivered;
485
+ const { followUp } = added;
486
+ write(`${style("green", "✓")} ${added.merged ? "merged into" : "added"} ${followUp.id}: ${plain(followUp.title)}`);
487
+ if (added.note !== void 0) writeErr(style("dim", added.note));
488
+ if (added.merged && input.draft !== void 0 && followUp.draft !== void 0 && input.draft.text !== followUp.draft.text) write(style("dim", ` the new --text was not kept: ${followUp.id} keeps its first draft (see \`ork follow-ups show ${followUp.id}\`)`));
489
+ if (relayed !== void 0) write(style("dim", ` sent to ${relayed.node} of run ${relayed.run}, which is live in that project`));
490
+ })
491
+ });
492
+ // src/commands/follow-ups.ts
493
+ /**
494
+ * @file `ork follow-ups`: what shipped changes left for later, apart from
495
+ * feedback (specs/follow-ups.md): list and show them, add one, and mark one
496
+ * done, dropped, reopened or due, or post its drafted GitHub comment (in
497
+ * `follow-ups-act.ts`). Every call first moves what has come due, reading
498
+ * pull requests through the person's own `gh`; a `gh` that fails only warns.
499
+ */
500
+ /** What a list with no `--status` shows. */
501
+ const OPEN = /* @__PURE__ */ new Set(["due", "waiting"]);
502
+ /** The list's own order: due first, then waiting, done, dropped; then `byFor`. */
503
+ const RANK = {
504
+ due: 0,
505
+ waiting: 1,
506
+ done: 2,
507
+ dropped: 3
508
+ };
509
+ const followUps = defineCommand({
510
+ meta: {
511
+ name: "follow-ups",
512
+ description: "What shipped changes left for later: list, show, add, done, drop, reopen, due, post, published"
513
+ },
514
+ subCommands: {
515
+ list: defineCommand({
516
+ meta: {
517
+ name: "list",
518
+ description: "List the follow-ups, due first, then by who they are for"
519
+ },
520
+ plugins: [refuseUnknownFlags, refuseExtraArguments],
521
+ args: filterArgs(followUpListTable),
522
+ run: async ({ args }) => {
523
+ const given = args;
524
+ const asked = readListArgs(followUpListTable, given);
525
+ if (asked === void 0) return;
526
+ const { filter, limit } = asked;
527
+ const status = filter["status"];
528
+ await inProjects(filter["project"], (store, places) => {
529
+ const target = filter["for"]?.match(/^project:(.*)$/u)?.[1];
530
+ const ids = store.projects().map((p) => p.id);
531
+ if (target !== void 0 && !ids.includes(target)) {
532
+ fail(unknownProjectRefusal(target, ids));
533
+ return;
534
+ }
535
+ const stored = places.flatMap((place) => store.followUps(place.root));
536
+ const shown = applyFilters(followUpListTable, stored, filter).filter((f) => status !== void 0 || OPEN.has(f.status)).toSorted((a, b) => RANK[a.status] - RANK[b.status] || byFor(a, b)).slice(0, limit);
537
+ printList(shown, followUpListTable, given, (rows) => {
538
+ for (const line of formatFollowUps(rows, status !== void 0)) write(line);
539
+ });
540
+ });
541
+ }
542
+ }),
543
+ show: defineCommand({
544
+ meta: {
545
+ name: "show",
546
+ description: "Show a follow-up whole, with its draft and its steps to copy"
547
+ },
548
+ plugins: [refuseExtraArguments],
549
+ args: {
550
+ id: idArg,
551
+ project,
552
+ json
553
+ },
554
+ run: ({ args }) => withFollowUp(args, (_store, _place, followUp) => {
555
+ if (args.json) write(JSON.stringify(followUp, void 0, 2));
556
+ else for (const line of formatFollowUp(followUp)) write(line);
557
+ })
558
+ }),
559
+ add,
560
+ done: closing("done", "Mark a follow-up done; a message is done once you have sent it", "done"),
561
+ drop: closing("drop", "Drop a follow-up that no longer applies", "dropped"),
562
+ reopen: closing("reopen", "Bring a done or dropped follow-up back", "reopened"),
563
+ due,
564
+ post,
565
+ published
566
+ }
567
+ });
568
+ export { followUps };