@krapht/ork 1.0.0-alpha.14 → 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.
Files changed (42) hide show
  1. package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
  2. package/dist/{store-CleF7KeS.mjs → attach-BHO3X3cf.mjs} +16 -19
  3. package/dist/{attach-CdtGX9se.mjs → attach-CPespxlQ.mjs} +5 -4
  4. package/dist/bin.mjs +64 -1339
  5. package/dist/brief-za0ozN_E.mjs +58 -0
  6. package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-2bHf76x-.mjs} +1 -1
  7. package/dist/{completion--ywde-56.mjs → completion-CQ2UYKuJ.mjs} +5 -5
  8. package/dist/context-BNqDw9z1.mjs +106 -0
  9. package/dist/{find-BVX-bL4u.mjs → find-BYLlszqj.mjs} +1 -1
  10. package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
  11. package/dist/{format-CvTE5CZQ.mjs → format-C8MsFj8_.mjs} +19 -5
  12. package/dist/{help-groups-CpR2UzSn.mjs → help-groups-CQ98mLjF.mjs} +4 -0
  13. package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
  14. package/dist/{__build_info__-B82-dGJY.mjs → install-DWtXTq4y.mjs} +3 -227
  15. package/dist/{limits-Dhe--wmX.mjs → limits-B1qzS4cX.mjs} +1 -1
  16. package/dist/{mcp-CsmboyXv.mjs → mcp-BOrVkyhN.mjs} +6 -6
  17. package/dist/{once-CrwI-ZdN.mjs → once-C1fL3GVo.mjs} +3 -3
  18. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  19. package/dist/{pause-DssrYcda.mjs → pause-DOMK-R8V.mjs} +3 -3
  20. package/dist/post-BzA6Qobq.mjs +44 -0
  21. package/dist/{relay-DASRgsTQ.mjs → relay-OWuu4fib.mjs} +1 -1
  22. package/dist/run--4-1xAF2.mjs +1451 -0
  23. package/dist/{run-D8divGCs.mjs → run-CiPsW9Kf.mjs} +1 -1
  24. package/dist/{send-CXv8bx0i.mjs → send-CnNNW-ot.mjs} +2 -2
  25. package/dist/{src-njdtejgZ.mjs → src-B9zWIVqo.mjs} +129 -12
  26. package/dist/{src-8Mul0695.mjs → src-DQKz2eOH.mjs} +322 -75
  27. package/dist/{src-C7z_6fhn.mjs → src-DcrhgipG.mjs} +17 -10
  28. package/dist/{src-tsuZRu0H.mjs → src-Dr_fTSu-.mjs} +4832 -4155
  29. package/dist/store-oKJ-cCf3.mjs +16 -0
  30. package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
  31. package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
  32. package/dist/ui/assets/{index-D-_uPI2v.js → index-BtwkftKe.js} +2 -2
  33. package/dist/ui/index.html +1 -1
  34. package/dist/{ui-CNO1M0qT.mjs → ui-CzKlsCnm.mjs} +3 -3
  35. package/dist/{usage-BVXi0iFY.mjs → usage-DHGNk2JL.mjs} +3 -3
  36. package/dist/{context-Y_I4Mofq.mjs → validate-Bs_gg22g.mjs} +4 -104
  37. package/dist/{watch-DVNIciHp.mjs → watch-CCCfw3n7.mjs} +19 -5
  38. package/dist/{workflows-B30QkwTk.mjs → workflows-CW28ZU-z.mjs} +2 -2
  39. package/dist/{worktrees-DD4_TpkH.mjs → worktrees-CyE8aF6j.mjs} +1 -1
  40. package/package.json +1 -1
  41. package/dist/ui/assets/app-D_nAPnHM.css +0 -1
  42. package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
