Commit A introduces a large amount of foundational functionality across the project: new UI plumbing, voting pages, ranking and reducer logic, parser infrastructure with extensive tests, form templating, UI actions, scripts, and seed/design files. Although parts of it appear to include accidental terminal captures and draft material, it establishes major subsystems that the project builds on. Commit B is a high-quality, substantial feature that replaces GitHub API polling with deterministic multi-repository Git discovery, adds replayable attribution, locking, extensive real-Git and property-based tests, and improves emission logic. It is a deeper improvement to one important area, but its scope is narrower than the broad foundational code introduced in A.
constitution · epochs · watch · epoch 3
c_f6d0fed9bf9a (tommy-mor) vs c_45659f04aa7c (tommy-mor)
download prompt · raw event · cmp_8d78e15ad3c4cf
council reasoning
Commit A bootstraps a substantial portion of the project: core parser architecture (~1800 lines), ranking algorithms, reducer state management, UI actions, frontend JS plumbing, and extensive tests. It establishes foundational systems across multiple layers (backend logic, UI interaction, testing). Commit B introduces a sophisticated and well-engineered git discovery and attribution system with strong testing, but it is a focused feature within an existing system. While complex and impactful, it is narrower in scope compared to the broad foundational groundwork laid in A.
Side A is effectively a foundational project seed: it introduces large core modules (parser ~1800 LOC, reducer ~800+ LOC, ranking ~400 LOC, vote UI, form handling, UI actions), extensive tests, scripts, and multiple new subsystems. It establishes the primary application architecture. Side B is a substantial and sophisticated feature addition (multi-repository git discovery, emission integration, and extensive tests), but it builds within an existing system and is narrower in scope. In terms of total code volume, architectural surface area, and baseline functionality introduced, Side A contributes significantly more.
sides
A — c_f6d0fed9bf9a (tommy-mor)
message
[1d9d8ade] init seed
diff preview
diff --git a/TEST.sh b/TEST.sh
new file mode 100755
index 0000000000000000000000000000000000000000..266b71f29259f5c2cad2617476ebe2ebc9596d39
--- /dev/null
+++ b/TEST.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -euo pipefail
+cargo test --all
+./scripts/clj-test.sh
diff --git a/bundle.js b/bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d316f9a57cc7c379fe4bd8e42e092c7eac5d265
--- /dev/null
+++ b/bundle.js
@@ -0,0 +1,52 @@
+/**
+ * Slug web UI: only plumbing — fetch/eval/SSE. No product UI logic here.
+ */
+(function () {
+ function evalJs(js) {
+ if (js && String(js).trim()) {
+ eval(js);
+ }
+ }
+
+ // Theme cookie sync (runs before paint; full reload if localStorage disagrees with cookie)
+
+ function initSlugUi() {
+ // POST forms → eval response (except theme + full-navigation forms)
+ document.addEventListener('submit', async function (e) {
+ var f = e.target;
+ if (!f || f.tagName !== 'FORM') return;
+ if ((f.method || 'get').toLowerCase() !== 'post') return;
+ if (f.id === 'slug-theme-form') return;
+ if (f.getAttribute('data-navigate') === 'full') return;
+ e.preventDefault();
+ var resp = await fetch(f.action, {
+ method: 'POST',
+ body: new URLSearchParams(new FormData(f)),
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ credentials: 'same-origin',
+ });
+ evalJs(await resp.text());
+ });
+
+ // SSE: server-pushed JS
+ function connectSSE() {
+ var ssePath = window.location.pathname + window.location.search;
+ var es = new EventSource('/sse?path=' + encodeURIComponent(ssePath));
+ es.onmessage = function (e) {
+ evalJs(e.data);
+ };
+ es.onerror = function () {
+ es.close();
+ setTimeout(connectSSE, 3000);
+ };
+ }
+ connectSSE();
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initSlugUi);
+ } else {
+ initSlugUi();
+ }
+})();
+
diff --git a/clj-test.sh b/clj-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b62a49b98ed60a65a46807b7ad80fa3142f14952
--- /dev/null
+++ b/clj-test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -euo pipefail
+cd "$(dirname "$0")/.."
+mkdir -p target
+exec clojure -M:kaocha
diff --git a/forms.rs b/forms.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d509fd889fde3fed2662ce0a39006b7a51ae762a
--- /dev/null
+++ b/forms.rs
@@ -0,0 +1,145 @@
+tommy@Tommys-Laptop:~/programming/slug-star/slug|main ⇒ cat server/src/form_template.rs
+//! Plan2-style JSON templates with `{"$form": "field_name"}` holes, filled from
+//! `application/x-www-form-urlencoded` (or any `String` → `String` map) **before**
+//! deserializing into a typed struct.
+//!
+//! # Wire format
+//!
+//! Templates are **compact JSON** (`serde_json::to_string`): one line, no pretty
+//! printing, strings escaped per JSON rules (`\"`, `\n`, etc.). Embed that string
+//! in HTML attributes or text nodes with normal HTML escaping (e.g. maud), not
+//! bespoke encodings.
+//!
+//! # Power vs flat hidden fields
+//!
+//! A form is always a string→string map. You can fake depth with dotted keys (`a.b.c`),
+//! but one structured blob (`__rpc__` = compact JSON) gives you nested objects,
+//! arrays, and optional fields without inventing a new naming scheme each time.
+//!
+//! # Security
+//!
+//! Substitution runs **before** `serde` into your command type. It does not fix
+//! authorization: if the client can replace the hidden `__rpc__` value, they can
+//! change the command shape unless you validate (signed blob, server-side session
+//! context, or treat the blob as hints only). Same threat model as any hidden field.
+
+use serde::Serialize;
+use serde_json::Value;
+use std::collections::HashMap;
+
+/// Serialize a value to compact JSON for a hidden `__rpc__` (or similar) field.
+pub fn template_json_compact<T: Serialize>(v: &T) -> serde_json::Result<String> {
+ serde_json::to_string(v)
+}
+
+/// Recursively walk the JSON AST and replace `{"$form": "key"}` with the submitted
+/// string for `key` (empty if missing). Other keys are unchanged.
+pub fn substitute_form_vars(val: &mut Value, form_data: &HashMap<String, String>) {
+ match val {
+ Value::Object(map) => {
+ if map.len() == 1 {
+ if let Some(Value::String(field_name)) = map.get("$form") {
+ let submitted = form_data
+ .get(field_name.as_str())
+ .map(|s| s.as_str())
+ .unwrap_or("");
+ *val = Value::String(submitted.to_string());
+ return;
+ }
+ }
+ for v in map.values_mut() {
+ substitute_form_vars(v, form_data);
+ }
+ }
+ Value::Array(arr) => {
+ for v in arr.iter_mut() {
+ substitute_form_vars(v, form_data);
+ }
+ }
+ _ => {}
+ }
+}
+
+/// Parse JSON, apply [`substitute_form_vars`], return the mutated value.
+pub fn fill_template_from_form(
+ template_json: &str,
+ form_data: &HashMap<String, String>,
+) -> Result<Value, serde_json::Error> {
+ let mut v: Value = serde_json::from_str(template_json)?;
+ substitute_form_vars(&mut v, form_data);
+ Ok(v)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde::Deserialize;
+
+ #[derive(Debug, Deserialize, PartialEq, Eq)]
+ struct Demo {
+ room: String,
+ thread_tag: String,
+ nested: Nested,
+ }
+
+ #[derive(Debug, Deserialize, PartialEq, Eq)]
+ struct Nested {
+ text: String,
+ }
+
+ #[test]
+ fn holes_become_strings() {
+ let json = r#"{
+ "room": "public",
+ "thread_tag": {"$form": "tag"},
+ "nested": {"text": {"$form": "body"}}
+ }"#;
+ let mut form = HashMap::new();
+ form.insert("tag".into(), "foo".into());
+ form.insert("body".into(), "hello\nworld".into());
+
+ let v = fill_template_from_form(json, &form).unwrap();
+ let d: Demo = serde_json::from_value(v).unwrap();
+ assert_eq!(
+ d,
+ Demo {
+ room: "public".into(),
+ thread_tag: "foo".into(),
+ nested: Nested {
+ text: "hello\nworld".into(),
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn missing_form_key_is_empty_string() {
+ let json = r#"{"x": {"$form": "nope"}}"#;
+ let mut form = HashMap::new();
+ form.insert("other".into(), "y".into());
+ let v = fill_template_from_form(json, &form).unwrap();
+ assert_eq!(v["x"], "");
+ }
+
+ #[test]
+ fn array_of_holes() {
+ let json = r#"{"items": [{"$form": "a"}, {"$form": "b"}]}"#;
+ let mut form = HashMap::new();
+ form.insert("a".into(), "1".into());
+ form.insert("b".into(), "2".into());
+ let v = fill_template_from_form(json, &form).unwrap();
+ assert_eq!(v["items"], serde_json::json!(["1", "2"]));
+ }
+
+ #[test]
+ fn template_json_compact_escapes_and_single_line() {
+ let s = template_json_compact(&serde_json::json!({
+ "x": "quote\"and\nnewline"
+ }))
+ .unwrap();
+ assert!(!s.contains('\n'));
+ assert!(s.contains("\\\"") || s.contains("\\n"));
+ }
+}
+tommy@Tommys-Laptop:~/programming/slug-star/slug|main ⇒
+
diff --git a/gameifying.tdsl b/gameifying.tdsl
new file mode 100644
index 0000000000000000000000000000000000000000..93d2a61d94fcba4370c82d9612e6bb0f0318cc15
--- /dev/null
+++ b/gameifying.tdsl
@@ -0,0 +1,2 @@
+consider gating certain views (top all time?) by a 10 day usage streak.. or something like that.
+or like a path, on homepage(?), that shows day 1: r/amitheasshole, day2: r/aww, day3: gaming, or something like that. progressive revelation + usage incentive.
diff --git a/pagerank_streaming.tdsl b/pagerank_streaming.tdsl
new file mode 100644
index 0000000000000000000000000000000000000000..0f2231b94936c17610adf653ca26b0faa6f2ac3a
--- /dev/null
+++ b/pagerank_streaming.tdsl
@@ -0,0 +1,11 @@
+eventually i want to have ranking histories.
+like "visionary" and "fraud" waxing and waning in a uplot graph over time for #elon-musk.
+there are too many query combinations to precomupte the ranking histories
+(arbitrary user filters, and tag overlap combinations maybe),
+so we're just going to have to calculate them all on demand.
+computers are fast, its okay. for each vote, we need to calculate rank centrality again.
+i was thinking, for n votes we calculate the rank centrality,
+and get node weights. we then output that data to the client (over websocket, or testable barrier).
+then we calculate n+1 votes, _but we keep the node weights in memory_
+so the rank centrality process converges faster.
+this also has the side effect of making the ranking stream in satisfyingly as you load the page.
diff --git a/parser.rs b/parser.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2b87b974f8d1dd93bee35681d87668a94e4ef349
--- /dev/null
+++ b/parser.rs
@@ -0,0 +1,1808 @@
+use std::collections::HashMap;
+use std::rc::Rc;
+use std::cell::RefCell;
+use crate::ui::action::UIAction;
+use crate::ui::types::{Suggestion, GuideOption, ScrollingSuggestion};
+
+// --- 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
+ }
+ }
+ }
+ }
+
+ /// G
… preview truncated; 148,978 characters omittedB — c_45659f04aa7c (tommy-mor)
message
[1efb7222] multi-repository git discovery Make contribution discovery deterministic across repository and branch DAGs, with replayable attribution and adversarial coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/constitution.py b/constitution.py
index b535faad658c553b3b9046e2401333618e212150..4bee9f83663ab7fb36db95129b92b64b4ef57258 100644
--- a/constitution.py
+++ b/constitution.py
@@ -6,7 +6,7 @@
# "uvicorn",
# "httpx",
# "tenacity",
-# "evaleval>=0.2.6",
+# "evaleval==0.2.7",
# "authlib",
# "itsdangerous",
# "starlette",
@@ -29,7 +29,7 @@ from datetime import datetime, timezone
from fastapi import FastAPI, Request, Response
from fastapi.responses import PlainTextResponse, HTMLResponse
from starlette.middleware.sessions import SessionMiddleware
-import json, time, os, asyncio, httpx, pathlib
+import json, time, os, asyncio, httpx, pathlib, subprocess, hashlib, re, fcntl
import sympy as sp # type: ignore[reportMissingImports]
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
from evaleval import (
@@ -118,11 +118,35 @@ JSONL_PATH = pathlib.Path(os.environ.get("JSONL_PATH", "/data/ledger.jsonl"))
GITHUB_CLIENT_ID = os.environ.get("GITHUB_CLIENT_ID", "")
GITHUB_CLIENT_SECRET = os.environ.get("GITHUB_CLIENT_SECRET", "")
-REPO = os.environ.get("REPO", "tommy-mor/slug")
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
OPENROUTER_BASE_URL = os.environ.get("OPENROUTER_BASE_URL", "https://openrouter.ai").rstrip("/")
-GITHUB_API_BASE_URL = os.environ.get("GITHUB_API_BASE_URL", "https://api.github.com").rstrip("/")
+
+# Repositories, branches, and contributor identities are constitutional inputs.
+# A ref pattern matches a complete Git ref: * stays within one path component,
+# while ** crosses slashes, so refs/heads/** includes branches on branches.
+# Environment overrides exist for deterministic integration tests and deployments
+# using the exact same source; their normalized values are committed to every
+# discovery event.
+DEFAULT_REPOSITORIES = [
+ {
+ "id": "slug",
+ "url": "https://github.com/tommy-mor/slug.git",
+ "refs": ["refs/heads/**"],
+ },
+]
+DEFAULT_CONTRIBUTORS = {
+ "tommy-mor": ["thmorriss@gmail.com"],
+}
+
+REPOSITORIES = json.loads(
+ os.environ.get("REPOSITORIES_JSON", json.dumps(DEFAULT_REPOSITORIES))
+)
+CONTRIBUTORS = json.loads(
+ os.environ.get("CONTRIBUTORS_JSON", json.dumps(DEFAULT_CONTRIBUTORS))
+)
+GIT_MIRROR_DIR = pathlib.Path(os.environ.get("GIT_MIRROR_DIR", "/data/git"))
+GIT_TIMEOUT_SECONDS = int(os.environ.get("GIT_TIMEOUT_SECONDS", "120"))
# Council model IDs: slug.social garden rank under this parent (bodies = OpenRouter URLs), then top-up from OpenRouter list.
SLUG_SOCIAL_BASE_URL = os.environ.get("SLUG_SOCIAL_BASE_URL", "https://slug.social").rstrip("/")
@@ -146,6 +170,7 @@ class Emission:
distributions: dict # author -> amount str
ranking: dict # author -> score str
models_used: list
+ discovery_snapshot_id: str = "" # empty only for pre-discovery ledger history
@event
@@ -163,6 +188,20 @@ class Redemption:
amount: str
+@event
+class GitDiscovery:
+ schema_version: int
+ epoch: int
+ snapshot_id: str
+ timestamp_ms: int
+ config_digest: str
+ initial_snapshot: bool
+ configuration: dict
+ repositories: list
+ observations: list
+ commits: list
+
+
store = JsonlStore(JSONL_PATH)
@@ -532,44 +571,426 @@ Side B — unified diffs (full patches):
return json.loads(content)
-def _github_headers():
- h = {"Accept": "application/vnd.github+json"}
- tok = os.environ.get("GITHUB_TOKEN", "")
- if tok:
- h["Authorization"] = f"Bearer {tok}"
- return h
+# ===========================================================================
+# §4b. GIT DISCOVERY — immutable reachability snapshots across repositories
+# ===========================================================================
+#
+# Git timestamps cannot prove when a branch first reached a commit. The first
+# snapshot therefore bootstraps history by committer time at GENESIS_MS. Every
+# later snapshot uses the stronger rule: a commit enters exactly once, when it
+# first becomes reachable from the union of configured refs.
+#
+# OIDs are deduplicated globally, then equivalent cherry-picks are deduplicated
+# by Git's stable patch identity. Merges and empty commits are graph structure,
+# not separately priced contributions. Discovery is all-or-nothing: if any
+# repository cannot be mirrored and verified, no snapshot is appended.
+
+GIT_DISCOVERY_SCHEMA_VERSION = 1
+PATCH_IDENTITY_VERSION = "git-patch-id-stable-v1"
+_DISCOVERY_LOCK = asyncio.Lock()
+
+
+def _normalized_discovery_config() -> dict:
+ repositories = []
+ seen_ids = set()
+ for raw in REPOSITORIES:
+ repo_id = str(raw.get("id", ""))
+ url = str(raw.get("url", ""))
+ refs = sorted(set(str(x) for x in raw.get("refs", [])))
+ if not re.fullmatch(r"[A-Za-z0-9._-]+", repo_id):
+ raise ValueError(f"invalid repository id: {repo_id!r}")
+ if repo_id in seen_ids:
+ raise ValueError(f"duplicate repository id: {repo_id}")
+ if not url or not refs or any(not r.startswith("refs/") for r in refs):
+ raise ValueError(f"repository {repo_id} requires a URL and full ref patterns")
+ seen_ids.add(repo_id)
+ repositories.append({"id": repo_id, "url": url, "refs": refs})
+
+ email_to_contributor = {}
+ contributors = {}
+ for contributor, emails in sorted(CONTRIBUTORS.items()):
+ contributor = str(contributor)
+ normalized = sorted(set(str(e).strip().lower() for e in emails))
+ if not contributor or not normalized:
+ raise ValueError("contributors require an id and at least one email")
+ for email in normalized:
+ if email in email_to_contributor:
+ raise ValueError(f"email belongs to multiple contributors: {email}")
+ email_to_contributor[email] = contributor
+ contributors[contributor] = normalized
+
+ repositories.sort(key=lambda r: r["id"])
+ return {"repositories": repositories, "contributors": contributors}
+
+
+def _config_digest(config: dict) -> str:
+ encoded = json.dumps(config, sort_keys=True, separators=(",", ":")).encode()
+ return hashlib.sha256(encoded).hexdigest()
+
+
+def _ref_pattern_regex(pattern: str) -> re.Pattern:
+ out = ""
+ i = 0
+ while i < len(pattern):
+ if pattern[i:i + 2] == "**":
+ out += ".*"
+ i += 2
+ elif pattern[i] == "*":
+ out += "[^/]*"
+ i += 1
+ elif pattern[i] == "?":
+ out += "[^/]"
+ i += 1
+ else:
+ out += re.escape(pattern[i])
+ i += 1
+ return re.compile(f"^{out}$")
+
+
+def _git(repo: pathlib.Path | None, *args: str, input_bytes: bytes | None = None) -> bytes:
+ command = [
+ "git",
+ "--no-replace-objects",
+ "-c", "core.quotepath=true",
+ "-c", "core.attributesFile=/dev/null",
+ "-c", "diff.external=",
+ "-c", "diff.renames=false",
+ "-c", "diff.algorithm=myers",
+ "-c", "diff.context=3",
+ ]
+ if repo is not None:
+ command += ["-C", str(repo)]
+ command += list(args)
+ try:
+ result = subprocess.run(
+ command,
+ input=input_bytes,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env={
+ **os.environ,
+ "GIT_CONFIG_NOSYSTEM": "1",
+ "GIT_CONFIG_GLOBAL": os.devnull,
+ "GIT_NO_REPLACE_OBJECTS": "1",
+ "LC_ALL": "C",
+ "TZ": "UTC",
+ },
+ timeout=GIT_TIMEOUT_SECONDS,
+ check=False,
+ )
+ except subprocess.TimeoutExpired as exc:
+ raise RuntimeError(f"git command timed out: {args[0]}") from exc
+ if result.returncode:
+ error = result.stderr.decode("utf-8", "replace").strip()
+ raise RuntimeError(f"git {args[0]} failed: {error}")
+ return result.stdout
+
+
+def _ensure_mirror(repo: dict) -> pathlib.Path:
+ GIT_MIRROR_DIR.mkdir(parents=True, exist_ok=True)
+ mirror = GIT_MIRROR_DIR / f"{repo['id']}.git"
+ if not mirror.exists():
+ _git(None, "clone", "--mirror", "--", repo["url"], str(mirror))
+ else:
+ actual_url = _git(mirror, "remote", "get-url", "origin").decode().strip()
+ if actual_url != repo["url"]:
+ raise RuntimeError(
+ f"mirror URL mismatch for {repo['id']}: {actual_url!r}"
+ )
+ _git(mirror, "fetch", "--prune", "origin", "+refs/*:refs/*")
+ _git(mirror, "fsck", "--connectivity-only", "--no-dangling")
+ return mirror
+
+
+def _matching_refs(mirror: pathlib.Path, patterns: list[str]) -> list[dict]:
+ regexes = [_ref_pattern_regex(p) for p in patterns]
+ lines = _git(
+ mirror, "for-each-ref", "--format=%(refname)%00%(objectname)"
+ ).decode("utf-8", "replace").splitlines()
+ selected = []
+ for line in lines:
+ if not line:
+ continue
+ ref_name, direct_oid = line.split("\x00", 1)
+ if not any(r.fullmatch(ref_name) for r in regexes):
+ continue
+ commit_oid = _git(
+ mirror, "rev-parse", "--verify", f"{ref_name}^{{commit}}"
+ ).decode().strip()
+ selected.append({
+ "name": ref_name,
+ "direct_oid": direct_oid,
+ "commit_oid": commit_oid,
+ })
+ if not selected:
+ raise RuntimeError(f"no refs matched patterns {patterns!r}")
+ return sorted(selected, key=lambda r: r["name"])
+
+
+def _commit_metadata(mirror: pathlib.Path, oid: str) -> dict:
+ raw = _git(
+ mirror,
+ "show",
+ "-s",
+ "--format=%H%x00%T%x00%P%x00%an%x00%ae%x00%at%x00%cn%x00%ce%x00%ct%x00%B",
+ oid,
+ ).decode("utf-8", "replace")
+ fields = raw.split("\x00", 9)
+ if len(fields) != 10:
+ raise RuntimeError(f"could not parse commit metadata for {oid}")
+ return {
+ "oid": fields[0],
+ "tree_oid": fields[1],
+ "parent_oids": fields[2].split() if fields[2] else [],
+ "author_name": fields[3],
+ "author_email": fields[4].strip().lower(),
+ "author_timestamp_ms": int(fields[5]) * 1000,
+ "committer_name": fields[6],
+ "committer_email": fields[7].strip().lower(),
+ "committer_timestamp_ms": int(fields[8]) * 1000,
+ "message": fields[9].rstrip("\n"),
+ }
-def unified_diff_from_commit_payload(payload: dict) -> str:
- parts = []
- for f in payload.get("files") or []:
- name = f.get("filename", "?")
- patch = f.get("patch")
- if patch:
- parts.append(f"--- {name}\n{patch}")
- else:
- parts.append(f"--- {name}\n[no textual patch: binary, submodule, or too large]\n")
- return "\n\n".join(parts) if parts else "[no files in API response]"
+def _commit_patch(mirror: pathlib.Path, metadata: dict) -> tuple[str, str | None]:
+ parents = metadata["parent_oids"]
+ if len(parents) > 1:
+ return "", None
+ if parents:
+ args = ("diff", "--patch", "--binary", "--full-index", "--no-renames",
+ "--no-ext-diff", "--no-textconv", "--src-prefix=a/",
+ "--dst-prefix=b/", parents[0], metadata["oid"], "--")
+ else:
+ args = ("diff-tree", "--root", "--patch", "--binary", "--full-index",
+ "--no-renames", "--no-ext-diff", "--no-textconv",
+ "--src-prefix=a/", "--dst-prefix=b/", "--no-commit-id",
+ metadata["oid"], "--")
+ patch_bytes = _git(mirror, *args)
+ if not patch_bytes.strip():
+ return "", None
+ # Run patch-id outside the repository so SHA-1 and SHA-256 repositories use
+ # the same canonical patch hash algorithm.
+ patch_id_out = _git(
+ None, "patch-id", "--stable", input_bytes=patch_bytes
+ ).decode().strip()
+ if patch_id_out:
+ st
… preview truncated; 120,051 characters omittedHardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.