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.

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