pub struct Repo {
pub style: Style,
pub branch_prefix: String,
pub state_store: StateStore,
pub followups: Followups,
/* private fields */
}Fields§
§style: Style§branch_prefix: String§state_store: StateStore§followups: FollowupsImplementations§
Source§impl Repo
impl Repo
pub fn open(root: impl AsRef<Path>, cfg: &Config) -> Result<Self>
pub fn root(&self) -> &Path
Sourcepub fn clean(&self, text: &str) -> Result<String>
pub fn clean(&self, text: &str) -> Result<String>
Scrub, then verify. A leak here reaches GitHub, so it is a hard error rather than a warning: silent partial compliance is how a style rule erodes over a long run.
Sourcepub fn clean_body(&self, text: &str) -> Result<String>
pub fn clean_body(&self, text: &str) -> Result<String>
Clean, and hold to a length budget. For anything a model wrote.
Sourcepub fn clean_issue_body(&self, text: &str) -> Result<String>
pub fn clean_issue_body(&self, text: &str) -> Result<String>
The same, with an issue’s far larger budget and its exemption for code.
Sourcepub fn clean_title(&self, text: &str) -> Result<String>
pub fn clean_title(&self, text: &str) -> Result<String>
The single transform every outbound title goes through.
Scrub first, clip second, and never the other way round. Clipping first lets the scrub lengthen the result past the budget (an em dash becomes two characters), so a second pass would clip again and produce a different string. That broke follow-up deduplication silently: the lookup searched for one title while GitHub had stored another, no match was ever found, and a fresh duplicate issue was filed every review round. Doing it in this order makes the transform idempotent, which the tests assert.
pub fn git(&self, args: &[&str]) -> Result<String>
pub fn git_at(&self, cwd: Option<&Path>, args: &[&str]) -> Result<String>
Sourcepub fn git_try(&self, args: &[&str]) -> String
pub fn git_try(&self, args: &[&str]) -> String
Run git, tolerating failure. Returns whatever landed on stdout.
pub fn git_try_at(&self, cwd: Option<&Path>, args: &[&str]) -> String
Sourcepub fn default_branch(&self, configured: &str) -> String
pub fn default_branch(&self, configured: &str) -> String
The base branch the remote actually points at, rather than assuming
main. Falls back to the configured value when there is no origin.
pub fn branch_for_issue(&self, issue: i64) -> String
pub fn branch_for_pr(&self, number: i64) -> String
pub fn known_branches(&self) -> BTreeMap<String, BranchRecord>
pub fn record_branch(&self, branch: &str, kind: &str, number: i64)
pub fn forget_branch(&self, branch: &str)
Sourcepub fn worktree_add(&self, issue: i64, base: &str) -> Result<(PathBuf, String)>
pub fn worktree_add(&self, issue: i64, base: &str) -> Result<(PathBuf, String)>
Isolate an issue so a failed run cannot poison the next one’s base.
pub fn worktree_remove(&self, issue: i64)
Sourcepub fn worktree_for_pr(&self, pr: &PrView) -> Result<(PathBuf, String)>
pub fn worktree_for_pr(&self, pr: &PrView) -> Result<(PathBuf, String)>
Check an existing PR branch out into an isolated worktree.
Sourcepub fn worktree_for_pr_head(&self, number: i64) -> Result<PathBuf>
pub fn worktree_for_pr_head(&self, number: i64) -> Result<PathBuf>
Check a pull request’s head out read only, detached, with no branch.
Fetches refs/pull/N/head, which GitHub serves for every pull request
including one from a fork whose branch is not in this repository at all.
That is what makes reviewing an outside contribution possible when
pushing to it is not.
Detached on purpose. Review only mode has nothing to push, and a branch would only invite something to try.
pub fn release_review_worktree(&self, number: i64)
pub fn release_pr_worktree(&self, number: i64)
Sourcepub fn base_ref(&self, cwd: &Path, base: &str) -> String
pub fn base_ref(&self, cwd: &Path, base: &str) -> String
What to diff against: the remote tracking branch when it resolves, the local branch when it does not.
This is not a nicety. Every “did the agent do anything” check hangs off
this ref, and git log against a ref that does not exist fails silently
and reads as “no commits”. A checkout whose origin/main was never
fetched would report every implementation as abandoned and throw the
work away.
pub fn has_changes(&self, cwd: &Path, base: &str) -> bool
pub fn diff_stat(&self, cwd: &Path, base: &str) -> String
Sourcepub fn rewrite_commits_if_needed(&self, cwd: &Path, base: &str) -> Result<()>
pub fn rewrite_commits_if_needed(&self, cwd: &Path, base: &str) -> Result<()>
Scrub commit messages that slipped past the prompt.
git filter-branch calls back into this same binary, so there is no
interpreter to find and no second copy of the rules to drift.
Sourcepub fn push(&self, cwd: &Path, branch: &str) -> Result<()>
pub fn push(&self, cwd: &Path, branch: &str) -> Result<()>
Push by explicit refspec from HEAD.
A resumed PR is checked out under a local name (pr-N) that does not
match its remote branch, so pushing by branch name would resolve the
wrong local ref or fail outright.
pub fn gh(&self, args: &[&str]) -> Result<String>
pub fn gh_at(&self, cwd: Option<&Path>, args: &[&str]) -> Result<String>
pub fn gh_try(&self, args: &[&str]) -> String
pub fn fetch_issues(&self, numbers: &[i64]) -> Result<Vec<Issue>>
Sourcepub fn list_open_issues(
&self,
limit: usize,
min_number: i64,
) -> Result<Vec<i64>>
pub fn list_open_issues( &self, limit: usize, min_number: i64, ) -> Result<Vec<i64>>
Open issues, lowest numbered first. gh issue list excludes PRs.
pub fn list_open_prs(&self, limit: usize, min_number: i64) -> Result<Vec<i64>>
pub fn pr_for_branch(&self, branch: &str) -> Option<PrRef>
Sourcepub fn item_kind(&self, number: i64) -> Result<ItemKind>
pub fn item_kind(&self, number: i64) -> Result<ItemKind>
Whether a number names an issue or a pull request.
gh issue view happily returns a pull request when handed its number,
so it cannot be used to tell them apart. The issues API carries both and
marks a pull request with a pull_request key, which is definitive.
Sourcepub fn open_pr_for_issue(&self, issue: i64) -> Option<PrRef>
pub fn open_pr_for_issue(&self, issue: i64) -> Option<PrRef>
An open pull request that would close this issue, whoever opened it.
spar’s own branch naming is checked first because it is exact and cheap. Falling back to GitHub’s own issue linkage is what lets spar pick up a pull request a person started on a branch named anything at all.
pub fn pr_view(&self, number: i64) -> Result<PrView>
pub fn pr_state(&self, number: i64) -> String
pub fn create_pr( &self, cwd: &Path, branch: &str, base: &str, title: &str, body: &str, ) -> Result<PrRef>
pub fn comment_pr(&self, number: i64, body: &str) -> Result<()>
pub fn comment_issue(&self, number: i64, body: &str) -> Result<()>
Sourcepub fn close_issue(&self, number: i64, body: &str) -> Result<()>
pub fn close_issue(&self, number: i64, body: &str) -> Result<()>
Comment, then close as not planned.
Only ever called when both agents independently declined the issue: one agent’s opinion is not enough to close somebody’s report.
pub fn create_issue(&self, title: &str, body: &str) -> Result<String>
Source§impl Repo
impl Repo
Sourcepub fn find_similar_issue(
&self,
title: &str,
body: &str,
) -> Option<ExistingIssue>
pub fn find_similar_issue( &self, title: &str, body: &str, ) -> Option<ExistingIssue>
An issue that already describes this defect, however it was worded.
Exact title matching let duplicates through: two agents, or two runs a week apart, never word one defect identically. A real run filed two duplicates that way, and each had to be closed by hand afterwards. Titles alone are too thin to match on, so this compares titles and bodies together.
Sourcepub fn find_issue_by_title(&self, title: &str) -> Option<String>
pub fn find_issue_by_title(&self, title: &str) -> Option<String>
Avoid filing a duplicate when a follow-up already exists.
Sourcepub fn merge_pr(&self, number: i64) -> Result<()>
pub fn merge_pr(&self, number: i64) -> Result<()>
Squash merge, tolerating cleanup failures after a successful merge.
gh pr merge --delete-branch exits non-zero when it cannot delete the
local branch, which happens after the merge has already landed.
Treating that as a failure reports work as lost when it is not.
Sourcepub fn append_local_followup(&self, title: &str, body: &str) -> Option<String>
pub fn append_local_followup(&self, title: &str, body: &str) -> Option<String>
Append a follow-up to a local note instead of the tracker.
Deduplicated on the title, matching the issue path. Returns a display string, or None when it was already recorded. The body arrives with its provenance already stamped by the caller, so nothing is added here.
pub fn state_path(&self, number: i64) -> PathBuf
pub fn read_state(&self, pr: &PrView) -> Option<PersistedState>
pub fn write_state(&self, number: i64, state: &PersistedState) -> Result<()>
Sourcepub fn clear_state(&self, number: i64)
pub fn clear_state(&self, number: i64)
Drop state once the PR is finished and there is nothing to resume.
Sourcepub fn prune_state(&self) -> Vec<String>
pub fn prune_state(&self) -> Vec<String>
Remove state files whose PR is merged or closed.
Sourcepub fn prune_pr_state(&self, numbers: Option<Vec<i64>>) -> Vec<String>
pub fn prune_pr_state(&self, numbers: Option<Vec<i64>>) -> Vec<String>
Delete state comments from PRs that are finished.
Open PRs are left alone: their state may still be live.
Sourcepub fn prune_worktrees(&self, force_all: bool) -> Vec<String>
pub fn prune_worktrees(&self, force_all: bool) -> Vec<String>
Drop worktrees whose PR is finished, then the branches they left behind.
With auto_merge off, which is the default, a run ends at “approved”, so
nothing would ever clean these up on its own and they accumulate one per
run. A stranded worktree also holds its branch checked out, which makes
a later gh pr merge --delete-branch fail to clean up.
Sourcepub fn prune_branches(&self, force_all: bool) -> Vec<String>
pub fn prune_branches(&self, force_all: bool) -> Vec<String>
Delete leftover branches spar created whose worktree is already gone.
Deletion is driven by the ledger of branches spar actually created, not
by a name pattern. Names default to issue-N, which is exactly what a
person would call a branch themselves, so a name alone can never
establish ownership. This is the data loss guard.