@krapht/ork 1.0.0-alpha.14 → 1.0.0-alpha.16

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 (48) hide show
  1. package/README.md +10 -4
  2. package/dist/__build_info__-DcRdK0Du.mjs +359 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
  5. package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
  6. package/dist/bin.mjs +328 -1734
  7. package/dist/brief-BvMtIFio.mjs +58 -0
  8. package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
  11. package/dist/context-BN9A2Nnu.mjs +106 -0
  12. package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
  13. package/dist/flags-CdgIH1L6.mjs +230 -0
  14. package/dist/follow-ups-DxCsOby1.mjs +496 -0
  15. package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
  16. package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
  17. package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
  18. package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
  19. package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
  20. package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
  21. package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
  22. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  23. package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
  24. package/dist/post-C9soCx7i.mjs +46 -0
  25. package/dist/print-BaWiWf4v.mjs +19 -0
  26. package/dist/project-records-BRiyQzUo.mjs +230 -0
  27. package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
  28. package/dist/run-B-j3Qxqc.mjs +1464 -0
  29. package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
  30. package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
  31. package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
  32. package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
  33. package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
  34. package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
  35. package/dist/store-Dyr1mP8K.mjs +16 -0
  36. package/dist/ui/assets/app-BPSCxb_l.css +1 -0
  37. package/dist/ui/assets/app-CnNRowFn.js +111 -0
  38. package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
  41. package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
  42. package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
  43. package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
  44. package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
  45. package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/ui/assets/app-D_nAPnHM.css +0 -1
  48. package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
