bd-6xd: Standardize on issues.jsonl as canonical filename

- Change default JSONL filename from beads.jsonl to issues.jsonl
- Add bd doctor check and fix to auto-migrate legacy beads.jsonl configs
- Update FindJSONLPath to prefer issues.jsonl over beads.jsonl
- Add CheckLegacyJSONLConfig and CheckLegacyJSONLFilename checks
- Add LegacyJSONLConfig fix to rename files and update config
- Update .gitattributes to reference issues.jsonl
- Fix tests to expect new canonical filename
- Add bd-6xd to v0.25.1 release notes

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-26 21:58:08 -08:00
parent 753333149e
commit ff3352ab23
26 changed files with 520 additions and 135 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ Files removed:
- Git merge driver temp files (*.json[0-9], *.jsonl[0-9])
Files preserved:
- beads.jsonl (source of truth)
- issues.jsonl (source of truth)
- beads.db (SQLite database)
- metadata.json
- config.yaml
+1 -1
View File
@@ -16,7 +16,7 @@ var cleanupCmd = &cobra.Command{
Short: "Delete closed issues from database to free up space",
Long: `Delete closed issues from the database to reduce database size.
This command permanently removes closed issues from beads.db and beads.jsonl.
This command permanently removes closed issues from beads.db and issues.jsonl.
It does NOT remove temporary files - use 'bd clean' for that.
By default, deletes ALL closed issues. Use --older-than to only delete
+15 -7
View File
@@ -210,6 +210,8 @@ func applyFixes(result doctorResult) {
err = fix.SyncBranchConfig(result.Path)
case "Database Config":
err = fix.DatabaseConfig(result.Path)
case "JSONL Config":
err = fix.LegacyJSONLConfig(result.Path)
case "Deletions Manifest":
err = fix.HydrateDeletionsManifest(result.Path)
case "Untracked Files":
@@ -484,6 +486,11 @@ func runDiagnostics(path string) doctorResult {
result.OverallOK = false
}
// Check 6a: Legacy JSONL config (bd-6xd: migrate beads.jsonl to issues.jsonl)
legacyConfigCheck := convertDoctorCheck(doctor.CheckLegacyJSONLConfig(path))
result.Checks = append(result.Checks, legacyConfigCheck)
// Don't fail overall check for legacy config, just warn
// Check 7: Database/JSONL configuration mismatch
configCheck := convertDoctorCheck(doctor.CheckDatabaseConfig(path))
result.Checks = append(result.Checks, configCheck)
@@ -625,20 +632,20 @@ func checkDatabaseVersion(path string) doctorCheck {
// Check if database file exists
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
// Check if JSONL exists (--no-db mode)
// Check both canonical (beads.jsonl) and legacy (issues.jsonl) names
beadsJSONL := filepath.Join(beadsDir, "beads.jsonl")
// Check canonical (issues.jsonl) first, then legacy (beads.jsonl)
issuesJSONL := filepath.Join(beadsDir, "issues.jsonl")
beadsJSONL := filepath.Join(beadsDir, "beads.jsonl")
if _, err := os.Stat(beadsJSONL); err == nil {
if _, err := os.Stat(issuesJSONL); err == nil {
return doctorCheck{
Name: "Database",
Status: statusOK,
Message: "JSONL-only mode",
Detail: "Using beads.jsonl (no SQLite database)",
Detail: "Using issues.jsonl (no SQLite database)",
}
}
if _, err := os.Stat(issuesJSONL); err == nil {
if _, err := os.Stat(beadsJSONL); err == nil {
return doctorCheck{
Name: "Database",
Status: statusOK,
@@ -2095,9 +2102,10 @@ func checkDeletionsManifest(path string) doctorCheck {
// deletions.jsonl doesn't exist or is empty
// Check if there's git history that might have deletions
jsonlPath := filepath.Join(beadsDir, "beads.jsonl")
// bd-6xd: Check canonical issues.jsonl first, then legacy beads.jsonl
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
jsonlPath = filepath.Join(beadsDir, "issues.jsonl")
jsonlPath = filepath.Join(beadsDir, "beads.jsonl")
if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
return doctorCheck{
Name: "Deletions Manifest",
+75 -4
View File
@@ -73,7 +73,8 @@ func DatabaseConfig(path string) error {
}
// findActualJSONLFile scans .beads/ for the actual JSONL file in use.
// Prefers beads.jsonl over issues.jsonl, skips backups and merge artifacts.
// Prefers issues.jsonl over beads.jsonl (canonical name), skips backups and merge artifacts.
// bd-6xd: issues.jsonl is the canonical filename
func findActualJSONLFile(beadsDir string) string {
entries, err := os.ReadDir(beadsDir)
if err != nil {
@@ -109,17 +110,87 @@ func findActualJSONLFile(beadsDir string) string {
return ""
}
// Prefer beads.jsonl over issues.jsonl (canonical name)
// bd-6xd: Prefer issues.jsonl over beads.jsonl (canonical name)
for _, name := range candidates {
if name == "beads.jsonl" {
if name == "issues.jsonl" {
return name
}
}
// Fall back to first candidate
// Fall back to first candidate (including beads.jsonl as legacy)
return candidates[0]
}
// LegacyJSONLConfig migrates from legacy beads.jsonl to canonical issues.jsonl.
// This renames the file, updates metadata.json, and updates .gitattributes if present.
// bd-6xd: issues.jsonl is the canonical filename
func LegacyJSONLConfig(path string) error {
if err := validateBeadsWorkspace(path); err != nil {
return err
}
beadsDir := filepath.Join(path, ".beads")
// Load existing config
cfg, err := configfile.Load(beadsDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if cfg == nil {
return fmt.Errorf("no metadata.json found")
}
legacyPath := filepath.Join(beadsDir, "beads.jsonl")
canonicalPath := filepath.Join(beadsDir, "issues.jsonl")
legacyExists := false
if _, err := os.Stat(legacyPath); err == nil {
legacyExists = true
}
canonicalExists := false
if _, err := os.Stat(canonicalPath); err == nil {
canonicalExists = true
}
// Case 1: Config says beads.jsonl, file exists, issues.jsonl doesn't exist -> rename
if cfg.JSONLExport == "beads.jsonl" && legacyExists && !canonicalExists {
fmt.Printf(" Renaming beads.jsonl → issues.jsonl\n")
if err := os.Rename(legacyPath, canonicalPath); err != nil {
return fmt.Errorf("failed to rename file: %w", err)
}
cfg.JSONLExport = "issues.jsonl"
// Update .gitattributes if it references beads.jsonl
gitattrsPath := filepath.Join(path, ".gitattributes")
if content, err := os.ReadFile(gitattrsPath); err == nil {
if strings.Contains(string(content), ".beads/beads.jsonl") {
newContent := strings.ReplaceAll(string(content), ".beads/beads.jsonl", ".beads/issues.jsonl")
// #nosec G306 -- .gitattributes should be world-readable
if err := os.WriteFile(gitattrsPath, []byte(newContent), 0644); err != nil {
fmt.Printf(" Warning: failed to update .gitattributes: %v\n", err)
} else {
fmt.Printf(" Updated .gitattributes\n")
}
}
}
}
// Case 2: Config says beads.jsonl but issues.jsonl exists -> just update config
if cfg.JSONLExport == "beads.jsonl" && canonicalExists {
fmt.Printf(" Updating config: beads.jsonl → issues.jsonl\n")
cfg.JSONLExport = "issues.jsonl"
}
// Save updated config
if err := cfg.Save(beadsDir); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
fmt.Printf(" Updated metadata.json\n")
return nil
}
// findActualDBFile scans .beads/ for the actual database file in use.
// Prefers beads.db (canonical name), skips backups and vc.db.
func findActualDBFile(beadsDir string) string {
+122 -24
View File
@@ -9,7 +9,7 @@ import (
)
// TestDatabaseConfigFix_JSONLMismatch tests that DatabaseConfig fixes JSONL mismatches.
// bd-afd: Verify auto-fix for metadata.json jsonl_export mismatch
// bd-6xd: Verify auto-fix for metadata.json jsonl_export mismatch
func TestDatabaseConfigFix_JSONLMismatch(t *testing.T) {
// Create temporary directory
tmpDir := t.TempDir()
@@ -18,16 +18,16 @@ func TestDatabaseConfigFix_JSONLMismatch(t *testing.T) {
t.Fatalf("Failed to create .beads dir: %v", err)
}
// Create beads.jsonl file (actual JSONL)
jsonlPath := filepath.Join(beadsDir, "beads.jsonl")
// Create issues.jsonl file (actual JSONL - canonical name)
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
if err := os.WriteFile(jsonlPath, []byte(`{"id":"test-123"}`), 0644); err != nil {
t.Fatalf("Failed to create beads.jsonl: %v", err)
t.Fatalf("Failed to create issues.jsonl: %v", err)
}
// Create metadata.json with wrong JSONL filename (issues.jsonl)
// Create metadata.json with wrong JSONL filename (beads.jsonl)
cfg := &configfile.Config{
Database: "beads.db",
JSONLExport: "issues.jsonl", // Wrong - should be beads.jsonl
JSONLExport: "beads.jsonl", // Wrong - should be issues.jsonl
}
if err := cfg.Save(beadsDir); err != nil {
t.Fatalf("Failed to save config: %v", err)
@@ -44,13 +44,14 @@ func TestDatabaseConfigFix_JSONLMismatch(t *testing.T) {
t.Fatalf("Failed to load updated config: %v", err)
}
if updatedCfg.JSONLExport != "beads.jsonl" {
t.Errorf("Expected JSONLExport to be 'beads.jsonl', got %q", updatedCfg.JSONLExport)
if updatedCfg.JSONLExport != "issues.jsonl" {
t.Errorf("Expected JSONLExport to be 'issues.jsonl', got %q", updatedCfg.JSONLExport)
}
}
// TestDatabaseConfigFix_PrefersBeadsJSONL tests that DatabaseConfig prefers beads.jsonl over issues.jsonl.
func TestDatabaseConfigFix_PrefersBeadsJSONL(t *testing.T) {
// TestDatabaseConfigFix_PrefersIssuesJSONL tests that DatabaseConfig prefers issues.jsonl over beads.jsonl.
// bd-6xd: issues.jsonl is the canonical filename
func TestDatabaseConfigFix_PrefersIssuesJSONL(t *testing.T) {
// Create temporary directory
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
@@ -72,7 +73,7 @@ func TestDatabaseConfigFix_PrefersBeadsJSONL(t *testing.T) {
// Create metadata.json with wrong JSONL filename (old.jsonl)
cfg := &configfile.Config{
Database: "beads.db",
JSONLExport: "old.jsonl", // Wrong - should prefer beads.jsonl
JSONLExport: "old.jsonl", // Wrong - should prefer issues.jsonl
}
if err := cfg.Save(beadsDir); err != nil {
t.Fatalf("Failed to save config: %v", err)
@@ -83,30 +84,31 @@ func TestDatabaseConfigFix_PrefersBeadsJSONL(t *testing.T) {
t.Fatalf("DatabaseConfig failed: %v", err)
}
// Verify the config was updated to beads.jsonl (not issues.jsonl)
// Verify the config was updated to issues.jsonl (canonical name)
updatedCfg, err := configfile.Load(beadsDir)
if err != nil {
t.Fatalf("Failed to load updated config: %v", err)
}
if updatedCfg.JSONLExport != "beads.jsonl" {
t.Errorf("Expected JSONLExport to be 'beads.jsonl', got %q", updatedCfg.JSONLExport)
if updatedCfg.JSONLExport != "issues.jsonl" {
t.Errorf("Expected JSONLExport to be 'issues.jsonl', got %q", updatedCfg.JSONLExport)
}
}
// TestFindActualJSONLFile_SkipsBackups tests that backup files are skipped.
// bd-6xd: issues.jsonl is the canonical filename
func TestFindActualJSONLFile_SkipsBackups(t *testing.T) {
// Create temporary directory
tmpDir := t.TempDir()
// Create beads.jsonl and various backup files
// Create issues.jsonl and various backup files
files := []string{
"beads.jsonl",
"beads.jsonl.backup",
"backup_beads.jsonl",
"beads.jsonl.orig",
"beads.jsonl.bak",
"beads.jsonl~",
"issues.jsonl",
"issues.jsonl.backup",
"backup_issues.jsonl",
"issues.jsonl.orig",
"issues.jsonl.bak",
"issues.jsonl~",
}
for _, name := range files {
@@ -116,9 +118,105 @@ func TestFindActualJSONLFile_SkipsBackups(t *testing.T) {
}
}
// findActualJSONLFile should return beads.jsonl (not backups)
// findActualJSONLFile should return issues.jsonl (not backups)
result := findActualJSONLFile(tmpDir)
if result != "beads.jsonl" {
t.Errorf("Expected 'beads.jsonl', got %q", result)
if result != "issues.jsonl" {
t.Errorf("Expected 'issues.jsonl', got %q", result)
}
}
// TestLegacyJSONLConfig_MigratesBeadsToIssues tests migration from beads.jsonl to issues.jsonl.
// bd-6xd: issues.jsonl is the canonical filename
func TestLegacyJSONLConfig_MigratesBeadsToIssues(t *testing.T) {
// Create temporary directory
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
if err := os.Mkdir(beadsDir, 0755); err != nil {
t.Fatalf("Failed to create .beads dir: %v", err)
}
// Create beads.jsonl file (legacy name)
legacyPath := filepath.Join(beadsDir, "beads.jsonl")
if err := os.WriteFile(legacyPath, []byte(`{"id":"test-123"}`), 0644); err != nil {
t.Fatalf("Failed to create beads.jsonl: %v", err)
}
// Create metadata.json with legacy filename
cfg := &configfile.Config{
Database: "beads.db",
JSONLExport: "beads.jsonl",
}
if err := cfg.Save(beadsDir); err != nil {
t.Fatalf("Failed to save config: %v", err)
}
// Run the fix
if err := LegacyJSONLConfig(tmpDir); err != nil {
t.Fatalf("LegacyJSONLConfig failed: %v", err)
}
// Verify the file was renamed
canonicalPath := filepath.Join(beadsDir, "issues.jsonl")
if _, err := os.Stat(canonicalPath); os.IsNotExist(err) {
t.Error("Expected issues.jsonl to exist after migration")
}
if _, err := os.Stat(legacyPath); err == nil {
t.Error("Expected beads.jsonl to be removed after migration")
}
// Verify the config was updated
updatedCfg, err := configfile.Load(beadsDir)
if err != nil {
t.Fatalf("Failed to load updated config: %v", err)
}
if updatedCfg.JSONLExport != "issues.jsonl" {
t.Errorf("Expected JSONLExport to be 'issues.jsonl', got %q", updatedCfg.JSONLExport)
}
}
// TestLegacyJSONLConfig_UpdatesGitattributes tests that .gitattributes is updated during migration.
func TestLegacyJSONLConfig_UpdatesGitattributes(t *testing.T) {
// Create temporary directory
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
if err := os.Mkdir(beadsDir, 0755); err != nil {
t.Fatalf("Failed to create .beads dir: %v", err)
}
// Create beads.jsonl file (legacy name)
legacyPath := filepath.Join(beadsDir, "beads.jsonl")
if err := os.WriteFile(legacyPath, []byte(`{"id":"test-123"}`), 0644); err != nil {
t.Fatalf("Failed to create beads.jsonl: %v", err)
}
// Create .gitattributes with legacy reference
gitattrsPath := filepath.Join(tmpDir, ".gitattributes")
if err := os.WriteFile(gitattrsPath, []byte(".beads/beads.jsonl merge=beads\n"), 0644); err != nil {
t.Fatalf("Failed to create .gitattributes: %v", err)
}
// Create metadata.json with legacy filename
cfg := &configfile.Config{
Database: "beads.db",
JSONLExport: "beads.jsonl",
}
if err := cfg.Save(beadsDir); err != nil {
t.Fatalf("Failed to save config: %v", err)
}
// Run the fix
if err := LegacyJSONLConfig(tmpDir); err != nil {
t.Fatalf("LegacyJSONLConfig failed: %v", err)
}
// Verify .gitattributes was updated
content, err := os.ReadFile(gitattrsPath)
if err != nil {
t.Fatalf("Failed to read .gitattributes: %v", err)
}
if string(content) != ".beads/issues.jsonl merge=beads\n" {
t.Errorf("Expected .gitattributes to reference issues.jsonl, got: %q", string(content))
}
}
+4 -3
View File
@@ -22,11 +22,12 @@ func HydrateDeletionsManifest(path string) error {
}
beadsDir := filepath.Join(path, ".beads")
jsonlPath := filepath.Join(beadsDir, "beads.jsonl")
// bd-6xd: issues.jsonl is the canonical filename
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
// Also check for legacy issues.jsonl
// Also check for legacy beads.jsonl
if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
legacyPath := filepath.Join(beadsDir, "issues.jsonl")
legacyPath := filepath.Join(beadsDir, "beads.jsonl")
if _, err := os.Stat(legacyPath); err == nil {
jsonlPath = legacyPath
} else {
+67
View File
@@ -177,6 +177,73 @@ func CheckLegacyJSONLFilename(repoPath string) DoctorCheck {
}
}
// CheckLegacyJSONLConfig detects if metadata.json is configured to use the legacy
// beads.jsonl filename and recommends migrating to the canonical issues.jsonl.
// bd-6xd: issues.jsonl is the canonical filename
func CheckLegacyJSONLConfig(repoPath string) DoctorCheck {
beadsDir := filepath.Join(repoPath, ".beads")
// Load config
cfg, err := configfile.Load(beadsDir)
if err != nil || cfg == nil {
// No config - using defaults, which are now issues.jsonl
return DoctorCheck{
Name: "JSONL Config",
Status: "ok",
Message: "Using default configuration (issues.jsonl)",
}
}
// Check if using legacy beads.jsonl
if cfg.JSONLExport == "beads.jsonl" {
// Check if beads.jsonl actually exists
legacyPath := filepath.Join(beadsDir, "beads.jsonl")
canonicalPath := filepath.Join(beadsDir, "issues.jsonl")
legacyExists := false
if _, err := os.Stat(legacyPath); err == nil {
legacyExists = true
}
canonicalExists := false
if _, err := os.Stat(canonicalPath); err == nil {
canonicalExists = true
}
if legacyExists && !canonicalExists {
return DoctorCheck{
Name: "JSONL Config",
Status: "warning",
Message: "Using legacy beads.jsonl filename",
Detail: "The canonical filename is now issues.jsonl (bd-6xd).\n" +
" Legacy beads.jsonl is still supported but should be migrated.",
Fix: "Run 'bd doctor --fix' to auto-migrate, or manually:\n" +
" 1. git mv .beads/beads.jsonl .beads/issues.jsonl\n" +
" 2. Update metadata.json: jsonl_export: \"issues.jsonl\"\n" +
" 3. Update .gitattributes if present",
}
}
if !legacyExists && canonicalExists {
// Config says beads.jsonl but issues.jsonl exists - just update config
return DoctorCheck{
Name: "JSONL Config",
Status: "warning",
Message: "Config references beads.jsonl but issues.jsonl exists",
Detail: "metadata.json says beads.jsonl but the actual file is issues.jsonl",
Fix: "Run 'bd doctor --fix' to update the configuration",
}
}
}
// Using issues.jsonl or custom name - all good
return DoctorCheck{
Name: "JSONL Config",
Status: "ok",
Message: fmt.Sprintf("Using %s", cfg.JSONLExport),
}
}
// CheckDatabaseConfig verifies that the configured database and JSONL paths
// match what actually exists on disk.
func CheckDatabaseConfig(repoPath string) DoctorCheck {
+83
View File
@@ -261,3 +261,86 @@ func TestCheckLegacyJSONLFilename(t *testing.T) {
})
}
}
func TestCheckLegacyJSONLConfig(t *testing.T) {
tests := []struct {
name string
configJSONL string // what metadata.json says
existingFiles []string // which files actually exist
expectedStatus string
expectWarning bool
}{
{
name: "no config (defaults)",
configJSONL: "",
existingFiles: []string{},
expectedStatus: "ok",
expectWarning: false,
},
{
name: "using canonical issues.jsonl",
configJSONL: "issues.jsonl",
existingFiles: []string{"issues.jsonl"},
expectedStatus: "ok",
expectWarning: false,
},
{
name: "using custom name",
configJSONL: "my-project.jsonl",
existingFiles: []string{"my-project.jsonl"},
expectedStatus: "ok",
expectWarning: false,
},
{
name: "using legacy beads.jsonl",
configJSONL: "beads.jsonl",
existingFiles: []string{"beads.jsonl"},
expectedStatus: "warning",
expectWarning: true,
},
{
name: "config says beads.jsonl but issues.jsonl exists",
configJSONL: "beads.jsonl",
existingFiles: []string{"issues.jsonl"},
expectedStatus: "warning",
expectWarning: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
beadsDir := filepath.Join(tmpDir, ".beads")
if err := os.Mkdir(beadsDir, 0750); err != nil {
t.Fatal(err)
}
// Create test files
for _, file := range tt.existingFiles {
filePath := filepath.Join(beadsDir, file)
if err := os.WriteFile(filePath, []byte(`{"id":"test"}`), 0644); err != nil {
t.Fatal(err)
}
}
// Create metadata.json if configJSONL is set
if tt.configJSONL != "" {
metadataPath := filepath.Join(beadsDir, "metadata.json")
content := `{"database":"beads.db","jsonl_export":"` + tt.configJSONL + `"}`
if err := os.WriteFile(metadataPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
}
check := CheckLegacyJSONLConfig(tmpDir)
if check.Status != tt.expectedStatus {
t.Errorf("Expected status %s, got %s (message: %s)", tt.expectedStatus, check.Status, check.Message)
}
if tt.expectWarning && check.Fix == "" {
t.Error("Expected fix message for warning, got empty string")
}
})
}
}
+5 -5
View File
@@ -43,7 +43,7 @@ NOTE: Import requires direct database access and does not work with daemon mode.
fmt.Fprintf(os.Stderr, "Did you mean: bd import -i %s\n\n", args[0])
fmt.Fprintf(os.Stderr, "The import command does not accept positional arguments.\n")
fmt.Fprintf(os.Stderr, "Use the -i flag to specify an input file:\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/beads.jsonl\n\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/issues.jsonl\n\n")
fmt.Fprintf(os.Stderr, "Or pipe data via stdin:\n")
fmt.Fprintf(os.Stderr, " cat data.jsonl | bd import\n")
os.Exit(1)
@@ -89,8 +89,8 @@ NOTE: Import requires direct database access and does not work with daemon mode.
if input == "" && term.IsTerminal(int(os.Stdin.Fd())) {
fmt.Fprintf(os.Stderr, "Error: No input specified.\n\n")
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/beads.jsonl # Import from file\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/beads.jsonl --dry-run # Preview changes\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/issues.jsonl # Import from file\n")
fmt.Fprintf(os.Stderr, " bd import -i .beads/issues.jsonl --dry-run # Preview changes\n")
fmt.Fprintf(os.Stderr, " cat data.jsonl | bd import # Import from pipe\n")
fmt.Fprintf(os.Stderr, " bd sync --import-only # Import latest JSONL\n\n")
fmt.Fprintf(os.Stderr, "For more information, run: bd import --help\n")
@@ -144,8 +144,8 @@ NOTE: Import requires direct database access and does not work with daemon mode.
if err := attemptAutoMerge(input); err != nil {
fmt.Fprintf(os.Stderr, "Error: Automatic merge failed: %v\n\n", err)
fmt.Fprintf(os.Stderr, "To resolve manually:\n")
fmt.Fprintf(os.Stderr, " git checkout --ours .beads/beads.jsonl && bd import -i .beads/beads.jsonl\n")
fmt.Fprintf(os.Stderr, " git checkout --theirs .beads/beads.jsonl && bd import -i .beads/beads.jsonl\n\n")
fmt.Fprintf(os.Stderr, " git checkout --ours .beads/issues.jsonl && bd import -i .beads/issues.jsonl\n")
fmt.Fprintf(os.Stderr, " git checkout --theirs .beads/issues.jsonl && bd import -i .beads/issues.jsonl\n\n")
fmt.Fprintf(os.Stderr, "For advanced field-level merging, see: https://github.com/neongreen/mono/tree/main/beads-merge\n")
os.Exit(1)
}
+4 -4
View File
@@ -120,8 +120,8 @@ func runContributorWizard(ctx context.Context, store storage.Storage) error {
return fmt.Errorf("failed to create .beads in planning repo: %w", err)
}
// Create issues.jsonl
jsonlPath := filepath.Join(beadsDir, "beads.jsonl")
// Create issues.jsonl (canonical name, bd-6xd)
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
// #nosec G306 -- planning repo JSONL must be shareable across collaborators
if err := os.WriteFile(jsonlPath, []byte{}, 0644); err != nil {
return fmt.Errorf("failed to create issues.jsonl: %w", err)
@@ -183,8 +183,8 @@ Created by: bd init --contributor
fmt.Printf("\n%s %s\n\n", green("✓"), bold("Contributor setup complete!"))
fmt.Println("Configuration:")
fmt.Printf(" Current repo issues: %s\n", cyan(".beads/beads.jsonl"))
fmt.Printf(" Planning repo issues: %s\n", cyan(filepath.Join(planningPath, ".beads/beads.jsonl")))
fmt.Printf(" Current repo issues: %s\n", cyan(".beads/issues.jsonl"))
fmt.Printf(" Planning repo issues: %s\n", cyan(filepath.Join(planningPath, ".beads/issues.jsonl")))
fmt.Println()
fmt.Println("How it works:")
fmt.Println(" • Issues you create will route to the planning repo")
+2 -2
View File
@@ -177,8 +177,8 @@ func TestAutoFlushJSONLContent(t *testing.T) {
dbPath = filepath.Join(tmpDir, "test.db")
// The actual JSONL path - findJSONLPath() will determine this
// but in tests it appears to be issues.jsonl in the same directory as the db
expectedJSONLPath := filepath.Join(tmpDir, "beads.jsonl")
// bd-6xd: Default is now issues.jsonl (canonical name)
expectedJSONLPath := filepath.Join(tmpDir, "issues.jsonl")
// Create store
testStore := newTestStore(t, dbPath)
+1 -1
View File
@@ -93,7 +93,7 @@ Vendored into bd with permission.`,
func cleanupMergeArtifacts(outputPath string, debug bool) {
// Determine the .beads directory from the output path
// outputPath is typically .beads/beads.jsonl
// outputPath is typically .beads/issues.jsonl
beadsDir := filepath.Dir(outputPath)
if debug {
+1 -1
View File
@@ -50,7 +50,7 @@ func ensureDatabaseFresh(ctx context.Context) error {
"hasn't been imported yet. This would cause you to see stale/incomplete data.\n\n"+
"To fix:\n"+
" bd sync --import-only # Import JSONL updates to database\n"+
" bd import -i .beads/beads.jsonl # Alternative: specify file explicitly\n\n"+
" bd import -i .beads/issues.jsonl # Alternative: specify file explicitly\n\n"+
"If in a sandboxed environment (e.g., Codex) where daemon can't be stopped:\n"+
" bd --sandbox ready # Use direct mode (no daemon)\n"+
" bd ready --allow-stale # Skip staleness check (use with caution)\n\n"+
+3 -3
View File
@@ -169,7 +169,7 @@ Examples:
},
}
// getGitActivity calculates activity stats from git log of beads.jsonl
// getGitActivity calculates activity stats from git log of issues.jsonl
func getGitActivity(hours int) *RecentActivitySummary {
activity := &RecentActivitySummary{
HoursTracked: hours,
@@ -177,7 +177,7 @@ func getGitActivity(hours int) *RecentActivitySummary {
// Run git log to get patches for the last N hours
since := fmt.Sprintf("%d hours ago", hours)
cmd := exec.Command("git", "log", "--since="+since, "--numstat", "--pretty=format:%H", ".beads/beads.jsonl") // #nosec G204 -- bounded arguments for local git history inspection
cmd := exec.Command("git", "log", "--since="+since, "--numstat", "--pretty=format:%H", ".beads/issues.jsonl") // #nosec G204 -- bounded arguments for local git history inspection
output, err := cmd.Output()
if err != nil {
@@ -213,7 +213,7 @@ func getGitActivity(hours int) *RecentActivitySummary {
}
// Get detailed diff to analyze changes
cmd = exec.Command("git", "log", "--since="+since, "-p", ".beads/beads.jsonl") // #nosec G204 -- bounded arguments for local git history inspection
cmd = exec.Command("git", "log", "--since="+since, "-p", ".beads/issues.jsonl") // #nosec G204 -- bounded arguments for local git history inspection
output, err = cmd.Output()
if err != nil {
return nil
+3 -3
View File
@@ -1045,9 +1045,9 @@ func showSyncStatus(ctx context.Context) error {
fmt.Print(string(logOutput))
}
// Show file diff for .beads/beads.jsonl
fmt.Println("\nFile differences in .beads/beads.jsonl:")
diffCmd := exec.CommandContext(ctx, "git", "diff", currentBranch+"..."+syncBranch, "--", ".beads/beads.jsonl")
// Show file diff for .beads/issues.jsonl
fmt.Println("\nFile differences in .beads/issues.jsonl:")
diffCmd := exec.CommandContext(ctx, "git", "diff", currentBranch+"..."+syncBranch, "--", ".beads/issues.jsonl")
diffOutput, err := diffCmd.CombinedOutput()
if err != nil {
// diff returns non-zero when there are differences, which is fine
+5 -5
View File
@@ -186,10 +186,10 @@ func findSubstring(haystack, needle string) int {
}
// findActualJSONLFile scans .beads/ for the actual JSONL file in use.
// Prefers beads.jsonl over issues.jsonl, skips backups and merge artifacts.
// Prefers issues.jsonl over beads.jsonl (canonical name), skips backups and merge artifacts.
// Returns empty string if no JSONL file is found.
//
// bd-afd: Auto-detect JSONL file to prevent metadata.json mismatches
// bd-6xd: Auto-detect JSONL file to prevent metadata.json mismatches
func findActualJSONLFile(beadsDir string) string {
entries, err := os.ReadDir(beadsDir)
if err != nil {
@@ -225,14 +225,14 @@ func findActualJSONLFile(beadsDir string) string {
return ""
}
// Prefer beads.jsonl over issues.jsonl (canonical name)
// bd-6xd: Prefer issues.jsonl over beads.jsonl (canonical name)
for _, name := range candidates {
if name == "beads.jsonl" {
if name == "issues.jsonl" {
return name
}
}
// Fall back to first candidate (including issues.jsonl if present)
// Fall back to first candidate (including beads.jsonl as legacy)
return candidates[0]
}