From 23beb115fad4e741ef66cabe72286531e862853d Mon Sep 17 00:00:00 2001 From: rootiest Date: Mon, 19 Jan 2026 21:33:54 -0500 Subject: [PATCH] feat: integrate beads bd sync: 2026-01-19 21:41:13 --- .beads/.gitignore | 44 +++++++++ .beads/README.md | 81 +++++++++++++++++ .beads/config.yaml | 62 +++++++++++++ .beads/interactions.jsonl | 0 .beads/issues.jsonl | 4 + .beads/metadata.json | 4 + .gitattributes | 3 + .gitea/sync_beads.py | 154 ++++++++++++++++++++++++++++++++ .gitea/workflows/beads-sync.yml | 27 ++++++ AGENTS.md | 40 +++++++++ 10 files changed, 419 insertions(+) create mode 100644 .beads/.gitignore create mode 100644 .beads/README.md create mode 100644 .beads/config.yaml create mode 100644 .beads/interactions.jsonl create mode 100644 .beads/issues.jsonl create mode 100644 .beads/metadata.json create mode 100644 .gitattributes create mode 100644 .gitea/sync_beads.py create mode 100644 .gitea/workflows/beads-sync.yml create mode 100644 AGENTS.md diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..d27a1db --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,44 @@ +# SQLite databases +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm + +# Daemon runtime files +daemon.lock +daemon.log +daemon.pid +bd.sock +sync-state.json +last-touched + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Legacy database files +db.sqlite +bd.db + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Merge artifacts (temporary files from 3-way merge) +beads.base.jsonl +beads.base.meta.json +beads.left.jsonl +beads.left.meta.json +beads.right.jsonl +beads.right.meta.json + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +sync_base.jsonl + +# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here. +# They would override fork protection in .git/info/exclude, allowing +# contributors to accidentally commit upstream issue databases. +# The JSONL files (issues.jsonl, interactions.jsonl) and config files +# are tracked by git by default since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..50f281f --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --status in_progress +bd update --status done + +# Sync with git remote +bd sync +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Intelligent JSONL merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..f242785 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,62 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: load from JSONL, no SQLite, write back after each command +# When true, bd will use .beads/issues.jsonl as the source of truth +# instead of SQLite database +# no-db: false + +# Disable daemon for RPC communication (forces direct database access) +# no-daemon: false + +# Disable auto-flush of database to JSONL after mutations +# no-auto-flush: false + +# Disable auto-import from JSONL when it's newer than database +# no-auto-import: false + +# Enable JSON output by default +# json: false + +# Default actor for audit trails (overridden by BD_ACTOR or --actor) +# actor: "" + +# Path to database (overridden by BEADS_DB or --db) +# db: "" + +# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) +# auto-start-daemon: true + +# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) +# flush-debounce: "5s" + +# Git branch for beads commits (bd sync will commit to this branch) +# IMPORTANT: Set this for team projects so all clones use the same sync branch. +# This setting persists across clones (unlike database config which is gitignored). +# Can also use BEADS_SYNC_BRANCH env var for local override. +# If not set, bd sync will require you to run 'bd config set sync.branch '. +# sync-branch: "beads-sync" + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct JSONL +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 0000000..641cef6 --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,4 @@ +{"id":"gitea-welcome-0kp","title":"Reverse-sync Test","status":"tombstone","priority":2,"issue_type":"task","owner":"chris@rootiest.com","created_at":"2026-01-19T21:09:52.497127424-05:00","created_by":"rootiest","updated_at":"2026-01-19T21:22:16.221889237-05:00","comments":[{"id":2,"issue_id":"gitea-welcome-0kp","author":"rootiest","text":"It","created_at":"2026-01-20T02:11:02Z"}],"deleted_at":"2026-01-19T21:22:16.221889237-05:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} +{"id":"gitea-welcome-24k","title":"Reverse-sync Test","status":"tombstone","priority":2,"issue_type":"task","owner":"chris@rootiest.com","created_at":"2026-01-19T21:13:44.940389131-05:00","created_by":"rootiest","updated_at":"2026-01-19T21:22:05.359383908-05:00","deleted_at":"2026-01-19T21:22:05.359383908-05:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} +{"id":"gitea-welcome-5a4","title":"Test Issue","status":"tombstone","priority":2,"issue_type":"task","owner":"chris@rootiest.com","created_at":"2026-01-19T19:24:28.988421899-05:00","created_by":"rootiest","updated_at":"2026-01-19T21:41:09.873551106-05:00","close_reason":"Closed","comments":[{"id":1,"issue_id":"gitea-welcome-5a4","author":"rootiest","text":"Completed!","created_at":"2026-01-20T01:07:50Z"},{"id":3,"issue_id":"gitea-welcome-5a4","author":"rootiest","text":"New","created_at":"2026-01-20T02:29:01Z"}],"deleted_at":"2026-01-19T21:41:09.873551106-05:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} +{"id":"gitea-welcome-d6r","title":"Reverse-sync Test #2","status":"tombstone","priority":2,"issue_type":"task","owner":"chris@rootiest.com","created_at":"2026-01-19T21:13:44.746027824-05:00","created_by":"rootiest","updated_at":"2026-01-19T21:22:10.935659182-05:00","deleted_at":"2026-01-19T21:22:10.935659182-05:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..c787975 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,4 @@ +{ + "database": "beads.db", + "jsonl_export": "issues.jsonl" +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..807d598 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/.gitea/sync_beads.py b/.gitea/sync_beads.py new file mode 100644 index 0000000..cf5933c --- /dev/null +++ b/.gitea/sync_beads.py @@ -0,0 +1,154 @@ +import json +import os +import requests +import sys + +# Configuration from Environment +TOKEN = os.getenv("GITEA_TOKEN") +URL = os.getenv("GITEA_URL") +REPO = os.getenv("REPO_NAME") +HEADERS = {"Authorization": f"token {TOKEN}", "Content-Type": "application/json"} + + +def sync(): + beads_path = ".beads/issues.jsonl" + + if not os.path.exists(beads_path): + print(f"❌ ERROR: {beads_path} not found.") + sys.exit(1) + + print(f"🔍 Reading Beads from: {beads_path}") + + # 1. Fetch existing issues from Gitea + try: + api_url = f"{URL}/api/v1/repos/{REPO}/issues?state=all" + resp = requests.get(api_url, headers=HEADERS) + resp.raise_for_status() + # Map by [ID] to identify existing ones + existing = { + i["title"].split("]")[0][1:]: i for i in resp.json() if "]" in i["title"] + } + print(f"📡 Found {len(existing)} existing issues in Gitea.") + except Exception as e: + print(f"❌ Gitea API Connection Failed: {e}") + sys.exit(1) + + # 2. Parse the Beads JSONL file + processed_count = 0 + skipped_count = 0 + deleted_count = 0 + + with open(beads_path, "r") as f: + for line in f: + line = line.strip() + if not line: + continue + + try: + data = json.loads(line) + bid = data.get("id") + status = data.get("status") + title = data.get("title") + itype = data.get("issue_type", "unknown") + + if not bid: + print("⚠️ Skipping line: No ID found.") + continue + + # --- DELETION / TOMBSTONE LOGIC --- + if status == "tombstone": + if bid in existing: + issue = existing[bid] + issue_num = issue["number"] + + # Check if it's already closed/archived in Gitea + if issue["state"] == "closed" and issue["title"].startswith( + "[DELETED]" + ): + print( + f"👻 Skipping tombstone: {bid} (Already archived in Gitea)" + ) + skipped_count += 1 + continue + + print( + f"🔒 Archiving Gitea Issue #{issue_num} (Tombstone found for {bid})" + ) + archive_payload = { + "state": "closed", + "title": f"[DELETED] {title}", + } + + patch_resp = requests.patch( + f"{URL}/api/v1/repos/{REPO}/issues/{issue_num}", + headers=HEADERS, + json=archive_payload, + ) + + # Accept 200 or 201 as success + if patch_resp.status_code in [200, 201]: + print(f"✅ Successfully archived {bid}") + + # Add a comment about the deletion + comment_payload = { + "body": f"⚠️ This issue was deleted in **Beads** (ID: `{bid}`). It has been automatically closed and renamed for archival purposes." + } + comment_url = ( + f"{URL}/api/v1/repos/{REPO}/issues/{issue_num}/comments" + ) + requests.post( + comment_url, headers=HEADERS, json=comment_payload + ) + + deleted_count += 1 + else: + print( + f"⚠️ Could not archive {bid}: {patch_resp.status_code}" + ) + else: + print(f"👻 Skipping tombstone: {bid} (Not found in Gitea)") + skipped_count += 1 + continue + # --- STANDARD SYNC LOGIC --- + processed_count += 1 + gitea_state = ( + "closed" if status in ["closed", "done", "finished"] else "open" + ) + + payload = { + "title": f"[{bid}] {title}", + "body": f"{data.get('description', 'No description provided.')}\n\n---\n**Beads ID:** `{bid}`\n**Type:** `{itype}`", + "state": gitea_state, + } + + if bid in existing: + print(f"✅ Updating {bid} (Issue #{existing[bid]['number']})") + requests.patch( + f"{URL}/api/v1/repos/{REPO}/issues/{existing[bid]['number']}", + headers=HEADERS, + json=payload, + ) + else: + print(f"➕ Creating New Gitea Issue for {bid}") + r = requests.post( + f"{URL}/api/v1/repos/{REPO}/issues", + headers=HEADERS, + json=payload, + ) + r.raise_for_status() + + except Exception as e: + print(f"⚠️ Error processing bead line: {e}") + + print( + f"🏁 Finished. Active: {processed_count}, Deleted: {deleted_count}, Skipped: {skipped_count}" + ) + + # Safety check to ensure we didn't process a completely empty file + if (processed_count + deleted_count + skipped_count) == 0: + print("❌ ERROR: File was found but it was EMPTY.") + sys.exit(1) + + +if __name__ == "__main__": + sync() diff --git a/.gitea/workflows/beads-sync.yml b/.gitea/workflows/beads-sync.yml new file mode 100644 index 0000000..3e0dd0f --- /dev/null +++ b/.gitea/workflows/beads-sync.yml @@ -0,0 +1,27 @@ +name: Sync Beads to Gitea Issues +on: + push: + branches: + - main # Adjust if your default branch is different + +jobs: + sync-beads: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install dependencies + run: pip install requests + + - name: Run Sync Script + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_URL: ${{ github.server_url }} + REPO_NAME: ${{ github.repository }} + run: python .gitea/sync_beads.py diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..df7a4af --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --status in_progress # Claim work +bd close # Complete work +bd sync # Sync with git +``` + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd sync + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds +