Skip to content

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

389 lines17,264 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,
Mission control: a busy week's earlier pushes count again6 placePushes,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms7 change,
Fast pages, required checks on the branch, self-hosted runners, honest incidents8 checksFact,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step9 confidenceAsk,
10 confidenceLine,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms11 dayKey,
12 isTestFile,
13 landedByAgents,
14 landedToday,
15 parseSort,
16 parseTab,
17 pullFacts,
18 reachesBack,
19 reasonFor,
20 signedPercent,
21 sortRows,
22 stallReason,
23 summaryLine,
Mission control counts people's direct pushes to the default branch24 pushedCommits,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms25 waitingRows,
26 weekOf,
27 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step28 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms29} from "./mission-control.ts";
30
31const HOUR = 3_600_000;
32const DAY = 24 * HOUR;
33// Monday 2026-10-05, 18:00 UTC.
34const NOW = Date.parse("2026-10-05T18:00:00Z");
35const repo = { namespace: "acme", name: "web" };
36
37test("each kind of need gets its reason chip", () => {
38 assert.equal(reasonFor({ kind: "deploy", detail: "" }), "blocking");
39 assert.equal(reasonFor({ kind: "limit", detail: "" }), "blocking");
40 assert.equal(reasonFor({ kind: "review", detail: "" }), "asked_for_you");
41 assert.equal(reasonFor({ kind: "invitation", detail: "" }), "asked_for_you");
42 assert.equal(reasonFor({ kind: "ready", detail: "" }), "ready_to_merge");
43 assert.equal(reasonFor({ kind: "checks", detail: "" }), "checks_failing");
44 assert.equal(reasonFor({ kind: "stuck", detail: "" }), "stalled");
45});
46
47test("a stopped pull request's reason comes from the sentence g1t stopped with", () => {
Fast pages, required checks on the branch, self-hosted runners, honest incidents48 assert.equal(stallReason("The required check CI still fails after the agent revised 3 times."), "checks_failing");
49 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 200ms50 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 incidents51 assert.equal(stallReason("It failed in the merge queue after the agent revised twice."), "checks_failing");
52 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 200ms53 assert.equal(
54 stallReason("g1t stopped the agent's implement run when it reached its cost cap. Raise the cap under Settings, Guardrails."),
55 "outside_guardrails",
56 );
57 assert.equal(stallReason("g1t stopped the agent's review run when it reached its time cap."), "outside_guardrails");
58 assert.equal(stallReason("g1t could not merge this: the branch moved"), "blocking");
59 assert.equal(stallReason("Needs 1 approving review from someone with write access."), "needs_review");
60 assert.equal(stallReason("The review still asks for changes after the agent revised 3 times."), "needs_review");
61 assert.equal(stallReason("alex asked for changes, and the agent has already revised 3 times."), "needs_review");
62 assert.equal(stallReason("syntaqx stopped the agent's implement run."), "stalled");
63 assert.equal(stallReason("g1t could not start the next step: no slot"), "stalled");
64});
65
66test("why a need needs a person is said for every reason", () => {
67 for (const kind of ["deploy", "limit", "invitation", "review", "stuck", "checks", "ready", "conflict"] as const) {
68 const need = { kind, detail: "" };
69 assert.ok(whyFor(reasonFor(need), need).length > 20, kind);
70 }
71 const stalled = { kind: "stalled" as const, detail: "It reached its cost cap." };
72 assert.match(whyFor(reasonFor(stalled), stalled), /Guardrails/);
73});
74
75test("what the agent knows lists only what is known", () => {
76 const facts = pullFacts({
77 checkStatus: "failed",
78 files: [
79 { path: "src/retry.ts", additions: 40, deletions: 2 },
80 { path: "src/retry.test.ts", additions: 60, deletions: 0 },
81 ],
82 lifecycle: { revisions: 2 },
83 runs: [
84 { kind: "implement", costUsd: 0.5 },
85 { kind: "revise", costUsd: 0.25 },
86 { kind: "checks", costUsd: null },
87 ],
88 });
89 const by = Object.fromEntries(facts.map((f) => [f.label, f]));
Fast pages, required checks on the branch, self-hosted runners, honest incidents90 assert.equal(by["Required checks"].value, "Failed in the merge queue");
91 assert.equal(by["Required checks"].tone, "bad");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms92 assert.equal(by["Files changed"].value, "2");
93 assert.equal(by.Lines.value, "+100 −2");
94 assert.equal(by.Tests.value, "1 file");
95 assert.equal(by["Sent back"].value, "2 times");
96 assert.equal(by["Agent runs"].value, "2 · $0.75");
97
98 const bare = pullFacts({ checkStatus: null, files: [] });
Fast pages, required checks on the branch, self-hosted runners, honest incidents99 assert.deepEqual(bare, [{ label: "Required checks", value: "Not known yet", tone: null }]);
100});
101
102test("the required checks are read from where the agent's pull request stands", () => {
103 const at = (stage: Parameters<typeof checksFact>[1] extends infer L ? NonNullable<L>["stage"] : never, detail: string) =>
104 checksFact(null, { stage, detail });
105 assert.deepEqual(at("checking", "Waiting for CI / pull_request to finish."), { label: "Required checks", value: "Running", tone: null });
106 assert.equal(at("checking", "Waiting for the required check Deploy to report on its latest commit.").value, "Not reported yet");
107 assert.equal(at("revising", "CI / pull_request failed. The agent is being sent back to fix it.").value, "Failing");
108 assert.equal(at("needs_you", "The required check CI still fails after the agent revised twice.").value, "Failing");
109 // Past the checks: a review, an approval or a person's decision holds it, not them.
110 assert.equal(at("revising", "The review asked for changes. The agent is being sent back to make them.").value, "Passing");
111 assert.equal(at("needs_you", "This repository requires 1 approving review before a pull request merges; this one has 0.").value, "Passing");
112 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-agent113 assert.equal(at("working", "g1t is making the change.").value, "Not run yet");
Fast pages, required checks on the branch, self-hosted runners, honest incidents114 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 200ms115});
116
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step117test("a change held for low confidence is its own reason, ahead of what it would read as", () => {
118 const held =
119 "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.";
120 // Its reasons name checks and approval, but it is held for its confidence.
121 assert.equal(stallReason(held), "low_confidence");
122 assert.equal(reasonFor({ kind: "stalled", detail: held }), "low_confidence");
123
124 const low = { level: "low" as const, reasons: ["tests not added", "3 revisions"], uncertainAbout: ["the retry limit"] };
125 assert.equal(withConfidence("ready_to_merge", low), "low_confidence");
126 assert.equal(withConfidence("needs_review", low), "low_confidence");
127 // A failure that needs someone anyway keeps its own chip.
128 assert.equal(withConfidence("checks_failing", low), "checks_failing");
129 assert.equal(withConfidence("ready_to_merge", { level: "medium" }), "ready_to_merge");
130 assert.equal(withConfidence("ready_to_merge", null), "ready_to_merge");
131
132 assert.equal(confidenceLine(low), "Low — tests not added, 3 revisions");
133 assert.equal(confidenceLine({ level: "high", reasons: [] }), "High");
134 assert.match(confidenceAsk(low), /not sure of the change: tests not added, 3 revisions\. Approve it/);
135
136 const why = whyFor("low_confidence", { kind: "stalled", detail: held }, low);
137 assert.match(why, /from tests not added, 3 revisions\./);
138 assert.match(why, /unsure about the retry limit/);
139 assert.match(why, /nothing merges it until you approve it/);
140 // Ready, not held: it says to look before merging.
141 assert.match(whyFor("low_confidence", { kind: "ready", detail: "" }, low), /Look at it before you merge it/);
142});
143
144test("what the agent knows gains its confidence, across the row", () => {
145 const facts = pullFacts({
146 checkStatus: "passed",
147 files: [{ path: "src/retry.ts", additions: 40, deletions: 2 }],
148 lifecycle: { revisions: 3 },
149 confidence: { level: "low", reasons: ["tests not added", "3 revisions"] },
150 });
151 const fact = facts.find((f) => f.label === "Confidence");
152 assert.deepEqual(fact, { label: "Confidence", value: "Low — tests not added, 3 revisions", tone: "bad", wide: true });
153 assert.equal(facts.at(-1)?.label, "Confidence");
154});
155
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms156test("test files are recognised by the names test runners use", () => {
157 assert.ok(isTestFile("src/a.test.ts"));
158 assert.ok(isTestFile("src/__tests__/a.ts"));
159 assert.ok(isTestFile("tests/api.rs"));
160 assert.ok(isTestFile("pkg/store_test.go"));
161 assert.ok(!isTestFile("src/testing-utils.ts"));
162 assert.ok(!isTestFile("src/contest.ts"));
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were163 assert.ok(isTestFile("test.js"));
164 assert.ok(isTestFile("lib/spec.rb"));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms165});
166
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar167const 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 200ms168 repo,
169 number,
170 title: `Change ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent171 agent: "g1t",
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar172 authoredByAgent,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms173 mergedBy,
174 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
175 files: [],
176});
177
Mission control counts people's direct pushes to the default branch178test("a push to the default branch counts a person's own commits, not merges or agents'", () => {
179 const c = (hash: string, author: string, parents = 1) => ({ hash, author: { name: author }, parents: Array(parents).fill("p") });
180 const history = [c("e", "Chase"), c("d", "g1t"), c("m", "g1t", 2), c("b", "Chase"), c("a", "Chase")];
181 assert.deepEqual(pushedCommits(history, "a").map((x) => x.hash), ["e", "b"]);
182 // Where it pointed before was not read: everything read counts.
183 assert.deepEqual(pushedCommits(history, "zz").map((x) => x.hash), ["e", "b", "a"]);
184});
185
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms186test("a change landed without a person when g1t merged it", () => {
187 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-agent188 assert.ok(landedByAgents({ mergedBy: "g1t" }));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms189 assert.ok(landedByAgents({ mergedBy: null }));
190 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
191});
192
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar193test("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 200ms194 const week = weekOf(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar195 [
196 merged(0, "g1t"),
197 merged(0, "syntaqx"),
198 // A person's own change, merged by them and auto-merged by g1t: both theirs.
199 merged(0, "syntaqx", 2, false),
200 merged(1, "g1t", 3, false),
201 merged(1, "g1t"),
202 merged(6, "g1t"),
203 merged(8, "g1t"),
204 merged(10, "alex"),
205 merged(20, "g1t"),
206 ],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms207 NOW,
208 "UTC",
209 );
210 assert.equal(week.days.length, 7);
211 assert.equal(week.days[6].key, "2026-10-05");
212 assert.equal(week.days[6].label, "Mon");
213 assert.deepEqual(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar214 week.days.map((d) => [d.agents, d.assisted, d.people]),
215 [[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 200ms216 );
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar217 assert.equal(week.total, 6);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms218 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 bar219 assert.equal(week.agentChanges, 4);
220 assert.equal(week.people, 2);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms221 assert.equal(week.previous, 2);
222 // Not knowing the week before is not the same as nothing in it.
223 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
224});
225
226test("days follow the viewer's time zone", () => {
227 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
228 const late = Date.parse("2026-10-05T02:00:00Z");
229 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
230 assert.equal(dayKey(late, "UTC"), "2026-10-05");
231 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 bar232 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 200ms233 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
234});
235
236test("the change from last week, and how it reads", () => {
237 assert.equal(change(12, 10), 0.2);
238 assert.equal(change(5, 0), null);
239 assert.equal(change(5, null), null);
240 assert.equal(signedPercent(0.2), "+20%");
241 assert.equal(signedPercent(-0.054), "−5%");
242 assert.equal(signedPercent(0.001), "0%");
243});
244
245test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
246 const since = NOW - 14 * DAY;
247 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
248 assert.ok(reachesBack([at(1), at(2)], since, 100));
249 assert.ok(reachesBack([at(1), at(20)], since, 2));
250 assert.ok(!reachesBack([at(1), at(3)], since, 2));
251});
252
253test("landed today is the viewer's calendar day, newest first", () => {
254 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
255 assert.deepEqual(
256 rows.map((r) => [r.ref, r.byAgents]),
257 [
258 ["#3", true],
259 ["#4", false],
260 ],
261 );
262 assert.equal(rows[1].by?.name, "syntaqx");
263});
264
265test("waiting on agents holds each pull request once, running ones first, and nothing that needs you", () => {
266 const pull = (number: number, minutesAgo: number) => ({
267 number,
268 title: `Pull ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent269 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms270 updatedAt: new Date(NOW - minutesAgo * 60_000).toISOString(),
271 checkStatus: null,
272 files: [],
273 });
274 const rows = waitingRows({
275 active: [
Fast pages, required checks on the branch, self-hosted runners, honest incidents276 { 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 200ms277 { pull: pull(2, 5), lifecycle: { stage: "needs_you", detail: "Stopped.", revisions: 0 }, repo },
278 { pull: pull(3, 50), lifecycle: { stage: "queued", detail: "In the merge queue.", revisions: 0 }, repo },
279 { pull: pull(9, 1), lifecycle: { stage: "reviewing", detail: "Reviewing.", revisions: 0 }, repo },
280 ],
281 live: [
282 {
283 id: "r1",
284 repo,
285 number: 3,
286 title: "Pull 3",
287 kind: "update",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent288 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms289 step: "Merging main in",
290 costUsd: 0.1,
291 startedAt: new Date(NOW - 60_000).toISOString(),
292 createdAt: new Date(NOW - 60_000).toISOString(),
293 updatedAt: new Date(NOW - 10_000).toISOString(),
294 },
295 {
296 id: "r2",
297 repo,
298 number: null,
299 title: null,
300 kind: "plan",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent301 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms302 step: null,
303 costUsd: null,
304 startedAt: null,
305 createdAt: new Date(NOW - 120_000).toISOString(),
306 updatedAt: new Date(NOW - 120_000).toISOString(),
307 },
308 ],
309 drafts: [
310 { ...pull(1, 30), repo },
311 { ...pull(4, 20), repo },
312 { ...pull(5, 20), agent: "syntaqx", repo },
313 ],
314 needKeys: new Set(["acme/web#9"]),
315 });
316 assert.deepEqual(
317 rows.map((r) => [r.ref, r.chip, r.live]),
318 [
319 ["#3", "In queue", true],
320 [null, "Planning", true],
321 ["#4", "Working", false],
322 ["#1", "Checking", false],
323 ],
324 );
325 assert.equal(rows[0].detail, "Merging main in");
326 assert.equal(rows[0].run, "/acme/web/agents/runs/r1");
327 assert.equal(rows[1].title, "Planning in web");
328});
329
330test("tabs and sorting come from the address", () => {
331 assert.equal(parseTab("waiting"), "waiting");
332 assert.equal(parseTab("nope"), null);
333 assert.equal(parseSort("newest"), "newest");
334 assert.equal(parseSort(null), "impact");
335 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
336 assert.deepEqual(sortRows(rows, "impact"), rows);
337 assert.deepEqual(
338 sortRows(rows, "newest").map((r) => r.at),
339 [3, 2, 1],
340 );
341});
342
343test("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 bar344 const none = { total: 0, byAgents: 0, agentChanges: 0, people: 0, live: 0, needs: 0 };
345 assert.equal(
346 summaryLine({ ...none, total: 47, byAgents: 39, agentChanges: 47 }),
347 "Agents landed 39 of their 47 changes this week without you.",
348 );
349 assert.equal(
350 summaryLine({ ...none, total: 3, byAgents: 3, agentChanges: 3 }),
351 "Agents landed all 3 changes of theirs this week without you.",
352 );
353 assert.equal(
354 summaryLine({ ...none, total: 2, agentChanges: 2 }),
355 "Agents made 2 changes this week, each merged by a person.",
356 );
357 // People's own work is counted as theirs, never as agents' that needed help.
358 assert.equal(
359 summaryLine({ ...none, total: 7, byAgents: 4, agentChanges: 5, people: 2 }),
360 "Agents landed 4 of their 5 changes this week without you, and people landed 2 changes of their own.",
361 );
362 assert.equal(summaryLine({ ...none, total: 3, people: 3 }), "People landed 3 changes this week; none were agents'.");
363 assert.equal(summaryLine({ ...none, live: 1 }), "1 agent is at work. Nothing has landed this week yet.");
364 assert.match(summaryLine(none), /Assign an issue/);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms365});
Usage, Billing settings and prepaid AI credit; fixes from the UX audit366
367test("a job waiting for a self-hosted runner says so, not that an agent went quiet", () => {
368 const runner = { kind: "runner" as const, detail: "No runner with labels self-hosted, docker is online." };
369 assert.equal(reasonFor(runner), "stalled");
370 const why = whyFor(reasonFor(runner), runner);
371 assert.match(why, /self-hosted runner/);
372 assert.doesNotMatch(why, /agent/);
373});
Mission control: a busy week's earlier pushes count again374
375test("each push to the default branch places the commits it brought, at its time, from one read of the history", () => {
376 const c = (hash: string, parents = ["p"], author = "Chase Pierce") => ({ hash, parents, author: { name: author } });
377 // Newest first: a Wednesday push of two, a Monday push of one, a merge, and g1t's own commit.
378 const history = [c("w2"), c("w1"), c("m1"), c("merge", ["a", "b"]), c("bot", ["p"], "g1t"), c("old")];
379 const pushes = [
380 { time: "2026-10-07T12:00:00Z", data: { after: "w2", before: "m1" } },
381 { time: "2026-10-05T12:00:00Z", data: { after: "m1", before: "old" } },
382 { time: "2026-10-01T12:00:00Z", data: { after: "gone", before: "older" } },
383 ];
384 assert.deepEqual(placePushes(history, pushes), [
385 { hash: "m1", at: "2026-10-05T12:00:00Z" },
386 { hash: "w2", at: "2026-10-07T12:00:00Z" },
387 { hash: "w1", at: "2026-10-07T12:00:00Z" },
388 ]);
389});