[object Object]

← back to build-pages

api: canonAuthor() collapses 'SteveStudio2'/'Steve Abrams'/etc into 'Steve' — authors[] no longer fragmented

c7c77634f5e9571ff168b562c58c61a86f12a0c2 · 2026-05-13 19:05:51 -0700 · SteveStudio2

Files touched

Diff

commit c7c77634f5e9571ff168b562c58c61a86f12a0c2
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 19:05:51 2026 -0700

    api: canonAuthor() collapses 'SteveStudio2'/'Steve Abrams'/etc into 'Steve' — authors[] no longer fragmented
---
 server.js | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index 2f944ed..6ed9f3e 100644
--- a/server.js
+++ b/server.js
@@ -787,6 +787,22 @@ for (const facet of ['agents', 'skills']) {
 // /api/recent — most recent N commits across the entire fleet, merged + sorted
 // by date desc. Powers a "today across the fleet" rail. Shares the bundle
 // cache so it's free after the first warm.
+// Normalize git author strings. Steve commits as "SteveStudio2", "Steve",
+// "Steve Abrams" — same person, different .gitconfig values on different
+// machines. Map these to a canonical name so the authors[] tally doesn't
+// fragment. Extensible — add more aliases here as new people come in.
+const AUTHOR_ALIASES = {
+  'stevestudio2': 'Steve',
+  'steve abrams': 'Steve',
+  'steveabrams': 'Steve',
+  'steve@designerwallcoverings.com': 'Steve',
+};
+function canonAuthor(name) {
+  if (!name) return 'unknown';
+  const key = name.toLowerCase().trim();
+  return AUTHOR_ALIASES[key] || name;
+}
+
 // Parse a ?since= query value into a unix ms cutoff (or 0 = no filter).
 // Accepts 24h, 7d, 30m, or an ISO date prefix (YYYY-MM-DD…). Returns 0 for
 // missing/malformed input so callers can use the result as a min-Date filter
@@ -1073,8 +1089,13 @@ app.get('/api/projects/:slug', async (req, res, next) => {
     const first = bundle.commits[bundle.commits.length - 1];
     const last  = bundle.commits[0];
     // Author breakdown — who landed how many commits on this project.
+    // Aliases collapsed via canonAuthor() so multiple git-config names map to
+    // one person.
     const authorTallies = new Map();
-    for (const c of bundle.commits) authorTallies.set(c.author, (authorTallies.get(c.author) || 0) + 1);
+    for (const c of bundle.commits) {
+      const a = canonAuthor(c.author);
+      authorTallies.set(a, (authorTallies.get(a) || 0) + 1);
+    }
     const authors = [...authorTallies.entries()]
       .map(([name, count]) => ({ name, count }))
       .sort((a, b) => b.count - a.count);

← 4acf48a api: /api/projects/:slug now includes authors[] breakdown  ·  back to build-pages  ·  api/fleet/authors + Authors chip rail on /p/:slug — uses can b806ffd →