Add cross-repo issue references (future enhancement)

Support referencing issues across different beads repositories. Useful for tracking dependencies between separate projects.

Proposed syntax:
- Local reference: bd-28 (current behavior)
- Cross-repo by path: ~/src/other-project#bd-456
- Cross-repo by workspace name: @project2:bd-789

Use cases:
1. Frontend project depends on backend API issue
2. Shared library changes blocking multiple projects
3. System administrator tracking work across machines
4. Monorepo with separate beads databases per component

Implementation challenges:
- Storage layer needs to query external databases
- Dependency resolution across repos
- What if external repo not available?
- How to handle in JSONL export/import?
- Security: should repos be able to read others?

Design questions to resolve first:
1. Read-only references vs full cross-repo dependencies?
2. How to handle repo renames/moves?
3. Absolute paths vs workspace names vs git remotes?
4. Should bd-27 auto-discover related repos?

Recommendation:
- Gather user feedback first
- Start with read-only references
- Implement as plugin/extension?

Context: This is mentioned in bd-27 as approach #2. Much more complex than daemon multi-repo approach. Only implement if there's strong user demand.

Priority: Backlog (4) - wait for user feedback before designing


Add rule-based compaction (e.g., compact children of closed epics)

Support semantic compaction rules beyond just time-based, such as:
- Compact all children of closed epics
- Compact by priority level (e.g., all P3/P4 closed issues)
- Compact by label (e.g., all issues labeled 'archive')
- Compact by type (e.g., all closed chores)

This would allow smarter database size management based on semantic meaning rather than just age.


Add automated duplicate detection tool for post-import cleanup

After collision resolution with --resolve-collisions, we can end up with content duplicates (different IDs, identical content) when parallel work creates the same issues.

## Problem
Current situation:
- `deduplicateIncomingIssues()` only deduplicates within the import batch
- Doesn't detect duplicates between DB and incoming issues
- Doesn't detect duplicates across the entire database post-import
- Manual detection: `bd list --json | jq 'group_by(.title) | map(select(length > 1))'`

## Real Example
After import with collision resolution, we had 7 duplicate pairs:
--196/bd-67: Same external_ref epic
--196-196: Same findByExternalRef task
--196-196: Same collision detection task
--196-196: Same import flow task
--196-196: Same test writing task
--196-196: Same documentation task
--196-196: Same code review task

