@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
@@ -1,79 +1,15 @@
1
- import { An as END_KINDS, Ar as readDocument, C as capsHere, D as runningScript, Dn as endOf, Dr as WorkflowEditSchema, E as runningRunIds, Er as skillsIn, Fn as DISCUSSIONS, Fr as parseWorkflow, Gt as pausedOf, H as isNodeEvent, In as planPost, It as checkWorkflowText, Kt as runStatus, Ln as runGh, Lt as walkChecks, N as triggersReport, O as detectInstaller, Or as applyEdits, Ot as limitsReport, Pr as renderJsonSchema, Qt as nestItems, Rn as sendPost, Rr as EdgeInputSchema, Rt as walkSummary, S as usageReport, St as resumable, T as releaseNotes, Tt as followEvents, U as openStore, Ur as NodeSchema, Ut as liveStatus, V as activeTree, Vr as expandEdge, W as storePath, Yt as processAlive, _ as keyOf, _n as nodeSays, _t as requestPause, a as planRemove, b as stepPrunes, d as planLine, f as removeRefusal, g as cleanStep, gr as runDirOf, gt as requestContinue, h as applyClean, ht as unlinkNodes, i as lostWork, jt as explainWorkflowText, kr as newWorkflowText, l as labelOf, lr as loadAgents, lt as answerDecision, mr as placeFor, mt as linkNodes, o as projectHead, p as stateParts, pr as placeFiles, qn as readCondition, r as applyRemove, s as readWorktrees, u as mergeStateOf, ut as pendingDecisions, v as planClean, vr as workflowDirOf, vt as requestStop, w as planUpgrade, wt as detachRun, xr as LESSON_CAP_TOKENS, xt as detachResume, y as stepActs, yr as inside, yt as tellLiveRun } from "./src-tsuZRu0H.mjs";
2
- import { C as feedbackActions, S as placeOf$1, _ as previewPost, a as registryShown, b as acceptItem, c as checkDue, f as readUpdateCache, g as nodeSession, h as writeUpdateCache, i as registryOf, l as fetchLatest, m as updateCachePath, o as updateSwitchOff, p as refreshUpdateCache, s as runInstall, u as newerVersion, w as openFixRun, x as lessonPlaceOf, y as recordNote } from "./__build_info__-B82-dGJY.mjs";
3
- import { t as builtinProblem } from "./builtin-files-Bdq23IIi.mjs";
4
- import { n as newRunId, t as findRun } from "./find-BVX-bL4u.mjs";
1
+ import { $n as DISCUSSIONS, Cn as linkNodes, D as triggersReport, Dn as requestStop, En as requestPause, F as activeTree, Fn as processAlive, Gn as endOf, Gt as applyClean, Hn as loadProject, I as isNodeEvent, Ir as runDirOf, Jn as END_KINDS, Jr as applyEdits, Jt as planClean, Kr as skillsIn, Kt as cleanStep, L as openStore, Mn as runStatus, Nr as placeFiles, On as tellLiveRun, Pr as placeFor, R as storePath, Rn as nestItems, Rr as workflowDirOf, S as detectInstaller, St as walkSummary, Tn as requestContinue, Vr as LESSON_CAP_TOKENS, Xr as readDocument, Xt as stepPrunes, Yr as newWorkflowText, Yt as stepActs, _ as capsHere, a as planRemove, an as nodeSays, b as runningRunIds, bn as DEFAULT_FOLLOW_UPS_RULE, bt as checkWorkflowText, d as planLine, ei as renderJsonSchema, er as planPost, f as removeRefusal, fn as checkFollowUpsDue, fr as readCondition, g as usageReport, hn as startFollowUpChecks, ht as explainWorkflowText, i as lostWork, ii as EdgeInputSchema, jn as pausedOf, kn as liveStatus, kr as loadAgents, l as labelOf, li as NodeSchema, lt as detachRun, nr as sendPost, nt as answerDecision, o as projectHead, ot as detachResume, p as stateParts, pt as limitsReport, qr as WorkflowEditSchema, qt as keyOf, r as applyRemove, rt as pendingDecisions, s as readWorktrees, si as expandEdge, st as resumable, ti as parseWorkflow, tr as runGh, u as mergeStateOf, ut as followEvents, v as planUpgrade, wn as unlinkNodes, x as runningScript, xt as walkChecks, y as releaseNotes, yn as liveSessionOf, zr as inside } from "./src-DN-RPsil.mjs";
2
+ import { a as acceptItem, c as feedbackActions, i as recordNote, l as openFixRun, n as previewPost, o as lessonPlaceOf, s as placeOf$1, t as runInstall } from "./install-23g_1ngs.mjs";
3
+ import { _ as dueCause, a as registryShown, c as fetchLatest, d as readUpdateCache, f as refreshUpdateCache, g as changeFollowUp, h as nodeSession, i as registryOf, l as newerVersion, m as writeUpdateCache, o as updateSwitchOff, p as updateCachePath, s as checkDue, v as findFollowUp, y as followUpActions } from "./__build_info__-DcRdK0Du.mjs";
4
+ import { t as postFollowUpMessage } from "./post-C9soCx7i.mjs";
5
+ import { t as builtinProblem } from "./builtin-files-BRSOObAv.mjs";
6
+ import { n as newRunId, t as findRun } from "./find-mLYsR0uC.mjs";
5
7
  import path from "node:path";
