fix: ExtractIssuePrefix now handles 3-char base36 hashes (#425)

- Lower minimum hash length from 4 to 3 characters
- Update hash validation to support base36 (0-9, a-z) instead of just hex
- Require at least one digit to distinguish hashes from English words
- Fixes prefix extraction for hyphenated prefixes with 3-char hashes
  e.g., 'document-intelligence-0sa' now correctly extracts 'document-intelligence'
- Add test cases for 3-char hashes with multi-part prefixes
- Resolves bd sync failures with 'prefix mismatch detected' errors
This commit is contained in:
Steve Yegge
2025-11-30 20:32:20 -08:00
parent af78e7b10c
commit 2320a1c2ef
3 changed files with 28 additions and 8 deletions

View File

@@ -402,6 +402,21 @@ func TestExtractIssuePrefix(t *testing.T) {
issueID: "proj-AbCd12",
expected: "proj", // Mixed case hash should work
},
{
name: "3-char hash with hyphenated prefix",
issueID: "document-intelligence-0sa",
expected: "document-intelligence", // 3-char hash (base36) should use last hyphen
},
{
name: "3-char hash with multi-part prefix",
issueID: "my-cool-app-1x7",
expected: "my-cool-app", // 3-char base36 hash
},
{
name: "3-char all-letters suffix (should fall back to first hyphen)",
issueID: "test-proj-abc",
expected: "test", // All letters = not a hash, falls back to first hyphen
},
}
for _, tt := range tests {

View File

@@ -51,19 +51,24 @@ func ExtractIssuePrefix(issueID string) string {
}
// isLikelyHash checks if a string looks like a hash ID suffix.
// Returns true for hexadecimal strings of 4-8 characters.
// Hash IDs in beads are typically 4-6 characters (progressive length scaling).
// Returns true for base36 strings of 3-8 characters (0-9, a-z) that contain at least one digit.
// Requires a digit to distinguish hashes from English words (e.g., accept "0sa" but reject "test").
// Hash IDs in beads use adaptive length scaling from 3-8 characters.
func isLikelyHash(s string) bool {
if len(s) < 4 || len(s) > 8 {
if len(s) < 3 || len(s) > 8 {
return false
}
// Check if all characters are hexadecimal
hasDigit := false
// Check if all characters are base36 (0-9, a-z) and at least one is a digit
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
if c >= '0' && c <= '9' {
hasDigit = true
}
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
return false
}
}
return true
return hasDigit
}
// ExtractIssueNumber extracts the number from an issue ID like "bd-123" -> 123