@automagik/genie 4.260407.6 → 4.260408.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260407.6",
13
+ "version": "4.260408.1",
14
14
  "source": "./plugins/genie",
15
15
  "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."
16
16
  }
@@ -20,3 +20,4 @@
20
20
  {"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}}
21
21
  {"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}}
22
22
  {"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}
23
+ {"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}
@@ -1,5 +1,5 @@
1
1
  {
2
- "last_run": "2026-04-07T12:04:30Z",
2
+ "last_run": "2026-04-08T00:00:00.000Z",
3
3
  "last_run_status": "failed",
4
4
  "last_metrics": {
5
5
  "releases_24h": 2,
@@ -69,12 +69,20 @@ def calc_ship_rate(prs_json: list, days: int = 7) -> float:
69
69
  return round((shipped_first / total) * 100, 0)
70
70
 
71
71
 
72
- def calc_loc_changed(repo_root: str) -> int:
73
- """Calculate total lines changed (additions + deletions) in last 24 hours."""
72
+ def calc_loc_changed(repo_root: str, branches: list[str] | None = None, hours: int = 24) -> int:
73
+ """Calculate total lines changed (additions + deletions) in last N hours.
74
+
75
+ When branches is provided, counts changes across all listed branches
76
+ (e.g. ['main', 'dev']) to capture work that hasn't been promoted yet.
77
+ """
74
78
  try:
79
+ cmd = ['git', 'log', f'--since={hours} hours ago', '--stat', '--format=']
80
+ if branches:
81
+ cmd.extend(branches)
82
+ else:
83
+ cmd.append('--all')
75
84
  result = subprocess.run(
76
- ['git', 'log', '--since=24 hours ago', '--stat', '--format='],
77
- capture_output=True, text=True, cwd=repo_root
85
+ cmd, capture_output=True, text=True, cwd=repo_root
78
86
  )
79
87
  total = 0
80
88
  for line in result.stdout.splitlines():
@@ -91,12 +99,20 @@ def calc_loc_changed(repo_root: str) -> int:
91
99
  return 0
92
100
 
93
101
 
94
- def calc_commits_24h(repo_root: str) -> int:
95
- """Count commits in the last 24 hours."""
102
+ def calc_commits_24h(repo_root: str, branches: list[str] | None = None, hours: int = 24) -> int:
103
+ """Count commits in the last N hours.
104
+
105
+ When branches is provided, counts across all listed branches
106
+ (e.g. ['main', 'dev']) to capture work not yet promoted to main.
107
+ """
96
108
  try:
109
+ cmd = ['git', 'log', f'--since={hours} hours ago', '--oneline']
110
+ if branches:
111
+ cmd.extend(branches)
112
+ else:
113
+ cmd.append('--all')
97
114
  result = subprocess.run(
98
- ['git', 'log', '--since=24 hours ago', '--oneline'],
99
- capture_output=True, text=True, cwd=repo_root
115
+ cmd, capture_output=True, text=True, cwd=repo_root
100
116
  )
101
117
  lines = [l for l in result.stdout.strip().splitlines() if l.strip()]
102
118
  return len(lines)
@@ -104,32 +120,98 @@ def calc_commits_24h(repo_root: str) -> int:
104
120
  return 0
105
121
 
106
122
 
107
- def calc_prs_24h(owner: str, repo: str) -> int:
108
- """Count PRs created in the last 24 hours using gh API."""
123
+ def calc_files_changed(repo_root: str, hours: int = 24, branches: list[str] | None = None) -> int:
124
+ """Count files changed in the last N hours."""
125
+ try:
126
+ cmd = ['git', 'log', f'--since={hours} hours ago', '--stat', '--format=']
127
+ if branches:
128
+ cmd.extend(branches)
129
+ else:
130
+ cmd.append('--all')
131
+ result = subprocess.run(
132
+ cmd, capture_output=True, text=True, cwd=repo_root
133
+ )
134
+ total = 0
135
+ for line in result.stdout.splitlines():
136
+ if 'file' in line and 'changed' in line:
137
+ total += int(line.strip().split()[0])
138
+ return total
139
+ except Exception:
140
+ return 0
141
+
142
+
143
+ def calc_npm_releases(package: str, hours: int = 24) -> int:
144
+ """Count npm releases in the last N hours."""
109
145
  try:
110
- since = (datetime.now(timezone.utc) - timedelta(hours=24)).strftime('%Y-%m-%dT%H:%M:%SZ')
111
146
  result = subprocess.run(
112
- ['gh', 'api', f'search/issues?q=repo:{owner}/{repo}+type:pr+created:>={since}',
113
- '--jq', '.total_count'],
147
+ ['npm', 'view', package, 'time', '--json'],
114
148
  capture_output=True, text=True
115
149
  )
116
- return int(result.stdout.strip()) if result.stdout.strip() else 0
150
+ if not result.stdout.strip():
151
+ return 0
152
+ data = json.loads(result.stdout)
153
+ cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
154
+ count = 0
155
+ for ver, ts in data.items():
156
+ if ver in ('created', 'modified'):
157
+ continue
158
+ dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
159
+ if dt >= cutoff:
160
+ count += 1
161
+ return count
117
162
  except Exception:
118
163
  return 0
119
164
 
120
165
 
121
- def format_metrics(releases_per_day: int, avg_merge_hours: float,
122
- ship_rate: float, parallel_agents: int,
123
- loc_changed_24h: int = 0, commits_24h: int = 0,
124
- prs_24h: int = 0) -> dict:
166
+ def calc_merged_prs(prs_json: list, hours: int = 24) -> int:
167
+ """Count merged PRs within the last N hours."""
168
+ cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
169
+ count = 0
170
+ for pr in prs_json:
171
+ if not pr.get('merged_at'):
172
+ continue
173
+ merged = datetime.fromisoformat(pr['merged_at'].replace('Z', '+00:00'))
174
+ if merged >= cutoff:
175
+ count += 1
176
+ return count
177
+
178
+
179
+ def calc_avg_merge_time_window(prs_json: list, hours: int = 24) -> float:
180
+ """Calculate average merge time in hours for PRs merged in the last N hours."""
181
+ cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
182
+ durations = []
183
+ for pr in prs_json:
184
+ if not pr.get('merged_at'):
185
+ continue
186
+ merged = datetime.fromisoformat(pr['merged_at'].replace('Z', '+00:00'))
187
+ if merged < cutoff:
188
+ continue
189
+ created = datetime.fromisoformat(pr['created_at'].replace('Z', '+00:00'))
190
+ durations.append((merged - created).total_seconds() / 3600)
191
+ if not durations:
192
+ return 0.0
193
+ return round(sum(durations) / len(durations), 1)
194
+
195
+
196
+ def format_metrics(*, commits_7d: int = 0, loc_7d: int = 0, files_7d: int = 0,
197
+ prs_7d: int = 0, releases_7d: int = 0, avg_merge_7d: float = 0,
198
+ commits_24h: int = 0, loc_24h: int = 0, files_24h: int = 0,
199
+ prs_24h: int = 0, releases_24h: int = 0, avg_merge_24h: float = 0,
200
+ parallel_agents: int = 0) -> dict:
125
201
  """Format metrics into the standard output structure."""
126
202
  return {
127
- 'releases_per_day': releases_per_day,
128
- 'avg_bugfix_time_hours': avg_merge_hours,
129
- 'ship_rate_pct': ship_rate,
130
- 'loc_changed_24h': loc_changed_24h,
203
+ 'commits_7d': commits_7d,
204
+ 'loc_changed_7d': loc_7d,
205
+ 'files_changed_7d': files_7d,
206
+ 'prs_7d': prs_7d,
207
+ 'releases_7d': releases_7d,
208
+ 'avg_merge_time_7d': avg_merge_7d,
131
209
  'commits_24h': commits_24h,
210
+ 'loc_changed_24h': loc_24h,
211
+ 'files_changed_24h': files_24h,
132
212
  'prs_24h': prs_24h,
213
+ 'releases_24h': releases_24h,
214
+ 'avg_merge_time_24h': avg_merge_24h,
133
215
  'parallel_agents': parallel_agents,
134
216
  'updated': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
135
217
  }
@@ -142,6 +224,8 @@ def main():
142
224
  parser.add_argument('--parallel-agents', type=int, default=0,
143
225
  help='Number of parallel agents active')
144
226
  parser.add_argument('--repo-root', default='.', help='Path to repo root for git log commands')
227
+ parser.add_argument('--branches', nargs='+', default=['main', 'dev'],
228
+ help='Branches to include in git metrics (default: main dev)')
145
229
  parser.add_argument('--owner', default='automagik-dev', help='GitHub owner')
146
230
  parser.add_argument('--repo', default='genie', help='GitHub repo name')
147
231
  parser.add_argument('--from-state', help='Fallback: read last_metrics from state.json')
@@ -166,21 +250,24 @@ def main():
166
250
  with open(args.prs_json) as f:
167
251
  prs = json.load(f)
168
252
 
169
- releases_count = parse_releases_count(releases)
170
- avg_merge = calc_avg_merge_time_hours(prs)
171
- ship_rate = calc_ship_rate(prs)
172
- loc_changed = calc_loc_changed(args.repo_root)
173
- commits = calc_commits_24h(args.repo_root)
174
- prs_count = calc_prs_24h(args.owner, args.repo)
253
+ pkg = f'@{args.owner}/{args.repo}'
175
254
 
176
255
  metrics = format_metrics(
177
- releases_per_day=releases_count,
178
- avg_merge_hours=avg_merge,
179
- ship_rate=ship_rate,
256
+ # 7-day metrics
257
+ commits_7d=calc_commits_24h(args.repo_root, args.branches, hours=168),
258
+ loc_7d=calc_loc_changed(args.repo_root, args.branches, hours=168),
259
+ files_7d=calc_files_changed(args.repo_root, hours=168, branches=args.branches),
260
+ prs_7d=calc_merged_prs(prs, hours=168),
261
+ releases_7d=calc_npm_releases(pkg, hours=168),
262
+ avg_merge_7d=calc_avg_merge_time_window(prs, hours=168),
263
+ # 24h metrics
264
+ commits_24h=calc_commits_24h(args.repo_root, args.branches),
265
+ loc_24h=calc_loc_changed(args.repo_root, args.branches),
266
+ files_24h=calc_files_changed(args.repo_root, branches=args.branches),
267
+ prs_24h=calc_merged_prs(prs),
268
+ releases_24h=calc_npm_releases(pkg),
269
+ avg_merge_24h=calc_avg_merge_time_window(prs),
180
270
  parallel_agents=args.parallel_agents,
181
- loc_changed_24h=loc_changed,
182
- commits_24h=commits,
183
- prs_24h=prs_count,
184
271
  )
185
272
 
186
273
  output = json.dumps(metrics, indent=2)
@@ -23,27 +23,41 @@ END_MARKER = '<!-- METRICS:END \u2014 \U0001f9de automagik/genie -->'
23
23
  def build_metrics_table(metrics: dict) -> str:
24
24
  """Build the markdown metrics table with signature markers."""
25
25
  timestamp = metrics.get('updated', datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'))
26
- releases = metrics.get('releases_per_day', 0)
27
- avg_time = metrics.get('avg_bugfix_time_hours', 0)
28
- ship_rate = metrics.get('ship_rate_pct', 0)
29
- loc_changed = metrics.get('loc_changed_24h', 0)
30
- commits = metrics.get('commits_24h', 0)
31
- prs = metrics.get('prs_24h', 0)
32
- agents = metrics.get('parallel_agents', 0)
26
+ date_str = timestamp[:10] if 'T' in timestamp else timestamp
27
+
28
+ # 7-day metrics
29
+ commits_7d = metrics.get('commits_7d', 0)
30
+ loc_7d = metrics.get('loc_changed_7d', 0)
31
+ files_7d = metrics.get('files_changed_7d', 0)
32
+ prs_7d = metrics.get('prs_7d', 0)
33
+ releases_7d = metrics.get('releases_7d', 0)
34
+ avg_merge_7d = metrics.get('avg_merge_time_7d', 0)
35
+
36
+ # 24h metrics
37
+ commits_24h = metrics.get('commits_24h', 0)
38
+ loc_24h = metrics.get('loc_changed_24h', 0)
39
+ files_24h = metrics.get('files_changed_24h', 0)
40
+ prs_24h = metrics.get('prs_24h', 0)
41
+ releases_24h = metrics.get('releases_24h', metrics.get('releases_per_day', 0))
42
+ avg_merge_24h = metrics.get('avg_merge_time_24h', metrics.get('avg_bugfix_time_hours', 0))
33
43
 
34
44
  start_marker = f'<!-- METRICS:START \u2014 Updated by Genie Metrics Agent at {timestamp} -->'
35
45
 
36
46
  lines = [
37
47
  start_marker,
38
- '| Metric | Value |',
39
- '|--------|-------|',
40
- f'| Releases/day | {releases} |',
41
- f'| Avg bug-fix time | {avg_time}h |',
42
- f'| SHIP rate | {ship_rate}% |',
43
- f'| Lines changed (24h) | {loc_changed:,} |',
44
- f'| Commits (24h) | {commits} |',
45
- f'| Pull requests (24h) | {prs} |',
46
- f'| Parallel agents | {agents} |',
48
+ '<p align="center">',
49
+ '',
50
+ '| Metric | 7 days | 24h |',
51
+ '|--------|--------|-----|',
52
+ f'| Commits | {commits_7d:,} | {commits_24h} |',
53
+ f'| Lines changed | {loc_7d:,} | {loc_24h:,} |',
54
+ f'| Files touched | {files_7d:,} | {files_24h} |',
55
+ f'| Merged PRs | {prs_7d} | {prs_24h} |',
56
+ f'| npm releases | {releases_7d} | {releases_24h} |',
57
+ f'| Avg merge time | {avg_merge_7d}h | {avg_merge_24h}h |',
58
+ '',
59
+ f'*Last updated: {date_str}*',
60
+ '</p>',
47
61
  END_MARKER,
48
62
  ]
49
63
  return '\n'.join(lines)
package/README.md CHANGED
@@ -27,14 +27,16 @@
27
27
  <!-- METRICS:START -->
28
28
  <p align="center">
29
29
 
30
- | Metric | Value |
31
- |--------|-------|
32
- | Releases (24h) | 2 |
33
- | Merged PRs (7d) | 35 |
34
- | Avg merge time | 0.3h |
35
- | SHIP rate | 85% |
36
-
37
- *Last updated: 2026-04-06*
30
+ | Metric | 7 days | 24h |
31
+ |--------|--------|-----|
32
+ | Commits | 445 | 38 |
33
+ | Lines changed | 57,462 | 3,917 |
34
+ | Files touched | 1,062 | 80 |
35
+ | Merged PRs | 87 | 10 |
36
+ | npm releases | 68 | 7 |
37
+ | Avg merge time | 3.3h | 5.4h |
38
+
39
+ *Last updated: 2026-04-07*
38
40
  </p>
39
41
  <!-- METRICS:END -->
40
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260407.6",
3
+ "version": "4.260408.1",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260407.6",
3
+ "version": "4.260408.1",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260407.6",
3
+ "version": "4.260408.1",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",