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.

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