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..fe80db0 --- /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":"closed","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-19T20:08:24.331722606-05:00","closed_at":"2026-01-19T20:08:24.331722606-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"}]} +{"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..fcd49f1 --- /dev/null +++ b/.gitea/sync_beads.py @@ -0,0 +1,110 @@ +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_num = existing[bid]['number'] + print(f"🗑️ Deleting Gitea Issue #{issue_num} (Tombstone found for {bid})") + del_resp = requests.delete(f"{URL}/api/v1/repos/{REPO}/issues/{issue_num}", headers=HEADERS) + if del_resp.status_code == 204: + print(f"✅ Successfully deleted {bid}") + deleted_count += 1 + else: + print(f"⚠️ Could not delete {bid}: {del_resp.status_code}") + else: + print(f"👻 Skipping tombstone: {bid} (Already gone from 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 +