6
8
  import { z } from "zod";
7
9
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
10
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
9
11
  import { homedir } from "node:os";
10
12
  import { createServer } from "node:http";
11
- // ../server/src/contract-builder.ts
12
- /** A new workflow's name, as its file and its `name:` take it. */
13
- const NewWorkflowSchema = z.strictObject({ name: z.string().min(1) });
14
- /** The server checks each edit; this file imports only types of the schema. */
15
- const WorkflowEditsSchema = z.strictObject({ edits: z.array(z.custom()).min(1) });
16
- /** Earlier text, and the file it was based on, to ask before replacing it. */
17
- const DraftTextSchema = z.strictObject({
18
- text: z.string(),
19
- baseHash: z.string().nullable()
20
- });
21
- const SaveWorkflowSchema = z.strictObject({
22
- /** Write over a file that changed since this draft began. */
23
- force: z.boolean().optional() });
24
- const BUILDER_API = {
25
- builderSchema: {
26
- method: "GET",
27
- path: "/api/workflow-schema"
28
- },
29
- builderCatalog: {
30
- method: "GET",
31
- path: "/api/projects/:id/catalog"
32
- },
33
- builderDrafts: {
34
- method: "GET",
35
- path: "/api/projects/:id/builder"
36
- },
37
- builderView: {
38
- method: "GET",
39
- path: "/api/projects/:id/builder/:name"
40
- },
41
- builderCreate: {
42
- method: "POST",
43
- path: "/api/projects/:id/builder",
44
- body: NewWorkflowSchema
45
- },
46
- builderEdit: {
47
- method: "POST",
48
- path: "/api/projects/:id/builder/:name/edits",
49
- body: WorkflowEditsSchema
50
- },
51
- builderRestore: {
52
- method: "POST",
53
- path: "/api/projects/:id/builder/:name/text",
54
- body: DraftTextSchema
55
- },
56
- builderDiscard: {
57
- method: "POST",
58
- path: "/api/projects/:id/builder/:name/discard"
59
- },
60
- builderSave: {
61
- method: "POST",
62
- path: "/api/projects/:id/builder/:name/save",
63
- body: SaveWorkflowSchema
64
- }
65
- };
66
- // ../server/src/contract-context.ts
67
- const CONTEXT_API = {
68
- runContext: {
69
- method: "GET",
70
- path: "/api/runs/:id/nodes/:node/context"
71
- },
72
- builderContext: {
73
- method: "GET",
74
- path: "/api/projects/:id/builder/:name/nodes/:node/context"
75
- }
76
- };
77
13
  // ../server/src/contract-feedback.ts
