Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
324 linesCodeBlameRaw

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.

Merge main into stream A: coordination meets the merge queue's decisions and the CLI. get_work gives the issue's decision records newest first and its latest integration_failure decision as last_integration_failure, a merge queue failure noted in a pull request's session is noted in the live harness sessions on it or its issue too, the CLI's coordination commands use the generated operations now that the API describes them (its pending list is empty) and show get_work's definition of done, and the session, decision and coordination events sit together in the catalog, the webhook form and the docs.1//! `why`: why a line of a file is the way it is.
2//!
3//! The line's commit comes from the repos service's blame; the merged pull
4//! request it arrived in is the one whose head it is, or else the one whose
5//! stretch of the default branch's history holds it (as the site's blame
6//! view finds it, `apps/web/app/lib/provenance.server.ts`); then that pull
7//! request's issue, its decision records, and the session entries recorded
8//! around the commit.
9
10use g1t_contracts::decisions::{Decision, ListDecisionsArgs, Why, WhyIssue, WhyPull};
11use g1t_contracts::repos::{Blame, BlameArgs, Commit, LogArgs, RepoPath};
12use g1t_contracts::work::{IssueDetail, ListPullsArgs, Pull, PullStatus, SessionEntry, SessionEntryKind, State, ViewArgs};
13use g1t_contracts::{FailureCode, Outcome, Viewer};
14use serde_json::Value;
15use worker::Result;
16
17use crate::operations::Services;
18
19/// How far back the default branch is read to place a commit.
20const HISTORY_READ: u32 = 300;
21/// How many steps of a session are shown for one commit.
22const MAX_STEPS: usize = 8;
23/// Longest a step's text is shown.
24const MAX_STEP_CHARS: usize = 600;
25/// Longest an issue's description is shown.
26const MAX_BODY_CHARS: usize = 900;
27
28/// The commit that last changed `line`, from a blame of the file.
29pub(crate) fn commit_of_line(blame: &Blame, line: u32) -> Option<&Commit> {
30 let range = blame.ranges.iter().find(|range| range.start <= line && line <= range.end)?;
31 blame.commits.iter().find(|commit| commit.hash == range.commit)
32}
33
34/// The merged pull request `hash` arrived in: the one whose head it is,
35/// or else the one whose stretch of `history` (the default branch, newest
36/// first) holds it, from its head back to the commit it started from.
37pub(crate) fn pull_for_commit<'a>(merged: &'a [Pull], history: &[String], hash: &str) -> Option<&'a Pull> {
38 let merged: Vec<&Pull> = merged
39 .iter()
40 .filter(|pull| pull.status == PullStatus::Merged && pull.head_commit.is_some())
41 .collect();
42 if let Some(direct) = merged.iter().find(|pull| pull.head_commit.as_deref() == Some(hash)) {
43 return Some(direct);
44 }
45 let position = |commit: &str| history.iter().position(|known| known == commit);
46 let at = position(hash)?;
47 let mut best: Option<(&Pull, usize)> = None;
48 for pull in merged {
49 let Some(head) = pull.head_commit.as_deref().and_then(position) else { continue };
50 if head > at {
51 continue;
52 }
53 if let Some(base) = pull.merge_base.as_deref().and_then(position)
54 && base <= at
55 {
56 continue;
57 }
58 if best.is_none_or(|(_, best_head)| head > best_head) {
59 best = Some((pull, head));
60 }
61 }
62 best.map(|(pull, _)| pull)
63}
64
65/// The steps of a session behind `hash`: what was said and done between
66/// the push before it and the push that carried it. Steps that touch
67/// `file` come first; at most [`MAX_STEPS`], each cut to
68/// [`MAX_STEP_CHARS`].
69pub(crate) fn steps_for(session: &[SessionEntry], hash: &str, file: &str) -> Vec<SessionEntry> {
70 // A push is noted "Pushed b1c42490059b."
71 let pushed = |entry: &SessionEntry| -> Option<String> {
72 if entry.kind != SessionEntryKind::Note {
73 return None;
74 }
75 let rest = entry.text.strip_prefix("Pushed ")?;
76 let short: String = rest.chars().take_while(char::is_ascii_hexdigit).collect();
77 (short.len() >= 7).then_some(short)
78 };
79 let end = session.iter().position(|entry| {
80 entry.commit.as_deref() == Some(hash) || pushed(entry).is_some_and(|short| hash.starts_with(&short))
81 });
82 let window: &[SessionEntry] = match end {
83 Some(end) => {
84 let mut start = end;
85 while start > 0 && pushed(&session[start - 1]).is_none() {
86 start -= 1;
87 }
88 &session[start..=end]
89 }
90 None => session,
91 };
92 let name = file.rsplit('/').next().unwrap_or(file);
93 let useful: Vec<&SessionEntry> = window
94 .iter()
95 .filter(|entry| {
96 entry.kind == SessionEntryKind::Message
97 || (entry.kind == SessionEntryKind::ToolCall && (entry.text.contains(file) || entry.text.contains(name)))
98 })
99 .collect();
100 let touching: Vec<&SessionEntry> = useful.iter().copied().filter(|entry| entry.text.contains(name)).collect();
101 let mut chosen: Vec<&SessionEntry> = if touching.is_empty() {
102 useful.clone()
103 } else {
104 touching.into_iter().chain(useful.iter().copied().filter(|entry| entry.kind == SessionEntryKind::Message)).collect()
105 };
106 chosen.sort_by_key(|entry| entry.seq);
107 chosen.dedup_by_key(|entry| entry.seq);
108 let skip = chosen.len().saturating_sub(MAX_STEPS);
109 chosen
110 .into_iter()
111 .skip(skip)
112 .map(|entry| {
113 let mut entry = entry.clone();
114 if entry.text.chars().count() > MAX_STEP_CHARS {
115 entry.text = format!("{}…", entry.text.chars().take(MAX_STEP_CHARS).collect::<String>());
116 }
117 entry
118 })
119 .collect()
120}
121
122/// Answers `why`.
123pub(crate) async fn why(
124 services: &Services,
125 viewer: &Viewer,
126 repo: RepoPath,
127 path: String,
128 line: u32,
129 git_ref: Option<String>,
130) -> Result<Outcome<Value>> {
131 let blame: Outcome<Blame> = g1t_kit::call(
132 &services.repos,
133 "blame",
134 &BlameArgs {
135 path: repo.clone(),
136 viewer: viewer.clone(),
137 git_ref,
138 file_path: path.clone(),
139 },
140 )
141 .await?;
142 let blame = match blame {
143 Outcome::Ok(blame) => blame,
144 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
145 };
146 let Some(commit) = commit_of_line(&blame, line).cloned() else {
147 let lines = blame.ranges.last().map_or(0, |range| range.end);
148 return Ok(Outcome::fail(
149 FailureCode::Invalid,
150 format!("{path} has {lines} lines at that ref: give a line from 1 to {lines}."),
151 ));
152 };
153
154 let closed: Outcome<Vec<Pull>> = g1t_kit::call(
155 &services.work,
156 "list_pulls",
157 &ListPullsArgs {
158 repo: repo.clone(),
159 viewer: viewer.clone(),
160 state: Some(State::Closed),
161 label: None,
162 milestone: None,
163 base: None,
164 },
165 )
166 .await?;
167 let merged = match closed {
168 Outcome::Ok(pulls) => pulls,
169 Outcome::Fail(_) => Vec::new(),
170 };
171 let mut pull = pull_for_commit(&merged, &[], &commit.hash).cloned();
172 if pull.is_none() && !merged.is_empty() {
173 let log: Outcome<Vec<Commit>> = g1t_kit::call(
174 &services.repos,
175 "log",
176 &LogArgs {
177 path: repo.clone(),
178 viewer: viewer.clone(),
179 git_ref: None,
180 limit: HISTORY_READ,
181 },
182 )
183 .await?;
184 if let Outcome::Ok(log) = log {
185 let history: Vec<String> = log.into_iter().map(|commit| commit.hash).collect();
186 pull = pull_for_commit(&merged, &history, &commit.hash).cloned();
187 }
188 }
189
190 let Some(pull) = pull else {
191 return ok(Why { path, line, commit, pull: None, issue: None, decisions: Vec::new(), sessions: Vec::new() });
192 };
193 let view = |number: u32| ViewArgs { repo: repo.clone(), number, viewer: viewer.clone(), after_seq: 0 };
194 // A few small reads, one after another: the API has no join helper.
195 let issue = match pull.issue {
196 Some(number) => match g1t_kit::call::<_, Outcome<IssueDetail>>(&services.work, "get_issue", &view(number)).await? {
197 Outcome::Ok(detail) => Some(detail.issue),
198 Outcome::Fail(_) => None,
199 },
200 None => None,
201 };
202 let decisions: Outcome<Vec<Decision>> = g1t_kit::call(
203 &services.work,
204 "list_decisions",
205 &ListDecisionsArgs { repo: repo.clone(), viewer: viewer.clone(), number: pull.number },
206 )
207 .await?;
208 let session: Outcome<Vec<SessionEntry>> = g1t_kit::call(&services.work, "read_session", &view(pull.number)).await?;
209 let decisions = match decisions {
210 Outcome::Ok(decisions) => decisions,
211 Outcome::Fail(_) => Vec::new(),
212 };
213 let sessions = match session {
214 Outcome::Ok(entries) => steps_for(&entries, &commit.hash, &path),
215 Outcome::Fail(_) => Vec::new(),
216 };
217 let issue = issue.map(|issue| WhyIssue {
218 number: issue.number,
219 title: issue.title,
220 state: if issue.state == State::Open { "open" } else { "closed" }.to_owned(),
221 body: if issue.body.chars().count() > MAX_BODY_CHARS {
222 format!("{}…", issue.body.chars().take(MAX_BODY_CHARS).collect::<String>())
223 } else {
224 issue.body
225 },
226 });
227 let pull = WhyPull {
228 number: pull.number,
229 title: pull.title.clone(),
230 author: pull.owner().username.clone(),
231 agent: pull.agent.clone(),
232 merged_by: pull.merged_by.clone(),
233 merged_at: pull.merged_at.clone(),
234 };
235 ok(Why { path, line, commit, pull: Some(pull), issue, decisions, sessions })
236}
237
238fn ok(why: Why) -> Result<Outcome<Value>> {
239 Ok(Outcome::Ok(serde_json::to_value(why)?))
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use g1t_contracts::repos::{BlameRange, Signature};
246
247 fn commit(hash: &str) -> Commit {
248 Commit {
249 hash: hash.into(),
250 tree_hash: "t".into(),
251 message: "m".into(),
252 author: Signature { name: "Ana".into(), email: "ana@example.com".into() },
253 parents: Vec::new(),
254 authored_at: "2026-10-12T00:00:00Z".into(),
255 }
256 }
257
258 fn merged(number: u32, head: &str, base: Option<&str>) -> Pull {
259 serde_json::from_value(serde_json::json!({
260 "id": format!("pr_{number}"), "repoId": "rep_1", "number": number, "issue": 9, "title": "t", "body": null,
261 "agent": "claude-code", "runtime": "external", "status": "merged", "fork": null, "forkRepoId": null,
262 "branch": "b", "headCommit": head, "mergeBase": base, "mergedBy": "ana", "mergedAt": "2026-10-12T00:00:00Z",
263 "supersededBy": null, "checkStatus": null,
264 "author": { "id": "usr_1", "username": "ana" },
265 "createdAt": "", "updatedAt": ""
266 }))
267 .unwrap()
268 }
269
270 #[test]
271 fn a_line_is_found_in_its_range() {
272 let blame = Blame {
273 head: "h".into(),
274 ranges: vec![
275 BlameRange { start: 1, end: 3, commit: "aaa".into() },
276 BlameRange { start: 4, end: 4, commit: "bbb".into() },
277 ],
278 commits: vec![commit("aaa"), commit("bbb")],
279 partial: false,
280 };
281 assert_eq!(commit_of_line(&blame, 3).map(|c| c.hash.as_str()), Some("aaa"));
282 assert_eq!(commit_of_line(&blame, 4).map(|c| c.hash.as_str()), Some("bbb"));
283 assert!(commit_of_line(&blame, 5).is_none());
284 }
285
286 #[test]
287 fn a_commit_is_placed_in_the_pull_request_it_arrived_in() {
288 // Newest first: m2 landed #12 on top of b1; m1 landed #11 on top of b0.
289 let history: Vec<String> = ["m2", "c3", "c2", "b1", "m1", "c1", "b0"].map(String::from).to_vec();
290 let pulls = vec![merged(11, "m1", Some("b0")), merged(12, "m2", Some("b1"))];
291 assert_eq!(pull_for_commit(&pulls, &[], "m1").map(|p| p.number), Some(11));
292 assert_eq!(pull_for_commit(&pulls, &history, "c2").map(|p| p.number), Some(12));
293 assert_eq!(pull_for_commit(&pulls, &history, "c1").map(|p| p.number), Some(11));
294 // Before any pull request.
295 assert!(pull_for_commit(&pulls, &history, "b0").is_none());
296 assert!(pull_for_commit(&pulls, &history, "zzz").is_none());
297 }
298
299 fn entry(seq: u32, kind: SessionEntryKind, text: &str, commit: Option<&str>) -> SessionEntry {
300 SessionEntry { seq, kind, text: text.into(), tool: None, commit: commit.map(String::from), at: String::new() }
301 }
302
303 #[test]
304 fn the_session_steps_are_those_behind_the_commit() {
305 use SessionEntryKind::*;
306 let session = vec![
307 entry(1, Message, "Starting on the parser.", None),
308 entry(2, Note, "Pushed 1111111aaaa.", None),
309 entry(3, Message, "The lexer drops the last token.", None),
310 entry(4, ToolCall, "Edit src/lexer.rs", None),
311 entry(5, ToolCall, "Run cargo test", None),
312 entry(6, Note, "Pushed 2222222bbbb.", None),
313 entry(7, Message, "Now the docs.", None),
314 ];
315 let steps = steps_for(&session, "2222222bbbbcccc", "src/lexer.rs");
316 let seqs: Vec<u32> = steps.iter().map(|step| step.seq).collect();
317 assert_eq!(seqs, [3, 4]);
318 // A commit no push names: the whole session is looked through.
319 let all = steps_for(&session, "9999999", "src/lexer.rs");
320 assert_eq!(all.iter().map(|s| s.seq).collect::<Vec<_>>(), [1, 3, 4, 7]);
321 let long = vec![entry(1, Message, &"x".repeat(MAX_STEP_CHARS + 10), None)];
322 assert!(steps_for(&long, "h", "a.rs")[0].text.ends_with('…'));
323 }
324}

This file's history is long; its oldest lines are credited to the oldest commit read.