Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/linear-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@northguild/worktree": minor
---

Add `worktree branch --linear <ID>` to start a worktree from a Linear issue, with a `linear.apiKey` config key set by `worktree config`.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ line, `worktree: no default for <value>; pass <flag>`.
the worktree, starts nothing and exits `1`.
- **Source branch:** `defaultSourceBranch`, else `origin/main` with a warning.
- **Removal** has no default: `remove <branch> -f`.
- **Bounded calls:** GitHub and Jira requests 15 s, `git fetch` 60 s, `gh auth token` and the session listing
- **Bounded calls:** GitHub, Jira and Linear requests 15 s, `git fetch` 60 s, `gh auth token` and the session listing
10 s, the install 10 min. A command still running at its bound is sent SIGTERM, then SIGKILL 2 s later, so
one that ignores SIGTERM cannot hold the run open.

Expand All @@ -307,7 +307,7 @@ worktree branch --github 42 --json --agent-file brief.md
"warnings":[]}
```

- `issue` is `{provider:"github",number,url}`, `{provider:"jira",key,url}`, or `null`.
- `issue` is `{provider:"github",number,url}`, `{provider:"jira",key,url}`, `{provider:"linear",key,url}`, or `null`.
- `assigned` is `null` when no assignment was attempted. `herdr` and `agent` are `null` when skipped, and
`agent.command` leaves the brief out. `agent.name` is `null` where this CLI named nothing (a detached start).
- `installed` is `{ran:false,reason}` when skipped. A failed install still prints the whole document, with
Expand Down Expand Up @@ -459,4 +459,5 @@ MIT
- [x] Add `cleanup` command to cleanup stale worktrees
- [x] Integrate with GitHub for automated branch naming
- [x] Integrate with JIRA for automated branch naming
- [x] Integrate with Linear for automated branch naming
- [ ] Integrate with ClickUp for automated branch naming
4 changes: 2 additions & 2 deletions context/stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ confusing errors rather than obvious ones — see the prerequisites and *Prepare
bin/ oclif entry point — bin/run.js, produced by tsc
src/commands/ one file per CLI command; each default-exports a BaseCommand subclass
src/lib/ CLI helpers — git integration, validators, env, types, constants, cli
src/integrations/ GitHub and Jira integrations
src/integrations/ GitHub, Jira and Linear integrations
scripts/ version-sync script, run by `pnpm sync-version`; `release-notes.mjs`, which publish.yml uses to cut a version's CHANGELOG section
.changeset/ Changesets config and pending release notes (`.changeset/<name>.md`)
skills/ the usage skill, `skills/worktree/SKILL.md`; hand-written, shipped inside the npm package (package.json `files`) and installable with `npx skills add`. Nothing on npm discovers it since TanStack Intent went (`skills add` reads GitHub); it stays in `files` so an `npm i -g` install has a copy matching its own version on disk
Expand Down Expand Up @@ -80,7 +80,7 @@ untrue on one of these surfaces is fixed by the phase that makes it untrue, not
| Surface | Who reads it | What has to reach it |
|---|---|---|
| [`README.md`](../README.md) | someone deciding whether to install; npm renders it as the package page | the fast path — a new command, a renamed flag, a change to what the tool is for |
| `docs/src/app/docs/` | users looking something up, at https://northguild.github.io/worktree | any user-visible CLI behaviour. One page per command under `commands/` (7), plus `configuration/`, `getting-started/`, `faq/`, and `guides/` (6) |
| `docs/src/app/docs/` | users looking something up, at https://northguild.github.io/worktree | any user-visible CLI behaviour. One page per command under `commands/` (7), plus `configuration/`, `getting-started/`, `faq/`, and `guides/` (8) |
| [`docs/README.md`](../docs/README.md) | someone working on the docs app | how the docs app is run or built |
| [`docs/worker/README.md`](../docs/worker/README.md) | someone working on the chat proxy | the Worker's routes, secrets or deploy |
| `skills/worktree/SKILL.md` | agents that install the skill with `npx skills add`; shipped in the npm package; also embedded in the docs chat system prompt | a command or flag change that the skill describes — **but see the caveat below** |
Expand Down
29 changes: 25 additions & 4 deletions docs/src/app/docs/commands/branch/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ worktree branch --github <issue-number>
worktree branch --github <issue-number> --assign
worktree branch --github <issue-number> --no-assign
worktree branch --jira <issue-id>
worktree branch --linear <issue-id>
worktree branch <name> --agent "<prompt>"
worktree branch <name> --agent-file <path>
worktree branch <name> --agent-stdin
Expand All @@ -36,6 +37,8 @@ worktree branch --github 42 --assign
worktree branch --github 42 --no-assign
worktree branch --jira DEV-123
worktree branch --jira dev-123
worktree branch --linear ENG-123
worktree branch --linear eng-123
worktree branch feature/add-bulk-actions --agent "add bulk actions to the table"
worktree branch --github 42 --agent "implement the issue"
worktree branch --github 42 --agent-file brief.md
Expand Down Expand Up @@ -106,8 +109,9 @@ worktree config github.autoAssign true # always assign, stop asking
- The assignment happens before the worktree is created, so the question is never buried behind creation
output or a launched editor. Assigning is idempotent — GitHub does not replace existing assignees — so
retrying after a failed creation is harmless.
- `--assign` and `--no-assign` both require `--github` or `--jira`; on their own there is no issue to act
on. `--assign --jira` warns that Jira assignment is not supported yet and carries on.
- `--assign` and `--no-assign` both require `--github`, `--jira` or `--linear`; on their own there is no issue to act
on. `--assign --jira` and `--assign --linear` warn that assignment is not supported for that provider yet
and carry on.

## Installing dependencies

Expand Down Expand Up @@ -158,6 +162,23 @@ Jira authentication requires three configuration keys:
2. `northguild.worktree.jira.email` - Your Jira email address
3. `northguild.worktree.jira.apiToken` - Your Jira API token

## Linear Issue Branching

Use the `--linear` flag (short: `-l`) to create a suggested branch name from an issue in your Linear workspace. It
cannot be combined with `--github` or `--jira`. See the [Linear guide](/docs/guides/linear-integration).

- The issue identifier can be passed in any case (`eng-123`, `ENG-123`, etc.).
- The generated name is pre-filled in the prompt and still editable.
- Linear has no issue type, so the issue's labels pick the optional prefix, by exact name and ignoring case:
- `branchPrefix.bugfix` for a `bug` label
- `branchPrefix.chore` for a `chore` or `maintenance` label, when there is no `bug` label
- `branchPrefix.feature` for anything else, including no label

If no prefix is configured, `worktree` falls back to an empty prefix and still generates a valid branch name.

Linear authentication requires one configuration key, `northguild.worktree.linear.apiKey`, your Linear personal
API key. Without it the command offers to run `worktree config`, and when there is still no key (or the run is non-interactive) exits `2` with `missing_value`. Assigning the issue is not supported yet.

## Agent Dispatch

Use the `--agent` flag (short: `-a`) to hand the new worktree to a coding agent with a prompt, called the
Expand Down Expand Up @@ -253,7 +274,7 @@ person goes to stderr instead: the spinner lines, the env-file list, notes and w
| Key | Value |
| --- | --- |
| `path`, `branch`, `source` | the worktree's absolute path, its branch, and the ref it was created from |
| `issue` | `{provider:"github", number, url}` or `{provider:"jira", key, url}`; `null` when no issue was used. `url` is `null` when it is not known |
| `issue` | `{provider:"github", number, url}`, `{provider:"jira", key, url}` or `{provider:"linear", key, url}`; `null` when no issue was used. `url` is `null` when it is not known |
| `assigned` | `true` or `false` once an assignment was attempted; `null` when none was (no issue, `--no-assign`, or a configured `false`) |
| `envFilesCopied` | the env files copied, relative to the repository root; `[]` when there were none |
| `installed` | `{ran:true, command, inferred, ok}`, plus `reason` when `ok` is `false`; `{ran:false, reason}` when the step was skipped |
Expand All @@ -276,7 +297,7 @@ describing it goes to stderr as usual:
| `code` | Means | Exit |
| --- | --- | --- |
| `missing_value` | a value with no default was not given; `details` names it and the flag | `2` |
| `invalid_value` | a value or flag combination was refused: a bad branch name, an empty brief, a malformed `--github` or `--jira` issue id | `2` |
| `invalid_value` | a value or flag combination was refused: a bad branch name, an empty brief, a malformed `--github`, `--jira` or `--linear` issue id | `2` |
| `not_found` | something named does not exist, such as the `--source` branch | `2` |
| `timeout` | a call that is bounded did not answer in time | `1` |
| `failed` | anything else | `1` |
Expand Down
2 changes: 1 addition & 1 deletion docs/src/app/docs/commands/config/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ nothing and exits `0`.
- With `--list`, it only prints config names that currently have no value.
- Without `--list`, it only prompts for missing values.
- `--names` limits prompting to specific config keys. Naming keys is asking for them, so the optional group
confirms (Jira, GitHub, prefixes, opener, editor, Herdr, agent) are not asked for them.
confirms (Jira, GitHub, Linear, prefixes, opener, editor, Herdr, agent, post-create) are not asked for them.
- `--yes` (`-y`) makes the run [non-interactive](/docs/commands/branch#non-interactive-runs): nothing is asked.
Each key takes its default, or the value it already holds, and the run fails with exit `2` naming
`worktree config <key> <value>` for a key with neither, such as a token. Without `--names` the optional groups have no default and the run fails naming `--names <keys>`.
Expand Down
11 changes: 11 additions & 0 deletions docs/src/app/docs/configuration/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ title: Configuration
- `jira.host` (Your Jira workspace URL, example: `https://company.atlassian.net`)
- `jira.email` (Your Jira email address)
- `jira.apiToken` (Jira API token)
- `linear.apiKey` (Linear personal API key, example: `lin_api_...`)
- `branchPrefix.feature` (example: `feature/`)
- `branchPrefix.bugfix` (example: `fix/`)
- `branchPrefix.chore` (example: `chore/`)
Expand Down Expand Up @@ -166,6 +167,7 @@ worktree config github.token <token>
worktree config jira.host https://company.atlassian.net
worktree config jira.email your-email@example.com
worktree config jira.apiToken <api-token>
worktree config linear.apiKey <api-key>
worktree config branchPrefix.feature feature/
worktree config branchPrefix.bugfix fix/
worktree config branchPrefix.chore chore/
Expand Down Expand Up @@ -212,9 +214,18 @@ worktree config github.autoAssign true
- Branch prefix keys are optional. If missing, `worktree` uses no prefix.
- Issue IDs are case-insensitive (both `dev-123` and `DEV-123` work).

