Publish harness and TUI open-source
initial sync from the monorepo
This commit is contained in:
commit
c68e39f604
2734 changed files with 1437016 additions and 0 deletions
249
crates/codegen/xai-grok-tools/src/util/git_detect.rs
Normal file
249
crates/codegen/xai-grok-tools/src/util/git_detect.rs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
//! Detection of `git commit` / `gh pr create` / `gh pr merge` in terminal
|
||||
//! commands, shared by the bash tool's counter spans and the shell's PR-metric
|
||||
//! session signals (the shell inspects `BashOutput.command` / output at its
|
||||
//! tool-result chokepoint rather than receiving detection through the tool).
|
||||
|
||||
/// Git/GitHub operations detected in a successful terminal command.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DetectedGitOps {
|
||||
/// A non-dry-run `git commit` statement ran.
|
||||
pub committed: bool,
|
||||
/// A `gh pr create` statement ran; url/number parsed from output when printed.
|
||||
pub pr_created: Option<PrRef>,
|
||||
/// A `gh pr merge` statement ran.
|
||||
pub pr_merged: bool,
|
||||
}
|
||||
|
||||
/// Reference to a pull request parsed from command output.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct PrRef {
|
||||
/// Full PR URL (e.g. `https://github.com/owner/repo/pull/123`).
|
||||
pub url: Option<String>,
|
||||
/// PR number parsed from the URL path.
|
||||
pub number: Option<u64>,
|
||||
}
|
||||
|
||||
impl PrRef {
|
||||
/// Find the last `http(s)://…/pull/<N>` URL in `text` — `gh pr create`
|
||||
/// stdout, or an MCP create_pull_request result (URLs may be embedded in
|
||||
/// JSON strings). Returns `None` when no PR URL is present (e.g.
|
||||
/// `gh pr create --web`).
|
||||
pub fn find_in(text: &str) -> Option<Self> {
|
||||
let mut last = None;
|
||||
for (start, _) in text.match_indices("http") {
|
||||
let rest = &text[start..];
|
||||
if !rest.starts_with("https://") && !rest.starts_with("http://") {
|
||||
continue;
|
||||
}
|
||||
let end = rest
|
||||
.find(|c: char| {
|
||||
c.is_whitespace() || matches!(c, '"' | '\'' | '<' | '>' | '\\' | '`')
|
||||
})
|
||||
.unwrap_or(rest.len());
|
||||
let url = rest[..end].trim_end_matches(['.', ',', ';', ':', ')', ']', '}']);
|
||||
// rsplit: an owner/repo literally named "pull" must not eat the marker.
|
||||
let Some((_, tail)) = url.rsplit_once("/pull/") else {
|
||||
continue;
|
||||
};
|
||||
let digits: String = tail.chars().take_while(char::is_ascii_digit).collect();
|
||||
let Ok(number) = digits.parse::<u64>() else {
|
||||
continue;
|
||||
};
|
||||
let url_len = url.len() - tail.len() + digits.len();
|
||||
last = Some(PrRef {
|
||||
url: Some(url[..url_len].to_string()),
|
||||
number: Some(number),
|
||||
});
|
||||
}
|
||||
last
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip invocation prefixes that precede the actual binary in a statement:
|
||||
/// `env` (with `-u NAME` args), `VAR=value` assignments, and an absolute /
|
||||
/// relative path on the binary itself (`/opt/homebrew/bin/gh` → `gh`).
|
||||
/// Covers common `env` / `VAR=value` / absolute-path wrappers around git/gh.
|
||||
fn strip_invocation_prefixes(statement: &str) -> &str {
|
||||
let mut rest = statement.trim_start();
|
||||
loop {
|
||||
let token_end = rest.find(char::is_whitespace).unwrap_or(rest.len());
|
||||
let token = &rest[..token_end];
|
||||
let is_env = token == "env";
|
||||
let is_env_unset = token == "-u";
|
||||
let is_assignment = token.split_once('=').is_some_and(|(name, _)| {
|
||||
!name.is_empty()
|
||||
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
&& !name.starts_with(|c: char| c.is_ascii_digit())
|
||||
});
|
||||
if (is_env || is_env_unset || is_assignment) && token_end < rest.len() {
|
||||
rest = rest[token_end..].trim_start();
|
||||
// `-u` consumes its NAME argument too.
|
||||
if is_env_unset {
|
||||
let name_end = rest.find(char::is_whitespace).unwrap_or(rest.len());
|
||||
if name_end < rest.len() {
|
||||
rest = rest[name_end..].trim_start();
|
||||
} else {
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Path-invoked binary: keep only the basename token.
|
||||
let token_end = rest.find(char::is_whitespace).unwrap_or(rest.len());
|
||||
if let Some(slash) = rest[..token_end].rfind('/')
|
||||
&& matches!(&rest[slash + 1..token_end], "git" | "gh")
|
||||
{
|
||||
rest = &rest[slash + 1..];
|
||||
}
|
||||
rest
|
||||
}
|
||||
|
||||
/// Detect `git commit` / `gh pr create` / `gh pr merge` statements in a
|
||||
/// successful command. Matched per shell statement, anchored at the statement
|
||||
/// start (after invocation prefixes), so `echo "git commit"`, comments, and
|
||||
/// `git commit-graph` don't count.
|
||||
///
|
||||
/// `output_for_prompt` is scanned for the created PR's URL (`gh pr create`
|
||||
/// prints it as the last stdout line; absent for `--web`, leaving an empty
|
||||
/// [`PrRef`]). Callers must only pass exit-code-0 results.
|
||||
pub fn detect_git_ops(command: &str, output_for_prompt: &str) -> Option<DetectedGitOps> {
|
||||
let statements = || {
|
||||
command
|
||||
.split(['\n', ';', '&', '|'])
|
||||
.map(strip_invocation_prefixes)
|
||||
};
|
||||
// `excluded` guards flags that make the statement a no-op for the metric
|
||||
// (`--dry-run` doesn't commit/create; `--disable-auto` un-queues a merge).
|
||||
let statement_runs = |prefix: &str, excluded: &str| {
|
||||
statements().any(|s| {
|
||||
s.strip_prefix(prefix)
|
||||
.is_some_and(|r| r.is_empty() || r.starts_with(char::is_whitespace))
|
||||
&& !s.contains(excluded)
|
||||
})
|
||||
};
|
||||
let committed = statement_runs("git commit", "--dry-run");
|
||||
let pr_created = statement_runs("gh pr create", "--dry-run")
|
||||
.then(|| PrRef::find_in(output_for_prompt).unwrap_or_default());
|
||||
let pr_merged = statement_runs("gh pr merge", "--disable-auto");
|
||||
(committed || pr_created.is_some() || pr_merged).then_some(DetectedGitOps {
|
||||
committed,
|
||||
pr_created,
|
||||
pr_merged,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn commit_and_pr_create_with_url() {
|
||||
let out = "exit: 0\nhttps://github.com/xai-org/example/pull/12345\n";
|
||||
let ops = detect_git_ops("git commit -m 'x' && gh pr create --fill", out).unwrap();
|
||||
assert!(ops.committed);
|
||||
assert!(!ops.pr_merged);
|
||||
let pr = ops.pr_created.unwrap();
|
||||
assert_eq!(
|
||||
pr.url.as_deref(),
|
||||
Some("https://github.com/xai-org/example/pull/12345")
|
||||
);
|
||||
assert_eq!(pr.number, Some(12345));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_create_web_has_no_url() {
|
||||
let ops = detect_git_ops("gh pr create --web", "exit: 0\nOpening browser...\n").unwrap();
|
||||
let pr = ops.pr_created.unwrap();
|
||||
assert_eq!(pr.url, None);
|
||||
assert_eq!(pr.number, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_merge_detected() {
|
||||
let ops = detect_git_ops("gh pr merge 42 --squash", "exit: 0\n").unwrap();
|
||||
assert!(ops.pr_merged);
|
||||
assert!(ops.pr_created.is_none());
|
||||
assert!(!ops.committed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statement_anchoring_rejects_lookalikes() {
|
||||
assert!(detect_git_ops(r#"echo "git commit""#, "").is_none());
|
||||
assert!(detect_git_ops("git commit-graph write", "").is_none());
|
||||
assert!(detect_git_ops(r#"echo "gh pr create is fun""#, "").is_none());
|
||||
assert!(detect_git_ops("git commit --dry-run", "").is_none());
|
||||
assert!(detect_git_ops("gh pr create --dry-run", "").is_none());
|
||||
assert!(detect_git_ops("gh pr merge 42 --disable-auto", "").is_none());
|
||||
assert!(detect_git_ops("gh pr view 42", "").is_none());
|
||||
assert!(detect_git_ops("grep 'gh pr create' transcript.txt", "").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_statement_split() {
|
||||
let ops = detect_git_ops("cd /repo; git commit -am wip", "").unwrap();
|
||||
assert!(ops.committed);
|
||||
assert!(detect_git_ops("ls | grep foo", "").is_none());
|
||||
}
|
||||
|
||||
// Representative invocation-prefix shapes (env vars, absolute paths, env -u).
|
||||
#[test]
|
||||
fn invocation_prefixes_are_stripped() {
|
||||
for cmd in [
|
||||
r#"GH_TOKEN="$GITHUB_TOKEN_FORGE" gh pr create --repo o/r --base main"#,
|
||||
"NO_COLOR=1 CLICOLOR_FORCE= FORCE_COLOR= gh pr create --fill",
|
||||
"/usr/local/bin/gh pr create --head my-branch",
|
||||
"/opt/homebrew/bin/gh pr create --fill",
|
||||
"env -u GITHUB_TOKEN gh pr create --base main",
|
||||
"cd /repo && GIT_AUTHOR_NAME=x /usr/bin/git commit -m msg",
|
||||
] {
|
||||
assert!(detect_git_ops(cmd, "").is_some(), "should match: {cmd}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_stripping_does_not_overreach() {
|
||||
// Assignment-only statements and non-git/gh path binaries don't match.
|
||||
assert!(detect_git_ops("FOO=gh pr create", "").is_none());
|
||||
assert!(detect_git_ops("/usr/bin/echo gh pr create", "").is_none());
|
||||
assert!(detect_git_ops("env", "").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_ref_find_in_takes_last_url_and_trims_punctuation() {
|
||||
let text = "see https://github.com/o/r/pull/1.\nhttps://ghe.example.test/team/repo/pull/987/files\n";
|
||||
let pr = PrRef::find_in(text).unwrap();
|
||||
assert_eq!(
|
||||
pr.url.as_deref(),
|
||||
Some("https://ghe.example.test/team/repo/pull/987")
|
||||
);
|
||||
assert_eq!(pr.number, Some(987));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_ref_find_in_handles_json_embedded_url() {
|
||||
let text = r#"{"number":5,"html_url":"https://github.com/o/r/pull/5","state":"open"}"#;
|
||||
let pr = PrRef::find_in(text).unwrap();
|
||||
assert_eq!(pr.url.as_deref(), Some("https://github.com/o/r/pull/5"));
|
||||
assert_eq!(pr.number, Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_ref_find_in_handles_repo_named_pull() {
|
||||
let pr = PrRef::find_in("https://github.com/org/pull/pull/7").unwrap();
|
||||
assert_eq!(
|
||||
pr.url.as_deref(),
|
||||
Some("https://github.com/org/pull/pull/7")
|
||||
);
|
||||
assert_eq!(pr.number, Some(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_ref_find_in_rejects_non_pr_text() {
|
||||
assert_eq!(PrRef::find_in("no urls here"), None);
|
||||
assert_eq!(PrRef::find_in("https://github.com/o/r/issues/5"), None);
|
||||
assert_eq!(PrRef::find_in("git pull origin main"), None);
|
||||
assert_eq!(PrRef::find_in("https://github.com/o/r/pull/"), None);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue