Skip to content
182 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { Claim, Decision, G1tEvent, WorkSession } from "@g1t/contracts";
5
6import {
7 checkHref,
8 evidenceOf,
9 expiresIn,
10 feedLine,
11 harnessName,
12 liveByIssue,
13 numberedParts,
14 parseBlockedBy,
15 resolutionOf,
16 span,
17 standingFailure,
18} from "./coordination.ts";
19
20const ana = { id: "usr_ana", username: "ana" };
21const bo = { id: "usr_bo", username: "bo" };
22
23function claim(issue: number, over: Partial<Claim> = {}): Claim {
24 return {
25 id: `clm_${issue}_${over.actor?.username ?? "ana"}`,
26 repoId: "rep_1",
27 issue,
28 sessionId: null,
29 actor: ana,
30 harness: "claude_code",
31 note: null,
32 state: "active",
33 createdAt: "2026-10-12T10:00:00Z",
34 expiresAt: "2026-10-12T10:30:00Z",
35 endedAt: null,
36 endReason: null,
37 handoffTo: null,
38 ...over,
39 };
40}
41
42function session(id: string, over: Partial<WorkSession> = {}): WorkSession {
43 return {
44 id,
45 repoId: "rep_1",
46 issue: null,
47 pull: null,
48 branch: null,
49 actor: bo,
50 harness: "codex",
51 harnessSessionId: null,
52 title: null,
53 state: "active",
54 headCommit: null,
55 startedAt: "2026-10-12T10:00:00Z",
56 lastSeenAt: "2026-10-12T10:05:00Z",
57 endedAt: null,
58 endReason: null,
59 ...over,
60 };
61}
62
63function decision(kind: Decision["kind"], at: string, over: Partial<Decision> = {}): Decision {
64 return {
65 id: `dec_${kind}_${at}`,
66 issue: 2,
67 pull: 5,
68 sessionId: null,
69 actor: null,
70 kind,
71 confidence: "tested",
72 summary: kind,
73 evidence: {},
74 createdAt: at,
75 ...over,
76 };
77}
78
79const event = (type: string, data: unknown): G1tEvent =>
80 ({ id: `evt_${type}`, type, source: "work", time: "2026-10-12T10:00:00Z", repoId: "rep_1", actor: "usr_ana", data }) as G1tEvent;
81
82test("harnesses are named as people know them", () => {
83 assert.equal(harnessName("claude_code"), "Claude Code");
84 assert.equal(harnessName("codex"), "Codex");
85 assert.equal(harnessName(null), null);
86});
87
88test("a claim says when it lapses; one that ended says nothing", () => {
89 const now = Date.parse("2026-10-12T10:07:00Z");
90 assert.equal(expiresIn(claim(1), now), "expires in 23m");
91 assert.equal(expiresIn(claim(1, { expiresAt: "2026-10-12T12:00:00Z" }), now), "expires in 1h 53m");
92 assert.equal(expiresIn(claim(1, { expiresAt: "2026-10-12T10:00:00Z" }), now), "expiring now");
93 assert.equal(expiresIn(claim(1, { state: "released" }), now), null);
94 assert.equal(span(30_000), "less than a minute");
95 assert.equal(span(2 * 3_600_000), "2h");
96});
97
98test("live work is grouped by issue, most recent first, with sessions on no issue apart", () => {
99 const { issues, loose } = liveByIssue(
100 [claim(1), claim(2, { createdAt: "2026-10-12T10:20:00Z" }), claim(2, { actor: bo })],
101 [session("ses_a", { issue: 1, lastSeenAt: "2026-10-12T10:30:00Z" }), session("ses_b")],
102 );
103 assert.deepEqual(
104 issues.map((group) => [group.issue, group.claims.length, group.sessions.length]),
105 [
106 [1, 1, 1],
107 [2, 2, 0],
108 ],
109 );
110 assert.deepEqual(loose.map((one) => one.id), ["ses_b"]);
111});
112
113test("evidence is read as far as it goes", () => {
114 const failed = decision("integration_failure", "2026-10-12T10:00:00Z", {
115 evidence: { commit: "abc1234", check: { name: "CI / merge_group", conclusion: "failure", url: "https://g1t.sh/acme/web/actions/runs/run_1" }, combination: [4, 5], suspects: [4], bisected: false },
116 });
117 assert.deepEqual(evidenceOf(failed), {
118 commit: "abc1234",
119 check: { name: "CI / merge_group", conclusion: "failure", url: "https://g1t.sh/acme/web/actions/runs/run_1" },
120 combination: [4, 5],
121 suspects: [4],
122 bisected: false,
123 });
124 assert.deepEqual(evidenceOf(decision("note", "2026-10-12T10:00:00Z", { evidence: { combination: "nope" } })).combination, []);
125 assert.equal(checkHref("https://g1t.sh/acme/web/actions/runs/run_1"), "/acme/web/actions/runs/run_1");
126 assert.equal(checkHref("https://ci.example.com/build/9"), "https://ci.example.com/build/9");
127 assert.equal(checkHref(null), null);
128});
129
130test("a failure stands until the pull request passes in the queue or merges, and a fix after it answers it", () => {
131 const failed = decision("integration_failure", "2026-10-12T10:00:00Z");
132 const fix = decision("fix", "2026-10-12T10:10:00Z", { confidence: "claimed" });
133 const passed = decision("integration_passed", "2026-10-12T10:20:00Z");
134 assert.equal(standingFailure([failed], 5), failed);
135 assert.equal(standingFailure([failed, fix], 5), failed);
136 assert.equal(standingFailure([passed, failed], 5), null);
137 assert.equal(standingFailure([failed], 6), null);
138 assert.deepEqual(resolutionOf([failed, fix, passed], failed), { fix, passed });
139 assert.deepEqual(resolutionOf([fix, failed], { ...failed, createdAt: "2026-10-12T10:15:00Z" }), { fix: null, passed: null });
140});
141
142test("coordination events read as lines of the feed", () => {
143 const claimed = feedLine(event("work.claimed", { repoId: "rep_1", number: 2, issue: 2, claim: claim(2, { actor: bo, harness: "codex" }), others: [claim(2)] }))!;
144 assert.equal(claimed.who, "bo");
145 assert.equal(claimed.harness, "codex");
146 assert.equal(claimed.text, "claimed #2 (ana is on it too)");
147 assert.equal(claimed.tone, "warn");
148
149 const failed = feedLine(
150 event("integration.failed", {
151 repoId: "rep_1", pullId: "pul_5", number: 5, pull: 5, issue: 2, combination: [4, 5],
152 check: { name: "CI / merge_group", conclusion: "failure", url: null }, bisected: false, suspects: [4], otherPulls: [4], commit: null,
153 }),
154 )!;
155 assert.equal(failed.who, null);
156 assert.equal(failed.text, "#5 failed in the merge queue combined as #4, #5: CI / merge_group; fails with #4");
157 assert.equal(failed.tone, "danger");
158
159 const unblocked = feedLine(
160 event("dependency.changed", { repoId: "rep_1", number: 2, issue: 2, blockedBy: [1], added: [], removed: [], cleared: [1], remaining: [], mergedPull: 4 }),
161 )!;
162 assert.equal(unblocked.text, "#2 #1 landed (#4 merged); unblocked");
163
164 const overlap = feedLine(event("overlap.detected", { repoId: "rep_1", number: 5, pull: 5, otherPull: 4, files: ["src/auth.rs"] }))!;
165 assert.equal(overlap.text, "#5 and #4 change the same file: src/auth.rs");
166
167 const started = feedLine(event("session.started", { repoId: "rep_1", session: session("ses_a", { issue: 2 }) }))!;
168 assert.equal(started.text, "started a session on #2");
169 assert.equal(feedLine(event("session.appended", { repoId: "rep_1", number: 5, pullId: "p", count: 3 })), null);
170});
171
172test("numbers in a sentence are found for linking", () => {
173 assert.deepEqual(numberedParts("#5 fails with #4."), [5, " fails with ", 4, "."]);
174 assert.deepEqual(numberedParts("no numbers"), ["no numbers"]);
175});
176
177test("blocked_by is typed as numbers", () => {
178 assert.deepEqual(parseBlockedBy("#12, 14 #12"), [12, 14]);
179 assert.deepEqual(parseBlockedBy(""), []);
180 assert.equal(parseBlockedBy("#12, soon"), null);
181 assert.equal(parseBlockedBy("0"), null);
182});