## Proposed Solution
Add `bd duplicates` command that:
1. Groups all issues by content hash (title + description + design + acceptance_criteria)
2. Reports duplicate groups with suggested merge target (lowest ID or most references)
3. Optionally auto-merge with `--auto-merge` flag
4. Respects status (don't merge open with closed)

## Example Output
```
🔍 Found 7 duplicate groups:

Group 1: "Feature: Use external_ref as primary matching key"
  --196 (open, P1, 0 references)
  - bd-67 (open, P1, 0 references)
  Suggested merge: bd merge-196 --into bd-67

Group 2: "Add findByExternalRef query"
  --196 (open, P1, 0 references)
  --196 (open, P1, 0 references)
  Suggested merge: bd merge-196 --into-196
...

Run with --auto-merge to execute all suggested merges
```

## Implementation Notes
- Use same content hashing as deduplicateIncomingIssues
- Consider reference counts when choosing merge target
- Skip duplicates with different status (open vs closed)
- Add --dry-run mode
- Integration with import: `bd import --resolve-collisions --dedupe-after`


Remove unused issueMap in scoreCollisions

scoreCollisions() creates issueMap and populates it (lines 135-138) but never uses it. Either remove it or add a TODO comment explaining future use. Located in collision.go:135-138. Cosmetic cleanup.


Make merge command idempotent for safe retry after partial failures

The merge command currently performs 3 operations without an outer transaction:
1. Migrate dependencies from source → target
2. Update text references across all issues
3. Close source issues

If merge fails mid-operation (network issue, daemon crash, etc.), a retry will fail or produce incorrect results because some operations already succeeded.

**Goal:** Make merge idempotent so retrying after partial failure is safe and completes the remaining work.

**Idempotency checks needed:**
- Skip dependency migration if target already has the dependency
- Skip text reference updates if already updated
- Skip closing source issues if already closed
- Report which operations were skipped vs performed

**Example output:**
```
✓ Merged 2 issue(s) into bd-48
  - Dependencies: 3 migrated, 2 already existed
  - Text references: 5 updated, 0 already correct
  - Source issues: 1 closed, 1 already closed
```

**Related:** bd-1 originally requested transaction support, but idempotency is a better solution for this use case since individual operations are already atomic.

Current merge code already has some idempotency:
- Dependency migration checks `alreadyExists` before adding (line ~145-151 in merge.go)
- Text reference updates are naturally idempotent (replacing bd-X with bd-Y twice has same result)

Missing idempotency:
- CloseIssue fails if source already closed
- Error messages don't distinguish "already done" from "real failure"

Implementation:
1. Check source issue status before closing - skip if already closed
2. Track which operations succeeded/skipped
3. Return detailed results for user visibility
4. Consider adding --dry-run output showing what would be done vs skipped


Daemon storage cache doesn't detect external database modifications

When bd commands bypass the daemon and directly modify the database (e.g., `bd import` with direct file access, or deleting/recreating bd.db), the daemon's cached storage connection becomes stale and serves outdated data.

**Reproduction**:
1. Start daemon: `bd daemon`
2. Run bd stats → shows N issues
3. Delete database: `rm .beads/bd.db`
4. Reinit and import: `bd init && bd import -i .beads/issues.jsonl`
5. Run bd stats → shows 0 issues (wrong!)
6. Direct query: `sqlite3 .beads/bd.db 'SELECT COUNT(*) FROM issues'` → shows correct count
7. Restart daemon: `bd daemon --stop` then retry stats → now shows correct count

**Root cause**:
- server.go:1410-1414 retrieves cached storage without checking if DB file changed
- Cache only evicts based on TTL (30min) or LRU, never on external modifications
- Direct file operations bypass daemon, leaving cache stale

**Impact**:
- Users see incorrect/stale data after external DB operations
- Confusing behavior with no clear indication cache is stale
- Requires daemon restart to fix

**Proposed fixes**:
1. Check mtime on cache hit, invalidate if file changed
2. Add cache eviction API (bd cache --clear)
3. Use file locking to prevent external modifications while daemon running
4. SQLite WAL mode change notifications

**Better approach: Check DB file mtime on cache lookup**

Too many commands bypass the daemon (import, init, renumber, compact, delete, dep tree, export, stale). Notifying from each would be error-prone and easy to forget when adding new commands.

**Implementation:**

1. Add `dbMtime time.Time` field to `StorageCacheEntry`
2. In `getStorageForRequest()` on cache hit:
   - Stat the DB file to get current mtime
   - If mtime changed since cached, evict entry and reopen
   - Otherwise return cached connection
3. Store mtime when initially caching

**Code location:**
- `internal/rpc/server.go:1410-1414` (cache hit path)
- `internal/rpc/server.go:49-52` (StorageCacheEntry struct)

**Benefits:**
- Simple, centralized check
- Works for all commands that bypass daemon
- Works for external tools modifying DB
- No need to update every command
- Minimal performance overhead (one stat() call on cache hit)

**Trade-offs:**
- Small overhead on every cache hit (negligible - stat is fast)
- mtime granularity may miss rapid changes (unlikely in practice)


Implement bd quickstart command

Add bd quickstart command to show context-aware repo information: recent issues, database location, configured prefix, example queries. Helps AI agents understand current project state. Companion to bd onboard.

After review, we already have good context tools: bd stats, bd list, bd ready, and the AGENTS.md onboarding section. Adding bd quickstart would be redundant and doesn't add enough value to justify maintenance cost. Closing as won't implement.


Global daemon should warn/reject --auto-commit and --auto-push

When user runs 'bd daemon --global --auto-commit', it's unclear which repo the daemon will commit to (especially after fixing bd-27 where global daemon won't open a DB).

Options:
1. Warn and ignore the flags in global mode
2. Error out with clear message

Line 87-91 already checks autoPush, but should skip check entirely for global mode. Add user-friendly messaging about flag incompatibility.


Convert repeated strings to constants (goconst)

12 instances of repeated strings that should be constants: "alice", "windows", "bd-2", "daemon", "import", "healthy", "unhealthy", "1.0.0", "custom-1", "custom-2"

Create package-level or test-level constants for frequently used test strings and command names.


Auto-flush writes test pollution and session work to git-tracked issues.jsonl

Auto-flush exports ALL issues from DB to issues.jsonl every 5 seconds, including:
- Test issues (bd-4053 through bd-4059 were version test junk)
- Issues created during debugging sessions
- Test pollution from stress tests
- Temporary diagnostic issues

This pollutes the git-tracked issues.jsonl with garbage that shouldn't be committed.

Example from today:
- Git had 49 clean issues
- Our DB grew to 100+ with test junk and session work
- Auto-flush wrote all 100+ to issues.jsonl
- Git status showed modified issues.jsonl with 50+ unwanted issues

Impact:
- Pollutes git history with test/debug garbage
- Makes code review difficult (noise in diffs)
- Can't distinguish real work from session artifacts
- Other team members pull polluted issues

