Skip to content
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `milestones list|get|create|update|delete` command group for managing Linear project milestones.
- `--milestone` / `-m` on `issues list`, `issues create`, and `issues update` to filter or assign issues by project milestone.
- Issue text and JSON output now includes milestone information when available.

## [1.9.0] - 2026-05-21

### Added
Expand Down
21 changes: 20 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ team: CEN
project: my-project # optional — used when --project flag is omitted
```

When set, commands with `--project` (`issues list`, `issues create`, `issues update`, `search`, `deps`) will use this default. Explicit `--project` flags always override it.
When set, commands with `--project` (`issues list`, `issues create`, `issues update`, `milestones`, `search`, `deps`) will use this default. Explicit `--project` flags always override it.

### Authentication Modes

Expand Down Expand Up @@ -109,6 +109,7 @@ linear issues get CEN-123 --format minimal --output json
- `issues list`, `issues get`
- `cycles list`, `cycles get`, `cycles analyze`
- `projects list`, `projects get`
- `milestones list`, `milestones get`, `milestones create`, `milestones update`
- `teams list`, `teams get`, `teams labels`, `teams states`
- `users list`, `users get`, `users me`
- `search` (all search operations)
Expand All @@ -131,6 +132,9 @@ linear issues list --priority 1 --format full
# Get issues in specific cycle
linear issues list --cycle 65 --format full

# Get issues in a project milestone
linear issues list --project "Q3 Launch" --milestone Beta --format full

# Filter by assignee
linear issues list --assignee me --format full

Expand Down Expand Up @@ -166,9 +170,24 @@ linear issues create "Implement feature" \
--assignee me \
--estimate 5 \
--cycle 65 \
--project "Q3 Launch" \
--milestone Beta \
--labels "backend,security"
```

#### Milestone Operations
```bash
# List milestones for a project
linear milestones list --project "Q3 Launch"

# Create and update milestones
linear milestones create Beta --project "Q3 Launch" --target-date 2026-08-01
linear milestones update Beta --project "Q3 Launch" --name "Private beta"

# Assign an issue to a milestone
linear issues update CEN-123 --project "Q3 Launch" --milestone Beta
```