@@ -0,0 +1,496 @@
1
+ import { Pr as placeFor, Vt as followUpListTable, _n as deliverFollowUp, mn as recordPublished, pn as markDue, tr as runGh, vn as followUpProblem } from "./src-DN-RPsil.mjs";
2
+ import { d as followUpsRule, f as refreshDue, n as flagValues } from "./run-B-j3Qxqc.mjs";
3
+ import { g as changeFollowUp, v as findFollowUp, x as isGithubIssueUrl, y as followUpActions } from "./__build_info__-DcRdK0Du.mjs";
4
+ import { t as postFollowUpMessage } from "./post-C9soCx7i.mjs";
5
+ import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
6
+ import { i as readListArgs, n as filterArgs, o as allAlias } from "./flags-CdgIH1L6.mjs";
7
+ import { t as findRun } from "./find-mLYsR0uC.mjs";
8
+ import { a as plain, c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
9
+ import { t as printList } from "./print-BaWiWf4v.mjs";
10
+ import { t as withStore } from "./store-Dyr1mP8K.mjs";
11
+ import { homedir } from "node:os";
12
+ import { defineCommand } from "citty";
13
+ // src/follow-ups/command.ts
14
+ /**
15
+ * @file What the `ork follow-ups` subcommands share: the flags, the project a
16
+ * call is about, and the due check every call runs first, in the user's
17
+ * store. A `gh` that fails only warns.
18
+ */
19
+ const json = {
20
+ type: "boolean",
21
+ description: "Print JSON",
22
+ default: false
23
+ };
24
+ const project = {
25
+ type: "string",
26
+ description: "A registered project's id (default: the one you are in)"
27
+ };
28
+ const idArg = {
29
+ type: "positional",
30
+ description: "A follow-up's id, or a prefix of one",
31
+ required: true
32
+ };
33
+ /** Says why, and makes the command exit 1. */
34
+ const fail = (message) => {
35
+ writeErr(message);
36
+ process.exitCode = 1;
37
+ };
38
+ /** The project a command is about, from `--project` or the folder. */
39
+ const placeOf = (store, key) => {
40
+ const projects = store.projects();
41
+ if (key === void 0) return placeFor(process.cwd(), projects);
42
+ const found = projects.find((p) => p.id === key || p.root === key);
43
+ if (found === void 0) {
44
+ const names = projects.map((p) => p.id);
45
+ fail(`No registered project \`${key}\`; registered: ${names.length === 0 ? "none" : names.join(", ")}.`);
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
+ // src/follow-ups/format.ts
83
+ /** The groups of the list, in the order they are shown. */
84
+ const GROUPS = [
85
+ {
86
+ status: "due",
87
+ title: "Due"
88
+ },
89
+ {
90
+ status: "waiting",
91
+ title: "Waiting"
92
+ },
93
+ {
94
+ status: "done",
95
+ title: "Done"
96
+ },
97
+ {
98
+ status: "dropped",
99
+ title: "Dropped"
100
+ }
101
+ ];
102
+ /** `agent` first, then the maintainer, other projects, then people. */
103
+ const FOR_ORDER = [
104
+ "agent",
105
+ "me",
106
+ "project:",
107
+ "person:"
108
+ ];
109
+ const forOrder = (value) => FOR_ORDER.findIndex((prefix) => value.startsWith(prefix));
110
+ /** The list's order within a status: by who it is for. */
111
+ const byFor = (a, b) => forOrder(a.for) - forOrder(b.for) || a.for.localeCompare(b.for);
112
+ /** A multi-line text, each line safe to print. */
113
+ const linesOf = (text) => text.split("\n").map((line) => plain(line));
114
+ const when = (f) => f.ref === void 0 ? f.due : `${f.due} ${f.ref}`;
115
+ /** One follow-up on one line. */
116
+ const followUpLine = (f) => `${style("dim", f.id)} ${f.kind.padEnd(7)} ${plain(f.title)} ${style("dim", `(${plain(when(f))})`)}`;
117
+ /**
118
+ * The follow-ups grouped by status, then by `for`; the closed ones only with
119
+ * `all`. A line each, under a heading each.
120
+ */
121
+ const formatFollowUps = (followUps, all) => {
122
+ const lines = [];
123
+ for (const group of GROUPS) {
124
+ if (!all && group.status !== "due" && group.status !== "waiting") continue;
125
+ const inGroup = followUps.filter((f) => f.status === group.status).toSorted(byFor);
126
+ if (inGroup.length === 0) continue;
127
+ if (lines.length > 0) lines.push("");
128
+ lines.push(style("bold", `${group.title} (${String(inGroup.length)})`));
129
+ for (const kind of new Set(inGroup.map((f) => f.for))) {
130
+ lines.push(` ${style("cyan", `for ${plain(kind)}`)}`);
131
+ for (const f of inGroup.filter((one) => one.for === kind)) lines.push(` ${followUpLine(f)}`);
132
+ }
133
+ }
134
+ return lines.length === 0 ? [style("dim", "No follow-ups.")] : lines;
135
+ };
136
+ const fromLine = (f) => f.from.run === void 0 ? [] : [`from run ${plain(f.from.run)}${f.from.node === void 0 ? "" : `, node ${plain(f.from.node)}`}`];
137
+ /** One follow-up whole: its record, its draft and its steps, ready to copy. */
138
+ const formatFollowUp = (f) => [
139
+ `${style("bold", plain(f.title))} ${style("dim", f.id)}`,
140
+ `status ${f.status}${f.note === void 0 ? "" : ` (${plain(f.note)})`}, for ${plain(f.for)}, ${f.kind}, due ${plain(f.due)}`,
141
+ ...f.ref === void 0 ? [] : [`waits on ${plain(f.ref)}`],
142
+ ...fromLine(f),
143
+ ...f.where.length === 0 ? [] : [`files ${f.where.map((file) => plain(file)).join(", ")}`],
144
+ ...f.url === void 0 ? [] : [`kept at ${plain(f.url)}`],
145
+ ...f.body === "" ? [] : ["", ...linesOf(f.body)],
146
+ ...f.draft === void 0 ? [] : [
147
+ "",
148
+ style("bold", `Draft to ${plain(f.draft.to)}${f.draft.channel === void 0 ? "" : ` (${plain(f.draft.channel)})`}: copy it, send it, then \`ork follow-ups done ${f.id}\``),
149
+ ...linesOf(f.draft.text).map((line) => ` ${line}`)
150
+ ],
151
+ ...f.steps === void 0 ? [] : [
152
+ "",
153
+ style("bold", "Steps (Ork never runs them):"),
154
+ ...f.steps.flatMap((step, index) => [` ${String(index + 1)}. ${plain(step.text)}`, ...step.command === void 0 ? [] : [` ${plain(step.command)}`]])
155
+ ]
156
+ ];
157
+ // src/commands/follow-ups-act.ts
158
+ /**
159
+ * @file What `ork follow-ups` does to one follow-up: done (a message: sent),
160
+ * drop, reopen, make due, post a drafted GitHub comment, and `published`,
161
+ * which a workflow's step calls once its change is out. Posting goes through
162
+ * the person's own `gh` and only with `--confirm`, after showing the exact
163
+ * text; Ork never runs a task's commands.
164
+ */
165
+ /** `done`, `drop` or `reopen`: one change of status, or why it can't be. */
166
+ const closing = (action, description, verb) => defineCommand({
167
+ meta: {
168
+ name: action,
169
+ description
170
+ },
171
+ args: {
172
+ id: idArg,
173
+ project
174
+ },
175
+ run: ({ args }) => withFollowUp(args, (store, _place, followUp) => {
176
+ const changed = changeFollowUp(store, followUp, action);
177
+ if (!changed.ok) {
178
+ fail(changed.error);
179
+ return;
180
+ }
181
+ write(`${style("green", "✓")} ${verb} ${changed.followUp.id}: ${plain(changed.followUp.title)}`);
182
+ })
183
+ });
184
+ const due = defineCommand({
185
+ meta: {
186
+ name: "due",
187
+ description: "Make a waiting follow-up due now, or all of a run's (after a deploy, say)"
188
+ },
189
+ args: {
190
+ target: {
191
+ type: "positional",
192
+ description: "A follow-up's id (or a prefix), or a run id",
193
+ required: true
194
+ },
195
+ project
196
+ },
197
+ run: ({ args }) => inProject(args.project, (store, place) => {
198
+ const one = findFollowUp(store, place.root, args.target);
199
+ const run = findRun(store, args.target, "");
200
+ const runId = run.ok ? run.run.id : args.target;
201
+ const key = one.ok ? one.followUp.id : runId;
202
+ const marked = markDue(store, place.root, key);
203
+ if (marked.length === 0) {
204
+ fail(`Nothing waiting under \`${args.target}\`: give a waiting follow-up's id, or a run that left some.`);
205
+ return;
206
+ }
207
+ for (const f of marked) write(`${style("green", "✓")} due ${f.id}: ${plain(f.title)}`);
208
+ })
209
+ });
210
+ const post = defineCommand({
211
+ meta: {
212
+ name: "post",
213
+ description: "Post a message drafted for GitHub, as a comment through your own gh, with --confirm; without it, show what would be posted"
214
+ },
215
+ args: {
216
+ id: idArg,
217
+ confirm: {
218
+ type: "boolean",
219
+ description: "Yes: post exactly this text",
220
+ default: false
221
+ },
222
+ project
223
+ },
224
+ run: ({ args }) => withFollowUp(args, async (store, _place, followUp) => {
225
+ const allowed = followUpActions(followUp).post;
226
+ if (!allowed.ok) {
227
+ fail(allowed.error);
228
+ return;
229
+ }
230
+ const draft = followUp.draft;
231
+ if (!args.confirm && draft !== void 0) {
232
+ write(`Would post to ${plain(draft.to)} through your gh:`);
233
+ for (const line of draft.text.split("\n")) write(` ${plain(line)}`);
234
+ writeErr("Run again with --confirm to post it.");
235
+ return;
236
+ }
237
+ const posted = await postFollowUpMessage(followUp, {
238
+ rows: store,
239
+ gh: runGh,
240
+ confirmed: args.confirm,
241
+ home: homedir()
242
+ });
243
+ if (!posted.ok) {
244
+ fail(posted.error);
245
+ return;
246
+ }
247
+ write(`${style("green", "✓")} posted${posted.url === void 0 ? "" : ` ${posted.url}`}; done ${followUp.id}`);
248
+ })
249
+ });
250
+ const published = defineCommand({
251
+ meta: {
252
+ name: "published",
253
+ 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"
254
+ },
255
+ args: { ref: {
256
+ type: "positional",
257
+ description: "The pull request URL or the branch",
258
+ required: true
259
+ } },
260
+ run: ({ args }) => withStore((store) => {
261
+ const runId = process.env["ORK_RUN"];
262
+ if (runId === void 0 || runId === "") {
263
+ fail("ork follow-ups published runs inside a run: ORK_RUN is not set.");
264
+ return;
265
+ }
266
+ const found = findRun(store, runId, "");
267
+ if (!found.ok) {
268
+ fail(found.error);
269
+ return;
270
+ }
271
+ const callers = [];
272
+ 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);
273
+ const recorded = recordPublished(store, {
274
+ project: found.run.project,
275
+ run: found.run.id,
276
+ ref: args.ref,
277
+ callers
278
+ });
279
+ write(`${style("green", "✓")} ${String(recorded.referenced.length)} follow-up(s) now wait on ${plain(args.ref)}; ${String(recorded.done.length)} done`);
280
+ })
281
+ });
282
+ // src/commands/follow-ups-add.ts
283
+ /**
284
+ * @file `ork follow-ups add`: a follow-up the maintainer adds by hand, with its
285
+ * `for`, `kind` and `due`, a message's draft or a task's steps. It is kept
286
+ * where the project's rule says, and one for another project goes into that
287
+ * project's list and, when a run is live there, to its session.
288
+ */
289
+ /** `text :: command` as a step; the command is optional. */
290
+ const stepOf = (raw) => {
291
+ const [text = "", command] = raw.split(" :: ", 2);
292
+ return command === void 0 ? { text: text.trim() } : {
293
+ text: text.trim(),
294
+ command: command.trim()
295
+ };
296
+ };
297
+ const defaultKind = (value) => {
298
+ if (value.startsWith("person:")) return "message";
299
+ return value === "agent" || value.startsWith("project:") ? "change" : "task";
300
+ };
301
+ const isKind = (value) => [
302
+ "change",
303
+ "message",
304
+ "task",
305
+ "check"
306
+ ].includes(value);
307
+ const isFor = (value) => /^(?:agent|me|project:.+|person:.+)$/u.test(value);
308
+ const isDue = (value) => /^(?:now|after-merge|after-deploy|\d+-\d+-\d+)$/u.test(value);
309
+ const add = defineCommand({
310
+ meta: {
311
+ name: "add",
312
+ description: "Add a follow-up: --for agent|me|project:<name>|person:<name>, --kind, --due"
313
+ },
314
+ args: {
315
+ title: {
316
+ type: "positional",
317
+ description: "What it is",
318
+ required: true
319
+ },
320
+ for: {
321
+ type: "string",
322
+ description: "agent, me, project:<name> or person:<name> (default: me)"
323
+ },
324
+ kind: {
325
+ type: "string",
326
+ description: "change, message, task or check (default: by --for)"
327
+ },
328
+ due: {
329
+ type: "string",
330
+ description: "now, after-merge, after-deploy or a YYYY-MM-DD date (default: now)"
331
+ },
332
+ body: {
333
+ type: "string",
334
+ description: "What and why"
335
+ },
336
+ where: {
337
+ type: "string",
338
+ description: "A file it is about (repeatable)"
339
+ },
340
+ ref: {
341
+ type: "string",
342
+ description: "The PR or branch it waits on"
343
+ },
344
+ to: {
345
+ type: "string",
346
+ description: "A message: who it is to (default: the person); for --channel github, the issue or PR URL"
347
+ },
348
+ channel: {
349
+ type: "string",
350
+ description: "A message: slack, email, github…"
351
+ },
352
+ text: {
353
+ type: "string",
354
+ description: "A message: its text, to send"
355
+ },
356
+ step: {
357
+ type: "string",
358
+ description: "A task's step, `text` or `text :: command` (repeatable); Ork never runs it"
359
+ },
360
+ project,
361
+ json
362
+ },
363
+ run: ({ args, rawArgs }) => inProject(args.project, async (store, place) => {
364
+ const recipient = args.for ?? "me";
365
+ const kind = args.kind ?? defaultKind(recipient);
366
+ if (!isKind(kind)) {
367
+ fail("--kind is change, message, task or check.");
368
+ return;
369
+ }
370
+ const due = args.due ?? "now";
371
+ if (!isFor(recipient) || !isDue(due)) {
372
+ 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>.");
373
+ return;
374
+ }
375
+ if (kind === "message" && args.text === void 0) {
376
+ fail("a message needs --text.");
377
+ return;
378
+ }
379
+ if (args.channel === "github" && !isGithubIssueUrl(args.to ?? "")) {
380
+ fail("--channel github needs --to <the issue or pull request URL>, https://github.com/<owner>/<repo>/(issues|pull)/<n>.");
381
+ return;
382
+ }
383
+ const steps = flagValues(rawArgs, "--step").map((raw) => stepOf(raw));
384
+ const input = {
385
+ title: args.title,
386
+ for: recipient,
387
+ kind,
388
+ due,
389
+ where: flagValues(rawArgs, "--where"),
390
+ ...args.body === void 0 ? {} : { body: args.body },
391
+ ...args.ref === void 0 ? {} : { ref: args.ref },
392
+ ...args.text === void 0 ? {} : { draft: {
393
+ to: args.to ?? recipient.replace(/^person:/u, ""),
394
+ ...args.channel === void 0 ? {} : { channel: args.channel },
395
+ text: args.text
396
+ } },
397
+ ...steps.length === 0 ? {} : { steps }
398
+ };
399
+ const problem = followUpProblem(input);
400
+ if (problem !== null) {
401
+ fail(`${problem}.`);
402
+ return;
403
+ }
404
+ const rule = followUpsRule(place);
405
+ const delivered = await deliverFollowUp(input, {
406
+ store,
407
+ project: place.root,
408
+ ...rule === void 0 ? {} : { rule }
409
+ });
410
+ if (!delivered.ok) {
411
+ fail(delivered.error);
412
+ return;
413
+ }
414
+ if (args.json) {
415
+ write(JSON.stringify(delivered, void 0, 2));
416
+ return;
417
+ }
418
+ const { added, relayed } = delivered;
419
+ const { followUp } = added;
420
+ write(`${style("green", "✓")} ${added.merged ? "merged into" : "added"} ${followUp.id}: ${plain(followUp.title)}`);
421
+ if (added.note !== void 0) writeErr(style("dim", added.note));
422
+ if (relayed !== void 0) write(style("dim", ` sent to ${relayed.node} of run ${relayed.run}, which is live in that project`));
423
+ })
424
+ });
425
+ // src/commands/follow-ups.ts
426
+ /**
427
+ * @file `ork follow-ups`: what shipped changes left for later, apart from
428
+ * feedback (specs/follow-ups.md): list and show them, add one, and mark one
429
+ * done, dropped, reopened or due, or post its drafted GitHub comment (in
430
+ * `follow-ups-act.ts`). Every call first moves what has come due, reading
431
+ * pull requests through the person's own `gh`; a `gh` that fails only warns.
432
+ */
433
+ /** What a list with no `--status` shows. */
434
+ const OPEN = /* @__PURE__ */ new Set(["due", "waiting"]);
435
+ /** The list's own order: due first, then waiting, done, dropped; then `byFor`. */
436
+ const RANK = {
437
+ due: 0,
438
+ waiting: 1,
439
+ done: 2,
440
+ dropped: 3
441
+ };
442
+ const followUps = defineCommand({
443
+ meta: {
444
+ name: "follow-ups",
445
+ description: "What shipped changes left for later: list, show, add, done, drop, reopen, due, post, published"
446
+ },
447
+ subCommands: {
448
+ list: defineCommand({
449
+ meta: {
450
+ name: "list",
451
+ description: "List the follow-ups, due first, then by who they are for"
452
+ },
453
+ args: {
454
+ ...filterArgs(followUpListTable),
455
+ ...allAlias
456
+ },
457
+ run: async ({ args }) => {
458
+ const given = args;
459
+ const asked = readListArgs(followUpListTable, given, { all: "status" });
460
+ if (asked === void 0) return;
461
+ const { filter, limit } = asked;
462
+ const status = filter["status"];
463
+ await inProjects(filter["project"], (store, places) => {
464
+ const stored = places.flatMap((place) => store.followUps(place.root));
465
+ 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);
466
+ printList(shown, followUpListTable, given, (rows) => {
467
+ for (const line of formatFollowUps(rows, status !== void 0)) write(line);
468
+ });
469
+ });
470
+ }
471
+ }),
472
+ show: defineCommand({
473
+ meta: {
474
+ name: "show",
475
+ description: "Show a follow-up whole, with its draft and its steps to copy"
476
+ },
477
+ args: {
478
+ id: idArg,
479
+ project,
480
+ json
481
+ },
482
+ run: ({ args }) => withFollowUp(args, (_store, _place, followUp) => {
483
+ if (args.json) write(JSON.stringify(followUp, void 0, 2));
484
+ else for (const line of formatFollowUp(followUp)) write(line);
485
+ })
486
+ }),
487
+ add,
488
+ done: closing("done", "Mark a follow-up done; a message is done once you have sent it", "done"),
489
+ drop: closing("drop", "Drop a follow-up that no longer applies", "dropped"),
490
+ reopen: closing("reopen", "Bring a done or dropped follow-up back", "reopened"),
491
+ due,
492
+ post,
493
+ published
494
+ }
495
+ });
496
+ export { followUps };
@@ -1,4 +1,4 @@
1
- import { Kt as runStatus, Ut as liveStatus, Wt as lockWaitsOf, Zt as isItem, nn as lockWaitLine, qt as slotWaitsOf } from "./src-tsuZRu0H.mjs";
1
+ import { An as lockWaitsOf, Ln as isItem, Mn as runStatus, Nn as slotWaitsOf, kn as liveStatus, kt as lockWaitLine } from "./src-DN-RPsil.mjs";
2
2
  import { C as termOf, E as widthOf, n as cost, o as shortCount, r as costOf, s as style, w as cut } from "./output-B1kwo0YU.mjs";
3
3
  import { i as statusChip, n as chip } from "./chips-BqcT58qr.mjs";
4
4
  import { n as nodeVerdict } from "./verdict-CMvPCpWC.mjs";
@@ -180,12 +180,29 @@ const stateText = (node) => {
180
180
  }
181
181
  return node.state;
182
182
  };