Solutions to consider:
1. Disable auto-flush by default (require explicit --enable-auto-flush)
2. Add .beadsignore to exclude issue ID patterns
3. Make auto-flush only export 'real' issues (exclude test-*)
4. Require manual 'bd sync' for git commit
5. Auto-flush to separate file (.beads/session.jsonl vs issues.jsonl)

Related: bd-8 (test pollution), isolation_test.go (test DB separation)


Issue counter gets out of sync with actual issues

The issue counter in issue_counters table frequently desyncs from actual max issue ID, causing:
- Import from JSONL leaves counter at old high value
- Test pollution increments counter but cleanup doesn't decrement it
- Delete issues doesn't update counter
- Only fix is 'rm -rf .beads' which is destructive

Examples from today's session:
- Had 48 issues but counter at 7714 after test pollution
- Import from git didn't reset counter
- Next new issue would be bd-7715 instead of bd-8

Proposed fixes:
1. Auto-recalculate counter from max(issue_id) on import
2. Add 'bd fix-counter' command
3. Make counter lazy (always compute from DB, don't store)
4. Import should reset counter to match imported data

Related:-254 (test isolation), bd-12 (init timestamp bug)


Counter not synced after import on existing DB with populated issue_counters table

The counter sync fix in counter_sync_test.go only syncs during initial migration when issue_counters table is empty (migrateIssueCountersTable checks count==0). For existing databases with stale counters:

- Import doesn't resync the counter
- Delete doesn't update counter
- Renumber doesn't fix counter
- Counter remains stuck at old high value

Example from today:
- Had 49 issues after clean import
- Counter stuck at 4106 from previous test pollution
- Next issue would be bd-4107 instead of bd-12
- Even after renumber, counter stayed at 4106

Root cause: Migration only syncs if table is empty (line 182 in sqlite.go). Once populated, never resyncs.

Fix needed:
1. Sync counter after import operations (not just empty table)
2. Add counter resync after renumber
3. Daemon caches counter value - needs to reload after external changes

Related: bd-8 (original counter sync fix), bd-9 (daemon cache staleness)

## Investigation Results

After thorough code review, all the fixes mentioned in the issue description have ALREADY been implemented:

### ✅ Fixes Already in Place:

1. **Import DOES resync counters**
   - `cmd/bd/import_shared.go:253` calls `SyncAllCounters()` after batch import
   - Verified with new test `TestCounterSyncAfterImport`

2. **Delete DOES update counters**
   - `internal/storage/sqlite/sqlite.go:1424` calls `SyncAllCounters()` after deletion
   - Both single delete and batch delete sync properly
   - Verified with existing tests: `TestCounterSyncAfterDelete`, `TestCounterSyncAfterBatchDelete`

3. **Renumber DOES fix counters**
   - `cmd/bd/renumber.go:298-304` calls `ResetCounter()` then `SyncAllCounters()`
   - Forces counter to actual max ID (not just MAX with stale value)

4. **Daemon cache DOES detect external changes**
   - `internal/rpc/server.go:1466-1487` checks file mtime and evicts stale cache
   - When DB file changes externally, cached storage is evicted and reopened

### Tests Added:

- `TestCounterSyncAfterImport`: Confirms import syncs counters from stale value (4106) to actual max (49)
- `TestCounterNotSyncedWithoutExplicitSync`: Documents what would happen without the fix (bd-4107 instead of bd-12)

### Conclusion:

The issue described in bd-12 has been **fully resolved**. All operations (import, delete, renumber) now properly sync counters. The daemon correctly detects external DB changes via file modification time.

The root cause (migration only syncing empty tables) was fixed by adding explicit `SyncAllCounters()` calls after import, delete, and renumber operations.


Re-land TestDatabaseReinitialization after fixing Windows/Nix issues

TestDatabaseReinitialization test was reverted due to CI failures:
- Windows: JSON parse errors, missing files
- Nix: git not available in build environment

Need to fix and re-land:
1. Make test work on Windows (path separators, file handling)
2. Skip test in Nix environment or mock git
3. Fix JSON parsing issues


Feature: Use external_ref as primary matching key for import updates

Implement external_ref-based matching for imports to enable hybrid workflows with external systems (Jira, GitHub, Linear).

## Problem
Current import collision detection treats any content change as a collision, preventing users from syncing updates from external systems without creating duplicates.

## Solution
Use external_ref field as primary matching key during imports. When an incoming issue has external_ref set:
- Search for existing issue with same external_ref
- If found, UPDATE (not collision)
- If not found, create new issue
- Never match local issues (without external_ref)

## Use Cases
- Jira integration: Import backlog, add local tasks, re-sync updates
- GitHub integration: Import issues, track with local subtasks, sync status
- Linear integration: Team coordination with local breakdown

## Reference
GitHub issue #142: https://github.com/steveyegge/beads/issues/142