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

362 lines15,887 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,
Fast pages, required checks on the branch, self-hosted runners, honest incidents7 checksFact,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step8 confidenceAsk,
9 confidenceLine,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms10 dayKey,
11 isTestFile,
12 landedByAgents,
13 landedToday,
14 parseSort,
15 parseTab,
16 pullFacts,
17 reachesBack,
18 reasonFor,
19 signedPercent,
20 sortRows,
21 stallReason,
22 summaryLine,
Mission control counts people's direct pushes to the default branch23 pushedCommits,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms24 waitingRows,
25 weekOf,
26 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step27 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms28} from "./mission-control.ts";
29
30const HOUR = 3_600_000;
31const DAY = 24 * HOUR;
32// Monday 2026-10-05, 18:00 UTC.
33const NOW = Date.parse("2026-10-05T18:00:00Z");
34const repo = { namespace: "acme", name: "web" };
35
36test("each kind of need gets its reason chip", () => {
37 assert.equal(reasonFor({ kind: "deploy", detail: "" }), "blocking");
38 assert.equal(reasonFor({ kind: "limit", detail: "" }), "blocking");
39 assert.equal(reasonFor({ kind: "review", detail: "" }), "asked_for_you");
40 assert.equal(reasonFor({ kind: "invitation", detail: "" }), "asked_for_you");
41 assert.equal(reasonFor({ kind: "ready", detail: "" }), "ready_to_merge");
42 assert.equal(reasonFor({ kind: "checks", detail: "" }), "checks_failing");
43 assert.equal(reasonFor({ kind: "stuck", detail: "" }), "stalled");
44});
45
46test("a stopped pull request's reason comes from the sentence g1t stopped with", () => {
Fast pages, required checks on the branch, self-hosted runners, honest incidents47 assert.equal(stallReason("The required check CI still fails after the agent revised 3 times."), "checks_failing");
48 assert.equal(stallReason("The required checks CI and Lint still fail after the agent revised twice."), "checks_failing");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms49 assert.equal(stallReason("CI / push still fails after the agent revised 2 times."), "checks_failing");
Fast pages, required checks on the branch, self-hosted runners, honest incidents50 assert.equal(stallReason("It failed in the merge queue after the agent revised twice."), "checks_failing");
51 assert.equal(stallReason("Its checks could not be run."), "checks_failing");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms52 assert.equal(
53 stallReason("g1t stopped the agent's implement run when it reached its cost cap. Raise the cap under Settings, Guardrails."),
54 "outside_guardrails",
55 );
56 assert.equal(stallReason("g1t stopped the agent's review run when it reached its time cap."), "outside_guardrails");
57 assert.equal(stallReason("g1t could not merge this: the branch moved"), "blocking");
58 assert.equal(stallReason("Needs 1 approving review from someone with write access."), "needs_review");
59 assert.equal(stallReason("The review still asks for changes after the agent revised 3 times."), "needs_review");
60 assert.equal(stallReason("alex asked for changes, and the agent has already revised 3 times."), "needs_review");
61 assert.equal(stallReason("syntaqx stopped the agent's implement run."), "stalled");
62 assert.equal(stallReason("g1t could not start the next step: no slot"), "stalled");
63});
64
65test("why a need needs a person is said for every reason", () => {
66 for (const kind of ["deploy", "limit", "invitation", "review", "stuck", "checks", "ready", "conflict"] as const) {
67 const need = { kind, detail: "" };
68 assert.ok(whyFor(reasonFor(need), need).length > 20, kind);
69 }
70 const stalled = { kind: "stalled" as const, detail: "It reached its cost cap." };
71 assert.match(whyFor(reasonFor(stalled), stalled), /Guardrails/);
72});
73
74test("what the agent knows lists only what is known", () => {
75 const facts = pullFacts({
76 checkStatus: "failed",
77 files: [
78 { path: "src/retry.ts", additions: 40, deletions: 2 },
79 { path: "src/retry.test.ts", additions: 60, deletions: 0 },
80 ],
81 lifecycle: { revisions: 2 },
82 runs: [
83 { kind: "implement", costUsd: 0.5 },
84 { kind: "revise", costUsd: 0.25 },
85 { kind: "checks", costUsd: null },
86 ],
87 });
88 const by = Object.fromEntries(facts.map((f) => [f.label, f]));
Fast pages, required checks on the branch, self-hosted runners, honest incidents89 assert.equal(by["Required checks"].value, "Failed in the merge queue");
90 assert.equal(by["Required checks"].tone, "bad");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms91 assert.equal(by["Files changed"].value, "2");
92 assert.equal(by.Lines.value, "+100 −2");
93 assert.equal(by.Tests.value, "1 file");
94 assert.equal(by["Sent back"].value, "2 times");
95 assert.equal(by["Agent runs"].value, "2 · $0.75");
96
97 const bare = pullFacts({ checkStatus: null, files: [] });
Fast pages, required checks on the branch, self-hosted runners, honest incidents98 assert.deepEqual(bare, [{ label: "Required checks", value: "Not known yet", tone: null }]);
99});
100
101test("the required checks are read from where the agent's pull request stands", () => {
102 const at = (stage: Parameters<typeof checksFact>[1] extends infer L ? NonNullable<L>["stage"] : never, detail: string) =>
103 checksFact(null, { stage, detail });
104 assert.deepEqual(at("checking", "Waiting for CI / pull_request to finish."), { label: "Required checks", value: "Running", tone: null });
105 assert.equal(at("checking", "Waiting for the required check Deploy to report on its latest commit.").value, "Not reported yet");
106 assert.equal(at("revising", "CI / pull_request failed. The agent is being sent back to fix it.").value, "Failing");
107 assert.equal(at("needs_you", "The required check CI still fails after the agent revised twice.").value, "Failing");
108 // Past the checks: a review, an approval or a person's decision holds it, not them.
109 assert.equal(at("revising", "The review asked for changes. The agent is being sent back to make them.").value, "Passing");
110 assert.equal(at("needs_you", "This repository requires 1 approving review before a pull request merges; this one has 0.").value, "Passing");
111 assert.equal(at("ready", "Everything this repository asks for is met. Ready to merge.").tone, "good");
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent112 assert.equal(at("working", "g1t is making the change.").value, "Not run yet");
Fast pages, required checks on the branch, self-hosted runners, honest incidents113 assert.equal(checksFact("failed", { stage: "revising", detail: "It failed in the merge queue." }).value, "Failed in the merge queue");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms114});
115
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step116test("a change held for low confidence is its own reason, ahead of what it would read as", () => {
117 const held =
118 "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.";
119 // Its reasons name checks and approval, but it is held for its confidence.
120 assert.equal(stallReason(held), "low_confidence");
121 assert.equal(reasonFor({ kind: "stalled", detail: held }), "low_confidence");
122
123 const low = { level: "low" as const, reasons: ["tests not added", "3 revisions"], uncertainAbout: ["the retry limit"] };
124 assert.equal(withConfidence("ready_to_merge", low), "low_confidence");
125 assert.equal(withConfidence("needs_review", low), "low_confidence");
126 // A failure that needs someone anyway keeps its own chip.
127 assert.equal(withConfidence("checks_failing", low), "checks_failing");
128 assert.equal(withConfidence("ready_to_merge", { level: "medium" }), "ready_to_merge");
129 assert.equal(withConfidence("ready_to_merge", null), "ready_to_merge");
130
131 assert.equal(confidenceLine(low), "Low — tests not added, 3 revisions");
132 assert.equal(confidenceLine({ level: "high", reasons: [] }), "High");
133 assert.match(confidenceAsk(low), /not sure of the change: tests not added, 3 revisions\. Approve it/);
134
135 const why = whyFor("low_confidence", { kind: "stalled", detail: held }, low);
136 assert.match(why, /from tests not added, 3 revisions\./);
137 assert.match(why, /unsure about the retry limit/);
138 assert.match(why, /nothing merges it until you approve it/);
139 // Ready, not held: it says to look before merging.
140 assert.match(whyFor("low_confidence", { kind: "ready", detail: "" }, low), /Look at it before you merge it/);
141});
142
143test("what the agent knows gains its confidence, across the row", () => {
144 const facts = pullFacts({
145 checkStatus: "passed",
146 files: [{ path: "src/retry.ts", additions: 40, deletions: 2 }],
147 lifecycle: { revisions: 3 },
148 confidence: { level: "low", reasons: ["tests not added", "3 revisions"] },
149 });
150 const fact = facts.find((f) => f.label === "Confidence");
151 assert.deepEqual(fact, { label: "Confidence", value: "Low — tests not added, 3 revisions", tone: "bad", wide: true });
152 assert.equal(facts.at(-1)?.label, "Confidence");
153});
154
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms155test("test files are recognised by the names test runners use", () => {
156 assert.ok(isTestFile("src/a.test.ts"));
157 assert.ok(isTestFile("src/__tests__/a.ts"));
158 assert.ok(isTestFile("tests/api.rs"));
159 assert.ok(isTestFile("pkg/store_test.go"));
160 assert.ok(!isTestFile("src/testing-utils.ts"));
161 assert.ok(!isTestFile("src/contest.ts"));
162});
163
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar164const merged = (daysAgo: number, mergedBy: string | null, number = 1, authoredByAgent = true): Merged => ({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms165 repo,
166 number,
167 title: `Change ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent168 agent: "g1t",
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar169 authoredByAgent,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms170 mergedBy,
171 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
172 files: [],
173});
174
Mission control counts people's direct pushes to the default branch175test("a push to the default branch counts a person's own commits, not merges or agents'", () => {
176 const c = (hash: string, author: string, parents = 1) => ({ hash, author: { name: author }, parents: Array(parents).fill("p") });
177 const history = [c("e", "Chase"), c("d", "g1t"), c("m", "g1t", 2), c("b", "Chase"), c("a", "Chase")];
178 assert.deepEqual(pushedCommits(history, "a").map((x) => x.hash), ["e", "b"]);
179 // Where it pointed before was not read: everything read counts.
180 assert.deepEqual(pushedCommits(history, "zz").map((x) => x.hash), ["e", "b", "a"]);
181});
182
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms183test("a change landed without a person when g1t merged it", () => {
184 assert.ok(landedByAgents({ mergedBy: "g1t" }));
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent185 assert.ok(landedByAgents({ mergedBy: "g1t" }));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms186 assert.ok(landedByAgents({ mergedBy: null }));
187 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
188});
189
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar190test("the week is seven days, each split by who did the work, with the week before", () => {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms191 const week = weekOf(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar192 [
193 merged(0, "g1t"),
194 merged(0, "syntaqx"),
195 // A person's own change, merged by them and auto-merged by g1t: both theirs.
196 merged(0, "syntaqx", 2, false),
197 merged(1, "g1t", 3, false),
198 merged(1, "g1t"),
199 merged(6, "g1t"),
200 merged(8, "g1t"),
201 merged(10, "alex"),
202 merged(20, "g1t"),
203 ],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms204 NOW,
205 "UTC",
206 );
207 assert.equal(week.days.length, 7);
208 assert.equal(week.days[6].key, "2026-10-05");
209 assert.equal(week.days[6].label, "Mon");
210 assert.deepEqual(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar211 week.days.map((d) => [d.agents, d.assisted, d.people]),
212 [[1, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 0, 1], [1, 1, 1]],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms213 );
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar214 assert.equal(week.total, 6);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms215 assert.equal(week.byAgents, 3);
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar216 assert.equal(week.agentChanges, 4);
217 assert.equal(week.people, 2);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms218 assert.equal(week.previous, 2);
219 // Not knowing the week before is not the same as nothing in it.
220 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
221});
222
223test("days follow the viewer's time zone", () => {
224 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
225 const late = Date.parse("2026-10-05T02:00:00Z");
226 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
227 assert.equal(dayKey(late, "UTC"), "2026-10-05");
228 assert.equal(dayKey(late, "Not/AZone"), "2026-10-05");
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar229 const week = weekOf([{ mergedAt: new Date(late).toISOString(), mergedBy: "g1t", authoredByAgent: true }], NOW, "America/Los_Angeles");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms230 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
231});
232
233test("the change from last week, and how it reads", () => {
234 assert.equal(change(12, 10), 0.2);
235 assert.equal(change(5, 0), null);
236 assert.equal(change(5, null), null);
237 assert.equal(signedPercent(0.2), "+20%");
238 assert.equal(signedPercent(-0.054), "−5%");
239 assert.equal(signedPercent(0.001), "0%");
240});
241
242test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
243 const since = NOW - 14 * DAY;
244 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
245 assert.ok(reachesBack([at(1), at(2)], since, 100));
246 assert.ok(reachesBack([at(1), at(20)], since, 2));
247 assert.ok(!reachesBack([at(1), at(3)], since, 2));
248});
249
250test("landed today is the viewer's calendar day, newest first", () => {
251 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
252 assert.deepEqual(
253 rows.map((r) => [r.ref, r.byAgents]),
254 [
255 ["#3", true],
256 ["#4", false],
257 ],
258 );
259 assert.equal(rows[1].by?.name, "syntaqx");
260});
261
262test("waiting on agents holds each pull request once, running ones first, and nothing that needs you", () => {
263 const pull = (number: number, minutesAgo: number) => ({
264 number,
265 title: `Pull ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent266 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms267 updatedAt: new Date(NOW - minutesAgo * 60_000).toISOString(),
268 checkStatus: null,
269 files: [],
270 });
271 const rows = waitingRows({
272 active: [
Fast pages, required checks on the branch, self-hosted runners, honest incidents273 { pull: pull(1, 30), lifecycle: { stage: "checking", detail: "Waiting for CI / pull_request to finish.", revisions: 0 }, repo },
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms274 { pull: pull(2, 5), lifecycle: { stage: "needs_you", detail: "Stopped.", revisions: 0 }, repo },
275 { pull: pull(3, 50), lifecycle: { stage: "queued", detail: "In the merge queue.", revisions: 0 }, repo },
276 { pull: pull(9, 1), lifecycle: { stage: "reviewing", detail: "Reviewing.", revisions: 0 }, repo },
277 ],
278 live: [
279 {
280 id: "r1",
281 repo,
282 number: 3,
283 title: "Pull 3",
284 kind: "update",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent285 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms286 step: "Merging main in",
287 costUsd: 0.1,
288 startedAt: new Date(NOW - 60_000).toISOString(),
289 createdAt: new Date(NOW - 60_000).toISOString(),
290 updatedAt: new Date(NOW - 10_000).toISOString(),
291 },
292 {
293 id: "r2",
294 repo,
295 number: null,
296 title: null,
297 kind: "plan",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent298 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms299 step: null,
300 costUsd: null,
301 startedAt: null,
302 createdAt: new Date(NOW - 120_000).toISOString(),
303 updatedAt: new Date(NOW - 120_000).toISOString(),
304 },
305 ],
306 drafts: [
307 { ...pull(1, 30), repo },
308 { ...pull(4, 20), repo },
309 { ...pull(5, 20), agent: "syntaqx", repo },
310 ],
311 needKeys: new Set(["acme/web#9"]),
312 });
313 assert.deepEqual(
314 rows.map((r) => [r.ref, r.chip, r.live]),
315 [
316 ["#3", "In queue", true],
317 [null, "Planning", true],
318 ["#4", "Working", false],
319 ["#1", "Checking", false],
320 ],
321 );
322 assert.equal(rows[0].detail, "Merging main in");
323 assert.equal(rows[0].run, "/acme/web/agents/runs/r1");
324 assert.equal(rows[1].title, "Planning in web");
325});
326
327test("tabs and sorting come from the address", () => {
328 assert.equal(parseTab("waiting"), "waiting");
329 assert.equal(parseTab("nope"), null);
330 assert.equal(parseSort("newest"), "newest");
331 assert.equal(parseSort(null), "impact");
332 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
333 assert.deepEqual(sortRows(rows, "impact"), rows);
334 assert.deepEqual(
335 sortRows(rows, "newest").map((r) => r.at),
336 [3, 2, 1],
337 );
338});
339
340test("the summary says the week honestly", () => {
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar341 const none = { total: 0, byAgents: 0, agentChanges: 0, people: 0, live: 0, needs: 0 };
342 assert.equal(
343 summaryLine({ ...none, total: 47, byAgents: 39, agentChanges: 47 }),
344 "Agents landed 39 of their 47 changes this week without you.",
345 );
346 assert.equal(
347 summaryLine({ ...none, total: 3, byAgents: 3, agentChanges: 3 }),
348 "Agents landed all 3 changes of theirs this week without you.",
349 );
350 assert.equal(
351 summaryLine({ ...none, total: 2, agentChanges: 2 }),
352 "Agents made 2 changes this week, each merged by a person.",
353 );
354 // People's own work is counted as theirs, never as agents' that needed help.
355 assert.equal(
356 summaryLine({ ...none, total: 7, byAgents: 4, agentChanges: 5, people: 2 }),
357 "Agents landed 4 of their 5 changes this week without you, and people landed 2 changes of their own.",
358 );
359 assert.equal(summaryLine({ ...none, total: 3, people: 3 }), "People landed 3 changes this week; none were agents'.");
360 assert.equal(summaryLine({ ...none, live: 1 }), "1 agent is at work. Nothing has landed this week yet.");
361 assert.match(summaryLine(none), /Assign an issue/);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms362});