Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions apps/website/public/roadmap/spec-authors.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"measuredAt": "2026-10-02T04:36:30.085Z",
"method": "Commits touching a directory that holds .t27 files, in every public gHashTag repository that has one (published mirrors and vendored copies excluded), each commit counted once. Credited to a GitHub account: the commit author, else whoever opened the merged pull request carrying it, else the committer. A measure of work on the spec corpus: not every file under those directories is a spec, and a commit is not a file.",
"measuredAt": "2026-10-03T10:37:52.568Z",
"method": "Commits touching a directory that holds .t27 files, in every public gHashTag repository that has one (published mirrors and vendored copies excluded), each commit counted once. Credited to a GitHub account: for a squash of the swarm's queen-<issue> pull request, the person whose provider key the accepted bee ran on (the Queen's record); otherwise the commit author, else whoever opened the merged pull request carrying it, else the committer. A measure of work on the spec corpus: not every file under those directories is a spec, and a commit is not a file.",
"laneCredits": {
"read": true,
"source": "https://trios-agent-server-production.up.railway.app/queen/public-credits",
"issues": 1190,
"queenSquashes": 564
},
"sources": [
{
"repo": "gHashTag/agent-mail-mcp",
Expand Down Expand Up @@ -217,14 +223,15 @@
],
"truncated": [],
"unattributed": {
"commits": 47,
"commits": 49,
"names": {
"Dmitrii Vasilev": 1,
"Claude": 8,
"Claude Agent": 13,
"Trinity Agent": 1,
"t27-agent": 4,
"gh CLI": 3,
"github-actions[bot]": 2,
"agent": 2,
"trinity-bot": 12,
"t27-wave-bot": 2,
Expand All @@ -235,15 +242,15 @@
{
"login": "gHashTag",
"avatar": "/gHashTag.png?size=96",
"commits": 3235,
"commits": 2953,
"via": {
"author": 2605,
"author": 2323,
"pr": 624,
"name": 6
},
"repos": [
"t27",
"agent-mail-mcp",
"t27",
"tri-net",
"trinity",
"trinity-fpga",
Expand All @@ -255,23 +262,13 @@
"turbobaby-user-bot"
]
},
{
"login": "mxfill77",
"avatar": "/mxfill77.png?size=96",
"commits": 74,
"via": {
"author": 74
},
"repos": [
"turbobaby-user-bot"
]
},
{
"login": "dmitrii-f-t27",
"avatar": "/dmitrii-f-t27.png?size=96",
"commits": 36,
"commits": 358,
"via": {
"author": 31,
"author": 42,
"lane": 311,
"pr": 5
},
"repos": [
Expand All @@ -284,6 +281,17 @@
"turbobaby-user-bot"
]
},
{
"login": "mxfill77",
"avatar": "/mxfill77.png?size=96",
"commits": 74,
"via": {
"author": 74
},
"repos": [
"turbobaby-user-bot"
]
},
{
"login": "Karim13014",
"avatar": "/Karim13014.png?size=96",
Expand Down
54 changes: 53 additions & 1 deletion apps/website/scripts/spec-authors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
// `attribute` below for the full order. What still has no account is counted
// as a number, not drawn as a person.
//
// THE SWARM'S WORK GOES TO THE LANE IT RAN ON. Owner's word, 2026-10-03. A bee's
// work reaches t27 master as the squash of a `queen-<issue>` pull request, and
// the squash is authored by whoever merged it, so the swarm's work read as the
// merger's own. The Queen records which lane carried each accepted issue and who
// lent it (`/queen/public-credits`), so a commit squashed from `queen-<issue>`
// whose issue has a credited lane goes to that lane's owner (`via: lane`). Turns
// from before an owner lent their lanes are not credited there and keep the
// rule below.
//
// WHY A FILE AND NOT A LIVE READ: an honest answer needs hundreds of GitHub
// requests and anonymous GitHub allows sixty an hour per address. A generated
// file costs the visitor nothing and cannot be rate-limited.
Expand Down Expand Up @@ -210,11 +219,50 @@ await pool(sources, 4, async ({ repo, path }) => {
if (page > MAX_PAGES) truncated.push(`${repo}:${path}`)
})

// ---- 2b. The swarm's lanes: queen-<issue> squashes and who lent the lane. ----

const CREDITS_URL =
process.env.QUEEN_CREDITS_URL ?? 'https://trios-agent-server-production.up.railway.app/queen/public-credits'
const SWARM_REPO = `${OWNER}/t27`

/** issue -> login, from the Queen's own record of accepted turns. */
const laneOwner = new Map()
let laneCreditsRead = false
try {
const res = await fetch(CREDITS_URL, { headers: { 'user-agent': 'trinity-spec-authors' } })
if (!res.ok) throw new Error(`${res.status}`)
const body = await res.json()
for (const c of body.credits ?? []) {
if (Number.isInteger(c.issue) && isPerson(c.github)) laneOwner.set(c.issue, c.github)
}
laneCreditsRead = true
} catch (err) {
// Without the record nothing is re-attributed; the old rule stands and the
// JSON says why, rather than the board silently changing hands.
console.error(`spec-authors: lane credits unavailable (${err.message}); keeping the merger rule`)
}

/** merge commit sha -> issue, for every merged queen-<issue> pull request. */
const queenSquash = new Map()
if (laneOwner.size > 0) {
for (let page = 1; page <= 200; page += 1) {
const rows = await gh(`repos/${SWARM_REPO}/pulls?state=closed&per_page=100&page=${page}`)
if (!rows?.length) break
for (const pr of rows) {
const m = /^queen-(\d+)$/.exec(pr.head?.ref ?? '')
if (m && pr.merged_at && pr.merge_commit_sha) queenSquash.set(pr.merge_commit_sha, Number(m[1]))
}
if (rows.length < 100) break
}
}

// ---- 3. Attribution: one GitHub account per commit, or none. ----

/**
* WHO A COMMIT BELONGS TO, IN THIS ORDER:
*
* 0. `lane` - a squash of a merged `queen-<issue>` pull request in t27 whose
* issue the Queen credits to the person who lent the lane (see 2b).
* 1. `author` - the account GitHub tied to the commit's email. Not for an agent
* address, which GitHub ties to a stranger.
* 2. `pr` - the account that opened the merged pull request carrying the
Expand All @@ -234,6 +282,9 @@ async function prAuthor(repo, sha) {
}

const credited = await pool([...commits.entries()], 4, async ([sha, { repo, row }]) => {
const issue = repo === SWARM_REPO ? queenSquash.get(sha) : undefined
const lender = issue === undefined ? undefined : laneOwner.get(issue)
if (lender) return { sha, login: lender, via: 'lane' }
const email = (row.commit?.author?.email ?? '').toLowerCase()
const author = row.author?.login
if (!AGENT_EMAILS.has(email) && isPerson(author)) return { sha, login: author, via: 'author' }
Expand Down Expand Up @@ -293,7 +344,8 @@ process.stdout.write(
{
measuredAt: new Date().toISOString(),
method:
'Commits touching a directory that holds .t27 files, in every public gHashTag repository that has one (published mirrors and vendored copies excluded), each commit counted once. Credited to a GitHub account: the commit author, else whoever opened the merged pull request carrying it, else the committer. A measure of work on the spec corpus: not every file under those directories is a spec, and a commit is not a file.',
'Commits touching a directory that holds .t27 files, in every public gHashTag repository that has one (published mirrors and vendored copies excluded), each commit counted once. Credited to a GitHub account: for a squash of the swarm\'s queen-<issue> pull request, the person whose provider key the accepted bee ran on (the Queen\'s record); otherwise the commit author, else whoever opened the merged pull request carrying it, else the committer. A measure of work on the spec corpus: not every file under those directories is a spec, and a commit is not a file.',
laneCredits: { read: laneCreditsRead, source: CREDITS_URL, issues: laneOwner.size, queenSquashes: queenSquash.size },
sources,
truncated,
unattributed,
Expand Down
9 changes: 9 additions & 0 deletions apps/website/src/components/QueenPeople.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface PeopleCopy {
commits: string
repos: string
viaPr: string
viaLane: string
unattributed: string
countedIn: string
loading: string
Expand All @@ -50,6 +51,7 @@ export const PEOPLE_COPY: Record<'en' | 'ru', PeopleCopy> = {
commits: 'spec commits',
repos: 'repositories',
viaPr: 'through their merged pull requests',
viaLane: 'by the swarm on their keys',
unattributed: 'spec commits have no GitHub account behind them and no merged pull request, so they are counted here and credited to nobody',
countedIn: 'Counted in',
loading: 'Reading the count\u2026',
Expand All @@ -62,6 +64,7 @@ export const PEOPLE_COPY: Record<'en' | 'ru', PeopleCopy> = {
commits: '\u043a\u043e\u043c\u043c\u0438\u0442\u043e\u0432 \u0432 \u0441\u043f\u0435\u043a\u0438',
repos: '\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438',
viaPr: '\u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u0438 \u0441\u043c\u0435\u0440\u0436\u0435\u043d\u043d\u044b\u0435 \u043f\u0443\u043b\u043b-\u0440\u0435\u043a\u0432\u0435\u0441\u0442\u044b',
viaLane: '\u0440\u043e\u0435\u043c \u043d\u0430 \u0438\u0445 \u043a\u043b\u044e\u0447\u0430\u0445',
unattributed: '\u043a\u043e\u043c\u043c\u0438\u0442\u043e\u0432 \u0432 \u0441\u043f\u0435\u043a\u0438 \u043d\u0435 \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u043d\u0438 \u0441 \u043e\u0434\u043d\u0438\u043c \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u043e\u043c GitHub \u0438 \u043d\u0438 \u0441 \u043e\u0434\u043d\u0438\u043c \u0441\u043c\u0435\u0440\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0443\u043b\u043b-\u0440\u0435\u043a\u0432\u0435\u0441\u0442\u043e\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043d\u0438 \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u043d\u044b \u0437\u0434\u0435\u0441\u044c \u0438 \u043d\u0435 \u043f\u0440\u0438\u043f\u0438\u0441\u0430\u043d\u044b \u043d\u0438\u043a\u043e\u043c\u0443',
countedIn: '\u041f\u043e\u0441\u0447\u0438\u0442\u0430\u043d\u043e \u0432',
loading: '\u0427\u0438\u0442\u0430\u044e \u043f\u043e\u0434\u0441\u0447\u0451\u0442\u2026',
Expand Down Expand Up @@ -103,6 +106,7 @@ export default function QueenPeople({ lang }: { lang: 'en' | 'ru' }) {
const login = loginOf(person)
if (!login) return null
const viaPr = person.via.pr ?? 0
const viaLane = person.via.lane ?? 0
return (
<li key={login} className="qp-row">
<span className="qp-rank">{i + 1}</span>
Expand All @@ -128,6 +132,11 @@ export default function QueenPeople({ lang }: { lang: 'en' | 'ru' }) {
</span>
<span className="qp-commits">
<b>{fmt(person.commits)}</b> {c.commits}
{viaLane > 0 && (
<small className="qp-via">
{fmt(viaLane)} {c.viaLane}
</small>
)}
{viaPr > 0 && (
<small className="qp-via">
{fmt(viaPr)} {c.viaPr}
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/lib/queenPeople.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { githubLogin } from './githubLogin.ts'

/** How a commit reached its account; see `attribute` in the script. */
export type CreditedVia = 'author' | 'pr' | 'committer' | 'name'
export type CreditedVia = 'lane' | 'author' | 'pr' | 'committer' | 'name'

/** One row: a GitHub account. Since 2026-10-02 nothing else is ever a row. */
export interface SpecAuthor {
Expand Down
Loading