@krapht/ork 1.0.0-alpha.15 → 1.0.0-alpha.17

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 (50) hide show
  1. package/README.md +10 -4
  2. package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-C1Nm0hlJ.mjs} +25 -6
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CPespxlQ.mjs → attach-EP374oMP.mjs} +5 -5
  5. package/dist/{attach-BHO3X3cf.mjs → attach-RQx__RzH.mjs} +46 -8
  6. package/dist/bin.mjs +238 -369
  7. package/dist/brief-Bs3cMQU0.mjs +100 -0
  8. package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-yYaNTQRU.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion-CQ2UYKuJ.mjs → completion-DIXWlnAl.mjs} +87 -6
  11. package/dist/{context-BNqDw9z1.mjs → context-Dq-VM_sq.mjs} +3 -3
  12. package/dist/{find-BYLlszqj.mjs → find-k489laBa.mjs} +2 -2
  13. package/dist/flags-B4ZZWspN.mjs +125 -0
  14. package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DD8NTPe6.mjs} +59 -24
  15. package/dist/{format-C8MsFj8_.mjs → format-DQc9A_ei.mjs} +17 -4
  16. package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
  17. package/dist/in-a-run-D_9GlAlS.mjs +27 -0
  18. package/dist/{install-DWtXTq4y.mjs → install-PVlV2QPq.mjs} +1 -1
  19. package/dist/{limits-B1qzS4cX.mjs → limits-CHqBO7wb.mjs} +1 -1
  20. package/dist/{mcp-BOrVkyhN.mjs → mcp-D38Ajb0b.mjs} +6 -6
  21. package/dist/{once-C1fL3GVo.mjs → once-DuUzpzL2.mjs} +3 -3
  22. package/dist/parse-BzWpMN-a.mjs +107 -0
  23. package/dist/{pause-DOMK-R8V.mjs → pause-Bx3ePGrn.mjs} +3 -3
  24. package/dist/{post-BzA6Qobq.mjs → post-2CaJz_mV.mjs} +5 -3
  25. package/dist/print-n2qetUHc.mjs +19 -0
  26. package/dist/project-records-Cune1l6I.mjs +230 -0
  27. package/dist/{relay-OWuu4fib.mjs → relay-D7RN4m68.mjs} +1 -1
  28. package/dist/{run-CiPsW9Kf.mjs → run-BfxdsQJk.mjs} +1 -1
  29. package/dist/{run--4-1xAF2.mjs → run-OOWQIKMJ.mjs} +33 -20
  30. package/dist/{send-CnNNW-ot.mjs → send-CKth_vgn.mjs} +2 -2
  31. package/dist/{src-B9zWIVqo.mjs → src-BHod38UQ.mjs} +104 -72
  32. package/dist/{src-Dr_fTSu-.mjs → src-DLsFsmOD.mjs} +790 -502
  33. package/dist/{src-DQKz2eOH.mjs → src-DOjTY2ET.mjs} +238 -79
  34. package/dist/{src-DcrhgipG.mjs → src-DmmrX2_C.mjs} +162 -58
  35. package/dist/{store-oKJ-cCf3.mjs → store-DACH768X.mjs} +1 -1
  36. package/dist/ui/assets/app-9Q-62Ywz.css +1 -0
  37. package/dist/ui/assets/app-CN3acQ-1.js +111 -0
  38. package/dist/ui/assets/{index-BtwkftKe.js → index-B2uGxA4W.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CzKlsCnm.mjs → ui-CJ-BX12n.mjs} +3 -3
  41. package/dist/{usage-DHGNk2JL.mjs → usage-DxZ6JGd4.mjs} +62 -107
  42. package/dist/{validate-Bs_gg22g.mjs → validate-BS0nCccW.mjs} +35 -8
  43. package/dist/{watch-CCCfw3n7.mjs → watch-Dp7IB4FR.mjs} +5 -5
  44. package/dist/{workflows-CW28ZU-z.mjs → workflows-Djo1edlK.mjs} +2 -2
  45. package/dist/{worktrees-CyE8aF6j.mjs → worktrees-Drof_Nyx.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/brief-za0ozN_E.mjs +0 -58
  48. package/dist/in-a-run-Bp0k0J09.mjs +0 -24
  49. package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
  50. package/dist/ui/assets/app-Drp8Dm9y.js +0 -111
@@ -1,15 +1,87 @@
1
- import { At as walkSummary, Br as WorkflowEditSchema, C as capsHere, Cn as liveStatus, Ct as explainWorkflowText, D as runningScript, Dr as placeFor, E as runningRunIds, En as runStatus, Er as placeFiles, H as isNodeEvent, Hn as END_KINDS, Hr as newWorkflowText, In as loadProject, Jn as planPost, Jr as parseWorkflow, Mn as nestItems, Mr as inside, N as triggersReport, O as detectInstaller, Ot as checkWorkflowText, Pr as LESSON_CAP_TOKENS, S as usageReport, Sn as tellLiveRun, Sr as loadAgents, T as releaseNotes, Tn as pausedOf, U as openStore, Ur as readDocument, V as activeTree, Vr as applyEdits, W as storePath, Xn as sendPost, Yn as runGh, Zr as EdgeInputSchema, _ as keyOf, _n as linkNodes, _t as followEvents, a as planRemove, b as stepPrunes, bn as requestPause, bt as limitsReport, d as planLine, ei as expandEdge, en as nodeSays, f as removeRefusal, g as cleanStep, gt as detachRun, h as applyClean, i as lostWork, jr as workflowDirOf, kn as processAlive, kr as runDirOf, kt as walkChecks, l as labelOf, ln as startFollowUpChecks, lt as answerDecision, mn as DEFAULT_FOLLOW_UPS_RULE, mt as resumable, ni as NodeSchema, o as projectHead, on as checkFollowUpsDue, or as readCondition, p as stateParts, pn as liveSessionOf, pt as detachResume, qn as DISCUSSIONS, qr as renderJsonSchema, r as applyRemove, s as readWorktrees, u as mergeStateOf, ut as pendingDecisions, v as planClean, vn as unlinkNodes, w as planUpgrade, xn as requestStop, y as stepActs, yn as requestContinue, zn as endOf, zr as skillsIn } from "./src-Dr_fTSu-.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-DWtXTq4y.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__-Bsb1kjgF.mjs";
4
- import { t as postFollowUpMessage } from "./post-BzA6Qobq.mjs";
5
- import { t as builtinProblem } from "./builtin-files-2bHf76x-.mjs";
6
- import { n as newRunId, t as findRun } from "./find-BYLlszqj.mjs";
1
+ import { An as liveStatus, Ar as loadAgents, Br as inside, Bt as decisionListTable, D as triggersReport, Dn as requestPause, En as requestContinue, F as activeTree, Fr as placeFor, Gt as worktreeListTable, Hr as LESSON_CAP_TOKENS, Ht as followUpListTable, I as isNodeEvent, In as processAlive, Jr as WorkflowEditSchema, Jt as keyOf, Kn as endOf, Kt as applyClean, L as openStore, Lr as runDirOf, Mn as pausedOf, Nn as runStatus, On as requestStop, Pr as placeFiles, R as storePath, S as detectInstaller, St as walkSummary, Tn as unlinkNodes, Un as loadProject, Ut as runListTable, Vt as feedbackListTable, Wt as usageListTable, Xr as newWorkflowText, Xt as stepActs, Yn as END_KINDS, Yr as applyEdits, Yt as planClean, Zr as readDocument, Zt as stepPrunes, _ as capsHere, a as planRemove, ai as EdgeInputSchema, b as runningRunIds, bn as liveSessionOf, bt as checkWorkflowText, ci as expandEdge, d as planLine, er as DISCUSSIONS, f as removeRefusal, g as usageReport, gn as startFollowUpChecks, ht as explainWorkflowText, i as lostWork, kn as tellLiveRun, l as labelOf, lt as detachRun, ni as parseWorkflow, nr as runGh, nt as answerDecision, o as projectHead, on as nodeSays, ot as detachResume, p as stateParts, pn as checkFollowUpsDue, pr as readCondition, pt as limitsReport, qr as skillsIn, qt as cleanStep, r as applyRemove, rr as sendPost, rt as pendingDecisions, s as readWorktrees, st as resumable, ti as renderJsonSchema, tr as planPost, u as mergeStateOf, ui as NodeSchema, ut as followEvents, v as planUpgrade, wn as linkNodes, x as runningScript, xn as DEFAULT_FOLLOW_UPS_RULE, xt as walkChecks, y as releaseNotes, zn as nestItems, zr as workflowDirOf, zt as LIST_TABLES } from "./src-DLsFsmOD.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-PVlV2QPq.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__-C1Nm0hlJ.mjs";
4
+ import { t as postFollowUpMessage } from "./post-2CaJz_mV.mjs";
5
+ import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
6
+ import { t as parseFilters } from "./parse-BzWpMN-a.mjs";
7
+ import { t as builtinProblem } from "./builtin-files-yYaNTQRU.mjs";
8
+ import { n as newRunId, t as findRun } from "./find-k489laBa.mjs";
7
9
  import path from "node:path";
8
10
  import { z } from "zod";
9
11
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
12
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
11
13
  import { homedir } from "node:os";
12
14
  import { createServer } from "node:http";
15
+ // ../server/src/contract-lists.ts
16
+ /** A filter's value: any text; the handler checks it with core. */
17
+ const text = () => z.string().min(1).optional();
18
+ /**
19
+ * A `since` or `until`: `all`, an age (`24h`), a day, or an ISO time. Read with
20
+ * core by the handler, so a bad one is refused in the CLI's words.
21
+ */
22
+ const time = text;
23
+ /** Each list's filter fields, by flag name, in its core table's order. */
24
+ const LIST_SHAPES = {
25
+ runs: {
26
+ status: text(),
27
+ workflow: text(),
28
+ project: text(),
29
+ since: time(),
30
+ until: time()
31
+ },
32
+ feedback: {
33
+ status: text(),
34
+ kind: text(),
35
+ severity: text(),
36
+ workflow: text(),
37
+ run: text()
38
+ },
39
+ "follow-ups": {
40
+ status: text(),
41
+ for: text(),
42
+ kind: text(),
43
+ due: text(),
44
+ project: text()
45
+ },
46
+ decisions: {
47
+ workflow: text(),
48
+ run: text(),
49
+ project: text()
50
+ },
51
+ worktrees: {
52
+ run: text(),
53
+ project: text()
54
+ },
55
+ usage: {
56
+ workflow: text(),
57
+ project: text(),
58
+ since: time(),
59
+ until: time()
60
+ }
61
+ };
62
+ /** A list's filter fields as query params: its core table's, each optional. */
63
+ const listShape = (list) => LIST_SHAPES[list];
64
+ Object.keys(LIST_SHAPES.runs), Object.keys(LIST_SHAPES.feedback), Object.keys(LIST_SHAPES["follow-ups"]), Object.keys(LIST_SHAPES.decisions), Object.keys(LIST_SHAPES.worktrees), Object.keys(LIST_SHAPES.usage);
65
+ const Count$1 = z.coerce.number().int().positive();
66
+ /** `GET /api/runs`: `runListTable`'s fields, and the newest `limit`. */
67
+ const RunsQuerySchema = z.strictObject({
68
+ limit: Count$1.optional(),
69
+ ...LIST_SHAPES.runs
70
+ });
71
+ /**
72
+ * `GET /api/usage`: `usageListTable`'s fields; `since` and `until` also take
73
+ * ISO times (`since` included, `until` not), as before.
74
+ */
75
+ const UsageQuerySchema = z.strictObject(LIST_SHAPES.usage);
76
+ /**
77
+ * `GET /api/decisions`: `decisionListTable`'s fields; `workflow` is ANDed with
78
+ * `run` or `project`.
79
+ */
80
+ const DecisionsQuerySchema = z.strictObject(LIST_SHAPES.decisions);
81
+ const LISTS_API = { listFields: {
82
+ method: "GET",
83
+ path: "/api/list-fields"
84
+ } };
13
85
  // ../server/src/contract-feedback.ts
14
86
  const NoteSchema = z.strictObject({
15
87
  /** Its first line is the title. */
@@ -45,6 +117,11 @@ const PublishSchema = z.strictObject({
45
117
  /** The preview's digest: what the person saw, so nothing else is sent. */
46
118
  digest: z.string().min(1)
47
119
  });
120
+ /**
121
+ * The filters are core's `feedbackListTable` fields; with none, every item
122
+ * comes, dismissed too (the panel hides them itself).
123
+ */
124
+ const FeedbackItemsQuerySchema = z.strictObject(listShape("feedback"));
48
125
  const FEEDBACK_API = {
49
126
  note: {
50
127
  method: "POST",
@@ -53,7 +130,8 @@ const FEEDBACK_API = {
53
130
  },
54
131
  feedbackItems: {
55
132
  method: "GET",
56
- path: "/api/feedback"
133
+ path: "/api/feedback",
134
+ query: FeedbackItemsQuerySchema
57
135
  },
58
136
  feedbackItem: {
59
137
  method: "GET",
@@ -90,19 +168,11 @@ const FEEDBACK_API = {
90
168
  }
91
169
  };
92
170
  // ../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
- });
171
+ /**
172
+ * The filters are core's `followUpListTable` fields. `status` is `open` (due
173
+ * and waiting, the default), `all` (adds done and dropped), or the table's.
174
+ */
175
+ const FollowUpsQuerySchema = z.strictObject(listShape("follow-ups"));
106
176
  const FollowUpChangeSchema = z.strictObject({ action: z.enum([
107
177
  "done",
108
178
  "drop",
@@ -271,9 +341,11 @@ planId: z.string().min(1) })
271
341
  };
272
342
  // ../server/src/contract-worktrees.ts
273
343
  const Root = z.string().min(1);
274
- const WorktreesQuerySchema = z.strictObject({
275
- /** A project's root; every project Ork knows without one. */
276
- project: Root.optional() });
344
+ /**
345
+ * The filters are core's `worktreeListTable` fields (`project`: a root; every
346
+ * project without one).
347
+ */
348
+ const WorktreesQuerySchema = z.strictObject(listShape("worktrees"));
277
349
  const RemovePlanSchema = z.strictObject({
278
350
  project: Root,
279
351
  /** The item's branch, or a detached worktree's path. */
@@ -323,17 +395,6 @@ const WORKTREES_API = {
323
395
  // ../server/src/contract.ts
324
396
  const Count = z.coerce.number().int().positive();
325
397
  const Seq = z.coerce.number().int().nonnegative();
326
- const RunsQuerySchema = z.strictObject({
327
- project: z.string().min(1).optional(),
328
- limit: Count.optional()
329
- });
330
- const UsageQuerySchema = z.strictObject({
331
- project: z.string().min(1).optional(),
332
- workflow: z.string().min(1).optional(),
333
- /** ISO times: `since` included, `until` not. */
334
- since: z.iso.datetime().optional(),
335
- until: z.iso.datetime().optional()
336
- });
337
398
  const EventsQuerySchema = z.strictObject({
338
399
  after: Seq.optional(),
339
400
  node: z.string().min(1).optional(),
@@ -342,12 +403,6 @@ const EventsQuerySchema = z.strictObject({
342
403
  /** Only the newest this many, still oldest first. */
343
404
  last: Count.optional()
344
405
  });
345
- const DecisionsQuerySchema = z.strictObject({
346
- run: z.string().min(1).optional(),
347
- project: z.string().min(1).optional(),
348
- /** Only runs of this workflow, by name; ANDed with `run` or `project`. */
349
- workflow: z.string().min(1).optional()
350
- });
351
406
  /** An answer: the option, and what the person said with it, if anything. */
352
407
  const AnswerSchema = z.strictObject({
353
408
  choose: z.string().min(1),
@@ -385,6 +440,7 @@ const API = {
385
440
  ...CONTEXT_API,
386
441
  ...AFTER_API,
387
442
  ...WORKTREES_API,
443
+ ...LISTS_API,
388
444
  ...UPDATE_API,
389
445
  projects: {
390
446
  method: "GET",
@@ -1108,17 +1164,14 @@ const checkTool = (context) => tool("check", "Checks a workflow as `ork validate
1108
1164
  return answer({
1109
1165
  errors: count("error"),
1110
1166
  warnings: count("warning"),
1111
- diagnostics: diagnostics.map(({ severity, code, message, line, column }) => line === void 0 ? {
1167
+ diagnostics: diagnostics.map(({ severity, code, message, file, line, column }) => Object.assign({
1112
1168
  severity,
1113
1169
  code,
1114
1170
  message
1115
- } : {
1116
- severity,
1117
- code,
1118
- message,
1171
+ }, file === void 0 ? {} : { file }, line === void 0 ? {} : {
1119
1172
  line,
1120
1173
  column
1121
- }),
1174
+ })),
1122
1175
  walk: walk === void 0 ? null : walkSummary(walk),
1123
1176
  ...args.paths === true && walk !== void 0 ? { endings: walk.endings.slice(0, MOST_ENDINGS) } : {}
1124
1177
  });
@@ -1578,6 +1631,76 @@ const treeWithEnds = (roots, stored) => {
1578
1631
  };
1579
1632
  return roots.map((node) => dress(node));
1580
1633
  };
1634
+ // ../server/src/list-fields-routes.ts
1635
+ /**
1636
+ * @file `GET /api/list-fields`: the filter fields of each list, from core's
1637
+ * tables (RFC-017), as text without their functions, so the UI builds its
1638
+ * filter bar without importing core.
1639
+ */
1640
+ const infoOf = (field) => ({
1641
+ name: field.name,
1642
+ description: field.description,
1643
+ type: field.type,
1644
+ ...field.values === void 0 ? {} : { values: [...field.values] },
1645
+ ...field.all === true ? { all: true } : {}
1646
+ });
1647
+ /** The answer, built once: the tables do not change while the server runs. */
1648
+ const listFields = () => ({
1649
+ runs: LIST_TABLES.ps.fields.map(infoOf),
1650
+ feedback: LIST_TABLES["feedback list"].fields.map(infoOf),
1651
+ "follow-ups": LIST_TABLES["follow-ups list"].fields.map(infoOf),
1652
+ decisions: LIST_TABLES["decide --list"].fields.map(infoOf),
1653
+ worktrees: LIST_TABLES["worktrees list"].fields.map(infoOf),
1654
+ usage: LIST_TABLES.usage.fields.map(infoOf)
1655
+ });
1656
+ const listFieldsRoutes = () => ({ listFields: () => ok(listFields()) });
1657
+ // ../server/src/list-query.ts
1658
+ /**
1659
+ * @file Reads a list route's filter fields the way the CLI reads its flags
1660
+ * (RFC-017, ADR-088): each value checked by core's `parseFilters` against the
1661
+ * list's table, a refused one answered as 400 in the CLI's words. A full ISO
1662
+ * time in `since` or `until` is taken as given (the form `usage` had before
1663
+ * the tables), the rest are an age, a day or `all`.
1664
+ */
1665
+ /** A `2026-11-01T10:00:00Z`-style time, as an ISO time; else `undefined`. */
1666
+ const isoTime = (value) => {
1667
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T/u.test(value)) return;
1668
+ const time = Date.parse(value);
1669
+ return Number.isNaN(time) ? void 0 : new Date(time).toISOString();
1670
+ };
1671
+ /**
1672
+ * The filter `query` asks for, checked against `table`; a value the table
1673
+ * refuses is a 400.
1674
+ */
1675
+ const filterOf = (table, query) => {
1676
+ const times = {};
1677
+ const rest = { ...query };
1678
+ for (const name of ["since", "until"]) {
1679
+ const iso = isoTime(query[name]);
1680
+ if (iso === void 0) continue;
1681
+ times[name] = iso;
1682
+ delete rest[name];
1683
+ }
1684
+ const parsed = parseFilters(table, rest, Date.now());
1685
+ return parsed.ok ? {
1686
+ ok: true,
1687
+ filter: {
1688
+ ...parsed.filter,
1689
+ ...times
1690
+ }
1691
+ } : {
1692
+ ok: false,
1693
+ refusal: fail(400, parsed.error)
1694
+ };
1695
+ };
1696
+ /**
1697
+ * The root a filter's `project` names (a registered project's id or root, else
1698
+ * the value as a root); `undefined` without one or for `all`.
1699
+ */
1700
+ const projectRootOf = (context, filter) => {
1701
+ const key = filter["project"];
1702
+ return key === void 0 || key === "all" ? void 0 : projectOf(context, key)?.root ?? key;
1703
+ };
1581
1704
  // ../server/src/workflows.ts
1582
1705
  /**
1583
1706
  * @file A project's workflows, for the UI's Run workflow dialog: each
@@ -1636,14 +1759,18 @@ const projectWorkflows = (project) => {
1636
1759
  * decisions, and usage. Each reads the store as the matching CLI command does
1637
1760
  * (`ork projects`, `ps`, `tree`, `logs`, `decide --list`, `usage`).
1638
1761
  */
1639
- /** A project filter's root: a registered project's, or the value as a root. */
1640
1762
  /** A registered project's place; caps are only known for one. */
1641
1763
  const placeOf = (context, key) => {
1642
1764
  const known = key === void 0 ? void 0 : projectOf(context, key);
1643
1765
  return known === void 0 ? void 0 : placeFor(known.root, context.store.projects());
1644
1766
  };
1645
- const projectRoot = (context, key) => projectOf(context, key)?.root ?? key;
1767
+ /** `ork ps`'s table, matching `--status` on the live status the API shows. */
1768
+ const liveRunTable = {
1769
+ ...runListTable,
1770
+ fields: runListTable.fields.map((field) => field.name === "status" ? Object.assign({}, field, { get: (run) => run.live }) : field)
1771
+ };
1646
1772
  const readRoutes = (context) => ({
1773
+ ...listFieldsRoutes(),
1647
1774
  health: () => ok({
1648
1775
  version: context.version,
1649
1776
  pid: context.pid,
@@ -1652,23 +1779,38 @@ const readRoutes = (context) => ({
1652
1779
  projects: () => ok(context.store.projects()),
1653
1780
  triggers: () => ok(triggersReport(context.store, context.store.projects(), context.home)),
1654
1781
  links: ({ query }) => ok(context.store.links(query.run === void 0 ? {} : { run: query.run })),
1655
- usage: ({ query }) => ok({
1656
- ...usageReport(context.store.usage({
1657
- ...query.project === void 0 ? {} : { project: projectRoot(context, query.project) },
1658
- ...query.workflow === void 0 ? {} : { workflow: query.workflow },
1659
- ...query.since === void 0 ? {} : { since: query.since },
1660
- ...query.until === void 0 ? {} : { until: query.until }
1661
- })),
1662
- limits: limitsReport(context.store, capsHere(context.home, placeOf(context, query.project)), /* @__PURE__ */ new Date())
1663
- }),
1782
+ usage: ({ query }) => {
1783
+ const read = filterOf(usageListTable, query);
1784
+ if (!read.ok) return read.refusal;
1785
+ const { filter } = read;
1786
+ const { workflow, since, until } = filter;
1787
+ const root = projectRootOf(context, filter);
1788
+ return ok({
1789
+ ...usageReport(context.store.usage({
1790
+ ...root === void 0 ? {} : { project: root },
1791
+ ...workflow === void 0 ? {} : { workflow },
1792
+ ...since === void 0 || since === "all" ? {} : { since },
1793
+ ...until === void 0 ? {} : { until }
1794
+ })),
1795
+ limits: limitsReport(context.store, capsHere(context.home, placeOf(context, root)), /* @__PURE__ */ new Date())
1796
+ });
1797
+ },
1664
1798
  workflows: ({ params }) => {
1665
1799
  const project = projectOf(context, params.id);
1666
1800
  return project === void 0 ? fail(404, `No registered project \`${params.id}\`.`) : ok(projectWorkflows(project));
1667
1801
  },
1668
- runs: ({ query }) => ok(context.store.runs({
1669
- limit: query.limit ?? 20,
1670
- ...query.project === void 0 ? {} : { project: projectRoot(context, query.project) }
1671
- }).map((run) => viewOf$2(context, run))),
1802
+ runs: ({ query }) => {
1803
+ const read = filterOf(liveRunTable, query);
1804
+ if (!read.ok) return read.refusal;
1805
+ const root = projectRootOf(context, read.filter);
1806
+ const limit = query.limit ?? 20;
1807
+ const narrowed = Object.keys(read.filter).some((name) => name !== "project");
1808
+ const views = context.store.runs({
1809
+ ...root === void 0 ? {} : { project: root },
1810
+ ...narrowed ? {} : { limit }
1811
+ }).map((run) => viewOf$2(context, run));
1812
+ return ok(applyFilters(liveRunTable, views, read.filter).slice(0, limit));
1813
+ },
1672
1814
  run: ({ params }) => {
1673
1815
  const found = runOf(context, params.id);
1674
1816
  return found.ok ? ok(viewOf$2(context, found.run)) : found.refusal;
@@ -1692,12 +1834,18 @@ const readRoutes = (context) => ({
1692
1834
  return ok((query.limit === void 0 ? newest : newest.slice(0, query.limit)).map((stored) => servedEvent(stored)));
1693
1835
  },
1694
1836
  decisions: ({ query }) => {
1695
- const workflow = query.workflow === void 0 ? {} : { workflow: query.workflow };
1696
- if (query.run === void 0) return ok(pendingDecisions(context.store, {
1697
- ...workflow,
1698
- ...query.project === void 0 ? {} : { project: projectRoot(context, query.project) }
1699
- }));
1700
- const found = runOf(context, query.run);
1837
+ const read = filterOf(decisionListTable, query);
1838
+ if (!read.ok) return read.refusal;
1839
+ const { run, workflow: named } = read.filter;
1840
+ const workflow = named === void 0 ? {} : { workflow: named };
1841
+ if (run === void 0) {
1842
+ const root = projectRootOf(context, read.filter);
1843
+ return ok(pendingDecisions(context.store, {
1844
+ ...workflow,
1845
+ ...root === void 0 ? {} : { project: root }
1846
+ }));
1847
+ }
1848
+ const found = runOf(context, run);
1701
1849
  if (!found.ok) return found.refusal;
1702
1850
  return ok(pendingDecisions(context.store, {
1703
1851
  ...workflow,
@@ -1742,7 +1890,7 @@ const mcpTools = (context) => {
1742
1890
  ...NO_QUERY,
1743
1891
  params: { id: args.project }
1744
1892
  }))),
1745
- tool("runs", "Runs, newest first, with their live status and cost. 10 unless a limit is given.", RunsQuerySchema.shape, (args) => fromReply(read.runs({
1893
+ tool("runs", "Runs, newest first, with their live status and cost. 10 unless a limit is given. Narrow them with `status` (running, paused, crashed, succeeded, failed, … or all), `workflow`, `project` (or all), `since` (24h, 30d, 2026-11-01, all) and `until` (a day).", RunsQuerySchema.shape, (args) => fromReply(read.runs({
1746
1894
  ...NO_QUERY,
1747
1895
  query: {
1748
1896
  limit: 10,
@@ -1778,11 +1926,11 @@ const mcpTools = (context) => {
1778
1926
  }
1779
1927
  }));
1780
1928
  }),
1781
- tool("usage", "What runs spent, by workflow and model, and how close the limits are. `since` and `until` are ISO times.", UsageQuerySchema.shape, (args) => fromReply(read.usage({
1929
+ tool("usage", "What runs spent, by workflow and model, and how close the limits are. Narrow it with `workflow`, `project` (or all), `since` and `until`: an age (24h, 30d), a day (2026-11-01) or an ISO time; `since` also takes all.", UsageQuerySchema.shape, (args) => fromReply(read.usage({
1782
1930
  ...NO_QUERY,
1783
1931
  query: args
1784
1932
  }))),
1785
- tool("decisions", "The decisions open in live runs, waiting on a person. This server can't answer them.", DecisionsQuerySchema.shape, (args) => fromReply(read.decisions({
1933
+ tool("decisions", "The decisions open in live runs, waiting on a person. This server can't answer them. Narrow them with `workflow`, `run` (an id or a start of one) and `project` (or all).", DecisionsQuerySchema.shape, (args) => fromReply(read.decisions({
1786
1934
  ...NO_QUERY,
1787
1935
  query: args
1788
1936
  }), (open) => ({
@@ -2183,7 +2331,10 @@ const feedbackRoutes = (context) => {
2183
2331
  const noted = recordNote(store, found.run, body);
2184
2332
  return noted.ok ? ok({ item: noted.item }) : fail(400, noted.error);
2185
2333
  },
2186
- feedbackItems: () => ok(store.feedbackItems({ all: true })),
2334
+ feedbackItems: ({ query }) => {
2335
+ const read = filterOf(feedbackListTable, query);
2336
+ return read.ok ? ok(applyFilters(feedbackListTable, store.feedbackItems({ all: true }), read.filter)) : read.refusal;
2337
+ },
2187
2338
  feedbackItem: ({ params }) => {
2188
2339
  const found = itemOf(store, params.id);
2189
2340
  if (!found.ok) return found.refusal;
@@ -2352,13 +2503,15 @@ const followUpRoutes = (context) => {
2352
2503
  gh
2353
2504
  }).then((checked) => checked.errors).catch((error) => [error instanceof Error ? error.message : String(error)]);
2354
2505
  const all = store.followUps(root);
2355
- const status = query.status ?? "open";
2356
- const shown = all.filter((f) => {
2357
- if (query.for !== void 0 && f.for !== query.for) return false;
2358
- if (status === "all") return true;
2359
- if (status === "open") return OPEN.some((s) => s === f.status);
2360
- return f.status === status;
2506
+ const open = (query.status ?? "open") === "open";
2507
+ const read = filterOf(followUpListTable, {
2508
+ ...query,
2509
+ ...open ? { status: "all" } : {}
2361
2510
  });
2511
+ if (!read.ok) return read.refusal;
2512
+ const other = projectRootOf(context, read.filter);
2513
+ const matched = other === void 0 || other === root ? applyFilters(followUpListTable, all, read.filter) : [];
2514
+ const shown = open ? matched.filter((f) => OPEN.some((s) => s === f.status)) : matched;
2362
2515
  return ok({
2363
2516
  project: root,
2364
2517
  followUps: shown,
@@ -2908,9 +3061,15 @@ const worktreesRoutes = (context) => {
2908
3061
  };
2909
3062
  return {
2910
3063
  worktrees: async ({ query }) => {
2911
- const all = await readAll(query.project);
3064
+ const filtered = filterOf(worktreeListTable, query);
3065
+ if (!filtered.ok) return filtered.refusal;
3066
+ const all = await readAll(projectRootOf(context, filtered.filter));
2912
3067
  if (!all.ok) return all.refusal;
2913
- return ok({ projects: all.reads.map((read) => listed(context, read)) });
3068
+ return ok({ projects: all.reads.map((read) => {
3069
+ const project = listed(context, read);
3070
+ project.items = applyFilters(worktreeListTable, project.items, filtered.filter);
3071
+ return project;
3072
+ }) });
2914
3073
  },
2915
3074
  worktreeRemovePlan: async ({ body }) => {
2916
3075
  const planned = await removePlanOf(context, body);