@@ -0,0 +1,58 @@
1
+ import { un as takeFollowUps } from "./src-Dr_fTSu-.mjs";
2
+ import { l as briefOf, n as flagValues, u as checkTakeable } from "./run--4-1xAF2.mjs";
3
+ import { n as newRunId } from "./find-BYLlszqj.mjs";
4
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
5
+ import { t as loadWorkflow } from "./validate-Bs_gg22g.mjs";
6
+ // src/follow-ups/brief.ts
7
+ /**
8
+ * @file `ork run --follow-ups <ids>`: the `agent` follow-ups become the run's
9
+ * `brief` input, and are marked taken by the run, which finishes them when it
10
+ * publishes (`ork follow-ups published`). Everything that could refuse them
11
+ * (an unknown id, one for someone else, a workflow with no `brief` input) is
12
+ * checked before any is marked, so a run that doesn't start leaves them be.
13
+ */
14
+ /** The ids `--follow-ups` was given, one per comma or repeat. */
15
+ const idsOf = (rawArgs) => flagValues(rawArgs, "--follow-ups").flatMap((value) => value.split(",")).map((id) => id.trim()).filter((id) => id !== "");
16
+ /**
17
+ * What stops `--follow-ups` from starting a run, one line each; none when
18
+ * nothing does.
19
+ */
20
+ const problemsOf = (loaded, inputs) => {
21
+ const name = loaded.workflow?.name ?? "";
22
+ if (loaded.workflow?.inputs?.["brief"] === void 0) return [`--follow-ups fills a workflow's \`brief\` input, and ${name} has none.`];
23
+ return inputs.some((input) => input.startsWith("brief=")) ? ["--follow-ups and --input brief=… both set the brief."] : [];
24
+ };
25
+ /**
26
+ * The inputs and run id `ork run` starts with: those it was given and, for
27
+ * `--follow-ups`, a `brief` made of the follow-ups with the id of the run that
28
+ * takes them. `undefined` after saying why it can't start.
29
+ */
30
+ const prepareFollowUps = (store, rawArgs, runId, target) => {
31
+ const inputs = flagValues(rawArgs, "--input");
32
+ const ids = idsOf(rawArgs);
33
+ const plain = {
34
+ inputs,
35
+ ...runId === void 0 ? {} : { runId }
36
+ };
37
+ if (ids.length === 0) return plain;
38
+ const loaded = loadWorkflow(target.file, process.cwd(), void 0, store.projects());
39
+ if (loaded.workflow === null) return plain;
40
+ const takeable = checkTakeable(store, loaded.place.root, ids);
41
+ const problems = [...problemsOf(loaded, inputs), ...takeable.ok ? [] : takeable.lines];
42
+ if (!takeable.ok || problems.length > 0) {
43
+ for (const line of problems) writeErr(line);
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ const taker = runId ?? newRunId(store);
48
+ takeFollowUps(store, {
49
+ project: loaded.place.root,
50
+ run: taker,
51
+ ids: takeable.followUps.map((f) => f.id)
52
+ });
53
+ return {
54
+ inputs: [...inputs, `brief=${briefOf(takeable.followUps)}`],
55
+ runId: taker
56
+ };
57
+ };
58
+ export { prepareFollowUps };
@@ -1,4 +1,4 @@
1
- import { Cr as guardedBase, Tr as writePlainFile, pr as placeFiles, wr as plainPathInside } from "./src-tsuZRu0H.mjs";
1
+ import { Er as placeFiles, Ir as guardedBase, Lr as plainPathInside, Rr as writePlainFile } from "./src-Dr_fTSu-.mjs";
2
2
  import path from "node:path";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { createHash } from "node:crypto";
@@ -1,9 +1,9 @@
1
- import { U as openStore, Ut as liveStatus, W as storePath, ii as AGENT_DRIVERS, mr as placeFor, pr as placeFiles, qr as NodeIdSchema, ut as pendingDecisions } from "./src-tsuZRu0H.mjs";
2
- import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-Bdq23IIi.mjs";
3
- import { t as findRun } from "./find-BVX-bL4u.mjs";
4
- import { a as findEntry, c as subcommandsOf, d as SHELL_SCRIPTS, l as COMPLETE_COMMAND, n as groupedEntries, r as argInfoOf, t as groupOf, u as SHELLS } from "./help-groups-CpR2UzSn.mjs";
1
+ import { Cn as liveStatus, Dr as placeFor, Er as placeFiles, U as openStore, W as storePath, _i as AGENT_DRIVERS, oi as NodeIdSchema, ut as pendingDecisions } from "./src-Dr_fTSu-.mjs";
2
+ import { a as isBuiltinArg, i as builtinWorkflowText, r as builtinWorkflowNames } from "./builtin-files-2bHf76x-.mjs";
3
+ import { t as findRun } from "./find-BYLlszqj.mjs";
4
+ import { a as findEntry, c as subcommandsOf, d as SHELL_SCRIPTS, l as COMPLETE_COMMAND, n as groupedEntries, r as argInfoOf, t as groupOf, u as SHELLS } from "./help-groups-CQ98mLjF.mjs";
5
5
  import { l as writeErr } from "./output-B1kwo0YU.mjs";
6
- import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-B30QkwTk.mjs";
6
+ import { a as resolveWorkflowArg, n as WORKFLOW_SUFFIX } from "./workflows-CW28ZU-z.mjs";
7
7
  import path from "node:path";
8
8
  import { parse } from "yaml";
9
9
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
@@ -0,0 +1,106 @@
1
+ import { Pr as LESSON_CAP_TOKENS, wt as explainSession } from "./src-Dr_fTSu-.mjs";
2
+ import { c as write, s as style } from "./output-B1kwo0YU.mjs";
3
+ import { i as refusedBuiltin, o as workflowArg } from "./workflows-CW28ZU-z.mjs";
4
+ import { a as formatReport, t as loadWorkflow } from "./validate-Bs_gg22g.mjs";
5
+ import { defineCommand } from "citty";
6
+ // src/commands/context.ts
7
+ /**
8
+ * @file `ork context explain <wf> <node>`: what one node's session would load,
9
+ * layer by layer, as core's `explainSession` plans it without writing
10
+ * anything (one of Ork's own `ork:` workflows is written into the project's
11
+ * run folder first, as `ork run` does): its project instructions, every skill
12
+ * with the layer it came from and whether it is live, shadowed or switched
13
+ * off, its context files, its practices, and estimated tokens.
14
+ */
15
+ /** Loads a workflow file and plans one node's session in it, in place. */
16
+ const explainNode = (file, nodeId, cwd, home) => {
17
+ const { report, workflow, graph, place, workflowDir } = loadWorkflow(file, cwd, home);
18
+ if (workflow === null) return {
19
+ ok: false,
20
+ lines: formatReport(report)
21
+ };
22
+ const result = explainSession(workflow, graph, nodeId, {
23
+ root: place.root,
24
+ orkDir: place.dir,
25
+ workflowDir,
26
+ ...home === void 0 ? {} : { home }
27
+ });
28
+ return result.ok ? result : {
29
+ ok: false,
30
+ lines: [result.error]
31
+ };
32
+ };
33
+ const approx = (count) => style("dim", `≈${count.toLocaleString("en")} tokens`);
34
+ const formatExplanation = (e) => {
35
+ const width = Math.max(8, ...e.skills.map((s) => s.name.length));
36
+ const sourceWidth = Math.max(0, ...e.skills.map((s) => s.source.length));
37
+ const lines = [
38
+ `${style("bold", e.node)} ${style("dim", `(${e.type}) in ${e.workflow}`)}${e.template === void 0 ? "" : style("dim", `, planned as ${e.template}, the node it was spawned from`)}`,
39
+ "",
40
+ ...e.agent === void 0 ? [] : [`${style("bold", "Agent")} ${e.agent.name} ${style("dim", `(${e.agent.where})`)} ${approx(e.agent.tokens)}`],
41
+ `${style("bold", "Instructions")} ${e.instructions}`,
42
+ "",
43
+ style("bold", "Skills")
44
+ ];
45
+ if (e.skills.length === 0) lines.push(style("dim", " none"));
46
+ for (const s of e.skills) {
47
+ const mark = s.state === "live" ? style("green", "✓") : style("dim", "−");
48
+ const state = s.why === void 0 ? approx(s.tokens).replace("tokens", "tokens when used") : style("yellow", `off: ${s.why}`);
49
+ lines.push(` ${mark} ${s.name.padEnd(width)} ${s.layer.padEnd(8)} ${style("dim", s.source.padEnd(sourceWidth))} ${state}`);
50
+ }
51
+ lines.push("", style("bold", "Context files"));
52
+ if (e.files.length === 0) lines.push(style("dim", " none"));
53
+ for (const f of e.files) {
54
+ const cut = f.cutFrom === void 0 ? "" : ` ${style("yellow", `cut at the ${LESSON_CAP_TOKENS.toLocaleString("en")}-token cap; the whole file is ${approx(f.cutFrom)}`)}`;
55
+ lines.push(` ${f.layer.padEnd(8)} ${f.name} ${approx(f.tokens)}${cut}`);
56
+ }
57
+ lines.push("", style("bold", "Practices"));
58
+ for (const p of e.practices) lines.push(` ${p.name.padEnd(8)} ${approx(p.tokens)}`);
59
+ for (const name of e.skippedPractices) lines.push(` ${name.padEnd(8)} ${style("dim", "the project's own skill wins")}`);
60
+ if (e.practices.length === 0 && e.skippedPractices.length === 0) lines.push(style("dim", " off"));
61
+ lines.push("", `Appended to Claude Code's system prompt: ${approx(e.appendedTokens)}`);
62
+ return lines;
63
+ };
64
+ const explain = defineCommand({
65
+ meta: {
66
+ name: "explain",
67
+ description: "Show what one node's session loads, layer by layer"
68
+ },
69
+ args: {
70
+ workflow: {
71
+ type: "positional",
72
+ description: "A workflow file, the name of one in the project's workflows (.ork/workflows/), or one of Ork's own (ork:feedback-apply)",
73
+ required: true
74
+ },
75
+ node: {
76
+ type: "positional",
77
+ description: "Node id",
78
+ required: true
79
+ },
80
+ json: {
81
+ type: "boolean",
82
+ description: "Print JSON",
83
+ default: false
84
+ }
85
+ },
86
+ run: ({ args }) => {
87
+ const cwd = process.cwd();
88
+ if (refusedBuiltin([args.workflow], cwd)) return;
89
+ const result = explainNode(workflowArg(args.workflow, cwd).file, args.node, cwd);
90
+ if (!result.ok) {
91
+ for (const line of result.lines) process.stderr.write(`${line}\n`);
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ if (args.json) write(JSON.stringify(result.explanation, void 0, 2));
96
+ else for (const line of formatExplanation(result.explanation)) write(line);
97
+ }
98
+ });
99
+ const context = defineCommand({
100
+ meta: {
101
+ name: "context",
102
+ description: "Inspect the context Ork compiles for each node"
103
+ },
104
+ subCommands: { explain }
105
+ });
106
+ export { explainNode as n, context as t };
@@ -1,4 +1,4 @@
1
- import { mr as placeFor } from "./src-tsuZRu0H.mjs";
1
+ import { Dr as placeFor } from "./src-Dr_fTSu-.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  // ../core/src/remote/find.ts
4
4
  /**
@@ -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 };