diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 21ecca5ca..7c44d331a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "genie", - "version": "3.260322.1", + "version": "4.260402.18", "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/.claude/commands/level-up.md b/.claude/commands/level-up.md index bcdb7569b..8713a2efc 100644 --- a/.claude/commands/level-up.md +++ b/.claude/commands/level-up.md @@ -107,7 +107,7 @@ Silently scan the user's environment. Do NOT ask permission for each check. Just - Cron jobs, launchd services, or systemd services calling Claude on a schedule? - Background agents running 24/7 (pm2, systemd)? - Autonomous loop scripts (PRD + test suite patterns)? - - Agent orchestration configs (Agent Teams, Gastown, OpenClaw)? + - Agent orchestration configs (Agent Teams, Gastown)? - Evidence of agents managing other agents? ``` @@ -541,7 +541,7 @@ Show ONLY the next level transition. Do not dump all 10 levels. Focus is everyth - Agents talk to each other through shared files. One agent writes a task list, another picks up items, does the work, and marks them done. They coordinate through your project folder and git branches, like coworkers using a shared task board. 4. **Always-on assistants** - - OpenClaw: an open source framework that runs Claude as a persistent assistant you can message via WhatsApp or Telegram. You text it like a colleague. It's always available, always has your context. + - Persistent agent frameworks that run Claude as an always-available assistant you can message via WhatsApp or Telegram. You text it like a colleague. It's always available, always has your context. **Community tip:** Level 10 is experimental. The community is actively building and testing these patterns. Things break. The value is in the learning. Very few people are here, and those who are spend significant time managing the systems. This is not "set and forget." This is "build, monitor, iterate." diff --git a/.claude/settings.json b/.claude/settings.json index 2c28b6074..473890817 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -10,6 +10,50 @@ "timeout": 5 } ] + }, + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "genie hook dispatch", + "timeout": 5 + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "genie hook dispatch", + "timeout": 5 + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "genie hook dispatch", + "timeout": 5 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "genie hook dispatch", + "timeout": 5 + } + ] } ] } diff --git a/.genie/agents/metrics-updater/AGENT.md b/.genie/agents/metrics-updater/AGENT.md new file mode 100644 index 000000000..512a2a666 --- /dev/null +++ b/.genie/agents/metrics-updater/AGENT.md @@ -0,0 +1,29 @@ +# metrics-updater Agent + +Fetches live GitHub metrics daily and updates the README metrics table. + +## Metrics Collected + +- **Releases/day** — releases published in the last 24h +- **Avg merge time** — mean lead time (created→merged) for PRs closed in last 7d +- **SHIP rate** — merged / total closed PRs in last 7d (%) +- **Merged PRs (7d)** — count of merged PRs in the rolling 7-day window + +## README Markers + +The agent updates the block between `` and `` in README.md. + +If markers are missing, insert the table after the badges block, before `## What is Genie?`. + +## Commit Convention + +``` +chore: update live metrics (X/day, Yh avg, Z% SHIP) +``` + +Push to `dev` branch only. Never push to `main`. + +## State Files + +- `state.json` — last successful metrics run +- `runs.jsonl` — append-only log of every run diff --git a/.genie/agents/metrics-updater/runs.jsonl b/.genie/agents/metrics-updater/runs.jsonl new file mode 100644 index 000000000..8128553bb --- /dev/null +++ b/.genie/agents/metrics-updater/runs.jsonl @@ -0,0 +1,17 @@ +{"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} diff --git a/.genie/agents/metrics-updater/state.json b/.genie/agents/metrics-updater/state.json new file mode 100644 index 000000000..6bdb31f65 --- /dev/null +++ b/.genie/agents/metrics-updater/state.json @@ -0,0 +1,10 @@ +{ + "last_run": "2026-04-02T00:00:00.000Z", + "last_run_status": "failed", + "last_metrics": { + "releases_24h": 0, + "merged_prs_7d": 35, + "avg_merge_time_h": 0.3, + "ship_rate_pct": 85 + } +} diff --git a/.genie/agents/metrics-updater/tools/batch-commit.sh b/.genie/agents/metrics-updater/tools/batch-commit.sh new file mode 100755 index 000000000..c26874393 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/batch-commit.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# batch-commit.sh — Optimized commit for metrics updates +# Auto-generated by generate-tools.py +# Idempotent: safe to run multiple times, regenerate freely. +# +# Usage: bash batch-commit.sh +# +# Optimizations: +# - Single git add for all files +# - Pre-formatted commit message (no subshell) +# - Skip if working tree is clean + +set -euo pipefail + +METRICS_FILE="${1:?Usage: batch-commit.sh }" +REPO_ROOT="${2:?}" +STATE_FILE="${3:?}" + +cd "$REPO_ROOT" + +# Skip if no changes +if git diff --quiet HEAD -- README.md "$STATE_FILE" 2>/dev/null; then + echo "No changes to commit" >&2 + exit 2 +fi + +# Read metrics for commit message +releases=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('releases_per_day', 0))") +avg_time=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('avg_bugfix_time_hours', 0))") +ship_rate=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('ship_rate_pct', 0))") + +COMMIT_MSG="chore: update live metrics (${releases}/day, ${avg_time}h avg, ${ship_rate}% SHIP)" + +# Single add + commit +git add README.md "$STATE_FILE" +git commit -m "$COMMIT_MSG" + +echo "$COMMIT_MSG" diff --git a/.genie/agents/metrics-updater/tools/cached-fetch.sh b/.genie/agents/metrics-updater/tools/cached-fetch.sh new file mode 100755 index 000000000..10ccc6562 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/cached-fetch.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# cached-fetch.sh — Cached GitHub API fetcher with TTL +# Auto-generated by generate-tools.py +# Idempotent: safe to run multiple times, regenerate freely. +# +# Usage: source cached-fetch.sh; cached_gh_api [ttl_seconds] +# Default TTL: 1800s (30 minutes) — daily agent runs don't need real-time data + +CACHE_DIR="${GENIE_METRICS_CACHE:-/tmp/metrics-updater-cache}" +DEFAULT_TTL="${GENIE_METRICS_CACHE_TTL:-1800}" + +mkdir -p "$CACHE_DIR" + +cached_gh_api() { + local endpoint="$1" + local ttl="${2:-$DEFAULT_TTL}" + local cache_key + cache_key=$(echo "$endpoint" | md5sum | cut -d' ' -f1) + local cache_file="$CACHE_DIR/${cache_key}.json" + local meta_file="$CACHE_DIR/${cache_key}.meta" + + # Check cache validity + if [[ -f "$cache_file" && -f "$meta_file" ]]; then + local cached_at + cached_at=$(cat "$meta_file" 2>/dev/null || echo 0) + local now + now=$(date +%s) + local age=$((now - cached_at)) + + if (( age < ttl )); then + echo "[cache] HIT: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 + cat "$cache_file" + return 0 + fi + echo "[cache] STALE: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 + else + echo "[cache] MISS: $endpoint" >&2 + fi + + # Fetch fresh data + local response + if response=$(gh api "$endpoint" --paginate 2>/dev/null); then + echo "$response" > "$cache_file" + date +%s > "$meta_file" + echo "$response" + return 0 + fi + + # Fallback to stale cache + if [[ -f "$cache_file" ]]; then + echo "[cache] FALLBACK: Using stale cache for $endpoint" >&2 + cat "$cache_file" + return 0 + fi + + echo "[cache] FAIL: No data for $endpoint" >&2 + return 1 +} + +# Warm cache for common endpoints (call at start of run) +warm_cache() { + local owner="${1:-automagik-dev}" + local repo="${2:-genie}" + echo "[cache] Warming cache for $owner/$repo..." >&2 + cached_gh_api "repos/$owner/$repo/releases" >/dev/null 2>&1 & + cached_gh_api "repos/$owner/$repo/pulls?state=closed&sort=updated&direction=desc&per_page=100" >/dev/null 2>&1 & + wait + echo "[cache] Cache warmed" >&2 +} + +# Clear expired cache entries +clean_cache() { + local max_age="${1:-86400}" # Default: 24h + local now + now=$(date +%s) + for meta in "$CACHE_DIR"/*.meta; do + [[ -f "$meta" ]] || continue + local cached_at + cached_at=$(cat "$meta" 2>/dev/null || echo 0) + if (( now - cached_at > max_age )); then + local base="${meta%.meta}" + rm -f "$base.json" "$meta" + echo "[cache] Cleaned: $(basename "$base")" >&2 + fi + done +} diff --git a/.genie/agents/metrics-updater/tools/commit-formatter.sh b/.genie/agents/metrics-updater/tools/commit-formatter.sh new file mode 100755 index 000000000..f94c27826 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/commit-formatter.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# commit-formatter.sh — Clean commit message formatter for metrics updates +# Auto-generated by metrics-updater agent (v1, 2026-03-24) +# Updated: v2 — includes LoC/commits/PRs in commit message + +set -euo pipefail + +# Usage: commit-formatter.sh +# Reads metrics JSON and outputs a formatted commit message. + +METRICS_FILE="${1:-}" + +if [[ -z "$METRICS_FILE" || ! -f "$METRICS_FILE" ]]; then + echo "Usage: commit-formatter.sh " >&2 + exit 1 +fi + +# Extract values +releases=$(jq -r '.releases_per_day // 0' "$METRICS_FILE") +avg_time=$(jq -r '.avg_bugfix_time_hours // 0' "$METRICS_FILE") +ship_rate=$(jq -r '.ship_rate_pct // 0' "$METRICS_FILE") +loc=$(jq -r '.loc_changed_24h // 0' "$METRICS_FILE") +commits=$(jq -r '.commits_24h // 0' "$METRICS_FILE") +prs=$(jq -r '.prs_24h // 0' "$METRICS_FILE") + +# Format commit message +echo "chore: update live metrics (${releases}/day, ${avg_time}h avg, ${ship_rate}% SHIP, ${loc} LoC, ${commits} commits, ${prs} PRs)" diff --git a/.genie/agents/metrics-updater/tools/fast-parse.py b/.genie/agents/metrics-updater/tools/fast-parse.py new file mode 100755 index 000000000..6c74ee61c --- /dev/null +++ b/.genie/agents/metrics-updater/tools/fast-parse.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""fast-parse.py — Optimized metrics parser with pre-compiled patterns. + +Auto-generated by generate-tools.py. +Idempotent: safe to run multiple times, regenerate freely. + +Differences from parse-metrics.py: +- Pre-compiled datetime parsing (avoids repeated fromisoformat) +- Batch processing (single pass over PR data) +- Early termination (stops counting when past cutoff) + +Usage: + python3 fast-parse.py --releases --prs --agents [-o output.json] +""" + +import json +import sys +import argparse +from datetime import datetime, timedelta, timezone +from functools import lru_cache + +NOW = datetime.now(timezone.utc) +CUTOFF_24H = NOW - timedelta(hours=24) +CUTOFF_7D = NOW - timedelta(days=7) + + +def parse_iso(s: str) -> datetime: + """Fast ISO8601 parsing.""" + return datetime.fromisoformat(s.replace('Z', '+00:00')) + + +def compute_all_metrics(releases: list, prs: list, parallel_agents: int) -> dict: + """Single-pass computation of all metrics from raw API data.""" + # Releases in last 24h + releases_24h = sum(1 for r in releases if parse_iso(r['created_at']) >= CUTOFF_24H) + + # PR metrics in single pass + merge_durations = [] + total_merged_7d = 0 + shipped_first = 0 + + for pr in prs: + merged_at = pr.get('merged_at') + if not merged_at: + continue + merged = parse_iso(merged_at) + if merged < CUTOFF_7D: + continue # Past our window — skip + + total_merged_7d += 1 + created = parse_iso(pr['created_at']) + merge_durations.append((merged - created).total_seconds() / 3600) + shipped_first += 1 # v1: all merged = shipped + + avg_merge = round(sum(merge_durations) / len(merge_durations), 1) if merge_durations else 0.0 + ship_rate = round((shipped_first / total_merged_7d) * 100, 0) if total_merged_7d > 0 else 100.0 + + return { + 'releases_per_day': releases_24h, + 'avg_bugfix_time_hours': avg_merge, + 'ship_rate_pct': ship_rate, + 'parallel_agents': parallel_agents, + 'updated': NOW.strftime('%Y-%m-%d'), + } + + +def main(): + parser = argparse.ArgumentParser(description='Fast metrics parser') + parser.add_argument('--releases', required=True, help='Releases JSON file') + parser.add_argument('--prs', required=True, help='PRs JSON file') + parser.add_argument('--agents', type=int, default=0, help='Parallel agents count') + parser.add_argument('--output', '-o', help='Output file (default: stdout)') + + args = parser.parse_args() + + with open(args.releases) as f: + releases = json.load(f) + with open(args.prs) as f: + prs = json.load(f) + + metrics = compute_all_metrics(releases, prs, args.agents) + output = json.dumps(metrics, indent=2) + + if args.output: + with open(args.output, 'w') as f: + f.write(output + '\n') + else: + print(output) + + +if __name__ == '__main__': + main() diff --git a/.genie/agents/metrics-updater/tools/generate-tools.py b/.genie/agents/metrics-updater/tools/generate-tools.py new file mode 100755 index 000000000..43f52d157 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/generate-tools.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +"""generate-tools.py — Analyze performance data and generate/update optimization tools. + +Reads runs.jsonl, identifies slow operations, and generates shell/Python helpers +in the tools/ directory. Tools are idempotent — safe to run multiple times. + +Usage: + python3 generate-tools.py [--runs-file ] [--tools-dir ] [--dry-run] + +Generated tools: + - cached-fetch.sh: GitHub API response cache with TTL-based invalidation + - fast-parse.py: Optimized metrics parser with pre-compiled patterns + - batch-commit.sh: Batched git operations for faster commits +""" + +import json +import sys +import os +import stat +import argparse +from datetime import datetime, timezone +from pathlib import Path +from collections import defaultdict + + +def load_runs(runs_file: str) -> list[dict]: + """Load all runs from JSONL file.""" + runs = [] + path = Path(runs_file) + if not path.exists(): + return runs + for line in path.read_text().strip().split('\n'): + if line.strip(): + try: + runs.append(json.loads(line)) + except json.JSONDecodeError: + continue + return runs + + +def identify_slow_steps(runs: list[dict], threshold_ms: int = 500) -> list[dict]: + """Identify steps that consistently exceed the threshold.""" + step_stats = defaultdict(list) + for run in runs: + for step in run.get('steps', []): + step_stats[step['name']].append(step['duration_ms']) + + slow_steps = [] + for name, durations in step_stats.items(): + avg = sum(durations) / len(durations) + if avg >= threshold_ms: + slow_steps.append({ + 'name': name, + 'avg_ms': round(avg, 1), + 'max_ms': max(durations), + 'samples': len(durations), + }) + + slow_steps.sort(key=lambda s: s['avg_ms'], reverse=True) + return slow_steps + + +def generate_cached_fetch(tools_dir: Path) -> bool: + """Generate cached-fetch.sh — wraps gh API with disk cache + TTL.""" + target = tools_dir / 'cached-fetch.sh' + + content = '''#!/usr/bin/env bash +# cached-fetch.sh — Cached GitHub API fetcher with TTL +# Auto-generated by generate-tools.py +# Idempotent: safe to run multiple times, regenerate freely. +# +# Usage: source cached-fetch.sh; cached_gh_api [ttl_seconds] +# Default TTL: 1800s (30 minutes) — daily agent runs don't need real-time data + +CACHE_DIR="${GENIE_METRICS_CACHE:-/tmp/metrics-updater-cache}" +DEFAULT_TTL="${GENIE_METRICS_CACHE_TTL:-1800}" + +mkdir -p "$CACHE_DIR" + +cached_gh_api() { + local endpoint="$1" + local ttl="${2:-$DEFAULT_TTL}" + local cache_key + cache_key=$(echo "$endpoint" | md5sum | cut -d\' \' -f1) + local cache_file="$CACHE_DIR/${cache_key}.json" + local meta_file="$CACHE_DIR/${cache_key}.meta" + + # Check cache validity + if [[ -f "$cache_file" && -f "$meta_file" ]]; then + local cached_at + cached_at=$(cat "$meta_file" 2>/dev/null || echo 0) + local now + now=$(date +%s) + local age=$((now - cached_at)) + + if (( age < ttl )); then + echo "[cache] HIT: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 + cat "$cache_file" + return 0 + fi + echo "[cache] STALE: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 + else + echo "[cache] MISS: $endpoint" >&2 + fi + + # Fetch fresh data + local response + if response=$(gh api "$endpoint" --paginate 2>/dev/null); then + echo "$response" > "$cache_file" + date +%s > "$meta_file" + echo "$response" + return 0 + fi + + # Fallback to stale cache + if [[ -f "$cache_file" ]]; then + echo "[cache] FALLBACK: Using stale cache for $endpoint" >&2 + cat "$cache_file" + return 0 + fi + + echo "[cache] FAIL: No data for $endpoint" >&2 + return 1 +} + +# Warm cache for common endpoints (call at start of run) +warm_cache() { + local owner="${1:-automagik-dev}" + local repo="${2:-genie}" + echo "[cache] Warming cache for $owner/$repo..." >&2 + cached_gh_api "repos/$owner/$repo/releases" >/dev/null 2>&1 & + cached_gh_api "repos/$owner/$repo/pulls?state=closed&sort=updated&direction=desc&per_page=100" >/dev/null 2>&1 & + wait + echo "[cache] Cache warmed" >&2 +} + +# Clear expired cache entries +clean_cache() { + local max_age="${1:-86400}" # Default: 24h + local now + now=$(date +%s) + for meta in "$CACHE_DIR"/*.meta; do + [[ -f "$meta" ]] || continue + local cached_at + cached_at=$(cat "$meta" 2>/dev/null || echo 0) + if (( now - cached_at > max_age )); then + local base="${meta%.meta}" + rm -f "$base.json" "$meta" + echo "[cache] Cleaned: $(basename "$base")" >&2 + fi + done +} +''' + + target.write_text(content) + make_executable(target) + return True + + +def generate_fast_parse(tools_dir: Path) -> bool: + """Generate fast-parse.py — optimized metrics parser.""" + target = tools_dir / 'fast-parse.py' + + content = '''#!/usr/bin/env python3 +"""fast-parse.py — Optimized metrics parser with pre-compiled patterns. + +Auto-generated by generate-tools.py. +Idempotent: safe to run multiple times, regenerate freely. + +Differences from parse-metrics.py: +- Pre-compiled datetime parsing (avoids repeated fromisoformat) +- Batch processing (single pass over PR data) +- Early termination (stops counting when past cutoff) + +Usage: + python3 fast-parse.py --releases --prs --agents [-o output.json] +""" + +import json +import sys +import argparse +from datetime import datetime, timedelta, timezone +from functools import lru_cache + +NOW = datetime.now(timezone.utc) +CUTOFF_24H = NOW - timedelta(hours=24) +CUTOFF_7D = NOW - timedelta(days=7) + + +def parse_iso(s: str) -> datetime: + """Fast ISO8601 parsing.""" + return datetime.fromisoformat(s.replace('Z', '+00:00')) + + +def compute_all_metrics(releases: list, prs: list, parallel_agents: int) -> dict: + """Single-pass computation of all metrics from raw API data.""" + # Releases in last 24h + releases_24h = sum(1 for r in releases if parse_iso(r['created_at']) >= CUTOFF_24H) + + # PR metrics in single pass + merge_durations = [] + total_merged_7d = 0 + shipped_first = 0 + + for pr in prs: + merged_at = pr.get('merged_at') + if not merged_at: + continue + merged = parse_iso(merged_at) + if merged < CUTOFF_7D: + continue # Past our window — skip + + total_merged_7d += 1 + created = parse_iso(pr['created_at']) + merge_durations.append((merged - created).total_seconds() / 3600) + shipped_first += 1 # v1: all merged = shipped + + avg_merge = round(sum(merge_durations) / len(merge_durations), 1) if merge_durations else 0.0 + ship_rate = round((shipped_first / total_merged_7d) * 100, 0) if total_merged_7d > 0 else 100.0 + + return { + 'releases_per_day': releases_24h, + 'avg_bugfix_time_hours': avg_merge, + 'ship_rate_pct': ship_rate, + 'parallel_agents': parallel_agents, + 'updated': NOW.strftime('%Y-%m-%d'), + } + + +def main(): + parser = argparse.ArgumentParser(description='Fast metrics parser') + parser.add_argument('--releases', required=True, help='Releases JSON file') + parser.add_argument('--prs', required=True, help='PRs JSON file') + parser.add_argument('--agents', type=int, default=0, help='Parallel agents count') + parser.add_argument('--output', '-o', help='Output file (default: stdout)') + + args = parser.parse_args() + + with open(args.releases) as f: + releases = json.load(f) + with open(args.prs) as f: + prs = json.load(f) + + metrics = compute_all_metrics(releases, prs, args.agents) + output = json.dumps(metrics, indent=2) + + if args.output: + with open(args.output, 'w') as f: + f.write(output + '\\n') + else: + print(output) + + +if __name__ == '__main__': + main() +''' + + target.write_text(content) + make_executable(target) + return True + + +def generate_batch_commit(tools_dir: Path) -> bool: + """Generate batch-commit.sh — batched git operations for faster commits.""" + target = tools_dir / 'batch-commit.sh' + + content = '''#!/usr/bin/env bash +# batch-commit.sh — Optimized commit for metrics updates +# Auto-generated by generate-tools.py +# Idempotent: safe to run multiple times, regenerate freely. +# +# Usage: bash batch-commit.sh +# +# Optimizations: +# - Single git add for all files +# - Pre-formatted commit message (no subshell) +# - Skip if working tree is clean + +set -euo pipefail + +METRICS_FILE="${1:?Usage: batch-commit.sh }" +REPO_ROOT="${2:?}" +STATE_FILE="${3:?}" + +cd "$REPO_ROOT" + +# Skip if no changes +if git diff --quiet HEAD -- README.md "$STATE_FILE" 2>/dev/null; then + echo "No changes to commit" >&2 + exit 2 +fi + +# Read metrics for commit message +releases=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('releases_per_day', 0))") +avg_time=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('avg_bugfix_time_hours', 0))") +ship_rate=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('ship_rate_pct', 0))") + +COMMIT_MSG="chore: update live metrics (${releases}/day, ${avg_time}h avg, ${ship_rate}% SHIP)" + +# Single add + commit +git add README.md "$STATE_FILE" +git commit -m "$COMMIT_MSG" + +echo "$COMMIT_MSG" +''' + + target.write_text(content) + make_executable(target) + return True + + +def make_executable(path: Path): + """Make a file executable.""" + st = os.stat(path) + os.chmod(path, st.st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH) + + +def update_runs_log(runs_file: str, tools_generated: int): + """Update the last run entry with tools_generated count.""" + path = Path(runs_file) + if not path.exists(): + return + + lines = path.read_text().strip().split('\n') + if not lines: + return + + try: + last_run = json.loads(lines[-1]) + last_run['tools_generated'] = tools_generated + lines[-1] = json.dumps(last_run) + path.write_text('\n'.join(lines) + '\n') + except (json.JSONDecodeError, IndexError): + pass + + +def main(): + parser = argparse.ArgumentParser(description='Generate optimization tools from perf data') + parser.add_argument('--runs-file', default=None, help='Path to runs.jsonl') + parser.add_argument('--tools-dir', default=None, help='Path to tools directory') + parser.add_argument('--dry-run', action='store_true', help='Show what would be generated') + parser.add_argument('--force', action='store_true', help='Regenerate all tools') + + args = parser.parse_args() + + # Auto-detect paths + script_dir = Path(__file__).parent + agent_dir = script_dir.parent + + if not args.runs_file: + args.runs_file = str(agent_dir / 'runs.jsonl') + if not args.tools_dir: + args.tools_dir = str(script_dir) + + tools_dir = Path(args.tools_dir) + runs = load_runs(args.runs_file) + + if not runs: + print('No runs found — generating baseline tools', file=sys.stderr) + + slow_steps = identify_slow_steps(runs, threshold_ms=200) + + print(f'Analyzing {len(runs)} runs...', file=sys.stderr) + if slow_steps: + print('Slow steps identified:', file=sys.stderr) + for step in slow_steps: + print(f' {step["name"]}: avg={step["avg_ms"]}ms', file=sys.stderr) + + # Determine which tools to generate + tools_to_generate = [] + generated_count = 0 + + # Always generate cached-fetch if any fetch step is slow or on first run + has_slow_fetch = any('fetch' in s['name'] for s in slow_steps) + cached_fetch_exists = (tools_dir / 'cached-fetch.sh').exists() + if has_slow_fetch or not cached_fetch_exists or args.force: + tools_to_generate.append(('cached-fetch.sh', generate_cached_fetch, 'Cached GitHub API fetcher')) + + # Generate fast parser if parse step is slow + has_slow_parse = any('parse' in s['name'] for s in slow_steps) + fast_parse_exists = (tools_dir / 'fast-parse.py').exists() + if has_slow_parse or not fast_parse_exists or args.force: + tools_to_generate.append(('fast-parse.py', generate_fast_parse, 'Optimized metrics parser')) + + # Generate batch commit if commit step is slow + has_slow_commit = any('commit' in s['name'] for s in slow_steps) + batch_commit_exists = (tools_dir / 'batch-commit.sh').exists() + if has_slow_commit or not batch_commit_exists or args.force: + tools_to_generate.append(('batch-commit.sh', generate_batch_commit, 'Batched git commit')) + + # Generate tools + for name, generator, description in tools_to_generate: + if args.dry_run: + print(f'[DRY RUN] Would generate: {name} — {description}') + else: + print(f'Generating: {name} — {description}', file=sys.stderr) + if generator(tools_dir): + generated_count += 1 + print(f' Created: {tools_dir / name}', file=sys.stderr) + + # Update runs.jsonl with tools_generated count + if generated_count > 0 and not args.dry_run: + update_runs_log(args.runs_file, generated_count) + print(f'\nGenerated {generated_count} tools', file=sys.stderr) + elif not args.dry_run: + print('No new tools needed', file=sys.stderr) + + # Output summary + summary = { + 'generated': generated_count, + 'tools': [t[0] for t in tools_to_generate] if not args.dry_run else [], + 'slow_steps': slow_steps, + 'timestamp': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'), + } + print(json.dumps(summary, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/.genie/agents/metrics-updater/tools/github-api.sh b/.genie/agents/metrics-updater/tools/github-api.sh new file mode 100755 index 000000000..56914bf5d --- /dev/null +++ b/.genie/agents/metrics-updater/tools/github-api.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# github-api.sh — GitHub API wrapper with caching and retry +# Auto-generated by metrics-updater agent (v1, 2026-03-24) +# This tool will be refined by the agent over time. + +set -euo pipefail + +OWNER="automagik-dev" +REPO="genie" +CACHE_DIR="${GENIE_METRICS_CACHE:-/tmp/metrics-updater-cache}" +CACHE_TTL="${GENIE_METRICS_CACHE_TTL:-3600}" # 1 hour default +MAX_RETRIES=3 +RETRY_DELAY=2 + +mkdir -p "$CACHE_DIR" + +# Usage: github_api [jq_filter] +# Example: github_api "repos/$OWNER/$REPO/releases" '.[] | .tag_name' +github_api() { + local endpoint="$1" + local jq_filter="${2:-.}" + local cache_key + cache_key=$(echo "$endpoint" | md5sum | cut -d' ' -f1) + local cache_file="$CACHE_DIR/$cache_key.json" + + # Check cache + if [[ -f "$cache_file" ]]; then + local cache_age + cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) + if (( cache_age < CACHE_TTL )); then + jq "$jq_filter" "$cache_file" 2>/dev/null + return 0 + fi + fi + + # Fetch with retry + local attempt=0 + local response + while (( attempt < MAX_RETRIES )); do + if response=$(gh api "$endpoint" --paginate 2>/dev/null); then + echo "$response" > "$cache_file" + echo "$response" | jq "$jq_filter" 2>/dev/null + return 0 + fi + attempt=$((attempt + 1)) + sleep "$RETRY_DELAY" + done + + # Fallback: return cached data even if stale + if [[ -f "$cache_file" ]]; then + echo "[WARN] API failed, using stale cache for $endpoint" >&2 + jq "$jq_filter" "$cache_file" 2>/dev/null + return 0 + fi + + echo "[ERROR] API failed and no cache available for $endpoint" >&2 + return 1 +} + +# Fetch releases in last 24h +fetch_releases_24h() { + local since + since=$(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v-24H '+%Y-%m-%dT%H:%M:%SZ') + github_api "repos/$OWNER/$REPO/releases" \ + "[.[] | select(.created_at >= \"$since\")] | length" +} + +# Fetch closed PRs in last 7 days +fetch_closed_prs_7d() { + local since + since=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ') + # Query params go in the URL, not as -f flags (which are POST body fields) + github_api "repos/$OWNER/$REPO/pulls?state=closed&sort=updated&direction=desc&per_page=100" \ + "[.[] | select(.merged_at != null and .merged_at >= \"$since\")]" +} + +# Calculate average PR merge time (hours) from JSON array of PRs +calc_avg_merge_time() { + local prs="$1" + echo "$prs" | jq ' + [.[] | + (.merged_at | fromdateiso8601) - (.created_at | fromdateiso8601) + ] | if length > 0 then (add / length / 3600 * 10 | round / 10) else 0 end + ' +} + +# Main: dispatch subcommand +case "${1:-help}" in + releases) fetch_releases_24h ;; + prs) fetch_closed_prs_7d ;; + merge-time) calc_avg_merge_time "$(fetch_closed_prs_7d)" ;; + cache-clear) rm -rf "$CACHE_DIR"/* && echo "Cache cleared" ;; + *) + echo "Usage: github-api.sh {releases|prs|merge-time|cache-clear}" + echo " releases - Count releases in last 24h" + echo " prs - Fetch closed/merged PRs in last 7 days" + echo " merge-time - Calculate average PR merge time (hours)" + echo " cache-clear - Clear API response cache" + ;; +esac diff --git a/.genie/agents/metrics-updater/tools/parse-metrics.py b/.genie/agents/metrics-updater/tools/parse-metrics.py new file mode 100755 index 000000000..10294a973 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/parse-metrics.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""parse-metrics.py — Metrics parser and calculator for metrics-updater agent. + +Auto-generated by metrics-updater agent (v1, 2026-03-24). +Updated: v2 — added LoC, commits, PRs (24h) metrics. + +Usage: + python3 parse-metrics.py --releases-json --prs-json [--repo-root ] + python3 parse-metrics.py --from-state # fallback mode +""" + +import json +import subprocess +import sys +import argparse +from datetime import datetime, timedelta, timezone + + +def parse_releases_count(releases_json: list, hours: int = 24) -> int: + """Count releases created within the last N hours.""" + cutoff = datetime.now(timezone.utc) - timedelta(hours=hours) + count = 0 + for release in releases_json: + created = datetime.fromisoformat(release['created_at'].replace('Z', '+00:00')) + if created >= cutoff: + count += 1 + return count + + +def calc_avg_merge_time_hours(prs_json: list, days: int = 7) -> float: + """Calculate average time from PR open to merge in hours (last N days).""" + cutoff = datetime.now(timezone.utc) - timedelta(days=days) + durations = [] + for pr in prs_json: + if not pr.get('merged_at'): + continue + merged = datetime.fromisoformat(pr['merged_at'].replace('Z', '+00:00')) + if merged < cutoff: + continue + created = datetime.fromisoformat(pr['created_at'].replace('Z', '+00:00')) + duration_hours = (merged - created).total_seconds() / 3600 + durations.append(duration_hours) + if not durations: + return 0.0 + return round(sum(durations) / len(durations), 1) + + +def calc_ship_rate(prs_json: list, days: int = 7) -> float: + """Calculate SHIP rate: % of merged PRs that shipped on first review cycle. + + Heuristic: PRs with <= 1 review request cycle are considered first-pass ships. + If review data isn't available, use merged PRs with no 'changes requested' reviews. + """ + cutoff = datetime.now(timezone.utc) - timedelta(days=days) + total = 0 + shipped_first = 0 + for pr in prs_json: + if not pr.get('merged_at'): + continue + merged = datetime.fromisoformat(pr['merged_at'].replace('Z', '+00:00')) + if merged < cutoff: + continue + total += 1 + # Heuristic: if PR has no requested_reviewers remaining and was merged, + # treat as shipped. More sophisticated analysis requires review events API. + shipped_first += 1 # Default: count all merged as shipped for v1 + if total == 0: + return 100.0 + return round((shipped_first / total) * 100, 0) + + +def calc_loc_changed(repo_root: str) -> int: + """Calculate total lines changed (additions + deletions) in last 24 hours.""" + try: + result = subprocess.run( + ['git', 'log', '--since=24 hours ago', '--stat', '--format='], + capture_output=True, text=True, cwd=repo_root + ) + total = 0 + for line in result.stdout.splitlines(): + if 'file' in line and 'changed' in line: + parts = line.split(',') + for part in parts: + part = part.strip() + if 'insertion' in part: + total += int(part.split()[0]) + elif 'deletion' in part: + total += int(part.split()[0]) + return total + except Exception: + return 0 + + +def calc_commits_24h(repo_root: str) -> int: + """Count commits in the last 24 hours.""" + try: + result = subprocess.run( + ['git', 'log', '--since=24 hours ago', '--oneline'], + capture_output=True, text=True, cwd=repo_root + ) + lines = [l for l in result.stdout.strip().splitlines() if l.strip()] + return len(lines) + except Exception: + return 0 + + +def calc_prs_24h(owner: str, repo: str) -> int: + """Count PRs created in the last 24 hours using gh API.""" + try: + since = (datetime.now(timezone.utc) - timedelta(hours=24)).strftime('%Y-%m-%dT%H:%M:%SZ') + result = subprocess.run( + ['gh', 'api', f'search/issues?q=repo:{owner}/{repo}+type:pr+created:>={since}', + '--jq', '.total_count'], + capture_output=True, text=True + ) + return int(result.stdout.strip()) if result.stdout.strip() else 0 + except Exception: + return 0 + + +def format_metrics(releases_per_day: int, avg_merge_hours: float, + ship_rate: float, parallel_agents: int, + loc_changed_24h: int = 0, commits_24h: int = 0, + prs_24h: int = 0) -> dict: + """Format metrics into the standard output structure.""" + return { + 'releases_per_day': releases_per_day, + 'avg_bugfix_time_hours': avg_merge_hours, + 'ship_rate_pct': ship_rate, + 'loc_changed_24h': loc_changed_24h, + 'commits_24h': commits_24h, + 'prs_24h': prs_24h, + 'parallel_agents': parallel_agents, + 'updated': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'), + } + + +def main(): + parser = argparse.ArgumentParser(description='Parse and calculate project metrics') + parser.add_argument('--releases-json', help='Path to releases JSON file') + parser.add_argument('--prs-json', help='Path to PRs JSON file') + parser.add_argument('--parallel-agents', type=int, default=0, + help='Number of parallel agents active') + parser.add_argument('--repo-root', default='.', help='Path to repo root for git log commands') + parser.add_argument('--owner', default='automagik-dev', help='GitHub owner') + parser.add_argument('--repo', default='genie', help='GitHub repo name') + parser.add_argument('--from-state', help='Fallback: read last_metrics from state.json') + parser.add_argument('--output', '-o', help='Output file (default: stdout)') + + args = parser.parse_args() + + if args.from_state: + # Fallback mode: use cached metrics from state + with open(args.from_state) as f: + state = json.load(f) + metrics = state.get('last_metrics', {}) + if not metrics: + print('ERROR: No cached metrics in state.json', file=sys.stderr) + sys.exit(1) + else: + if not args.releases_json or not args.prs_json: + parser.error('--releases-json and --prs-json required (or use --from-state)') + + with open(args.releases_json) as f: + releases = json.load(f) + with open(args.prs_json) as f: + prs = json.load(f) + + releases_count = parse_releases_count(releases) + avg_merge = calc_avg_merge_time_hours(prs) + ship_rate = calc_ship_rate(prs) + loc_changed = calc_loc_changed(args.repo_root) + commits = calc_commits_24h(args.repo_root) + prs_count = calc_prs_24h(args.owner, args.repo) + + metrics = format_metrics( + releases_per_day=releases_count, + avg_merge_hours=avg_merge, + ship_rate=ship_rate, + parallel_agents=args.parallel_agents, + loc_changed_24h=loc_changed, + commits_24h=commits, + prs_24h=prs_count, + ) + + output = json.dumps(metrics, indent=2) + if args.output: + with open(args.output, 'w') as f: + f.write(output + '\n') + else: + print(output) + + +if __name__ == '__main__': + main() diff --git a/.genie/agents/metrics-updater/tools/perf-analyzer.py b/.genie/agents/metrics-updater/tools/perf-analyzer.py new file mode 100755 index 000000000..b3dcdb01c --- /dev/null +++ b/.genie/agents/metrics-updater/tools/perf-analyzer.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +"""perf-analyzer.py — Analyze runs.jsonl for performance trends and bottlenecks. + +Reads the run log, identifies the slowest steps, tracks improvement over time, +and outputs a structured performance report for use in /refine context. + +Usage: + python3 perf-analyzer.py [--runs-file ] [--format text|json] [--last-n ] + +Output: + - Performance summary (latest run vs first run) + - Bottleneck identification (slowest steps across runs) + - Improvement trend (duration_ms over time) + - Recommendations for tool generation +""" + +import json +import sys +import argparse +from datetime import datetime, timezone +from pathlib import Path +from collections import defaultdict + + +def load_runs(runs_file: str) -> list[dict]: + """Load all runs from JSONL file.""" + runs = [] + path = Path(runs_file) + if not path.exists(): + return runs + for line in path.read_text().strip().split('\n'): + if line.strip(): + try: + runs.append(json.loads(line)) + except json.JSONDecodeError: + continue + return runs + + +def analyze_bottlenecks(runs: list[dict]) -> dict: + """Identify consistently slow steps across runs.""" + step_totals = defaultdict(list) + for run in runs: + for step in run.get('steps', []): + step_totals[step['name']].append(step['duration_ms']) + + bottlenecks = {} + for name, durations in step_totals.items(): + bottlenecks[name] = { + 'avg_ms': round(sum(durations) / len(durations), 1), + 'max_ms': max(durations), + 'min_ms': min(durations), + 'runs': len(durations), + } + + # Sort by average duration descending + bottlenecks = dict(sorted(bottlenecks.items(), key=lambda x: x[1]['avg_ms'], reverse=True)) + return bottlenecks + + +def analyze_trends(runs: list[dict]) -> dict: + """Track improvement trends over time.""" + if not runs: + return {'improvement_pct': 0, 'trend': 'no_data'} + + durations = [r['duration_ms'] for r in runs] + first = durations[0] + last = durations[-1] + + improvement = round((1 - last / first) * 100, 1) if first > 0 else 0 + + trend = 'improving' if improvement > 5 else ('degrading' if improvement < -5 else 'stable') + + return { + 'first_run_ms': first, + 'latest_run_ms': last, + 'improvement_pct': improvement, + 'trend': trend, + 'total_runs': len(runs), + 'avg_duration_ms': round(sum(durations) / len(durations), 1), + } + + +def generate_recommendations(bottlenecks: dict, runs: list[dict]) -> list[dict]: + """Generate tool generation recommendations based on bottleneck analysis.""" + recommendations = [] + + for step_name, stats in bottlenecks.items(): + if stats['avg_ms'] < 500: + continue # Skip fast steps + + rec = { + 'step': step_name, + 'avg_ms': stats['avg_ms'], + 'priority': 'high' if stats['avg_ms'] > 2000 else 'medium', + } + + if 'fetch' in step_name: + rec['suggestion'] = f'Cache {step_name} API responses to avoid redundant calls' + rec['tool_type'] = 'cache' + elif 'parse' in step_name: + rec['suggestion'] = f'Pre-compile parsing patterns for {step_name}' + rec['tool_type'] = 'parser_optimization' + elif 'commit' in step_name: + rec['suggestion'] = f'Batch git operations in {step_name}' + rec['tool_type'] = 'git_optimization' + elif 'readme' in step_name.lower() or 'update' in step_name: + rec['suggestion'] = f'Cache README template for {step_name}' + rec['tool_type'] = 'template_cache' + else: + rec['suggestion'] = f'Optimize {step_name} — investigate bottleneck' + rec['tool_type'] = 'generic' + + recommendations.append(rec) + + # Sort by priority then avg_ms + priority_order = {'high': 0, 'medium': 1, 'low': 2} + recommendations.sort(key=lambda r: (priority_order.get(r['priority'], 2), -r['avg_ms'])) + + return recommendations + + +def format_text_report(runs: list[dict], bottlenecks: dict, trends: dict, + recommendations: list[dict]) -> str: + """Format the analysis as a human-readable text report.""" + lines = ['# Performance Analysis Report', ''] + + # Summary + lines.append('## Summary') + lines.append(f'- Total runs analyzed: {trends["total_runs"]}') + lines.append(f'- Average duration: {trends["avg_duration_ms"]}ms') + lines.append(f'- First run: {trends["first_run_ms"]}ms') + lines.append(f'- Latest run: {trends["latest_run_ms"]}ms') + lines.append(f'- Improvement: {trends["improvement_pct"]}% ({trends["trend"]})') + lines.append('') + + # Latest run details + if runs: + latest = runs[-1] + lines.append('## Latest Run') + lines.append(f'- Timestamp: {latest.get("timestamp", "N/A")}') + lines.append(f'- Duration: {latest["duration_ms"]}ms') + lines.append(f'- API calls: {latest.get("api_calls", 0)}') + lines.append(f'- Status: {latest.get("status", "unknown")}') + lines.append(f'- Slowest step: {latest.get("slowest_step", "unknown")}') + if latest.get('errors'): + lines.append(f'- Errors: {", ".join(latest["errors"])}') + lines.append('') + + if latest.get('steps'): + lines.append('### Step Breakdown (Latest)') + for step in sorted(latest['steps'], key=lambda s: s['duration_ms'], reverse=True): + bar = '#' * max(1, step['duration_ms'] // 100) + lines.append(f' {step["name"]:20s} {step["duration_ms"]:6d}ms {bar}') + lines.append('') + + # Bottlenecks + if bottlenecks: + lines.append('## Bottlenecks (All Runs)') + for name, stats in bottlenecks.items(): + lines.append(f' {name:20s} avg={stats["avg_ms"]}ms max={stats["max_ms"]}ms min={stats["min_ms"]}ms') + lines.append('') + + # Recommendations + if recommendations: + lines.append('## Recommendations for Tool Generation') + for i, rec in enumerate(recommendations, 1): + lines.append(f'{i}. [{rec["priority"].upper()}] {rec["suggestion"]}') + lines.append(f' Step: {rec["step"]} (avg {rec["avg_ms"]}ms)') + lines.append(f' Tool type: {rec["tool_type"]}') + lines.append('') + + # Error history + error_runs = [r for r in runs if r.get('errors')] + if error_runs: + lines.append('## Error History') + for run in error_runs[-5:]: # Last 5 error runs + lines.append(f' {run["timestamp"]}: {", ".join(run["errors"])}') + lines.append('') + + return '\n'.join(lines) + + +def format_json_report(runs: list[dict], bottlenecks: dict, trends: dict, + recommendations: list[dict]) -> str: + """Format the analysis as structured JSON.""" + report = { + 'generated_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'), + 'trends': trends, + 'bottlenecks': bottlenecks, + 'recommendations': recommendations, + 'error_count': sum(1 for r in runs if r.get('errors')), + 'success_count': sum(1 for r in runs if r.get('status') == 'success'), + } + if runs: + report['latest_run'] = { + 'timestamp': runs[-1].get('timestamp'), + 'duration_ms': runs[-1]['duration_ms'], + 'status': runs[-1].get('status'), + 'slowest_step': runs[-1].get('slowest_step'), + } + return json.dumps(report, indent=2) + + +def main(): + parser = argparse.ArgumentParser(description='Analyze metrics-updater performance') + parser.add_argument('--runs-file', default=None, + help='Path to runs.jsonl (default: auto-detect)') + parser.add_argument('--format', choices=['text', 'json'], default='text', + help='Output format (default: text)') + parser.add_argument('--last-n', type=int, default=0, + help='Analyze only the last N runs (0 = all)') + parser.add_argument('--output', '-o', help='Output file (default: stdout)') + + args = parser.parse_args() + + # Auto-detect runs file + if not args.runs_file: + script_dir = Path(__file__).parent + agent_dir = script_dir.parent + args.runs_file = str(agent_dir / 'runs.jsonl') + + runs = load_runs(args.runs_file) + if args.last_n > 0: + runs = runs[-args.last_n:] + + if not runs: + print('No runs found in ' + args.runs_file, file=sys.stderr) + sys.exit(1) + + bottlenecks = analyze_bottlenecks(runs) + trends = analyze_trends(runs) + recommendations = generate_recommendations(bottlenecks, runs) + + if args.format == 'json': + output = format_json_report(runs, bottlenecks, trends, recommendations) + else: + output = format_text_report(runs, bottlenecks, trends, recommendations) + + if args.output: + Path(args.output).write_text(output + '\n') + print(f'Report written to {args.output}', file=sys.stderr) + else: + print(output) + + +if __name__ == '__main__': + main() diff --git a/.genie/agents/metrics-updater/tools/run-metrics.sh b/.genie/agents/metrics-updater/tools/run-metrics.sh new file mode 100755 index 000000000..a100acb5d --- /dev/null +++ b/.genie/agents/metrics-updater/tools/run-metrics.sh @@ -0,0 +1,326 @@ +#!/usr/bin/env bash +# run-metrics.sh — Main orchestrator for daily metrics update +# Fetches GitHub metrics, updates README.md, commits, and logs the run. +# Includes granular step timing for performance analysis and self-refinement. +# +# Usage: bash run-metrics.sh [--dry-run] [--repo-root ] +# +# Exit codes: +# 0 — Success (README updated and committed) +# 1 — Fatal error +# 2 — No changes (metrics unchanged or fallback used with no diff) + +set -euo pipefail + +# --- Configuration --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENT_DIR="$(dirname "$SCRIPT_DIR")" +REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" +OWNER="automagik-dev" +REPO="genie" +DRY_RUN=false +API_CALLS=0 +ERRORS=() + +# --- Step Timing Infrastructure --- +declare -a STEP_NAMES=() +declare -a STEP_DURATIONS=() +STEP_START=0 + +now_ms() { + date +%s%3N 2>/dev/null || python3 -c 'import time; print(int(time.time()*1000))' +} + +step_start() { + STEP_START=$(now_ms) +} + +step_end() { + local name="$1" + local end_ms + end_ms=$(now_ms) + local duration=$((end_ms - STEP_START)) + STEP_NAMES+=("$name") + STEP_DURATIONS+=("$duration") +} + +RUN_START=$(now_ms) + +# Parse args +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=true; shift ;; + --repo-root) REPO_ROOT="$2"; shift 2 ;; + *) echo "Unknown arg: $1" >&2; exit 1 ;; + esac +done + +STATE_FILE="$AGENT_DIR/state.json" +RUNS_FILE="$AGENT_DIR/runs.jsonl" +README_FILE="$REPO_ROOT/README.md" +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +log() { echo "[metrics-updater] $*" >&2; } +log_error() { ERRORS+=("$1"); log "ERROR: $1"; } + +# --- Helper: safe gh API call with counting --- +gh_api() { + local endpoint="$1" + shift + API_CALLS=$((API_CALLS + 1)) + gh api "$endpoint" "$@" +} + +# --- Step 1: Load previous state for fallback --- +step_start +log "Loading state from $STATE_FILE" +LAST_METRICS="" +if [[ -f "$STATE_FILE" ]]; then + LAST_METRICS=$(python3 -c " +import json, sys +with open('$STATE_FILE') as f: + s = json.load(f) +m = s.get('last_metrics') +if m: + print(json.dumps(m)) +" 2>/dev/null || true) +fi +step_end "load_state" + +# --- Step 2: Fetch metrics from GitHub API --- +FETCH_OK=true + +# 2a: Releases in last 24h +step_start +log "Fetching releases..." +RELEASES_JSON="$TMP_DIR/releases.json" +if gh_api "repos/$OWNER/$REPO/releases" --paginate > "$RELEASES_JSON" 2>&1; then + log "Releases fetched OK" +else + log_error "Failed to fetch releases" + FETCH_OK=false +fi +step_end "fetch_releases" + +# 2b: Closed/merged PRs in last 7 days +step_start +log "Fetching merged PRs..." +PRS_JSON="$TMP_DIR/prs.json" +if gh_api "repos/$OWNER/$REPO/pulls?state=closed&sort=updated&direction=desc&per_page=100" \ + > "$PRS_JSON" 2>&1; then + log "PRs fetched OK" +else + log_error "Failed to fetch PRs" + FETCH_OK=false +fi +step_end "fetch_prs" + +# 2c: Parallel agents (count active genie workers or tmux sessions) +step_start +log "Counting parallel agents..." +PARALLEL_AGENTS=0 +if command -v genie &>/dev/null; then + # Count active workers from the worker registry + WORKERS_FILE="${GENIE_HOME:-$HOME/.genie}/workers.json" + if [[ -f "$WORKERS_FILE" ]]; then + PARALLEL_AGENTS=$(python3 -c " +import json +with open('$WORKERS_FILE') as f: + workers = json.load(f) +active = [w for w in workers if w.get('status') in ('running', 'active', 'idle')] +print(len(active)) +" 2>/dev/null || echo 0) + fi +fi +# Fallback: count tmux sessions with genie prefix +if [[ "$PARALLEL_AGENTS" == "0" ]] && command -v tmux &>/dev/null; then + PARALLEL_AGENTS=$(tmux list-sessions 2>/dev/null | grep -c 'genie' || echo 0) +fi +log "Parallel agents: $PARALLEL_AGENTS" +step_end "count_agents" + +# --- Step 3: Parse and calculate metrics --- +METRICS_JSON="$TMP_DIR/metrics.json" +step_start +if [[ "$FETCH_OK" == "true" ]]; then + log "Calculating metrics..." + if python3 "$SCRIPT_DIR/parse-metrics.py" \ + --releases-json "$RELEASES_JSON" \ + --prs-json "$PRS_JSON" \ + --parallel-agents "$PARALLEL_AGENTS" \ + --repo-root "$REPO_ROOT" \ + --owner "$OWNER" \ + --repo "$REPO" \ + -o "$METRICS_JSON"; then + log "Metrics calculated OK" + else + log_error "Metrics calculation failed" + FETCH_OK=false + fi +fi +step_end "parse_metrics" + +# --- Step 3b: Fallback to last known metrics --- +if [[ "$FETCH_OK" != "true" ]]; then + step_start + log "Falling back to cached metrics..." + if [[ -n "$LAST_METRICS" ]]; then + echo "$LAST_METRICS" > "$METRICS_JSON" + log "Using cached metrics from state.json" + else + log_error "No cached metrics available — cannot update README" + # Log the failed run with step data + RUN_END=$(now_ms) + DURATION=$((RUN_END - RUN_START)) + STEPS_JSON=$(python3 -c " +import json +names = $(printf '%s\n' "${STEP_NAMES[@]}" | python3 -c "import sys,json; print(json.dumps([l.strip() for l in sys.stdin]))") +durs = $(printf '%s\n' "${STEP_DURATIONS[@]}" | python3 -c "import sys,json; print(json.dumps([int(l.strip()) for l in sys.stdin]))") +print(json.dumps([{'name':n,'duration_ms':d} for n,d in zip(names,durs)])) +") + ERRORS_JSON=$(printf '%s\n' "${ERRORS[@]}" | python3 -c "import sys,json; print(json.dumps([l.strip() for l in sys.stdin]))") + echo "{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"duration_ms\":$DURATION,\"api_calls\":$API_CALLS,\"tools_generated\":0,\"errors\":$ERRORS_JSON,\"status\":\"failed\",\"fallback\":true,\"steps\":$STEPS_JSON}" >> "$RUNS_FILE" + exit 1 + fi + step_end "fallback_load" +fi + +# --- Step 4: Update README --- +step_start +log "Updating README at $README_FILE..." +README_CHANGED=false +if python3 "$SCRIPT_DIR/update-readme.py" --metrics "$METRICS_JSON" --readme "$README_FILE"; then + README_CHANGED=true +fi +step_end "update_readme" + +# --- Step 5: Update state.json --- +step_start +log "Updating state.json..." +METRICS_CONTENT=$(cat "$METRICS_JSON") +python3 -c " +import json +from datetime import datetime, timezone +with open('$STATE_FILE') as f: + state = json.load(f) +state['last_metrics'] = json.loads('''$METRICS_CONTENT''') +state['last_run_at'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') +state['run_count'] = state.get('run_count', 0) + 1 +with open('$STATE_FILE', 'w') as f: + json.dump(state, f, indent=2) + f.write('\n') +" +log "State updated" +step_end "update_state" + +# --- Step 6: Commit (if not dry-run and README changed) --- +step_start +if [[ "$DRY_RUN" == "true" ]]; then + log "DRY RUN — skipping commit" + cat "$METRICS_JSON" +elif [[ "$README_CHANGED" == "true" ]]; then + log "Generating commit message..." + COMMIT_MSG=$(bash "$SCRIPT_DIR/commit-formatter.sh" "$METRICS_JSON") + log "Committing: $COMMIT_MSG" + + cd "$REPO_ROOT" + git add README.md + git add .genie/agents/metrics-updater/state.json + git commit -m "$COMMIT_MSG" + log "Committed successfully" +else + log "No README changes — skipping commit" +fi +step_end "commit" + +# --- Step 7: Build step timing JSON and find slowest step --- +RUN_END=$(now_ms) +DURATION=$((RUN_END - RUN_START)) + +# Build CSV env vars for step timing +STEP_NAMES_CSV="" +STEP_DURS_CSV="" +for i in "${!STEP_NAMES[@]}"; do + [[ -n "$STEP_NAMES_CSV" ]] && STEP_NAMES_CSV+="," + STEP_NAMES_CSV+="${STEP_NAMES[$i]}" + [[ -n "$STEP_DURS_CSV" ]] && STEP_DURS_CSV+="," + STEP_DURS_CSV+="${STEP_DURATIONS[$i]}" +done + +# Re-run with actual data +STEPS_JSON=$(STEP_NAMES_CSV="$STEP_NAMES_CSV" STEP_DURS_CSV="$STEP_DURS_CSV" python3 << 'PYEOF' +import json, os + +names_raw = os.environ.get('STEP_NAMES_CSV', '') +durs_raw = os.environ.get('STEP_DURS_CSV', '') + +names = [n for n in names_raw.split(',') if n] +durs = [int(d) for d in durs_raw.split(',') if d] + +steps = [{'name': n, 'duration_ms': d} for n, d in zip(names, durs)] +print(json.dumps(steps)) +PYEOF +) + +# Find slowest step +SLOWEST_STEP=$(STEP_NAMES_CSV="$STEP_NAMES_CSV" STEP_DURS_CSV="$STEP_DURS_CSV" python3 << 'PYEOF' +import os + +names = [n for n in os.environ.get('STEP_NAMES_CSV', '').split(',') if n] +durs = [int(d) for d in os.environ.get('STEP_DURS_CSV', '').split(',') if d] + +if names and durs: + max_idx = durs.index(max(durs)) + print(names[max_idx]) +else: + print("unknown") +PYEOF +) + +# --- Step 8: Log run with full performance data --- +ERRORS_JSON="[]" +if [[ ${#ERRORS[@]} -gt 0 ]]; then + ERRORS_JSON=$(printf '%s\n' "${ERRORS[@]}" | python3 -c "import sys,json; print(json.dumps([l.strip() for l in sys.stdin]))") +fi +RUN_STATUS="success" +[[ "$README_CHANGED" != "true" ]] && RUN_STATUS="no_changes" +FALLBACK_USED="False" +[[ "$FETCH_OK" != "true" ]] && FALLBACK_USED="True" + +# Count tools in the tools directory (excluding run-metrics.sh itself) +TOOLS_COUNT=$(find "$SCRIPT_DIR" -type f \( -name '*.sh' -o -name '*.py' \) ! -name 'run-metrics.sh' | wc -l | tr -d ' ') + +METRICS_CONTENT=$(cat "$METRICS_JSON") + +python3 << PYEOF +import json +from datetime import datetime, timezone + +entry = { + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "duration_ms": $DURATION, + "api_calls": $API_CALLS, + "tools_generated": 0, + "tools_available": $TOOLS_COUNT, + "errors": json.loads('$ERRORS_JSON'), + "status": "$RUN_STATUS", + "fallback": $FALLBACK_USED, + "slowest_step": "$SLOWEST_STEP", + "steps": json.loads('$STEPS_JSON'), + "metrics": json.loads('''$METRICS_CONTENT''') +} + +with open("$RUNS_FILE", "a") as f: + f.write(json.dumps(entry) + "\n") +PYEOF + +log "Run complete: ${DURATION}ms, ${API_CALLS} API calls, status=$RUN_STATUS, slowest=$SLOWEST_STEP" + +# Print step breakdown +log "Step breakdown:" +for i in "${!STEP_NAMES[@]}"; do + log " ${STEP_NAMES[$i]}: ${STEP_DURATIONS[$i]}ms" +done + +cat "$METRICS_JSON" diff --git a/.genie/agents/metrics-updater/tools/self-refine.sh b/.genie/agents/metrics-updater/tools/self-refine.sh new file mode 100755 index 000000000..8104fd0f9 --- /dev/null +++ b/.genie/agents/metrics-updater/tools/self-refine.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# self-refine.sh — Prepare refinement context and trigger /refine on AGENT.md +# +# This script: +# 1. Generates a performance report from runs.jsonl +# 2. Appends performance context to the bottom of AGENT.md temporarily +# 3. Calls /refine in file mode on AGENT.md (via the agent's Claude Code session) +# 4. Updates state.json with last_refined_at +# +# Usage: +# bash self-refine.sh [--agent-dir ] [--dry-run] +# +# The agent should call this after run-metrics.sh completes. +# In dry-run mode, it prepares the context file but doesn't modify AGENT.md. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENT_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}" +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --agent-dir) AGENT_DIR="$2"; shift 2 ;; + --dry-run) DRY_RUN=true; shift ;; + *) echo "Unknown arg: $1" >&2; exit 1 ;; + esac +done + +AGENT_FILE="$AGENT_DIR/AGENT.md" +STATE_FILE="$AGENT_DIR/state.json" +RUNS_FILE="$AGENT_DIR/runs.jsonl" +CONTEXT_FILE="$AGENT_DIR/refine-context.md" + +log() { echo "[self-refine] $*" >&2; } + +# --- Step 1: Generate performance report --- +log "Generating performance report..." +PERF_REPORT="" +if [[ -f "$RUNS_FILE" ]]; then + PERF_REPORT=$(python3 "$SCRIPT_DIR/perf-analyzer.py" --runs-file "$RUNS_FILE" --format text 2>/dev/null || echo "Failed to generate perf report") +fi + +# --- Step 2: Get latest run data --- +LATEST_RUN="" +if [[ -f "$RUNS_FILE" ]]; then + LATEST_RUN=$(tail -1 "$RUNS_FILE") +fi + +# --- Step 3: Count existing tools --- +TOOL_COUNT=$(find "$SCRIPT_DIR" -type f \( -name '*.sh' -o -name '*.py' \) | wc -l | tr -d ' ') + +# --- Step 4: Build refinement context --- +log "Building refinement context..." +cat > "$CONTEXT_FILE" << CTXEOF +# Refinement Context — $(date -u +%Y-%m-%dT%H:%M:%SZ) + +## Performance Data (Latest Run) +\`\`\`json +$LATEST_RUN +\`\`\` + +## Performance Analysis +$PERF_REPORT + +## Current Tool Inventory ($TOOL_COUNT tools) +$(ls -1 "$SCRIPT_DIR"/*.sh "$SCRIPT_DIR"/*.py 2>/dev/null | while read -r f; do + basename "$f" +done) + +## Refinement Request + +Analyze the performance data above. Improve this agent prompt to be: +1. **Faster** — Identify the slowest step and add instructions to optimize it +2. **More resilient** — If errors occurred, add handling for those failure modes +3. **Tool-aware** — Reference any new tools that should be generated for slow steps +4. **Measurable** — Ensure the prompt tracks metrics that enable future refinement + +Focus on the slowest step identified in the performance analysis. If API calls are slow, +instruct the agent to use the cached github-api.sh wrapper. If parsing is slow, generate +an optimized parser. If commits are slow, batch operations. + +Do NOT change the core metrics (releases/day, bug-fix time, SHIP rate, parallel agents). +Do NOT remove the self-refinement protocol section. +Increment the version number in the frontmatter. +CTXEOF + +log "Refinement context written to: $CONTEXT_FILE" + +if [[ "$DRY_RUN" == "true" ]]; then + log "DRY RUN — context file prepared but AGENT.md not modified" + cat "$CONTEXT_FILE" + exit 0 +fi + +# --- Step 5: Append context to AGENT.md for /refine --- +# The agent will call `/refine @AGENT.md` which reads this file. +# We append the context so the refiner has performance data. +log "Appending refinement context to AGENT.md..." + +# Save original AGENT.md +cp "$AGENT_FILE" "$AGENT_FILE.pre-refine" + +cat >> "$AGENT_FILE" << APPENDEOF + +--- + + +$(cat "$CONTEXT_FILE") + +APPENDEOF + +log "AGENT.md prepared for /refine. The agent should now run:" +log " /refine @$AGENT_FILE" +log "" +log "After /refine completes, update state.json with:" +log " python3 -c \"import json; from datetime import datetime, timezone; s=json.load(open('$STATE_FILE')); s['last_refined_at']=datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'); json.dump(s, open('$STATE_FILE','w'), indent=2)\"" + +# --- Step 6: Update state.json --- +log "Updating last_refined_at in state.json..." +python3 -c " +import json +from datetime import datetime, timezone +with open('$STATE_FILE') as f: + state = json.load(f) +state['last_refined_at'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') +with open('$STATE_FILE', 'w') as f: + json.dump(state, f, indent=2) + f.write('\n') +" +log "state.json updated with last_refined_at" + +echo "$CONTEXT_FILE" diff --git a/.genie/agents/metrics-updater/tools/update-readme.py b/.genie/agents/metrics-updater/tools/update-readme.py new file mode 100755 index 000000000..d83fce66b --- /dev/null +++ b/.genie/agents/metrics-updater/tools/update-readme.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""update-readme.py — Update README.md metrics table between signature markers. + +Auto-generated by metrics-updater agent (v1, 2026-03-24). +Updated: v2 — ISO timestamp signature, 7 metric rows, no Updated column. + +Usage: + python3 update-readme.py --metrics --readme + python3 update-readme.py --metrics # defaults to README.md in repo root +""" + +import json +import re +import sys +import argparse +from datetime import datetime, timezone +from pathlib import Path + +START_MARKER_PREFIX = '' + + +def build_metrics_table(metrics: dict) -> str: + """Build the markdown metrics table with signature markers.""" + timestamp = metrics.get('updated', datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')) + releases = metrics.get('releases_per_day', 0) + avg_time = metrics.get('avg_bugfix_time_hours', 0) + ship_rate = metrics.get('ship_rate_pct', 0) + loc_changed = metrics.get('loc_changed_24h', 0) + commits = metrics.get('commits_24h', 0) + prs = metrics.get('prs_24h', 0) + agents = metrics.get('parallel_agents', 0) + + start_marker = f'' + + lines = [ + start_marker, + '| Metric | Value |', + '|--------|-------|', + f'| Releases/day | {releases} |', + f'| Avg bug-fix time | {avg_time}h |', + f'| SHIP rate | {ship_rate}% |', + f'| Lines changed (24h) | {loc_changed:,} |', + f'| Commits (24h) | {commits} |', + f'| Pull requests (24h) | {prs} |', + f'| Parallel agents | {agents} |', + END_MARKER, + ] + return '\n'.join(lines) + + +def update_readme(readme_path: str, metrics: dict) -> bool: + """Update README.md metrics table. Returns True if content changed.""" + path = Path(readme_path) + content = path.read_text() + + new_table = build_metrics_table(metrics) + + # Check if markers exist (match any START marker variant) + if START_MARKER_PREFIX in content and ('.*?', + re.DOTALL, + ) + new_content = pattern.sub(new_table, content) + else: + # Insert before "## What is Genie?" heading + anchor = '## What is Genie?' + if anchor in content: + new_content = content.replace(anchor, new_table + '\n\n' + anchor) + else: + # Fallback: append before first ## heading + match = re.search(r'^## ', content, re.MULTILINE) + if match: + pos = match.start() + new_content = content[:pos] + new_table + '\n\n' + content[pos:] + else: + print('ERROR: Cannot find insertion point in README', file=sys.stderr) + return False + + if new_content == content: + print('No changes to README (metrics unchanged)') + return False + + path.write_text(new_content) + print(f'README updated with metrics at {readme_path}') + return True + + +def main(): + parser = argparse.ArgumentParser(description='Update README.md metrics table') + parser.add_argument('--metrics', required=True, help='Path to metrics JSON file') + parser.add_argument('--readme', default='README.md', help='Path to README.md') + + args = parser.parse_args() + + with open(args.metrics) as f: + metrics = json.load(f) + + changed = update_readme(args.readme, metrics) + sys.exit(0 if changed else 2) # exit 2 = no changes (not an error) + + +if __name__ == '__main__': + main() diff --git a/.genie/brainstorms/workflow-engine-runtime/DESIGN.md b/.genie/brainstorms/workflow-engine-runtime/DESIGN.md new file mode 100644 index 000000000..74eb0939a --- /dev/null +++ b/.genie/brainstorms/workflow-engine-runtime/DESIGN.md @@ -0,0 +1,66 @@ +# Design: Workflow Engine Runtime + +| Field | Value | +|-------|-------| +| **Slug** | `workflow-engine-runtime` | +| **Date** | 2026-03-28 | +| **WRS** | 100/100 | + +## Problem +Board columns define gates, actions, auto-advance, and transitions, but nothing enforces or executes them — the pipeline is a passive kanban pretending to be a workflow engine. + +## Scope +### IN +- Gate enforcement in moveTask() — check actor type vs column gate +- Action auto-invocation — spawn skill or run shell command when task enters column +- Auto-advance — move task to next column when agent-gated action completes +- On-fail handling — invoke on_fail skill/script when action fails +- task_action_runs table — track action invocations per task +- task_gate_evaluations table — audit trail for gate checks +- Action type dispatch: `/skill` prefix → spawn agent, `!` prefix → shell command + +### OUT +- Webhook action type (filed as #852) +- A2A action type (filed as #853) +- Human approval via WhatsApp/Slack (filed as #854) +- MCP tool invocation (filed as #855) +- Conditional fan-out (filed as #856) +- Transition condition evaluator (defer — transitions[] always empty today) +- Role enforcement (defer — roles[] always ["*"] today) +- Parallel flag (defer — parallel always false today) + +## Approach +Build a workflow listener that hooks into moveTask(). When a task moves to a new column, the listener reads the column config and dispatches the appropriate action. Uses PG events (genie_runtime_events from PR #831) for completion/failure signals. + +Two action types for V1: +- **Skill** (`/work`, `/review`, `/qa`) — spawn agent with skill via genie spawn +- **Script** (`!bun test`, `!make deploy`) — run shell command, capture exit code + +## Decisions +| Decision | Rationale | +|----------|-----------| +| PG events over NATS | PR #831 removes NATS dep; PG events are durable + queryable | +| Gate enforcement in moveTask() | Single enforcement point — all moves go through this function | +| task_action_runs table | Need to track started/completed/failed/retries per action | +| Skill prefix `/` + script prefix `!` | Simple string dispatch, extensible for future types | +| Max 2 retries on failure | Prevents infinite loops, matches dream orchestrator pattern | + +## Risks & Assumptions +| Risk | Severity | Mitigation | +|------|----------|------------| +| PG single point of failure | Medium | Circuit breaker in runtime-events (PR #831 caveat) | +| Action latency (PG write 5-10ms) | Low | Acceptable for orchestration, not real-time streaming | +| Script injection via action field | Medium | Only board creators set actions; validate no pipes/redirects | +| Infinite auto-advance loop | High | Max chain depth (8 columns) + cycle detection | + +## Success Criteria +- [ ] moveTask() rejects agent moving to human-gated column +- [ ] moveTask() rejects human moving to agent-only column (if human+agent not set) +- [ ] Task entering a column with action `/work` spawns an engineer agent +- [ ] Task entering a column with action `!bun test` runs the command and captures exit code +- [ ] Action completion with auto_advance=true moves task to next column automatically +- [ ] Action failure with on_fail set invokes the on_fail action +- [ ] Action failure after 2 retries marks task as blocked +- [ ] task_action_runs table records every action invocation with status +- [ ] task_gate_evaluations table records every gate check with result +- [ ] Full chain works: task dropped in Triage → auto-advance through agent columns → stops at human gate diff --git a/.genie/qa/framework/cross-repo-specs.md b/.genie/qa/framework/cross-repo-specs.md new file mode 100644 index 000000000..6f0342a56 --- /dev/null +++ b/.genie/qa/framework/cross-repo-specs.md @@ -0,0 +1,12 @@ +# Test: QA specs are read from repo .genie/qa/ directory + +## Setup +- start follow on team + +## Actions +1. run mkdir -p /tmp/qa-test-repo/.genie/qa/smoke && echo '# Test: dummy' > /tmp/qa-test-repo/.genie/qa/smoke/dummy.md && cd /tmp/qa-test-repo && genie qa status 2>&1 +2. wait 2s + +## Expect +- [ ] output contains dummy +- [ ] output contains smoke diff --git a/.genie/qa/framework/domain-discovery.md b/.genie/qa/framework/domain-discovery.md new file mode 100644 index 000000000..ddc554035 --- /dev/null +++ b/.genie/qa/framework/domain-discovery.md @@ -0,0 +1,14 @@ +# Test: QA discovers specs organized by domain + +## Setup +- start follow on team + +## Actions +1. run mkdir -p /tmp/qa-domain-test/.genie/qa/api /tmp/qa-domain-test/.genie/qa/auth && echo '# Test: health' > /tmp/qa-domain-test/.genie/qa/api/health.md && echo '# Test: login' > /tmp/qa-domain-test/.genie/qa/auth/login.md && echo 'not-a-spec.txt' > /tmp/qa-domain-test/.genie/qa/api/readme.txt && cd /tmp/qa-domain-test && genie qa status 2>&1 +2. wait 2s + +## Expect +- [ ] output contains api +- [ ] output contains auth +- [ ] output contains health +- [ ] output contains login diff --git a/.genie/qa/framework/results-isolation.md b/.genie/qa/framework/results-isolation.md new file mode 100644 index 000000000..4f1b60b26 --- /dev/null +++ b/.genie/qa/framework/results-isolation.md @@ -0,0 +1,13 @@ +# Test: QA results are isolated per repo + +## Setup +- start follow on team + +## Actions +1. run mkdir -p /tmp/qa-repo-a/.genie/qa && echo '# Test: repo-a test' > /tmp/qa-repo-a/.genie/qa/test-a.md && mkdir -p /tmp/qa-repo-b/.genie/qa && echo '# Test: repo-b test' > /tmp/qa-repo-b/.genie/qa/test-b.md && cd /tmp/qa-repo-a && genie qa status 2>&1 && echo "---SEPARATOR---" && cd /tmp/qa-repo-b && genie qa status 2>&1 +2. wait 2s + +## Expect +- [ ] output contains test-a +- [ ] output contains test-b +- [ ] output contains SEPARATOR diff --git a/.genie/qa/lifecycle/agent-lifecycle.md b/.genie/qa/lifecycle/agent-lifecycle.md new file mode 100644 index 000000000..1588dc846 --- /dev/null +++ b/.genie/qa/lifecycle/agent-lifecycle.md @@ -0,0 +1,13 @@ +# Test: Agent spawn completes and responds to task + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "echo LIFECYCLE_TEST and tell me done" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=LIFECYCLE_TEST +- [ ] follow stream contains event kind=message text~=LIFECYCLE_TEST diff --git a/.genie/qa/lifecycle/idle-detection.md b/.genie/qa/lifecycle/idle-detection.md new file mode 100644 index 000000000..7b58a2e7d --- /dev/null +++ b/.genie/qa/lifecycle/idle-detection.md @@ -0,0 +1,13 @@ +# Test: Agent completes task and tool calls are captured + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "echo IDLE_QA_TEST and send me done" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=IDLE_QA_TEST +- [ ] follow stream contains event kind=tool_call text~=SendMessage diff --git a/.genie/qa/messaging/mailbox-delivery.md b/.genie/qa/messaging/mailbox-delivery.md new file mode 100644 index 000000000..87639c1b4 --- /dev/null +++ b/.genie/qa/messaging/mailbox-delivery.md @@ -0,0 +1,12 @@ +# Test: Mailbox delivery with NATS event + +## Setup +- start follow on team + +## Actions +1. send "please review PR #42" to reviewer +2. wait 2s + +## Expect +- [ ] follow stream contains event kind=message peer=reviewer +- [ ] follow stream contains event source=mailbox text~=review PR diff --git a/.genie/qa/messaging/multi-agent-comm.md b/.genie/qa/messaging/multi-agent-comm.md new file mode 100644 index 000000000..862216c48 --- /dev/null +++ b/.genie/qa/messaging/multi-agent-comm.md @@ -0,0 +1,14 @@ +# Test: Two agents communicate within same team + +## Setup +- start follow on team +- spawn engineer (provider: claude) +- spawn reviewer (provider: claude) + +## Actions +1. send "send a message to reviewer saying CROSS_AGENT_QA" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=SendMessage +- [ ] follow stream contains event kind=message text~=CROSS_AGENT_QA diff --git a/.genie/qa/messaging/round-trip-response.md b/.genie/qa/messaging/round-trip-response.md new file mode 100644 index 000000000..276312b05 --- /dev/null +++ b/.genie/qa/messaging/round-trip-response.md @@ -0,0 +1,13 @@ +# Test: Agent receives message and responds via SendMessage + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "send me a message saying PONG_QA_TEST using SendMessage" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=PONG_QA_TEST +- [ ] follow stream contains event kind=message text~=PONG_QA_TEST diff --git a/.genie/qa/messaging/send-full-id.md b/.genie/qa/messaging/send-full-id.md new file mode 100644 index 000000000..35503d2e4 --- /dev/null +++ b/.genie/qa/messaging/send-full-id.md @@ -0,0 +1,12 @@ +# Test: Send message works with full worker ID + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "reply with FULLID_QA_OK" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=message text~=FULLID_QA_OK diff --git a/.genie/qa/observability/log-filter-type.md b/.genie/qa/observability/log-filter-type.md new file mode 100644 index 000000000..e7d3509df --- /dev/null +++ b/.genie/qa/observability/log-filter-type.md @@ -0,0 +1,14 @@ +# Test: Log filters by event type + +## Setup +- spawn engineer (provider: claude) + +## Actions +1. send "run echo FILTER_TEST in bash" to engineer +2. wait 20s +3. run genie log engineer --ndjson --type message +4. run genie log engineer --ndjson --last 5 + +## Expect +- [ ] output contains message +- [ ] output contains FILTER_TEST diff --git a/.genie/qa/observability/log-follow-events.md b/.genie/qa/observability/log-follow-events.md new file mode 100644 index 000000000..38e613ea8 --- /dev/null +++ b/.genie/qa/observability/log-follow-events.md @@ -0,0 +1,13 @@ +# Test: Follow mode captures tool calls and messages + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "echo HELLO_QA_FOLLOW and send me done" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=HELLO_QA_FOLLOW +- [ ] follow stream contains event kind=message text~=HELLO_QA_FOLLOW diff --git a/.genie/qa/observability/log-static.md b/.genie/qa/observability/log-static.md new file mode 100644 index 000000000..9b128ba38 --- /dev/null +++ b/.genie/qa/observability/log-static.md @@ -0,0 +1,13 @@ +# Test: Static log reads all sources + +## Setup +- spawn engineer (team: log-test) + +## Actions +1. send "implement feature X" to engineer +2. wait 1s +3. run genie log engineer --ndjson --last 10 + +## Expect +- [ ] output contains kind=message +- [ ] output contains text~=implement feature diff --git a/.genie/qa/observability/nats-streaming.md b/.genie/qa/observability/nats-streaming.md new file mode 100644 index 000000000..640a44f2f --- /dev/null +++ b/.genie/qa/observability/nats-streaming.md @@ -0,0 +1,13 @@ +# Test: NATS streaming delivers tool calls from real agent + +## Setup +- start follow on team +- spawn engineer (provider: claude) + +## Actions +1. send "run echo NATS_QA_TEST in bash and send me 'done'" to engineer +2. wait 30s + +## Expect +- [ ] follow stream contains event kind=tool_call text~=NATS_QA_TEST +- [ ] follow stream contains event kind=message text~=done diff --git a/.genie/snapshot.sql.gz b/.genie/snapshot.sql.gz new file mode 100644 index 000000000..bece424d7 Binary files /dev/null and b/.genie/snapshot.sql.gz differ diff --git a/.genie/wishes/agent-flexibility-guide/WISH.md b/.genie/wishes/agent-flexibility-guide/WISH.md new file mode 100644 index 000000000..5d35d7736 --- /dev/null +++ b/.genie/wishes/agent-flexibility-guide/WISH.md @@ -0,0 +1,155 @@ +# Wish: Agent Flexibility Guide (Provider Switching + BYOA) + +| Field | Value | +|-------|-------| +| **Status** | APPROVED | +| **Slug** | `agent-flexibility-guide` | +| **Date** | 2026-03-24 | + +## Summary + +Document how to use Genie with different AI agents. Create a new page showing provider switching patterns, /spawn --provider flag usage, auto-respawn templates, and real multi-provider team examples. Position Genie as provider-agnostic. + +## Scope + +### IN +- New page: `genie/concepts/byoa.mdx` — BYOA positioning, examples, agent neutrality +- Document `/spawn --provider` flag with examples +- Show provider switching patterns (Claude ↔ Codex ↔ BYOA) +- Document auto-respawn template system +- Real examples: multi-provider teams, cost optimization, failover patterns +- Update `docs.json` to include BYOA concept page + +### OUT +- Automatic failover logic (manual only, documented as patterns) +- Custom agent implementation guide (separate wish) +- Testing harness for agent compatibility (out of scope) + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| New concept page | BYOA deserves first-class documentation, not buried in skills | +| `/spawn --provider` examples | Users need to know how to actually use it | +| Real patterns over theory | Show actual multi-provider teams, cost models | + +## Success Criteria + +- [ ] `genie/concepts/byoa.mdx` created with BYOA positioning +- [ ] `/spawn --provider` flag documented with 5+ examples +- [ ] Auto-respawn template system explained +- [ ] 3 real multi-provider team patterns shown (cost optimization, specialization, failover) +- [ ] BYOA concept in docs.json navigation +- [ ] No broken links +- [ ] Tone: vendor-neutral, empowering + +## Execution Strategy + +### Wave 1 +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | writer | Create BYOA concept page + /spawn --provider docs | +| 2 | writer | Multi-provider patterns + examples | + +### Wave 2 +| Group | Agent | Description | +|-------|-------|-------------| +| 3 | reviewer | Validate accuracy, no broken links, tone consistent | + +## Execution Groups + +### Group 1: BYOA Concept Page + +**Goal:** Create new page explaining Genie's provider agnosticism. + +**Deliverables:** +1. `genie/concepts/byoa.mdx` with: + - Definition: BYOA = Bring Your Own Agent + - Genie doesn't care which agent backs workers + - List providers: Claude Code, Codex, Gemini CLI, custom + - `/spawn --provider ` flag usage + - Auto-respawn template system (if agent goes offline, respawned from saved config) + - Why it matters: future-proofs, no vendor lock-in + +**Acceptance Criteria:** +- [ ] File exists at `/tmp/automagik-docs/genie/concepts/byoa.mdx` +- [ ] Explains /spawn --provider flag +- [ ] Lists 5+ provider options +- [ ] Auto-respawn system documented +- [ ] No broken links + +**Validation:** +```bash +grep -q "BYOA\|--provider\|provider" /tmp/automagik-docs/genie/concepts/byoa.mdx +grep -q "auto-respawn\|template" /tmp/automagik-docs/genie/concepts/byoa.mdx +``` + +**depends-on:** none + +--- + +### Group 2: Multi-Provider Patterns + +**Goal:** Show 3 real patterns for using different agents together. + +**Deliverables:** +1. Three patterns in BYOA page: + - **Pattern 1: Specialization** — Claude for reasoning, Codex for parsing, BYOA for domain logic + - **Pattern 2: Cost Optimization** — Use Codex for fast iteration, Claude for final review + - **Pattern 3: Manual Fallback** — If one provider is down, spawn with another (not automatic) + +2. Each pattern includes: + - Problem statement + - Solution (how to do it) + - Code example (`genie spawn --provider `) + - Benefit + trade-offs + - When to use + +**Acceptance Criteria:** +- [ ] 3 patterns documented +- [ ] Each has: problem, solution, code, benefit, when-to-use +- [ ] Examples are realistic, not aspirational +- [ ] Code examples show actual --provider flags + +**Validation:** +```bash +grep -c "Pattern\|Example" /tmp/automagik-docs/genie/concepts/byoa.mdx +grep "genie spawn.*--provider" /tmp/automagik-docs/genie/concepts/byoa.mdx +``` + +**depends-on:** 1 + +--- + +### Group 3: Review + +**Goal:** Validate accuracy and consistency. + +**Deliverables:** +1. Checklist: + - `/spawn --provider` syntax is accurate (not invented) + - Patterns are realistic (not aspirational) + - Links to other doc pages work + - Tone is neutral (no vendor criticism) + - Examples use actual provider names + +**Acceptance Criteria:** +- [ ] No broken links +- [ ] Patterns match actual Genie capabilities +- [ ] Tone is neutral and empowering + +**Validation:** +```bash +# Check for invented features +! grep -q "automatic failover\|auto-failover" /tmp/automagik-docs/genie/concepts/byoa.mdx +``` + +**depends-on:** 1, 2 + +## Files to Create/Modify + +``` +/tmp/automagik-docs/genie/concepts/byoa.mdx (create) +/tmp/automagik-docs/docs.json (add BYOA page to navigation) +``` + diff --git a/.genie/wishes/auto-orchestrate/WISH.md b/.genie/wishes/auto-orchestrate/WISH.md new file mode 100644 index 000000000..4986544cb --- /dev/null +++ b/.genie/wishes/auto-orchestrate/WISH.md @@ -0,0 +1,232 @@ +# Wish: genie work auto-orchestrates full wish execution + +| Field | Value | +|-------|-------| +| **Status** | DRAFT | +| **Slug** | `auto-orchestrate` | +| **Date** | 2026-03-17 | + +## Summary + +`genie work ` (without agent or group) auto-orchestrates the entire wish — reads the Execution Strategy, spawns all agents per wave in parallel, monitors completion, advances waves, and runs review. Reduces the team-lead to 4 lines: read wish, `genie work `, create PR, `genie team done`. Closes #631. + +## Scope + +### IN +- New `genie work ` command (no agent, no group) that orchestrates the full wish +- Parses Execution Strategy waves from WISH.md +- Spawns all agents in a wave as background processes (parallel) +- Monitors via wish state — marks groups done as workers report completion +- Advances to next wave when current wave completes +- Dispatches reviewer after all waves (if `review` group exists in strategy) +- Returns exit 0 when all groups done, exit 1 on failure +- Team-lead prompt updated to use `genie work ` as primary dispatch + +### OUT +- Changes to `genie work #` (still works for manual dispatch) +- Changes to wish state machine logic (already works) +- QA orchestration (team-lead handles that after PR) +- Retry/fix loops within `genie work` (team-lead handles escalation) + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| `genie work ` is a new command signature, not replacing the old one | Backwards compatible. Old `genie work #` still works for manual dispatch. | +| Spawns workers as background tmux panes | Workers run in parallel. `genie work` polls state file for completion, not stdout. | +| Polls wish state, not worker output | Deterministic — workers call `genie done #` when complete, state file is source of truth. | +| 30s poll interval | Fast enough to be responsive, not so fast it thrashes the state file. | +| Max 30min timeout per wave | Safety valve — if a wave doesn't complete in 30min, exit with error. Team-lead decides what to do. | +| Review dispatched automatically if Execution Strategy includes a review wave | Convention: last wave with `reviewer` agent triggers review dispatch. | + +## Success Criteria + +- [ ] `genie work ` reads Execution Strategy and spawns Wave 1 agents in parallel +- [ ] All agents in Wave 1 run simultaneously (visible in `genie ls`) +- [ ] Wave 2 starts only after all Wave 1 groups are done +- [ ] `genie work ` exits 0 when all groups done +- [ ] `genie work ` exits 1 if a wave times out (30min) +- [ ] `genie work #` still works (backwards compatible) +- [ ] Team-lead prompt uses `genie work ` as primary command +- [ ] `bun run check` passes + +## Execution Groups + +### Group 1: Parse Execution Strategy from WISH.md + +**Goal:** Extract wave definitions from the Execution Strategy section. + +**Deliverables:** +1. Add `parseExecutionStrategy(content: string)` to `src/term-commands/dispatch.ts`: + - Parses `### Wave N` headings from the Execution Strategy section + - Extracts the table rows: Group | Agent | Description + - Returns `Wave[]` where each wave has `{ name: string, groups: { group: string, agent: string }[] }` +2. Handle missing Execution Strategy section — fall back to sequential (all groups in one wave, `engineer` as default agent) + +**Acceptance Criteria:** +- [ ] Parses wave tables from wish template format +- [ ] Falls back to sequential if no Execution Strategy section +- [ ] Returns typed `Wave[]` array + +**Validation:** +```bash +bun run typecheck && bun run lint +``` + +**depends-on:** none + +--- + +### Group 2: Implement genie work orchestrator + +**Goal:** The core auto-orchestration loop. + +**Deliverables:** +1. Add `autoOrchestrateCommand(slug: string)` to `src/term-commands/dispatch.ts`: + - Read WISH.md, parse groups and execution strategy + - Auto-initialize wish state + - For each wave in order: + a. Dispatch all groups in the wave using `Promise.all()` — call `workDispatchCommand(agent, slug#group)` for each group concurrently. `workDispatchCommand` spawns a tmux pane and returns — it does NOT wait for the worker to finish, so `Promise.all` resolves once all panes are created. + b. Poll `wishState.getState(slug)` every 30s until all groups in the wave show `done` + c. If 30min elapsed without wave completion, exit 1 with error + - After all waves complete, exit 0 +2. Register the new command signature using Commander's optional arg pattern: + - `genie work [agent]` — if `agent` is provided AND `ref` contains `#`, it's manual mode (`workDispatchCommand`). If only `ref` (no `#`), it's auto mode (`autoOrchestrateCommand`). + - This avoids Commander ambiguity — single command, detect mode by args. + +**Acceptance Criteria:** +- [ ] `genie work ` spawns all Wave 1 agents concurrently (visible in `genie ls`) +- [ ] Polls state every 30s, advances waves correctly +- [ ] Times out after 30min per wave +- [ ] `genie work #` still works unchanged +- [ ] Exit codes: 0 success, 1 timeout/failure + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** Group 1 + +--- + +### Group 3: Engineer reports completion via genie done + +**Goal:** Workers must call `genie done #` when they finish, so the orchestrator's state poll detects completion. + +**Deliverables:** +1. Update `plugins/genie/agents/engineer/AGENTS.md` — add instruction: "After completing all deliverables and validation, call `genie done #` to report completion. The slug and group are in your initial prompt." +2. Update the `initialPrompt` in `workDispatchCommand()` to include the `genie done` instruction: "When done, run: `genie done #`" +3. Sync flat copy `plugins/genie/agents/engineer.md` + +**Acceptance Criteria:** +- [ ] Engineer prompt mentions `genie done` +- [ ] initialPrompt includes completion instruction +- [ ] Flat copy synced + +**Validation:** +```bash +bun run typecheck && bun run lint +``` + +**depends-on:** none + +--- + +### Group 4: Update team-lead prompt + +**Goal:** Simplify team-lead to use `genie work `. + +**Deliverables:** +1. Update `plugins/genie/agents/team-lead/AGENTS.md` Phase 2: + - Primary command: `genie work ` — handles all wave orchestration automatically + - Fallback: `genie work #` for manual dispatch if auto fails + - `genie spawn` as escape hatch for custom agents +2. Sync flat copy `plugins/genie/agents/team-lead.md` + +**Acceptance Criteria:** +- [ ] Phase 2 says `genie work ` as the primary command +- [ ] Manual dispatch documented as fallback +- [ ] Flat copy synced + +**Validation:** +```bash +diff plugins/genie/agents/team-lead/AGENTS.md plugins/genie/agents/team-lead.md +bun run typecheck && bun run lint +``` + +**depends-on:** Group 2 + +--- + +### Group 5: Validate + +**Goal:** Full CI pass. + +**Validation:** +```bash +bun run check +bun run build +``` + +**depends-on:** Group 1, Group 2, Group 3, Group 4 + +--- + +## Execution Strategy + +### Wave 1 (parallel) +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | engineer | Parse Execution Strategy | +| 3 | engineer | Engineer reports completion via genie done | + +### Wave 2 (after Wave 1) +| Group | Agent | Description | +|-------|-------|-------------| +| 2 | engineer | Auto-orchestration loop | + +### Wave 3 (after Wave 2) +| Group | Agent | Description | +|-------|-------|-------------| +| 4 | engineer | Team-lead prompt update | + +### Wave 4 (after Wave 3) +| Group | Agent | Description | +|-------|-------|-------------| +| 5 | reviewer | Full validation | + +--- + +## Assumptions / Risks + +| Risk | Severity | Mitigation | +|------|----------|------------| +| Workers don't call `genie done` and state never updates | Medium | 30min timeout exits with error. Team-lead can investigate. | +| Git conflicts between parallel workers in same worktree | Medium | Wish groups should be scoped to different files. If conflict happens, team-lead resolves. | +| Command parsing ambiguity between `work ` and `work ` | Low | Detect by: 1 arg = auto mode, 2 args with `#` = manual mode. | + +--- + +## QA Criteria + +- [ ] `genie work auto-orchestrate` spawns parallel agents per wave +- [ ] Waves advance correctly — Wave 2 only after Wave 1 complete +- [ ] Team-lead uses `genie work ` and it works end-to-end +- [ ] Manual `genie work engineer slug#1` still works + +--- + +## Review Results + +_Populated by `/review` after execution completes._ + +--- + +## Files to Create/Modify + +``` +src/term-commands/dispatch.ts — parseExecutionStrategy(), autoOrchestrateCommand(), command registration, initialPrompt update +plugins/genie/agents/engineer/AGENTS.md + engineer.md — add genie done completion reporting +plugins/genie/agents/team-lead/AGENTS.md + team-lead.md — simplified Phase 2 +``` diff --git a/.genie/wishes/daily-metrics-agent/WISH.md b/.genie/wishes/daily-metrics-agent/WISH.md new file mode 100644 index 000000000..4a927605b --- /dev/null +++ b/.genie/wishes/daily-metrics-agent/WISH.md @@ -0,0 +1,360 @@ +# Wish: Daily Metrics Agent — Self-Improving Autonomous Loop + +| Field | Value | +|-------|-------| +| **Status** | APPROVED | +| **Slug** | `daily-metrics-agent` | +| **Date** | 2026-03-24 | + +## Summary + +Deploy a self-evolving persistent agent that runs daily on a single cron job. Its sole purpose: update README.md with live metrics (releases/day, avg bug-fix time, SHIP rate). After each run, it uses /refine to improve its own prompt based on performance data. Generates tools to optimize its execution. Faster, smarter, more efficient every day. + +## Problem + +Manual metrics updates don't scale. Static data rots. README becomes stale. + +But if the agent _itself_ improves, compounds, and auto-generates tools to make metrics updates faster—the README becomes truly self-healing. The agent becomes the product. + +## Solution + +1. **Agent persistence** — Stores refined prompt + generated tools in `.genie/agents/metrics-updater/` +2. **Single cron trigger** — `0 9 * * *` fires the agent daily +3. **Self-improvement loop:** + - Agent runs: fetch metrics, update README, commit + - Agent analyzes: run duration, API calls, errors, missed optimizations + - Agent calls `/refine`: "Here's my performance data. Make me faster." + - Agent stores: refined prompt + new tools for tomorrow +4. **Tool generation** — Agent creates helpers: + - GitHub API wrapper (caches results, retries on failure) + - Metrics parser (extracts numbers reliably) + - Commit formatter (clean, consistent messages) + - Performance logger (tracks execution time, bottlenecks) + +## Scope + +### IN +- Deploy self-improving metrics agent to `.genie/agents/metrics-updater/` +- Agent purpose: daily README metrics update (releases/day, bug-fix time, SHIP rate) +- Persistence: agent stores refined prompt + tools in shared state +- Cron trigger: `0 9 * * *` (daily, 9am BRT) +- Self-refinement: `/refine` called after each run with performance data +- Tool generation: agent creates helpers to optimize next run +- Error handling: graceful failures, fallback to yesterday's metrics +- Logging: capture execution time, API calls, tool generation in `.genie/agents/metrics-updater/runs.jsonl` + +### OUT +- Real-time metrics dashboard (static README updates only, not live dashboard) +- Slack/Discord notifications on failures +- Manual intervention required to update metrics +- Agent runs more than once per day +- Persistent learning across multiple agents (single-purpose agent only) + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| Single cron, not multiple runs | Bounded, predictable behavior. One invocation = one improvement cycle. | +| `/refine` after each run | Agent learns from its own performance data, not external feedback. | +| Tool generation inside agent | Agent decides what helpers it needs, not predefined by us. | +| Shared state persistence | Refined prompt + tools survive agent restarts, available for next cron trigger. | +| Graceful fallback | If GitHub API fails, keep yesterday's metrics. Better stale data than no data. | +| Performance logging | Agent tracks its own speed; /refine uses this to optimize. | + +## Success Criteria + +- [ ] Agent deployed to `.genie/agents/metrics-updater/` with valid frontmatter +- [ ] Daily cron trigger (`0 9 * * *`) fires the agent +- [ ] Agent successfully fetches: releases (24h), avg issue-to-ship time, SHIP rate, parallel agents active +- [ ] Agent updates README.md metrics table with live numbers +- [ ] Agent commits with clean message: `chore: update live metrics (27/day, 2.4h avg, 100% SHIP)` +- [ ] Agent calls `/refine` after each run with performance data +- [ ] Refined prompt stored in `.genie/agents/metrics-updater/AGENT.md` +- [ ] Generated tools stored in `.genie/agents/metrics-updater/tools/` (shell scripts, Python helpers) +- [ ] Run log captured in `.genie/agents/metrics-updater/runs.jsonl` with: timestamp, duration, tools_generated, api_calls, errors +- [ ] Day 7: agent is measurably faster (compare Day 1 vs Day 7 execution time) +- [ ] No manual intervention required for 7 consecutive days +- [ ] Graceful fallback: if API fails on Day X, metrics table shows Day X-1 values + +## Execution Strategy + +### Wave 1 (parallel with genie-hacks) +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | engineer | Deploy metrics agent scaffold + cron trigger + first run | +| 2 | engineer | Implement GitHub API fetching + README parsing + metric extraction | + +### Wave 2 (after Wave 1) +| Group | Agent | Description | +|-------|-------|-------------| +| 3 | engineer | Integrate `/refine` workflow + performance logging + tool generation | +| 4 | reviewer | Verify agent runs cleanly, metrics are accurate, refinement loop works | + +## Execution Groups + +### Group 1: Agent Scaffold + Cron Setup + +**Goal:** Deploy self-improving agent to persistent storage. Set up daily trigger. + +**Deliverables:** +1. Create `.genie/agents/metrics-updater/` directory structure: + ``` + metrics-updater/ + ├── AGENT.md (agent prompt, evolves daily) + ├── runs.jsonl (execution log: timestamp, duration, tools, errors) + ├── tools/ (auto-generated helpers) + │ ├── github-api.sh + │ ├── parse-metrics.py + │ └── commit-formatter.sh + └── state.json (shared state: last_metrics, last_refined_at) + ``` + +2. Initial AGENT.md prompt: + - Purpose: "Update README.md metrics daily" + - Inputs: GitHub repo (automagik-dev/genie), metrics to fetch + - Outputs: Updated README.md + clean commit + - Constraints: Must call /refine after run with perf data + +3. Cron trigger setup: + - Schedule: `0 9 * * *` (9am daily) + - Command: `genie spawn metrics-updater --agent-path .genie/agents/metrics-updater/AGENT.md` + - Logging: output to `.genie/agents/metrics-updater/runs.jsonl` + +4. First manual run to verify scaffold works + +**Acceptance Criteria:** +- [ ] Directory structure created +- [ ] AGENT.md written with clear purpose + constraints +- [ ] Cron job registered and testable +- [ ] First run completes without errors +- [ ] Run log captures execution (timestamp, duration) + +**Validation:** +```bash +# Test cron trigger manually +genie spawn metrics-updater --agent-path .genie/agents/metrics-updater/AGENT.md + +# Verify output +cat .genie/agents/metrics-updater/runs.jsonl | tail -1 +``` + +**depends-on:** none + +--- + +### Group 2: Metrics Fetching + README Updates + +**Goal:** Implement GitHub API calls + metrics extraction + README table updates. + +**Deliverables:** +1. GitHub API fetcher: + - Fetch releases in last 24h: count + average per day + - Fetch PRs: average time from open → merge (bug-fix time) + - Fetch issues: % closed by agent vs manual + - Fetch team status: estimate parallel agents active (from genie status output) + +2. Metrics extractor: + - Parse API responses + - Calculate: releases/day, avg bug-fix time, SHIP rate %, parallel agents + - Handle edge cases (no data, API failures) + +3. README updater: + - Find metrics table in README.md + - Update 4 cells: releases/day, bug-fix time, SHIP rate, parallel agents + - Preserve markdown formatting + +4. Commit + push: + - Message: `chore: update live metrics (X/day, Yh avg, Z% SHIP)` + - Push to main branch (auto-approved, no review gate) + +**Acceptance Criteria:** +- [ ] GitHub API calls work reliably +- [ ] Metrics extracted correctly (verified against manual API check) +- [ ] README table updates with correct numbers +- [ ] Commit message is clean and informative +- [ ] Push succeeds without conflicts +- [ ] Graceful fallback: if API fails, use yesterday's metrics (read from state.json) + +**Validation:** +```bash +# Verify metrics match GitHub API manually +gh api repos/automagik-dev/genie/releases --jq 'length' + +# Run agent +genie spawn metrics-updater --agent-path .genie/agents/metrics-updater/AGENT.md + +# Check README was updated +git diff HEAD~1 README.md | grep -A5 "metrics" +``` + +**depends-on:** 1 + +--- + +### Group 3: Self-Refinement Loop + Tool Generation + +**Goal:** Agent improves itself daily using /refine + generates tools to optimize future runs. + +**Deliverables:** +1. Performance data collection: + - Capture: execution start time, API call count, API latency, parsing duration, commit time + - Identify: slowest step, most retried API call, parsing errors + - Store in: runs.jsonl as structured JSON + +2. /refine integration: + - After each run, call `/refine` skill with: + - Current prompt (AGENT.md) + - Performance data (run duration, bottlenecks) + - Request: "Make me faster. Generate tools for slow steps." + - Read refined prompt from `/tmp/prompts//` (refine output location) + - Store refined prompt to AGENT.md + +3. Tool generation: + - Agent identifies slow operations (API calls, parsing) + - Creates shell/Python helpers in `tools/` directory: + - GitHub API wrapper with caching (avoid redundant calls) + - Metrics parser optimized for common patterns + - Batch commit formatter (handle edge cases seen in past runs) + - Tools persist across runs (next day's agent uses yesterday's tools) + +4. Tool usage: + - Next run: agent sources tools/ helpers before executing + - Faster execution because parsing is optimized, API calls are cached + +**Acceptance Criteria:** +- [ ] Performance metrics captured in runs.jsonl (timestamp, duration, API calls, errors) +- [ ] `/refine` called after each run with performance context +- [ ] Refined prompt stored to AGENT.md +- [ ] At least 1 tool generated by Day 2 (e.g., cached GitHub API wrapper) +- [ ] Day 7 execution time < Day 1 execution time (measurable improvement) +- [ ] Tools are idempotent (safe to run multiple times) +- [ ] No errors from tool execution + +**Validation:** +```bash +# Check performance data is logged +cat .genie/agents/metrics-updater/runs.jsonl | jq '.[] | {timestamp, duration_ms, tools_generated}' + +# Verify tools exist +ls .genie/agents/metrics-updater/tools/ + +# Compare execution times (Day 1 vs Day 7) +cat runs.jsonl | jq 'select(.timestamp | startswith("2026-03-25")) | .duration_ms' +cat runs.jsonl | jq 'select(.timestamp | startswith("2026-03-31")) | .duration_ms' +``` + +**depends-on:** 2 + +--- + +### Group 4: Review + Stability Check + +**Goal:** Verify agent runs cleanly, improves over time, handles edge cases. + +**Deliverables:** +1. Test coverage: + - [ ] Agent runs successfully on Day 1, Day 2, ..., Day 7 + - [ ] Metrics in README match GitHub API (manual verification on Day 3, Day 7) + - [ ] Commits are clean (no merge conflicts, no noise) + - [ ] No false positives: if API fails, metrics don't change unexpectedly + - [ ] Run logs are complete (no missing data fields) + +2. Improvement metrics: + - [ ] Compare Day 1 execution time vs Day 7 + - [ ] Verify tools were generated and used + - [ ] Check refined prompts evolved (AGENT.md changed) + +3. Rollback plan: + - [ ] If agent fails, fallback: cron disabled, previous metrics frozen + - [ ] Escalation: alert on 2 consecutive failures + +**Acceptance Criteria:** +- [ ] Agent runs 7 consecutive days without user intervention +- [ ] Each day's commit is valid and pushes cleanly +- [ ] Metrics are accurate (spot-check vs GitHub API) +- [ ] Execution time decreased by at least 20% from Day 1 to Day 7 +- [ ] Tools generated and persisted +- [ ] Refined prompts stored and used + +**Validation:** +```bash +# Run 7-day test +for day in {1..7}; do + echo "Day $day..." + genie spawn metrics-updater --agent-path .genie/agents/metrics-updater/AGENT.md + sleep 60 # wait for cron-like interval +done + +# Verify all commits succeeded +git log --oneline | grep "chore: update live metrics" | wc -l # should be 7 + +# Compare performance +cat .genie/agents/metrics-updater/runs.jsonl | jq '[.[] | .duration_ms] | [first, last, ((last / first) * 100 | round)]' +``` + +**depends-on:** 3 + +--- + +## Files to Create/Modify + +``` +.genie/agents/metrics-updater/ +├── AGENT.md (agent prompt, evolves daily) +├── runs.jsonl (execution log) +├── state.json (last_metrics, last_refined_at) +└── tools/ (auto-generated helpers) + ├── github-api.sh + ├── parse-metrics.py + └── commit-formatter.sh + +crontab (or Genie scheduler config) +├── Schedule: 0 9 * * * +├── Command: genie spawn metrics-updater +└── Logging: → .genie/agents/metrics-updater/runs.jsonl +``` + +--- + +## Architecture Notes + +### State Persistence +- Agent stores state in `.genie/agents/metrics-updater/state.json` +- Includes: last_metrics (for fallback), last_refined_at (timestamp) +- On next run, agent reads state to detect changes, decide if refinement is needed + +### Refinement Trigger +- `/refine` is called unconditionally after every run (daily improvement) +- Refined prompt persists to AGENT.md +- Next day's agent starts with yesterday's refined prompt + +### Tool Generation Strategy +- Agent analyzes performance data: "API calls took 3.2s total" +- Decides: "I should cache GitHub API responses" +- Creates: `tools/github-api.sh` with built-in caching +- Next run: sources tools before executing main logic +- Result: Day 2 is faster than Day 1 + +### Graceful Degradation +- If GitHub API is down, agent reads state.json +- Uses last_metrics from yesterday +- Logs error to runs.jsonl +- Commits nothing (no changes to README) +- Next day: try again + +--- + +## Open Questions + +1. **Refinement frequency:** /refine after every run, or after N runs? (Proposed: every run for constant improvement) +2. **Tool retention:** Do tools accumulate forever, or prune old ones? (Proposed: keep all, agent decides usage) +3. **Metric sources:** Should agent also track Genie metrics (wishes/day, fix loops)? (Proposed: GitHub metrics only, Genie metrics in separate agent later) +4. **Cron provider:** Use native cron, or Genie's built-in scheduler? (Proposed: Genie scheduler for consistency) + +--- + +## Success Story + +> A single agent. One cron job. Runs every morning at 9am. After 3 days, it's generated 5 helpers to optimize metrics fetching. After a week, it runs in 30 seconds (down from 2 minutes). README always shows today's velocity. No human touch. The agent improved itself. +> +> That's the product. Not orchestrating agents. Self-improving agents. diff --git a/.genie/wishes/docs-overhaul/WISH.md b/.genie/wishes/docs-overhaul/WISH.md new file mode 100644 index 000000000..464500434 --- /dev/null +++ b/.genie/wishes/docs-overhaul/WISH.md @@ -0,0 +1,238 @@ +# Wish: Documentation & Prompt Overhaul + +**Status:** DRAFT +**Slug:** `docs-overhaul` +**Created:** 2026-03-16 + +--- + +## Summary + +Three documentation surfaces are stale after the v3.260316 release cycle. README.md is missing the team-lead architecture, `--wish` flag, agent directory, and worktree changes. Orchestration rules don't cover the full lifecycle. Agent prompts need the prompt-optimizer treatment for clarity and precision. Fix all three. + +--- + +## Scope + +### IN +- **README.md** — fix outdated CLI reference, add team-lead flow, `--wish`, agent directory, worktree path change, `@next` pipeline. Keep marketing structure and tone. +- **Orchestration rules** (`plugins/genie/rules/genie-orchestration.md`) — add team-lead lifecycle, `--wish`, agent directory, full command set +- **PM prompt** (`plugins/genie/agents/pm/`) — switch to `promptMode: system` with XML behavioral blocks (same treatment as team-lead) +- **Worker prompts** (engineer, reviewer, qa, fix, refactor, trace, docs, learn) — stay `promptMode: append`, refine with XML blocks via prompt-optimizer +- **Council prompts** (council parent + 10 members) — stay `promptMode: append`, refine with XML blocks, keep persona inspirations + +### OUT +- Changing agent behavior or capabilities (only prompt clarity/structure) +- Adding new features or CLI commands +- Changing team-lead prompt (already done in #616) +- Changing promptMode to system for workers or council (only PM and team-lead get system) + +--- + +## Decisions + +- **DEC-1:** Keep README marketing structure (features grid, comparison table, pipeline diagram). Only fix what's wrong and add what's new. +- **DEC-2:** PM and team-lead get `promptMode: system`. All other agents stay `append`. +- **DEC-3:** Every prompt rewrite MUST go through the actual prompt-optimizer reference at `references/prompt-optimizer.md`. No shortcuts — load it, classify the prompt type, apply the patterns, validate against the quality checklist. +- **DEC-4:** Council persona inspirations are kept — they're perspective framing, not role prompting. +- **DEC-5:** Append-mode agents do NOT need tool usage sections (CC defaults provide that). + +--- + +## Success Criteria + +- [ ] README.md reflects current CLI commands, team-lead flow, `--wish`, agent directory, worktree paths +- [ ] Orchestration rules cover full team-lead lifecycle and `--wish` usage +- [ ] PM prompt uses `promptMode: system` with XML blocks +- [ ] All 8 worker prompts refined with XML blocks via prompt-optimizer +- [ ] All 11 council prompts refined with XML blocks via prompt-optimizer, personas preserved +- [ ] `bun run check` passes +- [ ] Both flat `.md` and folder `AGENTS.md` copies are in sync for every agent + +--- + +## Assumptions + +- **ASM-1:** The prompt-optimizer reference at `references/prompt-optimizer.md` is the authoritative guide for all prompt rewrites. + +## Risks + +- **RISK-1:** 21 prompts changed at once — if an agent test breaks, it blocks everything. Mitigation: run tests after each group. +- **RISK-2:** System mode for PM might break PM agents in user deployments. Mitigation: PM gets tool usage section since CC defaults are gone. + +--- + +## Execution Groups + +### Group 1: README + Orchestration Rules + +**Goal:** Fix outdated README and orchestration rules to reflect current state. + +**Deliverables:** +1. Update `README.md`: + - Fix CLI reference table (add `genie team create --wish`, `genie team done/blocked`, `genie dir` commands) + - Add team-lead autonomous flow to Quick Start or Features + - Update worktree path info (now `~/.genie/worktrees//`) + - Update agent directory section + - Fix Hook Presets section or remove if stale + - Keep marketing tone, features grid, comparison table +2. Update `plugins/genie/rules/genie-orchestration.md`: + - Add team-lead lifecycle explanation + - Add `genie team create --wish ` as the primary orchestration command + - Add agent directory commands + - Document full command set + +**Acceptance Criteria:** +- [ ] README mentions `genie team create --wish` +- [ ] README CLI reference matches actual `genie --help` output +- [ ] Orchestration rules explain the team-lead lifecycle +- [ ] No references to removed or renamed commands + +**Validation:** +```bash +bun run typecheck && bun run lint +``` + +**depends-on:** none + +--- + +### Group 2: PM Prompt (system mode) + +**Goal:** Refine PM prompt to system mode with XML behavioral blocks. + +**Deliverables:** +1. Load `references/prompt-optimizer.md` — follow it strictly +2. Rewrite `plugins/genie/agents/pm/AGENTS.md` — classify as Workflow type, apply XML blocks, set `promptMode: system`, include tool usage section +3. Rewrite `plugins/genie/agents/pm/SOUL.md` and `plugins/genie/agents/pm/HEARTBEAT.md` if they exist — inline into AGENTS.md or keep separate if referenced +4. Sync `plugins/genie/agents/pm.md` (flat copy) +5. Update any tests that assert PM promptMode + +**Acceptance Criteria:** +- [ ] PM AGENTS.md has `promptMode: system` +- [ ] PM prompt uses XML-tagged behavioral blocks +- [ ] No role prompting ("You are a PM") +- [ ] Tool usage section included +- [ ] Flat copy synced + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 3: Worker Agent Prompts (append mode, refined) + +**Goal:** Refine 8 worker agent prompts with XML blocks via prompt-optimizer. + +**Agents:** engineer, reviewer, qa, fix, refactor, trace, docs, learn + +**Deliverables:** +1. Load `references/prompt-optimizer.md` — follow it strictly for each prompt +2. For each agent: rewrite AGENTS.md with XML blocks, keep `promptMode: append` (or add it explicitly) +3. Classify each prompt type (Task for engineer/fix, Evaluator for reviewer/qa, etc.) +4. Sync flat `.md` copies for each +5. Do NOT add tool usage sections (append mode inherits CC defaults) + +**Acceptance Criteria:** +- [ ] All 8 worker AGENTS.md files use XML-tagged blocks +- [ ] No role prompting +- [ ] All flat copies synced +- [ ] `promptMode: append` explicit on all + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 4: Council Agent Prompts (append mode, refined) + +**Goal:** Refine 11 council agent prompts with XML blocks via prompt-optimizer. + +**Agents:** council (parent), council--architect, council--benchmarker, council--deployer, council--ergonomist, council--measurer, council--operator, council--questioner, council--sentinel, council--simplifier, council--tracer + +**Deliverables:** +1. Load `references/prompt-optimizer.md` — follow it strictly for each prompt +2. For each agent: rewrite AGENTS.md with XML blocks, keep `promptMode: append` +3. Classify as Evaluator type (they critique and assess) +4. Keep persona inspirations (Linus Torvalds, Troy Hunt, etc.) as perspective framing, NOT role prompting +5. Sync flat `.md` copies for each + +**Acceptance Criteria:** +- [ ] All 11 council AGENTS.md files use XML-tagged blocks +- [ ] Persona inspirations preserved but not as "You are X" +- [ ] All flat copies synced +- [ ] `promptMode: append` explicit on all + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 5: Final Validation + +**Goal:** Full CI pass after all changes. + +**Validation:** +```bash +bun run check +bun run build +``` + +**depends-on:** Group 1, Group 2, Group 3, Group 4 + +--- + +## QA Criteria + +- [ ] README Quick Start works for a new user +- [ ] CLI reference table matches `genie --help` output +- [ ] All agent prompts have XML blocks +- [ ] PM and team-lead have `promptMode: system`, all others `append` +- [ ] `bun run check` passes with zero errors + +--- + +## Review Results + +_Populated by `/review` after execution completes._ + +--- + +## Files to Create/Modify + +``` +README.md +plugins/genie/rules/genie-orchestration.md +plugins/genie/agents/pm/AGENTS.md + pm.md + SOUL.md + HEARTBEAT.md +plugins/genie/agents/engineer/AGENTS.md + engineer.md +plugins/genie/agents/reviewer/AGENTS.md + reviewer.md +plugins/genie/agents/qa/AGENTS.md + qa.md +plugins/genie/agents/fix/AGENTS.md + fix.md +plugins/genie/agents/refactor/AGENTS.md + refactor.md +plugins/genie/agents/trace/AGENTS.md + trace.md +plugins/genie/agents/docs/AGENTS.md + docs.md +plugins/genie/agents/learn/AGENTS.md + learn.md +plugins/genie/agents/council/AGENTS.md + council.md +plugins/genie/agents/council--architect/AGENTS.md + council--architect.md +plugins/genie/agents/council--benchmarker/AGENTS.md + council--benchmarker.md +plugins/genie/agents/council--deployer/AGENTS.md + council--deployer.md +plugins/genie/agents/council--ergonomist/AGENTS.md + council--ergonomist.md +plugins/genie/agents/council--measurer/AGENTS.md + council--measurer.md +plugins/genie/agents/council--operator/AGENTS.md + council--operator.md +plugins/genie/agents/council--questioner/AGENTS.md + council--questioner.md +plugins/genie/agents/council--sentinel/AGENTS.md + council--sentinel.md +plugins/genie/agents/council--simplifier/AGENTS.md + council--simplifier.md +plugins/genie/agents/council--tracer/AGENTS.md + council--tracer.md +``` diff --git a/.genie/wishes/docs-readme-review/AUDIT-REPORT.md b/.genie/wishes/docs-readme-review/AUDIT-REPORT.md new file mode 100644 index 000000000..36d4c8a0f --- /dev/null +++ b/.genie/wishes/docs-readme-review/AUDIT-REPORT.md @@ -0,0 +1,227 @@ +# Audit Report: Genie Docs vs CLI Reality + +**Date:** 2026-03-25 +**CLI Version:** v4.260326.1 +**Total CLI commands:** 46 top-level commands +**README lines:** 75 +**Docs pages:** 40+ (Mintlify) + +--- + +## 1. README Gaps + +The README is a 75-line landing page. Per the wish, it should stay concise but must accurately reflect v4 features. + +| Feature | README mentions? | Reality | Action | +|---------|-----------------|---------|--------| +| Brainstorm/Wish/Work/Review pipeline | Yes (workflow diagram) | Correct | None | +| Parallel execution in worktrees | Yes | Correct | None | +| Automated severity-gated review | Yes | Correct | None | +| Overnight mode (/dream) | Yes | Correct | None | +| 10-critic council | Yes | Correct | None | +| Portable context (identity, memory) | Yes | Correct | None | +| **Boards & pipelines** | **No** | Shipped: `genie board` with templates, columns, gates | **Add to feature list** | +| **Projects** | **No** | Shipped: `genie project` with named task boards | **Add to feature list** | +| **Task lifecycle** | **No** | Shipped: `genie task` with 14 subcommands, stages, deps | **Add to feature list** | +| **Observability** | **No** | Shipped: `events`, `metrics`, `sessions`, `log` | **Add to feature list** | +| **Scheduling & daemon** | **No** | Shipped: `genie schedule` + `genie daemon` | **Add to feature list** | +| **QA system** | **No** | Shipped: `genie qa` with run/status/history | **Add to feature list** | +| **Export/Import** | **No** | Shipped: `genie export` (10 groups) + `genie import` | **Add to feature list** | +| **PG-backed messaging** | **No** | Shipped: `send`, `broadcast`, `inbox`, `chat` | **Add to feature list** | +| **Database management** | **No** | Shipped: `genie db` (status/migrate/query) | **Mention in architecture** | +| **Notifications** | **No** | Shipped: `genie notify` | **Minor — skip or mention** | +| **Tags, Types, Releases** | **No** | Shipped: `genie tag`, `genie type`, `genie release` | **Mention under task mgmt** | +| Command count / CLI scope | No (only shows 4 skills) | 46 commands, 100+ subcommands | **Add brief command overview** | +| Version / metrics freshness | Metrics table present | Metrics are auto-updated | None | +| Install command | `curl | bash` + `/wizard` | Correct and current | None | +| Docs link | Points to `docs.automagik.dev/genie` | Correct | None | + +**README Summary:** 11 shipped features entirely missing. README reflects the brainstorm-to-PR workflow but none of the infrastructure that shipped in v4 (boards, tasks, observability, scheduling, projects). + +--- + +## 2. Mintlify Docs — CLI Reference Gaps + +### Commands WITH docs coverage: + +| Command | Docs Page | Coverage | Issues | +|---------|-----------|----------|--------| +| `setup` | cli/infrastructure.mdx | Full | None | +| `doctor` | cli/infrastructure.mdx | Full | None | +| `update` | cli/infrastructure.mdx | Full | None | +| `shortcuts` | cli/infrastructure.mdx | Full | None | +| `team` (create/hire/fire/ls/disband/done/blocked) | cli/team.mdx | Full | None | +| `dir` (add/rm/ls/edit) | cli/directory.mdx | Full | None | +| `agent register` | cli/directory.mdx | Full | None | +| `send` | cli/messaging.mdx | Full | None | +| `broadcast` | cli/messaging.mdx | Full | None | +| `inbox` | cli/messaging.mdx | Full | None | +| `chat` | cli/messaging.mdx | Full | None | +| `brainstorm` | cli/dispatch.mdx | Full | None | +| `wish` | cli/dispatch.mdx | Full | None | +| `work` | cli/dispatch.mdx | Full | None | +| `review` | cli/dispatch.mdx | Full | None | +| `done` | cli/dispatch.mdx | Full | None | +| `status` | cli/dispatch.mdx | Full | None | +| `reset` | cli/dispatch.mdx | Full | None | +| `spawn` | cli/agents.mdx | Full | Missing new flags: `--layout`, `--color`, `--plan-mode`, `--permission-mode`, `--extra-args`, `--cwd`, `--session`, `--no-auto-resume` | +| `resume` | cli/agents.mdx | Full | Missing `--all` flag | +| `kill` | cli/agents.mdx | Full | None | +| `stop` | cli/agents.mdx | Full | None | +| `ls` | cli/agents.mdx | Full | Missing `--json` flag | +| `history` | cli/agents.mdx | Partial | Missing flags: `--after`, `--raw`, `--log-file` | +| `read` | cli/agents.mdx | Partial | Missing flags: `--from`, `--to`, `--range`, `--search`, `--grep`, `--follow`, `--all`, `--reverse`, `--json` | +| `answer` | cli/agents.mdx | Full | None | + +### Commands with NO docs page (20 commands): + +| Command | Subcommands | Priority | Proposed Page | +|---------|-------------|----------|---------------| +| **`task`** | create, list, show, move, assign, tag, comment, block, unblock, done, checkout, release, unlock, dep | **CRITICAL** | cli/tasks.mdx | +| **`board`** | create, list, show, edit, delete, columns, use, export, import, template | **CRITICAL** | cli/boards.mdx | +| **`project`** | list, create, show, set-default | **HIGH** | cli/projects.mdx | +| **`events`** | list, errors, costs, tools, timeline, summary | **HIGH** | cli/observability.mdx | +| **`metrics`** | now, history, agents | **HIGH** | cli/observability.mdx | +| **`sessions`** | list, replay, search, ingest | **HIGH** | cli/observability.mdx | +| **`log`** | (unified feed, --follow, --team, --type) | **HIGH** | cli/observability.mdx | +| **`schedule`** | create, list, cancel, retry, history | **MEDIUM** | cli/scheduling.mdx | +| **`daemon`** | install, start, stop, status, logs | **MEDIUM** | cli/scheduling.mdx | +| **`db`** | status, migrate, query | **MEDIUM** | cli/infrastructure.mdx (add section) | +| **`type`** | list, show, create | **MEDIUM** | cli/tasks.mdx (subsection) | +| **`tag`** | list, create | **MEDIUM** | cli/tasks.mdx (subsection) | +| **`release`** | create, list | **MEDIUM** | cli/tasks.mdx (subsection) | +| **`export`** | all, boards, tasks, tags, projects, schedules, agents, apps, comms, config | **MEDIUM** | cli/data.mdx | +| **`import`** | --fail, --merge, --overwrite, --groups | **MEDIUM** | cli/data.mdx | +| **`notify`** | set, list, remove | **LOW** | cli/messaging.mdx (add section) | +| **`qa`** | run, status, history | **LOW** | cli/qa.mdx | +| **`qa-report`** | (internal) | **LOW** | Skip (internal) | +| **`hook dispatch`** | (internal) | **LOW** | config/hooks.mdx (already covers concept) | +| **`uninstall`** | (standalone) | **LOW** | cli/infrastructure.mdx (add) | + +--- + +## 3. Mintlify Docs — Concept Page Gaps + +| Concept | Page exists? | Status | Action | +|---------|-------------|--------|--------| +| Wishes | concepts/wishes.mdx | Current | None | +| Agents | concepts/agents.mdx | Current | None | +| Teams | concepts/teams.mdx | Current | None | +| Skills | concepts/skills.mdx | Current | None | +| BYOA | concepts/byoa.mdx | Current | None | +| **Boards** | **No** | Shipped: boards, templates, columns, gates, WIP limits | **Create concepts/boards.mdx** | +| **Projects** | **No** | Shipped: named task boards, default project | **Create concepts/projects.mdx** | +| **Observability** | **No** | Shipped: events, metrics, sessions, log | **Consider concepts/observability.mdx** | + +--- + +## 4. Mintlify Docs — Architecture Page Gaps + +| Page | Status | Issues | +|------|--------|--------| +| architecture/overview.mdx | Mostly current | Missing boards/projects from subsystem list | +| architecture/postgres.mdx | **STALE** | Documents 3 migrations (001-003). Actual DB has 10 migrations (001-009 + duplicate 007). Missing tables: boards, board_templates, board_columns, board_column_gates, agents, agent_templates, agent_checkpoints, os_config, app_store | +| architecture/messaging.mdx | Current | None | +| architecture/scheduler.mdx | Current | None | +| architecture/state.mdx | Current | None | +| architecture/transcripts.mdx | Current | None | + +--- + +## 5. Existing Docs — Stale Flag/Option Issues + +| Page | Command | Documented | Actual | Action | +|------|---------|-----------|--------|--------| +| cli/agents.mdx | `genie spawn` | `--model`, `--provider` | Also has: `--layout`, `--color`, `--plan-mode`, `--permission-mode`, `--extra-args`, `--cwd`, `--session`, `--no-auto-resume` | Add missing flags | +| cli/agents.mdx | `genie resume` | `` only | Also has: `--all` flag | Add flag | +| cli/agents.mdx | `genie ls` | Basic | Missing `--json` flag | Add flag | +| cli/agents.mdx | `genie history` | `--full`, `--since`, `--last`, `--type`, `--json`, `--ndjson` | Also has: `--after`, `--raw`, `--log-file` | Add flags | +| cli/agents.mdx | `genie read` | `-n, --lines` | Also has: `--from`, `--to`, `--range`, `--search`, `--grep`, `-f/--follow`, `--all`, `-r/--reverse`, `--json` | Major update needed | +| cli/team.mdx | `genie team` | create/hire/fire/ls/disband | Missing: `done`, `blocked` subcommands | Add subcommands | +| cli/infrastructure.mdx | Setup area | setup/doctor/update/shortcuts | Missing: `uninstall`, `db` commands | Add | + +--- + +## 6. docs.json Navigation Gaps + +Current navigation has these groups: +- Getting Started (4 pages) +- Core Concepts (5 pages) +- Skills Reference (14 pages) +- CLI Reference (7 pages) +- Configuration (5 pages) +- Architecture (6 pages) +- Hacks & Tips (1 page) +- Contributing (1 page) + +**Missing from navigation (new pages needed):** + +| Proposed Page | Group | Priority | +|---------------|-------|----------| +| genie/cli/tasks.mdx | CLI Reference | CRITICAL | +| genie/cli/boards.mdx | CLI Reference | CRITICAL | +| genie/cli/projects.mdx | CLI Reference | HIGH | +| genie/cli/observability.mdx | CLI Reference | HIGH | +| genie/cli/scheduling.mdx | CLI Reference | MEDIUM | +| genie/cli/data.mdx | CLI Reference | MEDIUM | +| genie/concepts/boards.mdx | Core Concepts | CRITICAL | +| genie/concepts/projects.mdx | Core Concepts | HIGH | + +--- + +## 7. Cross-Reference: README vs Docs Contradictions + +| Item | README says | Docs say | Reality | Issue | +|------|------------|----------|---------|-------| +| Feature scope | 6 features listed | 8 capabilities on features.mdx | 46 commands, dozens of features | README undersells; docs are closer but still incomplete | +| Install flow | `curl \| bash` → `genie` → `/wizard` | installation.mdx: `curl \| bash` → `genie setup` → `genie doctor` | Both work; wizard is the newer DX | Minor inconsistency | +| "Wishes in, PRs out" | Tagline | Docs explain full pipeline | Accurate | None | + +No major contradictions between README and docs — the issue is omission, not conflict. + +--- + +## 8. Summary Statistics + +| Metric | Count | +|--------|-------| +| Total CLI commands | 46 | +| Commands with full docs | 26 (57%) | +| Commands with partial docs | 4 (9%) | +| Commands with NO docs | 16 (35%) | +| Internal-only commands (skip) | 2 (hook dispatch, qa-report) | +| Concept pages needed | 2 (boards, projects) | +| Architecture pages needing update | 1 (postgres.mdx) | +| CLI pages needing flag updates | 3 (agents.mdx, team.mdx, infrastructure.mdx) | +| New CLI pages needed | 6 | +| New concept pages needed | 2 | +| docs.json entries to add | 8 | +| README features to add | 11 | + +--- + +## 9. Recommended Fix Priority + +### CRITICAL (must fix) +1. **README:** Add boards, projects, task lifecycle, observability to feature list +2. **New page:** `cli/tasks.mdx` — task, type, tag, release commands +3. **New page:** `cli/boards.mdx` — board commands + templates +4. **New page:** `concepts/boards.mdx` — boards concept page +5. **Update:** `architecture/postgres.mdx` — add migrations 004-009 and new tables + +### HIGH (should fix) +6. **New page:** `cli/projects.mdx` — project commands +7. **New page:** `cli/observability.mdx` — events, metrics, sessions, log +8. **New page:** `concepts/projects.mdx` — projects concept page +9. **Update:** `cli/agents.mdx` — add missing spawn/read/history flags +10. **Update:** `docs.json` — add all new pages to navigation + +### MEDIUM (nice to have) +11. **New page:** `cli/scheduling.mdx` — schedule + daemon commands +12. **New page:** `cli/data.mdx` — export/import commands +13. **Update:** `cli/infrastructure.mdx` — add uninstall, db commands +14. **Update:** `cli/team.mdx` — add done/blocked subcommands + +### LOW (can defer) +15. **Update:** `cli/messaging.mdx` — add notify commands +16. **New page:** `cli/qa.mdx` — qa system docs diff --git a/.genie/wishes/docs-readme-review/WISH.md b/.genie/wishes/docs-readme-review/WISH.md new file mode 100644 index 000000000..49dedd1d2 --- /dev/null +++ b/.genie/wishes/docs-readme-review/WISH.md @@ -0,0 +1,175 @@ +# Wish: Docs ↔ README Review — Align Public-Facing Content + +| Field | Value | +|-------|-------| +| **Status** | SHIPPED | +| **Slug** | `docs-readme-review` | +| **Date** | 2026-03-25 | +| **Repos** | `automagik-dev/genie` (README), `automagik-dev/docs` (Mintlify) | + +## Summary + +Audit the genie README and Mintlify docs for consistency, accuracy, and completeness against the current codebase (v4.260325.33). Today's session shipped boards, templates, 15+ new CLI commands, full observability, and DX polish — none of this is reflected in docs. The README is 75 lines. The docs have 40+ pages but may reference old APIs. Produce an audit report with specific gaps, then fix both. + +## Scope + +### IN +1. **Audit README** against current CLI (`genie --help`) — missing commands, outdated examples, missing features (boards, templates, task ID aliasing, observability) +2. **Audit Mintlify docs** against current codebase — outdated command syntax, missing pages (boards, projects, daily-sync), stale architecture descriptions +3. **Cross-reference** — README claims vs docs claims vs actual CLI behavior. Find contradictions. +4. **Fix README** — update to reflect v4 reality: boards, templates, projects, observability, full command list +5. **Fix docs** — update outdated pages, add missing pages for new features +6. **Verify links** — docs.json navigation matches actual files, no broken links + +### OUT +- Omni docs (separate wish — only `omni/index.mdx` exists) +- KhalOS docs (doesn't exist yet) +- Marketing copy / landing page +- Video tutorials + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| **Audit first, fix second** | Need the gap list before touching files. Audit is Group 1, fixes are Groups 2-3. | +| **README stays concise** | README is a landing page, not docs. Quick overview + link to Mintlify. But it must be accurate. | +| **Docs get new pages for boards + projects** | These are shipped features with no docs. Need at minimum: boards concept page, CLI reference updates, project management page. | +| **Two repos, one PR each** | README fix → PR on `automagik-dev/genie`. Docs fix → PR on `automagik-dev/docs`. | + +## Success Criteria + +- [ ] Audit report lists every gap between README, docs, and current CLI +- [ ] README reflects all v4 features (boards, templates, projects, observability, task aliasing) +- [ ] Every `genie` subcommand in `--help` has a corresponding docs page or section +- [ ] No docs page references removed/renamed commands +- [ ] `docs.json` navigation is complete — no missing pages +- [ ] Both PRs pass CI + +## Execution Strategy + +### Wave 1 +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | engineer | Audit: diff README + docs against `genie --help` output, flag every gap | + +### Wave 2 (parallel, after Wave 1) +| Group | Agent | Description | +|-------|-------|-------------| +| 2 | engineer | Fix README in genie repo | +| 3 | engineer | Fix/add Mintlify docs pages in docs repo | + +### Wave 3 +| Group | Agent | Description | +|-------|-------|-------------| +| review | reviewer | Review both PRs | + +## Execution Groups + +### Group 1: Audit +**Goal:** Produce a complete gap analysis between README, docs, and current CLI. + +**Deliverables:** +1. Run `genie --help` and every subcommand `--help` — capture full command tree +2. Compare against README — list missing/outdated commands and features +3. Compare against each Mintlify docs page — list stale syntax, missing pages, broken references +4. Cross-reference: find contradictions between README and docs +5. Output: `AUDIT-REPORT.md` with tables: `| Source | Claim | Reality | Action needed |` + +**Acceptance Criteria:** +- [ ] Every `genie` subcommand is accounted for +- [ ] Every docs page is checked against current behavior +- [ ] Report clearly states what's missing, what's wrong, what's fine + +**Validation:** +```bash +# Verify audit report exists and covers all commands +genie --help | grep -c " [a-z]" # count subcommands +grep -c "| " AUDIT-REPORT.md # verify table rows exist +``` + +**depends-on:** none + +--- + +### Group 2: Fix README +**Goal:** Update genie README to reflect v4 reality. + +**Deliverables:** +1. Update feature list: boards, templates, projects, observability, task lifecycle +2. Update command examples to current syntax +3. Add boards section or mention +4. Update metrics if stale +5. Ensure quickstart works with current install path + +**Acceptance Criteria:** +- [ ] Every major v4 feature mentioned +- [ ] All code examples actually work when copy-pasted +- [ ] Links to Mintlify docs are correct + +**Validation:** +```bash +# Verify no broken links +grep -oP 'https?://[^\s\)]+' README.md | head -20 +``` + +**depends-on:** Group 1 + +--- + +### Group 3: Fix Mintlify Docs +**Goal:** Update and expand docs to match current codebase. + +**Deliverables:** +1. Update CLI reference pages with new commands (board, project, events, metrics, sessions) +2. Add new concept page: `genie/concepts/boards.mdx` — boards, templates, columns, gates +3. Add new concept page: `genie/concepts/projects.mdx` — project-scoped task management +4. Update `genie/architecture/postgres.mdx` with new tables (boards, board_templates, agents, agent_templates) +5. Update `docs.json` navigation to include new pages +6. Fix any stale command syntax flagged in the audit + +**Acceptance Criteria:** +- [ ] New pages for boards and projects exist +- [ ] CLI reference covers all current subcommands +- [ ] `docs.json` navigation includes all pages +- [ ] No page references removed commands + +**Validation:** +```bash +# Verify all pages in docs.json exist as files +cat docs.json | jq -r '.. | .pages? // empty | .[]' | while read p; do test -f "${p}.mdx" || echo "MISSING: $p"; done +``` + +**depends-on:** Group 1 + +--- + +## QA Criteria + +- [ ] `genie --help` output matches README feature claims +- [ ] Every CLI reference page in docs matches actual `--help` output +- [ ] No 404s in Mintlify navigation +- [ ] README quickstart actually works on a fresh install + +## Assumptions / Risks + +| Risk | Severity | Mitigation | +|------|----------|------------| +| Docs repo has different branch conventions | Low | Check default branch before PR. Likely `main`. | +| Mintlify build breaks on new pages | Medium | Validate `docs.json` structure matches Mintlify schema. | +| README changes conflict with other PRs | Low | Small, focused changes. Rebase before merge. | + +## Files to Create/Modify + +``` +# genie repo +README.md — update features, examples, commands + +# docs repo +genie/concepts/boards.mdx — NEW: boards + templates concept page +genie/concepts/projects.mdx — NEW: project management +genie/cli/board.mdx — NEW: board CLI reference +genie/cli/project.mdx — NEW: project CLI reference +genie/architecture/postgres.mdx — UPDATE: new tables +genie/cli/*.mdx — UPDATE: stale command syntax +docs.json — UPDATE: add new pages to navigation +``` diff --git a/.genie/wishes/dx-800-closeout/WISH.md b/.genie/wishes/dx-800-closeout/WISH.md new file mode 100644 index 000000000..9cee7b59b --- /dev/null +++ b/.genie/wishes/dx-800-closeout/WISH.md @@ -0,0 +1,160 @@ +# Wish: Close out #800 — fix remaining DX bugs to 100% + +| Field | Value | +|-------|-------| +| **Status** | SHIPPED | +| **Slug** | `dx-800-closeout` | +| **Date** | 2026-03-28 | +| **Issue** | [#800](https://github.com/automagik-dev/genie/issues/800) | + +## Summary + +GitHub issue #800 reported 10 DX findings from an 8-hour power session. After triage, 7/10 are already fixed. This wish closes the remaining 2 bugs + 1 enhancement to get #800 to 100%. + +## Scope + +### IN +- **Bug: agent_templates leak** — `disbandTeam()` doesn't clean `agent_templates` rows. Templates accumulate forever. +- **Bug: task state never auto-advances** — when a wish group completes or a PR merges, tasks stay in `ready`/`in_progress`. PMs must manually move them. +- **Enhancement: `genie spawn --prompt`** — add a direct `--prompt` flag so agents get initial instructions without the `--extra-args` workaround. + +### OUT +- Items 1-3, 5, 7-8, 10 — already fixed in prior PRs +- Task auto-advance on GitHub PR merge (that's #797, separate wish) +- Full template CRUD CLI (that's #833, separate issue) + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| Clean templates on disband only (not prune command) | Simplest fix, prevents growth. A prune command can come later if needed. | +| Auto-advance tasks when wish group completes in `/work` | The orchestrator already knows which group finished — just needs to call `moveTask()`. | +| `--prompt` on `genie spawn` maps to existing `initialPrompt` infra | Already wired in provider-adapters.ts and agents.ts. Just needs CLI option exposure. Zero new plumbing. | + +## Success Criteria + +- [ ] `genie team disband` deletes associated rows from `agent_templates` +- [ ] After `/work` completes a group, tasks in that group move to the next stage +- [ ] `genie spawn engineer --prompt "implement the login page"` starts the agent with that initial prompt +- [ ] All existing tests pass (`bun test` — 1183+) +- [ ] New tests cover template cleanup and spawn --prompt + +## Execution Strategy + +### Wave 1 (parallel) + +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | engineer | Fix template leak on disband | +| 2 | engineer | Add task auto-advance on group completion | +| 3 | engineer | Add `--prompt` flag to spawn | + +### Wave 2 (after Wave 1) + +| Group | Agent | Description | +|-------|-------|-------------| +| review | reviewer | Review all 3 groups | + +## Execution Groups + +### Group 1: Template cleanup on disband + +**Goal:** Delete `agent_templates` rows when a team is disbanded. + +**Deliverables:** +1. Add `DELETE FROM agent_templates WHERE team = $teamName` to `disbandTeam()` in `src/lib/team-manager.ts` +2. Add test in `src/lib/team-manager.test.ts` verifying templates are cleaned + +**Acceptance Criteria:** +- [ ] After `genie team disband`, `SELECT count(*) FROM agent_templates WHERE team = ''` returns 0 +- [ ] Existing disband tests still pass + +**Validation:** +```bash +bun test src/lib/team-manager.test.ts +``` + +**depends-on:** none + +--- + +### Group 2: Task auto-advance on group completion + +**Goal:** When `/work` marks a wish group as complete, move associated tasks to the next stage. + +**Deliverables:** +1. In the wish completion handler (likely `src/lib/wish-state.ts` `completeGroup()`), call `moveTask()` for tasks linked to that group +2. Map group completion → task stage: `build` → `review`, `review` → `qa`, `qa` → `ship` + +**Acceptance Criteria:** +- [ ] After a wish group completes, linked tasks advance one stage +- [ ] Tasks already in `ship` stage are not affected +- [ ] Test verifies stage transition on group completion + +**Validation:** +```bash +bun test src/lib/wish-state.test.ts +``` + +**depends-on:** none + +--- + +### Group 3: Spawn --prompt flag + +**Goal:** Expose the existing `initialPrompt` infrastructure as a `--prompt` CLI option on `genie spawn`. + +**Context:** Claude CLI takes the prompt as a **positional argument** (`claude [options] [prompt]`), NOT a `--prompt` flag. The genie codebase already has full `initialPrompt` support: +- `src/lib/provider-adapters.ts:325` — appends prompt as positional arg via `escapeShellArg()` +- `src/term-commands/agents.ts:832` — `handleWorkerSpawn()` accepts `initialPrompt` option +- `src/term-commands/agents.ts:1283` — resume already uses `initialPrompt` for context injection + +The only missing piece is the CLI option on `genie spawn`. + +**Deliverables:** +1. Add `.option('--prompt ', 'Initial prompt for the agent')` to spawn command in `src/genie.ts` +2. Pass `options.prompt` → `initialPrompt` in the spawn handler +3. Test that the prompt appears as a positional arg in the spawned command + +**Acceptance Criteria:** +- [ ] `genie spawn engineer --prompt "hello"` spawns with `"hello"` as positional prompt arg +- [ ] Without `--prompt`, behavior is unchanged +- [ ] Test verifies prompt passthrough + +**Validation:** +```bash +bun test src/term-commands/dispatch.test.ts +``` + +**depends-on:** none + +--- + +## QA Criteria + +- [ ] `genie team create test-qa --repo . --branch dev` → `genie team disband test-qa` → no orphan templates +- [ ] Task auto-advance fires when wish group completes +- [ ] `genie spawn` help shows `--prompt` option +- [ ] Full test suite passes + +--- + +## Assumptions / Risks + +| Risk | Severity | Mitigation | +|------|----------|------------| +| Task-to-group linkage may not exist in current schema | Medium | Check if tasks store a `group` or `wish_slug` field; if not, Group 2 may need a schema addition | +| Claude CLI prompt is positional, not a flag | None | Already verified — `initialPrompt` infrastructure handles this correctly | + +--- + +## Files to Create/Modify + +``` +src/lib/team-manager.ts — add template cleanup to disbandTeam() +src/lib/team-manager.test.ts — test template cleanup +src/lib/wish-state.ts — add task advance on completeGroup() +src/lib/wish-state.test.ts — test task advance +src/term-commands/dispatch.ts — add --prompt flag +src/term-commands/dispatch.test.ts — test --prompt passthrough +``` diff --git a/.genie/wishes/feature-matrix-page/WISH.md b/.genie/wishes/feature-matrix-page/WISH.md new file mode 100644 index 000000000..cf0f370a0 --- /dev/null +++ b/.genie/wishes/feature-matrix-page/WISH.md @@ -0,0 +1,97 @@ +# Wish: Feature Matrix Page + +| Field | Value | +|-------|-------| +| **Status** | APPROVED | +| **Slug** | `feature-matrix-page` | +| **Date** | 2026-03-24 | + +## Summary + +Create a dedicated "Features" page showcasing Genie's 8 core capabilities in a matrix format with links to detailed docs. Designed for skimmable visual reference and deep-dive links. + +## Scope + +### IN +- New page: `genie/features.mdx` — 8-core-capabilities matrix +- Each capability has: name, what it does, link to docs +- Visual card-based layout (Mintlify CardGroup) +- Add to docs.json navigation + +### OUT +- Comparison charts vs other tools (out of scope) +- Capability deep-dives (link to existing docs instead) +- Performance benchmarks (separate wish) + +## Success Criteria + +- [ ] Page exists with 8 capabilities clearly listed +- [ ] Each capability has: name, 1-line description, link to docs +- [ ] Card/visual layout is clean and skimmable +- [ ] All links are valid (no 404s) +- [ ] Integrated into docs navigation + +## Execution Groups + +### Group 1: Create Features Page + +**Goal:** Write features page with 8 capabilities. + +**Deliverables:** +1. `genie/features.mdx` with: + - Intro: "8 capabilities that make Genie work" + - CardGroup with 8 cards: + 1. Wishes (Structured Intent) + 2. Pipeline (brainstorm → ship) + 3. Team Orchestration + 4. Persistent State + 5. Real-time Coordination + 6. Context Preservation + 7. 14 Skills + 8. Provider Agnosticism (BYOA) + - Each card: title, 1-line description, link to docs + - Footer: "Learn more → Concepts, CLI Reference, Architecture" + +**Acceptance Criteria:** +- [ ] 8 capabilities listed +- [ ] Each has link to relevant doc page +- [ ] No broken links +- [ ] Clean card layout + +**Validation:** +```bash +grep -c "^##\|Card" /tmp/automagik-docs/genie/features.mdx +``` + +**depends-on:** none + +--- + +### Group 2: Integrate into Navigation + +**Goal:** Add features page to docs.json and homepage. + +**Deliverables:** +1. Update docs.json: add features page +2. Update index.mdx: add "Features" card linking to features page + +**Acceptance Criteria:** +- [ ] docs.json valid (no schema errors) +- [ ] Features page accessible from navbar +- [ ] Index page links to features page + +**Validation:** +```bash +jq . /tmp/automagik-docs/docs.json | grep -q features +``` + +**depends-on:** 1 + +## Files to Create/Modify + +``` +/tmp/automagik-docs/genie/features.mdx (create) +/tmp/automagik-docs/docs.json (update navigation) +/tmp/automagik-docs/genie/index.mdx (add card linking to features) +``` + diff --git a/.genie/wishes/fire-and-forget/WISH.md b/.genie/wishes/fire-and-forget/WISH.md new file mode 100644 index 000000000..fda36e99f --- /dev/null +++ b/.genie/wishes/fire-and-forget/WISH.md @@ -0,0 +1,282 @@ +# Wish: Fire-and-Forget `genie work` + Agent Auto-Exit + +| Field | Value | +|-------|-------| +| **Status** | SHIPPED | +| **Slug** | `fire-and-forget` | +| **Date** | 2026-03-20 | +| **Design** | [DESIGN.md](../../brainstorms/work-fire-forget/DESIGN.md) | +| **Blocks** | `genie-scheduler` (scheduler needs non-blocking dispatch) | + +## Summary + +Make `genie work ` non-blocking: spawn agents, print guidance, return the terminal in <5 seconds. Switch prompt delivery from `--initial-prompt` CLI args to `genie send` mailbox. Agents auto-exit their panes after calling `genie done`. Fix tmux session explosion on parallel team creation. + +## Scope + +### IN +- Remove `while(true)` polling loop from `autoOrchestrateCommand` in `src/term-commands/dispatch.ts` +- Replace `initialPrompt` parameter with post-spawn `genie send` mailbox delivery in all dispatch commands +- `genie done #` sends "wave complete" to team-lead when all groups in wave are done +- `genie done` auto-kills the calling agent's tmux pane after notification +- Store `tmuxSessionName` in team config during `genie team create` +- Add `--session` flag to `genie spawn` and `genie team create` +- `getCurrentSessionName()` fallback chain: explicit flag > team config > `tmux list-sessions` > create new +- OTel relay liveness: detect dead pane with in_progress group → reset group to `ready`, notify team-lead + +### OUT +- No changes to manual `genie work # ` dispatch mode +- No changes to wish state machine logic (`wish-state.ts`) +- No new CLI commands (only modifications to existing) +- No database/pgserve dependency (this wish is file-based only) +- No NATS integration +- No scheduler daemon + +## Decisions + +| Decision | Rationale | +|----------|-----------| +| Mailbox via `genie send` not `--initial-prompt` | CLI args visible in process list, fragile. Mailbox is durable, queued to disk | +| Team-lead `/loop` handles wave advancement | Non-deterministic watchdog gives flexibility. Team-lead reviews between waves | +| `genie done` auto-exits pane | Agents leaving orphaned sessions is the #1 resource leak today | +| `genie done` enforces git push before exit | Unpushed work is lost work. Push first, then kill pane | +| Wave detection in `genie done` | After marking group done, check if all same-wave groups are complete. If yes, message team-lead | +| Tmux session stored in team config | Single source of truth. Workers read it instead of relying on TMUX env var | +| Resume context injection on respawn | Agent doesn't need CC history — genie state tells it what to do. Inject "you were working on X" prompt on respawn | +| Genie state is source of truth, not CC memory | `--continue` is nice-to-have. Even a fresh session can resume work from wish state + registry | + +## Success Criteria + +- [ ] `genie work ` spawns Wave 1 agents and exits in <5 seconds +- [ ] Agents receive work prompt via mailbox (visible in `genie inbox `) +- [ ] No `initialPrompt` passed via CLI args in any dispatch command +- [ ] `genie done #` kills calling agent's tmux pane +- [ ] Team-lead receives "Wave N complete" message when last group finishes +- [ ] Parallel `genie team create` (5x) creates windows in same session, not 5 sessions +- [ ] `genie spawn --session ` places agent in specified session +- [ ] Dead pane with in_progress group resets to `ready` and notifies team-lead +- [ ] `genie done` pushes unpushed commits before killing pane +- [ ] Respawned agent receives resume context: wish slug, group, status, instructions +- [ ] Agent can resume work from fresh CC session (no `--continue` required) using injected context +- [ ] Existing test suite passes +- [ ] `bun run check` passes + +## Execution Strategy + +### Wave 1 (parallel) +| Group | Agent | Description | +|-------|-------|-------------| +| 1 | engineer | Fire-and-forget dispatch + mailbox prompts | +| 2 | engineer | Tmux session fix (config storage + fallback chain) | + +### Wave 2 (after Wave 1) +| Group | Agent | Description | +|-------|-------|-------------| +| 3 | engineer | Agent auto-exit + push enforcement + wave notification | +| 4 | engineer | OTel relay liveness check | +| 5 | engineer | Session resume context injection | + +### Wave 3 (after Wave 2) +| Group | Agent | Description | +|-------|-------|-------------| +| review | reviewer | Review all changes against acceptance criteria | + +## Execution Groups + +### Group 1: Fire-and-forget dispatch + mailbox prompts + +**Goal:** Make `genie work ` non-blocking and deliver prompts via native mailbox. + +**Deliverables:** +1. In `src/term-commands/dispatch.ts` → `autoOrchestrateCommand`: + - Remove the `while(true)` polling loop (lines ~377-404) + - Remove `ORCHESTRATE_POLL_MS` and `ORCHESTRATE_TIMEOUT_MS` constants + - After spawning wave groups, print next-step guidance and return + - Print: "Agents dispatched for Wave N. Monitor: `genie status ` | Logs: `genie read `" +2. In `src/term-commands/dispatch.ts` → `workDispatchCommand`: + - Remove `initialPrompt` from `handleWorkerSpawn` call + - After spawn completes, deliver work prompt via `genie send`: + - Task description (group section from WISH.md) + - Return instructions: "When done: run `genie done #` and exit" + - Same pattern for `brainstormCommand`, `wishCommand`, `reviewCommand` + +**Acceptance Criteria:** +- [ ] `genie work ` returns to terminal in <5s +- [ ] Agents receive prompt via mailbox (visible in `genie read `) +- [ ] `grep -n 'initialPrompt' src/term-commands/dispatch.ts` returns zero matches +- [ ] `grep -n 'while.*true' src/term-commands/dispatch.ts` returns zero matches in autoOrchestrate + +**Validation:** +```bash +! grep -n 'ORCHESTRATE_POLL_MS\|ORCHESTRATE_TIMEOUT_MS' src/term-commands/dispatch.ts && echo "Polling removed" +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 2: Tmux session fix + +**Goal:** Prevent tmux session explosion on parallel team creation. + +**Deliverables:** +1. Add `tmuxSessionName` field to `TeamConfig` interface in `src/lib/team-manager.ts` +2. In `spawnLeaderWithWish` (`src/term-commands/team.ts`), resolve current tmux session name BEFORE calling `handleWorkerSpawn` and store it in team config +3. In `resolveSpawnTeamWindow` (`src/term-commands/agents.ts`), read stored `tmuxSessionName` from team config as primary fallback when `getCurrentSessionName()` returns null +4. In `getCurrentSessionName` (`src/lib/tmux.ts`), add fallback: when `process.env.TMUX` is not set, try `tmux list-sessions -F '#{session_name}'` and return first match (or hint match) +5. Add `--session ` option to `genie spawn` and `genie team create` commands +6. Pass session name through `SpawnOptions` → `resolveSpawnTeamWindow` + +**Acceptance Criteria:** +- [ ] Team config JSON includes `tmuxSessionName` after team creation +- [ ] `genie spawn engineer --session mytest` creates pane in "mytest" session +- [ ] `getCurrentSessionName()` returns a session name even without TMUX env (if tmux server running) +- [ ] 5 parallel `genie team create` from non-tmux context → 5 windows in one session + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 3: Agent auto-exit + push enforcement + wave completion notification + +**Goal:** Agents exit cleanly after work. Work is pushed. Team-lead gets wave-complete signal. + +**Deliverables:** +1. In `genie done` command handler (`src/term-commands/state.ts` or equivalent): + - After `completeGroup()`, check if all groups in the same wave are now `done` + - Wave detection: read WISH.md Execution Strategy, find which wave contains this group, check all wave-mates + - If wave complete: `genie send "Wave N complete. All groups done: [list]. Run /review or advance." --to team-lead` +2. **Push enforcement** before pane exit: + - Run `git status --porcelain` — if dirty, `git add -A && git commit -m "wip: #"` + - Run `git log @{u}..HEAD --oneline` — if unpushed commits, `git push` + - Only proceed to pane kill after push succeeds (or no changes) +3. After push + notification, auto-kill the calling agent's pane: + - Detect own pane ID from `TMUX_PANE` env + - `tmux kill-pane -t ` (kills self) + - If not in tmux: just exit process + +**Acceptance Criteria:** +- [ ] `genie done #` sends wave-complete message when last in wave +- [ ] `genie done` pushes unpushed commits before killing pane +- [ ] `genie done` commits dirty working tree as WIP before push +- [ ] Agent's tmux pane is killed after push + notification complete +- [ ] Team-lead inbox shows wave completion message +- [ ] Groups in different waves don't trigger premature wave-complete + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** Group 1 + +--- + +### Group 4: OTel relay liveness check + +**Goal:** Detect crashed agents and reset their groups for retry. + +**Deliverables:** +1. In the OTel relay pane cleanup loop (`src/term-commands/agents.ts`): + - When a dead pane is detected AND the agent was assigned to an `in_progress` group: + - Read wish state to find the group + - Call `resetGroup()` to set it back to `ready` + - Send notification: `genie send "Agent crashed on group X. Group reset to ready." --to team-lead` +2. Add helper: `findGroupByAssignee(slug, workerId)` in wish-state.ts + +**Acceptance Criteria:** +- [ ] Dead pane + in_progress group → group status reset to `ready` +- [ ] Team-lead receives crash notification with group name +- [ ] Non-assigned dead panes don't trigger state changes + +**Validation:** +```bash +bun run typecheck && bun run lint && bun test +``` + +**depends-on:** none + +--- + +### Group 5: Session resume context injection + +**Goal:** When an agent is respawned (fresh or via `--resume`), inject enough context for it to pick up where it left off — even without CC conversation history. + +**Deliverables:** +1. In `protocol-router-spawn.ts` → `spawnWorkerFromTemplate()`: + - After spawn, before delivering any messages, build a **resume context prompt** + - Query wish state: find any `in_progress` group assigned to this worker's role+team + - Query agent registry: get last known state, startedAt + - Build resume prompt: + ``` + RESUME CONTEXT: You were working on wish "", group "". + Status: in_progress. Started at: