diff --git a/cmd/bd/flags.go b/cmd/bd/flags.go index dfd55959..2d9c03f3 100644 --- a/cmd/bd/flags.go +++ b/cmd/bd/flags.go @@ -24,6 +24,8 @@ func registerCommonIssueFlags(cmd *cobra.Command) { // getDescriptionFlag retrieves the description value, checking --body-file, --description-file, // --description, and --body (in that order of precedence). +// Supports reading from stdin via --description=- or --body=- (useful when description +// contains apostrophes or other characters that are hard to escape in shell). // Returns the value and whether any flag was explicitly changed. func getDescriptionFlag(cmd *cobra.Command) (string, bool) { bodyFileChanged := cmd.Flags().Changed("body-file") @@ -64,10 +66,29 @@ func getDescriptionFlag(cmd *cobra.Command) (string, bool) { return content, true } + // Check if description or body is "-" (read from stdin) + // This provides a convenient shorthand: --description=- instead of --body-file=- + desc, _ := cmd.Flags().GetString("description") + body, _ := cmd.Flags().GetString("body") + + if desc == "-" || body == "-" { + // Error if both are set to different values + if descChanged && bodyChanged && desc != body { + fmt.Fprintf(os.Stderr, "Error: cannot specify both --description and --body with different values\n") + fmt.Fprintf(os.Stderr, " --description: %q\n", desc) + fmt.Fprintf(os.Stderr, " --body: %q\n", body) + os.Exit(1) + } + content, err := readBodyFile("-") + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err) + os.Exit(1) + } + return content, true + } + // Error if both description and body are specified with different values if descChanged && bodyChanged { - desc, _ := cmd.Flags().GetString("description") - body, _ := cmd.Flags().GetString("body") if desc != body { fmt.Fprintf(os.Stderr, "Error: cannot specify both --description and --body with different values\n") fmt.Fprintf(os.Stderr, " --description: %q\n", desc) @@ -78,11 +99,9 @@ func getDescriptionFlag(cmd *cobra.Command) (string, bool) { // Return whichever was set (or description's value if neither) if bodyChanged { - body, _ := cmd.Flags().GetString("body") return body, true } - desc, _ := cmd.Flags().GetString("description") return desc, descChanged } diff --git a/internal/storage/sqlite/issues.go b/internal/storage/sqlite/issues.go index 1dcfae07..c3e4bcfb 100644 --- a/internal/storage/sqlite/issues.go +++ b/internal/storage/sqlite/issues.go @@ -36,11 +36,6 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error pinned = 1 } - pinned := 0 - if issue.Pinned { - pinned = 1 - } - _, err := conn.ExecContext(ctx, ` INSERT OR IGNORE INTO issues ( id, content_hash, title, description, design, acceptance_criteria, notes, @@ -100,11 +95,6 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er pinned = 1 } - pinned := 0 - if issue.Pinned { - pinned = 1 - } - _, err = stmt.ExecContext(ctx, issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, issue.Status, diff --git a/internal/storage/sqlite/schema.go b/internal/storage/sqlite/schema.go index 9b93321a..a433a6ac 100644 --- a/internal/storage/sqlite/schema.go +++ b/internal/storage/sqlite/schema.go @@ -34,8 +34,6 @@ CREATE TABLE IF NOT EXISTS issues ( pinned INTEGER DEFAULT 0, -- NOTE: replies_to, relates_to, duplicate_of, superseded_by removed per Decision 004 -- These relationships are now stored in the dependencies table - -- Workflow fields - pinned INTEGER DEFAULT 0, CHECK ((status = 'closed') = (closed_at IS NOT NULL)) );