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

364 lines15,963 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"));
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were162 assert.ok(isTestFile("test.js"));
163 assert.ok(isTestFile("lib/spec.rb"));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms164});
165
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar166const 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 200ms167 repo,
168 number,
169 title: `Change ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent170 agent: "g1t",
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar171 authoredByAgent,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms172 mergedBy,
173 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
174 files: [],
175});
176
Mission control counts people's direct pushes to the default branch177test("a push to the default branch counts a person's own commits, not merges or agents'", () => {
178 const c = (hash: string, author: string, parents = 1) => ({ hash, author: { name: author }, parents: Array(parents).fill("p") });
179 const history = [c("e", "Chase"), c("d", "g1t"), c("m", "g1t", 2), c("b", "Chase"), c("a", "Chase")];
180 assert.deepEqual(pushedCommits(history, "a").map((x) => x.hash), ["e", "b"]);
181 // Where it pointed before was not read: everything read counts.
182 assert.deepEqual(pushedCommits(history, "zz").map((x) => x.hash), ["e", "b", "a"]);
183});
184
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms185test("a change landed without a person when g1t merged it", () => {
186 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-agent187 assert.ok(landedByAgents({ mergedBy: "g1t" }));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms188 assert.ok(landedByAgents({ mergedBy: null }));
189 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
190});
191
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar192test("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 200ms193 const week = weekOf(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar194 [
195 merged(0, "g1t"),
196 merged(0, "syntaqx"),
197 // A person's own change, merged by them and auto-merged by g1t: both theirs.
198 merged(0, "syntaqx", 2, false),
199 merged(1, "g1t", 3, false),
200 merged(1, "g1t"),
201 merged(6, "g1t"),
202 merged(8, "g1t"),
203 merged(10, "alex"),
204 merged(20, "g1t"),
205 ],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms206 NOW,
207 "UTC",
208 );
209 assert.equal(week.days.length, 7);
210 assert.equal(week.days[6].key, "2026-10-05");
211 assert.equal(week.days[6].label, "Mon");
212 assert.deepEqual(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar213 week.days.map((d) => [d.agents, d.assisted, d.people]),
214 [[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 200ms215 );
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar216 assert.equal(week.total, 6);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms217 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 bar218 assert.equal(week.agentChanges, 4);
219 assert.equal(week.people, 2);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms220 assert.equal(week.previous, 2);
221 // Not knowing the week before is not the same as nothing in it.
222 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
223});
224
225test("days follow the viewer's time zone", () => {
226 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
227 const late = Date.parse("2026-10-05T02:00:00Z");
228 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
229 assert.equal(dayKey(late, "UTC"), "2026-10-05");
230 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 bar231 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 200ms232 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
233});
234
235test("the change from last week, and how it reads", () => {
236 assert.equal(change(12, 10), 0.2);
237 assert.equal(change(5, 0), null);
238 assert.equal(change(5, null), null);
239 assert.equal(signedPercent(0.2), "+20%");
240 assert.equal(signedPercent(-0.054), "−5%");
241 assert.equal(signedPercent(0.001), "0%");
242});
243
244test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
245 const since = NOW - 14 * DAY;
246 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
247 assert.ok(reachesBack([at(1), at(2)], since, 100));
248 assert.ok(reachesBack([at(1), at(20)], since, 2));
249 assert.ok(!reachesBack([at(1), at(3)], since, 2));
250});
251
252test("landed today is the viewer's calendar day, newest first", () => {
253 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
254 assert.deepEqual(
255 rows.map((r) => [r.ref, r.byAgents]),
256 [
257 ["#3", true],
258 ["#4", false],
259 ],
260 );
261 assert.equal(rows[1].by?.name, "syntaqx");
262});
263
264test("waiting on agents holds each pull request once, running ones first, and nothing that needs you", () => {
265 const pull = (number: number, minutesAgo: number) => ({
266 number,
267 title: `Pull ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent268 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms269 updatedAt: new Date(NOW - minutesAgo * 60_000).toISOString(),
270 checkStatus: null,
271 files: [],
272 });
273 const rows = waitingRows({
274 active: [
Fast pages, required checks on the branch, self-hosted runners, honest incidents275 { 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 200ms276 { pull: pull(2, 5), lifecycle: { stage: "needs_you", detail: "Stopped.", revisions: 0 }, repo },
277 { pull: pull(3, 50), lifecycle: { stage: "queued", detail: "In the merge queue.", revisions: 0 }, repo },
278 { pull: pull(9, 1), lifecycle: { stage: "reviewing", detail: "Reviewing.", revisions: 0 }, repo },
279 ],
280 live: [
281 {
282 id: "r1",
283 repo,
284 number: 3,
285 title: "Pull 3",
286 kind: "update",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent287 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms288 step: "Merging main in",
289 costUsd: 0.1,
290 startedAt: new Date(NOW - 60_000).toISOString(),
291 createdAt: new Date(NOW - 60_000).toISOString(),
292 updatedAt: new Date(NOW - 10_000).toISOString(),
293 },
294 {
295 id: "r2",
296 repo,
297 number: null,
298 title: null,
299 kind: "plan",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent300 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms301 step: null,
302 costUsd: null,
303 startedAt: null,
304 createdAt: new Date(NOW - 120_000).toISOString(),
305 updatedAt: new Date(NOW - 120_000).toISOString(),
306 },
307 ],
308 drafts: [
309 { ...pull(1, 30), repo },
310 { ...pull(4, 20), repo },
311 { ...pull(5, 20), agent: "syntaqx", repo },
312 ],
313 needKeys: new Set(["acme/web#9"]),
314 });
315 assert.deepEqual(
316 rows.map((r) => [r.ref, r.chip, r.live]),
317 [
318 ["#3", "In queue", true],
319 [null, "Planning", true],
320 ["#4", "Working", false],
321 ["#1", "Checking", false],
322 ],
323 );
324 assert.equal(rows[0].detail, "Merging main in");
325 assert.equal(rows[0].run, "/acme/web/agents/runs/r1");
326 assert.equal(rows[1].title, "Planning in web");
327});
328
329test("tabs and sorting come from the address", () => {
330 assert.equal(parseTab("waiting"), "waiting");
331 assert.equal(parseTab("nope"), null);
332 assert.equal(parseSort("newest"), "newest");
333 assert.equal(parseSort(null), "impact");
334 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
335 assert.deepEqual(sortRows(rows, "impact"), rows);
336 assert.deepEqual(
337 sortRows(rows, "newest").map((r) => r.at),
338 [3, 2, 1],
339 );
340});
341
342test("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 bar343 const none = { total: 0, byAgents: 0, agentChanges: 0, people: 0, live: 0, needs: 0 };
344 assert.equal(
345 summaryLine({ ...none, total: 47, byAgents: 39, agentChanges: 47 }),
346 "Agents landed 39 of their 47 changes this week without you.",
347 );
348 assert.equal(
349 summaryLine({ ...none, total: 3, byAgents: 3, agentChanges: 3 }),
350 "Agents landed all 3 changes of theirs this week without you.",
351 );
352 assert.equal(
353 summaryLine({ ...none, total: 2, agentChanges: 2 }),
354 "Agents made 2 changes this week, each merged by a person.",
355 );
356 // People's own work is counted as theirs, never as agents' that needed help.
357 assert.equal(
358 summaryLine({ ...none, total: 7, byAgents: 4, agentChanges: 5, people: 2 }),
359 "Agents landed 4 of their 5 changes this week without you, and people landed 2 changes of their own.",
360 );
361 assert.equal(summaryLine({ ...none, total: 3, people: 3 }), "People landed 3 changes this week; none were agents'.");
362 assert.equal(summaryLine({ ...none, live: 1 }), "1 agent is at work. Nothing has landed this week yet.");
363 assert.match(summaryLine(none), /Assign an issue/);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms364});