@krapht/ork 1.0.0-alpha.13 → 1.0.0-alpha.15
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.
- package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
- package/dist/{store-CM3cERGl.mjs → attach-BHO3X3cf.mjs} +36 -91
- package/dist/{attach-E6NNyA3T.mjs → attach-CPespxlQ.mjs} +6 -5
- package/dist/bin.mjs +148 -1444
- package/dist/brief-za0ozN_E.mjs +58 -0
- package/dist/{builtin-files-n6o6oWpB.mjs → builtin-files-2bHf76x-.mjs} +1 -1
- package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
- package/dist/{completion-SNNNxnv9.mjs → completion-CQ2UYKuJ.mjs} +52 -11
- package/dist/context-BNqDw9z1.mjs +106 -0
- package/dist/{find-CjsNYAAf.mjs → find-BYLlszqj.mjs} +1 -1
- package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
- package/dist/{format-C60TFpRe.mjs → format-C8MsFj8_.mjs} +30 -16
- package/dist/{help-groups-CyfPWlST.mjs → help-groups-CQ98mLjF.mjs} +20 -6
- package/dist/{in-a-run-BYjTXaST.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
- package/dist/{__build_info__-Da73_nBs.mjs → install-DWtXTq4y.mjs} +3 -227
- package/dist/{limits-C5QKZNLT.mjs → limits-B1qzS4cX.mjs} +1 -1
- package/dist/{mcp-bcctR-wc.mjs → mcp-BOrVkyhN.mjs} +6 -6
- package/dist/{once-DPQu5Q3H.mjs → once-C1fL3GVo.mjs} +4 -4
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
- package/dist/{pause-BPvlDIvz.mjs → pause-DOMK-R8V.mjs} +4 -4
- package/dist/post-BzA6Qobq.mjs +44 -0
- package/dist/{relay-C8W0t_zc.mjs → relay-OWuu4fib.mjs} +2 -2
- package/dist/run--4-1xAF2.mjs +1451 -0
- package/dist/{run-CQIfq918.mjs → run-CiPsW9Kf.mjs} +1 -1
- package/dist/send-CnNNW-ot.mjs +105 -0
- package/dist/{src-CqRqsb2t.mjs → src-B9zWIVqo.mjs} +226 -44
- package/dist/{src-CGJGL1sZ.mjs → src-DQKz2eOH.mjs} +326 -81
- package/dist/{src-D93tHZ5B.mjs → src-DcrhgipG.mjs} +107 -47
- package/dist/{src-BeR6f70A.mjs → src-Dr_fTSu-.mjs} +5858 -3746
- package/dist/store-oKJ-cCf3.mjs +16 -0
- package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
- package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
- package/dist/ui/assets/{index-CWwm8HTG.js → index-BtwkftKe.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-D2Ydofq0.mjs → ui-CzKlsCnm.mjs} +4 -4
- package/dist/{usage-6WIowblz.mjs → usage-DHGNk2JL.mjs} +13 -141
- package/dist/{context-DHudLA5e.mjs → validate-Bs_gg22g.mjs} +17 -107
- package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
- package/dist/{watch-uOU_WeBI.mjs → watch-CCCfw3n7.mjs} +24 -8
- package/dist/{workflows-7tQdMnrA.mjs → workflows-CW28ZU-z.mjs} +3 -3
- package/dist/{worktrees-ZCII298F.mjs → worktrees-CyE8aF6j.mjs} +3 -5
- package/package.json +2 -2
- package/dist/clean-Bh4KZgzc.mjs +0 -91
- package/dist/decide-DuIHACvH.mjs +0 -50
- package/dist/list-BUb5Uc3H.mjs +0 -117
- package/dist/remove-BkFWGVYo.mjs +0 -201
- package/dist/report-CEN8VoUb.mjs +0 -140
- package/dist/send-CaZlGG5a.mjs +0 -163
- package/dist/ui/assets/app-DYRC0V4p.js +0 -111
- package/dist/ui/assets/app-QoHVi-mb.css +0 -1
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { Dr as placeFor, Yn as runGh, cn as recordPublished, dn as deliverFollowUp, fn as followUpProblem, sn as markDue } from "./src-Dr_fTSu-.mjs";
|
|
2
|
+
import { d as followUpsRule, f as refreshDue, n as flagValues } from "./run--4-1xAF2.mjs";
|
|
3
|
+
import { g as changeFollowUp, v as findFollowUp, y as followUpActions } from "./__build_info__-Bsb1kjgF.mjs";
|
|
4
|
+
import { t as postFollowUpMessage } from "./post-BzA6Qobq.mjs";
|
|
5
|
+
import { t as findRun } from "./find-BYLlszqj.mjs";
|
|
6
|
+
import { a as plain, c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
|
|
7
|
+
import { t as withStore } from "./store-oKJ-cCf3.mjs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { defineCommand } from "citty";
|
|
10
|
+
// src/follow-ups/command.ts
|
|
11
|
+
/**
|
|
12
|
+
* @file What the `ork follow-ups` subcommands share: the flags, the project a
|
|
13
|
+
* call is about, and the due check every call runs first, in the user's
|
|
14
|
+
* store. A `gh` that fails only warns.
|
|
15
|
+
*/
|
|
16
|
+
const json = {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description: "Print JSON",
|
|
19
|
+
default: false
|
|
20
|
+
};
|
|
21
|
+
const project = {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "A registered project's id (default: the one you are in)"
|
|
24
|
+
};
|
|
25
|
+
const idArg = {
|
|
26
|
+
type: "positional",
|
|
27
|
+
description: "A follow-up's id, or a prefix of one",
|
|
28
|
+
required: true
|
|
29
|
+
};
|
|
30
|
+
/** Says why, and makes the command exit 1. */
|
|
31
|
+
const fail = (message) => {
|
|
32
|
+
writeErr(message);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
};
|
|
35
|
+
/** The project a command is about, from `--project` or the folder. */
|
|
36
|
+
const placeOf = (store, key) => {
|
|
37
|
+
const projects = store.projects();
|
|
38
|
+
if (key === void 0) return placeFor(process.cwd(), projects);
|
|
39
|
+
const found = projects.find((p) => p.id === key || p.root === key);
|
|
40
|
+
if (found === void 0) {
|
|
41
|
+
const names = projects.map((p) => p.id);
|
|
42
|
+
fail(`No registered project \`${key}\`; registered: ${names.length === 0 ? "none" : names.join(", ")}.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
return placeFor(found.root, projects);
|
|
46
|
+
};
|
|
47
|
+
/** Runs `body` in the project, after the due check, in the user's store. */
|
|
48
|
+
const inProject = (key, body) => withStore(async (store) => {
|
|
49
|
+
const place = placeOf(store, key);
|
|
50
|
+
if (place === void 0) return;
|
|
51
|
+
await refreshDue(store, place.root, (line) => {
|
|
52
|
+
writeErr(style("dim", line));
|
|
53
|
+
});
|
|
54
|
+
await body(store, place);
|
|
55
|
+
});
|
|
56
|
+
/** Runs `body` on the follow-up `id` names, or says why there is none. */
|
|
57
|
+
const withFollowUp = (args, body) => inProject(args.project, async (store, place) => {
|
|
58
|
+
const found = findFollowUp(store, place.root, args.id);
|
|
59
|
+
if (!found.ok) {
|
|
60
|
+
fail(found.error);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await body(store, place, found.followUp);
|
|
64
|
+
});
|
|
65
|
+
// src/follow-ups/format.ts
|
|
66
|
+
/** The groups of the list, in the order they are shown. */
|
|
67
|
+
const GROUPS = [
|
|
68
|
+
{
|
|
69
|
+
status: "due",
|
|
70
|
+
title: "Due"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
status: "waiting",
|
|
74
|
+
title: "Waiting"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
status: "done",
|
|
78
|
+
title: "Done"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
status: "dropped",
|
|
82
|
+
title: "Dropped"
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
/** `agent` first, then the maintainer, other projects, then people. */
|
|
86
|
+
const FOR_ORDER = [
|
|
87
|
+
"agent",
|
|
88
|
+
"me",
|
|
89
|
+
"project:",
|
|
90
|
+
"person:"
|
|
91
|
+
];
|
|
92
|
+
const forOrder = (value) => FOR_ORDER.findIndex((prefix) => value.startsWith(prefix));
|
|
93
|
+
const byFor = (a, b) => forOrder(a.for) - forOrder(b.for) || a.for.localeCompare(b.for);
|
|
94
|
+
/** A multi-line text, each line safe to print. */
|
|
95
|
+
const linesOf = (text) => text.split("\n").map((line) => plain(line));
|
|
96
|
+
const when = (f) => f.ref === void 0 ? f.due : `${f.due} ${f.ref}`;
|
|
97
|
+
/** One follow-up on one line. */
|
|
98
|
+
const followUpLine = (f) => `${style("dim", f.id)} ${f.kind.padEnd(7)} ${plain(f.title)} ${style("dim", `(${plain(when(f))})`)}`;
|
|
99
|
+
/**
|
|
100
|
+
* The follow-ups grouped by status, then by `for`; the closed ones only with
|
|
101
|
+
* `all`. A line each, under a heading each.
|
|
102
|
+
*/
|
|
103
|
+
const formatFollowUps = (followUps, all) => {
|
|
104
|
+
const lines = [];
|
|
105
|
+
for (const group of GROUPS) {
|
|
106
|
+
if (!all && group.status !== "due" && group.status !== "waiting") continue;
|
|
107
|
+
const inGroup = followUps.filter((f) => f.status === group.status).toSorted(byFor);
|
|
108
|
+
if (inGroup.length === 0) continue;
|
|
109
|
+
if (lines.length > 0) lines.push("");
|
|
110
|
+
lines.push(style("bold", `${group.title} (${String(inGroup.length)})`));
|
|
111
|
+
for (const kind of new Set(inGroup.map((f) => f.for))) {
|
|
112
|
+
lines.push(` ${style("cyan", `for ${plain(kind)}`)}`);
|
|
113
|
+
for (const f of inGroup.filter((one) => one.for === kind)) lines.push(` ${followUpLine(f)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return lines.length === 0 ? [style("dim", "No follow-ups.")] : lines;
|
|
117
|
+
};
|
|
118
|
+
const fromLine = (f) => f.from.run === void 0 ? [] : [`from run ${plain(f.from.run)}${f.from.node === void 0 ? "" : `, node ${plain(f.from.node)}`}`];
|
|
119
|
+
/** One follow-up whole: its record, its draft and its steps, ready to copy. */
|
|
120
|
+
const formatFollowUp = (f) => [
|
|
121
|
+
`${style("bold", plain(f.title))} ${style("dim", f.id)}`,
|
|
122
|
+
`status ${f.status}${f.note === void 0 ? "" : ` (${plain(f.note)})`}, for ${plain(f.for)}, ${f.kind}, due ${plain(f.due)}`,
|
|
123
|
+
...f.ref === void 0 ? [] : [`waits on ${plain(f.ref)}`],
|
|
124
|
+
...fromLine(f),
|
|
125
|
+
...f.where.length === 0 ? [] : [`files ${f.where.map((file) => plain(file)).join(", ")}`],
|
|
126
|
+
...f.url === void 0 ? [] : [`kept at ${plain(f.url)}`],
|
|
127
|
+
...f.body === "" ? [] : ["", ...linesOf(f.body)],
|
|
128
|
+
...f.draft === void 0 ? [] : [
|
|
129
|
+
"",
|
|
130
|
+
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}\``),
|
|
131
|
+
...linesOf(f.draft.text).map((line) => ` ${line}`)
|
|
132
|
+
],
|
|
133
|
+
...f.steps === void 0 ? [] : [
|
|
134
|
+
"",
|
|
135
|
+
style("bold", "Steps (Ork never runs them):"),
|
|
136
|
+
...f.steps.flatMap((step, index) => [` ${String(index + 1)}. ${plain(step.text)}`, ...step.command === void 0 ? [] : [` ${plain(step.command)}`]])
|
|
137
|
+
]
|
|
138
|
+
];
|
|
139
|
+
// src/commands/follow-ups-act.ts
|
|
140
|
+
/**
|
|
141
|
+
* @file What `ork follow-ups` does to one follow-up: done (a message: sent),
|
|
142
|
+
* drop, reopen, make due, post a drafted GitHub comment, and `published`,
|
|
143
|
+
* which a workflow's step calls once its change is out. Posting goes through
|
|
144
|
+
* the person's own `gh` and only with `--confirm`, after showing the exact
|
|
145
|
+
* text; Ork never runs a task's commands.
|
|
146
|
+
*/
|
|
147
|
+
/** `done`, `drop` or `reopen`: one change of status, or why it can't be. */
|
|
148
|
+
const closing = (action, description, verb) => defineCommand({
|
|
149
|
+
meta: {
|
|
150
|
+
name: action,
|
|
151
|
+
description
|
|
152
|
+
},
|
|
153
|
+
args: {
|
|
154
|
+
id: idArg,
|
|
155
|
+
project
|
|
156
|
+
},
|
|
157
|
+
run: ({ args }) => withFollowUp(args, (store, _place, followUp) => {
|
|
158
|
+
const changed = changeFollowUp(store, followUp, action);
|
|
159
|
+
if (!changed.ok) {
|
|
160
|
+
fail(changed.error);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
write(`${style("green", "✓")} ${verb} ${changed.followUp.id}: ${plain(changed.followUp.title)}`);
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
const due = defineCommand({
|
|
167
|
+
meta: {
|
|
168
|
+
name: "due",
|
|
169
|
+
description: "Make a waiting follow-up due now, or all of a run's (after a deploy, say)"
|
|
170
|
+
},
|
|
171
|
+
args: {
|
|
172
|
+
target: {
|
|
173
|
+
type: "positional",
|
|
174
|
+
description: "A follow-up's id (or a prefix), or a run id",
|
|
175
|
+
required: true
|
|
176
|
+
},
|
|
177
|
+
project
|
|
178
|
+
},
|
|
179
|
+
run: ({ args }) => inProject(args.project, (store, place) => {
|
|
180
|
+
const one = findFollowUp(store, place.root, args.target);
|
|
181
|
+
const run = findRun(store, args.target, "");
|
|
182
|
+
const runId = run.ok ? run.run.id : args.target;
|
|
183
|
+
const key = one.ok ? one.followUp.id : runId;
|
|
184
|
+
const marked = markDue(store, place.root, key);
|
|
185
|
+
if (marked.length === 0) {
|
|
186
|
+
fail(`Nothing waiting under \`${args.target}\`: give a waiting follow-up's id, or a run that left some.`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const f of marked) write(`${style("green", "✓")} due ${f.id}: ${plain(f.title)}`);
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
const post = defineCommand({
|
|
193
|
+
meta: {
|
|
194
|
+
name: "post",
|
|
195
|
+
description: "Post a message drafted for GitHub, as a comment through your own gh, with --confirm; without it, show what would be posted"
|
|
196
|
+
},
|
|
197
|
+
args: {
|
|
198
|
+
id: idArg,
|
|
199
|
+
confirm: {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
description: "Yes: post exactly this text",
|
|
202
|
+
default: false
|
|
203
|
+
},
|
|
204
|
+
project
|
|
205
|
+
},
|
|
206
|
+
run: ({ args }) => withFollowUp(args, async (store, _place, followUp) => {
|
|
207
|
+
const allowed = followUpActions(followUp).post;
|
|
208
|
+
if (!allowed.ok) {
|
|
209
|
+
fail(allowed.error);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const draft = followUp.draft;
|
|
213
|
+
if (!args.confirm && draft !== void 0) {
|
|
214
|
+
write(`Would post to ${plain(draft.to)} through your gh:`);
|
|
215
|
+
for (const line of draft.text.split("\n")) write(` ${plain(line)}`);
|
|
216
|
+
fail("Run again with --confirm to post it.");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const posted = await postFollowUpMessage(followUp, {
|
|
220
|
+
rows: store,
|
|
221
|
+
gh: runGh,
|
|
222
|
+
confirmed: args.confirm,
|
|
223
|
+
home: homedir()
|
|
224
|
+
});
|
|
225
|
+
if (!posted.ok) {
|
|
226
|
+
fail(posted.error);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
write(`${style("green", "✓")} posted${posted.url === void 0 ? "" : ` ${posted.url}`}; done ${followUp.id}`);
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
const published = defineCommand({
|
|
233
|
+
meta: {
|
|
234
|
+
name: "published",
|
|
235
|
+
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"
|
|
236
|
+
},
|
|
237
|
+
args: { ref: {
|
|
238
|
+
type: "positional",
|
|
239
|
+
description: "The pull request URL or the branch",
|
|
240
|
+
required: true
|
|
241
|
+
} },
|
|
242
|
+
run: ({ args }) => withStore((store) => {
|
|
243
|
+
const runId = process.env["ORK_RUN"];
|
|
244
|
+
if (runId === void 0 || runId === "") {
|
|
245
|
+
fail("ork follow-ups published runs inside a run: ORK_RUN is not set.");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const found = findRun(store, runId, "");
|
|
249
|
+
if (!found.ok) {
|
|
250
|
+
fail(found.error);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const callers = [];
|
|
254
|
+
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);
|
|
255
|
+
const recorded = recordPublished(store, {
|
|
256
|
+
project: found.run.project,
|
|
257
|
+
run: found.run.id,
|
|
258
|
+
ref: args.ref,
|
|
259
|
+
callers
|
|
260
|
+
});
|
|
261
|
+
write(`${style("green", "✓")} ${String(recorded.referenced.length)} follow-up(s) now wait on ${plain(args.ref)}; ${String(recorded.done.length)} done`);
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
// src/commands/follow-ups-add.ts
|
|
265
|
+
/**
|
|
266
|
+
* @file `ork follow-ups add`: a follow-up the maintainer adds by hand, with its
|
|
267
|
+
* `for`, `kind` and `due`, a message's draft or a task's steps. It is kept
|
|
268
|
+
* where the project's rule says, and one for another project goes into that
|
|
269
|
+
* project's list and, when a run is live there, to its session.
|
|
270
|
+
*/
|
|
271
|
+
/** `text :: command` as a step; the command is optional. */
|
|
272
|
+
const stepOf = (raw) => {
|
|
273
|
+
const [text = "", command] = raw.split(" :: ", 2);
|
|
274
|
+
return command === void 0 ? { text: text.trim() } : {
|
|
275
|
+
text: text.trim(),
|
|
276
|
+
command: command.trim()
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
const defaultKind = (value) => {
|
|
280
|
+
if (value.startsWith("person:")) return "message";
|
|
281
|
+
return value === "agent" || value.startsWith("project:") ? "change" : "task";
|
|
282
|
+
};
|
|
283
|
+
const isKind = (value) => [
|
|
284
|
+
"change",
|
|
285
|
+
"message",
|
|
286
|
+
"task",
|
|
287
|
+
"check"
|
|
288
|
+
].includes(value);
|
|
289
|
+
const isFor = (value) => /^(?:agent|me|project:.+|person:.+)$/u.test(value);
|
|
290
|
+
const isDue = (value) => /^(?:now|after-merge|after-deploy|\d+-\d+-\d+)$/u.test(value);
|
|
291
|
+
const add = defineCommand({
|
|
292
|
+
meta: {
|
|
293
|
+
name: "add",
|
|
294
|
+
description: "Add a follow-up: --for agent|me|project:<name>|person:<name>, --kind, --due"
|
|
295
|
+
},
|
|
296
|
+
args: {
|
|
297
|
+
title: {
|
|
298
|
+
type: "positional",
|
|
299
|
+
description: "What it is",
|
|
300
|
+
required: true
|
|
301
|
+
},
|
|
302
|
+
for: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: "agent, me, project:<name> or person:<name> (default: me)"
|
|
305
|
+
},
|
|
306
|
+
kind: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "change, message, task or check (default: by --for)"
|
|
309
|
+
},
|
|
310
|
+
due: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "now, after-merge, after-deploy or a YYYY-MM-DD date (default: now)"
|
|
313
|
+
},
|
|
314
|
+
body: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "What and why"
|
|
317
|
+
},
|
|
318
|
+
where: {
|
|
319
|
+
type: "string",
|
|
320
|
+
description: "A file it is about (repeatable)"
|
|
321
|
+
},
|
|
322
|
+
ref: {
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "The PR or branch it waits on"
|
|
325
|
+
},
|
|
326
|
+
to: {
|
|
327
|
+
type: "string",
|
|
328
|
+
description: "A message: who it is to"
|
|
329
|
+
},
|
|
330
|
+
channel: {
|
|
331
|
+
type: "string",
|
|
332
|
+
description: "A message: slack, email, github…"
|
|
333
|
+
},
|
|
334
|
+
text: {
|
|
335
|
+
type: "string",
|
|
336
|
+
description: "A message: its text, to send"
|
|
337
|
+
},
|
|
338
|
+
step: {
|
|
339
|
+
type: "string",
|
|
340
|
+
description: "A task's step, `text` or `text :: command` (repeatable); Ork never runs it"
|
|
341
|
+
},
|
|
342
|
+
project,
|
|
343
|
+
json
|
|
344
|
+
},
|
|
345
|
+
run: ({ args, rawArgs }) => inProject(args.project, async (store, place) => {
|
|
346
|
+
const recipient = args.for ?? "me";
|
|
347
|
+
const kind = args.kind ?? defaultKind(recipient);
|
|
348
|
+
if (!isKind(kind)) {
|
|
349
|
+
fail("--kind is change, message, task or check.");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const due = args.due ?? "now";
|
|
353
|
+
if (!isFor(recipient) || !isDue(due)) {
|
|
354
|
+
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>.");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const steps = flagValues(rawArgs, "--step").map((raw) => stepOf(raw));
|
|
358
|
+
const input = {
|
|
359
|
+
title: args.title,
|
|
360
|
+
for: recipient,
|
|
361
|
+
kind,
|
|
362
|
+
due,
|
|
363
|
+
where: flagValues(rawArgs, "--where"),
|
|
364
|
+
...args.body === void 0 ? {} : { body: args.body },
|
|
365
|
+
...args.ref === void 0 ? {} : { ref: args.ref },
|
|
366
|
+
...args.text === void 0 ? {} : { draft: {
|
|
367
|
+
to: args.to ?? recipient.replace(/^person:/u, ""),
|
|
368
|
+
...args.channel === void 0 ? {} : { channel: args.channel },
|
|
369
|
+
text: args.text
|
|
370
|
+
} },
|
|
371
|
+
...steps.length === 0 ? {} : { steps }
|
|
372
|
+
};
|
|
373
|
+
const problem = followUpProblem(input);
|
|
374
|
+
if (problem !== null) {
|
|
375
|
+
fail(`${problem}.`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const rule = followUpsRule(place);
|
|
379
|
+
const delivered = await deliverFollowUp(input, {
|
|
380
|
+
store,
|
|
381
|
+
project: place.root,
|
|
382
|
+
...rule === void 0 ? {} : { rule }
|
|
383
|
+
});
|
|
384
|
+
if (!delivered.ok) {
|
|
385
|
+
fail(delivered.error);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (args.json) {
|
|
389
|
+
write(JSON.stringify(delivered, void 0, 2));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const { added, relayed } = delivered;
|
|
393
|
+
const { followUp } = added;
|
|
394
|
+
write(`${style("green", "✓")} ${added.merged ? "merged into" : "added"} ${followUp.id}: ${plain(followUp.title)}`);
|
|
395
|
+
if (added.note !== void 0) writeErr(style("dim", added.note));
|
|
396
|
+
if (relayed !== void 0) write(style("dim", ` sent to ${relayed.node} of run ${relayed.run}, which is live in that project`));
|
|
397
|
+
})
|
|
398
|
+
});
|
|
399
|
+
// src/commands/follow-ups.ts
|
|
400
|
+
/**
|
|
401
|
+
* @file `ork follow-ups`: what shipped changes left for later, apart from
|
|
402
|
+
* feedback (specs/follow-ups.md): list and show them, add one, and mark one
|
|
403
|
+
* done, dropped, reopened or due, or post its drafted GitHub comment (in
|
|
404
|
+
* `follow-ups-act.ts`). Every call first moves what has come due, reading
|
|
405
|
+
* pull requests through the person's own `gh`; a `gh` that fails only warns.
|
|
406
|
+
*/
|
|
407
|
+
const followUps = defineCommand({
|
|
408
|
+
meta: {
|
|
409
|
+
name: "follow-ups",
|
|
410
|
+
description: "What shipped changes left for later: list, show, add, done, drop, reopen, due, post, published"
|
|
411
|
+
},
|
|
412
|
+
subCommands: {
|
|
413
|
+
list: defineCommand({
|
|
414
|
+
meta: {
|
|
415
|
+
name: "list",
|
|
416
|
+
description: "List the follow-ups, due first, then by who they are for"
|
|
417
|
+
},
|
|
418
|
+
args: {
|
|
419
|
+
all: {
|
|
420
|
+
type: "boolean",
|
|
421
|
+
description: "Done and dropped ones too",
|
|
422
|
+
default: false
|
|
423
|
+
},
|
|
424
|
+
for: {
|
|
425
|
+
type: "string",
|
|
426
|
+
description: "Only those for agent, me, project:<name> or person:<name>"
|
|
427
|
+
},
|
|
428
|
+
project,
|
|
429
|
+
json
|
|
430
|
+
},
|
|
431
|
+
run: ({ args }) => inProject(args.project, (store, place) => {
|
|
432
|
+
const shown = store.followUps(place.root).filter((f) => (args.all || f.status === "due" || f.status === "waiting") && (args.for === void 0 || f.for === args.for));
|
|
433
|
+
if (args.json) write(JSON.stringify(shown, void 0, 2));
|
|
434
|
+
else for (const line of formatFollowUps(shown, args.all)) write(line);
|
|
435
|
+
})
|
|
436
|
+
}),
|
|
437
|
+
show: defineCommand({
|
|
438
|
+
meta: {
|
|
439
|
+
name: "show",
|
|
440
|
+
description: "Show a follow-up whole, with its draft and its steps to copy"
|
|
441
|
+
},
|
|
442
|
+
args: {
|
|
443
|
+
id: idArg,
|
|
444
|
+
project,
|
|
445
|
+
json
|
|
446
|
+
},
|
|
447
|
+
run: ({ args }) => withFollowUp(args, (_store, _place, followUp) => {
|
|
448
|
+
if (args.json) write(JSON.stringify(followUp, void 0, 2));
|
|
449
|
+
else for (const line of formatFollowUp(followUp)) write(line);
|
|
450
|
+
})
|
|
451
|
+
}),
|
|
452
|
+
add,
|
|
453
|
+
done: closing("done", "Mark a follow-up done; a message is done once you have sent it", "done"),
|
|
454
|
+
drop: closing("drop", "Drop a follow-up that no longer applies", "dropped"),
|
|
455
|
+
reopen: closing("reopen", "Bring a done or dropped follow-up back", "reopened"),
|
|
456
|
+
due,
|
|
457
|
+
post,
|
|
458
|
+
published
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
export { followUps };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { i as statusChip, n as chip } from "./chips-
|
|
4
|
-
import { n as nodeVerdict } from "./verdict-
|
|
1
|
+
import { Cn as liveStatus, Dn as slotWaitsOf, En as runStatus, Lt as lockWaitLine, jn as isItem, wn as lockWaitsOf } from "./src-Dr_fTSu-.mjs";
|
|
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
|
+
import { i as statusChip, n as chip } from "./chips-BqcT58qr.mjs";
|
|
4
|
+
import { n as nodeVerdict } from "./verdict-CMvPCpWC.mjs";
|
|
5
5
|
// src/term/table.ts
|
|
6
6
|
/**
|
|
7
7
|
* @file Aligned columns for the listing commands: each column as wide as its
|
|
@@ -85,13 +85,17 @@ const duration = (ms) => {
|
|
|
85
85
|
};
|
|
86
86
|
const dim = (s) => style("dim", s);
|
|
87
87
|
/**
|
|
88
|
-
* A run's status as `ork ps` reads it:
|
|
88
|
+
* A run's status as `ork ps` reads it: for a running run, `waiting for <lock>,
|
|
89
|
+
* held by <run>/<node>` with a node waiting for a lock, or `waiting for slot`
|
|
89
90
|
* with a command waiting for a machine-wide slot; `events` are a running
|
|
90
91
|
* run's.
|
|
91
92
|
*/
|
|
92
93
|
const listedStatus = (run, events) => {
|
|
93
94
|
const status = runStatus(run, events);
|
|
94
|
-
|
|
95
|
+
if (status !== "running") return status;
|
|
96
|
+
const [lock] = lockWaitsOf(events);
|
|
97
|
+
if (lock !== void 0) return lockWaitLine(lock.lock, lock.holders);
|
|
98
|
+
return slotWaitsOf(events).length > 0 ? "waiting for slot" : status;
|
|
95
99
|
};
|
|
96
100
|
/**
|
|
97
101
|
* The `ork ps` table; `withProject` adds each run's project. A called run's
|
|
@@ -107,7 +111,7 @@ const formatRuns = (runs, withProject, now = Date.now(), statusOf = liveStatus,
|
|
|
107
111
|
workflow: run.workflow,
|
|
108
112
|
status,
|
|
109
113
|
when: `${ago(run.startedAt, now)} · ${duration(end - Date.parse(run.startedAt))}`,
|
|
110
|
-
cost: run.parent === void 0 ?
|
|
114
|
+
cost: run.parent === void 0 ? costOf(run.costUsd, run.partial, run.tokens) : `${costOf(run.costUsd, run.partial, run.tokens)} in ${run.parent}`,
|
|
111
115
|
called: run.parent !== void 0,
|
|
112
116
|
project: run.project
|
|
113
117
|
};
|
|
@@ -166,11 +170,7 @@ const STATE_MARK = {
|
|
|
166
170
|
skipped: () => style("dim", "-")
|
|
167
171
|
};
|
|
168
172
|
/** `950`, `12.3k`, `1.2M`. */
|
|
169
|
-
const count =
|
|
170
|
-
if (n < 1e3) return String(n);
|
|
171
|
-
if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
|
|
172
|
-
return `${(n / 1e6).toFixed(1)}M`;
|
|
173
|
-
};
|
|
173
|
+
const count = shortCount;
|
|
174
174
|
const stateText = (node) => {
|
|
175
175
|
if (node.state === "running") return node.runs > 1 ? `running (round ${String(node.runs)})` : "running";
|
|
176
176
|
if (node.state === "ok" || node.state === "failed") {
|
|
@@ -180,8 +180,12 @@ const stateText = (node) => {
|
|
|
180
180
|
}
|
|
181
181
|
return node.state;
|
|
182
182
|
};
|
|
183
|
-
/**
|
|
184
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The ownership tree, one line per node, children under their owner. A node in
|
|
185
|
+
* `waits` (a live run's `lockWaitsOf`) reads `waiting for <lock>`, an item
|
|
186
|
+
* too.
|
|
187
|
+
*/
|
|
188
|
+
const formatTree = (roots, now = Date.now(), waits = []) => {
|
|
185
189
|
const lines = [];
|
|
186
190
|
const walk = (node, lead, branch) => {
|
|
187
191
|
lines.push({
|
|
@@ -198,9 +202,19 @@ const formatTree = (roots, now = Date.now()) => {
|
|
|
198
202
|
const type = Math.max(...lines.map((l) => l.node.type.length));
|
|
199
203
|
return lines.map(({ label: text, node }) => {
|
|
200
204
|
const usage = node.inputTokens + node.outputTokens === 0 ? "" : style("dim", ` ${count(node.inputTokens)} in / ${count(node.outputTokens)} out · ${cost(node.costUsd)}`);
|
|
201
|
-
const
|
|
205
|
+
const lock = waits.find((wait) => wait.id === node.id)?.lock;
|
|
206
|
+
const state = node.held === true ? `${style("yellow", "‖")} paused` : lock === void 0 ? `${STATE_MARK[node.state]()} ${stateText(node)}` : `${STATE_MARK.waiting()} waiting for ${lock}`;
|
|
202
207
|
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
208
|
return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${spent}${usage}`;
|
|
204
209
|
});
|
|
205
210
|
};
|
|
206
|
-
|
|
211
|
+
/**
|
|
212
|
+
* What `ork tree` prints: the run's header (its status as `ork ps` reads it)
|
|
213
|
+
* and the tree. A finished run's lock waits are stale, so only a live run's
|
|
214
|
+
* show; `events` are the run's, `roots` its nested tree.
|
|
215
|
+
*/
|
|
216
|
+
const formatTreeView = (run, events, roots, now = Date.now()) => {
|
|
217
|
+
const waits = runStatus(run, events) === "running" ? lockWaitsOf(events) : [];
|
|
218
|
+
return [`${style("bold", `run ${run.id}`)} ${dim(`of ${run.workflow} · ${listedStatus(run, events)} · ${cost(run.costUsd)}`)}`, ...formatTree(roots, now, waits)];
|
|
219
|
+
};
|
|
220
|
+
export { header as a, listedStatus as i, formatRuns as n, table as o, formatTreeView as r, ago as t };
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* shell. Each is thin: it runs `ork __complete -- <words…>` with the word
|
|
5
5
|
* under the cursor last, and offers the lines it prints, `value<TAB>
|
|
6
6
|
* description`. A line `:group <title>` starts a group (zsh labels the menu
|
|
7
|
-
* with it); `:files` asks for file names too. A
|
|
7
|
+
* with it); `:files` asks for file names too. A value ending in `=` (an
|
|
8
|
+
* input's `name=`) takes no space after it, so its value is typed next. A new
|
|
9
|
+
* shell is one more entry.
|
|
8
10
|
*/
|
|
9
11
|
/** The hidden command the scripts run. */
|
|
10
12
|
const COMPLETE_COMMAND = "__complete";
|
|
@@ -15,9 +17,11 @@ const SHELL_SCRIPTS = {
|
|
|
15
17
|
# (ork completion zsh > ~/.zsh/completions/_ork), or source it.
|
|
16
18
|
|
|
17
19
|
_ork() {
|
|
18
|
-
local -a lines cands
|
|
20
|
+
local -a lines cands opens
|
|
19
21
|
local line group=commands files=0 n=0 ret=1
|
|
20
22
|
lines=("\${(@f)$("\${words[1]}" ${COMPLETE_COMMAND} -- "\${(@)words[2,CURRENT]}" 2>/dev/null)}")
|
|
23
|
+
# A last group line offers what the last group gathered.
|
|
24
|
+
lines+=(":group end")
|
|
21
25
|
for line in "\${lines[@]}"; do
|
|
22
26
|
case $line in
|
|
23
27
|
(:files) files=1 ;;
|
|
@@ -25,15 +29,17 @@ _ork() {
|
|
|
25
29
|
if (( \${#cands} )); then
|
|
26
30
|
_describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
|
|
27
31
|
fi
|
|
28
|
-
|
|
32
|
+
# name= is followed by its value, not a space.
|
|
33
|
+
if (( \${#opens} )); then
|
|
34
|
+
_describe -V -t "ork-$(( ++n ))" "$group" opens -S '' && ret=0
|
|
35
|
+
fi
|
|
36
|
+
cands=() opens=()
|
|
29
37
|
group=\${line#:group }
|
|
30
38
|
;;
|
|
39
|
+
(*=$'\\t'*) opens+=("\${\${line%%$'\\t'*}//:/\\\\:}:\${line#*$'\\t'}") ;;
|
|
31
40
|
(?*) cands+=("\${\${line%%$'\\t'*}//:/\\\\:}:\${line#*$'\\t'}") ;;
|
|
32
41
|
esac
|
|
33
42
|
done
|
|
34
|
-
if (( \${#cands} )); then
|
|
35
|
-
_describe -V -t "ork-$(( ++n ))" "$group" cands && ret=0
|
|
36
|
-
fi
|
|
37
43
|
if (( files )); then
|
|
38
44
|
_files && ret=0
|
|
39
45
|
fi
|
|
@@ -71,6 +77,10 @@ _ork() {
|
|
|
71
77
|
*)
|
|
72
78
|
line="\${line%%$'\\t'*}"
|
|
73
79
|
COMPREPLY+=("\${line#"$strip"}")
|
|
80
|
+
# name= is followed by its value, not a space (bash 4 and later).
|
|
81
|
+
if [[ "$line" == *= ]] && type compopt &>/dev/null; then
|
|
82
|
+
compopt -o nospace
|
|
83
|
+
fi
|
|
74
84
|
;;
|
|
75
85
|
esac
|
|
76
86
|
done <<< "$out"
|
|
@@ -274,6 +284,10 @@ const HELP_GROUPS = [
|
|
|
274
284
|
name: "feedback",
|
|
275
285
|
short: "What got in the way in your runs"
|
|
276
286
|
},
|
|
287
|
+
{
|
|
288
|
+
name: "follow-ups",
|
|
289
|
+
short: "What shipped changes left for later"
|
|
290
|
+
},
|
|
277
291
|
{
|
|
278
292
|
name: "worktrees",
|
|
279
293
|
short: "Track and clean up Ork's worktrees"
|