g1t/apps/web/app/lib/mission-control.test.ts

305 lines12,704 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Mission control shows where you are needed and what agents landed without you; git answers in about 200ms1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 type Merged,
6 change,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step7 confidenceAsk,
8 confidenceLine,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms9 dayKey,
10 isTestFile,
11 landedByAgents,
12 landedToday,
13 parseSort,
14 parseTab,
15 pullFacts,
16 reachesBack,
17 reasonFor,
18 signedPercent,
19 sortRows,
20 stallReason,
21 summaryLine,
22 waitingRows,
23 weekOf,
24 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step25 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms26} from "./mission-control.ts";
27
28const HOUR = 3_600_000;
29const DAY = 24 * HOUR;
30// Monday 2026-10-05, 18:00 UTC.
31const NOW = Date.parse("2026-10-05T18:00:00Z");
32const repo = { namespace: "acme", name: "web" };
33
34test("each kind of need gets its reason chip", () => {
35 assert.equal(reasonFor({ kind: "deploy", detail: "" }), "blocking");
36 assert.equal(reasonFor({ kind: "limit", detail: "" }), "blocking");
37 assert.equal(reasonFor({ kind: "review", detail: "" }), "asked_for_you");
38 assert.equal(reasonFor({ kind: "invitation", detail: "" }), "asked_for_you");
39 assert.equal(reasonFor({ kind: "ready", detail: "" }), "ready_to_merge");
40 assert.equal(reasonFor({ kind: "checks", detail: "" }), "checks_failing");
41 assert.equal(reasonFor({ kind: "stuck", detail: "" }), "stalled");
42});
43
44test("a stopped pull request's reason comes from the sentence g1t stopped with", () => {
45 assert.equal(stallReason("The acceptance checks still fail after the agent revised 3 times."), "checks_failing");
46 assert.equal(stallReason("CI / push still fails after the agent revised 2 times."), "checks_failing");
47 assert.equal(stallReason("The acceptance checks could not be run."), "checks_failing");
48 assert.equal(
49 stallReason("g1t stopped the agent's implement run when it reached its cost cap. Raise the cap under Settings, Guardrails."),
50 "outside_guardrails",
51 );
52 assert.equal(stallReason("g1t stopped the agent's review run when it reached its time cap."), "outside_guardrails");
53 assert.equal(stallReason("g1t could not merge this: the branch moved"), "blocking");
54 assert.equal(stallReason("Needs 1 approving review from someone with write access."), "needs_review");
55 assert.equal(stallReason("The review still asks for changes after the agent revised 3 times."), "needs_review");
56 assert.equal(stallReason("alex asked for changes, and the agent has already revised 3 times."), "needs_review");
57 assert.equal(stallReason("syntaqx stopped the agent's implement run."), "stalled");
58 assert.equal(stallReason("g1t could not start the next step: no slot"), "stalled");
59});
60
61test("why a need needs a person is said for every reason", () => {
62 for (const kind of ["deploy", "limit", "invitation", "review", "stuck", "checks", "ready", "conflict"] as const) {
63 const need = { kind, detail: "" };
64 assert.ok(whyFor(reasonFor(need), need).length > 20, kind);
65 }
66 const stalled = { kind: "stalled" as const, detail: "It reached its cost cap." };
67 assert.match(whyFor(reasonFor(stalled), stalled), /Guardrails/);
68});
69
70test("what the agent knows lists only what is known", () => {
71 const facts = pullFacts({
72 checkStatus: "failed",
73 files: [
74 { path: "src/retry.ts", additions: 40, deletions: 2 },
75 { path: "src/retry.test.ts", additions: 60, deletions: 0 },
76 ],
77 lifecycle: { revisions: 2 },
78 runs: [
79 { kind: "implement", costUsd: 0.5 },
80 { kind: "revise", costUsd: 0.25 },
81 { kind: "checks", costUsd: null },
82 ],
83 });
84 const by = Object.fromEntries(facts.map((f) => [f.label, f]));
85 assert.equal(by.Checks.value, "Failing");
86 assert.equal(by.Checks.tone, "bad");
87 assert.equal(by["Files changed"].value, "2");
88 assert.equal(by.Lines.value, "+100 −2");
89 assert.equal(by.Tests.value, "1 file");
90 assert.equal(by["Sent back"].value, "2 times");
91 assert.equal(by["Agent runs"].value, "2 · $0.75");
92
93 const bare = pullFacts({ checkStatus: null, files: [] });
94 assert.deepEqual(bare, [{ label: "Checks", value: "Not run", tone: null }]);
95});
96
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step97test("a change held for low confidence is its own reason, ahead of what it would read as", () => {
98 const held =
99 "The agent's confidence in this change is low (checks failing, tests not added). This repository asks a person before merging it: approve it to let it land, or ask for changes.";
100 // Its reasons name checks and approval, but it is held for its confidence.
101 assert.equal(stallReason(held), "low_confidence");
102 assert.equal(reasonFor({ kind: "stalled", detail: held }), "low_confidence");
103
104 const low = { level: "low" as const, reasons: ["tests not added", "3 revisions"], uncertainAbout: ["the retry limit"] };
105 assert.equal(withConfidence("ready_to_merge", low), "low_confidence");
106 assert.equal(withConfidence("needs_review", low), "low_confidence");
107 // A failure that needs someone anyway keeps its own chip.
108 assert.equal(withConfidence("checks_failing", low), "checks_failing");
109 assert.equal(withConfidence("ready_to_merge", { level: "medium" }), "ready_to_merge");
110 assert.equal(withConfidence("ready_to_merge", null), "ready_to_merge");
111
112 assert.equal(confidenceLine(low), "Low — tests not added, 3 revisions");
113 assert.equal(confidenceLine({ level: "high", reasons: [] }), "High");
114 assert.match(confidenceAsk(low), /not sure of the change: tests not added, 3 revisions\. Approve it/);
115
116 const why = whyFor("low_confidence", { kind: "stalled", detail: held }, low);
117 assert.match(why, /from tests not added, 3 revisions\./);
118 assert.match(why, /unsure about the retry limit/);
119 assert.match(why, /nothing merges it until you approve it/);
120 // Ready, not held: it says to look before merging.
121 assert.match(whyFor("low_confidence", { kind: "ready", detail: "" }, low), /Look at it before you merge it/);
122});
123
124test("what the agent knows gains its confidence, across the row", () => {
125 const facts = pullFacts({
126 checkStatus: "passed",
127 files: [{ path: "src/retry.ts", additions: 40, deletions: 2 }],
128 lifecycle: { revisions: 3 },
129 confidence: { level: "low", reasons: ["tests not added", "3 revisions"] },
130 });
131 const fact = facts.find((f) => f.label === "Confidence");
132 assert.deepEqual(fact, { label: "Confidence", value: "Low — tests not added, 3 revisions", tone: "bad", wide: true });
133 assert.equal(facts.at(-1)?.label, "Confidence");
134});
135
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms136test("test files are recognised by the names test runners use", () => {
137 assert.ok(isTestFile("src/a.test.ts"));
138 assert.ok(isTestFile("src/__tests__/a.ts"));
139 assert.ok(isTestFile("tests/api.rs"));
140 assert.ok(isTestFile("pkg/store_test.go"));
141 assert.ok(!isTestFile("src/testing-utils.ts"));
142 assert.ok(!isTestFile("src/contest.ts"));
143});
144
145const merged = (daysAgo: number, mergedBy: string | null, number = 1): Merged => ({
146 repo,
147 number,
148 title: `Change ${number}`,
149 agent: "g1t-agent",
150 mergedBy,
151 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
152 files: [],
153});
154
155test("a change landed without a person when g1t merged it", () => {
156 assert.ok(landedByAgents({ mergedBy: "g1t" }));
157 assert.ok(landedByAgents({ mergedBy: "g1t-agent" }));
158 assert.ok(landedByAgents({ mergedBy: null }));
159 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
160});
161
162test("the week is seven days, each split by who landed it, with the week before", () => {
163 const week = weekOf(
164 [merged(0, "g1t"), merged(0, "syntaqx"), merged(1, "g1t"), merged(6, "g1t"), merged(8, "g1t"), merged(10, "alex"), merged(20, "g1t")],
165 NOW,
166 "UTC",
167 );
168 assert.equal(week.days.length, 7);
169 assert.equal(week.days[6].key, "2026-10-05");
170 assert.equal(week.days[6].label, "Mon");
171 assert.deepEqual(
172 week.days.map((d) => [d.agents, d.people]),
173 [[1, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, 0], [1, 1]],
174 );
175 assert.equal(week.total, 4);
176 assert.equal(week.byAgents, 3);
177 assert.equal(week.previous, 2);
178 // Not knowing the week before is not the same as nothing in it.
179 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
180});
181
182test("days follow the viewer's time zone", () => {
183 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
184 const late = Date.parse("2026-10-05T02:00:00Z");
185 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
186 assert.equal(dayKey(late, "UTC"), "2026-10-05");
187 assert.equal(dayKey(late, "Not/AZone"), "2026-10-05");
188 const week = weekOf([{ mergedAt: new Date(late).toISOString(), mergedBy: "g1t" }], NOW, "America/Los_Angeles");
189 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
190});
191
192test("the change from last week, and how it reads", () => {
193 assert.equal(change(12, 10), 0.2);
194 assert.equal(change(5, 0), null);
195 assert.equal(change(5, null), null);
196 assert.equal(signedPercent(0.2), "+20%");
197 assert.equal(signedPercent(-0.054), "−5%");
198 assert.equal(signedPercent(0.001), "0%");
199});
200
201test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
202 const since = NOW - 14 * DAY;
203 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
204 assert.ok(reachesBack([at(1), at(2)], since, 100));
205 assert.ok(reachesBack([at(1), at(20)], since, 2));
206 assert.ok(!reachesBack([at(1), at(3)], since, 2));
207});
208
209test("landed today is the viewer's calendar day, newest first", () => {
210 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
211 assert.deepEqual(
212 rows.map((r) => [r.ref, r.byAgents]),
213 [
214 ["#3", true],
215 ["#4", false],
216 ],
217 );
218 assert.equal(rows[1].by?.name, "syntaqx");
219});
220
221test("waiting on agents holds each pull request once, running ones first, and nothing that needs you", () => {
222 const pull = (number: number, minutesAgo: number) => ({
223 number,
224 title: `Pull ${number}`,
225 agent: "g1t-agent",
226 updatedAt: new Date(NOW - minutesAgo * 60_000).toISOString(),
227 checkStatus: null,
228 files: [],
229 });
230 const rows = waitingRows({
231 active: [
232 { pull: pull(1, 30), lifecycle: { stage: "checking", detail: "The acceptance checks are running.", revisions: 0 }, repo },
233 { pull: pull(2, 5), lifecycle: { stage: "needs_you", detail: "Stopped.", revisions: 0 }, repo },
234 { pull: pull(3, 50), lifecycle: { stage: "queued", detail: "In the merge queue.", revisions: 0 }, repo },
235 { pull: pull(9, 1), lifecycle: { stage: "reviewing", detail: "Reviewing.", revisions: 0 }, repo },
236 ],
237 live: [
238 {
239 id: "r1",
240 repo,
241 number: 3,
242 title: "Pull 3",
243 kind: "update",
244 agent: "g1t-agent",
245 step: "Merging main in",
246 costUsd: 0.1,
247 startedAt: new Date(NOW - 60_000).toISOString(),
248 createdAt: new Date(NOW - 60_000).toISOString(),
249 updatedAt: new Date(NOW - 10_000).toISOString(),
250 },
251 {
252 id: "r2",
253 repo,
254 number: null,
255 title: null,
256 kind: "plan",
257 agent: "g1t-agent",
258 step: null,
259 costUsd: null,
260 startedAt: null,
261 createdAt: new Date(NOW - 120_000).toISOString(),
262 updatedAt: new Date(NOW - 120_000).toISOString(),
263 },
264 ],
265 drafts: [
266 { ...pull(1, 30), repo },
267 { ...pull(4, 20), repo },
268 { ...pull(5, 20), agent: "syntaqx", repo },
269 ],
270 needKeys: new Set(["acme/web#9"]),
271 });
272 assert.deepEqual(
273 rows.map((r) => [r.ref, r.chip, r.live]),
274 [
275 ["#3", "In queue", true],
276 [null, "Planning", true],
277 ["#4", "Working", false],
278 ["#1", "Checking", false],
279 ],
280 );
281 assert.equal(rows[0].detail, "Merging main in");
282 assert.equal(rows[0].run, "/acme/web/agents/runs/r1");
283 assert.equal(rows[1].title, "Planning in web");
284});
285
286test("tabs and sorting come from the address", () => {
287 assert.equal(parseTab("waiting"), "waiting");
288 assert.equal(parseTab("nope"), null);
289 assert.equal(parseSort("newest"), "newest");
290 assert.equal(parseSort(null), "impact");
291 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
292 assert.deepEqual(sortRows(rows, "impact"), rows);
293 assert.deepEqual(
294 sortRows(rows, "newest").map((r) => r.at),
295 [3, 2, 1],
296 );
297});
298
299test("the summary says the week honestly", () => {
300 assert.equal(summaryLine({ total: 47, byAgents: 39, live: 2, needs: 8 }), "Agents landed 39 of 47 changes this week without you.");
301 assert.equal(summaryLine({ total: 3, byAgents: 3, live: 0, needs: 0 }), "Agents landed all 3 changes this week without you.");
302 assert.equal(summaryLine({ total: 2, byAgents: 0, live: 0, needs: 0 }), "2 changes landed this week, each merged by a person.");
303 assert.equal(summaryLine({ total: 0, byAgents: 0, live: 1, needs: 0 }), "1 agent is at work. Nothing has landed this week yet.");
304 assert.match(summaryLine({ total: 0, byAgents: 0, live: 0, needs: 0 }), /Assign an issue/);
305});