mintree 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  mintree wraps the steps you do manually every time a feature begins:
6
6
 
7
- 1. Pick an open issue or work item assigned to you (GitHub Issues or Plane).
7
+ 1. Pick an open issue or work item assigned to you (GitHub Issues or Linear).
8
8
  2. Create a git worktree on a branch named after that item, following the project's convention.
9
9
  3. Launch Claude Code inside the worktree with a session ID you can resume later.
10
10
  4. Live-track which Claude sessions are active, idle, or waiting.
11
11
 
12
- It is a smaller, opinionated cousin of [santree](https://github.com/santiagotoscanini/santree) — built on the same TypeScript + Ink + Pastel stack but stripped to the `<type>/<issue>-<desc>` branch convention and the two issue trackers most likely to be used by a small team: GitHub Issues and Plane.
12
+ It is a smaller, opinionated cousin of [santree](https://github.com/santiagotoscanini/santree) — built on the same TypeScript + Ink + Pastel stack but stripped to the `<type>/<issue>-<desc>` branch convention and the two issue trackers most likely to be used by a small team: GitHub Issues and Linear.
13
13
 
14
14
  ---
15
15
 
@@ -48,7 +48,7 @@ EOF
48
48
 
49
49
  `mintree doctor` should report **all required checks pass** before you continue. The most common gaps are:
50
50
 
51
- - `gh` not authenticated → `gh auth login` (still needed when using Plane — mintree uses `gh` for PR status on worktree branches)
51
+ - `gh` not authenticated → `gh auth login` (still needed when using Linear — mintree uses `gh` for PR status on worktree branches)
52
52
  - Claude Code not installed → `npm install -g @anthropic-ai/claude-code`
53
53
  - Shell integration not loaded → re-run the `echo … >> ~/.zshrc` step and start a new shell
54
54
 
@@ -64,8 +64,8 @@ cd path/to/repo
64
64
  # Default: GitHub Issues provider
65
65
  mintree init
66
66
 
67
- # Or: Plane provider
68
- mintree init --provider plane --workspace <your-workspace-slug>
67
+ # Or: Linear provider (--team is repeatable, one per team you pull work from)
68
+ mintree init --provider linear --workspace <your-workspace-slug> --team FE --team BE
69
69
 
70
70
  mintree helpers session-signal install # optional: live session state in the dashboard
71
71
  ```
@@ -77,40 +77,40 @@ mintree helpers session-signal install # optional: live session state in the d
77
77
  mintree supports two issue providers, selected per repo via `.mintree/metadata.json`:
78
78
 
79
79
  - **`github`** (default): lists issues assigned to you on the current repo via `gh`. Transitions to "In Progress" on a Projects v2 board when present.
80
- - **`plane`**: lists work items assigned to you across a configured set of [Plane](https://plane.so) projects, via the Plane REST API. Transitions to the project's "started" state on `w`.
80
+ - **`linear`**: lists issues assigned to you across a configured set of [Linear](https://linear.app) teams, via the Linear GraphQL API. Moves the issue to "In Progress" on `w`.
81
81
 
82
- After `mintree init --provider plane`, edit `.mintree/metadata.json` to add at least one project:
82
+ `mintree init --provider linear --workspace <slug> --team FE --team BE` scaffolds the metadata for you. If you skip `--team`, or want to tweak it later, edit `.mintree/metadata.json` so `linear.teams` lists at least one team key:
83
83
 
84
84
  ```json
85
85
  {
86
86
  "version": 1,
87
- "provider": "plane",
87
+ "provider": "linear",
88
88
  "issues": {},
89
- "plane": {
90
- "apiUrl": "https://api.plane.so",
89
+ "linear": {
90
+ "apiUrl": "https://api.linear.app/graphql",
91
91
  "workspaceSlug": "my-team",
92
- "projects": [
93
- { "id": "<project-uuid>", "identifier": "BACK", "name": "Backend" },
94
- { "id": "<project-uuid>", "identifier": "WEB", "name": "Web" }
92
+ "teams": [
93
+ { "key": "FE", "name": "Frontend" },
94
+ { "key": "BE", "name": "Backend" }
95
95
  ]
96
96
  }
97
97
  }
98
98
  ```
99
99
 
100
- You can find each project's UUID in the Plane URL (`app.plane.so/<workspace>/projects/<uuid>/...`). The `identifier` is the short prefix shown on work-item IDs (the `BACK` in `BACK-100`).
100
+ The `workspaceSlug` is the URL key of your Linear workspace (`linear.app/<slug>/...`). Each team `key` is the short prefix shown on issue IDs (the `FE` in `FE-123`); `name` is optional. Optional keys: `inProgressStateName` (override the workflow state `w` transitions to) and `protectedStateTypes` (workflow-state types `clean` won't touch).
101
101
 
102
- Authenticate by setting `PLANE_API_KEY` in your shell, or by writing the key to `~/.mintree/credentials.json`:
102
+ Authenticate by setting `LINEAR_API_KEY` in your shell, or by writing the key to `~/.mintree/credentials.json`:
103
103
 
104
104
  ```bash
105
- export PLANE_API_KEY=plane_api_XXXXXXXXXXXXXX
105
+ export LINEAR_API_KEY=lin_api_XXXXXXXXXXXXXX
106
106
  # or
107
107
  cat > ~/.mintree/credentials.json <<'EOF'
108
- { "plane": { "apiKey": "plane_api_XXXXXXXXXXXXXX" } }
108
+ { "linear": { "apiKey": "lin_api_XXXXXXXXXXXXXX" } }
109
109
  EOF
110
110
  chmod 600 ~/.mintree/credentials.json
111
111
  ```
112
112
 
113
- `mintree doctor` validates the key + each configured project's reachability when `provider === "plane"`.
113
+ The key goes straight into the `Authorization` header (no `Bearer` prefix). `mintree doctor` validates the key, resolves the viewer, and pings each configured team when `provider === "linear"`.
114
114
 
115
115
  ---
116
116
 
@@ -143,10 +143,11 @@ Same building blocks, scriptable from any shell:
143
143
  ```bash
144
144
  # Create a worktree, optionally launch Claude with an initial prompt
145
145
  mintree worktree create feat/100-validar-patente
146
- mintree worktree create feat/BACK-100-validar-patente --work --prompt "empezar BACK-100"
146
+ mintree worktree create feat/FE-123-validar-patente --work --prompt "empezar FE-123"
147
147
 
148
148
  # Resume Claude in the worktree you're currently inside
149
- cd .mintree/worktrees/BACK-100-validar-patente
149
+ # (the worktree dir is the bare issue id)
150
+ cd .mintree/worktrees/FE-123
150
151
  mintree worktree work
151
152
 
152
153
  # Inspect / clean up
@@ -176,11 +177,11 @@ mintree enforces:
176
177
  `<issue>` is one of:
177
178
 
178
179
  - Bare digits for GitHub Issues (the issue number, no `#`): `42`, `100`, `1234`
179
- - `<PROJ>-<digits>` for Plane work items (the human identifier): `BACK-100`, `WEB-7`, `PROJ_X-12`. The prefix is uppercase letters / digits / underscores, matching Plane's project-identifier constraints.
180
+ - `<TEAM>-<digits>` for Linear issues (the human identifier): `FE-123`, `BE-7`, `DSGN-12`. The prefix is uppercase letters / digits / underscores, matching Linear's team-key constraints.
180
181
 
181
182
  `<desc>` is lowercase kebab-case.
182
183
 
183
- Examples: `feat/42-validacion-patente`, `fix/55-selfie-upload-timeout`, `feat/BACK-100-readme-update`, `fix/WEB-7-modal`.
184
+ Examples: `feat/42-validacion-patente`, `fix/55-selfie-upload-timeout`, `feat/FE-123-readme-update`, `fix/BE-7-modal`.
184
185
 
185
186
  When the dashboard's `w` overlay opens, it suggests a kebab description capped at 5 words. If your repo has a `docs/conventions/git-workflow.md`, `CONTRIBUTING.md`, or `.claude/skills/` directory, mintree mentions it on the overlay so you can verify the suggestion against your project's rules — then edit the description to match.
186
187
 
@@ -194,16 +195,18 @@ When the dashboard's `w` overlay opens, it suggests a kebab description capped a
194
195
  └── .mintree/
195
196
  ├── metadata.json # gitignored. provider config + <issue-id> → { base_branch?, session_id? }
196
197
  ├── worktrees/ # gitignored
197
- │ ├── 100-validar-patente/ # GH form: <digits>-<desc>
198
- │ └── BACK-100-readme-update/ # Plane form: <PROJ-digits>-<desc>
198
+ │ ├── 100/ # GH form: <digits>
199
+ │ └── FE-123/ # Linear form: <TEAM-digits>
199
200
  ├── session-states/ # gitignored
200
201
  │ └── 100.json # live state written by Claude hooks (active/waiting/idle/exited)
201
202
  └── init.sh # opt-in. Runs in the new worktree post-create (copy .env, install deps, …)
202
203
  ```
203
204
 
204
- `metadata.json` is gitignored because the `session_id` is local to your machine sharing it would only generate noise. The `provider` and `plane.*` keys can be re-derived from a Plane workspace if needed; sharing them would just leak local config preference.
205
+ The worktree directory is named after the bare issue id (`100`, `FE-123`); the branch keeps the full `<type>/<issue>-<desc>` name.
205
206
 
206
- Plane authentication lives in `~/.mintree/credentials.json` (user-scoped, not per-repo) or the `PLANE_API_KEY` env var.
207
+ `metadata.json` is gitignored because the `session_id` is local to your machine — sharing it would only generate noise. The `provider` and `linear.*` keys can be re-derived from a Linear workspace if needed; sharing them would just leak local config preference.
208
+
209
+ Linear authentication lives in `~/.mintree/credentials.json` (user-scoped, not per-repo) or the `LINEAR_API_KEY` env var.
207
210
 
208
211
  ---
209
212
 
@@ -220,7 +223,7 @@ Plane authentication lives in `~/.mintree/credentials.json` (user-scoped, not pe
220
223
  - `mintree doctor` is the first stop. It surfaces missing tools, unauthenticated CLIs, missing hooks, and gitignore drift.
221
224
  - The shell wrapper exports `MINTREE_SHELL_INTEGRATION=1` — if doctor says it's missing, the wrapper isn't being loaded by your shell init file.
222
225
  - If the dashboard ever opens with a stale session state, press `r` to force a refetch (the auto-refresh runs every 5 minutes).
223
- - Plane-side issues (timeouts, rate limits, unexpected response shapes) can be logged to `~/.mintree/plane-debug.log` by running `MINTREE_DEBUG=1 mintree dashboard`. The log is file-only so it never corrupts the Ink-rendered TUI.
226
+ - Linear-side issues (timeouts, rate limits, unexpected response shapes) can be logged to `~/.mintree/linear-debug.log` by running `MINTREE_DEBUG=1 mintree dashboard`. The log is file-only so it never corrupts the Ink-rendered TUI.
224
227
 
225
228
  ---
226
229
 
@@ -228,7 +231,7 @@ Plane authentication lives in `~/.mintree/credentials.json` (user-scoped, not pe
228
231
 
229
232
  mintree was written for projects that have:
230
233
 
231
- - A small set of trackers (GitHub Issues or Plane) — santree supports both Linear and GitHub but is heavier.
234
+ - A small set of trackers (GitHub Issues or Linear) — santree supports both too but is heavier.
232
235
  - An established branch convention without the `gh-` prefix santree imposes.
233
236
  - Skills (`.claude/skills/`) that own the SDD + TDD flow — mintree intentionally leaves the rich PR-create / PR-review prompts out of scope.
234
237
 
@@ -67,15 +67,16 @@ function resolve(cwd) {
67
67
  const worktreeDirName = path.basename(worktreePath);
68
68
  // IssueId comes from the worktree dir name, not the branch — that way
69
69
  // detached-HEAD worktrees (the "current branch" path from the dashboard)
70
- // still resolve their session_id. Convention guarantees the dir is named
71
- // `<issueId>-<desc>` for both attached and detached creates, where
72
- // issueId is either bare digits (GitHub) or `<TEAM>-\d+` (Linear).
73
- const issueIdMatch = worktreeDirName.match(/^((?:[A-Z][A-Z0-9_]*-)?\d+)-/);
70
+ // still resolve their session_id. The dir is named after the bare issue
71
+ // id for both attached and detached creates; the trailing `-` clause still
72
+ // matches legacy `<issueId>-<desc>` worktrees. issueId is either bare
73
+ // digits (GitHub) or `<TEAM>-\d+` (Linear).
74
+ const issueIdMatch = worktreeDirName.match(/^((?:[A-Z][A-Z0-9_]*-)?\d+)(?:-|$)/);
74
75
  if (!issueIdMatch || !issueIdMatch[1]) {
75
76
  return {
76
77
  ok: false,
77
78
  message: `Worktree directory '${worktreeDirName}' doesn't start with an issue id.`,
78
- hint: "Expected `<issue>-<desc>` (e.g. 100-claude-md-inicial or AUTH-6-legal-endpoint).",
79
+ hint: "Expected the issue id (e.g. 100 or AUTH-6).",
79
80
  };
80
81
  }
81
82
  const issueId = issueIdMatch[1];
@@ -29,7 +29,7 @@ export const ALLOWED_TYPES = [
29
29
  // `<TEAM_PREFIX>-\d+` (Linear). The TEAM_PREFIX is uppercase letters/digits/
30
30
  // underscores starting with a letter, mirroring Linear's team-key
31
31
  // constraints. The full issueId captures group 2 verbatim so callers can
32
- // round-trip it into the worktree dir name.
32
+ // reuse it as the worktree dir name.
33
33
  const BRANCH_REGEX = /^([a-z]+)\/((?:[A-Z][A-Z0-9_]*-)?\d+)-([a-z0-9][a-z0-9-]*)$/;
34
34
  export function parseBranch(branch) {
35
35
  const match = BRANCH_REGEX.exec(branch);
@@ -57,7 +57,9 @@ export function parseBranch(branch) {
57
57
  type: type,
58
58
  issueId,
59
59
  desc,
60
- worktreeDirName: `${issueId}-${desc}`,
60
+ // Worktree dir is the bare issue id (e.g. "100" or "FE-123"). The desc
61
+ // only seeds the branch name, not the directory.
62
+ worktreeDirName: issueId,
61
63
  };
62
64
  }
63
65
  export function isParseError(result) {
@@ -16,8 +16,10 @@ import { createProvider } from "./providers/index.js";
16
16
  function buildWorktreeIndex(repoRoot) {
17
17
  const worktreesRoot = path.resolve(getWorktreesDir(repoRoot));
18
18
  // Same shape as the BRANCH_REGEX issueId capture: bare digits (GitHub) or
19
- // `<TEAM>-\d+` (Linear). Matches `100-foo` and `FE-123-foo` alike.
20
- const dirNameRegex = /^((?:[A-Z][A-Z0-9_]*-)?\d+)-/;
19
+ // `<TEAM>-\d+` (Linear). The dir name is now the bare issue id (`100`,
20
+ // `FE-123`); the trailing `-` clause keeps matching legacy `<id>-<desc>`
21
+ // worktrees still on disk.
22
+ const dirNameRegex = /^((?:[A-Z][A-Z0-9_]*-)?\d+)(?:-|$)/;
21
23
  const index = new Map();
22
24
  for (const w of listWorktrees(repoRoot)) {
23
25
  const wAbs = path.resolve(w.path);
@@ -119,9 +121,10 @@ function sortGroupedIssues(issues, configuredUrl) {
119
121
  * Worktrees" at the bottom of the dashboard so the user can find and `d`elete
120
122
  * them.
121
123
  *
122
- * The `issue` stub uses the worktree directory name as the title (the part
123
- * after the issue id, e.g. "claude-md-inicial") so the row is identifiable
124
- * even when there's no live issue to fetch a title from.
124
+ * The `issue` stub uses the worktree directory name as the title the bare
125
+ * issue id (e.g. "FE-123"), or the legacy "<id>-<desc>" suffix for older
126
+ * worktrees — so the row is identifiable even when there's no live issue to
127
+ * fetch a title from.
125
128
  */
126
129
  function buildOrphanRows(worktreesByIssue, assignedIds, sessionLookup, prByBranch, metadataSessionId) {
127
130
  const orphans = [];
@@ -16,10 +16,12 @@ export declare function extractRepoAndDir(cwd: string): {
16
16
  worktreeDir: string;
17
17
  } | null;
18
18
  /**
19
- * Pulls the issue id out of a `<issue>-<desc>` worktree directory name.
20
- * Returns null when the directory name doesn't follow the convention (e.g.
21
- * a manually-created worktree dropped under .mintree/worktrees/). The id
22
- * is either bare digits (GitHub) or a `<TEAM>-\d+` Linear identifier.
19
+ * Pulls the issue id out of a worktree directory name. The dir name is the
20
+ * bare issue id (`100`, `FE-123`); the trailing `-` clause still matches
21
+ * legacy `<issue>-<desc>` worktrees on disk. Returns null when the directory
22
+ * name doesn't follow the convention (e.g. a manually-created worktree
23
+ * dropped under .mintree/worktrees/). The id is either bare digits (GitHub)
24
+ * or a `<TEAM>-\d+` Linear identifier.
23
25
  */
24
26
  export declare function issueIdFromWorktreeDir(worktreeDir: string): string | null;
25
27
  export type StatePayload = {
@@ -32,13 +32,15 @@ export function extractRepoAndDir(cwd) {
32
32
  return { repoRoot, worktreeDir };
33
33
  }
34
34
  /**
35
- * Pulls the issue id out of a `<issue>-<desc>` worktree directory name.
36
- * Returns null when the directory name doesn't follow the convention (e.g.
37
- * a manually-created worktree dropped under .mintree/worktrees/). The id
38
- * is either bare digits (GitHub) or a `<TEAM>-\d+` Linear identifier.
35
+ * Pulls the issue id out of a worktree directory name. The dir name is the
36
+ * bare issue id (`100`, `FE-123`); the trailing `-` clause still matches
37
+ * legacy `<issue>-<desc>` worktrees on disk. Returns null when the directory
38
+ * name doesn't follow the convention (e.g. a manually-created worktree
39
+ * dropped under .mintree/worktrees/). The id is either bare digits (GitHub)
40
+ * or a `<TEAM>-\d+` Linear identifier.
39
41
  */
40
42
  export function issueIdFromWorktreeDir(worktreeDir) {
41
- const m = worktreeDir.match(/^((?:[A-Z][A-Z0-9_]*-)?\d+)-/);
43
+ const m = worktreeDir.match(/^((?:[A-Z][A-Z0-9_]*-)?\d+)(?:-|$)/);
42
44
  return m && m[1] ? m[1] : null;
43
45
  }
44
46
  /**
@@ -66,7 +66,7 @@ export type CreateDetachedOpts = {
66
66
  * the `<type>/<issue>-<desc>` convention upfront. They can `git switch -c`
67
67
  * later if/when the work warrants a branch.
68
68
  *
69
- * Worktree dir naming follows the same `<issueId>-<desc>` shape as the
69
+ * Worktree dir naming follows the same bare-issueId shape as the
70
70
  * branch-based flow so `worktree work` can still recover the issueId from
71
71
  * the dir name (where it can't read it from the branch).
72
72
  */
@@ -238,7 +238,7 @@ export async function runCreate(branchArg, opts) {
238
238
  * the `<type>/<issue>-<desc>` convention upfront. They can `git switch -c`
239
239
  * later if/when the work warrants a branch.
240
240
  *
241
- * Worktree dir naming follows the same `<issueId>-<desc>` shape as the
241
+ * Worktree dir naming follows the same bare-issueId shape as the
242
242
  * branch-based flow so `worktree work` can still recover the issueId from
243
243
  * the dir name (where it can't read it from the branch).
244
244
  */
@@ -280,7 +280,7 @@ export async function runCreateDetached(opts) {
280
280
  hint: "Switch the main repo to a branch first (`git switch main`) and try again.",
281
281
  };
282
282
  }
283
- const worktreeDirName = `${opts.issueId}-${opts.descKebab}`;
283
+ const worktreeDirName = opts.issueId;
284
284
  const worktreePath = path.join(getWorktreesDir(root), worktreeDirName);
285
285
  if (pathExists(worktreePath)) {
286
286
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Issue-driven git worktrees + Claude Code sessions for repos with an opinionated SDD+TDD flow.",
5
5
  "license": "MIT",
6
6
  "author": "Martin Mineo <mmineo@canarytechnologies.com>",