78
14
  const NoteSchema = z.strictObject({
79
15
  /** Its first line is the title. */
@@ -153,6 +89,133 @@ const FEEDBACK_API = {
153
89
  path: "/api/feedback/:id/split"
154
90
  }
155
91
  };
92
+ // ../server/src/contract-follow-ups.ts
93
+ /** `open` is due and waiting, the default; `all` adds done and dropped. */
94
+ const FollowUpsQuerySchema = z.strictObject({
95
+ status: z.enum([
96
+ "open",
97
+ "all",
98
+ "due",
99
+ "waiting",
100
+ "done",
101
+ "dropped"
102
+ ]).optional(),
103
+ /** Only those for this: `agent`, `me`, `project:<name>` or `person:<name>`. */
104
+ for: z.string().min(1).optional()
105
+ });
106
+ const FollowUpChangeSchema = z.strictObject({ action: z.enum([
107
+ "done",
108
+ "drop",
109
+ "reopen",
110
+ "due"
111
+ ]) });
112
+ /**
113
+ * Posts a drafted GitHub comment through the person's own `gh`. `confirm` says
114
+ * yes, and `digest` is the detail's `draftDigest`, which stands for the
115
+ * recipient and text they saw: the route refuses when the draft is no longer
116
+ * that, so nothing else is sent.
117
+ */
118
+ const FollowUpPostSchema = z.strictObject({
119
+ confirm: z.literal(true),
120
+ digest: z.string().min(1)
121
+ });
122
+ const FOLLOW_UPS_API = {
123
+ followUps: {
124
+ method: "GET",
125
+ path: "/api/projects/:id/follow-ups",
126
+ query: FollowUpsQuerySchema
127
+ },
128
+ followUp: {
129
+ method: "GET",
130
+ path: "/api/projects/:id/follow-ups/:followUp"
131
+ },
132
+ followUpChange: {
133
+ method: "POST",
134
+ path: "/api/projects/:id/follow-ups/:followUp",
135
+ body: FollowUpChangeSchema
136
+ },
137
+ followUpPost: {
138
+ method: "POST",
139
+ path: "/api/projects/:id/follow-ups/:followUp/post",
140
+ body: FollowUpPostSchema
141
+ }
142
+ };
143
+ // ../server/src/contract-after.ts
144
+ /**
145
+ * @file What the API offers after runs: the feedback loop's routes
146
+ * (contract-feedback.ts) and the follow-ups' (contract-follow-ups.ts), joined
147
+ * so the contract reads one part for both.
148
+ */
149
+ const AFTER_API = {
150
+ ...FEEDBACK_API,
151
+ ...FOLLOW_UPS_API
152
+ };
153
+ // ../server/src/contract-builder.ts
154
+ /** A new workflow's name, as its file and its `name:` take it. */
155
+ const NewWorkflowSchema = z.strictObject({ name: z.string().min(1) });
156
+ /** The server checks each edit; this file imports only types of the schema. */
157
+ const WorkflowEditsSchema = z.strictObject({ edits: z.array(z.custom()).min(1) });
158
+ /** Earlier text, and the file it was based on, to ask before replacing it. */
159
+ const DraftTextSchema = z.strictObject({
160
+ text: z.string(),
161
+ baseHash: z.string().nullable()
162
+ });
163
+ const SaveWorkflowSchema = z.strictObject({
164
+ /** Write over a file that changed since this draft began. */
165
+ force: z.boolean().optional() });
166
+ const BUILDER_API = {
167
+ builderSchema: {
168
+ method: "GET",
169
+ path: "/api/workflow-schema"
170
+ },
171
+ builderCatalog: {
172
+ method: "GET",
173
+ path: "/api/projects/:id/catalog"
174
+ },
175
+ builderDrafts: {
176
+ method: "GET",
177
+ path: "/api/projects/:id/builder"
178
+ },
179
+ builderView: {
180
+ method: "GET",
181
+ path: "/api/projects/:id/builder/:name"
182
+ },
183
+ builderCreate: {
184
+ method: "POST",
185
+ path: "/api/projects/:id/builder",
186
+ body: NewWorkflowSchema
187
+ },
188
+ builderEdit: {
189
+ method: "POST",
190
+ path: "/api/projects/:id/builder/:name/edits",
191
+ body: WorkflowEditsSchema
192
+ },
193
+ builderRestore: {
194
+ method: "POST",
195
+ path: "/api/projects/:id/builder/:name/text",
196
+ body: DraftTextSchema
197
+ },
198
+ builderDiscard: {
199
+ method: "POST",
200
+ path: "/api/projects/:id/builder/:name/discard"
201
+ },
202
+ builderSave: {
203
+ method: "POST",
204
+ path: "/api/projects/:id/builder/:name/save",
205
+ body: SaveWorkflowSchema
206
+ }
207
+ };
208
+ // ../server/src/contract-context.ts
209
+ const CONTEXT_API = {
210
+ runContext: {
211
+ method: "GET",
212
+ path: "/api/runs/:id/nodes/:node/context"
213
+ },
214
+ builderContext: {
215
+ method: "GET",
216
+ path: "/api/projects/:id/builder/:name/nodes/:node/context"
217
+ }
218
+ };
156
219
  // ../server/src/contract-links.ts
157
220
  /**
158
221
  * @file The routes that link two nodes of live runs (RFC-003): list, link and
@@ -320,7 +383,7 @@ const API = {
320
383
  },
321
384
  ...BUILDER_API,
322
385
  ...CONTEXT_API,
323
- ...FEEDBACK_API,
386
+ ...AFTER_API,
324
387
  ...WORKTREES_API,
325
388
  ...UPDATE_API,
326
389
  projects: {
@@ -1045,17 +1108,14 @@ const checkTool = (context) => tool("check", "Checks a workflow as `ork validate
1045
1108
  return answer({
1046
1109
  errors: count("error"),
1047
1110
  warnings: count("warning"),
1048
- diagnostics: diagnostics.map(({ severity, code, message, line, column }) => line === void 0 ? {
1111
+ diagnostics: diagnostics.map(({ severity, code, message, file, line, column }) => Object.assign({
1049
1112
  severity,
1050
1113
  code,
1051
1114
  message
1052
- } : {
1053
- severity,
1054
- code,
1055
- message,
1115
+ }, file === void 0 ? {} : { file }, line === void 0 ? {} : {
1056
1116
  line,
1057
1117
  column
1058
- }),
1118
+ })),
1059
1119
  walk: walk === void 0 ? null : walkSummary(walk),
1060
1120
  ...args.paths === true && walk !== void 0 ? { endings: walk.endings.slice(0, MOST_ENDINGS) } : {}
1061
1121
  });
@@ -2021,7 +2081,7 @@ const applyRoute = async (context, item, driver) => {
2021
2081
  * workflows would go and how big that file is, the project a fix would be
2022
2082
  * proposed in, and the run proposing one now, if any.
2023
2083
  */
2024
- const detailOf = (store, item) => {
2084
+ const detailOf$1 = (store, item) => {
2025
2085
  const registered = store.projects();
2026
2086
  const fixRun = openFixRun(store, item);
2027
2087
  const actions = feedbackActions(item, fixRun);
@@ -2124,7 +2184,7 @@ const feedbackRoutes = (context) => {
2124
2184
  feedbackItem: ({ params }) => {
2125
2185
  const found = itemOf(store, params.id);
2126
2186
  if (!found.ok) return found.refusal;
2127
- return ok(detailOf(store, found.item));
2187
+ return ok(detailOf$1(store, found.item));
2128
2188
  },
2129
2189
  feedbackStatus: ({ params, body }) => {
2130
2190
  const found = itemOf(store, params.id);
@@ -2173,6 +2233,172 @@ const feedbackRoutes = (context) => {
2173
2233
  }
2174
2234
  };
2175
2235
  };
2236
+ // ../server/src/follow-up-routes.ts
2237
+ /**
2238
+ * @file The follow-ups' routes (specs/follow-ups.md), through the same core
2239
+ * functions as `ork follow-ups`: a project's list (after the due check, which
2240
+ * reads pull requests through the person's own `gh` and only reports a `gh`
2241
+ * that fails), one follow-up with what to copy and what can be done to it,
2242
+ * done, drop, reopen and due, and posting a drafted GitHub comment only with
2243
+ * the person's confirmation of the draft they saw (its digest). Ork never
2244
+ * runs a task's commands.
2245
+ */
2246
+ const OPEN = ["due", "waiting"];
2247
+ /** Stands for what a person saw of a draft: its recipient and text. */
2248
+ const digestOf$1 = (f) => f.draft === void 0 ? void 0 : createHash("sha256").update(JSON.stringify([f.draft.to, f.draft.text])).digest("hex");
2249
+ /** The place a project's rule keeps follow-ups in. */
2250
+ const ruleOf = (project) => (loadProject(project.root, placeFiles(project).rules).project?.followUps ?? DEFAULT_FOLLOW_UPS_RULE).store;
2251
+ /** Why a follow-up isn't where the project's rule says, when it isn't. */
2252
+ const fallbackOf = (placed, rule) => {
2253
+ if (placed === rule) return void 0;
2254
+ return rule === "github" ? "GitHub couldn't be used when it was added (gh not signed in, or the issue failed), so Ork's store keeps it." : `It couldn't be written to ${rule} when it was added, so Ork's store keeps it.`;
2255
+ };
2256
+ const deliveryOf = (store, f) => {
2257
+ if (!f.for.startsWith("project:")) return void 0;
2258
+ const name = f.for.slice(8).trim();
2259
+ const target = store.projects().find((p) => p.id === name);
2260
+ if (target === void 0) return {
2261
+ project: name,
2262
+ registered: false
2263
+ };
2264
+ const live = liveSessionOf(store, target.root);
2265
+ return {
2266
+ project: name,
2267
+ registered: true,
2268
+ root: target.root,
2269
+ keptIn: ruleOf(target),
2270
+ ...live === void 0 ? {} : { live }
2271
+ };
2272
+ };
2273
+ /** `f`, what can be done to it, where it is kept, and what to copy from it. */
2274
+ const detailOf = (context, project, f) => {
2275
+ const rule = ruleOf(project);
2276
+ const fallback = fallbackOf(f.place, rule);
2277
+ const cause = dueCause(f);
2278
+ const delivery = deliveryOf(context.store, f);
2279
+ const digest = digestOf$1(f);
2280
+ return {
2281
+ followUp: f,
2282
+ actions: followUpActions(f),
2283
+ ...cause === void 0 ? {} : { cause },
2284
+ keptIn: {
2285
+ place: f.place,
2286
+ rule,
2287
+ ...f.url === void 0 ? {} : { url: f.url },
2288
+ ...fallback === void 0 ? {} : { fallback }
2289
+ },
2290
+ ...delivery === void 0 ? {} : { delivery },
2291
+ ...digest === void 0 ? {} : { draftDigest: digest },
2292
+ copy: {
2293
+ text: f.draft?.text ?? "",
2294
+ commands: (f.steps ?? []).flatMap((step) => step.command === void 0 ? [] : [step.command])
2295
+ }
2296
+ };
2297
+ };
2298
+ const toItem = (f) => ({
2299
+ id: f.id,
2300
+ project: f.project,
2301
+ title: f.title,
2302
+ for: f.for,
2303
+ kind: f.kind,
2304
+ ...f.note === void 0 ? {} : { note: f.note }
2305
+ });
2306
+ /** The due follow-ups of `roots`, most recently changed first. */
2307
+ const dueOf = (store, roots) => {
2308
+ const due = roots.flatMap((root) => store.followUps(root)).filter((f) => f.status === "due").toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt));
2309
+ return {
2310
+ count: due.length,
2311
+ items: due.slice(0, 20).map((f) => toItem(f))
2312
+ };
2313
+ };
2314
+ const found = (context, params) => {
2315
+ const project = projectOf(context, params.id);
2316
+ if (project === void 0) return {
2317
+ ok: false,
2318
+ refusal: fail(404, `No registered project \`${params.id}\`.`)
2319
+ };
2320
+ if (params.followUp === void 0) return {
2321
+ ok: true,
2322
+ project
2323
+ };
2324
+ const one = findFollowUp(context.store, project.root, params.followUp);
2325
+ if (!one.ok) {
2326
+ const status = one.error.startsWith("No follow-up") ? 404 : 400;
2327
+ return {
2328
+ ok: false,
2329
+ refusal: fail(status, one.error)
2330
+ };
2331
+ }
2332
+ return {
2333
+ ok: true,
2334
+ project,
2335
+ followUp: one.followUp
2336
+ };
2337
+ };
2338
+ const followUpRoutes = (context) => {
2339
+ const { store } = context;
2340
+ const gh = context.gh ?? runGh;
2341
+ return {
2342
+ followUps: async ({ params, query }) => {
2343
+ const one = found(context, params);
2344
+ if (!one.ok) return one.refusal;
2345
+ const { root } = one.project;
2346
+ const errors = await checkFollowUpsDue({
2347
+ rows: store,
2348
+ project: root,
2349
+ gh
2350
+ }).then((checked) => checked.errors).catch((error) => [error instanceof Error ? error.message : String(error)]);
2351
+ const all = store.followUps(root);
2352
+ const status = query.status ?? "open";
2353
+ const shown = all.filter((f) => {
2354
+ if (query.for !== void 0 && f.for !== query.for) return false;
2355
+ if (status === "all") return true;
2356
+ if (status === "open") return OPEN.some((s) => s === f.status);
2357
+ return f.status === status;
2358
+ });
2359
+ return ok({
2360
+ project: root,
2361
+ followUps: shown,
2362
+ due: all.filter((f) => f.status === "due").length,
2363
+ checker: {
2364
+ available: errors.length === 0,
2365
+ errors
2366
+ }
2367
+ });
2368
+ },
2369
+ followUp: ({ params }) => {
2370
+ const one = found(context, params);
2371
+ if (!one.ok) return one.refusal;
2372
+ return one.followUp === void 0 ? fail(404, "No such follow-up.") : ok(detailOf(context, one.project, one.followUp));
2373
+ },
2374
+ followUpChange: ({ params, body }) => {
2375
+ const one = found(context, params);
2376
+ if (!one.ok) return one.refusal;
2377
+ if (one.followUp === void 0) return fail(404, "No such follow-up.");
2378
+ const changed = changeFollowUp(store, one.followUp, body.action);
2379
+ return changed.ok ? ok(detailOf(context, one.project, changed.followUp)) : fail(409, changed.error);
2380
+ },
2381
+ followUpPost: async ({ params, body }) => {
2382
+ const one = found(context, params);
2383
+ if (!one.ok) return one.refusal;
2384
+ const { followUp } = one;
2385
+ if (followUp === void 0) return fail(404, "No such follow-up.");
2386
+ const check = followUpActions(followUp).post;
2387
+ if (!check.ok) return fail(409, check.error);
2388
+ if (digestOf$1(followUp) !== body.digest) return fail(409, "The draft is no longer the one you confirmed; open the follow-up again.");
2389
+ const posted = await postFollowUpMessage(followUp, {
2390
+ rows: store,
2391
+ gh,
2392
+ confirmed: body.confirm,
2393
+ home: context.home ?? homedir()
2394
+ });
2395
+ return posted.ok ? ok({
2396
+ ...posted.url === void 0 ? {} : { url: posted.url },
2397
+ followUp: detailOf(context, one.project, posted.followUp)
2398
+ }) : fail(409, posted.error);
2399
+ }
2400
+ };
2401
+ };
2176
2402
  const ANSI = /\u001B\[[0-?]*[ -/]*[@-~]|\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)|\u001B[@-Z\\-_]/gu;
2177
2403
  /** A line as a terminal would end up showing it. */
2178
2404
  const plainLine = (raw) => (raw.replaceAll(ANSI, "").split("\r").findLast((part) => part !== "") ?? "").replaceAll(/\s+$/gu, "");
@@ -2727,6 +2953,7 @@ const apiRoutes = (context, port) => ({
2727
2953
  ...builderRoutes(context),
2728
2954
  ...contextRoutes(context),
2729
2955
  ...feedbackRoutes(context),
2956
+ ...followUpRoutes(context),
2730
2957
  ...worktreesRoutes(context),
2731
2958
  ...updateRoutes(context, port)
2732
2959
  });
@@ -2885,7 +3112,11 @@ const overviewStream = (context, res, url, shutdown) => {
2885
3112
  const last = {
2886
3113
  runs: "",
2887
3114
  decisions: "",
2888
- version: ""
3115
+ version: "",
3116
+ followUps: JSON.stringify({
3117
+ count: 0,
3118
+ items: []
3119
+ })
2889
3120
  };
2890
3121
  const tick = () => {
2891
3122
  const filter = root === void 0 ? {} : { project: root };
@@ -2900,7 +3131,8 @@ const overviewStream = (context, res, url, shutdown) => {
2900
3131
  for (const [name, value] of [
2901
3132
  ["runs", runs],
2902
3133
  ["decisions", decisions],
2903
- ["version", context.updates.info()]
3134
+ ["version", context.updates.info()],
3135
+ ["followUps", dueOf(context.store, root === void 0 ? context.store.projects().map((p) => p.root) : [root])]
2904
3136
  ]) {
2905
3137
  const text = JSON.stringify(value);
2906
3138
  if (text === last[name]) continue;
@@ -3058,6 +3290,8 @@ const runningServer = (file) => {
3058
3290
  */
3059
3291
  /** The port `ork ui` serves on unless told otherwise. */
3060
3292
  const DEFAULT_PORT = 4417;
3293
+ /** The overview stream sends the due follow-ups itself; nothing more here. */
3294
+ const noticed = () => {};
3061
3295
  const listen = (server, port) => new Promise((resolve, reject) => {
3062
3296
  server.once("error", (error) => {
3063
3297
  reject(error.code === "EADDRINUSE" ? /* @__PURE__ */ new Error(`Port ${String(port)} is in use; start Ork's UI on another with \`--port <n>\`.`) : error);
@@ -3119,6 +3353,15 @@ const startServer = async (options) => {
3119
3353
  token,
3120
3354
  startedAt
3121
3355
  });
3356
+ const stopChecks = startFollowUpChecks({
3357
+ rows: store,
3358
+ gh: options.gh ?? runGh,
3359
+ projects: () => store.projects().map((project) => project.root),
3360
+ onDue: noticed,
3361
+ onError: (error) => {
3362
+ log(`ork ui: follow-ups: ${error}`);
3363
+ }
3364
+ });
3122
3365
  let closed;
3123
3366
  return {
3124
3367
  port,
@@ -3131,6 +3374,7 @@ const startServer = async (options) => {
3131
3374
  close: () => {
3132
3375
  closed ??= new Promise((resolve) => {
3133
3376
  shutdown.abort();
3377
+ stopChecks();
3134
3378
  updates.close();
3135
3379
  server.close(() => {
3136
3380
  store.close();
@@ -0,0 +1,16 @@
1
+ import { L as openStore } from "./src-DN-RPsil.mjs";
2
+ // src/store.ts
3
+ /**
4
+ * @file Opening the user's store (`~/.ork/state.db`) for the length of one
5
+ * command, and closing it after, whatever happens.
6
+ */
7
+ /** Runs `body` with the user's store open, and closes it after. */
8
+ const withStore = async (body) => {
9
+ const store = openStore();
10
+ try {
11
+ await body(store);
12
+ } finally {
13
+ store.close();
14
+ }
15
+ };
16
+ export { withStore as t };