Add --parent flag support in daemon mode (bd-2e94)
- Added Parent field to CreateArgs RPC protocol - Updated CLI to pass parent ID to daemon instead of erroring - Added parent ID handling in RPC server to call GetNextChildID - Added validation to prevent both --id and --parent flags - Added comprehensive tests for hierarchical child creation - Resolves error: '--parent flag not yet supported in daemon mode' Amp-Thread-ID: https://ampcode.com/threads/T-3e0f76df-4ba6-4b16-bf75-bb7ea6b19541 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -148,22 +148,14 @@ var createCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// If parent is specified, generate child ID
|
||||
if parentID != "" {
|
||||
// In daemon mode, the parent will be sent to the RPC handler
|
||||
// In direct mode, we generate the child ID here
|
||||
if parentID != "" && daemonClient == nil {
|
||||
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")
|
||||
childID, err := store.GetNextChildID(ctx, parentID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
explicitID = childID // Set as explicit ID for the rest of the flow
|
||||
}
|
||||
@@ -213,6 +205,7 @@ var createCmd = &cobra.Command{
|
||||
if daemonClient != nil {
|
||||
createArgs := &rpc.CreateArgs{
|
||||
ID: explicitID,
|
||||
Parent: parentID,
|
||||
Title: title,
|
||||
Description: description,
|
||||
IssueType: issueType,
|
||||
|
||||
@@ -57,6 +57,7 @@ type Response struct {
|
||||
// CreateArgs represents arguments for the create operation
|
||||
type CreateArgs struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Parent string `json:"parent,omitempty"` // Parent ID for hierarchical issues
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
IssueType string `json:"issue_type"`
|
||||
|
||||
@@ -538,6 +538,116 @@ func TestDatabaseHandshake(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate_WithParent(t *testing.T) {
|
||||
_, client, cleanup := setupTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create parent issue
|
||||
parentArgs := &CreateArgs{
|
||||
Title: "Parent Epic",
|
||||
IssueType: "epic",
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
parentResp, err := client.Create(parentArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("Create parent failed: %v", err)
|
||||
}
|
||||
|
||||
var parent types.Issue
|
||||
if err := json.Unmarshal(parentResp.Data, &parent); err != nil {
|
||||
t.Fatalf("Failed to unmarshal parent: %v", err)
|
||||
}
|
||||
|
||||
// Create child issue using --parent flag
|
||||
childArgs := &CreateArgs{
|
||||
Parent: parent.ID,
|
||||
Title: "Child Task",
|
||||
IssueType: "task",
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
childResp, err := client.Create(childArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("Create child failed: %v", err)
|
||||
}
|
||||
|
||||
var child types.Issue
|
||||
if err := json.Unmarshal(childResp.Data, &child); err != nil {
|
||||
t.Fatalf("Failed to unmarshal child: %v", err)
|
||||
}
|
||||
|
||||
// Verify hierarchical ID format (should be parent.1)
|
||||
expectedID := parent.ID + ".1"
|
||||
if child.ID != expectedID {
|
||||
t.Errorf("Expected child ID %s, got %s", expectedID, child.ID)
|
||||
}
|
||||
|
||||
// Create second child
|
||||
child2Args := &CreateArgs{
|
||||
Parent: parent.ID,
|
||||
Title: "Second Child Task",
|
||||
IssueType: "task",
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
child2Resp, err := client.Create(child2Args)
|
||||
if err != nil {
|
||||
t.Fatalf("Create second child failed: %v", err)
|
||||
}
|
||||
|
||||
var child2 types.Issue
|
||||
if err := json.Unmarshal(child2Resp.Data, &child2); err != nil {
|
||||
t.Fatalf("Failed to unmarshal second child: %v", err)
|
||||
}
|
||||
|
||||
// Verify second child has incremented ID (parent.2)
|
||||
expectedID2 := parent.ID + ".2"
|
||||
if child2.ID != expectedID2 {
|
||||
t.Errorf("Expected second child ID %s, got %s", expectedID2, child2.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate_WithParentAndIDConflict(t *testing.T) {
|
||||
_, client, cleanup := setupTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create parent issue
|
||||
parentArgs := &CreateArgs{
|
||||
Title: "Parent Epic",
|
||||
IssueType: "epic",
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
parentResp, err := client.Create(parentArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("Create parent failed: %v", err)
|
||||
}
|
||||
|
||||
var parent types.Issue
|
||||
if err := json.Unmarshal(parentResp.Data, &parent); err != nil {
|
||||
t.Fatalf("Failed to unmarshal parent: %v", err)
|
||||
}
|
||||
|
||||
// Try to create with both ID and Parent (should fail)
|
||||
conflictArgs := &CreateArgs{
|
||||
ID: "bd-custom",
|
||||
Parent: parent.ID,
|
||||
Title: "Should Fail",
|
||||
IssueType: "task",
|
||||
Priority: 1,
|
||||
}
|
||||
|
||||
resp, err := client.Create(conflictArgs)
|
||||
if err == nil && resp.Success {
|
||||
t.Fatal("Expected error when both ID and Parent are specified")
|
||||
}
|
||||
|
||||
if !strings.Contains(resp.Error, "cannot specify both ID and Parent") {
|
||||
t.Errorf("Expected conflict error message, got: %s", resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate_DiscoveredFromInheritsSourceRepo(t *testing.T) {
|
||||
_, client, cleanup := setupTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -95,7 +95,29 @@ func (s *Server) handleCreate(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for conflicting flags
|
||||
if createArgs.ID != "" && createArgs.Parent != "" {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: "cannot specify both ID and Parent",
|
||||
}
|
||||
}
|
||||
|
||||
store := s.storage
|
||||
ctx := s.reqCtx(req)
|
||||
|
||||
// If parent is specified, generate child ID
|
||||
issueID := createArgs.ID
|
||||
if createArgs.Parent != "" {
|
||||
childID, err := store.GetNextChildID(ctx, createArgs.Parent)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to generate child ID: %v", err),
|
||||
}
|
||||
}
|
||||
issueID = childID
|
||||
}
|
||||
|
||||
var design, acceptance, assignee *string
|
||||
if createArgs.Design != "" {
|
||||
@@ -109,7 +131,7 @@ func (s *Server) handleCreate(req *Request) Response {
|
||||
}
|
||||
|
||||
issue := &types.Issue{
|
||||
ID: createArgs.ID,
|
||||
ID: issueID,
|
||||
Title: createArgs.Title,
|
||||
Description: createArgs.Description,
|
||||
IssueType: types.IssueType(createArgs.IssueType),
|
||||
@@ -119,8 +141,6 @@ func (s *Server) handleCreate(req *Request) Response {
|
||||
Assignee: strValue(assignee),
|
||||
Status: types.StatusOpen,
|
||||
}
|
||||
|
||||
ctx := s.reqCtx(req)
|
||||
|
||||
// Check if any dependencies are discovered-from type
|
||||
// If so, inherit source_repo from the parent issue
|
||||
|
||||
Reference in New Issue
Block a user