#### Working with Team Context
- Cycle numbers (65, 66) require team context from `linear init`
- Issue identifiers (CEN-123) work without team context
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ linear auth login
- [Search](#search)
- [Dependencies](#dependencies)
- [Projects](#projects)
- [Milestones](#milestones)
- [Cycles](#cycles)
- [Teams](#teams)
- [Labels](#labels)
Expand Down Expand Up @@ -474,6 +475,9 @@ linear issues list --project "Q1 Release" # Filter by project name or UUID
linear search "auth" --project "Q1 Release" # Works with search too
linear deps --team ENG --project "Q1 Release" # Works with deps too

# Filter by project milestone
linear issues list --project "Q1 Release" --milestone "Beta"

# Filter by state (comma-separated)
linear issues list --state "Backlog,Todo,In Progress" --team ENG

Expand Down Expand Up @@ -504,6 +508,7 @@ linear issues create "Implement OAuth2 login" \
--assignee me \
--estimate 5 \
--cycle current \
--milestone "Beta" \
--labels "backend,security" \
--due 2026-03-31

Expand All @@ -520,6 +525,7 @@ linear issues update ENG-123 \
--state Done \
--assignee alice \
--priority 1 \
--milestone "Beta" \
--labels "urgent,hotfix" \
--due 2026-02-15

Expand Down Expand Up @@ -642,6 +648,18 @@ linear projects create "Q1 Release" --team ENG
linear projects update PROJECT-ID --state completed
```

### Milestones

Project milestones group issues inside a Linear project:

```bash
linear milestones list --project "Q1 Release"
linear milestones get "Beta" --project "Q1 Release"
linear milestones create "Beta" --project "Q1 Release" --target-date 2026-08-01
linear milestones update "Beta" --project "Q1 Release" --name "Private beta"
linear milestones delete MILESTONE-ID --project "Q1 Release"
```

### Cycles

```bash
Expand Down
4 changes: 3 additions & 1 deletion internal/cli/dependencies.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package cli

import (
"github.com/joa23/linear-cli/pkg/linear"
"github.com/joa23/linear-cli/internal/service"
"github.com/joa23/linear-cli/pkg/linear"
)

// Dependencies holds all injectable dependencies for CLI commands
Expand All @@ -15,6 +15,7 @@ type Dependencies struct {
Issues service.IssueServiceInterface
Cycles service.CycleServiceInterface
Projects service.ProjectServiceInterface
Milestones service.MilestoneServiceInterface
Search service.SearchServiceInterface
Teams service.TeamServiceInterface
Users service.UserServiceInterface
Expand All @@ -33,6 +34,7 @@ func NewDependencies(client *linear.Client) *Dependencies {
Issues: services.Issues,
Cycles: services.Cycles,
Projects: services.Projects,
Milestones: services.Milestones,
Search: services.Search,
Teams: services.Teams,
Users: services.Users,
Expand Down
21 changes: 19 additions & 2 deletions internal/cli/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func newIssuesListCmd() *cobra.Command {
priority string
assignee string
cycle string
milestone string
labels string
excludeLabels string
sortBy string
Expand Down Expand Up @@ -86,7 +87,7 @@ TIP: Use --format full for detailed output, --format minimal for concise output.
--priority 1 \
--assignee johannes.zillmann@centrum-ai.com \
--cycle 65 \
--project "My Project" \
--milestone "Beta" \
--labels "customer,bug" \
--limit 50 \
--format full
Expand Down Expand Up @@ -163,6 +164,9 @@ TIP: Use --format full for detailed output, --format minimal for concise output.
if cycle != "" {
filters.CycleID = cycle
}
if milestone != "" {
filters.MilestoneID = milestone
}
if project != "" {
filters.ProjectID = project
}
Expand Down Expand Up @@ -205,6 +209,7 @@ TIP: Use --format full for detailed output, --format minimal for concise output.
cmd.Flags().StringVar(&priority, "priority", "", "Filter by priority: 0-4 or none/urgent/high/normal/low")
cmd.Flags().StringVarP(&assignee, "assignee", "a", "", "Filter by assignee (email or 'me')")
cmd.Flags().StringVarP(&cycle, "cycle", "c", "", "Filter by cycle (number, 'current', or 'next')")
cmd.Flags().StringVarP(&milestone, "milestone", "m", "", "Filter by project milestone (name or UUID)")
cmd.Flags().StringVarP(&labels, "labels", "l", "", "Filter by labels (comma-separated)")
cmd.Flags().StringVarP(&excludeLabels, "exclude-labels", "L", "", "Exclude issues with these labels (comma-separated)")
cmd.Flags().StringVarP(&sortBy, "sort", "s", "", "Sort by: created, updated")
Expand Down Expand Up @@ -289,6 +294,7 @@ func newIssuesCreateCmd() *cobra.Command {
labels string
cycle string
project string
milestone string
assignee string
dueDate string
parent string
Expand Down Expand Up @@ -322,6 +328,7 @@ TIP: Run 'linear init' first to set default team.`,
--assignee stefan@centrum-ai.com \
--estimate 5 \
--cycle 65 \
--milestone "Beta" \
--labels "backend,security" \
--blocked-by CEN-99 \
--depends-on CEN-98,CEN-97 \
Expand Down Expand Up @@ -414,6 +421,9 @@ TIP: Run 'linear init' first to set default team.`,
if project != "" {
input.ProjectID = project
}
if milestone != "" {
input.ProjectMilestoneID = milestone
}
if assignee != "" {
input.AssigneeID = assignee
}
Expand Down Expand Up @@ -449,6 +459,7 @@ TIP: Run 'linear init' first to set default team.`,
cmd.Flags().StringVarP(&labels, "labels", "l", "", "Comma-separated label names/IDs")
cmd.Flags().StringVarP(&cycle, "cycle", "c", "", "Cycle number or name (e.g., 'current', 'next')")
cmd.Flags().StringVarP(&project, "project", "P", "", ProjectFlagDescription)
cmd.Flags().StringVarP(&milestone, "milestone", "m", "", "Project milestone name or UUID")
cmd.Flags().StringVarP(&assignee, "assignee", "a", "", "Assignee name or email (use 'me' for yourself)")
cmd.Flags().StringVar(&dueDate, "due", "", "Due date YYYY-MM-DD")
cmd.Flags().StringVar(&parent, "parent", "", "Parent issue ID (for sub-issues)")
Expand All @@ -472,6 +483,7 @@ func newIssuesUpdateCmd() *cobra.Command {
removeLabels string
cycle string
project string
milestone string
assignee string
dueDate string
parent string
Expand Down Expand Up @@ -523,7 +535,7 @@ LABEL MODES:
return err
}

// Get team from flag or config (for cycle resolution)
// Get team from flag or config (for cycle resolution)
if team == "" {
team = GetDefaultTeam()
}
Expand All @@ -534,6 +546,7 @@ LABEL MODES:
priority != "" || estimate != "" || labels != "" ||
addLabels != "" || removeLabels != "" ||
cycle != "" || project != "" || assignee != "" ||
milestone != "" ||
dueDate != "" || parent != "" || dependsOn != "" || blockedBy != "" ||
len(attachFiles) > 0

Expand Down Expand Up @@ -604,6 +617,9 @@ LABEL MODES:
if project != "" {
input.ProjectID = &project
}
if milestone != "" {
input.ProjectMilestoneID = &milestone
}
if assignee != "" {
input.AssigneeID = &assignee
}
Expand Down Expand Up @@ -644,6 +660,7 @@ LABEL MODES:
cmd.Flags().StringVar(&removeLabels, "remove-labels", "", "Remove specific labels without affecting others (comma-separated)")
cmd.Flags().StringVarP(&cycle, "cycle", "c", "", "Update cycle number or name")
cmd.Flags().StringVarP(&project, "project", "P", "", ProjectFlagDescription)
cmd.Flags().StringVarP(&milestone, "milestone", "m", "", "Update project milestone name or UUID")
cmd.Flags().StringVarP(&assignee, "assignee", "a", "", "Update assignee name or email (use 'me' for yourself)")
cmd.Flags().StringVar(&dueDate, "due", "", "Update due date YYYY-MM-DD")
cmd.Flags().StringVar(&parent, "parent", "", "Update parent issue")
Expand Down
Loading