fix(autoimport): auto-correct deleted status to tombstone for JSONL compatibility (#1231)
* fix(autoimport): auto-correct deleted status to tombstone for JSONL compatibility (GH#1223) This fix addresses the 'Stuck in sync diversion loop' issue where v0.48.0 encountered validation errors during JSONL import. The issue occurs when JSONL files from older versions have issues with status='deleted' but the current code expects status='tombstone' for deleted issues. Changes: - Add migration logic in parseJSONL to auto-correct 'deleted' status to 'tombstone' - Ensure tombstones always have deleted_at timestamp set - Add debug logging for both migration operations - Prevents users from being stuck in sync divergence when upgrading Fixes GH#1223: Stuck in sync diversion loop * fix(autoimport): comprehensively fix corrupted deleted_at on non-tombstone issues (GH#1223) The initial fix for GH#1223 only caught issues with status='deleted', but the real data in the wild had issues with status='closed' (or other statuses) but also had deleted_at set, which violates the validation rule. Changes: - Add broader migration logic: any non-tombstone issue with deleted_at should become tombstone - Apply fix in all three JSONL parsing locations: - internal/autoimport/autoimport.go (parseJSONL for auto-import) - cmd/bd/import.go (import command) - cmd/bd/daemon_sync.go (daemon sync helper) - Add comprehensive test case for corrupted closed issues with deleted_at - Fixes the 'non-tombstone issues cannot have deleted_at timestamp' validation error during fresh bd init or import Fixes GH#1223: Stuck in sync diversion loop * Add merge driver comment to .gitattributes * fix: properly clean up .gitattributes during bd admin reset Fixes GH#1223 - Stuck in sync diversion loop The removeGitattributesEntry() function was not properly cleaning up beads-related entries from .gitattributes. It only removed lines containing "merge=beads" but left behind: - The comment line "# Use bd merge for beads JSONL files" - Empty lines following removed entries This caused .gitattributes to remain in a modified state after bd admin reset --force, triggering sync divergence warning loop. The fix now: - Skips lines containing "merge=beads" (existing behavior) - Skips beads-related comment lines - Skips empty lines that follow removed beads entries - Properly cleans up file so it's either empty (and gets deleted) or contains only non-beads content --------- Co-authored-by: Amp <amp@example.com>
This commit is contained in:
6
.beads/.gitignore
vendored
6
.beads/.gitignore
vendored
@@ -32,9 +32,13 @@ 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.
|
||||
metadata.json
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
<!-- Auto-generated by bd v0.24.2 - DO NOT EDIT MANUALLY -->
|
||||
<!-- Run 'bd onboard --output .beads/BD_GUIDE.md' to regenerate -->
|
||||
|
||||
# BD (Beads) Guide for AI Agents
|
||||
|
||||
This file contains canonical bd (beads) workflow instructions for AI agents.
|
||||
It is auto-generated and version-stamped to track bd upgrades.
|
||||
|
||||
> **For project-specific instructions**, see AGENTS.md in the repository root.
|
||||
> This file only covers bd tool usage, not project-specific workflows.
|
||||
|
||||
---
|
||||
|
||||
## Issue Tracking with bd (beads)
|
||||
|
||||
**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
|
||||
|
||||
### Why bd?
|
||||
|
||||
- Dependency-aware: Track blockers and relationships between issues
|
||||
- Git-friendly: Auto-syncs to JSONL for version control
|
||||
- Agent-optimized: JSON output, ready work detection, discovered-from links
|
||||
- Prevents duplicate tracking systems and confusion
|
||||
|
||||
### Quick Start
|
||||
|
||||
**Check for ready work:**
|
||||
```bash
|
||||
bd ready --json
|
||||
```
|
||||
|
||||
**Create new issues:**
|
||||
```bash
|
||||
bd create "Issue title" -t bug|feature|task -p 0-4 --json
|
||||
bd create "Issue title" -p 1 --deps discovered-from:bd-123 --json
|
||||
```
|
||||
|
||||
**Claim and update:**
|
||||
```bash
|
||||
bd update bd-42 --status in_progress --json
|
||||
bd update bd-42 --priority 1 --json
|
||||
```
|
||||
|
||||
**Complete work:**
|
||||
```bash
|
||||
bd close bd-42 --reason "Completed" --json
|
||||
```
|
||||
|
||||
### Issue Types
|
||||
|
||||
- `bug` - Something broken
|
||||
- `feature` - New functionality
|
||||
- `task` - Work item (tests, docs, refactoring)
|
||||
- `epic` - Large feature with subtasks
|
||||
- `chore` - Maintenance (dependencies, tooling)
|
||||
|
||||
### Priorities
|
||||
|
||||
- `0` - Critical (security, data loss, broken builds)
|
||||
- `1` - High (major features, important bugs)
|
||||
- `2` - Medium (default, nice-to-have)
|
||||
- `3` - Low (polish, optimization)
|
||||
- `4` - Backlog (future ideas)
|
||||
|
||||
### Workflow for AI Agents
|
||||
|
||||
1. **Check ready work**: `bd ready` shows unblocked issues
|
||||
2. **Claim your task**: `bd update <id> --status in_progress`
|
||||
3. **Work on it**: Implement, test, document
|
||||
4. **Discover new work?** Create linked issue:
|
||||
- `bd create "Found bug" -p 1 --deps discovered-from:<parent-id>`
|
||||
5. **Complete**: `bd close <id> --reason "Done"`
|
||||
6. **Commit together**: Always commit the `.beads/issues.jsonl` file together with the code changes so issue state stays in sync with code state
|
||||
|
||||
### Auto-Sync
|
||||
|
||||
bd automatically syncs with git:
|
||||
- Exports to `.beads/issues.jsonl` after changes (5s debounce)
|
||||
- Imports from JSONL when newer (e.g., after `git pull`)
|
||||
- No manual export/import needed!
|
||||
|
||||
### GitHub Copilot Integration
|
||||
|
||||
If using GitHub Copilot, also create `.github/copilot-instructions.md` for automatic instruction loading.
|
||||
Run `bd onboard` to get the content, or see step 2 of the onboard instructions.
|
||||
|
||||
### MCP Server (Recommended)
|
||||
|
||||
If using Claude or MCP-compatible clients, install the beads MCP server:
|
||||
|
||||
```bash
|
||||
pip install beads-mcp
|
||||
```
|
||||
|
||||
Add to MCP config (e.g., `~/.config/claude/config.json`):
|
||||
```json
|
||||
{
|
||||
"beads": {
|
||||
"command": "beads-mcp",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then use `mcp__beads__*` functions instead of CLI commands.
|
||||
|
||||
### Managing AI-Generated Planning Documents
|
||||
|
||||
AI assistants often create planning and design documents during development:
|
||||
- PLAN.md, IMPLEMENTATION.md, ARCHITECTURE.md
|
||||
- DESIGN.md, CODEBASE_SUMMARY.md, INTEGRATION_PLAN.md
|
||||
- TESTING_GUIDE.md, TECHNICAL_DESIGN.md, and similar files
|
||||
|
||||
**Best Practice: Use a dedicated directory for these ephemeral files**
|
||||
|
||||
**Recommended approach:**
|
||||
- Create a `history/` directory in the project root
|
||||
- Store ALL AI-generated planning/design docs in `history/`
|
||||
- Keep the repository root clean and focused on permanent project files
|
||||
- Only access `history/` when explicitly asked to review past planning
|
||||
|
||||
**Example .gitignore entry (optional):**
|
||||
```
|
||||
# AI planning documents (ephemeral)
|
||||
history/
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Clean repository root
|
||||
- ✅ Clear separation between ephemeral and permanent documentation
|
||||
- ✅ Easy to exclude from version control if desired
|
||||
- ✅ Preserves planning history for archeological research
|
||||
- ✅ Reduces noise when browsing the project
|
||||
|
||||
### Important Rules
|
||||
|
||||
- ✅ Use bd for ALL task tracking
|
||||
- ✅ Always use `--json` flag for programmatic use
|
||||
- ✅ Link discovered work with `discovered-from` dependencies
|
||||
- ✅ Check `bd ready` before asking "what should I work on?"
|
||||
- ✅ Store AI planning docs in `history/` directory
|
||||
- ❌ Do NOT create markdown TODO lists
|
||||
- ❌ Do NOT use external issue trackers
|
||||
- ❌ Do NOT duplicate tracking systems
|
||||
- ❌ Do NOT clutter repo root with planning documents
|
||||
|
||||
For more details, see README.md and QUICKSTART.md.
|
||||
|
||||
---
|
||||
|
||||
# GitHub Copilot Instructions for Beads
|
||||
|
||||
## Project Overview
|
||||
|
||||
**beads** (command: `bd`) is a Git-backed issue tracker designed for AI-supervised coding workflows. We dogfood our own tool for all task tracking.
|
||||
|
||||
**Key Features:**
|
||||
- Dependency-aware issue tracking
|
||||
- Auto-sync with Git via JSONL
|
||||
- AI-optimized CLI with JSON output
|
||||
- Built-in daemon for background operations
|
||||
- MCP server integration for Claude and other AI assistants
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Language**: Go 1.21+
|
||||
- **Storage**: SQLite (internal/storage/sqlite/)
|
||||
- **CLI Framework**: Cobra
|
||||
- **Testing**: Go standard testing + table-driven tests
|
||||
- **CI/CD**: GitHub Actions
|
||||
- **MCP Server**: Python (integrations/beads-mcp/)
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
### Testing
|
||||
- Always write tests for new features
|
||||
- Use `BEADS_DB=/tmp/test.db` to avoid polluting production database
|
||||
- Run `go test -short ./...` before committing
|
||||
- Never create test issues in production DB (use temporary DB)
|
||||
|
||||
### Code Style
|
||||
- Run `golangci-lint run ./...` before committing
|
||||
- Follow existing patterns in `cmd/bd/` for new commands
|
||||
- Add `--json` flag to all commands for programmatic use
|
||||
- Update docs when changing behavior
|
||||
|
||||
### Git Workflow
|
||||
- Always commit `.beads/issues.jsonl` with code changes
|
||||
- Run `bd sync` at end of work sessions
|
||||
- Install git hooks: `bd hooks install` (ensures DB ↔ JSONL consistency)
|
||||
|
||||
## Issue Tracking with bd
|
||||
|
||||
**CRITICAL**: This project uses **bd** for ALL task tracking. Do NOT create markdown TODO lists.
|
||||
|
||||
### Essential Commands
|
||||
|
||||
```bash
|
||||
# Find work
|
||||
bd ready --json # Unblocked issues
|
||||
bd stale --days 30 --json # Forgotten issues
|
||||
|
||||
# Create and manage
|
||||
bd create "Title" -t bug|feature|task -p 0-4 --json
|
||||
bd update <id> --status in_progress --json
|
||||
bd close <id> --reason "Done" --json
|
||||
|
||||
# Search
|
||||
bd list --status open --priority 1 --json
|
||||
bd show <id> --json
|
||||
|
||||
# Sync (CRITICAL at end of session!)
|
||||
bd sync # Force immediate export/commit/push
|
||||
```
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Check ready work**: `bd ready --json`
|
||||
2. **Claim task**: `bd update <id> --status in_progress`
|
||||
3. **Work on it**: Implement, test, document
|
||||
4. **Discover new work?** `bd create "Found bug" -p 1 --deps discovered-from:<parent-id> --json`
|
||||
5. **Complete**: `bd close <id> --reason "Done" --json`
|
||||
6. **Sync**: `bd sync` (flushes changes to git immediately)
|
||||
|
||||
### Priorities
|
||||
|
||||
- `0` - Critical (security, data loss, broken builds)
|
||||
- `1` - High (major features, important bugs)
|
||||
- `2` - Medium (default, nice-to-have)
|
||||
- `3` - Low (polish, optimization)
|
||||
- `4` - Backlog (future ideas)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
beads/
|
||||
├── cmd/bd/ # CLI commands (add new commands here)
|
||||
├── internal/
|
||||
│ ├── types/ # Core data types
|
||||
│ └── storage/ # Storage layer
|
||||
│ └── sqlite/ # SQLite implementation
|
||||
├── integrations/
|
||||
│ └── beads-mcp/ # MCP server (Python)
|
||||
├── examples/ # Integration examples
|
||||
├── docs/ # Documentation
|
||||
└── .beads/
|
||||
├── beads.db # SQLite database (DO NOT COMMIT)
|
||||
└── issues.jsonl # Git-synced issue storage
|
||||
```
|
||||
|
||||
## Available Resources
|
||||
|
||||
### MCP Server (Recommended)
|
||||
Use the beads MCP server for native function calls instead of shell commands:
|
||||
- Install: `pip install beads-mcp`
|
||||
- Functions: `mcp__beads__ready()`, `mcp__beads__create()`, etc.
|
||||
- See `integrations/beads-mcp/README.md`
|
||||
|
||||
### Scripts
|
||||
- `./scripts/bump-version.sh <version> --commit` - Update all version files atomically
|
||||
- `./scripts/release.sh <version>` - Complete release workflow
|
||||
- `./scripts/update-homebrew.sh <version>` - Update Homebrew formula
|
||||
|
||||
### Key Documentation
|
||||
- **AGENTS.md** - Comprehensive AI agent guide (detailed workflows, advanced features)
|
||||
- **AGENT_INSTRUCTIONS.md** - Development procedures, testing, releases
|
||||
- **README.md** - User-facing documentation
|
||||
- **docs/CLI_REFERENCE.md** - Complete command reference
|
||||
|
||||
## Important Rules
|
||||
|
||||
- ✅ Use bd for ALL task tracking
|
||||
- ✅ Always use `--json` flag for programmatic use
|
||||
- ✅ Run `bd sync` at end of sessions
|
||||
- ✅ Test with `BEADS_DB=/tmp/test.db`
|
||||
- ❌ Do NOT create markdown TODO lists
|
||||
- ❌ Do NOT create test issues in production DB
|
||||
- ❌ Do NOT commit `.beads/beads.db` (JSONL only)
|
||||
|
||||
---
|
||||
|
||||
**For detailed workflows and advanced features, see [AGENTS.md](../AGENTS.md)**
|
||||
|
||||
---
|
||||
|
||||
**Generated by bd v0.24.2**
|
||||
|
||||
To regenerate this file after upgrading bd:
|
||||
```bash
|
||||
bd onboard --output .beads/BD_GUIDE.md
|
||||
```
|
||||
@@ -6,7 +6,7 @@
|
||||
# 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: "bd"
|
||||
# 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
|
||||
@@ -37,12 +37,20 @@ issue-prefix: "bd"
|
||||
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
|
||||
# flush-debounce: "5s"
|
||||
|
||||
# Sync branch for multi-clone setups
|
||||
# When set, .beads changes are committed to this branch via worktree
|
||||
# instead of the current branch. This allows multiple clones to sync
|
||||
# beads data without polluting main branch commits.
|
||||
# Can also be set via BEADS_SYNC_BRANCH environment variable.
|
||||
sync-branch: "beads-metadata"
|
||||
# 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:
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"hourly_tokens_used": 84530,
|
||||
"hourly_cost_used": 0.39143399999999995,
|
||||
"window_start_time": "2025-11-21T22:59:34.058436-08:00",
|
||||
"issue_tokens_used": {
|
||||
"SYSTEM": 50702,
|
||||
"bd-9f86-baseline-test": 33055
|
||||
},
|
||||
"total_tokens_used": 84530,
|
||||
"total_cost_used": 0.39143399999999995,
|
||||
"last_updated": "2025-11-21T23:28:18.766601-08:00"
|
||||
}
|
||||
@@ -1,770 +0,0 @@
|
||||
# Beads Release Formula v2 (Gate-Aware)
|
||||
#
|
||||
# This formula uses gates for async waits during CI, enabling clean phase handoffs.
|
||||
# Instead of polling, the polecat completes Phase 1, and a gate blocks Phase 2
|
||||
# until GitHub Actions finishes.
|
||||
#
|
||||
# Phases:
|
||||
# 1. Prep & Push: preflight → push-tag (polecat work)
|
||||
# 2. Gate: await-ci (async wait for release.yml)
|
||||
# 3. Verify: verify-github, verify-npm, verify-pypi (parallel)
|
||||
# 4. Install: local-install → release-complete (fan-in)
|
||||
|
||||
formula = "beads-release"
|
||||
description = """
|
||||
Beads release workflow v2 - gate-aware async release.
|
||||
|
||||
This formula orchestrates a complete release cycle with async gates:
|
||||
|
||||
Phase 1 (Polecat Work):
|
||||
1. Preflight checks (clean git, up to date)
|
||||
2. Documentation updates (CHANGELOG, info.go)
|
||||
3. Version bump (all components)
|
||||
4. Git operations (commit, tag, push)
|
||||
|
||||
Gate (Async Wait):
|
||||
5. await-ci: Gate on GitHub Actions release.yml completion
|
||||
|
||||
Phase 2 (Parallel Verification):
|
||||
6. Verify GitHub release, npm package, PyPI package (concurrent)
|
||||
|
||||
Phase 3 (Installation):
|
||||
7. Local installation update
|
||||
8. Daemon restart
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
bd mol wisp create beads-release --var version=0.44.0
|
||||
```
|
||||
|
||||
Or assign to a polecat:
|
||||
```bash
|
||||
gt sling beads/polecats/p1 --formula beads-release --var version=0.44.0
|
||||
```
|
||||
|
||||
The polecat will complete Phase 1, then signal phase-complete. The gate blocks
|
||||
until release.yml finishes. A new polecat (or the same one) resumes for Phases 2-3.
|
||||
"""
|
||||
type = "workflow"
|
||||
phase = "vapor" # Ensures this runs as a wisp - bd pour will warn, gt sling --formula creates wisp
|
||||
version = 1
|
||||
|
||||
[vars.version]
|
||||
description = "The semantic version to release (e.g., 0.44.0)"
|
||||
required = true
|
||||
|
||||
# =============================================================================
|
||||
# Phase 1: Prep & Push
|
||||
# =============================================================================
|
||||
|
||||
[[steps]]
|
||||
id = "preflight-worktree"
|
||||
title = "Preflight: Verify git context"
|
||||
description = """
|
||||
Ensure we're in the correct git directory, especially for worktree setups.
|
||||
|
||||
```bash
|
||||
# Check current repo root and remote
|
||||
git rev-parse --show-toplevel
|
||||
git remote get-url origin
|
||||
|
||||
# Verify this is the main worktree (not a linked worktree)
|
||||
git worktree list
|
||||
```
|
||||
|
||||
For worktree setups, releases should be done from the **main worktree** to ensure:
|
||||
- Correct file paths in commits
|
||||
- Proper tag association
|
||||
- Clean branch history
|
||||
|
||||
If you're in a linked worktree:
|
||||
1. Note the path to the main worktree (first line of `git worktree list`)
|
||||
2. Switch to that directory before proceeding
|
||||
3. Or use: `cd $(git worktree list | head -1 | cut -d' ' -f1)`
|
||||
|
||||
**Red flags:**
|
||||
- Remote URL doesn't match expected repository
|
||||
- You're in a linked worktree (not the main one)
|
||||
- `git status` shows different files than expected
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "preflight-git"
|
||||
title = "Preflight: Check git status & auto-stash"
|
||||
needs = ["preflight-worktree"]
|
||||
description = """
|
||||
Ensure working tree is clean before starting release.
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
**Handling uncommitted changes:**
|
||||
|
||||
If changes are in release-related files (CHANGELOG, version files), you may want to:
|
||||
- Include them in the release commit
|
||||
- Or stash and apply after version bump
|
||||
|
||||
If changes are in **non-release files** (e.g., .beads/config.yaml, .claude/settings.json, local configs):
|
||||
```bash
|
||||
# Auto-stash non-release files
|
||||
git stash push -m "pre-release: non-release changes" -- .beads/ .claude/ *.local* .env*
|
||||
|
||||
# Verify working tree is now clean (or only has release files)
|
||||
git status --short
|
||||
```
|
||||
|
||||
**Important:** The bump script may fail if non-release files are modified. Always stash:
|
||||
- `.beads/` directory (local config)
|
||||
- `.claude/` directory (agent settings)
|
||||
- Any `.local`, `.env`, or personal config files
|
||||
|
||||
After release completes, restore with:
|
||||
```bash
|
||||
git stash pop
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "preflight-pull"
|
||||
title = "Preflight: Pull latest & verify sync"
|
||||
needs = ["preflight-git"]
|
||||
description = """
|
||||
Ensure we're up to date with origin and branch is properly synced.
|
||||
|
||||
```bash
|
||||
# Fetch latest from origin
|
||||
git fetch origin
|
||||
|
||||
# Check branch status BEFORE pulling
|
||||
git status -sb
|
||||
```
|
||||
|
||||
**Branch sync verification:**
|
||||
- "Your branch is behind" → Pull needed, proceed with `git pull --rebase`
|
||||
- "Your branch is ahead" → You have unpushed commits - review before release!
|
||||
- "Your branch has diverged" → **STOP** - resolve divergence first
|
||||
|
||||
```bash
|
||||
# If branch is behind or even, pull latest
|
||||
git pull --rebase
|
||||
```
|
||||
|
||||
**Recovering from divergence:**
|
||||
If your branch has diverged from origin (e.g., after a botched commit):
|
||||
```bash
|
||||
# Option 1: Reset to origin (loses local commits)
|
||||
git reset --hard origin/main
|
||||
|
||||
# Option 2: Rebase local commits on top of origin
|
||||
git rebase origin/main
|
||||
|
||||
# Option 3: Create a backup branch first
|
||||
git branch backup-before-release
|
||||
git reset --hard origin/main
|
||||
```
|
||||
|
||||
**After pulling, verify sync:**
|
||||
```bash
|
||||
git status -sb
|
||||
# Should show: "## main...origin/main" (no ahead/behind)
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "detect-half-done-release"
|
||||
title = "Detect half-done release"
|
||||
needs = ["preflight-pull"]
|
||||
description = """
|
||||
Check if a previous release was started but not completed (version mismatch).
|
||||
|
||||
**Compare versions across all sources:**
|
||||
```bash
|
||||
# Last git tag
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
|
||||
echo "Last tag: $LAST_TAG"
|
||||
|
||||
# Version in code
|
||||
CODE_VERSION=$(grep 'Version = ' cmd/bd/version.go | cut -d'"' -f2)
|
||||
echo "Code version: $CODE_VERSION"
|
||||
|
||||
# Version in CHANGELOG (most recent versioned section)
|
||||
CHANGELOG_VERSION=$(grep -E '## \\[[0-9]+\\.[0-9]+\\.[0-9]+\\]' CHANGELOG.md | head -1 | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+')
|
||||
echo "CHANGELOG version: $CHANGELOG_VERSION"
|
||||
|
||||
# Version in npm package
|
||||
NPM_VERSION=$(jq -r '.version' npm-package/package.json)
|
||||
echo "npm version: $NPM_VERSION"
|
||||
```
|
||||
|
||||
**Healthy state (ready for new release):**
|
||||
- All versions match (e.g., all show 0.46.0)
|
||||
- Last tag matches code version
|
||||
|
||||
**Half-done release detected if:**
|
||||
- CHANGELOG shows {{version}} but code shows previous version
|
||||
- Code version doesn't match last tag
|
||||
- npm/PyPI versions are out of sync
|
||||
|
||||
**Recovery from half-done release:**
|
||||
```bash
|
||||
# If CHANGELOG was updated but code wasn't bumped:
|
||||
# Either complete the release or revert CHANGELOG changes
|
||||
|
||||
# Check what state we're in
|
||||
git diff $LAST_TAG -- CHANGELOG.md | head -50
|
||||
```
|
||||
|
||||
If mismatch detected, either:
|
||||
1. Complete the partial release (bump remaining files)
|
||||
2. Revert partial changes and start fresh
|
||||
3. Carefully verify what's already pushed vs local-only
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "review-changes"
|
||||
title = "Review changes since last release"
|
||||
needs = ["detect-half-done-release"]
|
||||
description = """
|
||||
Understand what's being released.
|
||||
|
||||
```bash
|
||||
git log $(git describe --tags --abbrev=0)..HEAD --oneline
|
||||
```
|
||||
|
||||
Categorize changes:
|
||||
- Features (feat:)
|
||||
- Fixes (fix:)
|
||||
- Breaking changes
|
||||
- Documentation
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "verify-changelog-complete"
|
||||
title = "Verify CHANGELOG completeness"
|
||||
needs = ["review-changes"]
|
||||
description = """
|
||||
Ensure CHANGELOG captures ALL commits since the last release.
|
||||
|
||||
**Step 1: Count commits since last tag**
|
||||
```bash
|
||||
LAST_TAG=$(git describe --tags --abbrev=0)
|
||||
COMMIT_COUNT=$(git rev-list $LAST_TAG..HEAD --count)
|
||||
echo "Commits since $LAST_TAG: $COMMIT_COUNT"
|
||||
```
|
||||
|
||||
**Step 2: List all commits with their messages**
|
||||
```bash
|
||||
git log $LAST_TAG..HEAD --oneline --no-merges
|
||||
```
|
||||
|
||||
**Step 3: Cross-reference with CHANGELOG**
|
||||
Open CHANGELOG.md and verify every significant commit is documented in [Unreleased].
|
||||
|
||||
**Red flags for stale CHANGELOG:**
|
||||
- If CHANGELOG was last modified days ago but there are recent commits
|
||||
- If commit count seems high but [Unreleased] section is sparse
|
||||
- If PR titles in commits don't match CHANGELOG entries
|
||||
|
||||
```bash
|
||||
# Check when CHANGELOG was last modified
|
||||
git log -1 --format="%ar" -- CHANGELOG.md
|
||||
|
||||
# Compare to latest commit date
|
||||
git log -1 --format="%ar"
|
||||
```
|
||||
|
||||
**If CHANGELOG is stale:**
|
||||
1. Review all commits since last tag
|
||||
2. Add missing entries to appropriate sections (Added/Changed/Fixed)
|
||||
3. Group related changes
|
||||
4. Focus on user-facing changes and breaking changes
|
||||
|
||||
Don't proceed until CHANGELOG is complete - it's the release notes!
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "update-changelog"
|
||||
title = "Update CHANGELOG.md"
|
||||
needs = ["verify-changelog-complete"]
|
||||
description = """
|
||||
Write the [Unreleased] section with all changes for {{version}}.
|
||||
|
||||
Format: Keep a Changelog (https://keepachangelog.com)
|
||||
|
||||
Sections:
|
||||
- ### Added
|
||||
- ### Changed
|
||||
- ### Fixed
|
||||
- ### Documentation
|
||||
|
||||
The bump script will stamp the date automatically.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "update-info-go"
|
||||
title = "Update info.go versionChanges"
|
||||
needs = ["update-changelog"]
|
||||
description = """
|
||||
Add entry to versionChanges in cmd/bd/info.go.
|
||||
|
||||
This powers `bd info --whats-new` for agents.
|
||||
|
||||
```go
|
||||
"{{version}}": {
|
||||
"summary": "Brief description",
|
||||
"changes": []string{
|
||||
"Key change 1",
|
||||
"Key change 2",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Focus on workflow-impacting changes agents need to know.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-version-go"
|
||||
title = "Bump version in version.go"
|
||||
needs = ["update-info-go"]
|
||||
description = """
|
||||
Update the Go version constant.
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
sed -i '' 's/Version = "[^"]*"/Version = "{{version}}"/' cmd/bd/version.go
|
||||
|
||||
# Linux
|
||||
sed -i 's/Version = "[^"]*"/Version = "{{version}}"/' cmd/bd/version.go
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep 'Version = ' cmd/bd/version.go
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-plugin-json"
|
||||
title = "Bump version in plugin JSON files"
|
||||
needs = ["bump-version-go"]
|
||||
description = """
|
||||
Update Claude plugin manifest versions.
|
||||
|
||||
```bash
|
||||
# macOS/Linux with jq
|
||||
jq '.version = "{{version}}"' .claude-plugin/plugin.json > tmp && mv tmp .claude-plugin/plugin.json
|
||||
jq '.plugins[0].version = "{{version}}"' .claude-plugin/marketplace.json > tmp && mv tmp .claude-plugin/marketplace.json
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
jq -r '.version' .claude-plugin/plugin.json
|
||||
jq -r '.plugins[0].version' .claude-plugin/marketplace.json
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-mcp-python"
|
||||
title = "Bump version in MCP Python package"
|
||||
needs = ["bump-plugin-json"]
|
||||
description = """
|
||||
Update the beads-mcp Python package version.
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
sed -i '' 's/version = "[^"]*"/version = "{{version}}"/' integrations/beads-mcp/pyproject.toml
|
||||
sed -i '' 's/__version__ = "[^"]*"/__version__ = "{{version}}"/' integrations/beads-mcp/src/beads_mcp/__init__.py
|
||||
|
||||
# Linux
|
||||
sed -i 's/version = "[^"]*"/version = "{{version}}"/' integrations/beads-mcp/pyproject.toml
|
||||
sed -i 's/__version__ = "[^"]*"/__version__ = "{{version}}"/' integrations/beads-mcp/src/beads_mcp/__init__.py
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep 'version = ' integrations/beads-mcp/pyproject.toml | head -1
|
||||
grep '__version__ = ' integrations/beads-mcp/src/beads_mcp/__init__.py
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-npm-package"
|
||||
title = "Bump version in npm package"
|
||||
needs = ["bump-mcp-python"]
|
||||
description = """
|
||||
Update the npm package version.
|
||||
|
||||
```bash
|
||||
# macOS/Linux with jq
|
||||
jq '.version = "{{version}}"' npm-package/package.json > tmp && mv tmp npm-package/package.json
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
jq -r '.version' npm-package/package.json
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-hook-templates"
|
||||
title = "Bump version in hook templates"
|
||||
needs = ["bump-npm-package"]
|
||||
description = """
|
||||
Update version comment in git hook templates.
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
for f in cmd/bd/templates/hooks/pre-commit cmd/bd/templates/hooks/post-merge cmd/bd/templates/hooks/pre-push cmd/bd/templates/hooks/post-checkout; do
|
||||
sed -i '' 's/# bd-hooks-version: .*/# bd-hooks-version: {{version}}/' "$f"
|
||||
done
|
||||
|
||||
# Linux
|
||||
for f in cmd/bd/templates/hooks/pre-commit cmd/bd/templates/hooks/post-merge cmd/bd/templates/hooks/pre-push cmd/bd/templates/hooks/post-checkout; do
|
||||
sed -i 's/# bd-hooks-version: .*/# bd-hooks-version: {{version}}/' "$f"
|
||||
done
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep '# bd-hooks-version:' cmd/bd/templates/hooks/pre-commit
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-readme"
|
||||
title = "Bump version in README badge"
|
||||
needs = ["bump-hook-templates"]
|
||||
description = """
|
||||
Update the Alpha version badge in README.md.
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
sed -i '' 's/Alpha (v[^)]*)/Alpha (v{{version}})/' README.md
|
||||
|
||||
# Linux
|
||||
sed -i 's/Alpha (v[^)]*)/Alpha (v{{version}})/' README.md
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep 'Alpha (v' README.md
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "bump-default-nix"
|
||||
title = "Bump version in default.nix"
|
||||
needs = ["bump-readme"]
|
||||
description = """
|
||||
Update the Nix package version.
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
sed -i '' 's/version = "[^"]*";/version = "{{version}}";/' default.nix
|
||||
|
||||
# Linux
|
||||
sed -i 's/version = "[^"]*";/version = "{{version}}";/' default.nix
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep 'version = ' default.nix
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "update-vendorhash"
|
||||
title = "Update Nix vendorHash if needed"
|
||||
needs = ["bump-default-nix"]
|
||||
description = """
|
||||
Update vendorHash if go.mod/go.sum changed since last release.
|
||||
|
||||
Check if update is needed:
|
||||
```bash
|
||||
# Compare go.mod/go.sum against last tag
|
||||
LAST_TAG=$(git describe --tags --abbrev=0)
|
||||
git diff $LAST_TAG -- go.mod go.sum
|
||||
```
|
||||
|
||||
If there are changes, run the update script:
|
||||
```bash
|
||||
./scripts/update-nix-vendorhash.sh
|
||||
```
|
||||
|
||||
The script auto-detects nix or Docker and updates the hash automatically.
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep 'vendorHash = ' default.nix
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "stamp-changelog"
|
||||
title = "Stamp changelog with release date"
|
||||
needs = ["update-vendorhash"]
|
||||
description = """
|
||||
Add the release date to the [Unreleased] section header.
|
||||
|
||||
```bash
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
|
||||
# macOS
|
||||
sed -i '' "s/## \\[Unreleased\\]/## [Unreleased]\\
|
||||
\\
|
||||
## [{{version}}] - $DATE/" CHANGELOG.md
|
||||
|
||||
# Linux
|
||||
sed -i "s/## \\[Unreleased\\]/## [Unreleased]\\n\\n## [{{version}}] - $DATE/" CHANGELOG.md
|
||||
```
|
||||
|
||||
Note: The update-changelog step handles the content; this step just adds the date stamp.
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
grep -A2 '\\[Unreleased\\]' CHANGELOG.md
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "verify-versions"
|
||||
title = "Verify version consistency"
|
||||
needs = ["stamp-changelog"]
|
||||
description = """
|
||||
Confirm all versions match {{version}}.
|
||||
|
||||
```bash
|
||||
grep 'Version = ' cmd/bd/version.go
|
||||
jq -r '.version' .claude-plugin/plugin.json
|
||||
jq -r '.version' npm-package/package.json
|
||||
grep 'version = ' integrations/beads-mcp/pyproject.toml
|
||||
grep 'version = ' default.nix
|
||||
```
|
||||
|
||||
All should show {{version}}.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "commit-release"
|
||||
title = "Commit release"
|
||||
needs = ["verify-versions"]
|
||||
description = """
|
||||
Stage and commit all version changes.
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: Bump version to {{version}}"
|
||||
```
|
||||
|
||||
Review the commit to ensure all expected files are included.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "create-tag"
|
||||
title = "Create release tag"
|
||||
needs = ["commit-release"]
|
||||
description = """
|
||||
Create annotated git tag.
|
||||
|
||||
```bash
|
||||
git tag -a v{{version}} -m "Release v{{version}}"
|
||||
```
|
||||
|
||||
Verify: `git tag -l | tail -5`
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "push-main"
|
||||
title = "Push to main"
|
||||
needs = ["create-tag"]
|
||||
description = """
|
||||
Push the release commit to origin.
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
If rejected, someone else pushed. Pull, rebase, try again.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "push-tag"
|
||||
title = "Push release tag"
|
||||
needs = ["push-main"]
|
||||
description = """
|
||||
Push the version tag to trigger CI release.
|
||||
|
||||
```bash
|
||||
git push origin v{{version}}
|
||||
```
|
||||
|
||||
This triggers GitHub Actions to build artifacts and publish.
|
||||
|
||||
**Phase 1 Complete**: After this step, signal phase-complete:
|
||||
```bash
|
||||
gt done --phase-complete
|
||||
```
|
||||
|
||||
The gate will block until CI finishes. A new session will resume at Phase 2.
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# Gate: CI Completion
|
||||
# =============================================================================
|
||||
|
||||
[[steps]]
|
||||
id = "await-ci"
|
||||
title = "Await CI: release.yml completion"
|
||||
needs = ["push-tag"]
|
||||
description = """
|
||||
Gate step: Wait for GitHub Actions release workflow to complete.
|
||||
|
||||
This gate blocks until the release.yml workflow run succeeds.
|
||||
The Refinery auto-discovers the workflow run triggered by v{{version}}
|
||||
and closes this gate when it completes.
|
||||
|
||||
Expected time: 5-10 minutes
|
||||
|
||||
The gate monitors:
|
||||
- Build artifacts (all platforms)
|
||||
- Test suite pass
|
||||
- npm publish
|
||||
- PyPI publish
|
||||
|
||||
If the workflow fails, this gate remains open and requires manual intervention.
|
||||
"""
|
||||
|
||||
[steps.gate]
|
||||
type = "gh:run"
|
||||
id = "release.yml"
|
||||
timeout = "30m"
|
||||
|
||||
# =============================================================================
|
||||
# Phase 2: Verification (Parallel)
|
||||
# =============================================================================
|
||||
|
||||
[[steps]]
|
||||
id = "verify-github"
|
||||
title = "Verify GitHub release"
|
||||
needs = ["await-ci"]
|
||||
description = """
|
||||
Check the GitHub releases page.
|
||||
|
||||
https://github.com/steveyegge/beads/releases/tag/v{{version}}
|
||||
|
||||
Verify:
|
||||
- Release created
|
||||
- Binaries attached (linux, darwin, windows)
|
||||
- Checksums present
|
||||
|
||||
```bash
|
||||
gh release view v{{version}} --json assets --jq '.assets[].name'
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "verify-npm"
|
||||
title = "Verify npm package"
|
||||
needs = ["await-ci"]
|
||||
description = """
|
||||
Confirm npm package published.
|
||||
|
||||
```bash
|
||||
npm show @beads/bd version
|
||||
```
|
||||
|
||||
Should show {{version}}.
|
||||
|
||||
Also check: https://www.npmjs.com/package/@beads/bd
|
||||
|
||||
Note: npm registry may have a small propagation delay (1-2 min).
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "verify-pypi"
|
||||
title = "Verify PyPI package"
|
||||
needs = ["await-ci"]
|
||||
description = """
|
||||
Confirm PyPI package published.
|
||||
|
||||
```bash
|
||||
pip index versions beads-mcp 2>/dev/null | head -3
|
||||
```
|
||||
|
||||
Or check: https://pypi.org/project/beads-mcp/
|
||||
|
||||
Should show {{version}}.
|
||||
|
||||
Note: PyPI may have a small propagation delay (1-2 min).
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# Phase 3: Installation (Fan-in)
|
||||
# =============================================================================
|
||||
|
||||
[[steps]]
|
||||
id = "local-install"
|
||||
title = "Update local installation"
|
||||
needs = ["verify-github", "verify-npm", "verify-pypi"]
|
||||
description = """
|
||||
Update local bd to the new version.
|
||||
|
||||
Option 1 - Homebrew:
|
||||
```bash
|
||||
brew upgrade bd
|
||||
```
|
||||
|
||||
Option 2 - Install script:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
bd --version
|
||||
```
|
||||
|
||||
Should show {{version}}.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "restart-daemons"
|
||||
title = "Restart daemons"
|
||||
needs = ["local-install"]
|
||||
description = """
|
||||
Restart bd daemons to pick up new version.
|
||||
|
||||
```bash
|
||||
bd daemons killall
|
||||
```
|
||||
|
||||
Daemons will auto-restart with new version on next bd command.
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
bd daemons list
|
||||
```
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "release-complete"
|
||||
title = "Release complete"
|
||||
needs = ["restart-daemons"]
|
||||
description = """
|
||||
Release v{{version}} is complete!
|
||||
|
||||
Summary:
|
||||
- All version files updated
|
||||
- Git tag pushed
|
||||
- CI artifacts built (via gate)
|
||||
- npm and PyPI packages verified
|
||||
- Local installation updated
|
||||
- Daemons restarted
|
||||
|
||||
Optional next steps:
|
||||
- Announce on social media
|
||||
- Update documentation site
|
||||
- Close related milestone
|
||||
"""
|
||||
6990
.beads/issues.jsonl
6990
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
|
||||
# Use bd merge for beads JSONL files
|
||||
.beads/issues.jsonl merge=beads
|
||||
|
||||
@@ -187,6 +187,30 @@ func importToJSONLWithStore(ctx context.Context, store storage.Storage, jsonlPat
|
||||
}
|
||||
issue.SetDefaults() // Apply defaults for omitted fields
|
||||
|
||||
// Migrate old JSONL format: auto-correct deleted status to tombstone
|
||||
// This handles JSONL files from versions that used "deleted" instead of "tombstone"
|
||||
// (GH#1223: Stuck in sync diversion loop)
|
||||
if issue.Status == types.Status("deleted") && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
}
|
||||
|
||||
// Fix: Any non-tombstone issue with deleted_at set is malformed and should be tombstone
|
||||
// This catches issues that may have been corrupted or migrated incorrectly
|
||||
if issue.Status != types.StatusTombstone && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
}
|
||||
|
||||
if issue.Status == types.StatusClosed && issue.ClosedAt == nil {
|
||||
now := time.Now()
|
||||
issue.ClosedAt = &now
|
||||
}
|
||||
|
||||
// Ensure tombstones have deleted_at set (fix for malformed data)
|
||||
if issue.Status == types.StatusTombstone && issue.DeletedAt == nil {
|
||||
now := time.Now()
|
||||
issue.DeletedAt = &now
|
||||
}
|
||||
|
||||
issues = append(issues, &issue)
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,39 @@ NOTE: Import requires direct database access and does not work with daemon mode.
|
||||
}
|
||||
issue.SetDefaults() // Apply defaults for omitted fields (beads-399)
|
||||
|
||||
// Migrate old JSONL format: auto-correct deleted status to tombstone
|
||||
// This handles JSONL files from versions that used "deleted" instead of "tombstone"
|
||||
// (GH#1223: Stuck in sync diversion loop)
|
||||
if issue.Status == types.Status("deleted") && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
if debug.Enabled() {
|
||||
debug.Logf("Auto-corrected status 'deleted' to 'tombstone' for issue %s\n", issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Fix: Any non-tombstone issue with deleted_at set is malformed and should be tombstone
|
||||
// This catches issues that may have been corrupted or migrated incorrectly
|
||||
if issue.Status != types.StatusTombstone && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
if debug.Enabled() {
|
||||
debug.Logf("Auto-corrected status %s to 'tombstone' (had deleted_at) for issue %s\n", issue.Status, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if issue.Status == types.StatusClosed && issue.ClosedAt == nil {
|
||||
now := time.Now()
|
||||
issue.ClosedAt = &now
|
||||
}
|
||||
|
||||
// Ensure tombstones have deleted_at set (fix for malformed data)
|
||||
if issue.Status == types.StatusTombstone && issue.DeletedAt == nil {
|
||||
now := time.Now()
|
||||
issue.DeletedAt = &now
|
||||
if debug.Enabled() {
|
||||
debug.Logf("Auto-added deleted_at timestamp for tombstone issue %s\n", issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
allIssues = append(allIssues, &issue)
|
||||
}
|
||||
|
||||
|
||||
@@ -344,10 +344,30 @@ func removeGitattributesEntry() error {
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var newLines []string
|
||||
skipNextEmpty := false
|
||||
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, "merge=beads") {
|
||||
newLines = append(newLines, line)
|
||||
// Skip lines containing beads merge configuration
|
||||
if strings.Contains(line, "merge=beads") {
|
||||
skipNextEmpty = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip beads-related comment lines
|
||||
if strings.Contains(line, "Use bd merge for beads JSONL files") {
|
||||
skipNextEmpty = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip empty lines that follow removed beads entries
|
||||
if skipNextEmpty && strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
skipNextEmpty = false
|
||||
|
||||
// Keep the line
|
||||
newLines = append(newLines, line)
|
||||
}
|
||||
|
||||
newContent := strings.Join(newLines, "\n")
|
||||
|
||||
@@ -217,7 +217,7 @@ func checkForMergeConflicts(jsonlData []byte, jsonlPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseJSONL(jsonlData []byte, _ Notifier) ([]*types.Issue, error) {
|
||||
func parseJSONL(jsonlData []byte, notify Notifier) ([]*types.Issue, error) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(jsonlData))
|
||||
scanner.Buffer(make([]byte, 0, 1024), 2*1024*1024)
|
||||
var allIssues []*types.Issue
|
||||
@@ -239,11 +239,39 @@ func parseJSONL(jsonlData []byte, _ Notifier) ([]*types.Issue, error) {
|
||||
return nil, fmt.Errorf("parse error at line %d: %v\nSnippet: %s", lineNo, err, snippet)
|
||||
}
|
||||
|
||||
// Migrate old JSONL format: auto-correct deleted status to tombstone
|
||||
// This handles JSONL files from versions that used "deleted" instead of "tombstone"
|
||||
// (GH#1223: Stuck in sync diversion loop)
|
||||
if issue.Status == types.Status("deleted") && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
if notify != nil {
|
||||
notify.Debugf("Auto-corrected status 'deleted' to 'tombstone' for issue %s", issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Fix: Any non-tombstone issue with deleted_at set is malformed and should be tombstone
|
||||
// This catches issues that may have been corrupted or migrated incorrectly
|
||||
if issue.Status != types.StatusTombstone && issue.DeletedAt != nil {
|
||||
issue.Status = types.StatusTombstone
|
||||
if notify != nil {
|
||||
notify.Debugf("Auto-corrected status %s to 'tombstone' (had deleted_at) for issue %s", issue.Status, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if issue.Status == types.StatusClosed && issue.ClosedAt == nil {
|
||||
now := time.Now()
|
||||
issue.ClosedAt = &now
|
||||
}
|
||||
|
||||
// Ensure tombstones have deleted_at set (fix for malformed data)
|
||||
if issue.Status == types.StatusTombstone && issue.DeletedAt == nil {
|
||||
now := time.Now()
|
||||
issue.DeletedAt = &now
|
||||
if notify != nil {
|
||||
notify.Debugf("Auto-added deleted_at timestamp for tombstone issue %s", issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
allIssues = append(allIssues, &issue)
|
||||
}
|
||||
|
||||
|
||||
@@ -466,6 +466,80 @@ not valid json`
|
||||
t.Error("Expected ClosedAt to be set for closed issue")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("migrate deleted status to tombstone (GH#1223)", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
deletedAt := now.Format(time.RFC3339)
|
||||
data := `{"id":"test-1","title":"Deleted Issue","status":"deleted","priority":1,"issue_type":"task","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z","deleted_at":"` + deletedAt + `"}`
|
||||
|
||||
notify := &testNotifier{}
|
||||
issues, err := parseJSONL([]byte(data), notify)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if len(issues) != 1 {
|
||||
t.Errorf("Expected 1 issue, got %d", len(issues))
|
||||
}
|
||||
|
||||
if issues[0].Status != types.StatusTombstone {
|
||||
t.Errorf("Expected status 'tombstone', got %s", issues[0].Status)
|
||||
}
|
||||
|
||||
if issues[0].DeletedAt == nil {
|
||||
t.Error("Expected DeletedAt to be set for migrated tombstone")
|
||||
}
|
||||
|
||||
// Check that debug message was logged
|
||||
if len(notify.debugs) == 0 {
|
||||
t.Error("Expected debug notification for status migration")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ensure tombstone has deleted_at", func(t *testing.T) {
|
||||
data := `{"id":"test-1","title":"Tombstone Without DeletedAt","status":"tombstone","priority":1,"issue_type":"task","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z"}`
|
||||
|
||||
notify := &testNotifier{}
|
||||
issues, err := parseJSONL([]byte(data), notify)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if issues[0].DeletedAt == nil {
|
||||
t.Error("Expected DeletedAt to be auto-set for tombstone without deleted_at")
|
||||
}
|
||||
|
||||
// Check that debug message was logged
|
||||
if len(notify.debugs) == 0 {
|
||||
t.Error("Expected debug notification for auto-added deleted_at")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fix corrupted closed issue with deleted_at (GH#1223)", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
deletedAt := now.Format(time.RFC3339)
|
||||
data := `{"id":"bd-6s61","title":"Version Bump","status":"closed","priority":1,"issue_type":"task","created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z","closed_at":"2024-01-02T00:00:00Z","deleted_at":"` + deletedAt + `"}`
|
||||
|
||||
notify := &testNotifier{}
|
||||
issues, err := parseJSONL([]byte(data), notify)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if len(issues) != 1 {
|
||||
t.Errorf("Expected 1 issue, got %d", len(issues))
|
||||
}
|
||||
|
||||
// Issue should be converted to tombstone
|
||||
if issues[0].Status != types.StatusTombstone {
|
||||
t.Errorf("Expected status 'tombstone' for closed issue with deleted_at, got %s", issues[0].Status)
|
||||
}
|
||||
|
||||
// Check that debug message was logged
|
||||
if len(notify.debugs) == 0 {
|
||||
t.Error("Expected debug notification for status correction")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestShowRemapping(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user