## Linear Integration Notes

- `worktree branch --linear <issue-id>` reads issues from the Linear workspace your key belongs to.
- `linear.apiKey` is the only Linear config key, and it is required.
- Linear has no issue type, so the branch prefix comes from the issue's labels: `bug` uses `branchPrefix.bugfix`,
`chore` or `maintenance` uses `branchPrefix.chore`, anything else uses `branchPrefix.feature`.
- Issue identifiers are case-insensitive (both `eng-123` and `ENG-123` work).

## Recommended Team Baseline

1. Agree on one source branch (`origin/main` for most teams).
2. Standardize branch naming conventions.
3. Keep editor command consistent across local setups.
4. For Jira integration, share workspace URL and branch prefix conventions.
5. For Linear integration, share which labels map to bug and chore work, since the label decides the prefix.
1 change: 1 addition & 0 deletions docs/src/app/docs/guides/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MetaRecord } from "nextra";
const meta: MetaRecord = {
"github-issue-integration": "GitHub Issue Integration",
"jira-integration": "Jira Issue Integration",
"linear-integration": "Linear Issue Integration",
"context-switching": "Context Switching",
"team-conventions": "Team Conventions",
"env-files": "Environment Files",
Expand Down
92 changes: 92 additions & 0 deletions docs/src/app/docs/guides/linear-integration/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Linear Issue Integration
---

# Linear Issue Integration

`worktree` can derive branch names directly from Linear issues in your Linear workspace.

## Basic Usage

```bash
worktree branch --linear ENG-123
worktree branch --linear eng-123
worktree branch -l ENG-123
```

Pass the issue identifier, the team key and number Linear shows on the issue (`ENG-123`). It is accepted in any
case. A pasted issue URL is not accepted.

The generated branch name is shown as the default in the prompt, so you can accept it or edit it before creation.

`--linear` cannot be combined with `--github` or `--jira`. `--assign` does not work with `--linear`: assigning a
Linear issue is not supported yet, so the flag warns `Assignment is not supported for Linear issues yet.` and the
worktree is still created.

## How Branch Names Are Built

1. Read the issue's identifier, title and labels from the Linear API.
2. Sanitize the title into a branch-safe slug.
3. Prepend a configured prefix chosen from the issue's labels.

The name is the prefix, the identifier and the slug, for example `fix/ENG-123-fix-login-redirect`. Keeping the
identifier in the name is what lets Linear's GitHub integration link the branch to the issue.

Linear has no issue type, so the prefix comes from the issue's labels. Labels are compared by exact name,
ignoring case:

- a `bug` label -> `branchPrefix.bugfix`
- otherwise a `chore` or `maintenance` label -> `branchPrefix.chore`
- otherwise (a `Feature` or `Improvement` label, any other label, or none) -> `branchPrefix.feature`

When an issue carries several of these labels, `bug` wins. A label such as `bugfix` or `Bug fix` is not `bug`,
so it does not match.

If a prefix key is not configured, no prefix is applied.

## What Is Read From the Issue

**Issue content is used for the branch name and nothing else.** `worktree` reads the issue's identifier, its title
and the names of its labels. The title is reduced to lowercase letters, digits and `-` before it becomes part of
the branch name, and the labels only select one of the configured `branchPrefix.*` values, or none. The issue
description and comments are never requested, never printed, never written to the `--json` document (whose `issue`
field carries only the provider, the identifier and the URL) and never passed to an agent.

With `--json`, `issue` is `{"provider":"linear","key":"ENG-123","url":"https://linear.app/..."}`. The `url` is the
one Linear returns, and is `null` only if Linear sent none.

Requests to Linear are cut off after 15 seconds.

## Authentication Resolution

Authentication uses one config key, `linear.apiKey`, read from local git config. In a terminal, `branch --linear`
offers to run `worktree config` when it is missing. Without a key after that, and in any non-interactive run, the
command exits with code `2` and `missing_value`, naming `worktree config linear.apiKey <key>`, before a worktree
is created.

## Suggested Setup

```bash
worktree config linear.apiKey <api-key>
worktree config branchPrefix.feature feature/
worktree config branchPrefix.bugfix fix/
worktree config branchPrefix.chore chore/
```

You can also run `worktree config` and answer yes to the Linear group, or configure only missing values:

```bash
worktree config --missing --names linear.apiKey,branchPrefix.feature,branchPrefix.bugfix,branchPrefix.chore
```

An empty answer to the key prompt keeps the key already stored rather than clearing it.

## Getting a Linear API Key

1. In Linear, open Settings -> Security & access -> Personal API keys
2. Click "New API key" and give it a label
3. Copy the key and use it with `worktree config linear.apiKey`

The key is stored locally in git config and never sent anywhere except to Linear. `worktree` never prints it:
any `lin_api_` key that would reach the output is replaced first. The key is checked the first time `--linear`
uses it; `worktree config` does not validate it.
2 changes: 2 additions & 0 deletions docs/src/app/docs/guides/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ asIndexPage: true
Practical workflows and conventions for teams using `worktree` every day.

- [GitHub Issue Integration](/docs/guides/github-issue-integration)
- [Jira Issue Integration](/docs/guides/jira-integration)
- [Linear Issue Integration](/docs/guides/linear-integration)
- [Context Switching](/docs/guides/context-switching)
- [Team Conventions](/docs/guides/team-conventions)
- [Environment Files](/docs/guides/env-files)
Expand Down
2 changes: 2 additions & 0 deletions docs/src/chat/components/MarkdownContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const DOCS_ROUTE_MAP: Record<string, string> = {
"github issue integration": "/docs/guides/github-issue-integration",
"jira issue integration": "/docs/guides/jira-integration",
"jira integration": "/docs/guides/jira-integration",
"linear issue integration": "/docs/guides/linear-integration",
"linear integration": "/docs/guides/linear-integration",
"context switching": "/docs/guides/context-switching",
"team conventions": "/docs/guides/team-conventions",
"environment files": "/docs/guides/env-files",
Expand Down
1 change: 1 addition & 0 deletions docs/src/chat/docs-routes.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export const VALID_DOC_ROUTES: ReadonlySet<string> = new Set([
"/docs/guides/github-issue-integration",
"/docs/guides/herdr-spaces",
"/docs/guides/jira-integration",
"/docs/guides/linear-integration",
"/docs/guides/team-conventions",
]);
Loading
Loading