Add template support for issue creation (bd-164b)

- Built-in templates: epic, bug, feature (embedded in binary)
- Custom templates in .beads/templates/ (override built-ins)
- Commands: bd template list/show/create
- Flag: bd create --from-template <name> "Title"
- Template fields: description, type, priority, labels, design, acceptance
- Security: sanitize template names to prevent path traversal
- Flag precedence: explicit flags override template defaults
- Tests: template loading, security, flag precedence
- Docs: commands/template.md and README.md updated

Closes bd-164b

Amp-Thread-ID: https://ampcode.com/threads/T-118fe54f-b112-4f99-a3d9-b7df53fb7284
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-03 20:31:11 -08:00
parent d2328f23bf
commit eb434dd08c
19 changed files with 1192 additions and 327 deletions

View File

@@ -2,8 +2,10 @@
{"id":"bd-164b","content_hash":"fd9fc7c9e966b68b55c7e60f4b9d4f59581eb79c95c4ae21cf94d2bd8a6fc5f5","title":"Add template support for issue creation","description":"Support creating issues from predefined templates to streamline common workflows like epics, bug reports, or feature proposals.\n\nExample usage:\n bd create --from-template epic \"Phase 3 Features\"\n bd create --from-template bug \"Login failure\"\n bd template list\n bd template create epic\n\nTemplates should include:\n- Pre-filled description structure\n- Suggested priority and type\n- Common labels\n- Design/acceptance criteria sections\n\nImplementation notes:\n- Store templates in .beads/templates/ directory\n- Support YAML or JSON format\n- Ship with built-in templates (epic, bug, feature)\n- Allow custom project-specific templates","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.985902-08:00","updated_at":"2025-11-03T19:56:41.287303-08:00","closed_at":"2025-11-03T19:56:41.287303-08:00"} {"id":"bd-164b","content_hash":"fd9fc7c9e966b68b55c7e60f4b9d4f59581eb79c95c4ae21cf94d2bd8a6fc5f5","title":"Add template support for issue creation","description":"Support creating issues from predefined templates to streamline common workflows like epics, bug reports, or feature proposals.\n\nExample usage:\n bd create --from-template epic \"Phase 3 Features\"\n bd create --from-template bug \"Login failure\"\n bd template list\n bd template create epic\n\nTemplates should include:\n- Pre-filled description structure\n- Suggested priority and type\n- Common labels\n- Design/acceptance criteria sections\n\nImplementation notes:\n- Store templates in .beads/templates/ directory\n- Support YAML or JSON format\n- Ship with built-in templates (epic, bug, feature)\n- Allow custom project-specific templates","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.985902-08:00","updated_at":"2025-11-03T19:56:41.287303-08:00","closed_at":"2025-11-03T19:56:41.287303-08:00"}
{"id":"bd-1vv","content_hash":"1db907ddb55edaf7a4c06a566c4e1b8244fcd9ba5d7e2fca4d5c053e424ac515","title":"Add WebSocket support","description":"## Feature Request\n\n[Describe the desired feature]\n\n## Motivation\n\n[Why is this feature needed? What problem does it solve?]\n\n## Use Cases\n\n1. **Use Case 1**: [description]\n2. **Use Case 2**: [description]\n\n## Proposed Solution\n\n[High-level approach to implementing this feature]\n\n## Alternatives Considered\n\n- **Alternative 1**: [description and why not chosen]\n- **Alternative 2**: [description and why not chosen]\n","design":"## Technical Design\n\n[Detailed technical approach]\n\n## API Changes\n\n[New commands, flags, or APIs]\n\n## Data Model Changes\n\n[Database schema changes if any]\n\n## Implementation Notes\n\n- Note 1\n- Note 2\n\n## Testing Strategy\n\n- Unit tests: [scope]\n- Integration tests: [scope]\n- Manual testing: [steps]\n","acceptance_criteria":"- [ ] Feature implements all described use cases\n- [ ] All tests pass\n- [ ] Documentation updated (README, commands)\n- [ ] Examples added if applicable\n- [ ] No performance regressions\n","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-03T19:56:41.271215-08:00","updated_at":"2025-11-03T19:56:41.271215-08:00","labels":["feature"]} {"id":"bd-1vv","content_hash":"1db907ddb55edaf7a4c06a566c4e1b8244fcd9ba5d7e2fca4d5c053e424ac515","title":"Add WebSocket support","description":"## Feature Request\n\n[Describe the desired feature]\n\n## Motivation\n\n[Why is this feature needed? What problem does it solve?]\n\n## Use Cases\n\n1. **Use Case 1**: [description]\n2. **Use Case 2**: [description]\n\n## Proposed Solution\n\n[High-level approach to implementing this feature]\n\n## Alternatives Considered\n\n- **Alternative 1**: [description and why not chosen]\n- **Alternative 2**: [description and why not chosen]\n","design":"## Technical Design\n\n[Detailed technical approach]\n\n## API Changes\n\n[New commands, flags, or APIs]\n\n## Data Model Changes\n\n[Database schema changes if any]\n\n## Implementation Notes\n\n- Note 1\n- Note 2\n\n## Testing Strategy\n\n- Unit tests: [scope]\n- Integration tests: [scope]\n- Manual testing: [steps]\n","acceptance_criteria":"- [ ] Feature implements all described use cases\n- [ ] All tests pass\n- [ ] Documentation updated (README, commands)\n- [ ] Examples added if applicable\n- [ ] No performance regressions\n","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-03T19:56:41.271215-08:00","updated_at":"2025-11-03T19:56:41.271215-08:00","labels":["feature"]}
{"id":"bd-35c7","content_hash":"9b46d0c0e124d960dc1bdf297640dea38727ab05a7b19591c81244ffccde8265","title":"Add label-based filtering to bd ready command","description":"Allow filtering ready work by labels to help organize work by sprint, week, or category.\n\nExample usage:\n bd ready --label week1-2\n bd ready --label frontend,high-priority\n\nThis helps teams organize work into batches and makes it easier for agents to focus on specific categories of work.\n\nImplementation notes:\n- Add --label flag to ready command\n- Support comma-separated labels (AND logic)\n- Should work with existing ready work logic (unblocked issues)","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.976536-08:00","updated_at":"2025-11-03T19:13:50.325022-08:00"} {"id":"bd-35c7","content_hash":"9b46d0c0e124d960dc1bdf297640dea38727ab05a7b19591c81244ffccde8265","title":"Add label-based filtering to bd ready command","description":"Allow filtering ready work by labels to help organize work by sprint, week, or category.\n\nExample usage:\n bd ready --label week1-2\n bd ready --label frontend,high-priority\n\nThis helps teams organize work into batches and makes it easier for agents to focus on specific categories of work.\n\nImplementation notes:\n- Add --label flag to ready command\n- Support comma-separated labels (AND logic)\n- Should work with existing ready work logic (unblocked issues)","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.976536-08:00","updated_at":"2025-11-03T19:13:50.325022-08:00"}
{"id":"bd-5iv","content_hash":"9260ad77a2fcc64520aa97f156bbf9d868d855b7a3b8491256b316f2563ca958","title":"Test Epic","description":"## Overview\n\n[Describe the high-level goal and scope of this epic]\n\n## Success Criteria\n\n- [ ] Criteria 1\n- [ ] Criteria 2\n- [ ] Criteria 3\n\n## Background\n\n[Provide context and motivation]\n\n## Scope\n\n**In Scope:**\n- Item 1\n- Item 2\n\n**Out of Scope:**\n- Item 1\n- Item 2\n","design":"## Architecture\n\n[Describe the overall architecture and approach]\n\n## Components\n\n- Component 1: [description]\n- Component 2: [description]\n\n## Dependencies\n\n[List external dependencies or constraints]\n","acceptance_criteria":"- [ ] All child issues are completed\n- [ ] Integration tests pass\n- [ ] Documentation is updated\n- [ ] Code review completed\n","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-03T20:15:03.864229-08:00","updated_at":"2025-11-03T20:15:03.864229-08:00","labels":["epic"]}
{"id":"bd-72w","content_hash":"4f61c75e6e438b0602085d31a889a40694c9c735d9030ea44e504fc948f9d157","title":"Q4 Platform Improvements","description":"## Overview\n\n[Describe the high-level goal and scope of this epic]\n\n## Success Criteria\n\n- [ ] Criteria 1\n- [ ] Criteria 2\n- [ ] Criteria 3\n\n## Background\n\n[Provide context and motivation]\n\n## Scope\n\n**In Scope:**\n- Item 1\n- Item 2\n\n**Out of Scope:**\n- Item 1\n- Item 2\n","design":"## Architecture\n\n[Describe the overall architecture and approach]\n\n## Components\n\n- Component 1: [description]\n- Component 2: [description]\n\n## Dependencies\n\n[List external dependencies or constraints]\n","acceptance_criteria":"- [ ] All child issues are completed\n- [ ] Integration tests pass\n- [ ] Documentation is updated\n- [ ] Code review completed\n","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-03T19:54:03.794244-08:00","updated_at":"2025-11-03T19:54:03.794244-08:00","labels":["epic"]} {"id":"bd-72w","content_hash":"4f61c75e6e438b0602085d31a889a40694c9c735d9030ea44e504fc948f9d157","title":"Q4 Platform Improvements","description":"## Overview\n\n[Describe the high-level goal and scope of this epic]\n\n## Success Criteria\n\n- [ ] Criteria 1\n- [ ] Criteria 2\n- [ ] Criteria 3\n\n## Background\n\n[Provide context and motivation]\n\n## Scope\n\n**In Scope:**\n- Item 1\n- Item 2\n\n**Out of Scope:**\n- Item 1\n- Item 2\n","design":"## Architecture\n\n[Describe the overall architecture and approach]\n\n## Components\n\n- Component 1: [description]\n- Component 2: [description]\n\n## Dependencies\n\n[List external dependencies or constraints]\n","acceptance_criteria":"- [ ] All child issues are completed\n- [ ] Integration tests pass\n- [ ] Documentation is updated\n- [ ] Code review completed\n","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-03T19:54:03.794244-08:00","updated_at":"2025-11-03T19:54:03.794244-08:00","labels":["epic"]}
{"id":"bd-74ee","content_hash":"6d1a4a691f8c06bdf6e770401debb18e5330ad694cf4360ca00726c0ac2fbf44","title":"Frontend task","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-03T19:11:59.358631-08:00","updated_at":"2025-11-03T19:11:59.358631-08:00","labels":["frontend","week1"]} {"id":"bd-74ee","content_hash":"6d1a4a691f8c06bdf6e770401debb18e5330ad694cf4360ca00726c0ac2fbf44","title":"Frontend task","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-03T19:11:59.358631-08:00","updated_at":"2025-11-03T19:11:59.358631-08:00","labels":["frontend","week1"]}
{"id":"bd-78w","content_hash":"ed3e38fda8a1f5f8335b605c7cac5c8e644052bf58d9ee2981e8bd5019ad2bc4","title":"Test Epic 2","description":"## Overview\n\n[Describe the high-level goal and scope of this epic]\n\n## Success Criteria\n\n- [ ] Criteria 1\n- [ ] Criteria 2\n- [ ] Criteria 3\n\n## Background\n\n[Provide context and motivation]\n\n## Scope\n\n**In Scope:**\n- Item 1\n- Item 2\n\n**Out of Scope:**\n- Item 1\n- Item 2\n","design":"## Architecture\n\n[Describe the overall architecture and approach]\n\n## Components\n\n- Component 1: [description]\n- Component 2: [description]\n\n## Dependencies\n\n[List external dependencies or constraints]\n","acceptance_criteria":"- [ ] All child issues are completed\n- [ ] Integration tests pass\n- [ ] Documentation is updated\n- [ ] Code review completed\n","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-03T20:15:03.878216-08:00","updated_at":"2025-11-03T20:15:03.878216-08:00","labels":["epic"]}
{"id":"bd-9b13","content_hash":"83a72169a32e4c0bd6fa48334900d52ec08c3573b3ed0bd9bd5178ede9720c49","title":"Backend task","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-03T19:11:59.359262-08:00","updated_at":"2025-11-03T19:11:59.359262-08:00","labels":["backend","week1"]} {"id":"bd-9b13","content_hash":"83a72169a32e4c0bd6fa48334900d52ec08c3573b3ed0bd9bd5178ede9720c49","title":"Backend task","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-03T19:11:59.359262-08:00","updated_at":"2025-11-03T19:11:59.359262-08:00","labels":["backend","week1"]}
{"id":"bd-cb2f","content_hash":"99b9c1c19d5e9f38308d78f09763426777797f133d4c86edd579419e7ba4043f","title":"Week 1 task","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-03T19:11:59.358093-08:00","updated_at":"2025-11-03T19:11:59.358093-08:00","labels":["frontend","week2"]} {"id":"bd-cb2f","content_hash":"99b9c1c19d5e9f38308d78f09763426777797f133d4c86edd579419e7ba4043f","title":"Week 1 task","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-03T19:11:59.358093-08:00","updated_at":"2025-11-03T19:11:59.358093-08:00","labels":["frontend","week2"]}
{"id":"bd-e044","content_hash":"1bfd3f688bb25d9714b5d7af7c2297753d08cc7d1fb5dd5219555c40f33ee07f","title":"Add mermaid output format for bd dep tree","description":"Add visual dependency graph output using Mermaid format for better visualization of issue relationships.\n\nExample usage:\n bd dep tree --format mermaid \u003cissue-id\u003e\n bd dep tree --format mermaid bd-42 \u003e graph.md\n\nThis would output Mermaid syntax that can be rendered in GitHub, documentation sites, or Mermaid live editor.\n\nImplementation notes:\n- Add --format flag to dep tree command\n- Support 'text' (default) and 'mermaid' formats\n- Mermaid graph should show issue IDs, titles, and dependency types\n- Consider using flowchart LR or graph TD syntax","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.978383-08:00","updated_at":"2025-11-03T18:10:18.978383-08:00"} {"id":"bd-e044","content_hash":"1bfd3f688bb25d9714b5d7af7c2297753d08cc7d1fb5dd5219555c40f33ee07f","title":"Add mermaid output format for bd dep tree","description":"Add visual dependency graph output using Mermaid format for better visualization of issue relationships.\n\nExample usage:\n bd dep tree --format mermaid \u003cissue-id\u003e\n bd dep tree --format mermaid bd-42 \u003e graph.md\n\nThis would output Mermaid syntax that can be rendered in GitHub, documentation sites, or Mermaid live editor.\n\nImplementation notes:\n- Add --format flag to dep tree command\n- Support 'text' (default) and 'mermaid' formats\n- Mermaid graph should show issue IDs, titles, and dependency types\n- Consider using flowchart LR or graph TD syntax","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.978383-08:00","updated_at":"2025-11-03T18:10:18.978383-08:00"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
name: performance-issue
description: |-
[Describe the issue]
## Additional Context
[Add relevant details]
type: task
priority: 2
labels: []
design: '[Design notes]'
acceptance_criteria: |-
- [ ] Acceptance criterion 1
- [ ] Acceptance criterion 2

View File

@@ -350,12 +350,21 @@ bd create "Task" -l "backend,urgent" --assignee alice
# Get JSON output for programmatic use # Get JSON output for programmatic use
bd create "Fix bug" -d "Description" --json bd create "Fix bug" -d "Description" --json
# Create from templates (built-in: epic, bug, feature)
bd create --from-template epic "Q4 Platform Improvements"
bd create --from-template bug "Auth token validation fails"
bd create --from-template feature "Add OAuth support"
# Override template defaults
bd create --from-template bug "Critical issue" -p 0 # Override priority
# Create multiple issues from a markdown file # Create multiple issues from a markdown file
bd create -f feature-plan.md bd create -f feature-plan.md
``` ```
Options: Options:
- `-f, --file` - Create multiple issues from markdown file - `-f, --file` - Create multiple issues from markdown file
- `--from-template` - Use template (epic, bug, feature, or custom)
- `-d, --description` - Issue description - `-d, --description` - Issue description
- `-p, --priority` - Priority (0-4, 0=highest, default=2) - `-p, --priority` - Priority (0-4, 0=highest, default=2)
- `-t, --type` - Type (bug|feature|task|epic|chore, default=task) - `-t, --type` - Type (bug|feature|task|epic|chore, default=task)
@@ -364,6 +373,8 @@ Options:
- `--id` - Explicit issue ID (e.g., `worker1-100` for ID space partitioning) - `--id` - Explicit issue ID (e.g., `worker1-100` for ID space partitioning)
- `--json` - Output in JSON format - `--json` - Output in JSON format
See `bd template list` for available templates and `bd help template` for managing custom templates.
### Viewing Issues ### Viewing Issues
```bash ```bash

View File

@@ -19,6 +19,7 @@ var createCmd = &cobra.Command{
Args: cobra.MinimumNArgs(0), // Changed to allow no args when using -f Args: cobra.MinimumNArgs(0), // Changed to allow no args when using -f
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
file, _ := cmd.Flags().GetString("file") file, _ := cmd.Flags().GetString("file")
fromTemplate, _ := cmd.Flags().GetString("from-template")
// If file flag is provided, parse markdown and create multiple issues // If file flag is provided, parse markdown and create multiple issues
if file != "" { if file != "" {
@@ -52,13 +53,51 @@ var createCmd = &cobra.Command{
fmt.Fprintf(os.Stderr, "Error: title required (or use --file to create from markdown)\n") fmt.Fprintf(os.Stderr, "Error: title required (or use --file to create from markdown)\n")
os.Exit(1) os.Exit(1)
} }
// Load template if specified
var tmpl *Template
if fromTemplate != "" {
var err error
tmpl, err = loadTemplate(fromTemplate)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Get field values, preferring explicit flags over template defaults
description, _ := cmd.Flags().GetString("description") description, _ := cmd.Flags().GetString("description")
if description == "" && tmpl != nil {
description = tmpl.Description
}
design, _ := cmd.Flags().GetString("design") design, _ := cmd.Flags().GetString("design")
if design == "" && tmpl != nil {
design = tmpl.Design
}
acceptance, _ := cmd.Flags().GetString("acceptance") acceptance, _ := cmd.Flags().GetString("acceptance")
if acceptance == "" && tmpl != nil {
acceptance = tmpl.AcceptanceCriteria
}
priority, _ := cmd.Flags().GetInt("priority") priority, _ := cmd.Flags().GetInt("priority")
if cmd.Flags().Changed("priority") == false && tmpl != nil {
priority = tmpl.Priority
}
issueType, _ := cmd.Flags().GetString("type") issueType, _ := cmd.Flags().GetString("type")
if !cmd.Flags().Changed("type") && tmpl != nil && tmpl.Type != "" {
// Flag not explicitly set and template has a type, use template
issueType = tmpl.Type
}
assignee, _ := cmd.Flags().GetString("assignee") assignee, _ := cmd.Flags().GetString("assignee")
labels, _ := cmd.Flags().GetStringSlice("labels") labels, _ := cmd.Flags().GetStringSlice("labels")
if len(labels) == 0 && tmpl != nil && len(tmpl.Labels) > 0 {
labels = tmpl.Labels
}
explicitID, _ := cmd.Flags().GetString("id") explicitID, _ := cmd.Flags().GetString("id")
parentID, _ := cmd.Flags().GetString("parent") parentID, _ := cmd.Flags().GetString("parent")
externalRef, _ := cmd.Flags().GetString("external-ref") externalRef, _ := cmd.Flags().GetString("external-ref")
@@ -259,6 +298,7 @@ var createCmd = &cobra.Command{
func init() { func init() {
createCmd.Flags().StringP("file", "f", "", "Create multiple issues from markdown file") createCmd.Flags().StringP("file", "f", "", "Create multiple issues from markdown file")
createCmd.Flags().String("from-template", "", "Create issue from template (e.g., 'epic', 'bug', 'feature')")
createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)") createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)")
createCmd.Flags().StringP("description", "d", "", "Issue description") createCmd.Flags().StringP("description", "d", "", "Issue description")
createCmd.Flags().String("design", "", "Design notes") createCmd.Flags().String("design", "", "Design notes")

View File

@@ -17,11 +17,20 @@ var readyCmd = &cobra.Command{
limit, _ := cmd.Flags().GetInt("limit") limit, _ := cmd.Flags().GetInt("limit")
assignee, _ := cmd.Flags().GetString("assignee") assignee, _ := cmd.Flags().GetString("assignee")
sortPolicy, _ := cmd.Flags().GetString("sort") sortPolicy, _ := cmd.Flags().GetString("sort")
labels, _ := cmd.Flags().GetStringSlice("label")
labelsAny, _ := cmd.Flags().GetStringSlice("label-any")
// Use global jsonOutput set by PersistentPreRun (respects config.yaml + env vars) // Use global jsonOutput set by PersistentPreRun (respects config.yaml + env vars)
// Normalize labels: trim, dedupe, remove empty
labels = normalizeLabels(labels)
labelsAny = normalizeLabels(labelsAny)
filter := types.WorkFilter{ filter := types.WorkFilter{
// Leave Status empty to get both 'open' and 'in_progress' (bd-165) // Leave Status empty to get both 'open' and 'in_progress' (bd-165)
Limit: limit, Limit: limit,
SortPolicy: types.SortPolicy(sortPolicy), SortPolicy: types.SortPolicy(sortPolicy),
Labels: labels,
LabelsAny: labelsAny,
} }
// Use Changed() to properly handle P0 (priority=0) // Use Changed() to properly handle P0 (priority=0)
if cmd.Flags().Changed("priority") { if cmd.Flags().Changed("priority") {
@@ -42,6 +51,8 @@ var readyCmd = &cobra.Command{
Assignee: assignee, Assignee: assignee,
Limit: limit, Limit: limit,
SortPolicy: sortPolicy, SortPolicy: sortPolicy,
Labels: labels,
LabelsAny: labelsAny,
} }
if cmd.Flags().Changed("priority") { if cmd.Flags().Changed("priority") {
priority, _ := cmd.Flags().GetInt("priority") priority, _ := cmd.Flags().GetInt("priority")
@@ -261,6 +272,8 @@ func init() {
readyCmd.Flags().IntP("priority", "p", 0, "Filter by priority") readyCmd.Flags().IntP("priority", "p", 0, "Filter by priority")
readyCmd.Flags().StringP("assignee", "a", "", "Filter by assignee") readyCmd.Flags().StringP("assignee", "a", "", "Filter by assignee")
readyCmd.Flags().StringP("sort", "s", "hybrid", "Sort policy: hybrid (default), priority, oldest") readyCmd.Flags().StringP("sort", "s", "hybrid", "Sort policy: hybrid (default), priority, oldest")
readyCmd.Flags().StringSliceP("label", "l", []string{}, "Filter by labels (AND: must have ALL). Can combine with --label-any")
readyCmd.Flags().StringSlice("label-any", []string{}, "Filter by labels (OR: must have AT LEAST ONE). Can combine with --label")
rootCmd.AddCommand(readyCmd) rootCmd.AddCommand(readyCmd)
rootCmd.AddCommand(blockedCmd) rootCmd.AddCommand(blockedCmd)
rootCmd.AddCommand(statsCmd) rootCmd.AddCommand(statsCmd)

310
cmd/bd/template.go Normal file
View File

@@ -0,0 +1,310 @@
package main
import (
"embed"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
//go:embed templates/*.yaml
var builtinTemplates embed.FS
// Template represents an issue template
type Template struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Type string `yaml:"type" json:"type"`
Priority int `yaml:"priority" json:"priority"`
Labels []string `yaml:"labels" json:"labels"`
Design string `yaml:"design" json:"design"`
AcceptanceCriteria string `yaml:"acceptance_criteria" json:"acceptance_criteria"`
}
var templateCmd = &cobra.Command{
Use: "template",
Short: "Manage issue templates",
Long: `Manage issue templates for streamlined issue creation.
Templates can be built-in (epic, bug, feature) or custom templates
stored in .beads/templates/ directory.`,
}
var templateListCmd = &cobra.Command{
Use: "list",
Short: "List available templates",
Run: func(cmd *cobra.Command, args []string) {
templates, err := loadAllTemplates()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading templates: %v\n", err)
os.Exit(1)
}
if jsonOutput {
outputJSON(templates)
return
}
// Group by source
builtins := []Template{}
customs := []Template{}
for _, tmpl := range templates {
if isBuiltinTemplate(tmpl.Name) {
builtins = append(builtins, tmpl)
} else {
customs = append(customs, tmpl)
}
}
green := color.New(color.FgGreen).SprintFunc()
blue := color.New(color.FgBlue).SprintFunc()
if len(builtins) > 0 {
fmt.Printf("%s\n", green("Built-in Templates:"))
for _, tmpl := range builtins {
fmt.Printf(" %s\n", blue(tmpl.Name))
fmt.Printf(" Type: %s, Priority: P%d\n", tmpl.Type, tmpl.Priority)
if len(tmpl.Labels) > 0 {
fmt.Printf(" Labels: %s\n", strings.Join(tmpl.Labels, ", "))
}
}
fmt.Println()
}
if len(customs) > 0 {
fmt.Printf("%s\n", green("Custom Templates:"))
for _, tmpl := range customs {
fmt.Printf(" %s\n", blue(tmpl.Name))
fmt.Printf(" Type: %s, Priority: P%d\n", tmpl.Type, tmpl.Priority)
if len(tmpl.Labels) > 0 {
fmt.Printf(" Labels: %s\n", strings.Join(tmpl.Labels, ", "))
}
}
fmt.Println()
}
if len(templates) == 0 {
fmt.Println("No templates available")
}
},
}
var templateShowCmd = &cobra.Command{
Use: "show <template-name>",
Short: "Show template details",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
templateName := args[0]
tmpl, err := loadTemplate(templateName)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if jsonOutput {
outputJSON(tmpl)
return
}
green := color.New(color.FgGreen).SprintFunc()
blue := color.New(color.FgBlue).SprintFunc()
fmt.Printf("%s %s\n", green("Template:"), blue(tmpl.Name))
fmt.Printf("Type: %s\n", tmpl.Type)
fmt.Printf("Priority: P%d\n", tmpl.Priority)
if len(tmpl.Labels) > 0 {
fmt.Printf("Labels: %s\n", strings.Join(tmpl.Labels, ", "))
}
fmt.Printf("\n%s\n%s\n", green("Description:"), tmpl.Description)
if tmpl.Design != "" {
fmt.Printf("\n%s\n%s\n", green("Design:"), tmpl.Design)
}
if tmpl.AcceptanceCriteria != "" {
fmt.Printf("\n%s\n%s\n", green("Acceptance Criteria:"), tmpl.AcceptanceCriteria)
}
},
}
var templateCreateCmd = &cobra.Command{
Use: "create <template-name>",
Short: "Create a custom template",
Long: `Create a custom template in .beads/templates/ directory.
This will create a template file that you can edit to customize
the default values for your common issue types.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
templateName := args[0]
// Sanitize template name
if err := sanitizeTemplateName(templateName); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Ensure .beads/templates directory exists
templatesDir := filepath.Join(".beads", "templates")
if err := os.MkdirAll(templatesDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating templates directory: %v\n", err)
os.Exit(1)
}
// Create template file
templatePath := filepath.Join(templatesDir, templateName+".yaml")
if _, err := os.Stat(templatePath); err == nil {
fmt.Fprintf(os.Stderr, "Error: template '%s' already exists\n", templateName)
os.Exit(1)
}
// Default template structure
tmpl := Template{
Name: templateName,
Description: "[Describe the issue]\n\n## Additional Context\n\n[Add relevant details]",
Type: "task",
Priority: 2,
Labels: []string{},
Design: "[Design notes]",
AcceptanceCriteria: "- [ ] Acceptance criterion 1\n- [ ] Acceptance criterion 2",
}
// Marshal to YAML
data, err := yaml.Marshal(tmpl)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating template: %v\n", err)
os.Exit(1)
}
// Write template file
if err := os.WriteFile(templatePath, data, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing template: %v\n", err)
os.Exit(1)
}
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf("%s Created template: %s\n", green("✓"), templatePath)
fmt.Printf("Edit the file to customize your template.\n")
},
}
func init() {
templateCmd.AddCommand(templateListCmd)
templateCmd.AddCommand(templateShowCmd)
templateCmd.AddCommand(templateCreateCmd)
rootCmd.AddCommand(templateCmd)
}
// loadAllTemplates loads both built-in and custom templates
func loadAllTemplates() ([]Template, error) {
templates := []Template{}
// Load built-in templates
builtins := []string{"epic", "bug", "feature"}
for _, name := range builtins {
tmpl, err := loadBuiltinTemplate(name)
if err != nil {
// Skip if not found (shouldn't happen with built-ins)
continue
}
templates = append(templates, *tmpl)
}
// Load custom templates from .beads/templates/
templatesDir := filepath.Join(".beads", "templates")
if _, err := os.Stat(templatesDir); err == nil {
entries, err := os.ReadDir(templatesDir)
if err != nil {
return nil, fmt.Errorf("reading templates directory: %w", err)
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
continue
}
name := strings.TrimSuffix(entry.Name(), ".yaml")
tmpl, err := loadCustomTemplate(name)
if err != nil {
// Skip invalid templates
continue
}
templates = append(templates, *tmpl)
}
}
return templates, nil
}
// sanitizeTemplateName validates template name to prevent path traversal
func sanitizeTemplateName(name string) error {
if name != filepath.Base(name) {
return fmt.Errorf("invalid template name '%s' (no path separators allowed)", name)
}
if strings.Contains(name, "..") {
return fmt.Errorf("invalid template name '%s' (no .. allowed)", name)
}
return nil
}
// loadTemplate loads a template by name (checks custom first, then built-in)
func loadTemplate(name string) (*Template, error) {
if err := sanitizeTemplateName(name); err != nil {
return nil, err
}
// Try custom templates first
tmpl, err := loadCustomTemplate(name)
if err == nil {
return tmpl, nil
}
// Fall back to built-in templates
return loadBuiltinTemplate(name)
}
// loadBuiltinTemplate loads a built-in template
func loadBuiltinTemplate(name string) (*Template, error) {
path := fmt.Sprintf("templates/%s.yaml", name)
data, err := builtinTemplates.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("template '%s' not found", name)
}
var tmpl Template
if err := yaml.Unmarshal(data, &tmpl); err != nil {
return nil, fmt.Errorf("parsing template: %w", err)
}
return &tmpl, nil
}
// loadCustomTemplate loads a custom template from .beads/templates/
func loadCustomTemplate(name string) (*Template, error) {
path := filepath.Join(".beads", "templates", name+".yaml")
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("template '%s' not found", name)
}
var tmpl Template
if err := yaml.Unmarshal(data, &tmpl); err != nil {
return nil, fmt.Errorf("parsing template: %w", err)
}
return &tmpl, nil
}
// isBuiltinTemplate checks if a template name is a built-in template
func isBuiltinTemplate(name string) bool {
builtins := map[string]bool{
"epic": true,
"bug": true,
"feature": true,
}
return builtins[name]
}

View File

@@ -0,0 +1,44 @@
package main
import (
"testing"
)
func TestSanitizeTemplateName(t *testing.T) {
tests := []struct {
name string
input string
wantError bool
}{
{"valid simple name", "epic", false},
{"valid with dash", "my-template", false},
{"valid with underscore", "my_template", false},
{"path traversal with ../", "../etc/passwd", true},
{"path traversal with ..", "..", true},
{"absolute path", "/etc/passwd", true},
{"relative path", "foo/bar", true},
{"hidden file", ".hidden", false}, // Hidden files are okay
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := sanitizeTemplateName(tt.input)
if (err != nil) != tt.wantError {
t.Errorf("sanitizeTemplateName(%q) error = %v, wantError %v", tt.input, err, tt.wantError)
}
})
}
}
func TestLoadTemplatePathTraversal(t *testing.T) {
// Try to load a template with path traversal
_, err := loadTemplate("../../../etc/passwd")
if err == nil {
t.Error("Expected error for path traversal, got nil")
}
_, err = loadTemplate("foo/bar")
if err == nil {
t.Error("Expected error for path with separator, got nil")
}
}

193
cmd/bd/template_test.go Normal file
View File

@@ -0,0 +1,193 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func TestLoadBuiltinTemplate(t *testing.T) {
tests := []struct {
name string
templateName string
wantType string
wantPriority int
wantHasLabels bool
}{
{
name: "epic template",
templateName: "epic",
wantType: "epic",
wantPriority: 1,
wantHasLabels: true,
},
{
name: "bug template",
templateName: "bug",
wantType: "bug",
wantPriority: 1,
wantHasLabels: true,
},
{
name: "feature template",
templateName: "feature",
wantType: "feature",
wantPriority: 2,
wantHasLabels: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl, err := loadBuiltinTemplate(tt.templateName)
if err != nil {
t.Fatalf("loadBuiltinTemplate() error = %v", err)
}
if tmpl.Type != tt.wantType {
t.Errorf("Type = %v, want %v", tmpl.Type, tt.wantType)
}
if tmpl.Priority != tt.wantPriority {
t.Errorf("Priority = %v, want %v", tmpl.Priority, tt.wantPriority)
}
if tt.wantHasLabels && len(tmpl.Labels) == 0 {
t.Errorf("Expected labels but got none")
}
if tmpl.Description == "" {
t.Errorf("Expected description but got empty string")
}
if tmpl.AcceptanceCriteria == "" {
t.Errorf("Expected acceptance criteria but got empty string")
}
})
}
}
func TestLoadBuiltinTemplateNotFound(t *testing.T) {
_, err := loadBuiltinTemplate("nonexistent")
if err == nil {
t.Errorf("Expected error for nonexistent template, got nil")
}
}
func TestLoadCustomTemplate(t *testing.T) {
// Create temporary directory for test
tmpDir := t.TempDir()
oldWd, _ := os.Getwd()
defer os.Chdir(oldWd)
os.Chdir(tmpDir)
// Create .beads/templates directory
templatesDir := filepath.Join(".beads", "templates")
if err := os.MkdirAll(templatesDir, 0755); err != nil {
t.Fatalf("Failed to create templates directory: %v", err)
}
// Create a custom template
customTemplate := `name: custom-test
description: Test custom template
type: chore
priority: 3
labels:
- test
- custom
design: Test design
acceptance_criteria: Test acceptance
`
templatePath := filepath.Join(templatesDir, "custom-test.yaml")
if err := os.WriteFile(templatePath, []byte(customTemplate), 0644); err != nil {
t.Fatalf("Failed to write template: %v", err)
}
// Load the custom template
tmpl, err := loadCustomTemplate("custom-test")
if err != nil {
t.Fatalf("loadCustomTemplate() error = %v", err)
}
if tmpl.Name != "custom-test" {
t.Errorf("Name = %v, want custom-test", tmpl.Name)
}
if tmpl.Type != "chore" {
t.Errorf("Type = %v, want chore", tmpl.Type)
}
if tmpl.Priority != 3 {
t.Errorf("Priority = %v, want 3", tmpl.Priority)
}
if len(tmpl.Labels) != 2 {
t.Errorf("Expected 2 labels, got %d", len(tmpl.Labels))
}
}
func TestLoadTemplate_PreferCustomOverBuiltin(t *testing.T) {
// Create temporary directory for test
tmpDir := t.TempDir()
oldWd, _ := os.Getwd()
defer os.Chdir(oldWd)
os.Chdir(tmpDir)
// Create .beads/templates directory
templatesDir := filepath.Join(".beads", "templates")
if err := os.MkdirAll(templatesDir, 0755); err != nil {
t.Fatalf("Failed to create templates directory: %v", err)
}
// Create a custom template with same name as builtin
customTemplate := `name: epic
description: Custom epic override
type: epic
priority: 0
labels:
- custom-epic
design: Custom design
acceptance_criteria: Custom acceptance
`
templatePath := filepath.Join(templatesDir, "epic.yaml")
if err := os.WriteFile(templatePath, []byte(customTemplate), 0644); err != nil {
t.Fatalf("Failed to write template: %v", err)
}
// loadTemplate should prefer custom over builtin
tmpl, err := loadTemplate("epic")
if err != nil {
t.Fatalf("loadTemplate() error = %v", err)
}
// Should get custom template (priority 0) not builtin (priority 1)
if tmpl.Priority != 0 {
t.Errorf("Priority = %v, want 0 (custom template)", tmpl.Priority)
}
if len(tmpl.Labels) != 1 || tmpl.Labels[0] != "custom-epic" {
t.Errorf("Expected custom-epic label, got %v", tmpl.Labels)
}
}
func TestIsBuiltinTemplate(t *testing.T) {
tests := []struct {
name string
template string
want bool
}{
{"epic is builtin", "epic", true},
{"bug is builtin", "bug", true},
{"feature is builtin", "feature", true},
{"custom is not builtin", "custom", false},
{"random is not builtin", "random", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isBuiltinTemplate(tt.template); got != tt.want {
t.Errorf("isBuiltinTemplate(%v) = %v, want %v", tt.template, got, tt.want)
}
})
}
}

56
cmd/bd/templates/bug.yaml Normal file
View File

@@ -0,0 +1,56 @@
# Built-in template for bug reports
name: bug
description: |
## Summary
[Brief description of the bug]
## Steps to Reproduce
1. Step 1
2. Step 2
3. Step 3
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happens]
## Environment
- OS: [e.g., macOS 15.7.1]
- Version: [e.g., bd 0.20.1]
- Additional context: [any relevant details]
## Additional Context
[Screenshots, logs, or other relevant information]
type: bug
priority: 1
labels:
- bug
design: |
## Root Cause Analysis
[Describe the underlying cause once identified]
## Proposed Fix
[Outline the solution approach]
## Impact Assessment
- Affected features: [list]
- Breaking changes: [yes/no and details]
- Migration needed: [yes/no and details]
acceptance_criteria: |
- [ ] Bug no longer reproduces with original steps
- [ ] Regression tests added
- [ ] Related edge cases tested
- [ ] Documentation updated if behavior changed

View File

@@ -0,0 +1,51 @@
# Built-in template for creating epics
name: epic
description: |
## Overview
[Describe the high-level goal and scope of this epic]
## Success Criteria
- [ ] Criteria 1
- [ ] Criteria 2
- [ ] Criteria 3
## Background
[Provide context and motivation]
## Scope
**In Scope:**
- Item 1
- Item 2
**Out of Scope:**
- Item 1
- Item 2
type: epic
priority: 1
labels:
- epic
design: |
## Architecture
[Describe the overall architecture and approach]
## Components
- Component 1: [description]
- Component 2: [description]
## Dependencies
[List external dependencies or constraints]
acceptance_criteria: |
- [ ] All child issues are completed
- [ ] Integration tests pass
- [ ] Documentation is updated
- [ ] Code review completed

View File

@@ -0,0 +1,60 @@
# Built-in template for feature requests
name: feature
description: |
## Feature Request
[Describe the desired feature]
## Motivation
[Why is this feature needed? What problem does it solve?]
## Use Cases
1. **Use Case 1**: [description]
2. **Use Case 2**: [description]
## Proposed Solution
[High-level approach to implementing this feature]
## Alternatives Considered
- **Alternative 1**: [description and why not chosen]
- **Alternative 2**: [description and why not chosen]
type: feature
priority: 2
labels:
- feature
design: |
## Technical Design
[Detailed technical approach]
## API Changes
[New commands, flags, or APIs]
## Data Model Changes
[Database schema changes if any]
## Implementation Notes
- Note 1
- Note 2
## Testing Strategy
- Unit tests: [scope]
- Integration tests: [scope]
- Manual testing: [steps]
acceptance_criteria: |
- [ ] Feature implements all described use cases
- [ ] All tests pass
- [ ] Documentation updated (README, commands)
- [ ] Examples added if applicable
- [ ] No performance regressions

337
commands/template.md Normal file
View File

@@ -0,0 +1,337 @@
# bd template
Manage issue templates for streamlined issue creation.
## Synopsis
Templates provide pre-filled structures for common issue types, making it faster to create well-formed issues with consistent formatting.
```bash
bd template list
bd template show <template-name>
bd template create <template-name>
```
## Description
Templates can be:
- **Built-in**: Provided by bd (epic, bug, feature)
- **Custom**: Stored in `.beads/templates/` directory
Each template defines default values for:
- Description structure with placeholders
- Issue type (bug, feature, task, epic, chore)
- Priority (0-4)
- Labels
- Design notes structure
- Acceptance criteria structure
## Commands
### list
List all available templates (built-in and custom).
```bash
bd template list
bd template list --json
```
**Examples:**
```bash
$ bd template list
Built-in Templates:
epic
Type: epic, Priority: P1
Labels: epic
bug
Type: bug, Priority: P1
Labels: bug
feature
Type: feature, Priority: P2
Labels: feature
```
### show
Show detailed structure of a specific template.
```bash
bd template show <template-name>
bd template show <template-name> --json
```
**Examples:**
```bash
$ bd template show bug
Template: bug
Type: bug
Priority: P1
Labels: bug
Description:
## Summary
[Brief description of the bug]
## Steps to Reproduce
...
```
### create
Create a custom template in `.beads/templates/` directory.
```bash
bd template create <template-name>
```
This creates a YAML file with default structure that you can edit to customize.
**Examples:**
```bash
$ bd template create performance
✓ Created template: .beads/templates/performance.yaml
Edit the file to customize your template.
$ cat .beads/templates/performance.yaml
name: performance
description: |-
[Describe the issue]
## Additional Context
[Add relevant details]
type: task
priority: 2
labels: []
design: '[Design notes]'
acceptance_criteria: |-
- [ ] Acceptance criterion 1
- [ ] Acceptance criterion 2
# Edit the template to customize it
$ vim .beads/templates/performance.yaml
```
## Using Templates with `bd create`
Use the `--from-template` flag to create issues from templates:
```bash
bd create --from-template <template-name> "Issue title"
```
Template values can be overridden with explicit flags:
```bash
# Use bug template but override priority
bd create --from-template bug "Login crashes on special chars" -p 0
# Use epic template but add extra labels
bd create --from-template epic "Q4 Infrastructure" -l infrastructure,ops
```
**Examples:**
```bash
# Create epic from template
$ bd create --from-template epic "Phase 3 Features"
✓ Created issue: bd-a3f8e9
Title: Phase 3 Features
Priority: P1
Status: open
# Create bug report from template
$ bd create --from-template bug "Auth token validation fails"
✓ Created issue: bd-42bc7a
Title: Auth token validation fails
Priority: P1
Status: open
# Use custom template
$ bd template create security-audit
$ bd create --from-template security-audit "Review authentication flow"
```
## Template File Format
Templates are YAML files with the following structure:
```yaml
name: template-name
description: |
Multi-line description with placeholders
## Section heading
[Placeholder text]
type: bug|feature|task|epic|chore
priority: 0-4
labels:
- label1
- label2
design: |
Design notes structure
acceptance_criteria: |
- [ ] Acceptance criterion 1
- [ ] Acceptance criterion 2
```
## Built-in Templates
### epic
For large features composed of multiple issues.
**Structure:**
- Overview and scope
- Success criteria checklist
- Background and motivation
- In-scope / out-of-scope sections
- Architecture design notes
- Component breakdown
**Defaults:**
- Type: epic
- Priority: P1
- Labels: epic
### bug
For bug reports with consistent structure.
**Structure:**
- Summary
- Steps to reproduce
- Expected vs actual behavior
- Environment details
- Root cause analysis (design)
- Proposed fix
- Impact assessment
**Defaults:**
- Type: bug
- Priority: P1
- Labels: bug
### feature
For feature requests and enhancements.
**Structure:**
- Feature description
- Motivation and use cases
- Proposed solution
- Alternatives considered
- Technical design
- API changes
- Testing strategy
**Defaults:**
- Type: feature
- Priority: P2
- Labels: feature
## Custom Templates
Custom templates override built-in templates with the same name. This allows you to customize built-in templates for your project.
**Priority:**
1. Custom templates in `.beads/templates/`
2. Built-in templates
**Example - Override bug template:**
```bash
# Create custom bug template
$ bd template create bug
# Edit to add project-specific fields
$ cat > .beads/templates/bug.yaml << 'EOF'
name: bug
description: |
## Bug Report
**Severity:** [critical|high|medium|low]
**Component:** [auth|api|frontend|backend]
## Description
[Describe the bug]
## Reproduction
1. Step 1
2. Step 2
## Impact
[Who is affected? How many users?]
type: bug
priority: 0
labels:
- bug
- needs-triage
design: |
## Investigation Notes
[Technical details]
acceptance_criteria: |
- [ ] Bug fixed and verified
- [ ] Tests added
- [ ] Monitoring added
EOF
# Now 'bd create --from-template bug' uses your custom template
```
## JSON Output
All template commands support `--json` flag for programmatic use:
```bash
$ bd template list --json
[
{
"name": "epic",
"description": "## Overview...",
"type": "epic",
"priority": 1,
"labels": ["epic"],
"design": "## Architecture...",
"acceptance_criteria": "- [ ] All child issues..."
}
]
$ bd template show bug --json
{
"name": "bug",
"description": "## Summary...",
"type": "bug",
"priority": 1,
"labels": ["bug"],
"design": "## Root Cause...",
"acceptance_criteria": "- [ ] Bug no longer..."
}
```
## Best Practices
1. **Use templates for consistency**: Establish team conventions for common issue types
2. **Customize built-ins**: Override built-in templates to match your workflow
3. **Version control templates**: Commit `.beads/templates/` to share across team
4. **Keep templates focused**: Create specific templates (e.g., `performance`, `security-audit`) rather than generic ones
5. **Use placeholders**: Mark sections requiring input with `[brackets]` or `TODO`
6. **Include checklists**: Use `- [ ]` for actionable items in description and acceptance criteria
## See Also
- [bd create](create.md) - Create issues
- [bd list](list.md) - List issues
- [README](../README.md) - Main documentation

14
go.mod
View File

@@ -7,28 +7,24 @@ toolchain go1.24.2
require ( require (
github.com/anthropics/anthropic-sdk-go v1.14.0 github.com/anthropics/anthropic-sdk-go v1.14.0
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/google/uuid v1.6.0 github.com/fsnotify/fsnotify v1.9.0
github.com/ncruces/go-sqlite3 v0.29.1
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
github.com/spf13/viper v1.21.0 github.com/spf13/viper v1.21.0
golang.org/x/mod v0.29.0 golang.org/x/mod v0.29.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.36.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
modernc.org/sqlite v1.38.2 gopkg.in/yaml.v3 v3.0.1
rsc.io/script v0.0.2 rsc.io/script v0.0.2
) )
require ( require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-sqlite3 v0.29.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/julianday v1.0.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
@@ -41,10 +37,6 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.37.0 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
) )

42
go.sum
View File

@@ -3,8 +3,6 @@ github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5C
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -15,10 +13,6 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -32,16 +26,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-sqlite3 v0.29.1 h1:NIi8AISWBToRHyoz01FXiTNvU147Tqdibgj2tFzJCqM= github.com/ncruces/go-sqlite3 v0.29.1 h1:NIi8AISWBToRHyoz01FXiTNvU147Tqdibgj2tFzJCqM=
github.com/ncruces/go-sqlite3 v0.29.1/go.mod h1:PpccBNNhvjwUOwDQEn2gXQPFPTWdlromj0+fSkd5KSg= github.com/ncruces/go-sqlite3 v0.29.1/go.mod h1:PpccBNNhvjwUOwDQEn2gXQPFPTWdlromj0+fSkd5KSg=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -78,18 +68,12 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
@@ -101,31 +85,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/script v0.0.2 h1:eYoG7A3GFC3z1pRx3A2+s/vZ9LA8cxojHyCvslnj4RI= rsc.io/script v0.0.2 h1:eYoG7A3GFC3z1pRx3A2+s/vZ9LA8cxojHyCvslnj4RI=
rsc.io/script v0.0.2/go.mod h1:cKBjCtFBBeZ0cbYFRXkRoxP+xGqhArPa9t3VWhtXfzU= rsc.io/script v0.0.2/go.mod h1:cKBjCtFBBeZ0cbYFRXkRoxP+xGqhArPa9t3VWhtXfzU=

View File

@@ -113,10 +113,12 @@ type ResolveIDArgs struct {
// ReadyArgs represents arguments for the ready operation // ReadyArgs represents arguments for the ready operation
type ReadyArgs struct { type ReadyArgs struct {
Assignee string `json:"assignee,omitempty"` Assignee string `json:"assignee,omitempty"`
Priority *int `json:"priority,omitempty"` Priority *int `json:"priority,omitempty"`
Limit int `json:"limit,omitempty"` Limit int `json:"limit,omitempty"`
SortPolicy string `json:"sort_policy,omitempty"` SortPolicy string `json:"sort_policy,omitempty"`
Labels []string `json:"labels,omitempty"`
LabelsAny []string `json:"labels_any,omitempty"`
} }
// StaleArgs represents arguments for the stale command // StaleArgs represents arguments for the stale command

View File

@@ -458,6 +458,8 @@ func (s *Server) handleReady(req *Request) Response {
Priority: readyArgs.Priority, Priority: readyArgs.Priority,
Limit: readyArgs.Limit, Limit: readyArgs.Limit,
SortPolicy: types.SortPolicy(readyArgs.SortPolicy), SortPolicy: types.SortPolicy(readyArgs.SortPolicy),
Labels: normalizeLabels(readyArgs.Labels),
LabelsAny: normalizeLabels(readyArgs.LabelsAny),
} }
if readyArgs.Assignee != "" { if readyArgs.Assignee != "" {
wf.Assignee = &readyArgs.Assignee wf.Assignee = &readyArgs.Assignee

View File

@@ -34,6 +34,36 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
args = append(args, *filter.Assignee) args = append(args, *filter.Assignee)
} }
// Label filtering (AND semantics)
if len(filter.Labels) > 0 {
for _, label := range filter.Labels {
whereClauses = append(whereClauses, `
EXISTS (
SELECT 1 FROM labels
WHERE issue_id = i.id AND label = ?
)
`)
args = append(args, label)
}
}
// Label filtering (OR semantics)
if len(filter.LabelsAny) > 0 {
placeholders := make([]string, len(filter.LabelsAny))
for i := range filter.LabelsAny {
placeholders[i] = "?"
}
whereClauses = append(whereClauses, fmt.Sprintf(`
EXISTS (
SELECT 1 FROM labels
WHERE issue_id = i.id AND label IN (%s)
)
`, strings.Join(placeholders, ",")))
for _, label := range filter.LabelsAny {
args = append(args, label)
}
}
// Build WHERE clause properly // Build WHERE clause properly
whereSQL := strings.Join(whereClauses, " AND ") whereSQL := strings.Join(whereClauses, " AND ")

View File

@@ -305,6 +305,8 @@ type WorkFilter struct {
Status Status Status Status
Priority *int Priority *int
Assignee *string Assignee *string
Labels []string // AND semantics: issue must have ALL these labels
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
Limit int Limit int
SortPolicy SortPolicy SortPolicy SortPolicy
} }