Implement hierarchical child ID generation (bd-171)

- Add GetNextChildID to storage interface for generating child IDs
- Implement in SQLiteStorage with atomic counter using child_counters table
- Implement in MemoryStorage with in-memory counter
- Add --parent flag to bd create command
- Support hierarchical IDs (bd-a3f8e9.1, bd-a3f8e9.1.5) in CreateIssue
- Validate parent exists when creating hierarchical issues
- Enforce max depth of 3 levels
- Update ID validation to accept hierarchical IDs with dots
- Add comprehensive tests for child ID generation
- Manual testing confirms: sequential children, nested hierarchies, depth enforcement
This commit is contained in:
Steve Yegge
2025-10-30 14:42:08 -07:00
parent 6c31329ef8
commit 3ed2aa07cb
6 changed files with 319 additions and 13 deletions

View File

@@ -60,28 +60,54 @@ var createCmd = &cobra.Command{
assignee, _ := cmd.Flags().GetString("assignee")
labels, _ := cmd.Flags().GetStringSlice("labels")
explicitID, _ := cmd.Flags().GetString("id")
parentID, _ := cmd.Flags().GetString("parent")
externalRef, _ := cmd.Flags().GetString("external-ref")
deps, _ := cmd.Flags().GetStringSlice("deps")
forceCreate, _ := cmd.Flags().GetBool("force")
jsonOutput, _ := cmd.Flags().GetBool("json")
// Validate explicit ID format if provided (prefix-number)
if explicitID != "" {
// Check format: must contain hyphen and have numeric suffix
parts := strings.Split(explicitID, "-")
if len(parts) != 2 {
fmt.Fprintf(os.Stderr, "Error: invalid ID format '%s' (expected format: prefix-number, e.g., 'bd-42')\n", explicitID)
// Check for conflicting flags
if explicitID != "" && parentID != "" {
fmt.Fprintf(os.Stderr, "Error: cannot specify both --id and --parent flags\n")
os.Exit(1)
}
// If parent is specified, generate child ID
if parentID != "" {
ctx := context.Background()
var childID string
var err error
if daemonClient != nil {
// TODO: Add RPC support for GetNextChildID (bd-171)
fmt.Fprintf(os.Stderr, "Error: --parent flag not yet supported in daemon mode\n")
os.Exit(1)
} else {
// Direct mode - use storage
childID, err = store.GetNextChildID(ctx, parentID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Validate numeric suffix
if _, err := fmt.Sscanf(parts[1], "%d", new(int)); err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid ID format '%s' (numeric suffix required, e.g., 'bd-42')\n", explicitID)
explicitID = childID // Set as explicit ID for the rest of the flow
}
// Validate explicit ID format if provided
// Supports: prefix-number (bd-42), prefix-hash (bd-a3f8e9), or hierarchical (bd-a3f8e9.1)
if explicitID != "" {
// Must contain hyphen
if !strings.Contains(explicitID, "-") {
fmt.Fprintf(os.Stderr, "Error: invalid ID format '%s' (expected format: prefix-hash or prefix-hash.number, e.g., 'bd-a3f8e9' or 'bd-a3f8e9.1')\n", explicitID)
os.Exit(1)
}
// Extract prefix (before the first hyphen)
hyphenIdx := strings.Index(explicitID, "-")
requestedPrefix := explicitID[:hyphenIdx]
// Validate prefix matches database prefix (unless --force is used)
if !forceCreate {
requestedPrefix := parts[0]
ctx := context.Background()
// Get database prefix from config
@@ -96,8 +122,7 @@ var createCmd = &cobra.Command{
if dbPrefix != "" && dbPrefix != requestedPrefix {
fmt.Fprintf(os.Stderr, "Error: prefix mismatch detected\n")
fmt.Fprintf(os.Stderr, " This database uses prefix '%s-', but you specified '%s-'\n", dbPrefix, requestedPrefix)
fmt.Fprintf(os.Stderr, " Did you mean to create '%s-%s'?\n", dbPrefix, parts[1])
fmt.Fprintf(os.Stderr, " This database uses prefix '%s', but you specified '%s'\n", dbPrefix, requestedPrefix)
fmt.Fprintf(os.Stderr, " Use --force to create with mismatched prefix anyway\n")
os.Exit(1)
}
@@ -243,6 +268,7 @@ func init() {
createCmd.Flags().StringP("assignee", "a", "", "Assignee")
createCmd.Flags().StringSliceP("labels", "l", []string{}, "Labels (comma-separated)")
createCmd.Flags().String("id", "", "Explicit issue ID (e.g., 'bd-42' for partitioning)")
createCmd.Flags().String("parent", "", "Parent issue ID for hierarchical child (e.g., 'bd-a3f8e9')")
createCmd.Flags().String("external-ref", "", "External reference (e.g., 'gh-9', 'jira-ABC')")
createCmd.Flags().StringSlice("deps", []string{}, "Dependencies in format 'type:id' or 'id' (e.g., 'discovered-from:bd-20,blocks:bd-15' or 'bd-20')")
createCmd.Flags().Bool("force", false, "Force creation even if prefix doesn't match database prefix")