g1t/apps/web/app/routes/repo/why.ts
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.
| Agents as a team: lifecycle, merge queue, billing and a new shell | 1 | import { data } from "react-router"; |
| 2 | ||
| 3 | import type { SessionEntry } from "@g1t/contracts"; | |
| 4 | ||
| 5 | import type { Route } from "./+types/why"; | |
| 6 | import { pullForCommit } from "../../lib/provenance.server"; | |
| 7 | import { repos, work } from "../../lib/services.server"; | |
| 8 | import { getViewer } from "../../lib/session.server"; | |
| 9 | ||
| 10 | /** How many steps of the agent's session are shown for one commit. */ | |
| 11 | const MAX_STEPS = 8; | |
| 12 | /** Longest a step's text is shown. */ | |
| 13 | const MAX_STEP_CHARS = 600; | |
| 14 | ||
| 15 | /** | |
| 16 | * The steps of a session behind `hash`: what the agent said and did | |
| 17 | * between the previous commit and this one. Entries carry the head commit | |
| 18 | * when they were recorded, so the work that produced `hash` is the run of | |
| 19 | * entries just before the first one recorded at it. Steps that touch | |
| 20 | * `file` come first. | |
| 21 | */ | |
| 22 | function stepsFor(session: SessionEntry[], hash: string, file: string): SessionEntry[] { | |
| 23 | // The sandbox notes each push: "Pushed b1c42490059b." The work behind a | |
| 24 | // commit runs from the push before it to the push that carried it. | |
| 25 | const pushed = (entry: SessionEntry) => | |
| 26 | entry.kind === "note" ? /^Pushed ([0-9a-f]{7,40})/.exec(entry.text)?.[1] ?? null : null; | |
| 27 | const end = session.findIndex((entry) => { | |
| 28 | const pushedHash = pushed(entry); | |
| 29 | return entry.commit === hash || (pushedHash != null && hash.startsWith(pushedHash)); | |
| 30 | }); | |
| 31 | let window = session; | |
| 32 | if (end >= 0) { | |
| 33 | let start = end - 1; | |
| 34 | while (start >= 0 && pushed(session[start]!) == null) start--; | |
| 35 | window = session.slice(start + 1, end + 1); | |
| 36 | } | |
| 37 | const name = file.split("/").pop() ?? file; | |
| 38 | const useful = window.filter( | |
| 39 | (entry) => | |
| 40 | entry.kind === "message" || | |
| 41 | (entry.kind === "tool_call" && (entry.text.includes(file) || entry.text.includes(name))), | |
| 42 | ); | |
| 43 | const touching = useful.filter((entry) => entry.text.includes(name)); | |
| 44 | const chosen = (touching.length > 0 ? [...touching, ...useful.filter((e) => e.kind === "message")] : useful) | |
| 45 | .filter((entry, index, all) => all.findIndex((other) => other.seq === entry.seq) === index) | |
| 46 | .sort((a, b) => a.seq - b.seq) | |
| 47 | .slice(-MAX_STEPS); | |
| 48 | return chosen.map((entry) => ({ | |
| 49 | ...entry, | |
| 50 | text: entry.text.length > MAX_STEP_CHARS ? `${entry.text.slice(0, MAX_STEP_CHARS)}…` : entry.text, | |
| 51 | })); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Why a line is the way it is: the commit that last changed it, the pull | |
| 56 | * request and issue it came from, and what the agent was thinking. | |
| 57 | * Fetched by the blame view when a line is picked. | |
| 58 | */ | |
| 59 | export async function loader({ params, request, context }: Route.LoaderArgs) { | |
| 60 | const viewer = getViewer(context); | |
| 61 | const path = { namespace: params.owner, name: params.repo }; | |
| 62 | const file = new URL(request.url).searchParams.get("path") ?? ""; | |
| 63 | if (!/^[0-9a-f]{40}$/i.test(params.hash)) throw data(null, { status: 404 }); | |
| 64 | const [log, pull] = await Promise.all([ | |
| 65 | repos.log(path, viewer, params.hash, 1), | |
| 66 | pullForCommit(path, viewer, params.hash), | |
| 67 | ]); | |
| 68 | const commit = log.ok ? log.value[0] : undefined; | |
| 69 | if (!commit) throw data(null, { status: 404 }); | |
| 70 | if (!pull) return { commit, pull: null, issue: null, steps: [] as SessionEntry[] }; | |
| 71 | const [detail, session] = await Promise.all([ | |
| 72 | work.getPull(path, pull.number, viewer), | |
| 73 | pull.runtime === "hosted" || pull.agent ? work.readSession(path, pull.number, viewer) : null, | |
| 74 | ]); | |
| 75 | const issue = detail.ok ? detail.value.issue : null; | |
| 76 | return { | |
| 77 | commit, | |
| 78 | pull: { | |
| 79 | number: pull.number, | |
| 80 | title: pull.title, | |
| 81 | agent: pull.agent, | |
| 82 | runtime: pull.runtime, | |
| 83 | author: pull.author.username, | |
| 84 | mergedBy: pull.mergedBy, | |
| 85 | mergedAt: pull.mergedAt, | |
| 86 | }, | |
| 87 | issue: issue && { | |
| 88 | number: issue.number, | |
| 89 | title: issue.title, | |
| 90 | body: issue.body.length > 900 ? `${issue.body.slice(0, 900)}…` : issue.body, | |
| 91 | author: issue.author.username, | |
| 92 | }, | |
| 93 | steps: session?.ok ? stepsFor(session.value, commit.hash, file) : [], | |
| 94 | }; | |
| 95 | } |