@kud/gh-workflow 0.1.0

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.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * A named slice of the reader's repos. Two or more turn on the in-app toggle;
3
+ * one or none means an undivided set and no toggle at all.
4
+ *
5
+ * Named rather than boolean because the distinction was never a property of a
6
+ * repo — it is whatever separation the reader actually keeps, and a boolean
7
+ * leaves a third slice nowhere to go.
8
+ */
9
+ export type RepoProfile = {
10
+ /** Stable id — what `--profile=<name>` selects. */
11
+ name: string;
12
+ /** Shown on the toggle. Defaults to `name`. */
13
+ label?: string;
14
+ /** Which repos belong to this profile. */
15
+ match: (repo: string) => boolean;
16
+ /** Where this profile's checkouts live, if it keeps its own directory. */
17
+ checkoutDir?: string;
18
+ };
19
+ export type InboxConfig = {
20
+ /**
21
+ * Repo ranking, best first. An entry ending in `/` matches by owner prefix,
22
+ * anything else must equal `owner/name` exactly — so a single repo can outrank
23
+ * the owner that contains it. Repos matching nothing sort last, together.
24
+ */
25
+ repoPriority: readonly string[];
26
+ /**
27
+ * Label ranking, best first, deciding which two labels a row shows when it
28
+ * carries more than two. An entry ending in `*` matches by prefix; anything
29
+ * else must equal the label name exactly. Labels matching nothing share the
30
+ * last rank and are ordered among themselves by name.
31
+ *
32
+ * Empty like everything else here, and that is load-bearing rather than
33
+ * merely consistent: `plan`, `spike` and `app:*` are one reader's filing
34
+ * conventions, invented in their own notes, and a library that shipped them
35
+ * as defaults would be ranking a stranger's labels by a scheme they have
36
+ * never seen. Empty means a labelled row shows its labels in name order,
37
+ * which is honest about knowing nothing.
38
+ */
39
+ labelPriority: readonly string[];
40
+ profiles: readonly RepoProfile[];
41
+ /**
42
+ * Fallback directory holding checkouts, for repos in no profile that names one
43
+ * of its own. Unset means local checkouts are not resolved at all, which is
44
+ * correct for a host that only ever opens things in a browser.
45
+ */
46
+ checkoutDir?: string;
47
+ /**
48
+ * Directory name the on-disk cache lives under, inside the platform cache
49
+ * root. Defaults to this package's name — a library has no business claiming a
50
+ * directory named after whichever tool happens to embed it, and two hosts on
51
+ * one machine would otherwise share a cache keyed only by section name.
52
+ */
53
+ cacheNamespace: string;
54
+ /**
55
+ * How long a cached inbox is trusted before a launch refetches. Every launch
56
+ * past it pays the full query — eight searches with check rollups — so this is
57
+ * the single knob between "always current" and "always slow, and drawing 502s
58
+ * from the API".
59
+ *
60
+ * It can be generous because staleness is bounded from the other end: acting
61
+ * on a row drops the entry outright, and `r` refetches on demand. What the TTL
62
+ * actually governs is how long a change made ELSEWHERE — someone approving
63
+ * your PR while you were not looking — can go unseen, which is a glance
64
+ * arriving late, not a wrong action taken.
65
+ */
66
+ cacheTtlMs: number;
67
+ };
68
+ /** Supply the host's opinions. Call once, before rendering. */
69
+ export declare const configureInbox: (config: Partial<InboxConfig>) => void;
70
+ export declare const inboxConfig: () => InboxConfig;
71
+ /** Test seam — restores the empty defaults. */
72
+ export declare const resetInboxConfig: () => void;
73
+ /** The profile a repo belongs to, or null when none claims it. */
74
+ export declare const profileOf: (repo: string) => RepoProfile | null;
75
+ /**
76
+ * Where checkouts are searched, in order: every profile that names its own
77
+ * directory, then the fallback. Deduped, since the ordinary single-directory
78
+ * case points them all at the same place. Empty when nothing is configured —
79
+ * callers treat that as "not checked out locally", which it truthfully is.
80
+ */
81
+ export declare const checkoutDirs: () => string[];
82
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAChC,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB;;;;OAIG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;;OAWG;IACH,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAYD,+DAA+D;AAC/D,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,CAAC,WAAW,CAAC,KAAG,IAE7D,CAAA;AAED,eAAO,MAAM,WAAW,QAAO,WAAsB,CAAA;AAErD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,WAAW,GAAG,IACF,CAAA;AAErD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAO,MAAM,EAKrC,CAAA"}
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ // Host-supplied opinions.
2
+ //
3
+ // Everything here was once a constant compiled into the inbox: which repos to
4
+ // rank first, which ones counted as a separate part of the reader's life, where
5
+ // checkouts were kept on disk. None of that is a property of GitHub, and a
6
+ // library published to a registry cannot hold any of it — a ranking baked in at
7
+ // build time ranks one reader's repos above its next reader's.
8
+ //
9
+ // Set once at startup, before the first render, because it is process-global by
10
+ // nature: one reader, one machine, one arrangement of checkouts. Threading it
11
+ // through layoutGHItems → sortItems → repoPriority, and again through every
12
+ // filter that re-lays a section, would put a parameter on nine functions to
13
+ // carry a value that cannot vary within a run.
14
+ //
15
+ // Every default is empty. A host that configures nothing gets flat repo
16
+ // ordering, no profile split and no local-checkout resolution — never a guess at
17
+ // where someone keeps their code.
18
+ const EMPTY = {
19
+ repoPriority: [],
20
+ labelPriority: [],
21
+ profiles: [],
22
+ cacheNamespace: "gh-ink",
23
+ cacheTtlMs: 600_000,
24
+ };
25
+ let current = EMPTY;
26
+ /** Supply the host's opinions. Call once, before rendering. */
27
+ export const configureInbox = (config) => {
28
+ current = { ...EMPTY, ...config };
29
+ };
30
+ export const inboxConfig = () => current;
31
+ /** Test seam — restores the empty defaults. */
32
+ export const resetInboxConfig = () => {
33
+ current = EMPTY;
34
+ };
35
+ /** The profile a repo belongs to, or null when none claims it. */
36
+ export const profileOf = (repo) => current.profiles.find((p) => p.match(repo)) ?? null;
37
+ /**
38
+ * Where checkouts are searched, in order: every profile that names its own
39
+ * directory, then the fallback. Deduped, since the ordinary single-directory
40
+ * case points them all at the same place. Empty when nothing is configured —
41
+ * callers treat that as "not checked out locally", which it truthfully is.
42
+ */
43
+ export const checkoutDirs = () => [
44
+ ...new Set([
45
+ ...current.profiles.flatMap((p) => p.checkoutDir ?? []),
46
+ ...(current.checkoutDir ? [current.checkoutDir] : []),
47
+ ]),
48
+ ];
49
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,+DAA+D;AAC/D,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,+CAA+C;AAC/C,EAAE;AACF,wEAAwE;AACxE,iFAAiF;AACjF,kCAAkC;AAuElC,MAAM,KAAK,GAAgB;IACzB,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,EAAE;IACjB,QAAQ,EAAE,EAAE;IACZ,cAAc,EAAE,QAAQ;IACxB,UAAU,EAAE,OAAO;CACpB,CAAA;AAED,IAAI,OAAO,GAAgB,KAAK,CAAA;AAEhC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAA4B,EAAQ,EAAE;IACnE,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,EAAE,CAAA;AACnC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,GAAgB,EAAE,CAAC,OAAO,CAAA;AAErD,+CAA+C;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,OAAO,GAAG,KAAK,CAAA;AACjB,CAAC,CAAA;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAsB,EAAE,CAC5D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAA;AAErD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAa,EAAE,CAAC;IAC1C,GAAG,IAAI,GAAG,CAAC;QACT,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;QACvD,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;CACH,CAAA"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,263 @@
1
+ import type { Health } from "@kud/gh/health";
2
+ /** Mirrors `@kud/ink-ui`'s `PillVariant`, structurally, so this package needs
3
+ * no renderer dependency to say which fill a row's pill takes. */
4
+ export type PillVariant = "success" | "error" | "warning" | "info" | "accent" | "muted";
5
+ export type GHDetail = {
6
+ reviewDecision?: string;
7
+ mergeable?: string;
8
+ checksPass: number;
9
+ checksFail: number;
10
+ checksPending: number;
11
+ checksStale?: number;
12
+ threadsTotal: number;
13
+ lastCommitAt?: string;
14
+ lastEventAt?: string;
15
+ };
16
+ export type GHItem = {
17
+ kind: "pr" | "issue";
18
+ number: number;
19
+ title: string;
20
+ repo: string;
21
+ url: string;
22
+ branch?: string;
23
+ health: Health;
24
+ author?: string;
25
+ age: string;
26
+ activityAge?: string;
27
+ ts: number;
28
+ unresolved: number;
29
+ conversation: number;
30
+ lastActor?: string;
31
+ /**
32
+ * The item's labels, unordered and by name only — which is all GitHub gives
33
+ * without the timeline API. `labels(first: N)` orders by when the label was
34
+ * created IN THE REPO, never by when it was applied here, so "the newest two"
35
+ * is not derivable and the row ranks them against the host's `labelPriority`
36
+ * instead.
37
+ *
38
+ * Optional because a `minimal` fetch omits the selection entirely — these
39
+ * fields vanish rather than degrade — so undefined means "not asked for",
40
+ * which the row draws as nothing at all rather than as an unlabelled item.
41
+ */
42
+ labels?: readonly string[];
43
+ detail?: GHDetail;
44
+ /**
45
+ * Where YOU stand on this row, when the host knows it per row rather than per
46
+ * tab. Set it and the whose-move band reads it instead of inferring from the
47
+ * section id — which is what lets one tab hold rows of mixed standing: a
48
+ * review asked of you and one you have already given differ only in whether
49
+ * the ball comes back, and that is a fact about the search a row came from,
50
+ * never about the PR. Left unset, the section id decides as before.
51
+ */
52
+ standing?: Standing;
53
+ /**
54
+ * A turn the viewer has claimed BY HAND, outranking everything inferred.
55
+ *
56
+ * The inference below is good and still gets things wrong in one direction it
57
+ * cannot see: a row can be genuinely yours while every signal says otherwise —
58
+ * nothing red, no unresolved thread, nobody waiting on a word from you — and
59
+ * no amount of reading the PR harder will discover it. That knowledge lives
60
+ * with the viewer, so this is the field where they put it.
61
+ *
62
+ * It is a PIN, not a correction: it applies whether or not the row was already
63
+ * yours, which is why it does not pair with an opposite. The other direction —
64
+ * "no reply is owed for this particular comment" — is a fact about one event
65
+ * and belongs where the events are read, not here; a host expresses it by
66
+ * handing back a `lastActor` that no longer claims a turn.
67
+ */
68
+ pinned?: boolean;
69
+ /**
70
+ * What this row STANDS FOR, when that differs from what it is. Absent means
71
+ * the row is a unit of work and is counted as one.
72
+ *
73
+ * `"container"` marks a row that exists to carry context rather than to be
74
+ * done: the initiative a story hangs under, drawn as a real row because it is
75
+ * still selectable, openable, and worth seeing until it closes — but not
76
+ * itself a thing on anyone's plate. Four stories under one container is four
77
+ * items, not five.
78
+ *
79
+ * Deliberately not `uncounted`. A host knows what a row IS; it should not have
80
+ * to know what the tab badge does with that, and a field named after one
81
+ * consumer starts lying the moment a second one reads it.
82
+ *
83
+ * Orthogonal to `depth`, which says where the row hangs rather than what it
84
+ * stands for. A container is a genuine depth-0 row with genuine depth-1
85
+ * children; spelling it as a depth would mean lying about the tree to fix a
86
+ * number, and every site that draws indentation reads that lie as truth.
87
+ *
88
+ * A union with one member rather than a boolean. `indent` was a boolean that
89
+ * turned out to need a scalar, and widening it cost a deprecation that is
90
+ * still in this file. Adding a second role here is additive; turning a boolean
91
+ * into a union is not.
92
+ */
93
+ role?: "container";
94
+ /**
95
+ * How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
96
+ * grandchild. Absent means 0.
97
+ *
98
+ * Always read it through `depthOf`, never off the row — `indent` is still a
99
+ * legal way to spell "depth 1", and that helper is the only place that knows
100
+ * both spellings. A site that reads `item.indent` directly prices a `depth: 2`
101
+ * row as top level, which is the bug this field exists to end rather than a
102
+ * new one to introduce.
103
+ */
104
+ depth?: number;
105
+ /**
106
+ * @deprecated Legacy spelling of `depth: 1`. Write `depth`; this is kept so
107
+ * every existing producer still typechecks, and it is read in exactly one
108
+ * place (`depthOf`). Due to be removed at the next major.
109
+ */
110
+ indent?: boolean;
111
+ };
112
+ export type TaskRow = {
113
+ kind: "task";
114
+ key: string;
115
+ summary: string;
116
+ url: string;
117
+ status: string;
118
+ age: string;
119
+ /** What this row stands for. See `GHItem.role`. */
120
+ role?: "container";
121
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
122
+ depth?: number;
123
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
124
+ indent?: boolean;
125
+ instanceKey?: string;
126
+ /**
127
+ * Trailing annotation, rendered dim after the summary — a recurrence marker,
128
+ * a source hint, anything secondary to the title. Its own node rather than
129
+ * part of `summary` so it can be dimmed, and so its width is measured
130
+ * separately instead of being smuggled past the truncation maths.
131
+ */
132
+ note?: string;
133
+ /**
134
+ * A category this row BELONGS to — `epic`, `blocked`, `spike` — drawn as a
135
+ * filled pill after the summary.
136
+ *
137
+ * Deliberately not a second spelling of `note`, which the epic marker used to
138
+ * borrow. The two want opposite weights: a pill says the word itself is the
139
+ * information, while `note` is for a reference the reader follows — a parent
140
+ * ticket key, a source. Filling a breadcrumb gives it a weight it has not
141
+ * earned, and a row can legitimately carry both (a story under someone else's
142
+ * epic shows its parent's key AND, one day, a status of its own).
143
+ */
144
+ pill?: string;
145
+ /** Which fill the pill takes. See `@kud/ink-ui`'s `PillVariant`. */
146
+ pillVariant?: PillVariant;
147
+ /**
148
+ * The Jira issue key behind this row, when one exists. Its PRESENCE is what
149
+ * turns on the ticket affordances — ↵ opens a menu led by `jira issue view`,
150
+ * and `t` transitions the issue. A row without it is just a task: ↵ opens its
151
+ * URL and nothing here shells out to `jira`.
152
+ *
153
+ * Separate from `key` because the two are not the same thing. `key` is the
154
+ * left column, and a caller is free to put anything legible there: cockpit
155
+ * puts the ticket key, `life` puts a Todoist project name. Reading the drill
156
+ * off `key` is what made `life` run `jira issue view "Maison "` and
157
+ * label it "View ticket" on a surface that has never touched Jira.
158
+ */
159
+ ticket?: string;
160
+ };
161
+ export type RepoHeader = {
162
+ kind: "repo-header";
163
+ repo: string;
164
+ age: string;
165
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
166
+ depth?: number;
167
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
168
+ indent?: boolean;
169
+ };
170
+ export type ShowMore = {
171
+ kind: "show-more";
172
+ hidden: GHItem[];
173
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
174
+ depth?: number;
175
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
176
+ indent?: boolean;
177
+ };
178
+ export type ShowLess = {
179
+ kind: "show-less";
180
+ toHide: GHItem[];
181
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
182
+ depth?: number;
183
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
184
+ indent?: boolean;
185
+ };
186
+ export type SubgroupHeader = {
187
+ kind: "subgroup-header";
188
+ label: string;
189
+ age: string;
190
+ /** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
191
+ depth?: number;
192
+ /** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
193
+ indent?: boolean;
194
+ };
195
+ export type AnyItem = GHItem | TaskRow | RepoHeader | SubgroupHeader | ShowMore | ShowLess;
196
+ /**
197
+ * The one place either spelling of depth is read. Every other site goes through
198
+ * this, so a row written as `depth: 2` cannot be priced as top level by a
199
+ * reader that only knew about the boolean.
200
+ *
201
+ * Clamped and floored rather than trusted: a bad producer degrades to a flatter
202
+ * list, where the old behaviour was a `NaN` column width that renders as a
203
+ * blank row and gives you nothing to diagnose from.
204
+ */
205
+ export declare const depthOf: (item?: AnyItem) => number;
206
+ export type CiStatus = {
207
+ job: string;
208
+ buildNumber: number;
209
+ result: string;
210
+ building: boolean;
211
+ url: string;
212
+ age: string;
213
+ };
214
+ export type Section = {
215
+ id: string;
216
+ label: string;
217
+ items: AnyItem[];
218
+ };
219
+ export declare const relativeTime: (iso: string) => string;
220
+ export declare const repoPriority: (repo: string) => number;
221
+ export declare const labelPriority: (name: string) => number;
222
+ export declare const sortItems: (items: GHItem[]) => GHItem[];
223
+ export declare const sortByRecency: (items: GHItem[]) => GHItem[];
224
+ export declare const insertRepoHeaders: (items: GHItem[]) => AnyItem[];
225
+ export type Standing = "authored" | "queued" | "spoken";
226
+ export declare const whoseMove: (health: Health, sectionId: string, standing?: Standing, theySpokeLast?: boolean, pinned?: boolean) => "you" | "them";
227
+ export declare const PIN_MARK = "+";
228
+ export declare const layoutGHItems: (items: GHItem[], sectionId: string, login?: string) => AnyItem[];
229
+ /**
230
+ * A two-way repo split, when a host has one. `undefined` — the ordinary case —
231
+ * means no split at all: every row stands.
232
+ *
233
+ * The package deliberately does not NAME the sides. It hardcoded "work" and
234
+ * "home" until 2026-08-27: one reader's two lives compiled into a library
235
+ * anyone can install, printed in a header where no other host would recognise
236
+ * the word. The predicate, the side, and the word are all yours.
237
+ *
238
+ * Chosen once, by the host, before the first paint. There is no in-app toggle,
239
+ * because a key that flipped it could only ever put the inbox out of step with
240
+ * the scope the command was started in, with nothing on screen to explain the
241
+ * disagreement.
242
+ */
243
+ export type OriginSplit = {
244
+ /** Rows whose repo this matches are one side; everything else is the other. */
245
+ match: (repo: string) => boolean;
246
+ /** Which side to show. */
247
+ show: "matched" | "rest";
248
+ /** What to call the visible side in the header. Omitted, nothing is shown. */
249
+ label?: string;
250
+ };
251
+ /**
252
+ * Keep only one side of a two-way repo split, leaving non-GH rows (task)
253
+ * untouched. Mirrors filterByRepos' gh/other split.
254
+ *
255
+ * `keep` is positional — "the ones that matched" or "the rest" — and never a
256
+ * name. This took the literal strings "work" and "home" until 2026-08-27, which
257
+ * is one reader's life compiled into a published library: no other host has
258
+ * those two categories, and several have none at all.
259
+ */
260
+ export declare const filterByOrigin: (sections: Section[], keep: "matched" | "rest", match: (repo: string) => boolean) => Section[];
261
+ export declare const filterBySearch: (sections: Section[], query: string) => Section[];
262
+ export declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
263
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
package/dist/core.js ADDED
@@ -0,0 +1,301 @@
1
+ // Workflow semantics for a GitHub inbox: what a row is, whose move it is, how
2
+ // rows sort, group and filter. Lifted verbatim out of @kud/gh-ink so a browser
3
+ // surface can reach it — the logic was never terminal-specific, only its
4
+ // address was.
5
+ import { inboxConfig } from "./config.js";
6
+ /**
7
+ * The one place either spelling of depth is read. Every other site goes through
8
+ * this, so a row written as `depth: 2` cannot be priced as top level by a
9
+ * reader that only knew about the boolean.
10
+ *
11
+ * Clamped and floored rather than trusted: a bad producer degrades to a flatter
12
+ * list, where the old behaviour was a `NaN` column width that renders as a
13
+ * blank row and gives you nothing to diagnose from.
14
+ */
15
+ export const depthOf = (item) => {
16
+ if (!item)
17
+ return 0;
18
+ if (typeof item.depth === "number")
19
+ return Math.max(0, Math.floor(item.depth));
20
+ return item.indent === true ? 1 : 0;
21
+ };
22
+ // Everything a host needs to render a drilled-into row. `kind` is narrowed so a
23
+ // host can branch without re-testing item.kind.
24
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
25
+ export const relativeTime = (iso) => {
26
+ const diff = (Date.now() - new Date(iso).getTime()) / 1000;
27
+ if (diff < 3600)
28
+ return `${Math.floor(diff / 60)}m`;
29
+ if (diff < 86400)
30
+ return `${Math.floor(diff / 3600)}h`;
31
+ if (diff < 604800)
32
+ return `${Math.floor(diff / 86400)}d`;
33
+ return `${Math.floor(diff / 604800)}w`;
34
+ };
35
+ // Rank from the host's ordered list rather than a compiled-in one. An entry
36
+ // ending in `/` matches an owner; anything else must equal `owner/name`, so a
37
+ // single repo can be pinned above the owner containing it. Unmatched repos share
38
+ // the last rank, which leaves the name tiebreak in sortItems to order them.
39
+ export const repoPriority = (repo) => {
40
+ const order = inboxConfig().repoPriority;
41
+ const i = order.findIndex((p) => p.endsWith("/") ? repo.startsWith(p) : repo === p);
42
+ return i === -1 ? order.length : i;
43
+ };
44
+ // The same idea for labels, with two deliberate differences. An entry ending in
45
+ // `*` matches by prefix — `app:*` covers every per-app label without listing
46
+ // them — where the repo form uses a trailing `/`, because `/` is a real
47
+ // separator in a repo name and `*` is not a character a label may contain.
48
+ //
49
+ // And unmatched labels rank Infinity rather than `order.length`, so they all
50
+ // share one rank and the localeCompare tiebreak orders them among themselves.
51
+ // With `order.length` an unmatched label would tie with nothing below it and the
52
+ // distinction would be invisible — same behaviour today, but it stops being the
53
+ // same the moment a rank is compared against anything other than another rank.
54
+ export const labelPriority = (name) => {
55
+ const order = inboxConfig().labelPriority;
56
+ const i = order.findIndex((p) => p.endsWith("*") ? name.startsWith(p.slice(0, -1)) : name === p);
57
+ return i === -1 ? Infinity : i;
58
+ };
59
+ // Repo grouping is the OUTER key and is deliberately unchanged — priority tier,
60
+ // then repo name — because insertRepoHeaders below depends on same-repo items
61
+ // staying adjacent, and a strict recency sort scatters a repo down the list.
62
+ // Within a repo, two keys break the tie: draft-ness, then recency — the one
63
+ // place either can reorder rows without costing the grouping.
64
+ //
65
+ // Draft sinks because a draft is not asking. Every tab but `draft` itself is a
66
+ // RELATIONSHIP ("they requested you", "it's on your repo"), and a draft row
67
+ // answers the only question those tabs pose with "not yet" — including the case
68
+ // that reads worst, a PR that had you requested and was then converted BACK to
69
+ // draft. Recency alone floated exactly that row to the top of the list, since a
70
+ // fresh draft outranks an open PR someone has genuinely been waiting on for a
71
+ // fortnight. Sunk, not filtered: a draft you were deliberately asked to look at
72
+ // early must still be visible, and it keeps its `~` glyph either way.
73
+ //
74
+ // Sorting on `ts` and not on `age`: `age` is a rendered string ("23h", "2d") and
75
+ // sorts lexicographically, which puts "2d" before "23h".
76
+ export const sortItems = (items) => [...items].sort((a, b) => {
77
+ const pd = repoPriority(a.repo) - repoPriority(b.repo);
78
+ if (pd !== 0)
79
+ return pd;
80
+ const rd = a.repo.localeCompare(b.repo);
81
+ if (rd !== 0)
82
+ return rd;
83
+ const dd = Number(a.health === "draft") - Number(b.health === "draft");
84
+ return dd !== 0 ? dd : b.ts - a.ts;
85
+ });
86
+ // Flat, newest-first ordering. Repos are *not* clustered — an item's repo
87
+ // header still appears (via insertRepoHeaders), but only when the repo changes
88
+ // as we walk down the timeline, so the same repo can recur further down.
89
+ export const sortByRecency = (items) => [...items].sort((a, b) => b.ts - a.ts);
90
+ export const insertRepoHeaders = (items) => {
91
+ const result = [];
92
+ let lastRepo = "";
93
+ for (const item of items) {
94
+ if (depthOf(item) === 0 && item.repo !== lastRepo) {
95
+ lastRepo = item.repo;
96
+ result.push({
97
+ kind: "repo-header",
98
+ repo: item.repo,
99
+ age: "",
100
+ indent: false,
101
+ });
102
+ }
103
+ result.push(item);
104
+ }
105
+ return result;
106
+ };
107
+ // The per-TAB fallback, for a host whose every row in a tab shares a standing.
108
+ // A host that merges two searches into one tab sets `standing` on the rows
109
+ // instead, and this is never consulted for them.
110
+ const STANDING = {
111
+ mine: "authored",
112
+ // `open` and `draft` predate `mine` and are the same standing: a host that
113
+ // still splits its own PRs by draft-ness keeps working, and one that folds
114
+ // them into a single tab (as cockpit does — the band already sinks a draft,
115
+ // so the split said it twice) gets the same reading.
116
+ open: "authored",
117
+ draft: "authored",
118
+ assigned: "authored",
119
+ issues: "authored",
120
+ review: "queued",
121
+ incoming: "queued",
122
+ reviewed: "spoken",
123
+ };
124
+ // What is YOURS from each position. Read down a column and the flips are the
125
+ // point: the mechanical blockers (ci-fail, conflict, changes-req) are yours only
126
+ // on your own PR, and the queue states (waiting, pending) only while a review is
127
+ // still wanted from you. `threads` alone is yours from all three — it is
128
+ // literally "your reply is owed", and it is the only thing left on a PR you have
129
+ // already reviewed.
130
+ //
131
+ // `draft` is yours ONLY when you authored it, and that asymmetry is the whole
132
+ // point of listing it here. The band asks whose move it is, and on your own
133
+ // draft there is no one else in the room — nobody can advance it, nobody has
134
+ // been asked to, and filing it under Their move said the opposite of what was
135
+ // true. Somebody else's draft you were pointed at stays theirs, which is why
136
+ // this appears in `authored` and not in the other two.
137
+ //
138
+ // It is still SUNK to the bottom of its band by sortItems: yours to finish is
139
+ // not the same as yours to finish now, and a fresh draft must not outrank a PR
140
+ // somebody has genuinely been waiting on. Visible as yours, ranked last.
141
+ //
142
+ // `none` remains unlisted from every position — an issue has no review state to
143
+ // read, so claiming it would be a guess rather than a reading.
144
+ const YOURS = {
145
+ authored: [
146
+ "ci-fail",
147
+ "conflict",
148
+ "changes-req",
149
+ "threads",
150
+ "approved",
151
+ "draft",
152
+ ],
153
+ queued: ["waiting", "pending", "threads", "approved"],
154
+ spoken: ["threads"],
155
+ };
156
+ // The row's own standing wins where the host set one; the tab decides otherwise,
157
+ // and an unrecognised tab reads as `queued` — over-claiming a stranger's PR as
158
+ // your work is the worse wrong guess.
159
+ export const whoseMove = (health, sectionId, standing, theySpokeLast, pinned) => {
160
+ const position = standing ?? STANDING[sectionId] ?? "queued";
161
+ // First, and unconditionally. Everything below is inference from what GitHub
162
+ // reports; this is the viewer having said so outright, and inference does not
163
+ // get to argue with it. It is one-directional by design — there is no way to
164
+ // pin a row AWAY, because "not mine" is what the bands already conclude on
165
+ // their own and a second control for it would only be a way to hide work.
166
+ if (pinned)
167
+ return "you";
168
+ // Somebody else having the last word is a claim on you — a question asked, an
169
+ // objection raised, a "can you rebase" — and none of it shows up as a health,
170
+ // because a bare comment approves nothing, fails nothing and opens no thread.
171
+ // The row already said so and the band disagreed: the turn arrow reads
172
+ // lastActor and drew `←`, the explain panel spelled out "X spoke last, your
173
+ // reply is owed", and the band filed it under Their move.
174
+ //
175
+ // ON YOUR OWN PR ONLY, though. Read unconditionally it destroys the very
176
+ // distinction the table below is built on — the mechanical blockers (ci-fail,
177
+ // conflict, changes-req) are yours on your PR and theirs on theirs — so a
178
+ // stranger's failing build became your move the moment they commented on it.
179
+ // Shipped that way for one release on 2026-08-27; eleven rows of somebody
180
+ // else's work turned up under Your move, which is exactly the noise the bands
181
+ // exist to prevent.
182
+ //
183
+ // The other two positions need no help from it: a review actually wanted from
184
+ // you is `waiting`/`pending`, and a conversation you are in is `threads`, both
185
+ // already listed. What is deliberately NOT claimed is a plain reply on a PR
186
+ // you reviewed once — real, but indistinguishable from the author saying
187
+ // "rebased" to nobody in particular.
188
+ //
189
+ // One direction only, even here: YOU having spoken last does not hand the row
190
+ // over, since red CI on your own PR is yours whether or not you commented
191
+ // after it.
192
+ if (theySpokeLast && position === "authored")
193
+ return "you";
194
+ return YOURS[position].includes(health) ? "you" : "them";
195
+ };
196
+ // Exported so the colourblind invariant can be tested against the health glyphs
197
+ // rather than restated as a literal in two files that drift apart.
198
+ export const PIN_MARK = "+";
199
+ const BAND_LABEL = {
200
+ you: "Your move",
201
+ them: "Their move",
202
+ };
203
+ // Lay out a section's GH items. The Done tab is a flat newest-first list; every
204
+ // other tab splits into two whose-move bands, each keeping its own repo grouping
205
+ // so the outer key stays legible inside a band. Repo headers are inserted in
206
+ // every case, and restart per band — a repo with work on both sides of the line
207
+ // appears under each.
208
+ //
209
+ // One band is not a band: a tab whose rows all land on the same side gains two
210
+ // header rows and no information, so it keeps the plain list. That is not an
211
+ // edge case, it is the Draft and Issues tabs by construction — every row there
212
+ // carries the same health token, so the split has nothing to say.
213
+ export const layoutGHItems = (items, sectionId,
214
+ // Needed to read `lastActor`, which is only meaningful against somebody. Left
215
+ // out, the bands fall back to health and standing alone — the behaviour every
216
+ // caller had before, so an un-updated host degrades rather than breaks.
217
+ login) => {
218
+ if (sectionId === "done")
219
+ return insertRepoHeaders(sortByRecency(items));
220
+ const sorted = sortItems(items);
221
+ const bands = ["you", "them"].map((side) => ({
222
+ side,
223
+ rows: sorted.filter((i) => whoseMove(i.health, sectionId, i.standing, !!login && !!i.lastActor && i.lastActor !== login, i.pinned) === side),
224
+ }));
225
+ const filled = bands.filter((b) => b.rows.length > 0);
226
+ if (filled.length < 2)
227
+ return insertRepoHeaders(sorted);
228
+ return filled.flatMap(({ side, rows }) => [
229
+ {
230
+ kind: "subgroup-header",
231
+ label: `${BAND_LABEL[side]} (${rows.length})`,
232
+ age: "",
233
+ indent: false,
234
+ },
235
+ ...insertRepoHeaders(rows),
236
+ ]);
237
+ };
238
+ /**
239
+ * Keep only one side of a two-way repo split, leaving non-GH rows (task)
240
+ * untouched. Mirrors filterByRepos' gh/other split.
241
+ *
242
+ * `keep` is positional — "the ones that matched" or "the rest" — and never a
243
+ * name. This took the literal strings "work" and "home" until 2026-08-27, which
244
+ * is one reader's life compiled into a published library: no other host has
245
+ * those two categories, and several have none at all.
246
+ */
247
+ export const filterByOrigin = (sections, keep, match) => sections
248
+ .map((s) => {
249
+ const kept = s.items.filter((i) => i.kind !== "repo-header" &&
250
+ i.kind !== "subgroup-header" &&
251
+ i.kind !== "show-more" &&
252
+ i.kind !== "show-less" &&
253
+ (i.kind === "pr" || i.kind === "issue"
254
+ ? keep === "matched"
255
+ ? match(i.repo)
256
+ : !match(i.repo)
257
+ : true));
258
+ const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
259
+ const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
260
+ return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
261
+ })
262
+ .filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
263
+ const searchText = (i) => i.kind === "pr" || i.kind === "issue"
264
+ ? `${i.title} ${i.repo} #${i.number}`
265
+ : i.kind === "task"
266
+ ? `${i.summary} ${i.key}`
267
+ : "";
268
+ export const filterBySearch = (sections, query) => {
269
+ const q = query.trim().toLowerCase();
270
+ if (!q)
271
+ return sections;
272
+ return sections
273
+ .map((s) => {
274
+ const kept = s.items.filter((i) => i.kind !== "repo-header" &&
275
+ i.kind !== "subgroup-header" &&
276
+ i.kind !== "show-more" &&
277
+ i.kind !== "show-less" &&
278
+ searchText(i).toLowerCase().includes(q));
279
+ const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
280
+ const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
281
+ return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
282
+ })
283
+ .filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
284
+ };
285
+ export const filterByRepos = (sections, repos) => {
286
+ if (repos.size === 0)
287
+ return sections;
288
+ return sections
289
+ .map((s) => {
290
+ const kept = s.items.filter((i) => i.kind !== "repo-header" &&
291
+ i.kind !== "subgroup-header" &&
292
+ i.kind !== "show-more" &&
293
+ i.kind !== "show-less" &&
294
+ (i.kind === "pr" || i.kind === "issue" ? repos.has(i.repo) : true));
295
+ const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
296
+ const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
297
+ return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
298
+ })
299
+ .filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
300
+ };
301
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./core.js";
2
+ export * from "./map.js";
3
+ export * from "./config.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // @kud/gh-workflow — the workflow half of a GitHub inbox, with no surface
2
+ // attached. Everything here is a total function over plain data, so a terminal
3
+ // renderer, a browser and a server route all consume it alike.
4
+ //
5
+ // The hard rule this package exists to keep: nothing in here may import a
6
+ // renderer, a transport or the filesystem. `imports.test.ts` asserts it against
7
+ // the BUILT output rather than the source, because in-workspace every forbidden
8
+ // dependency is installed and an accidental import simply succeeds.
9
+ export * from "./core.js";
10
+ export * from "./map.js";
11
+ export * from "./config.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+EAA+E;AAC/E,+DAA+D;AAC/D,EAAE;AACF,0EAA0E;AAC1E,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
package/dist/map.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type GHItem } from "./core.js";
2
+ export type CockpitItem = GHItem;
3
+ export declare const toGHItem: (node: any, opts?: {
4
+ indent?: boolean;
5
+ }) => CockpitItem;
6
+ //# sourceMappingURL=map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAWA,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAwFpE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAA;AAEhC,eAAO,MAAM,QAAQ,GACnB,MAAM,GAAG,EACT,OAAM;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,KAC9B,WA0EF,CAAA"}
package/dist/map.js ADDED
@@ -0,0 +1,141 @@
1
+ // GraphQL node → row. Sat in @kud/gh-cockpit next to signalPath(), which
2
+ // mkdirSync-es a cache directory — so importing the mapper touched the
3
+ // filesystem. It does not any more.
4
+ import { computeHealth, latestChecks, isPassCheck, isFailCheck, isPendingCheck, } from "@kud/gh/health";
5
+ import { relativeTime } from "./core.js";
6
+ const viewerReacted = (n, content) => (n?.reactionGroups ?? []).some((g) => g?.content === content && g?.viewerHasReacted);
7
+ const conversationOf = (node) => {
8
+ // `acked` is only ever set on the PR-level comments, because they are the only
9
+ // events @kud/gh fetches reactions for. Reviews and thread replies carry none
10
+ // and read as unacked, which is the safe direction: an ack that cannot be seen
11
+ // leaves the turn where it was rather than clearing one nobody claimed.
12
+ const events = [];
13
+ let count = node.comments?.totalCount ?? 0;
14
+ const isBot = (author) => author?.__typename === "Bot";
15
+ for (const c of node.comments?.nodes ?? [])
16
+ events.push({
17
+ at: c.createdAt,
18
+ login: c.author?.login,
19
+ bot: isBot(c.author),
20
+ acked: viewerReacted(c, "EYES"),
21
+ });
22
+ for (const r of node.reviews?.nodes ?? [])
23
+ if (r.state !== "PENDING")
24
+ events.push({
25
+ at: r.submittedAt,
26
+ login: r.author?.login,
27
+ bot: isBot(r.author),
28
+ });
29
+ for (const t of node.reviewThreads?.nodes ?? []) {
30
+ count += t.comments?.totalCount ?? 0;
31
+ for (const c of t.comments?.nodes ?? [])
32
+ events.push({
33
+ at: c.createdAt,
34
+ login: c.author?.login,
35
+ bot: isBot(c.author),
36
+ });
37
+ }
38
+ const last = events
39
+ .filter((e) => e.at)
40
+ .sort((a, b) => a.at.localeCompare(b.at))
41
+ .pop();
42
+ // Only the LAST event needs testing: a push later than it is later than every
43
+ // event before it too. ISO-8601 UTC compares lexicographically, the same trick
44
+ // the stall test in @kud/gh-ink already relies on.
45
+ const pushedAt = node.commits?.nodes?.[0]?.commit?.committedDate;
46
+ const answeredByPush = Boolean(last?.bot && pushedAt && pushedAt > last.at);
47
+ // An ack moves no clock: unlike a push it is not an event with a time of its
48
+ // own here, so the comment it settles stays the last thing that happened and
49
+ // the row keeps reading as active. Only the turn changes.
50
+ if (answeredByPush || last?.acked)
51
+ return {
52
+ count,
53
+ lastActor: node.author?.login,
54
+ lastEventAt: answeredByPush ? pushedAt : last?.at,
55
+ };
56
+ return { count, lastActor: last?.login, lastEventAt: last?.at };
57
+ };
58
+ const detailOf = (node, lastEventAt) => {
59
+ const active = latestChecks(node.statusCheckRollup?.contexts?.nodes ?? []);
60
+ return {
61
+ reviewDecision: node.reviewDecision ?? undefined,
62
+ mergeable: node.mergeable ?? undefined,
63
+ checksPass: active.filter(isPassCheck).length,
64
+ checksFail: active.filter(isFailCheck).length,
65
+ checksPending: active.filter(isPendingCheck).length,
66
+ threadsTotal: (node.reviewThreads?.nodes ?? []).length,
67
+ lastCommitAt: node.commits?.nodes?.[0]?.commit?.committedDate,
68
+ lastEventAt,
69
+ };
70
+ };
71
+ export const toGHItem = (node, opts = {}) => {
72
+ const completedAt = node.mergedAt ?? node.closedAt ?? node.createdAt;
73
+ const convo = conversationOf(node);
74
+ // What "last activity" means, and it is deliberately NOT GitHub's `updatedAt`.
75
+ // That field bumps on label and assignee churn, so a triage pass would float
76
+ // every issue it touched above a PR someone had actually replied to. These
77
+ // three are the events a human would call activity, and every one of them is
78
+ // already in the response — conversationOf folds the last comment, review and
79
+ // thread reply into lastEventAt, and the last commit rides along beside it. So
80
+ // this costs nothing: no extra field, no extra node, no extra GraphQL points.
81
+ //
82
+ // Compared as ISO-8601 UTC strings rather than parsed, the same lexicographic
83
+ // trick conversationOf above already relies on.
84
+ const activityAt = [
85
+ node.createdAt,
86
+ convo.lastEventAt,
87
+ node.commits?.nodes?.[0]?.commit?.committedDate,
88
+ ]
89
+ .filter(Boolean)
90
+ .sort()
91
+ .pop();
92
+ // Done rows sort and read by completion — that is their recency, and the
93
+ // recentlyDone fragment fetches no conversation to derive anything else from.
94
+ // Everywhere else the item is open, so `age` stays the opening date and
95
+ // activity is the new, separate value.
96
+ const isDone = Boolean(node.mergedAt ?? node.closedAt);
97
+ const sortAt = isDone ? completedAt : (activityAt ?? completedAt);
98
+ return {
99
+ // Prefer GraphQL's own discriminator; fall back to field-presence only when
100
+ // a fragment omitted __typename. Field-presence alone mislabels PRs as issues
101
+ // whenever a fragment (reviewRequests, assigned, reviewed…) skips isDraft /
102
+ // headRefName — which sent PR drills to the issues endpoint.
103
+ kind: node.__typename === "PullRequest"
104
+ ? "pr"
105
+ : node.__typename === "Issue"
106
+ ? "issue"
107
+ : "isDraft" in node || "headRefName" in node
108
+ ? "pr"
109
+ : "issue",
110
+ number: node.number,
111
+ title: node.title ?? "",
112
+ repo: node.repository?.nameWithOwner ?? "",
113
+ url: node.url ?? "",
114
+ branch: node.headRefName,
115
+ health: computeHealth(node),
116
+ author: node.author?.login,
117
+ age: completedAt ? relativeTime(completedAt) : "",
118
+ activityAge: !isDone && activityAt ? relativeTime(activityAt) : undefined,
119
+ ts: sortAt ? new Date(sortAt).getTime() : 0,
120
+ unresolved: (node.reviewThreads?.nodes ?? []).filter((t) => t && !t.isResolved).length,
121
+ conversation: convo.count,
122
+ lastActor: convo.lastActor,
123
+ // 👍 on the PR BODY, and the anchor is load-bearing rather than incidental.
124
+ // Scoped to a comment this would quietly evaporate on the next bot comment,
125
+ // which is exactly backwards for something meant to persist — anchoring it
126
+ // on the PR makes it PR-scoped by construction, with no bookkeeping to get
127
+ // wrong. It also reads distinctly on GitHub itself: an eye down in the
128
+ // thread, a thumb at the top.
129
+ //
130
+ // Precedence over 👀 comes for free: the pin is read by whoseMove, which
131
+ // answers before anything derived from lastActor is consulted. Nobody pins
132
+ // by accident, so the pin wins.
133
+ pinned: viewerReacted(node, "THUMBS_UP"),
134
+ labels: (node.labels?.nodes ?? [])
135
+ .map((l) => l?.name)
136
+ .filter((n) => typeof n === "string"),
137
+ detail: detailOf(node, convo.lastEventAt),
138
+ indent: opts.indent ?? false,
139
+ };
140
+ };
141
+ //# sourceMappingURL=map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@kud/gh-workflow",
3
+ "version": "0.1.0",
4
+ "description": "Pure workflow semantics for a GitHub inbox — whose move it is, how rows sort, group and filter. No renderer, no transport, no filesystem.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p .",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "npm run build && vitest run",
21
+ "test:watch": "vitest",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "github",
26
+ "inbox",
27
+ "workflow",
28
+ "pull-request",
29
+ "triage"
30
+ ],
31
+ "author": "Erwann Mest <m@kud.io>",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/kud/gh",
36
+ "directory": "packages/gh-workflow"
37
+ },
38
+ "engines": {
39
+ "node": ">=22"
40
+ },
41
+ "sideEffects": false,
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "@kud/gh": "0.9.0"
47
+ },
48
+ "devDependencies": {
49
+ "typescript": "5.9.3",
50
+ "vitest": "4.1.10"
51
+ }
52
+ }