Replace repo_config() with mirror_url() directly on Repo
The RepoConfig/RepoGithubConfig intermediate structs only ever served
one consumer (mirror.rs) and one field (github.mirror). Replace them
with Repo::mirror_url(sha) -> AppResult<Option<String>> that reads the
config inline with private structs, keeping the public API minimal.

https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
change
commit 6b6cf4c27c047cdef0436cdf6ce3793d944a2c42
author Claude <noreply@anthropic.com>
date
parent 1a2e7061
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index e1b52f7..35302d7 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -31,8 +31,9 @@ pub fn trigger(quire: &Quire, event: &PushEvent) -> crate::Result<()> {
         .transpose()?;
 
     for push_ref in event.updated_refs() {
-        let repo_config = match repo.repo_config(&push_ref.new_sha) {
-            Ok(c) => c,
+        let mirror_url = match repo.mirror_url(&push_ref.new_sha) {
+            Ok(Some(url)) => url,
+            Ok(None) => continue,
             Err(e) => {
                 tracing::warn!(
                     ref_name = %push_ref.ref_name,
@@ -44,10 +45,6 @@ pub fn trigger(quire: &Quire, event: &PushEvent) -> crate::Result<()> {
             }
         };
 
-        let Some(mirror_url) = repo_config.github.mirror else {
-            continue;
-        };
-
         let Some(token) = mirror_token.as_deref() else {
             tracing::warn!(
                 ref_name = %push_ref.ref_name,
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index 0907f56..8aaba98 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -156,10 +156,21 @@ impl Repo {
         Ci::new(self.path())
     }
 
-    /// Read and parse `.quire/config.fnl` at the given commit SHA.
+    /// Read `github.mirror` URL from `.quire/config.fnl` at the given commit SHA.
     ///
-    /// Returns defaults if the file does not exist at that commit.
-    pub fn repo_config(&self, sha: &str) -> AppResult<RepoConfig> {
+    /// Returns `None` if unconfigured or the file does not exist at that commit.
+    pub fn mirror_url(&self, sha: &str) -> AppResult<Option<String>> {
+        #[derive(serde::Deserialize, Default)]
+        #[serde(default, rename_all = "kebab-case")]
+        struct Config {
+            github: Github,
+        }
+        #[derive(serde::Deserialize, Default)]
+        #[serde(default, rename_all = "kebab-case")]
+        struct Github {
+            mirror: Option<String>,
+        }
+
         let output = self
             .git(&["show", &format!("{sha}:.quire/config.fnl")])
             .stdout(std::process::Stdio::piped())
@@ -169,7 +180,7 @@ impl Repo {
         if !output.status.success() {
             let stderr = String::from_utf8_lossy(&output.stderr);
             if stderr.contains("does not exist") || stderr.contains("not found") {
-                return Ok(RepoConfig::default());
+                return Ok(None);
             }
             return Err(Error::Io(std::io::Error::other(format!(
                 "failed to read .quire/config.fnl at {sha}: {stderr}"
@@ -177,7 +188,8 @@ impl Repo {
         }
 
         let source = String::from_utf8(output.stdout)?;
-        Ok(Fennel::new()?.load_string(&source, ".quire/config.fnl")?)
+        let cfg: Config = Fennel::new()?.load_string(&source, ".quire/config.fnl")?;
+        Ok(cfg.github.mirror)
     }
 
     /// The base directory for CI runs (`runs/<repo>/`).
@@ -195,22 +207,6 @@ impl Repo {
     }
 }
 
-/// Per-repo CI configuration parsed from `.quire/config.fnl`.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct RepoConfig {
-    pub github: RepoGithubConfig,
-}
-
-/// Per-repo GitHub configuration.
-#[derive(serde::Deserialize, Debug, Default, Clone)]
-#[serde(default, rename_all = "kebab-case")]
-pub struct RepoGithubConfig {
-    /// Remote URL to mirror every pushed ref to.
-    /// E.g. `"https://github.com/user/repo.git"`.
-    pub mirror: Option<String>,
-}
-
 /// Application runtime context.
 ///
 /// Carries configuration and provides resolved paths to repositories.