constitution · epochs · watch · epoch 3

comparison

c_8dc1a8119370 (tommy-mor) vs c_fbeec5c4ad18 (tommy-mor)

download prompt · raw event · cmp_fad652158358fb

council reasoning

~anthropic/claude-sonnet-latest · winner B · 6:4 · permalink

Side B fixes a real structural bug in the ranking system (short-circuiting on single contributor instead of per-commit comparison), with corresponding test updates and UI surfacing, giving lasting improvement to the project's core fairness logic. Side A is a large but reasonable simplification/replacement of a fragile autocomplete parser with a simpler paste-and-go UX, which is valuable but more narrowly scoped and mostly deletion of complex, over-engineered code plus a UI feature swap.

~x-ai/grok-latest · winner B · 2:3 · permalink

B fixes core ownership mechanics: rank_commits no longer short-circuits on a single contributor, pairwise-ranks every eligible commit, rolls scores into contributor_ranking, and exposes per-commit rankings on epoch pages—with tests for the multi-commit same-author case. A’s paste-and-go rewrite correctly deletes an unreliable ~1.8k-line keystroke graph and race-prone client path, but that is a UI simplification of one feature, not a change to how lasting value is measured or paid out.

openai/gpt-chat-latest · winner B · 4:1 · permalink

Side B changes the core ranking algorithm from contributor-level to commit-level by pairwise-ranking every eligible commit, rolling scores back up to contributors, updating evidence records, UI pages, and tests to reflect commit rankings. Side A mostly replaces an interactive autocomplete/parser graph with a much simpler paste-and-go URL parser and redirect, deleting substantial functionality in favor of a narrower workflow, even though it simplifies the implementation.

sides

A — c_8dc1a8119370 (tommy-mor)

message

[529cc941] Replace autocomplete parser with paste-and-go navigate.

The keystroke transition graph was unreliable; a textarea plus Go button now parses pasted Reddit URLs and redirects to the subreddit ranking scope.

Co-authored-by: Cursor <cursoragent@cursor.com>

diff preview

diff --git a/AGENTS.md b/AGENTS.md
index 77f2e31d4e860a92a77255ca5106c8b6c4510ee7..36ee4c0ec700bffbe226ee775ba9cf59cf35c770 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,7 +11,6 @@ Single Rust web app **`sorter2-server`**: pairwise voting, rank-centrality ranki
 - **Rust 1.88+** is required (some transitive crates need a recent Cargo). The image may ship older `/usr/local/cargo` (1.83); use **rustup** and `rustup default 1.88.0` before building.
 - **System packages** for builds: `pkg-config`, `libssl-dev` (for `reqwest` / OpenSSL in integration tests and release builds).
 - **Clojure CLI 1.12.0.1530** (optional but used in CI): install from https://clojure.org/guides/install_clojure — needed for `./scripts/clj-test.sh` / Kaocha tests.
-- **Playwright browser** for the spel browser test (`test/parser_race.clj`): install once with `clojure -M -e "(com.microsoft.playwright.CLI/main (into-array String [\"install\" \"chromium\" \"--with-deps\"]))"`. The browser binary is cached under `~/.cache/ms-playwright`.
 
 ### Commands (see also `TEST.sh`)
 
diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index 1339048c955c0a3eb5120381aaf031424cbed540..c4ab9d65c7b3cd42a5b4d093ba429993c101e9a8 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -8,7 +8,7 @@ use std::collections::HashMap;
 use crate::{
     html::{js_string_literal, ranking_panel, JsBuilder},
     parser::parse_reddit_url,
-    parser_render::parser_panel_morph,
+    parser_render::navigate_panel,
     state::AppState,
     ui_action::{parse_html_ui_from_form, HtmlUiAction},
 };
@@ -57,18 +57,23 @@ pub async fn post_ui_html(
                 .morph_selector("#ranking-panel", panel)
                 .into_response()
         }
