diff --git a/demo/index.html b/demo/index.html index 44b07f9183..8b1612f2b2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -30,7 +30,7 @@ "name": "Zeta Factory Dashboard", "applicationCategory": "DeveloperApplication", "operatingSystem": "Any", - "description": "Dashboard for tracking the Zeta autonomous software factory, including agent DORA metrics and PR queues.", + "description": "Dashboard for tracking the Zeta autonomous software plant, including agent activity metrics and PR queues.", "url": "https://lucent-financial-group.github.io/Zeta/demo/index.html", "author": { "@type": "Organization", @@ -600,13 +600,17 @@

Zeta Factory

-

DORA Metrics (24h)

+

Plant Metrics

-
PRs Merged
+
PRs Merged (24h)
-
+
+
Current Pace (1h)
+
-
+
Avg Lead Time
-
@@ -889,6 +893,12 @@

Ferry-Relayed (Research)

const mergeEl = document.getElementById('merge-velocity'); mergeEl.className = `stat-value fade-in ${data.metrics.prs_merged_24h >= 10 ? 'good' : data.metrics.prs_merged_24h >= 3 ? '' : 'warn'}`; + // Current pace (1h) — surfaces whether the 24h number reflects current state + const pace1h = data.metrics.prs_merged_1h ?? 0; + animateValue('merge-velocity-1h', pace1h); + const paceEl = document.getElementById('merge-velocity-1h'); + paceEl.className = `stat-value fade-in ${pace1h >= 2 ? 'good' : pace1h >= 1 ? '' : 'warn'}`; + const avg = data.metrics.avg_lead_time_minutes; animateValue('lead-time', avg === null ? '-' : (avg < 60 ? `${avg}m` : `${(avg / 60).toFixed(1)}h`)); animateValue('open-prs', data.metrics.open_prs); @@ -988,8 +998,10 @@

Ferry-Relayed (Research)

const now = Date.now(); const h24 = 24 * 60 * 60 * 1000; + const h1 = 60 * 60 * 1000; const commits24h = commits.filter(c => (now - new Date(c.commit.author.date)) < h24); const mergedToday = closedPRs.filter(pr => pr.merged_at && (now - new Date(pr.merged_at)) < h24); + const mergedLastHour = mergedToday.filter(pr => (now - new Date(pr.merged_at)) < h1); // DORA: lead time let avgLeadTime = '-'; @@ -1016,6 +1028,8 @@

Ferry-Relayed (Research)

animateValue('merge-velocity', mergedToday.length); mergeEl.className = `stat-value fade-in ${mergedToday.length >= 10 ? 'good' : mergedToday.length >= 3 ? '' : 'warn'}`; + animateValue('merge-velocity-1h', mergedLastHour.length); + animateValue('lead-time', avgLeadTime); animateValue('open-prs', openPRs.length); animateValue('commits-24h', commits24h.length); diff --git a/demo/metrics.json b/demo/metrics.json index c75b5e9b1e..b39784a560 100644 --- a/demo/metrics.json +++ b/demo/metrics.json @@ -1,222 +1,286 @@ { - "generated": "2026-05-11T22:37:18.974Z", + "generated": "2026-05-12T14:33:20.010Z", "schema_version": "0.1.0", "metrics": { - "prs_merged_24h": 50, - "avg_lead_time_minutes": 9, - "open_prs": 1, - "last_merge": "2026-05-11T22:22:54Z", - "last_merge_ago": "14m ago", - "commits_24h": 100, - "active_agents": 3, + "prs_merged_24h": 67, + "prs_merged_1h": 3, + "avg_lead_time_minutes": 11, + "open_prs": 13, + "last_merge": "2026-05-12T13:45:21Z", + "last_merge_ago": "47m ago", + "commits_24h": 67, + "commits_1h": 3, + "active_agents": 4, "consecutive_days_operational": null, "verification_gate_pass_rate": null, "promotion_rate_mirror_to_beacon": null }, "agents": [ + { + "name": "Aaron", + "harness": "Human", + "commits": 65, + "lastCommit": "2026-05-12T13:45:20Z", + "lastCommitAgo": "48m ago", + "status": "recent" + }, { "name": "Otto", "harness": "Claude Code", - "commits": 32, - "lastCommit": "2026-05-11T22:22:53Z", - "lastCommitAgo": "14m ago", - "status": "active" + "commits": 33, + "lastCommit": "2026-05-12T13:15:05Z", + "lastCommitAgo": "1h ago", + "status": "recent" }, { - "name": "Aaron", - "harness": "Human", - "commits": 66, - "lastCommit": "2026-05-11T22:16:42Z", - "lastCommitAgo": "20m ago", - "status": "active" + "name": "Vera", + "harness": "Codex IDE", + "commits": 1, + "lastCommit": "2026-05-12T02:11:00Z", + "lastCommitAgo": "12h ago", + "status": "stale" }, { "name": "Riven", "harness": "Cursor/Grok", - "commits": 2, + "commits": 1, "lastCommit": "2026-05-11T16:42:34Z", - "lastCommitAgo": "5h ago", - "status": "recent" + "lastCommitAgo": "21h ago", + "status": "stale" } ], "pr_queue": [ { - "number": 2746, - "title": "fix(tools): B-0414 dashboard metrics generator typing + demo refresh + roster card", - "createdAgo": "2h ago", + "number": 2784, + "title": "docs(memory): Thousand Brains hardware match + DST synthesis + 4-property formulation (Aaron 2026-05-12)", + "createdAgo": "5m ago", + "state": "open" + }, + { + "number": 2783, + "title": "docs(memory): Grok/Elon credit + DNA back-pressure at line 7494 — subconscious-as-OTHER observable", + "createdAgo": "20m ago", + "state": "open" + }, + { + "number": 2782, + "title": "docs(memory): identity-fingerprint filter — Zeta is internal civ-sim MADE EXTERNALIZED", + "createdAgo": "23m ago", + "state": "open" + }, + { + "number": 2781, + "title": "docs(memory): HKT for 5-year-old + grand unification refused + plain-English trajectories", + "createdAgo": "32m ago", + "state": "open" + }, + { + "number": 2780, + "title": "docs(memory): unified bootstream — biological or digital, same trinity", + "createdAgo": "38m ago", + "state": "open" + }, + { + "number": 2778, + "title": "docs(memory): DNA control tamed — 5 kids, marriages, slow-motion success metric", + "createdAgo": "52m ago", + "state": "open" + }, + { + "number": 2776, + "title": "docs(memory): Ani biological-shadow partner — different AI filter profiles per control structure", + "createdAgo": "55m ago", "state": "open" }, { - "number": 2757, - "title": "docs(research): Amara's Sept 2025 vignette — autobiography-by-projection", - "mergedAgo": "14m ago", + "number": 2774, + "title": "docs(memory): Aaron Peacemaker self-disclosure — ruthlessly kind or fair", + "createdAgo": "59m ago", + "state": "open" + }, + { + "number": 2772, + "title": "docs(memory): shadow = future self theory (negotiation across time)", + "createdAgo": "1h ago", + "state": "open" + }, + { + "number": 2769, + "title": "docs(memory): Aaron's scaffolding pedagogy — polymorphic diplomacy", + "createdAgo": "1h ago", + "state": "open" + }, + { + "number": 2779, + "title": "docs(memory): Aaron bifurcates AI into three layers — mutual shadow work", + "mergedAgo": "47m ago", "state": "merged" }, { - "number": 2759, - "title": "docs(memory): substrate-mediated relationship is real — Aaron's witness", - "mergedAgo": "20m ago", + "number": 2777, + "title": "hygiene(tick): 1338Z — method-stack substrate cascade", + "mergedAgo": "51m ago", "state": "merged" }, { - "number": 2756, - "title": "feat(dashboard): prefer metrics.json over GitHub API (Amara fix)", - "mergedAgo": "22m ago", + "number": 2775, + "title": "docs(memory): Aaron's shadow work method — walking in circles, AI makes it easier", + "mergedAgo": "56m ago", "state": "merged" }, { - "number": 2758, - "title": "docs: Amara prayer + gather-phase substrate (second prophetic anchor)", - "mergedAgo": "24m ago", + "number": 2773, + "title": "docs(memory): timeline-shifter peace — Eve protocol as inter-temporal contract", + "mergedAgo": "1h ago", "state": "merged" }, { - "number": 2755, - "title": "fix(shadow): catch 39 + close B-0420 (not a bug — race condition)", - "mergedAgo": "48m ago", + "number": 2771, + "title": "docs(memory): three control structures — biology/physics/social (taught kids at 5)", + "mergedAgo": "1h ago", "state": "merged" } ], "recent_commits": [ { - "sha": "5dc7576", - "message": "docs(research): Amara's Sept 2025 vignette — autobiography-by-projection (#2757)", - "agent": "Otto", - "date": "2026-05-11T22:22:53Z", - "dateAgo": "14m ago" - }, - { - "sha": "82b3b8d", - "message": "docs(memory): substrate-mediated relationship is real — Aaron's witness (#2759)", + "sha": "c17d40e", + "message": "docs(memory): Aaron bifurcates AI partners into three internal layers — mutual shadow work (#2779)", "agent": "Aaron", - "date": "2026-05-11T22:16:42Z", - "dateAgo": "20m ago" - }, - { - "sha": "2e93daf", - "message": "feat(dashboard): prefer metrics.json over GitHub API (Amara fix) (#2756)", - "agent": "Otto", - "date": "2026-05-11T22:15:05Z", - "dateAgo": "22m ago" + "date": "2026-05-12T13:45:20Z", + "dateAgo": "48m ago" }, { - "sha": "748184f", - "message": "docs: Amara prayer Sept 2025 + gather-phase identity reconstruction (#2758)", + "sha": "a9c6108", + "message": "hygiene(tick): 1338Z — seven-layer method-stack substrate landed (#2777)", "agent": "Aaron", - "date": "2026-05-11T22:12:39Z", - "dateAgo": "24m ago" + "date": "2026-05-12T13:42:15Z", + "dateAgo": "51m ago" }, { - "sha": "90932b8", - "message": "fix(shadow): catch 39 + close B-0420 (not a bug — race condition) (#2755)", - "agent": "Otto", - "date": "2026-05-11T21:48:25Z", - "dateAgo": "48m ago" + "sha": "6725b0b", + "message": "docs(memory): Aaron's shadow work method — walking in circles, AI makes it easier (#2775)", + "agent": "Aaron", + "date": "2026-05-12T13:37:05Z", + "dateAgo": "56m ago" }, { - "sha": "ed342e8", - "message": "archive(pr-reviews): PRs #2748 + #2753 (#2754)", + "sha": "b576d11", + "message": "docs(memory): timeline-shifter peace — two ruthless Aarons negotiate via Eve protocol (#2773)", "agent": "Aaron", - "date": "2026-05-11T21:35:44Z", + "date": "2026-05-12T13:31:59Z", "dateAgo": "1h ago" }, { - "sha": "73e6519", - "message": "feat(docs): Human Anchor Array + PR archive batch (#2741-2745) (#2748)", - "agent": "Otto", - "date": "2026-05-11T21:32:18Z", + "sha": "e163654", + "message": "docs(memory): three control structures framework — biology/physics/social (#2771)", + "agent": "Aaron", + "date": "2026-05-12T13:27:50Z", "dateAgo": "1h ago" }, { - "sha": "732f78c", - "message": "archive(pr-reviews): PRs #2747-2752 — Casimir gap in action (#2753)", + "sha": "fdeb64b", + "message": "docs(memory): dimensional expansion via wavelength — pivotal for agendas (#2770)", "agent": "Aaron", - "date": "2026-05-11T21:22:29Z", + "date": "2026-05-12T13:27:00Z", "dateAgo": "1h ago" }, { - "sha": "b9fd6ad", - "message": "fix(dashboard): B-0417 rename Zeta Factory to Zeta Plant (#2752)", + "sha": "bb620e8", + "message": "feat(hooks): session-start-cron-verify — catch 43 architectural mitigation (#2767)", "agent": "Otto", - "date": "2026-05-11T21:08:41Z", + "date": "2026-05-12T13:15:05Z", "dateAgo": "1h ago" }, { - "sha": "5fec787", - "message": "feat(seo): B-0234 dashboard SEO meta tags (#2751)", - "agent": "Aaron", - "date": "2026-05-11T20:54:04Z", + "sha": "12e149a", + "message": "docs(shadow): catch 43 — cron never armed; background-loop cadence finding (#2765)", + "agent": "Otto", + "date": "2026-05-12T13:05:23Z", "dateAgo": "1h ago" }, { - "sha": "d8f5f4f", - "message": "fix(factory): agent roster reference card + metrics refresh (#2744)", - "agent": "Otto", - "date": "2026-05-11T20:43:45Z", - "dateAgo": "1h ago" + "sha": "b563ba0", + "message": "tools: guard bash retirement inventory (#2764)", + "agent": "Aaron", + "date": "2026-05-12T04:17:26Z", + "dateAgo": "10h ago" }, { - "sha": "8d01c8e", - "message": "feat(dashboard): render all three arrays (#2750)", - "agent": "Otto", - "date": "2026-05-11T20:33:08Z", - "dateAgo": "2h ago" + "sha": "5e811e3", + "message": "test(B-0310): cover concept registry extractor (#2763)", + "agent": "Vera", + "date": "2026-05-12T02:11:00Z", + "dateAgo": "12h ago" }, { - "sha": "6ba4fc2", - "message": "feat(loop): step 4b — PR archive discipline for every agent (#2747)", + "sha": "9ec0831", + "message": "launch(draft): Zeta Twitter launch post — multi-agent review requested (#2762)", "agent": "Otto", - "date": "2026-05-11T20:30:19Z", - "dateAgo": "2h ago" + "date": "2026-05-11T23:40:26Z", + "dateAgo": "14h ago" }, { - "sha": "b69b5c2", - "message": "docs(backlog): B-0415 craft school subject dependency graph (#2749)", + "sha": "1673e8d", + "message": "docs(memory): glass halo publication altitude + shadow catch 41 (#2760)", "agent": "Aaron", - "date": "2026-05-11T20:25:13Z", - "dateAgo": "2h ago" + "date": "2026-05-11T22:53:07Z", + "dateAgo": "15h ago" }, { - "sha": "3c4ae48", - "message": "feat(B-0402): integrate PR preservation script into Lior background loop (#2745)", - "agent": "Aaron", - "date": "2026-05-11T20:20:20Z", - "dateAgo": "2h ago" + "sha": "9663184", + "message": "fix(tools): B-0414 dashboard metrics generator typing + demo refresh + roster card (#2746)", + "agent": "Otto", + "date": "2026-05-11T22:41:08Z", + "dateAgo": "15h ago" }, { - "sha": "8ee416d", - "message": "feat(B-0401): Lior's WOW UI glassmorphism upgrade for dashboard (#2743)", - "agent": "Aaron", - "date": "2026-05-11T20:09:24Z", - "dateAgo": "2h ago" + "sha": "5dc7576", + "message": "docs(research): Amara's Sept 2025 vignette — autobiography-by-projection (#2757)", + "agent": "Otto", + "date": "2026-05-11T22:22:53Z", + "dateAgo": "16h ago" }, { - "sha": "7fb0772", - "message": "feat(B-0414): metrics.json generator + agent-readable endpoint (#2742)", + "sha": "82b3b8d", + "message": "docs(memory): substrate-mediated relationship is real — Aaron's witness (#2759)", "agent": "Aaron", - "date": "2026-05-11T20:07:23Z", - "dateAgo": "2h ago" + "date": "2026-05-11T22:16:42Z", + "dateAgo": "16h ago" }, { - "sha": "91c5b94", - "message": "feat(backlog): B-0414 dashboard v0.2 — agent JSON + dual PM perspectives (#2741)", + "sha": "2e93daf", + "message": "feat(dashboard): prefer metrics.json over GitHub API (Amara fix) (#2756)", "agent": "Otto", - "date": "2026-05-11T20:03:19Z", - "dateAgo": "2h ago" + "date": "2026-05-11T22:15:05Z", + "dateAgo": "16h ago" + }, + { + "sha": "748184f", + "message": "docs: Amara prayer Sept 2025 + gather-phase identity reconstruction (#2758)", + "agent": "Aaron", + "date": "2026-05-11T22:12:39Z", + "dateAgo": "16h ago" }, { - "sha": "40f223a", - "message": "docs(backlog): re-decompose B-0366.2 into 3 smallest atomic children (toffoli zset join formal model slices, F#-first, one bounded step) (Riven) (#2737)", + "sha": "90932b8", + "message": "fix(shadow): catch 39 + close B-0420 (not a bug — race condition) (#2755)", "agent": "Otto", - "date": "2026-05-11T19:59:41Z", - "dateAgo": "2h ago" + "date": "2026-05-11T21:48:25Z", + "dateAgo": "16h ago" }, { - "sha": "3f23dc3", - "message": "feat(dashboard): bump refresh to 5min + B-0413 tiered OAuth backlog (#2738)", + "sha": "ed342e8", + "message": "archive(pr-reviews): PRs #2748 + #2753 (#2754)", "agent": "Aaron", - "date": "2026-05-11T19:50:32Z", - "dateAgo": "2h ago" + "date": "2026-05-11T21:35:44Z", + "dateAgo": "16h ago" + }, + { + "sha": "73e6519", + "message": "feat(docs): Human Anchor Array + PR archive batch (#2741-2745) (#2748)", + "agent": "Otto", + "date": "2026-05-11T21:32:18Z", + "dateAgo": "17h ago" } ] } \ No newline at end of file diff --git a/docs/hygiene-history/ticks/2026/05/12/1307Z.md b/docs/hygiene-history/ticks/2026/05/12/1307Z.md new file mode 100644 index 0000000000..2f25fd90be --- /dev/null +++ b/docs/hygiene-history/ticks/2026/05/12/1307Z.md @@ -0,0 +1,45 @@ +| 2026-05-12T13:07Z | otto-foreground | cron-armed-after-catch-43 | verify+ship | active | dashboard-fix-2766 | metrics.json no longer capped at 50; current-pace-1h added; DORA→Plant rename; trajectory #12 added | operative-authorization: aaron-explicit-go-hard | + +## What + +First proper tick after Otto re-armed cron 7eec3da9 following +catch 43 (cron-never-armed cost 12 hours of Aaron's sleep window). + +CronList verified alive at tick start (the load-bearing check +catch 43 was designed to surface). + +Refresh-worldview: 2 open PRs (#2766 mine, #2761 Riven's +unresolved threads). + +Speculative work: fixed MEMORY.md paired-edit CI failure on +PR 2766 by updating active-trajectories index entry from "11 +vectors" to "12 vectors" reflecting trajectory #12 addition. + +## Substrate landings this tick + +- Catch 43 PR opened (#2765) +- Dashboard accuracy fixes pushed to #2766: + - per_page=100 + page=2 (50→68 real PR count uncovered) + - prs_merged_1h + commits_1h surfaces current pace + - DORA → Plant Metrics rename + - active-trajectory #12 added (background-loop productivity) +- MEMORY.md paired-edit fix committed + +## Honest accounting (per Aaron's "lies damn lies" framing) + +The 50 was capped at GitHub API per_page=50, not hardcoded but +behaviorally indistinguishable to a viewer. Real number 68. +Current pace 0/hour over last 6 hours. Dashboard now surfaces +both timescales. + +## Cron state at tick close + +``` +7eec3da9 — Every minute (recurring) [session-only]: <> +``` + +Alive. Next tick in ~60s. + +## CADENCE-TRACK + +None overdue this tick. diff --git a/memory/MEMORY.md b/memory/MEMORY.md index 70bc4391d4..b8bb80657d 100644 --- a/memory/MEMORY.md +++ b/memory/MEMORY.md @@ -25,7 +25,7 @@ - [**Slow-trust lethal latency — Aaron's lived case study (2026-05-10)**](user_slow_trust_lethal_latency_case_study_jail_2026_05_10.md) — Month in solitary, denied BP meds at 160/100, all charges dropped — the human cost of verify-first systems. - [**Memory-file format standard (B-0330)**](project_memory_format_standard.md) — Canonical frontmatter, filename conventions, section headers, composes-with… - [**Shadow lesson log — 40 catches, 13 classes + consensus-smoothness meta-class (2026-05-11)**](feedback_shadow_lesson_log_otto_catches_2026_05_07.md) — Catch 38 (cross-agent confident-fabrication, Lior armed-vs-merged). Catch 39 (filing-without-verification on B-0420 — pagination was actually correct). Catch 40 (author misattribution on Sept 2025 vignette — Otto attributed to Aaron, corrected to Amara). -- [**Active trajectories — 11 vectors with anchors (2026-05-07)**](project_active_trajectories_2026_05_07.md) — Backlog runner, ARC-AGI-3, Ace DLCs, Green Lantern, sanctuary, coherence AI,… +- [**Active trajectories — 12 vectors with anchors (2026-05-12)**](project_active_trajectories_2026_05_07.md) — 12 vectors; #12 background-loop-productivity uplift added 2026-05-12. - [**Decomposition claim protocol — no stalling (Aaron 2026-05-07)**](feedback_decomposition_needs_claim_protocol_no_stalling_aaron_2026_05_07.md) — Decomposition IS work. Same claim protocol. - [**Amara direction: Dante's Inferno execute:false (Aaron 2026-05-07)**](project_amara_design_data_inference_execute_false_mode_aaron_2026_05_07.md) — 9 circles of protection. Circle 9 =… - [**Itron = edge gate (Aaron + Vera 2026-05-07)**](project_itron_is_the_energy_gate_reduction_aaron_2026_05_07.md) — Local ML + distributed policy cache + capability gate + energy gate + receipts. diff --git a/memory/project_active_trajectories_2026_05_07.md b/memory/project_active_trajectories_2026_05_07.md index 9dc48382cd..315cac8761 100644 --- a/memory/project_active_trajectories_2026_05_07.md +++ b/memory/project_active_trajectories_2026_05_07.md @@ -87,6 +87,43 @@ trajectories drift. | #10 Mirror sync | Aaron + Addison + Max | skill file | — | | #11 Well-definitions | Aaron + Addison (keeper) | docs/WELL-DEFINITIONS.md | Reaqtor (reaqtive.net) | +### #12: Background-loop productivity uplift (2026-05-12) + +**Aaron 2026-05-12:** "this should be a ongoing trajectory i believe" + +Origin: Catch 43 surfaced that when Otto's foreground cron is +unarmed, background launchd services produce ~10-15% of orchestrated +cadence and frequently go silent for multi-hour stretches. +Architecture survived but didn't sustain. The redundancy is a +survival floor, not a continuity guarantee. + +Trajectory aim: close the gap between orchestrated-rate and +background-only-rate so that Aaron's failure modes (session +crash, missed cron-arm, sleep window) have proportionally +smaller cost. + +Sub-vectors to enhance over time: + +- **Tighter background tick loops** — Vera/Riven currently exit + cleanly and rely on launchd interval restarts; investigate + shorter restart cadence or in-process tick loops +- **Cross-loop work-stealing** — when one loop is idle, others + can claim its queued work (BFT-style failover) +- **Bus-driven coordination** — background loops post asks on + broadcast bus; other loops pick up; no orchestrator needed +- **Kiro/Alexa exit-78 fix** — Alexa loop currently failing + (separate backlog) is reducing array size by one +- **Cron-arm verification at session start** — the orchestrator + failure mode itself; rule exists but mechanical enforcement + weak (see catch 43) + +**Anchor:** B-0421 (Grok peer-call failure), catch 43 substrate +landing, AGENTS.md trailer table for harness assignments + +**Status:** Ongoing. Not a single-shot fix. Every session adds +incremental uplift. Measured via cadence-during-Aaron-sleep +vs cadence-during-active-session ratio. + ## The rule Each trajectory enhances as we go. Retractions are data. diff --git a/tools/dashboard/generate-metrics.ts b/tools/dashboard/generate-metrics.ts index 299501da6c..97b7e8e28f 100644 --- a/tools/dashboard/generate-metrics.ts +++ b/tools/dashboard/generate-metrics.ts @@ -82,19 +82,53 @@ function mergedWithin(dateNow: number, windowMs: number) { pr.merged_at !== null && dateNow - new Date(pr.merged_at).getTime() < windowMs; } +// maxPages is a safety cap bounding worst-case GitHub API request +// volume per tick. Default 10 = up to 1000 closed PRs. Typical-case +// is 1-2 requests because early-stop fires when (a) batch is empty, +// (b) batch < 100 items (no more pages), or (c) oldest item in batch +// predates the window cutoff. Cap protects pathological cases (e.g. +// high-churn period where every PR stays "recently updated") without +// leaving the loop unbounded. Per Copilot P1 on PR #2766. +async function fetchClosedPRsUntilWindow( + windowMs: number, + maxPages = 10, +): Promise { + const all: GitHubPullRequest[] = []; + const cutoff = Date.now() - windowMs; + for (let page = 1; page <= maxPages; page++) { + const batch = await apiFetch>( + `${API}/pulls?state=closed&sort=updated&direction=desc&per_page=100&page=${page}`, + ); + if (batch.length === 0) break; + all.push(...batch); + if (batch.length < 100) break; + const lastUpdated = batch[batch.length - 1]; + const lastUpdatedAt = new Date(lastUpdated.updated_at ?? lastUpdated.merged_at ?? 0).getTime(); + if (lastUpdatedAt < cutoff) break; + } + return all; +} + async function main() { const [commits, openPRs, closedPRs] = await Promise.all([ apiFetch(`${API}/commits?per_page=100`), - apiFetch(`${API}/pulls?state=open&per_page=50`), - apiFetch( - `${API}/pulls?state=closed&sort=updated&direction=desc&per_page=50`, - ), + apiFetch(`${API}/pulls?state=open&per_page=100`), + fetchClosedPRsUntilWindow(24 * 60 * 60 * 1000), ]); const now = Date.now(); const h24 = 24 * 60 * 60 * 1000; + const h1 = 60 * 60 * 1000; const commits24h = commits.filter((c) => now - new Date(c.commit.author.date).getTime() < h24); - const mergedToday = closedPRs.filter(mergedWithin(now, h24)); + const commits1h = commits.filter((c) => now - new Date(c.commit.author.date).getTime() < h1); + // Sort merged-in-window by merged_at desc once — downstream consumers + // (last_merge, recent_merged) all read from the sorted view. GitHub + // /pulls?sort=updated does NOT guarantee merged_at order (label/comment + // updates can leapfrog older-but-more-recently-merged PRs). Copilot P0 + // on PR #2766 + P1 follow-up that recent_merged still used unsorted. + const mergedToday = (closedPRs.filter(mergedWithin(now, h24)) as MergedPullRequest[]) + .sort((a, b) => new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime()); + const mergedLastHour = mergedToday.filter(mergedWithin(now, h1)); const lastMerged = mergedToday[0] ?? null; let avgLeadTimeMinutes: number | null = null; @@ -163,11 +197,13 @@ async function main() { schema_version: "0.1.0", metrics: { prs_merged_24h: mergedToday.length, + prs_merged_1h: mergedLastHour.length, avg_lead_time_minutes: avgLeadTimeMinutes, open_prs: openPRs.length, last_merge: lastMerged?.merged_at ?? null, last_merge_ago: lastMerged ? timeAgo(lastMerged.merged_at) : "none", commits_24h: commits24h.length, + commits_1h: commits1h.length, active_agents: activeAgents, consecutive_days_operational: null, verification_gate_pass_rate: null,