feat: integrate beads
All checks were successful
Sync Beads to Gitea Issues / sync-beads (push) Successful in 7s
All checks were successful
Sync Beads to Gitea Issues / sync-beads (push) Successful in 7s
bd sync: 2026-01-19 21:41:13
This commit is contained in:
parent
829ab46608
commit
23beb115fa
44
.beads/.gitignore
vendored
Normal file
44
.beads/.gitignore
vendored
Normal file
@ -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.
|
||||
81
.beads/README.md
Normal file
81
.beads/README.md
Normal file
@ -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 <issue-id>
|
||||
|
||||
# Update issue status
|
||||
bd update <issue-id> --status in_progress
|
||||
bd update <issue-id> --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* ⚡
|
||||
62
.beads/config.yaml
Normal file
62
.beads/config.yaml
Normal file
@ -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 <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
|
||||
0
.beads/interactions.jsonl
Normal file
0
.beads/interactions.jsonl
Normal file
4
.beads/issues.jsonl
Normal file
4
.beads/issues.jsonl
Normal file
@ -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"}
|
||||
4
.beads/metadata.json
Normal file
4
.beads/metadata.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"database": "beads.db",
|
||||
"jsonl_export": "issues.jsonl"
|
||||
}
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
# Use bd merge for beads JSONL files
|
||||
.beads/issues.jsonl merge=beads
|
||||
154
.gitea/sync_beads.py
Normal file
154
.gitea/sync_beads.py
Normal file
@ -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()
|
||||
27
.gitea/workflows/beads-sync.yml
Normal file
27
.gitea/workflows/beads-sync.yml
Normal file
@ -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
|
||||
40
AGENTS.md
Normal file
40
AGENTS.md
Normal file
@ -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 <id> # View issue details
|
||||
bd update <id> --status in_progress # Claim work
|
||||
bd close <id> # 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user