183
- /** The ownership tree, one line per node, children under their owner. */
184
- const formatTree = (roots, now = Date.now()) => {
183
+ /** A node's counted tokens: input, output and cache write. */
184
+ const countedOf = (node) => node.inputTokens + node.outputTokens + (node.cacheWriteTokens ?? 0);
185
+ /**
186
+ * What a node spent: its counted tokens, then dollars unless its driver reports
187
+ * none, where `$0.00` would be false. Blank with no tokens.
188
+ */
189
+ const worth = (node) => {
190
+ const counted = countedOf(node);
191
+ if (counted === 0) return "";
192
+ const tok = `${count(counted)} tok`;
193
+ return style("dim", ` ${node.unpriced === true ? tok : `${tok} · ${cost(node.costUsd)}`}`);
194
+ };
195
+ /**
196
+ * The ownership tree, one line per node, children under their owner. A node in
197
+ * `waits` (a live run's `lockWaitsOf`) reads `waiting for <lock>`, an item
198
+ * too.
199
+ */
200
+ const formatTree = (roots, now = Date.now(), waits = []) => {
185
201
  const lines = [];
186
202
  const walk = (node, lead, branch) => {
203
+ const named = node.description === void 0 ? "" : ` ${node.description}`;
187
204
  lines.push({
188
- label: `${lead}${branch}${node.id}`,
205
+ label: `${lead}${branch}${node.id}${named}`,
189
206
  node
190
207
  });
191
208
  const next = lead + (branch === "├─ " ? "│ " : branch === "" ? "" : " ");
@@ -197,10 +214,20 @@ const formatTree = (roots, now = Date.now()) => {
197
214
  const label = Math.max(...lines.map((l) => l.label.length));
198
215
  const type = Math.max(...lines.map((l) => l.node.type.length));
199
216
  return lines.map(({ label: text, node }) => {
200
- const usage = node.inputTokens + node.outputTokens === 0 ? "" : style("dim", ` ${count(node.inputTokens)} in / ${count(node.outputTokens)} out · ${cost(node.costUsd)}`);
201
- const state = node.held === true ? `${style("yellow", "‖")} paused` : `${STATE_MARK[node.state]()} ${stateText(node)}`;
217
+ const usage = worth(node);
218
+ const lock = waits.find((wait) => wait.id === node.id)?.lock;
219
+ const state = node.held === true ? `${style("yellow", "‖")} paused` : lock === void 0 ? `${STATE_MARK[node.state]()} ${stateText(node)}` : `${STATE_MARK.waiting()} waiting for ${lock}`;
202
220
  const spent = isItem(node) && node.startedAt !== void 0 ? style("dim", ` ${duration((node.finishedAt === void 0 ? now : Date.parse(node.finishedAt)) - Date.parse(node.startedAt))}`) : "";
203
- return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
221
+ return `${node.description === void 0 ? text : `${text.slice(0, text.length - node.description.length)}${dim(node.description)}`}${" ".repeat(label - text.length)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
204
222
  });
205
223
  };
206
- export { header as a, listedStatus as i, formatRuns as n, table as o, formatTree as r, ago as t };
224
+ /**
225
+ * What `ork tree` prints: the run's header (its status as `ork ps` reads it)
226
+ * and the tree. A finished run's lock waits are stale, so only a live run's
227
+ * show; `events` are the run's, `roots` its nested tree.
228
+ */
229
+ const formatTreeView = (run, events, roots, now = Date.now()) => {
230
+ const waits = runStatus(run, events) === "running" ? lockWaitsOf(events) : [];
231
+ return [`${style("bold", `run ${run.id}`)} ${dim(`of ${run.workflow} · ${listedStatus(run, events)} · ${cost(run.costUsd)}`)}`, ...formatTree(roots, now, waits)];
232
+ };
233
+ export { header as a, listedStatus as i, formatRuns as n, table as o, formatTreeView as r, ago as t };
@@ -113,68 +113,6 @@ complete -c ork -f -a '(__ork_complete)'
113
113
  };
114
114
  /** The shells there is a script for. */
115
115
  const SHELLS = Object.keys(SHELL_SCRIPTS);
116
- // src/command-tree.ts
117
- /** How a flag that only Ork passes to itself starts its description. */
118
- const INTERNAL = "Internal:";
119
- /** Whether citty was given a function that gives the value. */
120
- const isThunk = (value) => typeof value === "function";
121
- /** A value citty lets be given as is, as a promise, or as a function. */
122
- const resolved = (value) => Promise.resolve(isThunk(value) ? value() : value);
123
- /** `args` without the flags only Ork passes to itself. */
124
- const publicArgs = (args) => Object.fromEntries(Object.entries(args).filter(([, def]) => !(def.description ?? "").startsWith(INTERNAL)));
125
- /** A command's flags and positionals, which citty lets be given lazily. */
126
- const argsOf = (cmd) => cmd.args === void 0 ? Promise.resolve({}) : resolved(cmd.args);
127
- const listOf = (value) => value === void 0 ? [] : [value].flat();
128
- const flagOf = (name, def) => ({
129
- name,
130
- description: def.description ?? "",
131
- takesValue: def.type === "string" || def.type === "enum",
132
- aliases: "alias" in def ? listOf(def.alias) : [],
133
- options: def.type === "enum" ? def.options ?? [] : [],
134
- negatable: def.type === "boolean" && def.default === true
135
- });
136
- /** A command's public flags and positionals, in the order it defines them. */
137
- const argInfoOf = async (cmd) => {
138
- const entries = Object.entries(publicArgs(await argsOf(cmd)));
139
- return {
140
- flags: entries.filter(([, def]) => def.type !== "positional").map(([name, def]) => flagOf(name, def)),
141
- positionals: entries.filter(([, def]) => def.type === "positional").map(([name, def]) => ({
142
- name,
143
- description: def.description ?? ""
144
- }))
145
- };
146
- };
147
- /**
148
- * A command's subcommands, in order, loaded ones resolved. A name whose
149
- * definition is one listed before it (`wt`, `worktrees`'s) is an alias of that
150
- * one, not an entry of its own.
151
- */
152
- const subcommandsOf = async (cmd) => {
153
- const subs = cmd.subCommands === void 0 ? {} : await resolved(cmd.subCommands);
154
- const loaded = await Promise.all(Object.entries(subs).map(async ([name, sub]) => {
155
- const def = await resolved(sub);
156
- return {
157
- name,
158
- def,
159
- meta: def.meta === void 0 ? {} : await resolved(def.meta)
160
- };
161
- }));
162
- const entries = [];
163
- for (const { name, def, meta } of loaded) {
164
- const same = entries.find((entry) => entry.def === def);
165
- if (same === void 0) entries.push({
166
- name,
167
- aliases: listOf(meta.alias),
168
- description: meta.description ?? "",
169
- hidden: meta.hidden === true,
170
- def
171
- });
172
- else same.aliases.push(name);
173
- }
174
- return entries;
175
- };
176
- /** The subcommand `name` (or one of its aliases) of `entries`. */
177
- const findEntry = (entries, name) => entries.find((entry) => entry.name === name || entry.aliases.includes(name));
178
116
  // src/help-groups.ts
179
117
  const HELP_GROUPS = [
180
118
  {
@@ -284,6 +222,10 @@ const HELP_GROUPS = [
284
222
  name: "feedback",
285
223
  short: "What got in the way in your runs"
286
224
  },
225
+ {
226
+ name: "follow-ups",
227
+ short: "What shipped changes left for later"
228
+ },
287
229
  {
288
230
  name: "worktrees",
289
231
  short: "Track and clean up Ork's worktrees"
@@ -369,4 +311,4 @@ const groupedEntries = (entries) => {
369
311
  entries: other
370
312
  }].filter((group) => group.entries.length > 0);
371
313
  };
372
- export { findEntry as a, subcommandsOf as c, SHELL_SCRIPTS as d, argsOf as i, COMPLETE_COMMAND as l, groupedEntries as n, publicArgs as o, argInfoOf as r, resolved as s, groupOf as t, SHELLS as u };
314
+ export { SHELL_SCRIPTS as a, SHELLS as i, groupedEntries as n, COMPLETE_COMMAND as r, groupOf as t };
@@ -1,4 +1,4 @@
1
- import { p as runMarkerIn } from "./src-C7z_6fhn.mjs";
1
+ import { p as runMarkerIn } from "./src-DHDQ0itC.mjs";
2
2
  // src/commands/in-a-run.ts
3
3
  /**
4
4
  * @file The commands that act as the user refuse a program or an agent in a run