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.

117 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { Claim, ItemRepo, WorkItem, WorkspaceSession } from "@g1t/contracts";
5
6import { boardOf, pullsByItem, shownStage, stageOf } from "./board.ts";
7
8const NOW = Date.parse("2026-10-17T12:00:00Z");
9const DAY = 86_400_000;
10
11function repo(path: string, role: ItemRepo["role"], landed = false): ItemRepo {
12 return { repoId: `rep_${path}`, path, role, legacyNumber: null, landed };
13}
14
15function item(number: number, more: Partial<WorkItem> = {}): WorkItem {
16 return {
17 id: `wki_${number}`,
18 workspace: "acme",
19 number,
20 title: `Item ${number}`,
21 body: "",
22 labels: [],
23 state: "open",
24 reason: null,
25 resolutions: [],
26 author: { id: "usr_1", username: "ana" } as WorkItem["author"],
27 requestedBy: null,
28 createdAt: new Date(NOW - 5 * DAY).toISOString(),
29 updatedAt: new Date(NOW - number * 1000).toISOString(),
30 closedAt: null,
31 pullCount: 0,
32 commentCount: 0,
33 assignees: [],
34 blockedBy: [],
35 agent: null,
36 repos: [repo("acme/web", "origin")],
37 ...more,
38 };
39}
40
41function live(item: number, more: Partial<WorkspaceSession> = {}): WorkspaceSession {
42 return {
43 kind: "harness",
44 id: `ses_${item}`,
45 repo: "acme/web",
46 repoId: "rep_acme/web",
47 title: null,
48 actor: "bo",
49 harness: "claude_code",
50 agent: null,
51 branch: "main",
52 item,
53 itemTitle: null,
54 pull: null,
55 pullTitle: null,
56 pullStatus: null,
57 state: "active",
58 startedAt: new Date(NOW).toISOString(),
59 lastActivityAt: new Date(NOW).toISOString(),
60 subagents: 0,
61 step: null,
62 session: null,
63 ...more,
64 };
65}
66
67const claim = (number: number, who: string): Claim =>
68 ({ id: `clm_${number}`, item: number, workspace: "acme", state: "active", actor: { id: "usr_9", username: who } }) as Claim;
69
70test("each work item's stage is the first that holds: done, landing, review, progress, backlog", () => {
71 assert.equal(stageOf(item(1), {}, NOW), "backlog");
72 assert.equal(stageOf(item(2, { assignees: ["ana"] }), {}, NOW), "progress");
73 assert.equal(stageOf(item(3), { claimed: true }, NOW), "progress");
74 assert.equal(stageOf(item(4), { live: true }, NOW), "progress");
75 assert.equal(stageOf(item(5), { pulls: ["draft"] }, NOW), "progress");
76 assert.equal(stageOf(item(6, { assignees: ["ana"] }), { pulls: ["draft", "open"] }, NOW), "review");
77 // Landed in one target, not yet the other: Landing, even with a pull request open.
78 const landing = item(7, { repos: [repo("acme/web", "target", true), repo("acme/api", "target")] });
79 assert.equal(stageOf(landing, { pulls: ["open"] }, NOW), "landing");
80 // A touched repository's landing is not a target's.
81 assert.equal(stageOf(item(8, { repos: [repo("acme/web", "touched", true), repo("acme/api", "target")] }), {}, NOW), "backlog");
82 assert.equal(stageOf(item(9, { state: "closed", closedAt: new Date(NOW - 3 * DAY).toISOString() }), {}, NOW), "done");
83 // Closed long ago: off the board.
84 assert.equal(stageOf(item(10, { state: "closed", closedAt: new Date(NOW - 40 * DAY).toISOString() }), {}, NOW), null);
85});
86
87test("the board puts each item in its column with who and which session is on it", () => {
88 const items = [
89 item(1),
90 item(2),
91 item(3),
92 item(4),
93 item(5, { state: "closed", closedAt: new Date(NOW - DAY).toISOString() }),
94 item(6, { state: "closed", closedAt: new Date(NOW - 2 * DAY).toISOString() }),
95 ];
96 const board = boardOf(
97 items,
98 { claims: [claim(2, "cy")], sessions: [live(3, { pull: 9, pullStatus: "open" }), live(1, { state: "ended" })], pulls: pullsByItem([{ item: 4, status: "draft" }, { item: null, status: "open" }]) },
99 NOW,
100 );
101 assert.deepEqual(board.backlog.map((card) => card.item.number), [1]);
102 assert.deepEqual(board.progress.map((card) => card.item.number), [2, 4]);
103 assert.deepEqual(board.progress[0]!.on.people, ["cy"]);
104 // A live session's open pull request puts its item in review, the session on the card.
105 assert.deepEqual(board.review.map((card) => card.item.number), [3]);
106 assert.deepEqual(board.review[0]!.on.sessions.map((session) => session.id), ["ses_3"]);
107 assert.deepEqual(board.review[0]!.on.people, ["bo"]);
108 // Done: the most recently closed first.
109 assert.deepEqual(board.done.map((card) => card.item.number), [5, 6]);
110});
111
112test("a phone shows the stage asked for, else the first with something in it", () => {
113 const board = boardOf([item(1, { assignees: ["ana"] })], {}, NOW);
114 assert.equal(shownStage("review", board), "review");
115 assert.equal(shownStage("nonsense", board), "progress");
116 assert.equal(shownStage(null, boardOf([], {}, NOW)), "backlog");
117});