Commit Graph

285 Commits

Author SHA1 Message Date
Steve Yegge
e0872ebbd0 fix(test): remove incorrect duplicate ID rollback test
The test expected CreateIssues to error on duplicate IDs, but the
implementation uses INSERT OR IGNORE which silently skips duplicates.
This is intentional behavior needed for JSONL import scenarios.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 18:24:28 -08:00
matt wilkie
fb16e504e6 Fix tests (bd-6ss and sub-issues) (#626)
Test coverage improvements for bd-6ss. Fixing failing test assumption in follow-up commit.
2025-12-18 18:23:30 -08:00
Steve Yegge
6087cd438b fix(storage): race condition when reconnect closes db mid-query (GH#607)
Change reconnectMu from sync.Mutex to sync.RWMutex so read operations
can hold RLock during database access. This prevents reconnect() from
closing the connection while queries are in progress.

- GetIssue and SearchIssues now hold RLock during database operations
- Close() acquires write lock to coordinate with reconnect
- Add TestConcurrentReadsWithReconnect to verify the fix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:28:13 -08:00
Steve Yegge
e0d8abe8c3 Add tests for Decision 004 Phase 4 edge schema consolidation
Tests added:
- TestTransactionAddDependency_RelatesTo: bidirectional relates-to in txn
- TestTransactionAddDependency_RepliesTo: thread_id preserved in txn
- TestRelateCommand: bd relate/unrelate CLI commands
- TestRelateCommandInit: command registration

Provides regression coverage for transaction.go fixes and relates-to behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:25:17 -08:00
Steve Yegge
4981e84e86 Fix transaction.go AddDependency to match dependencies.go (Decision 004)
Code review found three issues where transaction.go diverged from the
main dependencies.go implementation:

1. Add relates-to exemption from cycle detection - bidirectional
   relationships are valid and should not trigger cycle errors

2. Add metadata and thread_id fields to INSERT - required for
   replies-to threading to work in transaction context

3. Update error message to match dependencies.go wording

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:15:16 -08:00
Steve Yegge
ee3d51fc21 Merge pull request #624 from marcodelpin/fix/schema-probe-dependencies-columns
fix(schema): add metadata and thread_id to dependencies expectedSchema
2025-12-18 11:15:04 -08:00
Marco Del Pin
973ecfc9e6 fix(lint): resolve golangci-lint errors for clean CI
Fixes 5 linting issues to allow PR checks to pass:
1. hooks.go: Explicitly ignore error in async goroutine
2. 022_drop_edge_columns.go: Handle deferred PRAGMA error
3. flags.go: Add nosec comment for validated file path
4. create_form.go: Fix American spelling (canceled vs cancelled)
5. create_form.go: Explicitly mark cmd parameter as required by cobra
These are pre-existing issues in the codebase, fixed here to
enable clean CI for the import deduplication fix.
🤖 Generated with Claude Code
2025-12-18 20:06:41 +01:00
Marco Del Pin
ef99f0700c fix(lint): resolve golangci-lint errors for clean CI
Fixes 5 linting issues to allow PR checks to pass:
1. hooks.go: Explicitly ignore error in async goroutine
2. 022_drop_edge_columns.go: Handle deferred PRAGMA error
3. flags.go: Add nosec comment for validated file path
4. create_form.go: Fix American spelling (canceled vs cancelled)
5. create_form.go: Explicitly mark cmd parameter as required by cobra
These are pre-existing issues in the codebase, fixed here to
enable clean CI for the schema probe fix.
🤖 Generated with Claude Code
2025-12-18 20:05:13 +01:00
Marco Del Pin
d5e569443d fix(schema): add metadata and thread_id to dependencies expectedSchema
**Problem:**
Schema compatibility probe was failing with "no such column: thread_id"
error when opening databases created before v0.30.5, even after running
migrations. This caused database initialization to fail.
**Root Cause:**
The migration 020_edge_consolidation.go correctly adds the metadata and
thread_id columns to the dependencies table, but schema_probe.go's
expectedSchema map was missing these columns in its validation list.
This caused verifySchemaCompatibility() to fail even though:
1. The columns existed in the actual schema (schema.go:51-52)
2. Migration 020 would add them if missing
3. The database was otherwise valid
**Solution:**
Updated expectedSchema in schema_probe.go to include "metadata" and
"thread_id" in the dependencies table column list. This aligns the schema
probe expectations with the actual schema definition and migration 020
behavior.
**Testing:**
 Tested on 5 databases across version range pre-0.17.5 → v0.30.5
   - All migrations completed successfully
   - Schema probe passes after migration
   - All bd commands work correctly
 No regressions in fresh database initialization
**Impact:**
- Fixes database initialization errors for users upgrading from v0.30.3
- No breaking changes or data migrations required
- Compatible with all existing databases
- Enables smooth migration path for all database versions
Fixes database migration issues reported in v0.30.5 upgrade path.
🤖 Generated with Claude Code
2025-12-18 19:32:26 +01:00
Marco Del Pin
dba9bb91c3 fix(import): handle duplicate issue IDs in JSONL files gracefully
Implements three-layer deduplication strategy to prevent UNIQUE
constraint errors during import:
1. Early deduplication during processing (importer.go)
2. Pre-batch deduplication (importer.go)
3. INSERT OR IGNORE with explicit error handling (issues.go)
**Problem:**
JSONL files with duplicate issue IDs caused import failures:
  Import failed: UNIQUE constraint failed: issues.id
**Root Cause:**
- Go SQLite driver returns errors even with INSERT OR IGNORE
- Only content hash was deduplicated, not IDs
- Multiple code paths affected (insertIssue, insertIssues)
**Solution:**
Layer 1: Early deduplication by ID in upsertIssues (lines 489-502)
Layer 2: Pre-batch deduplication (lines 713-726)
Layer 3: INSERT OR IGNORE + isUniqueConstraintError() helper
**Testing:**
- Multiple production databases tested
- 9 duplicates handled successfully
- 100% success rate on v0.30.5 databases
- Zero UNIQUE constraint errors
**Impact:**
- Enables importing JSONL with duplicate IDs
- Duplicate count shown in import statistics
- No breaking changes, backward compatible
🤖 Generated with Claude Code
2025-12-18 19:26:29 +01:00
Steve Yegge
7c8b69f5b3 Phase 4: Remove deprecated edge fields from Issue struct (Decision 004)
This is the final phase of the Edge Schema Consolidation. It removes
the deprecated edge fields (RepliesTo, RelatesTo, DuplicateOf, SupersededBy)
from the Issue struct and all related code.

Changes:
- Remove edge fields from types.Issue struct
- Remove edge field scanning from queries.go and transaction.go
- Update graph_links_test.go to use dependency API exclusively
- Update relate.go to use AddDependency/RemoveDependency
- Update show.go with helper functions for thread traversal via deps
- Update mail_test.go to verify thread links via dependencies
- Add migration 022 to drop columns from issues table
- Fix cycle detection to allow bidirectional relates-to links
- Fix migration 022 to disable foreign keys before table recreation

All edge relationships now use the dependencies table exclusively.
The old Issue fields are fully removed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 02:48:13 -08:00
Steve Yegge
5d7187f29b Phase 3: Migrate existing edge fields to dependencies (Decision 004)
Add migration to convert existing issue fields to dependency edges:
- replies_to -> replies-to dependency with thread_id
- relates_to -> relates-to dependencies (JSON array)
- duplicate_of -> duplicates dependency
- superseded_by -> supersedes dependency

The migration is idempotent (INSERT OR IGNORE) so it does not duplicate
edges that were already created by Phase 2 dual-write.

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 01:22:34 -08:00
Steve Yegge
1d40b8f970 Phase 2: Dual-write for edge schema consolidation (Decision 004)
When issue fields (replies_to, relates_to, duplicate_of, superseded_by)
are set during CreateIssue or UpdateIssue, we now ALSO create the
corresponding dependency edges. This enables gradual migration to
edge-based storage while maintaining backward compatibility.

Changes:
- createGraphEdgesFromIssueFields: handles CreateIssue dual-write
- createGraphEdgesFromUpdates: handles UpdateIssue dual-write
- Replies-to edges include thread_id for efficient thread queries
- Uses INSERT OR IGNORE to handle idempotency

The dual-write ensures both field and dependency stay in sync during
the migration period.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 01:21:15 -08:00
Steve Yegge
d390aa8834 Phase 1: Edge schema consolidation infrastructure (Decision 004)
Add metadata and thread_id columns to dependencies table to support:
- Edge metadata: JSON blob for type-specific data (similarity scores, etc.)
- Thread queries: O(1) conversation threading via thread_id

Changes:
- New migration 020_edge_consolidation.go
- Updated Dependency struct with Metadata and ThreadID fields
- Added new entity types: authored-by, assigned-to, approved-by
- Relaxed DependencyType validation (any non-empty string ≤50 chars)
- Added IsWellKnown() and AffectsReadyWork() methods
- Updated SQL queries to include new columns
- Updated tests for new behavior

This enables HOP knowledge graph requirements and Reddit-style threading.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 01:17:57 -08:00
Steve Yegge
003a7d98db fix: Exclude closed issues from orphan detection warnings
No point warning about orphaned issues that are already closed or
tombstoned - they're dead either way.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:32:37 -08:00
Steve Yegge
1e97d9ccee fix: Exclude tombstones from orphan detection warnings
Orphan detection was reporting tombstoned issues (already deleted) as
orphans, causing repeated warnings during sync. Filter out status='tombstone'
from the orphan query.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:20:21 -08:00
Steve Yegge
5869adadf2 fix: Remove unsafe ClearDirtyIssues() method (bd-b6xo)
Remove ClearDirtyIssues() which had a race condition that could lose
dirty issues if export failed partway through. All callers now use
ClearDirtyIssuesByID() which only clears specific exported issues.

- Remove from Storage interface
- Remove from SQLite and Memory implementations
- Update 6 test call sites to use ClearDirtyIssuesByID()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 19:10:13 -08:00
Steve Yegge
8d73a86f7a feat: complete bd-kwro messaging & knowledge graph epic
- Add bd cleanup --ephemeral flag for transient message cleanup (bd-kwro.9)
- Add Ephemeral filter to IssueFilter type
- Add ephemeral filtering to SQLite storage queries

Tests (bd-kwro.10):
- Add internal/hooks/hooks_test.go for hook system
- Add cmd/bd/mail_test.go for mail commands
- Add internal/storage/sqlite/graph_links_test.go for graph links

Documentation (bd-kwro.11):
- Add docs/messaging.md for full messaging reference
- Add docs/graph-links.md for graph link types
- Update AGENTS.md with inter-agent messaging section
- Update CHANGELOG.md with all bd-kwro features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 20:36:47 -08:00
Alessandro De Blasis
1ba12e1620 fix: Windows build + gosec lint errors (#585)
Fixes Windows build by adding platform-specific inode handling via build tags. Also fixes gosec lint warnings in migrate_tombstones.go.

Thanks @deblasis!
2025-12-16 13:26:51 -08:00
Steve Yegge
aed166fe85 fix: improve issue ID prefix extraction for word-like suffixes
Refine ExtractIssuePrefix to better distinguish hash IDs from English
words in multi-part issue IDs. Hash suffixes now require digits or be
exactly 3 chars, preventing "test", "gate", "part" from being treated
as hashes. This fixes prefix extraction for IDs like "vc-baseline-test".

Also updates git hooks to use -q flag and adds AGENTS.md documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 13:19:22 -08:00
Steve Yegge
0aea2d93c6 feat(schema): add messaging fields for bd-kwro epic
- Add TypeMessage issue type for inter-agent communication
- Add 6 new Issue fields: Sender, Ephemeral, RepliesTo, RelatesTo,
  DuplicateOf, SupersededBy
- Add 4 new dependency types: replies-to, relates-to, duplicates, supersedes
- Create migration 019_messaging_fields with indexes
- Update all CRUD operations across storage layer
- Fix reset_test.go to use correct function names
- Fix redundant newline lint error in sync.go

Closes: bd-kwro.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 13:06:47 -08:00
Steve Yegge
c4e122a888 Merge bd-dvw8-rictus: GH#505 reset 2025-12-16 01:18:38 -08:00
Steve Yegge
11c3bb4fdb Merge bd-0yzm-ace: GH#522 --type flag for bd update 2025-12-16 01:15:11 -08:00
Steve Yegge
39e09449cc fix: orphan detection false positive with dots in directory name
Only treat issue IDs as hierarchical (parent.child) when the dot appears
AFTER the first hyphen. This prevents false positives when the project
directory name contains a dot (e.g., "my.project-abc123" was incorrectly
being treated as having parent "my").

Fixes GH#508

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 01:06:14 -08:00
Alessandro De Blasis
78c248a17a fix(daemon): detect external db file replacement
When git merge replaces the .beads/beads.db file, the daemon's
SQLite connection becomes stale (still reading deleted inode).
This adds FreshnessChecker that detects file replacement via
inode/mtime comparison and triggers automatic reconnection.

Implementation:
- freshness.go: monitors db file for replacement
- store.go: adds EnableFreshnessChecking() and reconnect()
- queries.go: calls checkFreshness() on GetIssue/SearchIssues
- daemon.go: enables freshness checking at startup
- freshness_test.go: comprehensive tests including merge scenario

Code quality (per review):
- Extract configureConnectionPool() helper to reduce duplication
- Handle Close() error in reconnect() (log but continue)
- Use t.Cleanup() pattern in tests per project conventions
- Rename setupFreshnessTest() per naming conventions

Overhead: ~2.6μs per read op (~0.8% of total query time)

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 06:29:59 +01:00
Steve Yegge
8e58c306a7 fix(sync,blocked): add safety guards for issue deletion and include status=blocked in bd blocked
GH#464: Add safety guards to prevent deletion of open/in_progress issues during sync:
- Safety guard in git-history-backfill (importer.go)
- Safety guard in deletions manifest processing
- Warning when uncommitted changes detected before pull (daemon_sync.go)
- Enhanced repo ID mismatch error message

GH#545: Fix bd blocked to show status=blocked issues (sqlite/ready.go):
- Changed from INNER JOIN to LEFT JOIN to include issues without dependencies
- Added WHERE clause to include both status=blocked AND dependency-blocked issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 23:07:54 -08:00
Steve Yegge
fb20e43f5f fix(orphan): handle prefixes with dots in orphan detection (GH#508)
The orphan detection was incorrectly flagging issues with dots in their
prefix (e.g., "my.project-abc123") as orphans because it was looking for
any dot in the ID, treating everything before the first dot as the
parent ID.

The fix:
- Add IsHierarchicalID() helper that correctly detects hierarchical IDs
  by checking if the ID ends with .{digits} (e.g., "bd-abc.1")
- Update SQL query in orphan detection migration to use GLOB patterns
  that only match IDs ending with numeric suffixes
- Update all Go code that checks for hierarchical IDs to use the new
  helper function

Test cases added:
- Unit tests for IsHierarchicalID covering normal, dotted prefix, and
  edge cases
- Integration test verifying dotted prefixes do not trigger false
  positives

Fixes: #508
2025-12-14 17:23:46 -08:00
Steve Yegge
c7e119a1cc fix(import): defensive handling for closed issues missing closed_at timestamp
Fixes GH#523: older versions of bd could close issues without setting
the closed_at timestamp. When importing such issues, validation would
fail with "closed issues must have closed_at timestamp".

This fix adds defensive handling in all issue creation/validation paths:
- If status is "closed" and closed_at is nil, set closed_at to
  max(created_at, updated_at) + 1 second
- Similarly for tombstones missing deleted_at

Applied to:
- batch_ops.go: validateBatchIssuesWithCustomStatuses (main import path)
- transaction.go: CreateIssue and CreateIssues
- queries.go: CreateIssue
- multirepo.go: upsertIssueInTx

Also adds comprehensive tests for the defensive fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 17:21:44 -08:00
Steve Yegge
a60972cd6a Merge remote-tracking branch 'origin/main' into performance-fix 2025-12-14 14:53:30 -08:00
cbro
2651620a4c fix(storage): persist close_reason to issues table on close (#551)
CloseIssue was storing the reason only in the events table, not in the
issues.close_reason column. This caused `bd show --json` to return an
empty close_reason even when one was provided.

- Update CloseIssue in queries.go and transaction.go to set close_reason
- Clear close_reason when reopening issues (in manageClosedAt)
- Add tests for close_reason in storage and CLI JSON output
- Document the dual-storage of close_reason (issues + events tables)
2025-12-14 14:18:01 -08:00
Ryan Snodgrass
f88a0d015b feat(cli): add 'bd thanks' command to thank contributors
Adds a new command that displays a thank you page listing all human
contributors to the beads project. Features:

- Static list of contributors (compiled into binary)
- Top 20 featured contributors displayed in columns
- Additional contributors in wrapped list
- Styled output using lipgloss (colored box, sections)
- Dynamic width based on content
- JSON output support (--json flag)
- Excludes bots and AI agents by email pattern
2025-12-14 12:40:32 -08:00
Steve Yegge
a61ca252ae fix(cleanup): resolve CHECK constraint failure and add tombstone pruning
- Fix bd-tnsq: executeDelete now sets closed_at=NULL when creating
  tombstones, satisfying the CHECK constraint that requires
  closed_at IS NULL when status != 'closed'

- Fix bd-08ea: cleanup command now also prunes expired tombstones
  (older than 30 days) after converting closed issues to tombstones

- Add regression test for batch deletion of closed issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 00:37:54 -08:00
Steve Yegge
fc23dca7fb feat(cli): add --lock-timeout flag for SQLite busy_timeout control (#536)
Implements single-shot mode improvements for Windows and Docker scenarios:

- Add --lock-timeout global flag (default 30s, 0 = fail immediately)
- Add config file support: lock-timeout: 100ms
- Parameterize SQLite busy_timeout via NewWithTimeout() function
- In --sandbox mode: default lock-timeout to 100ms
- In --sandbox mode: skip FlushManager creation (no background goroutines)

This addresses bd.exe hanging on Windows and locking conflicts when
using beads across host + Docker containers.

Closes: bd-59er, bd-r4od, bd-dh8a

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 18:07:49 -08:00
matt wilkie
e01b7412d9 feat: add Git worktree compatibility (PR #478)
Adds comprehensive Git worktree support for beads issue tracking:

Core changes:
- New internal/git/gitdir.go package for worktree detection
- GetGitDir() returns proper .git location (main repo, not worktree)
- Updated all hooks to use git.GetGitDir() instead of local helper
- BeadsDir() now prioritizes main repository's .beads directory

Features:
- Hooks auto-install in main repo when run from worktree
- Shared .beads directory across all worktrees
- Config option no-install-hooks to disable auto-install
- New bd worktree subcommand for diagnostics

Documentation:
- New docs/WORKTREES.md with setup instructions
- Updated CHANGELOG.md and AGENT_INSTRUCTIONS.md

Testing:
- Updated tests to use exported git.GetGitDir()
- Added worktree detection tests

Co-authored-by: Claude <noreply@anthropic.com>
Closes: #478
2025-12-13 12:50:33 -08:00
Steve Yegge
2c6748fd59 fix(tombstone): clear closed_at when converting closed issue to tombstone
When CreateTombstone was called on a closed issue, the CHECK constraint
(status = closed) = (closed_at IS NOT NULL) was violated because
closed_at was not cleared. Now setting closed_at = NULL in the UPDATE.

Added regression test for creating tombstone from closed issue.

Fixes: bd-fi05

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 07:31:16 -08:00
Steve Yegge
18b1eb2e07 fix(sqlite): handle deleted_at TEXT column scanning properly
The deleted_at column was defined as TEXT in the schema but code was
trying to scan into sql.NullTime. The ncruces/go-sqlite3 driver only
auto-converts TEXT to time.Time for columns declared as DATETIME/DATE/
TIME/TIMESTAMP. For TEXT columns, it returns raw strings which
sql.NullTime.Scan() cannot handle.

Added parseNullableTimeString() helper that manually parses time strings
and changed all deletedAt variables from sql.NullTime to sql.NullString.

Fixes import failure: "sql: Scan error on column index 22, name
deleted_at: unsupported Scan, storing driver.Value type string into
type *time.Time"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 07:13:40 -08:00
Ryan
335887e000 perf: fix stale startlock delay and add comprehensive benchmarks (#484)
* fix(daemon): check for stale startlock before waiting 5 seconds

When a previous daemon startup left behind a bd.sock.startlock file
(e.g., from a crashed process), the code was waiting 5 seconds before
checking if the lock was stale. This caused unnecessary delays on
every bd command when the daemon wasn't running.

Now checks if the PID in the startlock file is alive BEFORE waiting.
If the PID is dead or unreadable, the stale lock is cleaned up
immediately and lock acquisition is retried.

Fixes ~5s delay when startlock file exists from crashed process.

* perf: add benchmarks for large descriptions, bulk operations, and sync merge

Added three new performance benchmarks to identify bottlenecks in common operations:

1. BenchmarkLargeDescription - Tests handling of 100KB+ issue descriptions
   - Measures string allocation/parsing overhead
   - Result: 3.3ms/op, 874KB/op allocation

2. BenchmarkBulkCloseIssues - Tests closing 100 issues sequentially
   - Measures batch write performance
   - Result: 1.9s total, shows write amplification

3. BenchmarkSyncMerge - Tests JSONL merge cycle with creates/updates
   - Simulates real sync operations (10 creates + 10 updates per iteration)
   - Result: 29ms/op, identifies sync bottlenecks

Added BENCHMARKS.md documentation describing:
- How to run benchmarks with various options
- All available benchmark categories
- Performance targets on M2 Pro hardware
- Dataset caching strategy
- CPU profiling integration
- Optimization workflow

This completes performance testing coverage for previously unmeasured scenarios.

* docs: clarify daemon lock acquisition logic in comments

Improve comments to clarify that acquireStartLock does both:
1. Immediately check for stale locks from crashed processes (avoids 5s delay)
2. If PID is alive, properly wait for legitimate daemon startup (5s timeout)

No code changes - only clarified comment documentation for maintainability.

---------

Co-authored-by: Steve Yegge <steve.yegge@gmail.com>
2025-12-13 06:57:11 -08:00
Steve Yegge
2fd1d1fb87 fix: resolve golangci-lint warnings
- Handle ignored errors with explicit _ assignment (errcheck)
- Add #nosec comments for false positive G304/G204 warnings (gosec)
- Fix misspelling: cancelled -> canceled

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 10:07:36 +11:00
Steve Yegge
f4864c9cc4 feat(import/export): add tombstone support (bd-dve)
Update import/export to handle tombstones for deletion sync propagation:

Exporter:
- Include tombstones in JSONL output by setting IncludeTombstones: true
- Both single-repo and multi-repo exports now include tombstones

Importer:
- Tombstones from JSONL are imported as-is (they're issues with status=tombstone)
- Legacy deletions.jsonl entries are converted to tombstones via convertDeletionToTombstone()
- Non-tombstone issues in deletions manifest are still skipped (backward compat)
- purgeDeletedIssues() now creates tombstones instead of hard-deleting

This is Phase 2 of the tombstone implementation (bd-dli design), enabling
inline soft-delete tracking for cross-clone deletion synchronization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 20:34:02 +11:00
Steve Yegge
2adba0d8e0 feat(tombstone): implement delete-to-tombstone and TTL expiration (bd-3b4, bd-olt)
Phase 1 of tombstone migration: bd delete now creates tombstones instead
of hard-deleting issues.

Key changes:
- Add CreateTombstone() method to SQLiteStorage for soft-delete
- Modify executeDelete() to create tombstones instead of removing rows
- Add IsExpired() method with 30-day default TTL and clock skew grace
- Fix deleted_at schema from TEXT to DATETIME for proper time scanning
- Update delete.go to call CreateTombstone (single issue path)
- Still writes to deletions.jsonl for backward compatibility (dual-write)
- Dependencies are removed when creating tombstones
- Tombstones are excluded from normal searches (bd-1bu)

TTL constants:
- DefaultTombstoneTTL: 30 days
- MinTombstoneTTL: 7 days (safety floor)
- ClockSkewGrace: 1 hour

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 16:20:43 -08:00
Steve Yegge
5c49c25e9e feat(tombstone): add P2 code review improvements (bd-saa, bd-1bu, bd-nyt)
- Add partial index on deleted_at for efficient TTL queries
- Exclude tombstones from SearchIssues by default (new IncludeTombstones filter)
- Report tombstone count separately in GetStatistics
- Display tombstone count in bd stats output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 15:48:46 -08:00
Steve Yegge
b689ea1b28 fix(types): add tombstone validation and block direct status update (bd-md2, bd-y68)
- Add validation in ValidateWithCustomStatuses() requiring deleted_at for tombstones
- Add validation that non-tombstones cannot have deleted_at set
- Block direct status update to tombstone in validateStatusWithCustom()
- Users must use 'bd delete' instead of 'bd update --status=tombstone'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 15:41:35 -08:00
Steve Yegge
08e43d9fc7 feat(types): add tombstone support for inline soft-delete (bd-fbj)
Add tombstone types and schema migration as foundation for the tombstone
epic (bd-vw8) which replaces deletions.jsonl with inline tombstones.

Changes:
- Add tombstone fields to Issue struct: DeletedAt, DeletedBy, DeleteReason, OriginalType
- Add StatusTombstone constant and IsTombstone() helper method
- Update Status.IsValid() to accept tombstone status
- Create migration 018_tombstone_columns.go for new database columns
- Update schema.go with tombstone columns: deleted_at, deleted_by, delete_reason, original_type
- Update all issue insert/update/scan operations across:
  - issues.go (insertIssue, insertIssues)
  - queries.go (GetIssue, GetIssueByExternalRef, SearchIssues)
  - dependencies.go (scanIssues, scanIssuesWithDependencyType)
  - transaction.go (scanIssueRow, GetIssue, SearchIssues)
  - multirepo.go (import operations)
  - ready.go (GetReadyWork, GetStaleIssues)
  - labels.go (GetIssuesByLabel)
- Add test for IsTombstone() helper
- Update migration test to include tombstone columns

Unblocks: bd-olt (TTL logic), bd-3b4 (delete command), bd-0ih (merge updates), bd-dve (import/export)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 15:29:42 -08:00
Darin
e0734e230f fix: NixOS symlink compatibility for mtime and permission checks (#459)
* fix: use os.Lstat for symlink-safe mtime and permission checks

On NixOS and other systems using symlinks heavily (e.g., home-manager),
os.Stat follows symlinks and returns the target's metadata. This causes:

1. False staleness detection when JSONL is symlinked - mtime of target
   changes unpredictably when symlinks are recreated
2. os.Chmod failing or changing wrong file's permissions when target
   is in read-only location (e.g., /nix/store)
3. os.Chtimes modifying target's times instead of the symlink itself

Changes:
- autoimport.go: Use Lstat for JSONL mtime in CheckStaleness()
- import.go: Use Lstat in TouchDatabaseFile() for JSONL mtime
- export.go: Skip chmod for symlinked files
- multirepo.go: Use Lstat for JSONL mtime cache
- multirepo_export.go: Use Lstat for mtime, skip chmod for symlinks
- doctor/fix/permissions.go: Skip permission fixes for symlinked paths

These changes are safe cross-platform:
- On systems without symlinks, Lstat behaves identically to Stat
- Symlink permission bits are ignored on Unix anyway
- The extra Lstat syscall overhead is negligible

Fixes symlink-related data loss on NixOS. See GitHub issue #379.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add symlink behavior tests for NixOS compatibility

Add tests that verify symlink handling behavior:
- TestCheckStaleness_SymlinkedJSONL: verifies mtime detection uses
  symlink's own mtime (os.Lstat), not target's mtime (os.Stat)
- TestPermissions_SkipsSymlinkedBeadsDir: verifies chmod is skipped
  for symlinked .beads directories
- TestPermissions_SkipsSymlinkedDatabase: verifies chmod is skipped
  for symlinked database files while still fixing .beads dir perms

Also adds devShell to flake.nix for local development with go, gopls,
golangci-lint, and sqlite tools.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 13:22:09 -08:00
Steve Yegge
cd7830a5a6 fix: replace string(rune()) with strconv.Itoa in tests (bd-fmc)
Also updated CONFIG.md to clarify mass delete threshold requires >5 issues (bd-in6).

The string(rune('0'+i)) pattern produces incorrect characters when i >= 10.
Changed to strconv.Itoa(i) for reliable conversion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 22:41:07 -08:00
Steve Yegge
a4260c9660 fix(db): remove redundant GetCloseReason() calls (bd-bbh)
After adding close_reason column to issues table, two functions still
called GetCloseReason() to fetch from events table after already
scanning the column. Removed the redundant code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:06:08 -08:00
Steve Yegge
09fffa4eaf fix(db): add close_reason column to issues table (bd-uyu)
- Add migration 017_close_reason_column.go to create the column
- Update all INSERT statements to include close_reason
- Update all SELECT statements to include close_reason
- Update doctor.go to check for close_reason in schema validation
- Remove workaround code that batch-loaded close reasons from events table
- Fix migrations_test.go to include close_reason in test table schema

This fixes sync loops where close_reason values were silently dropped
because the DB lacked the column despite the struct having the field.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 21:56:41 -08:00
Johannes Zillmann
b267395d8c fix: checkpoint WAL on Close() to persist writes
SQLite WAL mode writes go to the -wal file, not the main database.
Without an explicit checkpoint before Close(), writes can be stranded
and lost between CLI invocations.

This was causing `bd migrate` to report success but not actually
persist the version update to the database.

Fixes #434

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 17:28:02 -06:00
Steve Yegge
64771a28d2 feat(ready): add --unassigned filter for bd ready
Add -u/--unassigned flag to bd ready command to show only issues
with no assignee. This supports the SCAVENGE protocol where polecats
query the 'Salvage Yard' for unassigned ready work.

Changes:
- Add NoAssignee field to WorkFilter struct
- Update SQLite GetReadyWork to filter by empty/null assignee
- Add --unassigned/-u flag to ready command
- Update RPC protocol and daemon handler
- Add test for NoAssignee filter functionality

Fixes: gt-3rp

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 00:38:57 -08:00
Steve Yegge
53342732b5 feat: add --unassigned flag to bd ready command
Adds the ability to filter ready work for issues with no assignee,
which is useful for the SCAVENGE protocol in Gas Town where polecats
need to query the "Salvage Yard" for unclaimed work.

Changes:
- Add Unassigned bool field to types.WorkFilter
- Add --unassigned/-u flag to bd ready command
- Update SQL query in GetReadyWork to filter for NULL/empty assignee
- Add Unassigned field to RPC ReadyArgs for daemon support
- Add tests for the new functionality

Closes: gt-3rp

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 00:37:12 -08:00