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:
@@ -1,4 +1,4 @@
|
||||
{"id":"bd-93d","title":"Jira export script (jsonl2jira.py)","description":"Create a Python script to push beads issues to Jira.\n\n**Requires**: Jira import script to be complete first (need external_ref matching logic working)\n\n**Features needed**:\n- Create new Jira issues from beads issues without external_ref\n- Update existing Jira issues matched by external_ref\n- Map beads fields back to Jira fields\n- Handle Jira workflow transitions (status changes may need transitions)\n- Support custom field mapping for design/acceptance_criteria/notes\n\n**Challenges**:\n- Jira status changes often require workflow transitions, not direct updates\n- Need to discover valid transitions via API\n- Custom fields vary by Jira instance\n\n**Usage**:\n```bash\nbd export | python jsonl2jira.py --create-only # Only create, don't update\nbd export | python jsonl2jira.py # Create and update\n```\n\n**After creation**: Sets external_ref on beads issue to link back","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-30T12:56:14.266357-08:00","updated_at":"2025-11-30T15:19:40.264737-08:00","closed_at":"2025-11-30T15:19:40.264737-08:00","dependencies":[{"issue_id":"bd-93d","depends_on_id":"bd-qvj","type":"parent-child","created_at":"2025-11-30T12:56:44.652391-08:00","created_by":"stevey"},{"issue_id":"bd-93d","depends_on_id":"bd-tjn","type":"blocks","created_at":"2025-11-30T12:56:54.941116-08:00","created_by":"stevey"}]}
|
||||
{"id":"bd-clg","title":"bd jira sync command","description":"Add a built-in bd command for Jira synchronization.\n\n**Requires**: Both import and export scripts working\n\n**Features**:\n- `bd jira sync --pull` - Import from Jira to beads\n- `bd jira sync --push` - Export from beads to Jira\n- `bd jira sync` - Bidirectional (pull then push, with conflict resolution)\n- `bd jira status` - Show sync status and last sync time\n\n**Conflict resolution**:\n- Timestamp-based: newer update wins\n- Option for --prefer-local or --prefer-jira to override\n- Interactive mode for manual conflict resolution (optional)\n\n**Integration**:\n- Uses jira.* config settings from bd config\n- Stores last sync timestamp in config\n- Logs sync activity for audit\n\n**Stretch goals**:\n- Webhook integration for real-time sync\n- Selective sync by JQL filter","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-11-30T12:56:27.716537-08:00","updated_at":"2025-11-30T15:25:37.896045-08:00","closed_at":"2025-11-30T15:25:37.896045-08:00","dependencies":[{"issue_id":"bd-clg","depends_on_id":"bd-qvj","type":"parent-child","created_at":"2025-11-30T12:56:49.796568-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-tjn","type":"blocks","created_at":"2025-11-30T12:57:00.075288-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-93d","type":"blocks","created_at":"2025-11-30T12:57:05.206431-08:00","created_by":"stevey"}]}
|
||||
{"id":"bd-qvj","title":"Jira import/export integration","description":"Add ability to import issues from Jira and export beads issues to Jira. See GitHub discussion #430 for user request.\n\n**Background**: User @kaihendry configured jira.* settings but found bd sync doesn't sync with Jira - only with git. The config namespace exists but the integration doesn't.\n\n**Existing infrastructure**:\n- external_ref field for linking to external systems\n- Import logic matches by external_ref first (priority over ID matching)\n- Config namespace jira.* documented in CONFIG.md\n- GitHub import example (examples/github-import/gh2jsonl.py) provides pattern\n\n**Schema mapping challenges**:\n- Jira workflows are highly customizable (status mapping)\n- Jira has more field types (components, versions, custom fields)\n- Two-way sync needs conflict resolution\n- Jira Cloud vs Server/Data Center have different APIs\n\n**Approach**: Phased implementation starting with import, then export, then bidirectional sync.","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-30T12:55:44.835009-08:00","updated_at":"2025-11-30T12:55:44.835009-08:00"}
|
||||
{"id":"bd-tjn","title":"Jira import script (jira2jsonl.py)","description":"Create a Python script to import Jira issues into beads JSONL format.\n\n**Pattern**: Follow examples/github-import/gh2jsonl.py\n\n**Features needed**:\n- Fetch issues via Jira REST API with JQL queries\n- Pagination handling (100 issues per request)\n- Map Jira fields to beads fields (see schema mapping in epic)\n- Set external_ref to Jira issue URL for re-sync\n- Support both Jira Cloud and Server/Data Center APIs\n- Read config from bd config (jira.url, jira.project, jira.api_token)\n\n**Config-driven mapping**:\n- jira.status_map.* for status conversion\n- jira.type_map.* for issue type conversion\n- jira.priority_map.* for priority conversion\n\n**Usage**:\n```bash\npython jira2jsonl.py --project PROJ | bd import\n# or\npython jira2jsonl.py --jql 'project=PROJ AND status\\!=Done' | bd import\n```\n\n**Output**: JSONL with external_ref set to Jira issue URL","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-30T12:55:59.517985-08:00","updated_at":"2025-11-30T15:11:56.692594-08:00","closed_at":"2025-11-30T15:11:56.692594-08:00","dependencies":[{"issue_id":"bd-tjn","depends_on_id":"bd-qvj","type":"parent-child","created_at":"2025-11-30T12:56:39.507643-08:00","created_by":"stevey"}]}
|
||||
{"id":"bd-qvj","title":"Jira import/export integration","description":"Add ability to import issues from Jira and export beads issues to Jira. See GitHub discussion #430 for user request.\n\n**Background**: User @kaihendry configured jira.* settings but found bd sync doesn't sync with Jira - only with git. The config namespace exists but the integration doesn't.\n\n**Existing infrastructure**:\n- external_ref field for linking to external systems\n- Import logic matches by external_ref first (priority over ID matching)\n- Config namespace jira.* documented in CONFIG.md\n- GitHub import example (examples/github-import/gh2jsonl.py) provides pattern\n\n**Schema mapping challenges**:\n- Jira workflows are highly customizable (status mapping)\n- Jira has more field types (components, versions, custom fields)\n- Two-way sync needs conflict resolution\n- Jira Cloud vs Server/Data Center have different APIs\n\n**Approach**: Phased implementation starting with import, then export, then bidirectional sync.","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-30T12:55:44.835009-08:00","updated_at":"2025-11-30T12:55:44.835009-08:00"}
|
||||
{"id":"bd-clg","title":"bd jira sync command","description":"Add a built-in bd command for Jira synchronization.\n\n**Requires**: Both import and export scripts working\n\n**Features**:\n- `bd jira sync --pull` - Import from Jira to beads\n- `bd jira sync --push` - Export from beads to Jira\n- `bd jira sync` - Bidirectional (pull then push, with conflict resolution)\n- `bd jira status` - Show sync status and last sync time\n\n**Conflict resolution**:\n- Timestamp-based: newer update wins\n- Option for --prefer-local or --prefer-jira to override\n- Interactive mode for manual conflict resolution (optional)\n\n**Integration**:\n- Uses jira.* config settings from bd config\n- Stores last sync timestamp in config\n- Logs sync activity for audit\n\n**Stretch goals**:\n- Webhook integration for real-time sync\n- Selective sync by JQL filter","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-11-30T12:56:27.716537-08:00","updated_at":"2025-11-30T15:25:37.896045-08:00","closed_at":"2025-11-30T15:25:37.896045-08:00","dependencies":[{"issue_id":"bd-clg","depends_on_id":"bd-qvj","type":"parent-child","created_at":"2025-11-30T12:56:49.796568-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-tjn","type":"blocks","created_at":"2025-11-30T12:57:00.075288-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-93d","type":"blocks","created_at":"2025-11-30T12:57:05.206431-08:00","created_by":"stevey"}]}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user