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

353 lines15,298 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,
23 waitingRows,
24 weekOf,
25 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step26 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms27} from "./mission-control.ts";
28
29const HOUR = 3_600_000;
30const DAY = 24 * HOUR;
31// Monday 2026-10-05, 18:00 UTC.
32const NOW = Date.parse("2026-10-05T18:00:00Z");
33const repo = { namespace: "acme", name: "web" };
34
35test("each kind of need gets its reason chip", () => {
36 assert.equal(reasonFor({ kind: "deploy", detail: "" }), "blocking");
37 assert.equal(reasonFor({ kind: "limit", detail: "" }), "blocking");
38 assert.equal(reasonFor({ kind: "review", detail: "" }), "asked_for_you");
39 assert.equal(reasonFor({ kind: "invitation", detail: "" }), "asked_for_you");
40 assert.equal(reasonFor({ kind: "ready", detail: "" }), "ready_to_merge");
41 assert.equal(reasonFor({ kind: "checks", detail: "" }), "checks_failing");
42 assert.equal(reasonFor({ kind: "stuck", detail: "" }), "stalled");
43});
44
45test("a stopped pull request's reason comes from the sentence g1t stopped with", () => {
Fast pages, required checks on the branch, self-hosted runners, honest incidents46 assert.equal(stallReason("The required check CI still fails after the agent revised 3 times."), "checks_failing");
47 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 200ms48 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 incidents49 assert.equal(stallReason("It failed in the merge queue after the agent revised twice."), "checks_failing");
50 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 200ms51 assert.equal(
52 stallReason("g1t stopped the agent's implement run when it reached its cost cap. Raise the cap under Settings, Guardrails."),
53 "outside_guardrails",
54 );
55 assert.equal(stallReason("g1t stopped the agent's review run when it reached its time cap."), "outside_guardrails");
56 assert.equal(stallReason("g1t could not merge this: the branch moved"), "blocking");
57 assert.equal(stallReason("Needs 1 approving review from someone with write access."), "needs_review");
58 assert.equal(stallReason("The review still asks for changes after the agent revised 3 times."), "needs_review");
59 assert.equal(stallReason("alex asked for changes, and the agent has already revised 3 times."), "needs_review");
60 assert.equal(stallReason("syntaqx stopped the agent's implement run."), "stalled");
61 assert.equal(stallReason("g1t could not start the next step: no slot"), "stalled");
62});
63
64test("why a need needs a person is said for every reason", () => {
65 for (const kind of ["deploy", "limit", "invitation", "review", "stuck", "checks", "ready", "conflict"] as const) {
66 const need = { kind, detail: "" };
67 assert.ok(whyFor(reasonFor(need), need).length > 20, kind);
68 }
69 const stalled = { kind: "stalled" as const, detail: "It reached its cost cap." };
70 assert.match(whyFor(reasonFor(stalled), stalled), /Guardrails/);
71});
72
73test("what the agent knows lists only what is known", () => {
74 const facts = pullFacts({
75 checkStatus: "failed",
76 files: [
77 { path: "src/retry.ts", additions: 40, deletions: 2 },
78 { path: "src/retry.test.ts", additions: 60, deletions: 0 },
79 ],
80 lifecycle: { revisions: 2 },
81 runs: [
82 { kind: "implement", costUsd: 0.5 },
83 { kind: "revise", costUsd: 0.25 },
84 { kind: "checks", costUsd: null },
85 ],
86 });
87 const by = Object.fromEntries(facts.map((f) => [f.label, f]));
Fast pages, required checks on the branch, self-hosted runners, honest incidents88 assert.equal(by["Required checks"].value, "Failed in the merge queue");
89 assert.equal(by["Required checks"].tone, "bad");
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms90 assert.equal(by["Files changed"].value, "2");
91 assert.equal(by.Lines.value, "+100 −2");
92 assert.equal(by.Tests.value, "1 file");
93 assert.equal(by["Sent back"].value, "2 times");
94 assert.equal(by["Agent runs"].value, "2 · $0.75");
95
96 const bare = pullFacts({ checkStatus: null, files: [] });
Fast pages, required checks on the branch, self-hosted runners, honest incidents97 assert.deepEqual(bare, [{ label: "Required checks", value: "Not known yet", tone: null }]);
98});
99
100test("the required checks are read from where the agent's pull request stands", () => {
101 const at = (stage: Parameters<typeof checksFact>[1] extends infer L ? NonNullable<L>["stage"] : never, detail: string) =>
102 checksFact(null, { stage, detail });
103 assert.deepEqual(at("checking", "Waiting for CI / pull_request to finish."), { label: "Required checks", value: "Running", tone: null });
104 assert.equal(at("checking", "Waiting for the required check Deploy to report on its latest commit.").value, "Not reported yet");
105 assert.equal(at("revising", "CI / pull_request failed. The agent is being sent back to fix it.").value, "Failing");
106 assert.equal(at("needs_you", "The required check CI still fails after the agent revised twice.").value, "Failing");
107 // Past the checks: a review, an approval or a person's decision holds it, not them.
108 assert.equal(at("revising", "The review asked for changes. The agent is being sent back to make them.").value, "Passing");
109 assert.equal(at("needs_you", "This repository requires 1 approving review before a pull request merges; this one has 0.").value, "Passing");
110 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-agent111 assert.equal(at("working", "g1t is making the change.").value, "Not run yet");
Fast pages, required checks on the branch, self-hosted runners, honest incidents112 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 200ms113});
114
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step115test("a change held for low confidence is its own reason, ahead of what it would read as", () => {
116 const held =
117 "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.";
118 // Its reasons name checks and approval, but it is held for its confidence.
119 assert.equal(stallReason(held), "low_confidence");
120 assert.equal(reasonFor({ kind: "stalled", detail: held }), "low_confidence");
121
122 const low = { level: "low" as const, reasons: ["tests not added", "3 revisions"], uncertainAbout: ["the retry limit"] };
123 assert.equal(withConfidence("ready_to_merge", low), "low_confidence");
124 assert.equal(withConfidence("needs_review", low), "low_confidence");
125 // A failure that needs someone anyway keeps its own chip.
126 assert.equal(withConfidence("checks_failing", low), "checks_failing");
127 assert.equal(withConfidence("ready_to_merge", { level: "medium" }), "ready_to_merge");
128 assert.equal(withConfidence("ready_to_merge", null), "ready_to_merge");
129
130 assert.equal(confidenceLine(low), "Low — tests not added, 3 revisions");
131 assert.equal(confidenceLine({ level: "high", reasons: [] }), "High");
132 assert.match(confidenceAsk(low), /not sure of the change: tests not added, 3 revisions\. Approve it/);
133
134 const why = whyFor("low_confidence", { kind: "stalled", detail: held }, low);
135 assert.match(why, /from tests not added, 3 revisions\./);
136 assert.match(why, /unsure about the retry limit/);
137 assert.match(why, /nothing merges it until you approve it/);
138 // Ready, not held: it says to look before merging.
139 assert.match(whyFor("low_confidence", { kind: "ready", detail: "" }, low), /Look at it before you merge it/);
140});
141
142test("what the agent knows gains its confidence, across the row", () => {
143 const facts = pullFacts({
144 checkStatus: "passed",
145 files: [{ path: "src/retry.ts", additions: 40, deletions: 2 }],
146 lifecycle: { revisions: 3 },
147 confidence: { level: "low", reasons: ["tests not added", "3 revisions"] },
148 });
149 const fact = facts.find((f) => f.label === "Confidence");
150 assert.deepEqual(fact, { label: "Confidence", value: "Low — tests not added, 3 revisions", tone: "bad", wide: true });
151 assert.equal(facts.at(-1)?.label, "Confidence");
152});
153
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms154test("test files are recognised by the names test runners use", () => {
155 assert.ok(isTestFile("src/a.test.ts"));
156 assert.ok(isTestFile("src/__tests__/a.ts"));
157 assert.ok(isTestFile("tests/api.rs"));
158 assert.ok(isTestFile("pkg/store_test.go"));
159 assert.ok(!isTestFile("src/testing-utils.ts"));
160 assert.ok(!isTestFile("src/contest.ts"));
161});
162
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar163const 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 200ms164 repo,
165 number,
166 title: `Change ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent167 agent: "g1t",
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar168 authoredByAgent,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms169 mergedBy,
170 mergedAt: new Date(NOW - daysAgo * DAY).toISOString(),
171 files: [],
172});
173
174test("a change landed without a person when g1t merged it", () => {
175 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-agent176 assert.ok(landedByAgents({ mergedBy: "g1t" }));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms177 assert.ok(landedByAgents({ mergedBy: null }));
178 assert.ok(!landedByAgents({ mergedBy: "syntaqx" }));
179});
180
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar181test("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 200ms182 const week = weekOf(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar183 [
184 merged(0, "g1t"),
185 merged(0, "syntaqx"),
186 // A person's own change, merged by them and auto-merged by g1t: both theirs.
187 merged(0, "syntaqx", 2, false),
188 merged(1, "g1t", 3, false),
189 merged(1, "g1t"),
190 merged(6, "g1t"),
191 merged(8, "g1t"),
192 merged(10, "alex"),
193 merged(20, "g1t"),
194 ],
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms195 NOW,
196 "UTC",
197 );
198 assert.equal(week.days.length, 7);
199 assert.equal(week.days[6].key, "2026-10-05");
200 assert.equal(week.days[6].label, "Mon");
201 assert.deepEqual(
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar202 week.days.map((d) => [d.agents, d.assisted, d.people]),
203 [[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 200ms204 );
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar205 assert.equal(week.total, 6);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms206 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 bar207 assert.equal(week.agentChanges, 4);
208 assert.equal(week.people, 2);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms209 assert.equal(week.previous, 2);
210 // Not knowing the week before is not the same as nothing in it.
211 assert.equal(weekOf([merged(0, "g1t")], NOW, "UTC", false).previous, null);
212});
213
214test("days follow the viewer's time zone", () => {
215 // 02:00 UTC on the 5th is still the 4th in Los Angeles.
216 const late = Date.parse("2026-10-05T02:00:00Z");
217 assert.equal(dayKey(late, "America/Los_Angeles"), "2026-10-04");
218 assert.equal(dayKey(late, "UTC"), "2026-10-05");
219 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 bar220 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 200ms221 assert.equal(week.days.find((d) => d.key === "2026-10-04")?.agents, 1);
222});
223
224test("the change from last week, and how it reads", () => {
225 assert.equal(change(12, 10), 0.2);
226 assert.equal(change(5, 0), null);
227 assert.equal(change(5, null), null);
228 assert.equal(signedPercent(0.2), "+20%");
229 assert.equal(signedPercent(-0.054), "−5%");
230 assert.equal(signedPercent(0.001), "0%");
231});
232
233test("a repository's merged list reaches back when it is not full or its oldest is old enough", () => {
234 const since = NOW - 14 * DAY;
235 const at = (days: number) => ({ mergedAt: new Date(NOW - days * DAY).toISOString(), updatedAt: new Date(NOW).toISOString() });
236 assert.ok(reachesBack([at(1), at(2)], since, 100));
237 assert.ok(reachesBack([at(1), at(20)], since, 2));
238 assert.ok(!reachesBack([at(1), at(3)], since, 2));
239});
240
241test("landed today is the viewer's calendar day, newest first", () => {
242 const rows = landedToday([merged(0.1, "g1t", 3), merged(0.5, "syntaqx", 4), merged(1, "g1t", 2)], NOW, "UTC");
243 assert.deepEqual(
244 rows.map((r) => [r.ref, r.byAgents]),
245 [
246 ["#3", true],
247 ["#4", false],
248 ],
249 );
250 assert.equal(rows[1].by?.name, "syntaqx");
251});
252
253test("waiting on agents holds each pull request once, running ones first, and nothing that needs you", () => {
254 const pull = (number: number, minutesAgo: number) => ({
255 number,
256 title: `Pull ${number}`,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent257 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms258 updatedAt: new Date(NOW - minutesAgo * 60_000).toISOString(),
259 checkStatus: null,
260 files: [],
261 });
262 const rows = waitingRows({
263 active: [
Fast pages, required checks on the branch, self-hosted runners, honest incidents264 { 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 200ms265 { pull: pull(2, 5), lifecycle: { stage: "needs_you", detail: "Stopped.", revisions: 0 }, repo },
266 { pull: pull(3, 50), lifecycle: { stage: "queued", detail: "In the merge queue.", revisions: 0 }, repo },
267 { pull: pull(9, 1), lifecycle: { stage: "reviewing", detail: "Reviewing.", revisions: 0 }, repo },
268 ],
269 live: [
270 {
271 id: "r1",
272 repo,
273 number: 3,
274 title: "Pull 3",
275 kind: "update",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent276 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms277 step: "Merging main in",
278 costUsd: 0.1,
279 startedAt: new Date(NOW - 60_000).toISOString(),
280 createdAt: new Date(NOW - 60_000).toISOString(),
281 updatedAt: new Date(NOW - 10_000).toISOString(),
282 },
283 {
284 id: "r2",
285 repo,
286 number: null,
287 title: null,
288 kind: "plan",
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent289 agent: "g1t",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms290 step: null,
291 costUsd: null,
292 startedAt: null,
293 createdAt: new Date(NOW - 120_000).toISOString(),
294 updatedAt: new Date(NOW - 120_000).toISOString(),
295 },
296 ],
297 drafts: [
298 { ...pull(1, 30), repo },
299 { ...pull(4, 20), repo },
300 { ...pull(5, 20), agent: "syntaqx", repo },
301 ],
302 needKeys: new Set(["acme/web#9"]),
303 });
304 assert.deepEqual(
305 rows.map((r) => [r.ref, r.chip, r.live]),
306 [
307 ["#3", "In queue", true],
308 [null, "Planning", true],
309 ["#4", "Working", false],
310 ["#1", "Checking", false],
311 ],
312 );
313 assert.equal(rows[0].detail, "Merging main in");
314 assert.equal(rows[0].run, "/acme/web/agents/runs/r1");
315 assert.equal(rows[1].title, "Planning in web");
316});
317
318test("tabs and sorting come from the address", () => {
319 assert.equal(parseTab("waiting"), "waiting");
320 assert.equal(parseTab("nope"), null);
321 assert.equal(parseSort("newest"), "newest");
322 assert.equal(parseSort(null), "impact");
323 const rows = [{ at: 1 }, { at: 3 }, { at: 2 }];
324 assert.deepEqual(sortRows(rows, "impact"), rows);
325 assert.deepEqual(
326 sortRows(rows, "newest").map((r) => r.at),
327 [3, 2, 1],
328 );
329});
330
331test("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 bar332 const none = { total: 0, byAgents: 0, agentChanges: 0, people: 0, live: 0, needs: 0 };
333 assert.equal(
334 summaryLine({ ...none, total: 47, byAgents: 39, agentChanges: 47 }),
335 "Agents landed 39 of their 47 changes this week without you.",
336 );
337 assert.equal(
338 summaryLine({ ...none, total: 3, byAgents: 3, agentChanges: 3 }),
339 "Agents landed all 3 changes of theirs this week without you.",
340 );
341 assert.equal(
342 summaryLine({ ...none, total: 2, agentChanges: 2 }),
343 "Agents made 2 changes this week, each merged by a person.",
344 );
345 // People's own work is counted as theirs, never as agents' that needed help.
346 assert.equal(
347 summaryLine({ ...none, total: 7, byAgents: 4, agentChanges: 5, people: 2 }),
348 "Agents landed 4 of their 5 changes this week without you, and people landed 2 changes of their own.",
349 );
350 assert.equal(summaryLine({ ...none, total: 3, people: 3 }), "People landed 3 changes this week; none were agents'.");
351 assert.equal(summaryLine({ ...none, live: 1 }), "1 agent is at work. Nothing has landed this week yet.");
352 assert.match(summaryLine(none), /Assign an issue/);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms353});