Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

802 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { ActivityDigest, ActorCount, ChatActivity, Deployment, InboxItem, RepoDigest, WorkflowRun } from "@g1t/contracts";
5
6import {
7 type AttentionRow,
8 type CodeNeed,
9 type CodePull,
10 type LandedChange,
11 actorKind,
12 agentLabel,
13 attention,
14 awayOf,
15 chatRow,
16 codeRow,
17 dayIn,
18 deploysIn,
19 digestOf,
20 duration,
21 explainQuestion,
22 landed,
23 landedByDay,
24 landedDeploys,
25 landedFromActivity,
26 landedIn,
27 notificationRow,
28 parseWindow,
29 pushRows,
30 rankAttention,
31 running,
32 runningDeploys,
33 runningWorkflows,
34 sessionTrailers,
35 settledIn,
36 shippedAsks,
37 shippedLabel,
38 sinceWords,
39 spanFor,
40 spanMonths,
41 spanSentence,
42 spendIn,
43 startHere,
44 summarizeQuestion,
45 waitingSentence,
46 withSessions,
47 withShipped,
48} from "./home.ts";
49
50const HOUR = 3_600_000;
51const DAY = 24 * HOUR;
52// Friday 2026-10-09, 18:00 UTC; 11:00 in Los Angeles.
53const NOW = Date.parse("2026-10-09T18:00:00Z");
54const LA = "America/Los_Angeles";
55const repo = { namespace: "acme", name: "web" };
56const iso = (ms: number) => new Date(ms).toISOString();
57
58function pull(over: Partial<CodePull> = {}): CodePull {
59 return {
60 repo,
61 number: 1,
62 title: "Fix the flaky test",
63 status: "merged",
64 mergedAt: iso(NOW - HOUR),
65 createdAt: iso(NOW - 3 * HOUR),
66 updatedAt: iso(NOW - HOUR),
67 author: { username: "g1t", kind: "agent" },
68 ...over,
69 };
70}
71
72const need = (over: Partial<CodeNeed>): CodeNeed => ({
73 key: "pull:p1",
74 reason: "checks_failing",
75 repo,
76 ref: "#12",
77 title: "Add retries",
78 ask: "It failed in the merge queue.",
79 by: { name: "bo", agent: false },
80 at: NOW - 2 * HOUR,
81 to: "/acme/web/pull/12",
82 facts: [{ label: "Files changed", value: "4", tone: null }],
83 ...over,
84});
85
86test("Code's needs become rows with what is at stake", () => {
87 const failing = codeRow(need({}));
88 assert.equal(failing.kind, "pull_blocked");
89 assert.equal(failing.stake?.text, "Checks failing");
90 assert.equal(failing.action.label, "Look");
91 const deploy = codeRow(need({ key: "deploy:site", reason: "blocking", facts: [{ label: "Production", value: "Serving the build before", tone: "good" }] }));
92 assert.equal(deploy.kind, "deploy");
93 assert.equal(deploy.stake?.text, "Production on an older build");
94 assert.equal(codeRow(need({ key: "review:p2", reason: "needs_review", facts: [{ label: "Lines", value: "+10 −2", tone: null }] })).stake?.text, "+10 −2");
95 assert.equal(codeRow(need({ key: "runner:j1", reason: "stalled", facts: [{ label: "Waiting", value: "2 h", tone: "warn" }] })).stake?.text, "Waiting 2 h");
96 // Your pull request that meets everything: merge it, beside reviews asked of you.
97 const ready = codeRow(need({ key: "ready:p3", reason: "ready", ask: "Ready to land when you merge it." }));
98 assert.equal(ready.kind, "ready");
99 assert.equal(ready.stake?.text, "Ready to merge");
100 assert.equal(ready.action.label, "Merge");
101 // One that needs you (a conflict, a request for changes) blocks it.
102 const conflict = codeRow(need({ key: "pull:p4", reason: "blocking", ask: "It conflicts with the branch it will land on." }));
103 assert.equal(conflict.kind, "pull_blocked");
104 assert.equal(conflict.stake?.text, "Blocking");
105});
106
107const row = (over: Partial<AttentionRow>): AttentionRow => ({
108 key: "k",
109 kind: "chat",
110 title: "t",
111 detail: "d",
112 stake: null,
113 action: { label: "Open", to: "/x" },
114 owner: null,
115 at: NOW,
116 from: "chat",
117 ...over,
118});
119
120test("Start here: the most pressing tier first, then what has waited longest", () => {
121 const rows = [
122 row({ key: "chat:old", kind: "chat", at: NOW - 5 * DAY, action: { label: "Reply", to: "/acme/-/chat/general" } }),
123 row({ key: "review:new", kind: "review", at: NOW - HOUR, action: { label: "Review", to: "/acme/web/pull/2" } }),
124 row({ key: "review:old", kind: "review", at: NOW - 3 * HOUR, action: { label: "Review", to: "/acme/web/pull/1" } }),
125 row({ key: "pull:p", kind: "pull_blocked", at: NOW - 10 * 60_000, action: { label: "Look", to: "/acme/web/pull/3" } }),
126 ];
127 const ranked = rankAttention(rows);
128 assert.deepEqual(ranked.map((r) => r.key), ["pull:p", "review:old", "review:new", "chat:old"]);
129 const start = startHere(ranked);
130 assert.equal(start?.row.key, "pull:p");
131 assert.match(start?.why ?? "", /can't land/);
132 // A failed production deploy beats everything.
133 const withDeploy = rankAttention([...rows, row({ key: "deploy:site", kind: "deploy", at: NOW, action: { label: "See the build", to: "/acme/site/deployments/1" } })]);
134 assert.equal(startHere(withDeploy)?.row.key, "deploy:site");
135 // Ties in tier say so.
136 const two = startHere(rankAttention(rows.filter((r) => r.kind === "review")));
137 assert.equal(two?.row.key, "review:old");
138 assert.match(two?.why ?? "", /Of the 2 like it, it has waited longest/);
139 assert.equal(startHere([]), null);
140});
141
142test("the same order whatever order the rows came in", () => {
143 const rows = [row({ key: "b", kind: "review", at: NOW }), row({ key: "a", kind: "review", at: NOW, action: { label: "Review", to: "/y" } })];
144 assert.deepEqual(rankAttention(rows).map((r) => r.key), ["a", "b"]);
145 assert.deepEqual(rankAttention([...rows].reverse()).map((r) => r.key), ["a", "b"]);
146});
147
148const item = (over: Partial<InboxItem>): InboxItem => ({
149 id: "n1",
150 reason: "ci_activity",
151 severity: "error",
152 title: "Checks failed on #12",
153 body: "Add retries",
154 event: "check.failed",
155 repo: "acme/web",
156 workspace: "acme",
157 subject: "pull",
158 number: 12,
159 url: "/acme/web/pull/12",
160 actor: "g1t",
161 count: 1,
162 createdAt: iso(NOW - HOUR),
163 updatedAt: iso(NOW - HOUR),
164 readAt: null,
165 doneAt: null,
166 saved: false,
167 snoozedUntil: null,
168 ...over,
169});
170
171test("notifications: warnings and failures in this workspace; none about repositories without Code", () => {
172 assert.equal(notificationRow(item({}), "acme", true)?.kind, "notification");
173 assert.equal(notificationRow(item({}), "acme", false), null);
174 assert.equal(notificationRow(item({ repo: null, url: "/acme/-/chat/general" }), "acme", false)?.kind, "notification");
175 // A reason this page does not know, older ones included, reads as a plain notification.
176 assert.equal(notificationRow(item({ reason: "agent" as string as InboxItem["reason"] }), "acme", true)?.stake?.text, "Notification");
177 assert.equal(notificationRow(item({ severity: "info" }), "acme", true), null);
178 assert.equal(notificationRow(item({ workspace: "other", repo: "other/web" }), "acme", true), null);
179 assert.equal(notificationRow(item({ readAt: iso(NOW) }), "acme", true), null);
180});
181
182test("a notification about something already listed is left out", () => {
183 const out = attention({
184 slug: "acme",
185 code: true,
186 codeNeeds: [need({})],
187 notifications: [item({})],
188 chat: [],
189 });
190 assert.deepEqual(out.rows.map((r) => r.key), ["pull:p1"]);
191 assert.deepEqual(out.missing, []);
192});
193
194test("without Code access no Code row is asked for or shown, and a silent source is named", () => {
195 const out = attention({
196 slug: "acme",
197 code: false,
198 codeNeeds: [need({})],
199 notifications: [item({}), item({ id: "n2", repo: null, reason: "mention", severity: "warning", url: "/acme/-/chat/general" })],
200 chat: null,
201 });
202 assert.deepEqual(out.rows.map((r) => r.key), ["notification:n2"]);
203 assert.equal(out.rows.some((r) => r.from === "code"), false);
204 assert.deepEqual(out.missing, ["Chat"]);
205});
206
207test("chat: mentions anywhere and unread direct messages, never muted", () => {
208 const channel = { id: "c1", kind: "channel" as const, name: "general", last_message_at: iso(NOW) };
209 assert.equal(chatRow({ channel, title: "general", muted: false, unread: 4, mentions: 0 }, "acme"), null);
210 assert.equal(chatRow({ channel, title: "general", muted: false, unread: 4, mentions: 2 }, "acme")?.stake?.text, "2 mentions");
211 assert.equal(chatRow({ channel, title: "general", muted: true, unread: 4, mentions: 2 }, "acme"), null);
212 const dm = chatRow({ channel: { ...channel, kind: "dm", name: null }, title: "Bo Reed", muted: false, unread: 1, mentions: 0 }, "acme");
213 assert.equal(dm?.action.to, "/acme/-/chat/dm/c1");
214});
215
216// The span is NOW back to 3 days ago unless a test says otherwise.
217const SPAN = { from: NOW - 3 * DAY, now: NOW };
218
219test("a day is the reader's day", () => {
220 assert.equal(dayIn(NOW, LA), "2026-10-09");
221 assert.equal(dayIn(Date.parse("2026-10-10T02:00:00Z"), LA), "2026-10-09");
222 assert.equal(dayIn(Date.parse("2026-10-10T02:00:00Z"), null), "2026-10-10");
223 assert.equal(dayIn(NOW, "Not/AZone"), "2026-10-09");
224});
225
226test("since your last visit, in words, in the reader's time zone", () => {
227 // NOW is Friday 11:00 in Los Angeles.
228 assert.equal(sinceWords(NOW - 20_000, NOW, LA), "a moment ago");
229 assert.equal(sinceWords(NOW - 40 * 60_000, NOW, LA), "40 minutes ago");
230 assert.equal(sinceWords(NOW - HOUR - 60_000, NOW, LA), "an hour ago");
231 assert.equal(sinceWords(NOW - 2 * HOUR, NOW, LA), "2 hours ago");
232 // 07:00 Friday.
233 assert.equal(sinceWords(NOW - 4 * HOUR, NOW, LA), "this morning");
234 // 02:00 Friday is Thursday night.
235 assert.equal(sinceWords(NOW - 9 * HOUR, NOW, LA), "last night");
236 // 19:00 Thursday.
237 assert.equal(sinceWords(NOW - 16 * HOUR, NOW, LA), "yesterday evening");
238 // 19:00 Tuesday.
239 assert.equal(sinceWords(NOW - 2 * DAY - 16 * HOUR, NOW, LA), "Tuesday evening");
240 // 02:00 Wednesday is Tuesday night.
241 assert.equal(sinceWords(NOW - 2 * DAY - 9 * HOUR, NOW, LA), "Tuesday night");
242 assert.equal(sinceWords(NOW - 9 * DAY, NOW, LA), "Sep 30");
243});
244
245test("how long a span is", () => {
246 assert.equal(duration(30_000), "a moment");
247 assert.equal(duration(45 * 60_000), "45 minutes");
248 assert.equal(duration(HOUR), "1 hour");
249 assert.equal(duration(3 * DAY + HOUR), "3 days");
250 assert.equal(duration(15 * DAY), "2 weeks");
251});
252
253test("the span: since your last visit, capped, or a fixed window", () => {
254 assert.equal(parseWindow("24h"), "24h");
255 assert.equal(parseWindow("7d"), "7d");
256 assert.equal(parseWindow("nonsense"), "last");
257 assert.equal(parseWindow(null), "last");
258 const away = spanFor("last", NOW, NOW - 2 * DAY - 16 * HOUR, LA);
259 assert.equal(away.from, NOW - 2 * DAY - 16 * HOUR);
260 assert.equal(away.words, "Since Tuesday evening");
261 assert.equal(away.length, "2 days");
262 assert.equal(away.note, null);
263 // Never visited: the last 24 hours, and it says why.
264 const first = spanFor("last", NOW, null, LA);
265 assert.equal(first.from, NOW - DAY);
266 assert.equal(first.note, "first");
267 assert.equal(spanFor("last", NOW, undefined, LA).note, "unknown");
268 // Away a month: 14 days at most.
269 const capped = spanFor("last", NOW, NOW - 30 * DAY, LA);
270 assert.equal(capped.from, NOW - 14 * DAY);
271 assert.equal(capped.note, "capped");
272 assert.equal(capped.words, "In the last 14 days");
273 // The fixed windows ignore the last visit.
274 assert.equal(spanFor("24h", NOW, NOW - 3 * DAY, LA).from, NOW - DAY);
275 assert.deepEqual([spanFor("7d", NOW, NOW - HOUR, LA).from, spanFor("7d", NOW, NOW - HOUR, LA).words], [NOW - 7 * DAY, "In the last 7 days"]);
276});
277
278// --- People ------------------------------------------------------------
279
280const ANA = "user:usr_ana";
281const BO = "user:usr_bo";
282const CY = "user:usr_cy";
283const MARGO = "agent:agt_margo";
284const G1T = "user:usr_g1t_agent";
285const NAMES = { [ANA]: "ana", [BO]: "bo", [CY]: "cy", [MARGO]: "margo" };
286const REPOS = {
287 rep_web: { namespace: "acme", name: "web", defaultBranch: "main" },
288 rep_api: { namespace: "acme", name: "api", defaultBranch: "main" },
289 rep_docs: { namespace: "acme", name: "docs", defaultBranch: "main" },
290 rep_cli: { namespace: "acme", name: "cli", defaultBranch: "trunk" },
291};
292
293const count = (actor: string, n: number): ActorCount => ({ actor, count: n });
294const quietRepo = (repo_id: string): RepoDigest => ({
295 repo_id,
296 pushes: { count: 0, commits: 0, branches: [], by: [], default_branch: { count: 0, commits: 0, by: [], last_at: null } },
297 pulls: { opened: [], merged: [], closed: [] },
298 issues: { opened: [], closed: [] },
299 reviews: [],
300 comments: [],
301 deployments: { succeeded: [], failed: [], production: 0 },
302 releases: [],
303 packages: [],
304});
305
306/**
307 * A day on a busy workspace: 3 people pushed 41 commits to 4 projects,
308 * merged 5 pull requests and opened 2; older agent rows (g1t and Margo)
309 * pushed 6, opened 3 pull requests and reviewed 2, and are not people;
310 * 2 production deploys went out; 120 messages were sent, 15 of them
311 * Margo's; 7 docs were edited.
312 */
313function busyDay(): ActivityDigest {
314 return {
315 from: iso(SPAN.from),
316 until: iso(SPAN.now),
317 complete: true,
318 repos: [
319 {
320 ...quietRepo("rep_web"),
321 pushes: {
322 count: 14,
323 commits: 33,
324 branches: ["fix-login", "main"],
325 by: [
326 { actor: ANA, count: 8, commits: 24 },
327 { actor: BO, count: 4, commits: 5 },
328 { actor: G1T, count: 2, commits: 4 },
329 ],
330 default_branch: { count: 3, commits: 9, by: [{ actor: ANA, count: 3, commits: 9 }], last_at: iso(NOW - 2 * HOUR) },
331 },
332 pulls: { opened: [count(ANA, 1), count(G1T, 2), count(MARGO, 1)], merged: [count(ANA, 3), count(BO, 1)], closed: [] },
333 issues: { opened: [count(BO, 2)], closed: [count(ANA, 1), count(G1T, 1)] },
334 reviews: [count(BO, 2), count(G1T, 2)],
335 comments: [count(ANA, 4), count(MARGO, 3)],
336 deployments: { succeeded: [count(ANA, 2)], failed: [], production: 2 },
337 releases: [{ tag: "v1.4.0", name: "Autumn", actor: ANA, at: iso(NOW - 3 * HOUR) }],
338 packages: [],
339 },
340 {
341 ...quietRepo("rep_api"),
342 pushes: { count: 5, commits: 8, branches: ["main"], by: [{ actor: CY, count: 5, commits: 8 }], default_branch: { count: 5, commits: 8, by: [{ actor: CY, count: 5, commits: 8 }], last_at: iso(NOW - 5 * HOUR) } },
343 pulls: { opened: [count(CY, 1)], merged: [count(CY, 1)], closed: [] },
344 packages: [{ ecosystem: "container", name: "api", version: "1.4.0", actor: G1T, at: iso(NOW - HOUR) }],
345 },
346 {
347 ...quietRepo("rep_docs"),
348 pushes: { count: 2, commits: 2, branches: ["typo"], by: [{ actor: BO, count: 2, commits: 2 }], default_branch: { count: 0, commits: 0, by: [], last_at: null } },
349 },
350 {
351 ...quietRepo("rep_cli"),
352 pushes: { count: 3, commits: 4, branches: ["trunk"], by: [{ actor: ANA, count: 1, commits: 2 }, { actor: MARGO, count: 2, commits: 2 }], default_branch: { count: 3, commits: 4, by: [{ actor: ANA, count: 1, commits: 2 }, { actor: MARGO, count: 2, commits: 2 }], last_at: iso(NOW - 30 * 60_000) } },
353 },
354 ],
355 folios: {
356 created: [count(ANA, 1)],
357 edited_count: 7,
358 edited: [
359 { folio_id: "fol_1", kind: "doc", authors: [ANA, MARGO] },
360 { folio_id: "fol_2", kind: "doc", authors: [ANA] },
361 { folio_id: "fol_3", kind: "doc", authors: [BO] },
362 { folio_id: "fol_4", kind: "doc", authors: [BO] },
363 { folio_id: "fol_5", kind: "doc", authors: [CY] },
364 { folio_id: "fol_6", kind: "doc", authors: [CY] },
365 { folio_id: "fol_7", kind: "doc", authors: [MARGO] },
366 ],
367 },
368 };
369}
370
371const busyChat: ChatActivity = {
372 from: iso(SPAN.from),
373 until: iso(SPAN.now),
374 messages: 120,
375 channels: 5,
376 authors: [
377 { key: ANA, messages: 60 },
378 { key: BO, messages: 30 },
379 { key: CY, messages: 15 },
380 { key: MARGO, messages: 15 },
381 ],
382};
383
384test("an actor is not a person when it is g1t or an agent of older rows; everyone else is", () => {
385 assert.equal(actorKind(MARGO), "agent");
386 assert.equal(actorKind(G1T), "agent");
387 assert.equal(actorKind("user:agt_01abc"), "agent", "an agent named by its own id under user: is still a robot");
388 assert.equal(actorKind("user:wsp_01abc"), "agent", "a workspace, by its own token or g1t's upkeep, is never a person");
389 assert.equal(actorKind("user:g1t"), "agent");
390 assert.equal(actorKind(ANA), "person");
391 assert.equal(actorKind(""), "person");
392 assert.equal(actorKind("user:usr_bot", new Set(["user:usr_bot"])), "agent");
393});
394
395test("the digest: what people did, in lines with counts, and who", () => {
396 const digest = digestOf({ slug: "acme", repos: REPOS, activity: busyDay(), chat: busyChat, names: NAMES, code: true });
397 assert.deepEqual(
398 digest.people.lines.map((line) => [line.text, line.to]),
399 [
400 ["3 people pushed 41 commits to 4 projects", "/acme/-/repositories"],
401 ["opened 2 pull requests, merged 5", "/acme/-/repositories"],
402 ["reviewed 2 pull requests", "/acme/web/pulls"],
403 ["opened 2 issues, closed 1", "/acme/web/issues"],
404 ["left 4 comments", "/acme/web/issues"],
405 ["2 deploys went out", "/acme/web/deployments"],
406 ["published 1 release", "/acme/web/releases"],
407 ["created 1 doc, edited 6 docs", "/acme/-/artifacts"],
408 ["sent 105 messages in 5 conversations", "/acme/-/chat"],
409 ],
410 );
411 // Faces: most active first, by name.
412 assert.deepEqual(
413 digest.people.actors.map((actor) => actor.name),
414 ["ana", "bo", "cy"],
415 );
416 assert.deepEqual(digest.summary, { people: 3, commits: 41, merged: 5, said: 105, anything: true, peopleRead: true });
417 assert.deepEqual(digest.missing, []);
418 assert.equal(digest.partial, false);
419});
420
421test("the digest says which part didn't answer, and counts what could be read", () => {
422 const noChat = digestOf({ slug: "acme", repos: REPOS, activity: busyDay(), chat: null, names: NAMES, code: true });
423 assert.deepEqual(noChat.missing, ["Chat"]);
424 assert.equal(noChat.people.lines.some((line) => line.key === "chat"), false);
425 const noCode = digestOf({ slug: "acme", repos: REPOS, activity: null, chat: busyChat, names: NAMES, code: true });
426 assert.deepEqual(noCode.missing, ["Code"]);
427 assert.deepEqual(
428 noCode.people.lines.map((line) => line.text),
429 ["sent 105 messages in 5 conversations"],
430 );
431 assert.equal(noCode.summary.peopleRead, true);
432 // Without Code access, events are read for artifacts only: no repositories, nothing missing.
433 const member = digestOf({ slug: "acme", repos: {}, activity: { ...busyDay(), repos: [] }, chat: busyChat, names: NAMES, code: false });
434 assert.deepEqual(member.missing, []);
435 assert.deepEqual(
436 member.people.lines.map((line) => line.key),
437 ["docs", "chat"],
438 );
439 // A digest cut short says so.
440 const cut = digestOf({ slug: "acme", repos: REPOS, activity: { ...busyDay(), complete: false }, chat: busyChat, names: NAMES, code: true });
441 assert.equal(cut.partial, true);
442 // Nobody named: an import's pushes are still commits.
443 const imported = digestOf({
444 slug: "acme",
445 repos: REPOS,
446 activity: { ...busyDay(), folios: null, repos: [{ ...quietRepo("rep_web"), pushes: { count: 2, commits: 7, branches: ["main"], by: [{ actor: "", count: 2, commits: 7 }], default_branch: { count: 2, commits: 7, by: [{ actor: "", count: 2, commits: 7 }], last_at: iso(NOW - HOUR) } } }] },
447 chat: null,
448 names: {},
449 code: true,
450 });
451 assert.deepEqual(
452 imported.people.lines.map((line) => [line.text, line.to]),
453 [["7 commits pushed to 1 project", "/acme/web/activity"]],
454 );
455 assert.equal(imported.summary.people, 0);
456});
457
458test("the sentence: people first, then what landed; honest when quiet or unread", () => {
459 const digest = digestOf({ slug: "acme", repos: REPOS, activity: busyDay(), chat: busyChat, names: NAMES, code: true });
460 assert.equal(
461 spanSentence({ words: "Since Tuesday evening" }, digest, 12),
462 "Since Tuesday evening, 3 people pushed 41 commits and merged 5 changes and 12 changes landed.",
463 );
464 const quiet = digestOf({ slug: "acme", repos: REPOS, activity: { ...busyDay(), repos: [], folios: { created: [], edited: [], edited_count: 0 } }, chat: { ...busyChat, messages: 0, channels: 0, authors: [] }, names: {}, code: true });
465 assert.equal(spanSentence({ words: "Since Tuesday evening" }, quiet, 0), "Quiet since Tuesday evening: nothing pushed, merged, deployed or said.");
466 assert.equal(spanSentence({ words: "In the last 24 hours" }, quiet, 0), "Quiet in the last 24 hours: nothing pushed, merged, deployed or said.");
467 // Only merges.
468 const mergesOnly = digestOf({ slug: "acme", repos: REPOS, activity: { ...busyDay(), repos: [], folios: null }, chat: { ...busyChat, authors: [], messages: 0, channels: 0 }, names: {}, code: true });
469 assert.equal(spanSentence({ words: "Since this morning" }, mergesOnly, 5), "Since this morning, 5 changes landed.");
470 // People only talked.
471 const talked = digestOf({ slug: "acme", repos: REPOS, activity: { ...busyDay(), repos: [], folios: null }, chat: busyChat, names: NAMES, code: true });
472 assert.equal(spanSentence({ words: "Since an hour ago" }, talked, 0), "Since an hour ago, 3 people sent 105 messages.");
473 // Neither events nor chat answered.
474 const unread = digestOf({ slug: "acme", repos: REPOS, activity: null, chat: null, names: {}, code: true });
475 assert.equal(spanSentence({ words: "Since this morning" }, unread, 5), "Since this morning, 5 changes landed; what people did couldn't be read.");
476 const nothingRead = digestOf({ slug: "acme", repos: REPOS, activity: null, chat: null, names: {}, code: true });
477 assert.equal(spanSentence({ words: "Since this morning" }, nothingRead, 0), "Since this morning, what people did couldn't be read.");
478 assert.equal(spanSentence({ words: "Since this morning" }, null, null), "Since this morning, what happened couldn't be read.");
479 assert.equal(waitingSentence(0), "Nothing needs you right now.");
480 assert.equal(waitingSentence(1), "1 thing needs you.");
481 assert.equal(waitingSentence(8), "8 things need you.");
482});
483
484test("what landed: every merge in the span, newest first, g1t's older ones as @g1t", () => {
485 const merged = landedIn(
486 [
487 pull({ number: 1, mergedAt: iso(NOW - 2 * DAY), mergedBy: "bo" }),
488 pull({ number: 2, author: { username: "bo", kind: "user" }, mergedAt: iso(NOW - HOUR) }),
489 pull({ number: 3, mergedAt: iso(NOW - 5 * DAY) }),
490 pull({ number: 4, status: "open", mergedAt: null }),
491 ],
492 SPAN,
493 );
494 assert.deepEqual(merged.map((change) => [change.kind, change.title, change.detail]), [
495 ["merge", "Fix the flaky test", "web#2 · by bo"],
496 ["merge", "Fix the flaky test", "web#1 · by @g1t, merged by bo"],
497 ]);
498});
499
500test("what landed, besides merges: direct pushes, releases, packages and production builds, newest first", () => {
501 const fromActivity = landedFromActivity(busyDay(), REPOS, NAMES, "acme");
502 assert.deepEqual(
503 fromActivity.map((change) => [change.kind, change.title, change.detail, change.to]),
504 [
505 ["push", "9 commits pushed to main", "web · 3 pushes by ana", "/acme/web/activity"],
506 ["release", "v1.4.0 · Autumn", "web · released by ana", "/acme/web/releases/tag/v1.4.0"],
507 ["push", "8 commits pushed to main", "api · 5 pushes by cy", "/acme/api/activity"],
508 ["package", "api 1.4.0", "container package · published by @g1t", "/acme/-/packages"],
509 ["push", "4 commits pushed to trunk", "cli · 3 pushes by ana and @margo", "/acme/cli/activity"],
510 ],
511 );
512 const builds = landedDeploys([
513 { key: "deploy:site:d1", project: "site", kind: "production", outcome: "live", commit: "abcdef1", branch: null, at: NOW - HOUR, to: "/acme/site/deployments/d1", by: "@g1t" },
514 { key: "deploy:site:d2", project: "site", kind: "preview", outcome: "live", commit: "abcdef2", branch: "fix", at: NOW - HOUR, to: "/acme/site/deployments/d2", by: "bo" },
515 { key: "deploy:site:d3", project: "site", kind: "production", outcome: "failed", commit: "abcdef3", branch: null, at: NOW - HOUR, to: "/acme/site/deployments/d3", by: "bo" },
516 ]);
517 assert.deepEqual(builds.map((change) => [change.kind, change.title]), [["deploy", "Production of site went live"]]);
518 const all = landed([fromActivity, builds]);
519 assert.deepEqual(
520 all.map((change) => change.key),
521 ["push:rep_cli", "landed:deploy:site:d1", "package:rep_api:container:api:1.4.0", "push:rep_web", "release:rep_web:v1.4.0", "push:rep_api"],
522 );
523});
524
525const deploy = (over: Partial<Deployment>): Deployment => ({
526 id: "d1",
527 kind: "production",
528 branch: null,
529 number: null,
530 commit: "abcdef1234",
531 status: "ready",
532 url: "https://site.g1t.page",
533 error: null,
534 warnings: [],
535 buildSeconds: 40,
536 createdBy: "bo",
537 createdAt: iso(NOW - 2 * HOUR),
538 finishedAt: iso(NOW - HOUR),
539 ...over,
540});
541
542test("builds that finished in the span: live or failed, never skipped", () => {
543 const rows = deploysIn(
544 [
545 {
546 slug: "site",
547 deployments: [
548 deploy({}),
549 deploy({ id: "d2", status: "replaced", finishedAt: iso(NOW - DAY) }),
550 deploy({ id: "d3", status: "failed", finishedAt: iso(NOW - 2 * DAY) }),
551 deploy({ id: "d4", status: "skipped", finishedAt: iso(NOW - HOUR) }),
552 deploy({ id: "d5", status: "building", finishedAt: null }),
553 deploy({ id: "d6", finishedAt: iso(NOW - 5 * DAY) }),
554 ],
555 },
556 ],
557 "acme",
558 SPAN,
559 { "user:bo": "bo" },
560 );
561 assert.deepEqual(rows.map((row) => [row.key, row.outcome]), [
562 ["deploy:site:d1", "live"],
563 ["deploy:site:d2", "live"],
564 ["deploy:site:d3", "failed"],
565 ]);
566 assert.equal(rows[0]!.to, "/acme/site/deployments/d1");
567 assert.equal(rows[0]!.commit, "abcdef1");
568 // The maker is named, never shown as an id; an older agent's by its handle; an id identity doesn't know as someone.
569 assert.equal(rows[0]!.by, "bo");
570 const unknown = deploysIn([{ slug: "site", deployments: [deploy({ createdBy: "usr_zzz" })] }], "acme", SPAN);
571 assert.equal(unknown[0]!.by, "someone");
572 const robot = deploysIn([{ slug: "site", deployments: [deploy({ createdBy: "agt_01x" })] }], "acme", SPAN, { "user:agt_01x": "otto" });
573 assert.equal(robot[0]!.by, "@otto");
574});
575
576test("running now: workflow runs and builds going", () => {
577 const builds = runningDeploys(
578 [
579 { slug: "site", enabled: true, production: null, previews: 0, latest: deploy({ status: "building", finishedAt: null }) },
580 { slug: "docs", enabled: true, production: null, previews: 0, latest: deploy({}) },
581 ],
582 "acme",
583 );
584 assert.deepEqual(builds.map((row) => row.status), ["Building"]);
585 const run = (over: Partial<WorkflowRun>): WorkflowRun => ({
586 id: "run_1",
587 workflowId: "wf_1",
588 path: ".g1t/workflows/ci.yml",
589 name: "CI",
590 title: "Fix the flaky test",
591 number: 12,
592 attempt: 1,
593 event: "push",
594 ref: "refs/heads/main",
595 sha: "abcdef1",
596 pull: null,
597 status: "in_progress",
598 conclusion: null,
599 error: null,
600 actor: "ana",
601 createdAt: iso(NOW - 10 * 60_000),
602 startedAt: iso(NOW - 9 * 60_000),
603 finishedAt: null,
604 ...over,
605 });
606 const workflows = runningWorkflows([
607 { repo, runs: [run({}), run({ id: "run_2", status: "queued", startedAt: null, title: "" }), run({ id: "run_3", status: "completed", conclusion: "success" }), run({ id: "run_4", status: "action_required" })] },
608 ]);
609 assert.deepEqual(
610 workflows.map((row) => [row.key, row.title, row.detail, row.status, row.to]),
611 [
612 ["workflow:acme/web:run_1", "CI: Fix the flaky test", "acme/web main · by ana", "Running", "/acme/web/actions/runs/run_1"],
613 ["workflow:acme/web:run_2", "CI", "acme/web main · by ana", "Queued", "/acme/web/actions/runs/run_2"],
614 ["workflow:acme/web:run_4", "CI: Fix the flaky test", "acme/web main · by ana", "Needs approval", "/acme/web/actions/runs/run_4"],
615 ],
616 );
617 // Longest-going first within a kind: workflow runs, then builds.
618 assert.deepEqual(running([builds, workflows.slice(0, 1)]).map((row) => row.key), ["workflow:acme/web:run_1", "deploy:site:d1"]);
619});
620
621
622test("the span's months: one, or the ones it crosses", () => {
623 assert.deepEqual(spanMonths(SPAN), ["2026-10"]);
624 assert.deepEqual(spanMonths({ from: Date.parse("2026-09-28T00:00:00Z"), now: NOW }), ["2026-09", "2026-10"]);
625 assert.deepEqual(spanMonths({ from: Date.parse("2025-12-30T00:00:00Z"), now: Date.parse("2026-01-02T00:00:00Z") }), ["2025-12", "2026-01"]);
626});
627
628test("spend in the span: the UTC days it covers across months, at price, without money in", () => {
629 const september = {
630 groups: [
631 { key: "2026-09-29", label: "", chargedMicros: 0, lines: [{ kind: "Workflow minutes", count: 2, chargedMicros: 1_000_000, costMicros: 0 }] },
632 { key: "2026-09-20", label: "", chargedMicros: 0, lines: [{ kind: "Workflow minutes", count: 9, chargedMicros: 9_000_000, costMicros: 0 }] },
633 ],
634 totals: { chargedMicros: 10_000_000, paidMicros: 0, costMicros: 0, entries: 2 },
635 };
636 const october = {
637 groups: [
638 {
639 key: "2026-10-09",
640 label: "2026-10-09",
641 chargedMicros: 0,
642 lines: [
643 { kind: "Workflow minutes", count: 12, chargedMicros: 3_000_000, costMicros: 2_500_000, priceMicros: 4_000_000 },
644 { kind: "Sandbox time", count: 12, chargedMicros: 600_000, costMicros: 500_000 },
645 { kind: "Payments", count: 1, chargedMicros: -50_000_000, costMicros: 0, priceMicros: 0 },
646 ],
647 },
648 ],
649 totals: { chargedMicros: 12_600_000, paidMicros: 50_000_000, costMicros: 0, entries: 4, priceMicros: 13_600_000 },
650 };
651 const spend = spendIn([september, october], { from: Date.parse("2026-09-29T15:00:00Z"), now: NOW }, 13_609_000);
652 assert.deepEqual([spend.from, spend.to], ["2026-09-29", "2026-10-09"]);
653 assert.equal(spend.totalMicros, 5_600_000);
654 assert.deepEqual(spend.lines.map((l) => [l.kind, l.micros, l.count]), [
655 ["Workflow minutes", 5_000_000, 14],
656 ["Sandbox time", 600_000, 12],
657 ]);
658 // The month so far is billing's usage report, the top bar's figure, not the statement: it carries what is not yet closed.
659 assert.equal(spend.monthMicros, 13_609_000);
660 assert.equal(spendIn([september, october], { from: Date.parse("2026-09-29T15:00:00Z"), now: NOW }, null).monthMicros, null);
661});
662
663
664// --- What landed: shipped, made in a session, day by day -----------------------
665
666const merge = (over: Partial<LandedChange> = {}): LandedChange => ({
667 key: "landed:acme/web#12",
668 kind: "merge",
669 title: "Add retries",
670 detail: "web#12 · by ana",
671 to: "/acme/web/pull/12",
672 at: NOW - 2 * HOUR,
673 sha: "m12",
674 repoId: "rep_web",
675 ...over,
676});
677
678test("each merge and push says whether it is live, from what deployments recorded", () => {
679 const push = merge({ key: "push:rep_web:evt_1", kind: "push", sha: "p1", at: NOW - HOUR });
680 const release = merge({ key: "release:rep_web:v1", kind: "release", sha: null });
681 assert.deepEqual(shippedAsks([merge(), push, release]), [
682 { repo_id: "rep_web", commits: [{ sha: "m12", landed_at: iso(NOW - 2 * HOUR) }, { sha: "p1", landed_at: iso(NOW - HOUR) }] },
683 ]);
684 const answers = [
685 {
686 repo_id: "rep_web",
687 deploys: true,
688 commits: [
689 { sha: "m12", state: "live" as const, deployed_at: iso(NOW - 2 * HOUR + 9 * 60_000), url: "/acme/web/actions/runs/run_1", environment: "production" },
690 { sha: "p1", state: "waiting" as const, deployed_at: null, url: null, environment: null },
691 ],
692 },
693 ];
694 const [shippedMerge, shippedPush, untouched] = withShipped([merge(), push, release], answers);
695 assert.deepEqual(shippedMerge!.shipped, { state: "live", at: NOW - 2 * HOUR + 9 * 60_000, to: "/acme/web/actions/runs/run_1" });
696 assert.equal(shippedLabel(shippedMerge!.shipped!, shippedMerge!), "Live 9 min after merge");
697 assert.equal(shippedLabel(shippedPush!.shipped!, shippedPush!), "Waiting to deploy");
698 assert.equal(shippedLabel({ state: "failed", at: null, to: "/x" }, push), "Deploy failed");
699 assert.equal(shippedLabel({ state: "deploying", at: null, to: null }, push), "Deploying");
700 assert.equal(shippedLabel({ state: "live", at: push.at + 3 * HOUR, to: null }, push), "Live 3 h after push");
701 assert.equal(untouched!.shipped, undefined);
702 // A project that does not deploy says nothing; deployments not answering leaves rows as they were.
703 assert.equal(withShipped([merge()], [{ repo_id: "rep_web", deploys: false, commits: [] }])[0]!.shipped, undefined);
704 assert.deepEqual(withShipped([merge()], null), [merge()]);
705});
706
707test("a change made in a harness session carries the agent mark, linked to the session", () => {
708 const [marked, plain] = withSessions([merge(), merge({ key: "landed:acme/web#13", sha: "m13" })], [
709 { repo: { namespace: "acme", name: "web" }, pulls: [{ number: 12, sessions: [{ id: "ses_1", harness: "claude_code" }, { id: "ses_2", harness: "codex" }] }] },
710 ]);
711 assert.deepEqual(marked!.agent, { harness: "claude_code", to: "/acme/web/work/sessions/ses_1" });
712 assert.equal(agentLabel(marked!.agent!), "Made in a Claude Code session");
713 assert.equal(plain!.agent, undefined);
714 assert.equal(agentLabel({ harness: null, to: null }), "Made in a session");
715 assert.deepEqual(sessionTrailers("Fix the bell\n\nG1t-Session: ses_01ABC\ng1t-session: ses_01abc\nG1t-Session: ses_02x"), ["ses_01abc", "ses_02x"]);
716 assert.deepEqual(sessionTrailers("Mentions G1t-Session: ses_1 in prose"), []);
717});
718
719test("each person's push to a default branch is a row: its commit, who pushed, and its session", () => {
720 const repo = { namespace: "acme", name: "web", defaultBranch: "main" };
721 const history = {
722 rep_web: [
723 { hash: "c3", parents: ["c2"], message: "Fix the bell's count\n\nG1t-Session: ses_9" },
724 { hash: "c2", parents: ["c1"], message: "Tidy" },
725 { hash: "c1", parents: ["c0"], message: "Start" },
726 { hash: "c0", parents: [], message: "Root" },
727 ],
728 };
729 const push = (id: string, after: string, before: string, at: number, actor = "usr_ana") => ({
730 repoId: "rep_web",
731 repo,
732 push: { id, time: iso(at), actor, data: { after, before } },
733 by: "ana",
734 });
735 const span = { from: NOW - DAY, now: NOW };
736 const rows = pushRows([push("evt_2", "c3", "c2", NOW - HOUR), push("evt_1", "c2", "c0", NOW - 2 * HOUR), push("evt_0", "c1", "c0", NOW - 2 * DAY)], history, span);
737 assert.deepEqual(
738 rows.map((row) => [row.title, row.detail, row.sha, row.to, row.agent?.to ?? null]),
739 [
740 ["Fix the bell's count", "web · pushed to main · by ana", "c3", "/acme/web/commit/c3", "/acme/web/work/sessions/ses_9"],
741 ["2 commits pushed to main", "web · Tidy · by ana", "c2", "/acme/web/activity", null],
742 ],
743 );
744 // g1t's own pushes (a pull request landing) are listed as the merge instead.
745 assert.deepEqual(pushRows([push("evt_3", "c3", "c2", NOW - HOUR, "usr_g1t_agent")], history, span), []);
746});
747
748test("what landed over the week reads day by day, newest first", () => {
749 const tz = "America/Los_Angeles";
750 const days = landedByDay([merge({ at: NOW - DAY }), merge({ key: "b", at: NOW - HOUR }), merge({ key: "c", at: NOW - 3 * DAY })], NOW, tz);
751 assert.deepEqual(days.map((day) => [day.label, day.rows.length]), [["Today", 1], ["Yesterday", 1], ["Tue, Oct 6", 1]]);
752});
753
754test("while you were away: what settled by itself, what shipped, and what still needs you", () => {
755 const span = { from: NOW - DAY, now: NOW };
756 const settled = settledIn(
757 [
758 item({ id: "n1", doneAt: iso(NOW - HOUR), settledReason: "CI passed in run 330" }),
759 item({ id: "n2", doneAt: iso(NOW - 2 * HOUR), settledReason: "Merged", settledBy: "ana" }),
760 // Marked done by hand: yours, not g1t's.
761 item({ id: "n3", doneAt: iso(NOW - HOUR) }),
762 // Before the span, or another workspace's.
763 item({ id: "n4", doneAt: iso(NOW - 2 * DAY), settledReason: "Merged" }),
764 item({ id: "n5", doneAt: iso(NOW - HOUR), settledReason: "Merged", workspace: "other", repo: "other/x" }),
765 ],
766 "acme",
767 true,
768 span,
769 );
770 assert.deepEqual(
771 settled.map((row) => [row.key, row.why, row.where]),
772 [
773 ["settled:n1", "CI passed in run 330", "acme/web#12"],
774 ["settled:n2", "Merged by @ana", "acme/web#12"],
775 ],
776 );
777 assert.equal(settledIn([item({ doneAt: iso(NOW - HOUR), settledReason: "Merged" })], "acme", false, span).length, 0, "nothing about repositories without Code");
778 const away = awayOf({
779 settled,
780 landed: [merge({ shipped: { state: "live", at: NOW, to: null } }), merge({ key: "x", shipped: { state: "waiting", at: null, to: null } }), merge({ key: "y" })],
781 needs: 3,
782 });
783 assert.deepEqual({ ...away, settled: away.settled.length }, { settled: 2, shipped: 1, waiting: 1, deploying: 0, failed: 0, needs: 3 });
784});
785
786test("Summarize asks about the span the page shows, as a person would", () => {
787 const now = Date.UTC(2026, 9, 14, 22, 0);
788 assert.equal(
789 summarizeQuestion({ key: "last", note: null, words: "Since this afternoon", from: Date.UTC(2026, 9, 14, 18, 0), now }, "UTC"),
790 "Summarize what changed since I was last here (6:00 PM) in plain English.",
791 );
792 assert.equal(summarizeQuestion({ key: "24h", note: null, words: "In the last 24 hours", from: now - DAY, now }, "UTC"), "Summarize what changed in the last 24 hours in plain English.");
793 // A first visit shows the last 24 hours, so it is not asked as "since I was last here".
794 assert.doesNotMatch(summarizeQuestion({ key: "last", note: "first", words: "In the last 24 hours", from: now - DAY, now }, "UTC"), /last here/);
795});
796
797test("Explain asks about the one change, by what it is", () => {
798 assert.equal(explainQuestion({ kind: "merge", title: "Faster pushes", to: "/acme/web/pull/12" }), "Explain pull request #12 in acme/web in plain English: what changed, why, and is it live?");
799 assert.equal(explainQuestion({ kind: "push", title: "Fix login", to: "/acme/web/commit/abc1234def" }), "Explain commit abc1234 in acme/web in plain English: what changed, why, and is it live?");
800 assert.match(explainQuestion({ kind: "deploy", title: "web", to: "/acme/web/deployments/1" }), /went out in this deployment/);
801 assert.match(explainQuestion({ kind: "release", title: "v1.2.0", to: "/acme/web/releases/v1.2.0" }), /What's new in v1\.2\.0/);
802});