refactor: remove unused bd pin/unpin/hook commands (bd-x0zl)

Analysis found these commands are dead code:
- gt never calls `bd pin` - uses `bd update --status=pinned` instead
- Beads.Pin() wrapper exists but is never called
- bd hook functionality duplicated by gt mol status
- Code comment says "pinned field is cosmetic for bd hook visibility"

Removed:
- cmd/bd/pin.go
- cmd/bd/unpin.go
- cmd/bd/hook.go

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-27 16:02:15 -08:00
parent c8b912cbe6
commit 1611f16751
178 changed files with 10291 additions and 1682 deletions

View File

@@ -892,3 +892,108 @@ func TestExportToMultiRepo(t *testing.T) {
}
})
}
// TestUpsertPreservesGateFields tests that gate await fields are preserved during upsert (bd-gr4q).
// Gates are wisps and aren't exported to JSONL. When an issue with the same ID is imported,
// the await fields should NOT be cleared.
func TestUpsertPreservesGateFields(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create a gate with await fields directly in the database
gate := &types.Issue{
ID: "bd-gate1",
Title: "Test Gate",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeGate,
Ephemeral: true,
AwaitType: "gh:run",
AwaitID: "123456789",
Timeout: 30 * 60 * 1000000000, // 30 minutes in nanoseconds
Waiters: []string{"beads/dave"},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
gate.ContentHash = gate.ComputeContentHash()
if err := store.CreateIssue(ctx, gate, "test"); err != nil {
t.Fatalf("failed to create gate: %v", err)
}
// Verify gate was created with await fields
retrieved, err := store.GetIssue(ctx, gate.ID)
if err != nil || retrieved == nil {
t.Fatalf("failed to get gate: %v", err)
}
if retrieved.AwaitType != "gh:run" {
t.Errorf("expected AwaitType=gh:run, got %q", retrieved.AwaitType)
}
if retrieved.AwaitID != "123456789" {
t.Errorf("expected AwaitID=123456789, got %q", retrieved.AwaitID)
}
// Create a JSONL file with an issue that has the same ID but no await fields
// (simulating what happens when a non-gate issue is imported)
tmpDir := t.TempDir()
jsonlPath := filepath.Join(tmpDir, "issues.jsonl")
f, err := os.Create(jsonlPath)
if err != nil {
t.Fatalf("failed to create JSONL file: %v", err)
}
// Same ID, different content (to trigger update), no await fields
incomingIssue := types.Issue{
ID: "bd-gate1",
Title: "Test Gate Updated", // Different title to trigger update
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeGate,
AwaitType: "", // Empty - simulating JSONL without await fields
AwaitID: "", // Empty
Timeout: 0,
Waiters: nil,
CreatedAt: time.Now(),
UpdatedAt: time.Now().Add(time.Second), // Newer timestamp
}
incomingIssue.ContentHash = incomingIssue.ComputeContentHash()
enc := json.NewEncoder(f)
if err := enc.Encode(incomingIssue); err != nil {
t.Fatalf("failed to encode issue: %v", err)
}
f.Close()
// Import the JSONL file (this should NOT clear the await fields)
_, err = store.importJSONLFile(ctx, jsonlPath, "test")
if err != nil {
t.Fatalf("importJSONLFile failed: %v", err)
}
// Verify await fields are preserved
updated, err := store.GetIssue(ctx, gate.ID)
if err != nil || updated == nil {
t.Fatalf("failed to get updated gate: %v", err)
}
// Title should be updated
if updated.Title != "Test Gate Updated" {
t.Errorf("expected title to be updated, got %q", updated.Title)
}
// Await fields should be PRESERVED (not cleared)
if updated.AwaitType != "gh:run" {
t.Errorf("AwaitType was cleared! expected 'gh:run', got %q", updated.AwaitType)
}
if updated.AwaitID != "123456789" {
t.Errorf("AwaitID was cleared! expected '123456789', got %q", updated.AwaitID)
}
if updated.Timeout != 30*60*1000000000 {
t.Errorf("Timeout was cleared! expected %d, got %d", 30*60*1000000000, updated.Timeout)
}
if len(updated.Waiters) != 1 || updated.Waiters[0] != "beads/dave" {
t.Errorf("Waiters was cleared! expected [beads/dave], got %v", updated.Waiters)
}
}