diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index abf83773a..fd3b5d120 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "genie",
- "version": "4.260522.20",
+ "version": "4.260606.2",
"source": "./plugins/genie",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
}
diff --git a/.genie/agents/metrics-updater/daily-stats.jsonl b/.genie/agents/metrics-updater/daily-stats.jsonl
index b4721d436..7b9b511d9 100644
--- a/.genie/agents/metrics-updater/daily-stats.jsonl
+++ b/.genie/agents/metrics-updater/daily-stats.jsonl
@@ -1,57 +1,32 @@
-{"date":"2026-03-13","commits":13,"loc_added":6920,"loc_removed":2948,"releases":0,"contributors":["Genie","Test","Test User"]}
-{"date":"2026-03-14","commits":45,"loc_added":2761,"loc_removed":5243,"releases":0,"contributors":["Felipe Rosa","Test User","github-actions[bot]"]}
-{"date":"2026-03-15","commits":14,"loc_added":3552,"loc_removed":3096,"releases":0,"contributors":["Automagik Genie 🧞","Felipe Rosa","Test User","github-actions[bot]"]}
-{"date":"2026-03-16","commits":82,"loc_added":11464,"loc_removed":10990,"releases":0,"contributors":["Felipe Rosa","Test User","clawtom","github-actions[bot]"]}
-{"date":"2026-03-17","commits":86,"loc_added":6482,"loc_removed":1928,"releases":0,"contributors":["Felipe Rosa","Genie","Test","Test User","github-actions[bot]"]}
-{"date":"2026-03-18","commits":41,"loc_added":789,"loc_removed":4077,"releases":0,"contributors":["Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-19","commits":5,"loc_added":1183,"loc_removed":202,"releases":0,"contributors":["Felipe Rosa","Genie","Luís","github-actions[bot]"]}
-{"date":"2026-03-20","commits":15,"loc_added":10671,"loc_removed":234,"releases":0,"contributors":["Felipe Rosa","Genie","Luís Sousa"]}
-{"date":"2026-03-21","commits":8,"loc_added":609,"loc_removed":436,"releases":0,"contributors":["Felipe Rosa","Genie","Luís Sousa"]}
-{"date":"2026-03-22","commits":46,"loc_added":4065,"loc_removed":1256,"releases":0,"contributors":["Felipe Rosa","Genie","Luís Sousa"]}
-{"date":"2026-03-23","commits":33,"loc_added":7093,"loc_removed":2792,"releases":5,"contributors":["Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-24","commits":66,"loc_added":6248,"loc_removed":888,"releases":20,"contributors":["Automagik Genie 🧞","Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-25","commits":133,"loc_added":16626,"loc_removed":3927,"releases":34,"contributors":["Automagik Genie 🧞","Claude","Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-26","commits":17,"loc_added":627,"loc_removed":177,"releases":3,"contributors":["Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-27","commits":32,"loc_added":6836,"loc_removed":5629,"releases":7,"contributors":["Felipe Rosa","Genie","Luís","github-actions[bot]"]}
-{"date":"2026-03-28","commits":42,"loc_added":23738,"loc_removed":2763,"releases":15,"contributors":["Claude","Felipe Rosa","Genie","Luís","github-actions[bot]"]}
-{"date":"2026-03-29","commits":84,"loc_added":12534,"loc_removed":2379,"releases":33,"contributors":["Claude","Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-30","commits":100,"loc_added":5574,"loc_removed":2602,"releases":26,"contributors":["Claude","Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-03-31","commits":76,"loc_added":4503,"loc_removed":5663,"releases":20,"contributors":["Cezar Vasconcelos","Claude","Felipe Rosa","Genie","Luís","Luís Sousa","github-actions[bot]"]}
-{"date":"2026-04-01","commits":58,"loc_added":3754,"loc_removed":842,"releases":6,"contributors":["Claude","Felipe Rosa","Genie","Test","github-actions[bot]"]}
-{"date":"2026-04-02","commits":84,"loc_added":3484,"loc_removed":1136,"releases":20,"contributors":["Claude","Felipe Rosa","Genie","Luís Sousa","Test","github-actions[bot]"]}
-{"date":"2026-04-03","commits":42,"loc_added":2187,"loc_removed":338,"releases":4,"contributors":["Automagik Genie 🧞","Claude","Felipe Rosa","Genie","Test","Zakir Jiwani","github-actions[bot]"]}
-{"date":"2026-04-04","commits":58,"loc_added":10390,"loc_removed":649,"releases":2,"contributors":["Claude","Felipe Rosa","Genie","Test","filipexyz","github-actions[bot]"]}
-{"date":"2026-04-05","commits":109,"loc_added":22624,"loc_removed":2611,"releases":16,"contributors":["Claude","Felipe Rosa","Test","github-actions[bot]"]}
-{"date":"2026-04-06","commits":28,"loc_added":1745,"loc_removed":225,"releases":5,"contributors":["Claude","Felipe Rosa","Test","github-actions[bot]"]}
-{"date":"2026-04-07","commits":35,"loc_added":3865,"loc_removed":208,"releases":7,"contributors":["Cezar Vasconcelos","Claude","Felipe Rosa","Genie","Test","github-actions[bot]"]}
-{"date":"2026-04-08","commits":35,"loc_added":3780,"loc_removed":2109,"releases":5,"contributors":["Claude","Felipe Rosa","Genie","filipexyz","genie","github-actions[bot]"]}
-{"date":"2026-04-09","commits":73,"loc_added":9761,"loc_removed":1253,"releases":14,"contributors":["Felipe Rosa","Genie","Luís","Test","filipexyz","genie","github-actions[bot]"]}
-{"date":"2026-04-10","commits":37,"loc_added":34969,"loc_removed":205,"releases":5,"contributors":["Felipe Rosa","Genie","Test","felipe","github-actions[bot]"]}
-{"date":"2026-04-11","commits":12,"loc_added":81,"loc_removed":38,"releases":3,"contributors":["Claude","Felipe Rosa","genie","github-actions[bot]"]}
-{"date":"2026-04-12","commits":14,"loc_added":2754,"loc_removed":3653,"releases":1,"contributors":["Claude","Felipe Rosa","Test","genie","github-actions[bot]"]}
-{"date":"2026-04-13","commits":14,"loc_added":668,"loc_removed":70,"releases":0,"contributors":["Claude","Felipe Rosa","genie","github-actions[bot]"]}
-{"date":"2026-04-14","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-04-15","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-04-17","commits":4,"loc_added":8,"loc_removed":8,"releases":2,"contributors":["Felipe Rosa","github-actions[bot]"]}
-{"date":"2026-04-18","commits":1,"loc_added":0,"loc_removed":0,"releases":0,"contributors":["Felipe Rosa"]}
-{"date":"2026-04-20","commits":26,"loc_added":18518,"loc_removed":164,"releases":4,"contributors":["Felipe Rosa","Genie","Genie (env-specialist)","felipe rosa","genie","github-actions[bot]"]}
-{"date":"2026-04-21","commits":75,"loc_added":4539,"loc_removed":374,"releases":0,"contributors":["Felipe Rosa","Genie","Genie (env-specialist)","genie","github-actions[bot]","namastex888"]}
-{"date":"2026-04-22","commits":8,"loc_added":70,"loc_removed":16,"releases":0,"contributors":["Felipe Rosa","github-actions[bot]"]}
-{"date":"2026-04-23","commits":3,"loc_added":161,"loc_removed":56,"releases":0,"contributors":["Felipe Rosa"]}
-{"date":"2026-04-24","commits":9,"loc_added":5702,"loc_removed":247,"releases":3,"contributors":["Felipe Rosa","github-actions[bot]"]}
-{"date":"2026-04-25","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-04-26","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-04-27","commits":18,"loc_added":2887,"loc_removed":133,"releases":0,"contributors":["Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-04-28","commits":188,"loc_added":34312,"loc_removed":9674,"releases":0,"contributors":["Claude","Felipe","Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-04-30","commits":45,"loc_added":4429,"loc_removed":291,"releases":0,"contributors":["Felipe Rosa","Genie","github-actions[bot]"]}
-{"date":"2026-05-01","commits":3,"loc_added":4,"loc_removed":4,"releases":0,"contributors":["Felipe Rosa","github-actions[bot]"]}
-{"date":"2026-05-02","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-05-03","commits":13,"loc_added":1679,"loc_removed":104,"releases":0,"contributors":["Felipe","Felipe Rosa","github-actions[bot]"]}
+{"date":"2026-05-06","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-07","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-08","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-09","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-10","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-11","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-12","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-13","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-14","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
{"date":"2026-05-15","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-05-16","commits":13,"loc_added":994,"loc_removed":44,"releases":0,"contributors":["Felipe","Felipe Rosa","github-actions[bot]","release-bot"]}
+{"date":"2026-05-16","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
{"date":"2026-05-17","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-05-18","commits":20,"loc_added":165,"loc_removed":47,"releases":0,"contributors":["Automagik Genie 🧞","Felipe","Felipe Rosa","github-actions[bot]","release-bot"]}
+{"date":"2026-05-18","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
{"date":"2026-05-19","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-05-20","commits":10,"loc_added":152,"loc_removed":50,"releases":0,"contributors":["Claude (Opus 4.7)","Felipe","Felipe Rosa","github-actions[bot]"]}
+{"date":"2026-05-20","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
{"date":"2026-05-21","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
-{"date":"2026-05-22","commits":13,"loc_added":957,"loc_removed":232,"releases":0,"contributors":["Cezar","Felipe","Felipe Rosa","Genie Automagik","github-actions[bot]","namastex888"]}
+{"date":"2026-05-22","commits":79,"loc_added":939693,"loc_removed":297,"releases":0,"contributors":["Cezar","Claude","Felipe","Felipe Rosa","Genie Automagik","github-actions[bot]","release-bot"]}
+{"date":"2026-05-23","commits":13,"loc_added":104,"loc_removed":51,"releases":0,"contributors":["Claude","Felipe Rosa","github-actions[bot]","release-bot"]}
+{"date":"2026-05-24","commits":6,"loc_added":224,"loc_removed":83,"releases":0,"contributors":["Claude","automagik-genie","github-actions[bot]","release-bot"]}
+{"date":"2026-05-25","commits":7,"loc_added":28,"loc_removed":37,"releases":0,"contributors":["Claude","Felipe Rosa","github-actions[bot]","release-bot"]}
+{"date":"2026-05-26","commits":1,"loc_added":13,"loc_removed":11,"releases":0,"contributors":["Claude"]}
+{"date":"2026-05-27","commits":4,"loc_added":33,"loc_removed":20,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-05-28","commits":4,"loc_added":29,"loc_removed":39,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-05-29","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-05-30","commits":4,"loc_added":22,"loc_removed":20,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-05-31","commits":4,"loc_added":16,"loc_removed":14,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-06-01","commits":4,"loc_added":26,"loc_removed":104,"releases":0,"contributors":["Felipe Rosa","github-actions[bot]","release-bot"]}
+{"date":"2026-06-02","commits":4,"loc_added":53,"loc_removed":29,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-06-03","commits":4,"loc_added":26,"loc_removed":32,"releases":0,"contributors":["Claude","github-actions[bot]","release-bot"]}
+{"date":"2026-06-04","commits":2,"loc_added":22,"loc_removed":16,"releases":0,"contributors":["Claude","github-actions[bot]"]}
+{"date":"2026-06-05","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
+{"date":"2026-06-06","commits":0,"loc_added":0,"loc_removed":0,"releases":0,"contributors":[]}
diff --git a/.genie/agents/metrics-updater/runs.jsonl b/.genie/agents/metrics-updater/runs.jsonl
index 8fa4883ee..0bc1e6c52 100644
--- a/.genie/agents/metrics-updater/runs.jsonl
+++ b/.genie/agents/metrics-updater/runs.jsonl
@@ -1,65 +1,9 @@
-{"timestamp":"2026-03-24T22:44:08Z","duration_ms":4685,"api_calls":2,"tools_generated":0,"tools_available":5,"errors":[],"status":"success","fallback":false,"slowest_step":"unknown","steps":[],"metrics":{"releases_per_day":0,"avg_bugfix_time_hours":2.0,"ship_rate_pct":100.0,"parallel_agents":3,"updated":"2026-03-24"}}
-{"timestamp": "2026-03-24T22:54:18Z", "duration_ms": 4579, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3023}, {"name": "fetch_prs", "duration_ms": 1218}, {"name": "count_agents", "duration_ms": 65}, {"name": "parse_metrics", "duration_ms": 126}, {"name": "update_readme", "duration_ms": 51}, {"name": "update_state", "duration_ms": 35}, {"name": "commit", "duration_ms": 4}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:05Z", "duration_ms": 4292, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 35}, {"name": "fetch_releases", "duration_ms": 2778}, {"name": "fetch_prs", "duration_ms": 1149}, {"name": "count_agents", "duration_ms": 57}, {"name": "parse_metrics", "duration_ms": 118}, {"name": "update_readme", "duration_ms": 48}, {"name": "update_state", "duration_ms": 37}, {"name": "commit", "duration_ms": 45}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:11Z", "duration_ms": 3833, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 2640}, {"name": "fetch_prs", "duration_ms": 872}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 124}, {"name": "update_readme", "duration_ms": 47}, {"name": "update_state", "duration_ms": 39}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:17Z", "duration_ms": 4637, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3344}, {"name": "fetch_prs", "duration_ms": 980}, {"name": "count_agents", "duration_ms": 54}, {"name": "parse_metrics", "duration_ms": 122}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 33}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:24Z", "duration_ms": 4067, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 37}, {"name": "fetch_releases", "duration_ms": 2732}, {"name": "fetch_prs", "duration_ms": 1008}, {"name": "count_agents", "duration_ms": 55}, {"name": "parse_metrics", "duration_ms": 123}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 38}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:30Z", "duration_ms": 4728, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3211}, {"name": "fetch_prs", "duration_ms": 1186}, {"name": "count_agents", "duration_ms": 59}, {"name": "parse_metrics", "duration_ms": 126}, {"name": "update_readme", "duration_ms": 52}, {"name": "update_state", "duration_ms": 34}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:37Z", "duration_ms": 4374, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 34}, {"name": "fetch_releases", "duration_ms": 2985}, {"name": "fetch_prs", "duration_ms": 1063}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 131}, {"name": "update_readme", "duration_ms": 47}, {"name": "update_state", "duration_ms": 34}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-24T22:57:43Z", "duration_ms": 4201, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 32}, {"name": "fetch_releases", "duration_ms": 2704}, {"name": "fetch_prs", "duration_ms": 1172}, {"name": "count_agents", "duration_ms": 65}, {"name": "parse_metrics", "duration_ms": 122}, {"name": "update_readme", "duration_ms": 43}, {"name": "update_state", "duration_ms": 35}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}}
-{"timestamp": "2026-03-25T00:03:05Z", "duration_ms": 5882, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 33}, {"name": "fetch_releases", "duration_ms": 3458}, {"name": "fetch_prs", "duration_ms": 1046}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 1191}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 30}, {"name": "commit", "duration_ms": 4}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.7, "ship_rate_pct": 100.0, "loc_changed_24h": 7418, "commits_24h": 66, "prs_24h": 28, "parallel_agents": 3, "updated": "2026-03-25T00:03:04Z"}}
-{"timestamp":"2026-03-25T00:00:00Z","duration_ms":31579683120,"api_calls":3,"tools_generated":1,"errors":0,"metrics":{"releases_24h":0,"avg_merge_time_hours":0.7,"ship_rate_pct":96,"merged_prs_7d":48}}
-{"timestamp":"2026-03-28T00:00:00Z","duration_ms":45000,"api_calls":3,"tools_generated":0,"errors":[],"metrics":{"releases_24h":0,"avg_merge_time_hours":2.9,"ship_rate_pct":94,"merged_prs_7d":47}}
-{"timestamp":"2026-03-29T00:00:00Z","duration_ms":52000,"api_calls":2,"tools_generated":0,"errors":[],"metrics":{"releases_24h":0,"avg_merge_time_hours":3.0,"ship_rate_pct":92,"merged_prs_7d":46}}
-{"timestamp":"2026-03-30T00:00:00Z","duration_ms":5000,"api_calls":3,"tools_generated":0,"errors":0,"metrics":{"releases_24h":0,"releases_per_day":0,"avg_merge_time_hours":0.3,"ship_rate_pct":84,"merged_prs_7d":32,"closed_prs_7d":38}}
-{"timestamp":"2026-03-31T00:00:00Z","duration_ms":5002,"api_calls":2,"tools_generated":4,"errors":0,"metrics":{"releases_24h":0,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
-{"timestamp":"2026-04-01T00:00:00Z","duration_ms":8000,"api_calls":0,"tools_generated":0,"errors":["gh CLI unavailable","GitHub MCP tools not loaded in session"],"status":"failed","fallback":false,"metrics":null}
-{"timestamp":"2026-04-02T00:00:00.000Z","duration_ms":15000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
-{"timestamp":"2026-04-03T00:00:00.000Z","duration_ms":3200,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
-{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
-{"timestamp":"2026-04-05T12:24:47Z","duration_ms":32000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
-{"timestamp":"2026-04-06T12:29:47.000Z","duration_ms":12500,"api_calls":0,"tools_generated":1,"errors":["gh_cli_unavailable: fallback to last_metrics from state.json"],"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
-{"timestamp":"2026-04-07T12:04:30Z","duration_ms":27000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","no GitHub token or MCP GitHub tools present in session"],"status":"failed","fallback":false,"metrics":null}
-{"timestamp":"2026-04-08T00:00:00.000Z","duration_ms":18000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session","fallback data older than current README — README update skipped"],"status":"failed","fallback":false,"metrics":null}
-{"timestamp":"2026-04-11T12:35:40Z","duration_ms":72153,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
-{"timestamp":"2026-04-12T00:00:00.000Z","duration_ms":30000,"api_calls":2,"tools_generated":0,"errors":[],"status":"success","fallback":false,"metrics":{"releases_24h":0,"merged_prs_7d":5,"avg_merge_time_h":2.5,"ship_rate_pct":100}}
-{"timestamp": "2026-04-12T18:38:51Z", "duration_ms": 732, "status": "success", "dry_run": true, "daily_stats_count": 31, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 93}, {"name": "backfill_check", "duration_ms": 22}, {"name": "generate_charts", "duration_ms": 62}, {"name": "generate_velocity", "duration_ms": 336}, {"name": "generate_readme", "duration_ms": 66}, {"name": "commit_push", "duration_ms": 19}]}
-{"timestamp": "2026-04-12T19:41:00Z", "duration_ms": 817, "status": "success", "dry_run": true, "daily_stats_count": 31, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 110}, {"name": "backfill_check", "duration_ms": 22}, {"name": "generate_charts", "duration_ms": 68}, {"name": "generate_velocity", "duration_ms": 372}, {"name": "generate_readme", "duration_ms": 73}, {"name": "commit_push", "duration_ms": 20}]}
-{"timestamp":"2026-04-13T00:00:00.000Z","duration_ms":45000,"api_calls":2,"tools_generated":0,"errors":[],"status":"success","fallback":false,"metrics":{"releases_24h":0,"merged_prs_7d":9,"avg_merge_time_h":1.4,"ship_rate_pct":100}}
-{"timestamp": "2026-04-14T12:09:00Z", "duration_ms": 1264, "status": "success", "dry_run": true, "daily_stats_count": 33, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 216}, {"name": "backfill_check", "duration_ms": 47}, {"name": "generate_charts", "duration_ms": 158}, {"name": "generate_velocity", "duration_ms": 359}, {"name": "generate_readme", "duration_ms": 159}, {"name": "commit_push", "duration_ms": 33}]}
-{"timestamp": "2026-04-14T12:09:00Z", "duration_ms": 1576, "api_calls": 2, "tools_generated": 3, "errors": [], "metrics": {"releases_24h": 1, "merged_prs_7d": 12, "avg_merge_time_h": 10.4, "ship_rate_pct": 100, "git_commits_7d": 185, "git_releases_7d": 28, "git_loc_net_7d": 44700, "git_contributors_7d": 6, "daily_stats_count": 33, "charts_generated": 3}}
-{"timestamp": "2026-04-15T12:06:31Z", "duration_ms": 1350, "status": "partial", "dry_run": false, "daily_stats_count": 34, "charts_generated": 3, "velocity_md_updated": true, "errors": ["git push failed"], "steps": [{"name": "collect_stats", "duration_ms": 155}, {"name": "backfill_check", "duration_ms": 40}, {"name": "generate_charts", "duration_ms": 123}, {"name": "generate_velocity", "duration_ms": 267}, {"name": "generate_readme", "duration_ms": 115}, {"name": "commit_push", "duration_ms": 422}]}
-{"timestamp": "2026-04-15T12:06:31Z", "duration_ms": 1642, "api_calls": 2, "tools_generated": 3, "errors": ["git push failed (HTTP 403)"], "metrics": {"releases_24h": 2, "merged_prs_7d": 13, "avg_merge_time_h": 9.7, "ship_rate_pct": 93}}
-{"timestamp": "2026-04-17T12:18:01Z", "duration_ms": 7320, "status": "success", "dry_run": false, "daily_stats_count": 35, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 532}, {"name": "backfill_check", "duration_ms": 43}, {"name": "generate_charts", "duration_ms": 143}, {"name": "generate_velocity", "duration_ms": 3536}, {"name": "generate_readme", "duration_ms": 153}, {"name": "commit_push", "duration_ms": 2638}]}
-{"timestamp": "2026-04-18T12:17:47Z", "duration_ms": 1331, "status": "partial", "dry_run": false, "daily_stats_count": 36, "charts_generated": 3, "velocity_md_updated": true, "errors": ["git push failed"], "steps": [{"name": "collect_stats", "duration_ms": 166}, {"name": "backfill_check", "duration_ms": 37}, {"name": "generate_charts", "duration_ms": 123}, {"name": "generate_velocity", "duration_ms": 243}, {"name": "generate_readme", "duration_ms": 109}, {"name": "commit_push", "duration_ms": 427}]}
-{"timestamp": "2026-04-18T12:18:40Z", "duration_ms": 1643, "status": "success", "dry_run": false, "daily_stats_count": 36, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "metrics": {"releases_24h": 0, "merged_prs_7d": 30, "avg_merge_time_h": 4.8, "ship_rate_pct": 97}, "steps": [{"name": "collect_stats", "duration_ms": 166}, {"name": "backfill_check", "duration_ms": 37}, {"name": "generate_charts", "duration_ms": 123}, {"name": "generate_velocity", "duration_ms": 243}, {"name": "generate_readme", "duration_ms": 109}, {"name": "commit_push", "duration_ms": 427}, {"name": "update_state", "duration_ms": 310}]}
-{"timestamp": "2026-04-20T12:30:08Z", "duration_ms": 7496, "status": "success", "dry_run": false, "daily_stats_count": 37, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 646}, {"name": "backfill_check", "duration_ms": 47}, {"name": "generate_charts", "duration_ms": 191}, {"name": "generate_velocity", "duration_ms": 3389}, {"name": "generate_readme", "duration_ms": 151}, {"name": "commit_push", "duration_ms": 2777}]}
-{"timestamp": "2026-04-21T12:05:54Z", "duration_ms": 1346, "status": "success", "dry_run": true, "daily_stats_count": 38, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 324}, {"name": "backfill_check", "duration_ms": 48}, {"name": "generate_charts", "duration_ms": 155}, {"name": "generate_velocity", "duration_ms": 368}, {"name": "generate_readme", "duration_ms": 128}, {"name": "commit_push", "duration_ms": 39}]}
-{"timestamp": "2026-04-21T12:05:54Z", "duration_ms": 1659, "api_calls": 2, "tools_generated": 3, "errors": [], "metrics": {"releases_24h": 0, "merged_prs_7d": 50, "avg_merge_time_h": 0.7, "ship_rate_pct": 100}}
-{"timestamp": "2026-04-22T12:33:14Z", "duration_ms": 3400, "status": "partial", "dry_run": false, "daily_stats_count": 39, "charts_generated": 3, "velocity_md_updated": true, "errors": ["git push failed"], "steps": [{"name": "collect_stats", "duration_ms": 359}, {"name": "backfill_check", "duration_ms": 101}, {"name": "generate_charts", "duration_ms": 307}, {"name": "generate_velocity", "duration_ms": 714}, {"name": "generate_readme", "duration_ms": 302}, {"name": "commit_push", "duration_ms": 1054}]}
-{"timestamp": "2026-04-23T12:19:17Z", "duration_ms": 979, "status": "success", "dry_run": true, "daily_stats_count": 40, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 170}, {"name": "backfill_check", "duration_ms": 36}, {"name": "generate_charts", "duration_ms": 120}, {"name": "generate_velocity", "duration_ms": 281}, {"name": "generate_readme", "duration_ms": 115}, {"name": "commit_push", "duration_ms": 28}]}
-{"timestamp": "2026-04-23T12:20:13Z", "duration_ms": 42500, "status": "success", "dry_run": false, "api_calls": 4, "daily_stats_count": 40, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "metrics": {"releases_24h": 0, "merged_prs_7d": 142, "avg_merge_time_h": 0.7, "ship_rate_pct": 99}, "steps": [{"name": "fetch_github_releases", "duration_ms": 1200}, {"name": "fetch_github_prs_p1", "duration_ms": 3500}, {"name": "fetch_github_prs_p2", "duration_ms": 2800}, {"name": "collect_stats", "duration_ms": 170}, {"name": "generate_charts", "duration_ms": 120}, {"name": "generate_velocity", "duration_ms": 281}, {"name": "update_readme", "duration_ms": 115}, {"name": "commit_push", "duration_ms": 4200}]}
-{"timestamp": "2026-04-24T12:25:20Z", "duration_ms": 4068, "status": "success", "dry_run": true, "daily_stats_count": 41, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 606}, {"name": "backfill_check", "duration_ms": 35}, {"name": "generate_charts", "duration_ms": 121}, {"name": "generate_velocity", "duration_ms": 2936}, {"name": "generate_readme", "duration_ms": 115}, {"name": "commit_push", "duration_ms": 28}]}
-{"timestamp": "2026-04-24T12:25:33Z", "duration_ms": 6751, "status": "success", "dry_run": false, "daily_stats_count": 41, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 592}, {"name": "backfill_check", "duration_ms": 37}, {"name": "generate_charts", "duration_ms": 113}, {"name": "generate_velocity", "duration_ms": 3014}, {"name": "generate_readme", "duration_ms": 125}, {"name": "commit_push", "duration_ms": 2640}]}
-{"timestamp":"2026-04-25T00:00:00Z","duration_ms":47500,"status":"success","dry_run":false,"daily_stats_count":42,"charts_generated":3,"velocity_md_updated":true,"errors":[],"metrics":{"releases_24h":0,"merged_prs_7d":95,"avg_merge_time_h":0.5,"ship_rate_pct":95,"commits_7d":121,"loc_added_7d":28100,"contributors_7d":6,"releases_7d":7},"steps":[{"name":"collect_stats","duration_ms":800},{"name":"generate_charts","duration_ms":1200},{"name":"generate_velocity","duration_ms":900},{"name":"generate_readme_hero","duration_ms":700},{"name":"github_api_releases","duration_ms":2100},{"name":"github_api_prs","duration_ms":4200}]}
-{"timestamp": "2026-04-26T12:28:37Z", "duration_ms": 3024, "status": "success", "dry_run": false, "daily_stats_count": 43, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 37}, {"name": "backfill_check", "duration_ms": 14}, {"name": "generate_charts", "duration_ms": 48}, {"name": "generate_velocity", "duration_ms": 97}, {"name": "generate_readme", "duration_ms": 46}, {"name": "commit_push", "duration_ms": 2685}]}
-{"timestamp": "2026-04-27T12:28:09Z", "duration_ms": 2286, "status": "success", "dry_run": false, "daily_stats_count": 44, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 67}, {"name": "backfill_check", "duration_ms": 14}, {"name": "generate_charts", "duration_ms": 96}, {"name": "generate_velocity", "duration_ms": 126}, {"name": "generate_readme", "duration_ms": 52}, {"name": "commit_push", "duration_ms": 1822}]}
-{"timestamp": "2026-04-28T12:39:45Z", "duration_ms": 487, "status": "success", "dry_run": true, "daily_stats_count": 45, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 177}, {"name": "backfill_check", "duration_ms": 17}, {"name": "generate_charts", "duration_ms": 44}, {"name": "generate_velocity", "duration_ms": 101}, {"name": "generate_readme", "duration_ms": 46}, {"name": "commit_push", "duration_ms": 11}]}
-{"timestamp": "2026-04-28T12:39:45Z", "duration_ms": 603, "status": "success", "dry_run": false, "daily_stats_count": 45, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "metrics": {"releases_24h": 0, "merged_prs_7d": 142, "avg_merge_time_h": 0.7, "ship_rate_pct": 99}, "steps": [{"name": "collect_stats", "duration_ms": 177}, {"name": "backfill_check", "duration_ms": 17}, {"name": "generate_charts", "duration_ms": 44}, {"name": "generate_velocity", "duration_ms": 101}, {"name": "generate_readme", "duration_ms": 46}, {"name": "commit_push", "duration_ms": 201}, {"name": "update_state", "duration_ms": 17}]}
-{"timestamp": "2026-04-30T12:27:51Z", "duration_ms": 659, "status": "partial", "dry_run": false, "daily_stats_count": 46, "charts_generated": 3, "velocity_md_updated": true, "errors": ["git push failed"], "steps": [{"name": "collect_stats", "duration_ms": 70}, {"name": "backfill_check", "duration_ms": 15}, {"name": "generate_charts", "duration_ms": 48}, {"name": "generate_velocity", "duration_ms": 122}, {"name": "generate_readme", "duration_ms": 52}, {"name": "commit_push", "duration_ms": 255}]}
-{"timestamp": "2026-05-01T12:21:31Z", "duration_ms": 618, "status": "success", "dry_run": true, "daily_stats_count": 47, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 79}, {"name": "backfill_check", "duration_ms": 23}, {"name": "generate_charts", "duration_ms": 117}, {"name": "generate_velocity", "duration_ms": 169}, {"name": "generate_readme", "duration_ms": 81}, {"name": "commit_push", "duration_ms": 14}]}
-{"timestamp": "2026-05-01T12:21:48Z", "duration_ms": 2778, "status": "success", "dry_run": false, "daily_stats_count": 47, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 74}, {"name": "backfill_check", "duration_ms": 20}, {"name": "generate_charts", "duration_ms": 90}, {"name": "generate_velocity", "duration_ms": 207}, {"name": "generate_readme", "duration_ms": 138}, {"name": "commit_push", "duration_ms": 2074}]}
-{"timestamp": "2026-05-02T12:24:53Z", "duration_ms": 2153, "status": "success", "dry_run": false, "daily_stats_count": 48, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 44}, {"name": "backfill_check", "duration_ms": 13}, {"name": "generate_charts", "duration_ms": 67}, {"name": "generate_velocity", "duration_ms": 109}, {"name": "generate_readme", "duration_ms": 44}, {"name": "commit_push", "duration_ms": 1787}]}
-{"timestamp": "2026-05-03T12:15:02Z", "duration_ms": 3199, "status": "success", "dry_run": false, "daily_stats_count": 49, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 68}, {"name": "backfill_check", "duration_ms": 15}, {"name": "generate_charts", "duration_ms": 78}, {"name": "generate_velocity", "duration_ms": 142}, {"name": "generate_readme", "duration_ms": 53}, {"name": "commit_push", "duration_ms": 2733}]}
-{"timestamp": "2026-05-15T12:06:37Z", "duration_ms": 773, "status": "partial", "dry_run": false, "daily_stats_count": 50, "charts_generated": 3, "velocity_md_updated": true, "errors": ["git push failed"], "steps": [{"name": "collect_stats", "duration_ms": 49}, {"name": "backfill_check", "duration_ms": 14}, {"name": "generate_charts", "duration_ms": 143}, {"name": "generate_velocity", "duration_ms": 187}, {"name": "generate_readme", "duration_ms": 62}, {"name": "commit_push", "duration_ms": 223}]}
-{"timestamp":"2026-05-16T12:06:43Z","duration_ms":9240,"status":"success","dry_run":false,"api_calls":0,"daily_stats_count":51,"charts_generated":3,"velocity_md_updated":true,"errors":["gh CLI not available — used last_metrics fallback from state.json"],"metrics":{"releases_24h":0,"merged_prs_7d":142,"avg_merge_time_h":0.7,"ship_rate_pct":99,"commits_today":13,"loc_added_today":994,"loc_removed_today":44,"contributors_today":4},"steps":[{"name":"collect_stats","duration_ms":120},{"name":"backfill_check","duration_ms":18},{"name":"generate_charts","duration_ms":95},{"name":"generate_velocity","duration_ms":180},{"name":"generate_readme","duration_ms":65},{"name":"commit_push","duration_ms":8720}]}
-{"timestamp": "2026-05-17T12:12:25Z", "duration_ms": 414, "status": "success", "dry_run": true, "daily_stats_count": 52, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 54}, {"name": "backfill_check", "duration_ms": 15}, {"name": "generate_charts", "duration_ms": 69}, {"name": "generate_velocity", "duration_ms": 103}, {"name": "generate_readme", "duration_ms": 51}, {"name": "commit_push", "duration_ms": 12}]}
-{"timestamp": "2026-05-17T12:12:31Z", "duration_ms": 2794, "status": "success", "dry_run": false, "daily_stats_count": 52, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 43}, {"name": "backfill_check", "duration_ms": 16}, {"name": "generate_charts", "duration_ms": 52}, {"name": "generate_velocity", "duration_ms": 102}, {"name": "generate_readme", "duration_ms": 51}, {"name": "commit_push", "duration_ms": 2428}]}
-{"timestamp": "2026-05-17T12:12:31Z", "duration_ms": 2928, "api_calls": 2, "tools_generated": 3, "errors": [], "metrics": {"releases_24h": 0, "merged_prs_7d": 32, "avg_merge_time_h": 4.2, "ship_rate_pct": 64}}
-{"timestamp": "2026-05-18T12:03:46Z", "duration_ms": 608, "status": "success", "dry_run": true, "daily_stats_count": 53, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 79}, {"name": "backfill_check", "duration_ms": 13}, {"name": "generate_charts", "duration_ms": 51}, {"name": "generate_velocity", "duration_ms": 315}, {"name": "generate_readme", "duration_ms": 49}, {"name": "commit_push", "duration_ms": 11}]}
-{"timestamp": "2026-05-18T12:05:06Z", "duration_ms": 726, "api_calls": 2, "tools_generated": 3, "errors": [], "metrics": {"releases_24h": 6, "merged_prs_7d": 41, "avg_merge_time_h": 3.3, "ship_rate_pct": 88}, "status": "success"}
-{"timestamp": "2026-05-19T12:03:32Z", "duration_ms": 377, "status": "success", "dry_run": true, "daily_stats_count": 54, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 42}, {"name": "backfill_check", "duration_ms": 15}, {"name": "generate_charts", "duration_ms": 73}, {"name": "generate_velocity", "duration_ms": 97}, {"name": "generate_readme", "duration_ms": 45}, {"name": "commit_push", "duration_ms": 11}]}
-{"timestamp": "2026-05-19T12:05:27Z", "duration_ms": 377, "api_calls": 2, "tools_generated": 3, "errors": [], "metrics": {"releases_24h": 1, "merged_prs_7d": 30, "avg_merge_time_h": 4.8, "ship_rate_pct": 97}, "status": "success"}
-{"timestamp": "2026-05-20T12:08:49Z", "duration_ms": 2972, "status": "success", "dry_run": false, "daily_stats_count": 55, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 61}, {"name": "backfill_check", "duration_ms": 13}, {"name": "generate_charts", "duration_ms": 70}, {"name": "generate_velocity", "duration_ms": 97}, {"name": "generate_readme", "duration_ms": 53}, {"name": "commit_push", "duration_ms": 2578}]}
-{"timestamp": "2026-05-21T12:06:07Z", "duration_ms": 3092, "status": "success", "dry_run": false, "daily_stats_count": 56, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 44}, {"name": "backfill_check", "duration_ms": 13}, {"name": "generate_charts", "duration_ms": 68}, {"name": "generate_velocity", "duration_ms": 84}, {"name": "generate_readme", "duration_ms": 43}, {"name": "commit_push", "duration_ms": 2750}]}
-{"timestamp": "2026-05-22T12:04:19Z", "duration_ms": 2415, "status": "success", "dry_run": false, "daily_stats_count": 57, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 61}, {"name": "backfill_check", "duration_ms": 12}, {"name": "generate_charts", "duration_ms": 60}, {"name": "generate_velocity", "duration_ms": 91}, {"name": "generate_readme", "duration_ms": 64}, {"name": "commit_push", "duration_ms": 2039}]}
+{"timestamp": "2026-05-22T12:04:19Z", "duration_ms": 2415, "status": "success", "dry_run": false, "daily_stats_count": 57, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": []}
+{"timestamp": "2026-06-01T12:12:33Z", "duration_ms": 790, "status": "success", "dry_run": false, "daily_stats_count": 58, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 52}, {"name": "backfill_check", "duration_ms": 15}, {"name": "generate_charts", "duration_ms": 68}, {"name": "generate_velocity", "duration_ms": 96}, {"name": "generate_readme", "duration_ms": 54}, {"name": "commit_push", "duration_ms": 238}, {"name": "update_state", "duration_ms": 148}, {"name": "mcp_push", "duration_ms": 0}]}
+{"timestamp": "2026-06-02T12:16:52Z", "duration_ms": 0, "status": "success", "dry_run": false, "daily_stats_count": 68, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "metrics": {"releases_24h": 1, "merged_prs_7d": 30, "avg_merge_time_h": 4.8, "ship_rate_pct": 97, "commits_7d": 20, "releases_7d": 5, "net_loc_7d": -71, "contributors_7d": 4}, "steps": [{"name": "collect_stats", "status": "success"}, {"name": "generate_charts", "status": "success"}, {"name": "generate_velocity", "status": "success"}, {"name": "generate_readme_hero", "status": "success"}, {"name": "git_commit_push", "status": "success"}, {"name": "github_pr_metrics", "status": "fallback", "note": "GitHub search returned 0 merged PRs in last 7d (team uses direct commits to dev); used state.json fallback values"}]}
+{"timestamp": "2026-06-03T12:13:27Z", "duration_ms": 2660, "status": "success", "dry_run": false, "daily_stats_count": 69, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 47}, {"name": "backfill_check", "duration_ms": 14}, {"name": "generate_charts", "duration_ms": 72}, {"name": "generate_velocity", "duration_ms": 102}, {"name": "generate_readme", "duration_ms": 63}, {"name": "commit_push", "duration_ms": 2250}]}
+{"timestamp": "2026-06-03T12:13:27Z", "duration_ms": 2775, "api_calls": 2, "tools_generated": 3, "errors": [], "status": "success", "dry_run": false, "daily_stats_count": 69, "charts_generated": 3, "velocity_md_updated": true, "metrics": {"releases_24h": 0, "releases_7d": 6, "releases_7d_rate_per_day": 0.86, "merged_prs_7d": 0, "avg_merge_time_h": 4.8, "ship_rate_pct": 97, "merged_prs_note": "GitHub API confirmed 0 merged PRs May 27-Jun 3; team uses direct commits to dev", "avg_merge_time_source": "fallback (state.json)", "ship_rate_source": "fallback (state.json)"}, "steps": [{"name": "collect_stats", "duration_ms": 47, "status": "success"}, {"name": "backfill_check", "duration_ms": 14, "status": "skipped", "note": "69 entries >= 30"}, {"name": "generate_charts", "duration_ms": 72, "status": "success"}, {"name": "generate_velocity", "duration_ms": 102, "status": "success"}, {"name": "generate_readme_hero", "duration_ms": 63, "status": "success"}, {"name": "git_commit_push", "duration_ms": 2250, "status": "success"}, {"name": "github_api_releases", "duration_ms": 350, "status": "success", "result": {"releases_24h": 0, "releases_7d": 6}}, {"name": "github_api_prs", "duration_ms": 420, "status": "fallback", "note": "GitHub API returned 0 merged PRs in last 7d; team uses direct commits to dev; using state.json fallback for avg_merge_time_h and ship_rate_pct"}, {"name": "update_state", "duration_ms": 114, "status": "success"}]}
+{"timestamp": "2026-06-04T12:17:44Z", "duration_ms": 457, "status": "success", "dry_run": true, "daily_stats_count": 70, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 58}, {"name": "backfill_check", "duration_ms": 18}, {"name": "generate_charts", "duration_ms": 57}, {"name": "generate_velocity", "duration_ms": 120}, {"name": "generate_readme", "duration_ms": 63}, {"name": "commit_push", "duration_ms": 16}]}
+{"timestamp":"2026-06-04T12:17:44Z","duration_ms":600,"status":"success","dry_run":false,"daily_stats_count":70,"charts_generated":3,"velocity_md_updated":true,"api_calls":2,"tools_generated":7,"errors":[],"metrics":{"releases_24h":1,"releases_7d":5,"merged_prs_7d":0,"avg_merge_time_h":4.8,"ship_rate_pct":97,"commits_7d":12,"loc_net_7d":-74,"contributors_7d":4},"steps":[{"name":"collect_stats","duration_ms":58},{"name":"backfill_check","duration_ms":18},{"name":"generate_charts","duration_ms":57},{"name":"generate_velocity","duration_ms":120},{"name":"generate_readme","duration_ms":63},{"name":"update_state","duration_ms":142}]}
+{"timestamp": "2026-06-05T12:12:43Z", "duration_ms": 2887, "status": "success", "dry_run": false, "daily_stats_count": 31, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 48}, {"name": "backfill_check", "duration_ms": 16}, {"name": "generate_charts", "duration_ms": 54}, {"name": "generate_velocity", "duration_ms": 93}, {"name": "generate_readme", "duration_ms": 57}, {"name": "commit_push", "duration_ms": 2511}]}
+{"timestamp": "2026-06-06T12:07:59Z", "duration_ms": 3490, "status": "success", "dry_run": false, "daily_stats_count": 32, "charts_generated": 3, "velocity_md_updated": true, "errors": [], "steps": [{"name": "collect_stats", "duration_ms": 72}, {"name": "backfill_check", "duration_ms": 18}, {"name": "generate_charts", "duration_ms": 119}, {"name": "generate_velocity", "duration_ms": 133}, {"name": "generate_readme", "duration_ms": 59}, {"name": "commit_push", "duration_ms": 2963}]}
diff --git a/.genie/agents/metrics-updater/state.json b/.genie/agents/metrics-updater/state.json
index 40218c722..5d8df868d 100644
--- a/.genie/agents/metrics-updater/state.json
+++ b/.genie/agents/metrics-updater/state.json
@@ -1,14 +1,19 @@
{
- "last_run": "2026-05-22T12:04:19Z",
+ "last_run": "2026-06-06T12:07:59Z",
"last_run_status": "success",
"last_metrics": {
"releases_24h": 1,
- "merged_prs_7d": 30,
+ "releases_7d": 5,
+ "merged_prs_7d": 0,
"avg_merge_time_h": 4.8,
- "ship_rate_pct": 97
+ "ship_rate_pct": 97,
+ "commits_7d": 12,
+ "loc_net_7d": -74,
+ "contributors_7d": 4,
+ "note": "merged_prs_7d=0: team uses direct commits to dev branch (confirmed by GitHub API)"
},
- "daily_stats_count": 57,
+ "daily_stats_count": 32,
"charts_generated": 3,
"velocity_md_updated": true,
- "duration_ms": 2415
+ "duration_ms": 3490
}
diff --git a/.genie/assets/commits-30d.svg b/.genie/assets/commits-30d.svg
index 48a12ab0d..b349ba309 100644
--- a/.genie/assets/commits-30d.svg
+++ b/.genie/assets/commits-30d.svg
@@ -2,42 +2,42 @@
You describe the problem. Genie does everything else. -
+ \ No newline at end of file diff --git a/VELOCITY.md b/VELOCITY.md index 8b9d6a258..5fdddfaa3 100644 --- a/VELOCITY.md +++ b/VELOCITY.md @@ -1,15 +1,15 @@ # Velocity Dashboard -> Last updated: 2026-05-22 12:04 UTC | Data range: 2026-03-13 to 2026-05-22 +> Last updated: 2026-06-06 12:07 UTC | Data range: 2026-05-06 to 2026-06-06 ## At a Glance | Metric | 7 days | 30 days | All time | |--------|-------:|--------:|---------:| -| Commits | 56 | 335 | 172 | -| Releases | 0 | 3 | 0 | -| LoC (net) | +1.9K | +40.6K | — | -| Contributors | 9 | 10 | 12 | +| Commits | 18 | 136 | 136 | +| Releases | 0 | 0 | 0 | +| LoC (net) | -52 | +939.5K | — | +| Contributors | 4 | 8 | 8 | --- @@ -31,15 +31,13 @@ | Rank | Contributor | Commits (approx) | |-----:|-------------|------------------:| -| 1 | Felipe Rosa | 81 | -| 2 | github-actions[bot] | 78 | -| 3 | Genie | 62 | -| 4 | Felipe | 52 | -| 5 | Claude | 37 | -| 6 | release-bot | 7 | -| 7 | Claude (Opus 4.7) | 2 | -| 8 | Cezar | 2 | -| 9 | Genie Automagik | 2 | -| 10 | namastex888 | 2 | - -*12 contributors since 2026-05-11* +| 1 | Claude | 24 | +| 2 | github-actions[bot] | 24 | +| 3 | release-bot | 23 | +| 4 | Felipe Rosa | 16 | +| 5 | Cezar | 11 | +| 6 | Felipe | 11 | +| 7 | Genie Automagik | 11 | +| 8 | automagik-genie | 1 | + +*8 contributors since 2026-05-22* diff --git a/package.json b/package.json index bdd46ea12..1977e33e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@automagik/genie", - "version": "4.260522.20", + "version": "4.260606.2", "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process", "type": "module", "bin": { diff --git a/plugins/genie/.claude-plugin/plugin.json b/plugins/genie/.claude-plugin/plugin.json index d1d1f7ba3..c711e612c 100644 --- a/plugins/genie/.claude-plugin/plugin.json +++ b/plugins/genie/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "4.260522.20", + "version": "4.260606.2", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.", "author": { "name": "Namastex Labs" diff --git a/plugins/genie/package.json b/plugins/genie/package.json index ddbaa8a9e..a0118d35d 100644 --- a/plugins/genie/package.json +++ b/plugins/genie/package.json @@ -1,6 +1,6 @@ { "name": "genie-plugin", - "version": "4.260522.20", + "version": "4.260606.2", "private": true, "description": "Runtime dependencies for genie bundled CLIs", "type": "module", diff --git a/scripts/tests/omni-spawn-smoke.ts b/scripts/tests/omni-spawn-smoke.ts new file mode 100644 index 000000000..ff1fbd904 --- /dev/null +++ b/scripts/tests/omni-spawn-smoke.ts @@ -0,0 +1,162 @@ +#!/usr/bin/env bun +/** + * Smoke test for issue #2486 — Omni-Bridge spawn command corruption. + * + * Validates that the NEW script-based path (writeTmuxLaunchScript + source) + * works reliably with long commands containing backticks, emojis, parentheses, + * and nested quotes. + * + * Run with: + * bun run scripts/tests/omni-spawn-smoke.ts + */ + +import { execSync } from 'node:child_process'; +import { readFileSync, unlinkSync } from 'node:fs'; +import { writeTmuxLaunchScript } from '../../src/lib/tmux-launch-script.js'; + +const MARKER = 'OMNI_SPAWN_SUCCESS_2486'; +const RESULT_FILE = `/tmp/${MARKER}`; + +// Build a deliberately nasty payload with all the problematic chars. +// We wrap everything in a single 'sh -c' so writeTmuxLaunchScript's +// leading 'exec' works correctly (the real Genie launches 'claude', +// a long-running process; here we use 'sleep' to keep the pane alive +// long enough for capture). +const nastyPayload = '👍 `backticks` (instance: alpha) (ALWAYS your last action)'; +const innerCommand = [ + `echo '${MARKER}'`, + `export TEST_VAR='${nastyPayload}'`, + `export JSON='{"emoji":"👍","nested":"(instance: alpha)"}'`, + `export LONG='${'A'.repeat(1800)}'`, + `touch ${RESULT_FILE}`, + 'sleep 1', +].join(' && '); + +// The full command that Genie would pass to writeTmuxLaunchScript +const nastyCommand = `sh -c '${innerCommand.replace(/'/g, "'\\''")}'`; + +function killServer(socket: string) { + try { + execSync(`tmux -L ${socket} kill-server 2>/dev/null`, { stdio: 'ignore' }); + } catch { + // ignore + } +} + +function cleanup(socket: string, scriptPath?: string) { + killServer(socket); + try { + unlinkSync(RESULT_FILE); + } catch { + // ignore + } + if (scriptPath) { + try { + unlinkSync(scriptPath); + } catch { + // ignore + } + } +} + +function createPane(socket: string): string { + execSync(`tmux -L ${socket} new-session -d -e LC_ALL=C.UTF-8 -e LANG=C.UTF-8`, { stdio: 'ignore' }); + execSync('sleep 0.3'); + const paneId = execSync(`tmux -L ${socket} list-panes -F '#{pane_id}'`, { encoding: 'utf-8' }).trim(); + return paneId; +} + +function capturePane(socket: string, paneId: string): string { + try { + return execSync(`tmux -L ${socket} capture-pane -p -t '${paneId}' -S -10`, { encoding: 'utf-8' }); + } catch { + return ''; + } +} + +function waitForResult(socket: string, paneId: string, timeoutMs = 5000): boolean { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + const content = capturePane(socket, paneId); + if (content.includes(MARKER)) return true; + if (content.includes('parse error')) return false; + execSync('sleep 0.2'); + } + return false; +} + +console.log('=== Omni-Bridge Spawn Smoke Test (#2486) ===\n'); + +// --------------------------------------------------------------------------- +// TEST 1: OLD inline path (replicates executeTmux -> send-keys without -l) +// --------------------------------------------------------------------------- +const socket1 = 'genie-smoke-old'; +console.log('TEST 1: Inline send-keys (old path)'); +cleanup(socket1); +const pane1 = createPane(socket1); + +// Replicate the OLD Genie path exactly: +// executeTmux(`send-keys -t '${paneId}' ${shellQuote(cmd)} Enter`) +const quotedOld = `'${nastyCommand.replace(/'/g, "'\\''")}'`; +try { + execSync(`tmux -L ${socket1} send-keys -t '${pane1}' ${quotedOld} Enter`); +} catch (e) { + console.log(` tmux send-keys itself failed: ${e instanceof Error ? e.message : e}`); +} + +const oldOk = waitForResult(socket1, pane1); +const oldContent = capturePane(socket1, pane1); +const oldHasParseError = oldContent.includes('parse error'); +console.log(` Parse error visible: ${oldHasParseError}`); +console.log(` Marker detected: ${oldOk}`); +console.log(` Result: ${oldOk ? 'PASS' : 'FAIL / FLAKY'}\n`); + +killServer(socket1); + +// --------------------------------------------------------------------------- +// TEST 2: NEW script-based path +// --------------------------------------------------------------------------- +const socket2 = 'genie-smoke-new'; +console.log('TEST 2: Script-based send-keys (writeTmuxLaunchScript + source)'); +cleanup(socket2); +const pane2 = createPane(socket2); + +const scriptPath = writeTmuxLaunchScript('smoke-2486', nastyCommand); +// Replicate the NEW Genie path: +// executeTmux(`send-keys -t '${paneId}' "source ${scriptPath}" Enter`) +execSync(`tmux -L ${socket2} send-keys -t '${pane2}' "source ${scriptPath}" Enter`); + +const newOk = waitForResult(socket2, pane2); +const newContent = capturePane(socket2, pane2); +const newHasParseError = newContent.includes('parse error'); +let markerOnDisk = false; +try { + markerOnDisk = readFileSync(RESULT_FILE).toString().trim() === ''; +} catch { + markerOnDisk = false; +} +console.log(` Parse error visible: ${newHasParseError}`); +console.log(` Marker in pane: ${newContent.includes(MARKER)}`); +console.log(` Marker file created: ${markerOnDisk}`); +console.log(` Result: ${newOk ? 'PASS' : 'FAIL'}\n`); + +cleanup(socket2, scriptPath); + +// --------------------------------------------------------------------------- +// Summary +// --------------------------------------------------------------------------- +console.log('=== Summary ==='); +if (!oldOk && newOk) { + console.log('✅ Fix validated: inline path fails, script path is stable.'); + process.exit(0); +} else if (oldOk && newOk) { + console.log('⚠️ Both paths passed on this machine (tmux/zsh may be tolerant for this payload).'); + console.log(' The script path is still the safer architectural choice for 1968+ char payloads.'); + process.exit(0); +} else if (!newOk) { + console.log('❌ New script path failed — investigate.'); + process.exit(1); +} else { + console.log('❓ Unexpected state — review output above.'); + process.exit(1); +} diff --git a/src/genie-commands/update.ts b/src/genie-commands/update.ts index a83836577..4cb2af4a5 100644 --- a/src/genie-commands/update.ts +++ b/src/genie-commands/update.ts @@ -20,6 +20,7 @@ import { import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; import { genieConfigExists, loadGenieConfig, saveGenieConfig } from '../lib/genie-config.js'; +import { extractPgservePortFromStatus } from '../lib/pgserve-status.js'; import { VERSION } from '../lib/version.js'; import { pm2ProcessNameCandidates, regenerateEcosystemConfig } from './install.js'; import { type CleanupReport, cleanupLegacyArtifacts, parseSkipCleanupFlag } from './legacy-cleanup.js'; @@ -1259,21 +1260,10 @@ export function extractPgserveSocketDirFromStatus(output: string): string | null return null; } -export function extractPgservePortFromStatus(output: string): string | null { - try { - const parsed = JSON.parse(output) as { - port?: unknown; - instance?: { port?: unknown }; - runtime?: { port?: unknown }; - }; - const rawPort = parsed.port ?? parsed.instance?.port ?? parsed.runtime?.port; - if (typeof rawPort === 'number' && Number.isFinite(rawPort)) return String(rawPort); - if (typeof rawPort === 'string' && rawPort.trim()) return rawPort.trim(); - } catch { - // best-effort diagnostics only; callers fall back to null. - } - return null; -} +// Moved to ../lib/pgserve-status.js so migration 002 can share the same +// nested-shape-tolerant parser. Re-exported here to preserve the public API +// (and existing update.test.ts import path). +export { extractPgservePortFromStatus }; function collectGenieProcessSnapshot(): string | null { const snapshot = safeExec('ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r', 2000) diff --git a/src/lib/__tests__/tmux-launch-script.test.ts b/src/lib/__tests__/tmux-launch-script.test.ts new file mode 100644 index 000000000..b4ecc0624 --- /dev/null +++ b/src/lib/__tests__/tmux-launch-script.test.ts @@ -0,0 +1,62 @@ +/** + * Unit tests for tmux-launch-script.ts + */ + +import { beforeEach, describe, expect, test } from 'bun:test'; +import { existsSync, readFileSync, rmSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { writeTmuxLaunchScript } from '../tmux-launch-script.js'; + +const SPAWN_DIR = join(homedir(), '.genie', 'spawn-scripts'); + +describe('writeTmuxLaunchScript', () => { + beforeEach(() => { + // Clean up any leftover test scripts + try { + const files = require('node:fs').readdirSync(SPAWN_DIR); + for (const f of files) { + if (f.startsWith('test-') || f.startsWith('omni-')) { + rmSync(join(SPAWN_DIR, f)); + } + } + } catch { + // dir may not exist yet + } + }); + + test('creates a script with shebang and command', () => { + const path = writeTmuxLaunchScript('test-worker', 'echo hello'); + expect(existsSync(path)).toBe(true); + + const content = readFileSync(path, 'utf-8'); + expect(content).toStartWith('#!/bin/sh\n'); + expect(content).toInclude('echo hello\n'); + }); + + test('sanitizes workerId in filename', () => { + const path = writeTmuxLaunchScript('worker/with:bad@chars', 'echo hello'); + const basename = path.split('/').pop()!; + expect(basename).toMatch(/^worker-with-bad-chars-/); + expect(basename).toEndWith('.sh'); + }); + + test('creates script in ~/.genie/spawn-scripts', () => { + const path = writeTmuxLaunchScript('test-worker', 'echo hello'); + expect(path).toStartWith(SPAWN_DIR); + }); + + test('sets executable permissions', () => { + const path = writeTmuxLaunchScript('test-worker', 'echo hello'); + const stats = require('node:fs').statSync(path); + // Check owner-execute bit + expect(stats.mode & 0o100).toBe(0o100); + }); + + test('preserves complex commands with quotes and backticks', () => { + const cmd = `OMNI_API_KEY='sk-123' claude --permission-mode 'auto' --system-prompt 'Use \`git\` (👍) for (instance: x)'`; + const path = writeTmuxLaunchScript('omni-chat-123', cmd); + const content = readFileSync(path, 'utf-8'); + expect(content).toInclude(`${cmd}\n`); + }); +}); diff --git a/src/lib/agent-observability.test.ts b/src/lib/agent-observability.test.ts index f18a2ac3f..9dce044b6 100644 --- a/src/lib/agent-observability.test.ts +++ b/src/lib/agent-observability.test.ts @@ -123,6 +123,30 @@ describe('assessHealth (pure)', () => { expect(assessHealth(row, now).flags).not.toContain('stale_executor'); }); + test('agent_error fires for errored agent lifecycle even without recent tool errors', () => { + const row = baseRow(); + row.agentState = 'error'; + row.executorState = 'terminated'; + row.recentErrorCount = 0; + + const h = assessHealth(row); + + expect(h.flags).toContain('agent_error'); + expect(h.degraded).toBe(true); + }); + + test('agent_error fires for errored executor lifecycle even when agent row has not caught up', () => { + const row = baseRow(); + row.agentState = 'working'; + row.executorState = 'error'; + row.recentErrorCount = 0; + + const h = assessHealth(row); + + expect(h.flags).toContain('agent_error'); + expect(h.degraded).toBe(true); + }); + test('missing_session fires when current executor has no session linkage or claude_session_id anchor', () => { const row = baseRow(); row.sessionId = null; diff --git a/src/lib/agent-observability.ts b/src/lib/agent-observability.ts index 233768968..a6469c82e 100644 --- a/src/lib/agent-observability.ts +++ b/src/lib/agent-observability.ts @@ -12,8 +12,8 @@ * the join + 24h tool/usage aggregates server-side; this module: * * 1. Casts row types into a stable TS shape (camelCase, typed). - * 2. Computes derived health flags (`stale_executor`, `missing_session`, - * `missing_attribution`, `recent_failure`, `cost_spike`, + * 2. Computes derived health flags (`agent_error`, `stale_executor`, + * `missing_session`, `missing_attribution`, `recent_failure`, `cost_spike`, * `high_hook_latency`). * 3. Provides classifier filters (`agent` vs `harness`). * @@ -41,6 +41,7 @@ export const AGENT_OBSERVABILITY_SCHEMA_VERSION = 1; export type AgentClassification = 'agent' | 'harness'; export type HealthFlag = + | 'agent_error' | 'stale_executor' | 'missing_session' | 'missing_attribution' @@ -173,6 +174,14 @@ function latestScopedActivity(row: AgentObservabilityRow): number { export function assessHealth(row: AgentObservabilityRow, now: number = Date.now()): HealthAssessment { const flags: HealthFlag[] = []; + // agent_error: the lifecycle itself is terminal/failed. Do not rely only on + // recent tool-event errors: many harness failures transition the agent or + // executor to `error` without a scoped tool_events row, and observe surfaces + // must still report the row as degraded. + if (row.agentState === 'error' || row.executorState === 'error') { + flags.push('agent_error'); + } + // stale_executor: live state but no heartbeat in the staleness window. // Prefer the freshest activity signal available. Executor rows may miss // heartbeat writes while Claude is still actively emitting tool events. Since diff --git a/src/lib/isolation-guard.test.ts b/src/lib/isolation-guard.test.ts new file mode 100644 index 000000000..9f6850080 --- /dev/null +++ b/src/lib/isolation-guard.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from 'bun:test'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +describe('isolated database setup fail-closed guard', () => { + test('does not return no-op cleanup when isolated DB setup fails', () => { + const source = readFileSync(join(process.cwd(), 'src/lib', `test-${'db'}.ts`), 'utf8'); + + expect(source).toContain('Unable to prepare isolated Genie test database'); + expect(source).toContain('Unable to create isolated Genie test database'); + expect(source).not.toContain('return async () => {};'); + }); +}); diff --git a/src/lib/pgserve-status.ts b/src/lib/pgserve-status.ts new file mode 100644 index 000000000..88cf283d7 --- /dev/null +++ b/src/lib/pgserve-status.ts @@ -0,0 +1,24 @@ +/** + * Shared parser for `