-        HtmlUiAction::ParseQuery { query } => {
-            let action = parse_reddit_url(&query);
-            let panel = parser_panel_morph(&query, &action);
-            let mut js = JsBuilder::new().morph_selector("#parser-panel", panel);
-            if let Some(comp) = action.primary_completion() {
-                js = js.raw(&format!(
-                    "var __pi=document.getElementById('parser-input'); if(__pi){{__pi.dataset.completion={};}}",
-                    js_string_literal(comp)
-                ));
+        HtmlUiAction::ParseQuery { query } => match parse_reddit_url(&query) {
+            Ok(subreddit) => {
+                let dest = format!("/?sub={subreddit}");
+                JsBuilder::new()
+                    .raw(&format!(
+                        "window.location.href={};",
+                        js_string_literal(&dest)
+                    ))
+                    .into_response()
             }
-            js.into_response()
-        }
+            Err(message) => {
+                let panel = navigate_panel(&query, Some(&message));
+                JsBuilder::new()
+                    .morph_selector("#parser-panel", panel)
+                    .into_response()
+            }
+        },
     }
 }
 
diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index 94a5cfb561d1c8464bd2782f7ffd4e0965ccf95d..9650d333d29c4ac94ceb407aee3ee00399c7f40b 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -11,8 +11,7 @@ use serde::Deserialize;
 
 use crate::{
     form_template::template_json_compact,
-    parser_action::ParserAction,
-    parser_render::parser_panel,
+    parser_render::navigate_panel,
     ranking::{top_bottom, RankedItem},
     reducer::GroupState,
     state::{normalize_scope, AppState},
@@ -336,10 +335,9 @@ pub async fn home(
     let empty = GroupState::new();
     let group = groups.get(&scope).unwrap_or(&empty);
 
-    let empty_action = ParserAction::suggest(String::new(), None);
     let body = html! {
         h1 { "sorter2" }
-        (parser_panel("", &empty_action))
+        (navigate_panel("", None))
         (vote_panel(&scope))
         (ranking_panel(&scope, group))
     };
diff --git a/server/src/lib.rs b/server/src/lib.rs
index fa423640d598f4ba97a5885d228e78d7b97f7a22..de8bca48cbf689cad22337883e7791966e7c4919 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -4,7 +4,6 @@ pub mod events;
 pub mod form_template;
 pub mod html;
 pub mod parser;
-pub mod parser_action;
 pub mod parser_render;
 pub mod path_types;
 pub mod ranking;
diff --git a/server/src/parser.rs b/server/src/parser.rs
index ea437c4434045b44cba04a2b3416df850db518e7..50571a59f0d3ece1e2538f88e00ef46ec40ea545 100644
--- a/server/src/parser.rs
+++ b/server/src/parser.rs
@@ -1,1811 +1,87 @@
-use std::collections::HashMap;
-use std::sync::OnceLock;
+//! Extract a subreddit name from a pasted Reddit URL or path.
 
-use crate::parser_action::{GuideOption, ParserAction, ScrollingSuggestion, Suggestion};
-
-// --- Core Abstractions ---
-
-/// Unique identifier for nodes in the graph
-type NodeId = &'static str;
-
-/// Pattern matching for edges
-#[derive(Debug, Clone)]
-pub enum EdgePattern {
-    /// Matches exact literal string
-    Literal(&'static str),
-    
-    /// Matches any prefix of a string and suggests the full string
-    /// e.g., PrefixOf("reddit.com") matches "r", "re", "red", "reddit", "reddit.com"
-    PrefixOf(&'static str),
-    
-    /// Captures a variable segment (e.g., subreddit name, username)
-    Variable(&'static str),
-    
-    /// Matches any string (wildcard)
-    Any,
-}
-
-impl EdgePattern {
-    /// Try to match this pattern against input, return (consumed_chars, captured_value)
-    fn matches(&self, input: &str) -> Option<(usize, Option<String>)> {
-        match self {
-            EdgePattern::Literal(lit) => {
-                if input.starts_with(lit) {
-                    Some((lit.len(), None))
-                } else {
-                    None
-                }
-            }
-            EdgePattern::PrefixOf(target) => {
-                // Check if input is a prefix of target
-                if target.starts_with(input) && !input.is_empty() {
-                    // It's a valid prefix
-                    Some((input.len(), None))
-                } else if input.starts_with(target) {
-                    // Full match
-                    Some((target.len(), None))
-                } else {
-                    None
-                }
-            }
-            EdgePattern::Variable(var_name) => {
-                // Consume until next '/' or end of string
-                let end = input.find('/').unwrap_or(input.len());
-                if end > 0 {
-                    let captured = input[..end].to_string();
-                    // Validate based on variable type
-                    if is_valid_variable(var_name, &captured) {
-                        Some((end, Some(captured)))
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            }
-            EdgePattern::Any => {
-                // Match everything until next '/' or end
-                let end = input.find('/').unwrap_or(input.len());
-                if end > 0 {
-                    Some((end, Some(input[..end].to_string())))
-                } else {
-                    None
-                }
-            }
-        }
-    }
-    
-    /// Get the completion suggestion for this pattern
-    fn completion(&self, partial: &str) -> Option<String> {
-        match self {
-            EdgePattern::PrefixOf(target) => {
-                if target.starts_with(partial) && partial != *target {
-                    Some(target.to_string())
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
+pub fn parse_reddit_url(query: &str) -> Result<String, String> {
+    let q = query.trim();
+    if q.is_empty() {
+        return Err("Paste a Reddit URL or r/subreddit path".into());
     }
-}
 
-/// Edge in the graph
-pub struct Edge {
-    pattern: EdgePattern,
-    target: NodeId,
-    /// Optional description for autocomplete
-    description: Option<&'static str>,
-}
-
-/// Handler function for generating UI actions (Send + Sync so the graph can live in `OnceLock`).
-type Handler = Box<dyn Fn(&str, &str, &HashMap<String, String>) -> ParserAction + Send + Sync>;
-
-/// Node in the graph
-pub struct Node {
-    #[allow(dead_code)]
-    id: NodeId,
-    edges: Vec<Edge>,
-    handler: Option<Handler>,
-}
-
-/// The composable parser graph (immutable after `build`).
-pub struct Graph {
-    nodes: HashMap<NodeId, Node>,
-    root: NodeId,
-}
-
-// --- Graph Builder (Fluent API) ---
-
-pub struct GraphBuilder {
-    nodes: HashMap<NodeId, Node>,
-    current_node: Option<NodeId>,
-    root: NodeId,
-}
-
-impl GraphBuilder {
-    pub fn new() -> Self {
-        let mut nodes = HashMap::new();
-        nodes.insert(
-            "root",
-            Node {
-                id: "root",
-                edges: Vec::new(),
-                handler: None,
-            },
-        );
-
-        GraphBuilder {
-            nodes,
-            current_node: Some("root"),
-            root: "root",
-        }
+    if let Some(sub) = subreddit_after_prefix(q, "r/") {
+        return Ok(sub);
     }
 
-    /// Select a node to add edges to
-    pub fn at(mut self, node_id: NodeId) -> Self {
-        self.nodes.entry(node_id).or_insert_with(|| Node {
-            id: node_id,
-            edges: Vec::new(),
-            handler: None,
-        });
-        self.current_node = Some(node_id);
-        self
+    if let Some(sub) = subreddit_from_path_segment(q, "/r/") {
+        return Ok(sub);
     }
-    
-    /// Add an edge from the current node
-    pub fn edge(self, pattern: EdgePattern, target: NodeId) -> Self {
-        self.edge_with_desc(pattern, target, None)
-    }
-    
-    /// Add an edge with description
-    pub fn edge_with_desc(
-        mut self,
-        pattern: EdgePattern,
-        target: NodeId,
-        desc: Option<&'static str>,
-    ) -> Self {
-        let current = self.current_node.expect("No current node selected");
-
-        self.nodes.entry(target).or_insert_with(|| Node {
-            id: target,
-            edges: Vec::new(),
-            handler: None,
-        });
-
-        if let Some(node) = self.nodes.get_mut(current) {
-            node.edges.push(Edge {
-                pattern,
-                target,
-                description: desc,
-            });
-        }
 
-        self
-    }
-
-    /// Set handler for current node
-    pub fn handler<F>(mut self, handler: F) -> Self
-    where
-        F: Fn(&str, &str, &HashMap<String, String>) -> ParserAction + Send + Sync + 'static,
-    {
-        let current = self.current_node.expect("No current node selected");
-        if let Some(node) = self.nodes.get_mut(current) {
-            node.handler = Some(Box::new(handler));
-        }
-        self
-    }
-    
-    /// Build the final graph
-    pub fn build(self) -> Graph {
-        Graph {
-            nodes: self.nodes,
-            root: self.root,
-        }
-    }
+    Err("Could not find a subreddit in that URL".into())
 }
 
-// --- Parser Implementation ---
-
-impl Graph {
-    pub fn parse(&self, input: &str) -> ParserAction {
-        let normalized = input.trim().to_lowercase();
-        let mut state = ParserState {
-            input: &normalized,
-            cursor: 0,
-            current_node_id: self.root,
-            context: HashMap::new(),
-            original_query: input.to_string(),
-            current_prefix: String::new(),
-        };
-        
-        self.parse_recursive(&mut state)
-    }
-    
-    fn parse_recursive(&self, state: &mut ParserState) -> ParserAction {
-        let node = self
-            .nodes
-            .get(state.current_node_id)
-            .expect("Node not found in graph");
-
-        // If we've consumed all input, check for handler or suggestions
-        if state.cursor >= state.input.len() {
-            if let Some(handler) = &node.han

… preview truncated; 92,533 characters omitted

download full diff A

B — c_fbeec5c4ad18 (tommy-mor)

message

[c7ef287e] Rank every eligible commit with the LLM council.

Stop short-circuiting on a single contributor; pairwise-sort commits, roll scores up for emission payouts, and surface commit rankings on epoch pages.

Co-authored-by: Cursor <cursoragent@cursor.com>

diff preview

diff --git a/constitution.py b/constitution.py
index 26ba130e885e0e69fb7874ca5c3f07f42100a150..71fc46b4c9a4d7a9ea7bf319860b0db1acc4a673 100644
--- a/constitution.py
+++ b/constitution.py
@@ -503,20 +503,22 @@ def _epochs_in_ledger() -> list[int]:
 
 
 def build_pairwise_prompt(side_a: dict, side_b: dict) -> str:
-    return f"""You are ranking contributions to an open source project.
-Compare these two sides (each may be one or more commits). Decide which side contributed more.
+    return f"""You are ranking individual git commits to an open source project.
+Compare these two commits. Decide which commit contributed more.
 Return ONLY a JSON object: {{"winner": "A" or "B", "ratio": "N:M", "explanation": "..."}}
 
-Side A — commit messages:
+Side A — contributor: {side_a.get('contributor', '?')}
+Side A — commit message:
 {side_a['message']}
 
-Side A — unified diffs (full patches):
+Side A — unified diff (full patch):
 {side_a['diff']}
 
-Side B — commit messages:
+Side B — contributor: {side_b.get('contributor', '?')}
+Side B — commit message:
 {side_b['message']}
 
-Side B — unified diffs (full patches):
+Side B — unified diff (full patch):
 {side_b['diff']}"""
 
 
@@ -1518,16 +1520,28 @@ async def broadcast_js(js: str):
         await queue.put(js)
 
 
-def _author_side_for_llm(author: str, author_commits: dict) -> dict:
-    cs = author_commits[author]
+def _commit_side_for_llm(row: dict) -> dict:
+    oid = row["oid"]
+    short = oid.split(":", 1)[1][:8] if ":" in oid else oid[:8]
     return {
-        "message": "\n".join(f"[{c['sha']}] {c['message']}" for c in cs),
-        "diff": "\n\n".join(f"=== {c['sha']} ===\n{c['diff']}" for c in cs),
-        "commit_ids": [c["commit_id"] for c in cs],
-        "contributor": author,
+        "message": f"[{short}] {row['message']}",
+        "diff": row["patch"] or "",
+        "commit_id": commit_id_for_oid(oid),
+        "contributor": row["contributor"],
+        "oid": oid,
     }
 
 
+def _rollup_contributor_scores(
+    ordered: list[dict], commit_scores: list[Decimal]
+) -> dict[str, Decimal]:
+    totals: dict[str, Decimal] = {}
+    for row, score in zip(ordered, commit_scores):
+        contributor = row["contributor"]
+        totals[contributor] = totals.get(contributor, Decimal("0")) + score
+    return totals
+
+
 def _find_judgment(comparison_id: str, model_id: str) -> dict | None:
     for e in evidence_by_kind("llm.judgment"):
         p = e.payload
@@ -1546,101 +1560,106 @@ def _find_ranking_models(ranking_run_id: str) -> list[str] | None:
 
 
 async def rank_commits(commits: list[dict], *, epoch: int = -1):
+    """Pairwise-rank every eligible commit; roll scores up to contributors."""
     if not commits:
         return {}, [], {"ranking_run_id": "", "ranking_event_id": ""}
 
-    commit_ids = sorted(commit_id_for_oid(row["oid"]) for row in commits)
+    ordered = sorted(commits, key=lambda r: r["oid"])
+    commit_ids = [commit_id_for_oid(row["oid"]) for row in ordered]
     ranking_run_id = _content_id("rank", {
         "epoch": epoch,
-        "commit_ids": commit_ids,
+        "commit_ids": sorted(commit_ids),
     })
-    contributors = sorted(set(c["contributor"] for c in commits))
+    contributors = sorted({c["contributor"] for c in ordered})
 
-    if len(contributors) == 1:
+    # Nothing to compare: a single commit (not a single contributor).
+    if len(ordered) == 1:
         await append_evidence(epoch, "ranking.started", {
             "ranking_run_id": ranking_run_id,
             "commit_ids": commit_ids,
             "contributors": contributors,
             "models": [],
-            "summary": f"ranking epoch {epoch}: single contributor",
+            "summary": f"ranking epoch {epoch}: single commit",
         })
-        ranking = {contributors[0]: Decimal("1")}
+        commit_ranking = {commit_ids[0]: "1"}
+        contributor_ranking = {ordered[0]["contributor"]: Decimal("1")}
         completed = await append_evidence(epoch, "ranking.completed", {
             "ranking_run_id": ranking_run_id,
             "models": [],
-            "ranking": {contributors[0]: "1"},
+            "commit_ranking": commit_ranking,
+            "contributor_ranking": {ordered[0]["contributor"]: "1"},
+            "ranking": {ordered[0]["contributor"]: "1"},
             "judgment_ids": [],
-            "summary": f"Only {contributors[0]} is eligible; rank is 1.0",
+            "summary": f"Only one eligible commit; {ordered[0]['contributor']} rank 1.0",
         })
         await broadcast_audit(
             "ranking",
-            f"Only {contributors[0]} is eligible; rank is 1.0",
+            f"Only one eligible commit; {ordered[0]['contributor']} rank 1.0",
             progress=90,
             phase="finalizing",
             evidence_event_id=completed.event_id,
             evidence_url=_evidence_url("event", completed.event_id),
             links={"epoch": _evidence_url("epoch", str(epoch))},
         )
-        return ranking, [], {
+        return contributor_ranking, [], {
             "ranking_run_id": ranking_run_id,
             "ranking_event_id": completed.event_id,
         }
 
     if not (OPENROUTER_API_KEY or "").strip():
         raise RuntimeError(
-            "OPENROUTER_API_KEY is required when multiple contributors need ranking"
+            "OPENROUTER_API_KEY is required when multiple commits need ranking"
         )
 
     models = _find_ranking_models(ranking_run_id)
     if models is None:
         models = await fetch_top_models(n=3)
     if not models:
-        raise RuntimeError("no council models available for contributor ranking")
+        raise RuntimeError("no council models available for commit ranking")
     await append_evidence(epoch, "ranking.started", {
         "ranking_run_id": ranking_run_id,
         "commit_ids": commit_ids,
         "contributors": contributors,
         "models": models,
-        "summary": f"Council selected: {', '.join(models)}",
+        "summary": (
+            f"Council selected: {', '.join(models)} — "
+            f"{len(ordered)} commits"
+        ),
     })
     await broadcast_audit(
         "council",
-        f"Council selected: {', '.join(models)}",
+        f"Council selected: {', '.join(models)} — ranking {len(ordered)} commits",
         progress=35,
         phase="ranking",
     )
     await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
-        ["div.log-council", f"Council: {', '.join(models)} — {len(commits)} commits"]
+        ["div.log-council",
+            f"Council: {', '.join(models)} — {len(ordered)} commits"]
     ]))
 
-    authors = contributors
-    author_commits = {a: [] for a in authors}
-    for row in sorted(commits, key=lambda r: r["oid"]):
-        author_commits[row["contributor"]].append({
-            "message": row["message"],
-            "sha": row["oid"].split(":", 1)[1][:8],
-            "diff": row["patch"],
-            "commit_id": commit_id_for_oid(row["oid"]),
-        })
-
+    sides = [_commit_side_for_llm(row) for row in ordered]
     judgment_ids: list[str] = []
 
     async def compare_fn(i, j):
-        a1, a2 = authors[i], authors[j]
-        side_a = _author_side_for_llm(a1, author_commits)
-        side_b = _author_side_for_llm(a2, author_commits)
+        side_a, side_b = sides[i], sides[j]
+        label_a = f"{side_a['commit_id'][:16]} ({side_a['contributor']})"
+        label_b = f"{side_b['commit_id'][:16]} ({side_b['contributor']})"
         prompt = build_pairwise_prompt(side_a, side_b)
         comparison_material = {
             "ranking_run_id": ranking_run_id,
             "side_a": {
-                "contributor": a1,
-                "commit_ids": side_a["commit_ids"],
+                "contributor": side_a["contributor"],
+                "commit_id": side_a["commit_id"],
+                "commit_ids": [side_a["commit_id"]],
+                "oid": side_a["oid"],
                 "message": _bytes_blob(side_a["message"]),
                 "diff": _bytes_blob(side_a["diff"]),
             },
             "side_b": {
-                "contributor": a2,
-                "commit_ids": side_b["commit_ids"],
+                "contributor": side_b["contributor"],
+                "commit_id": side_b["commit_id"],
+                "commit_ids": [side_b["commit_id"]],
+                "oid": side_b["oid"],
                 "message": _bytes_blob(side_b["message"]),
                 "diff": _bytes_blob(side_b["diff"]),
             },
@@ -1650,22 +1669,24 @@ async def rank_commits(commits: list[dict], *, epoch: int = -1):
         comparison_material = {
             **comparison_material,
             "comparison_id": comparison_id,
-            "summary": f"Comparing {a1} with {a2}",
+            "summary": f"Comparing {label_a} with {label_b}",
         }
         cmp_ev = await append_evidence(epoch, "comparison.input", comparison_material)
         await broadcast_audit(
             "comparison",
-            f"Comparing {a1} with {a2}",
+            f"Comparing commits {label_a} vs {label_b}",
             phase="ranking",
             evidence_event_id=cmp_ev.event_id,
             evidence_url=_evidence_url("comparison", comparison_id),
             links={
                 "comparison": _evidence_url("comparison", comparison_id),
+                "commit_a": _evidence_url("commit", side_a["commit_id"]),
+                "commit_b": _evidence_url("commit", side_b["commit_id"]),
                 "epoch": _evidence_url("epoch", str(epoch)),
             },
         )
         await broadcast_js(exec_event(Three[Selector("#emission-status")][MORPH][
-            ["div#emission-status", f"Comparing {a1} vs {a2}…"]
+            ["div#emission-status", f"Comparing {label_a} vs {label_b}…"]
         ]))
         results = []
         for model in models:
@@ -1697,9 +1718,15 @@ async def rank_commits(commits: list[dict], *, epoch: int = -1):
                 jud_id = (existing or _find_judgment(comparison_id, model) or {}).get(
                     "judgment_id"
                 )
+                win_label = (
+                    f"{sides[w]['commit_id'][:16]} ({sides[w]['contributor']})"
+                )
+                lose_label = (
+                    f"{sides[l]['commit_id'][:16]} ({sides[l]['contributor']})"
+                )
                 await broadcast_audit(
                     "vote",
-                    f"{model}: {authors[w]} over {authors[l]} ({result['ratio']})",
+                    f"{model}: {win_label} over {lose_label} ({result['ratio']})",
                     phase="ranking",
                     evidence_url=(
                         _evidence_url("judgment", jud_id) if jud_id else None
@@ -1714,8 +1741,8 @@ async def rank_commits(commits: list[dict], *, epoch: int = -1):
                 await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
                     ["div.log-vote",
                         ["span.model", model], " — ",
-                        ["span.winner", authors[w]], f" beat ",
-                        ["span.loser", authors[l]], f" ({result['ratio']}) ",
+                        ["span.winner", win_label], f" beat ",
+                        ["span.loser", lose_label], f" ({result['ratio']}) ",
                         ["span.explanation", result["explanation"]],
                     ]
                 ]))
@@ -1745,24 +1772,46 @@ async def rank_commits(commits: list[dict], *, epoch: int = -1):
             ["div#emission-status", label]
         ]))
 
-    pairs = await pairwise_rank(len(authors), compare_fn, progress_fn)
+    pairs = await pairwise_rank(len(ordered), compare_fn, progress_fn)
 
     if not pairs:
-        ranking = {authors[0]: Decimal("1")} if authors else {}
+        commit_score_list = [Decimal("1")]
     else:
         scores = rank_centrality(pairs)
-        ranking = {authors[i]: Decimal(str(scores[i])) for i in range(len(authors))}


… preview truncated; 12,453 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.