mintree 0.4.6 → 0.4.8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Martin Mineo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,20 @@
1
- # mintree
1
+ <h1 align="center">mintree</h1>
2
2
 
3
- > Issue-driven worktrees + Claude Code sessions for repos with an opinionated SDD+TDD flow.
3
+ <p align="center">
4
+ <strong>Issue-driven Git worktrees + Claude Code sessions</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/mintree"><img src="https://img.shields.io/npm/v/mintree.svg" alt="npm version"></a>
9
+ <a href="https://www.npmjs.com/package/mintree"><img src="https://img.shields.io/npm/dm/mintree.svg" alt="npm downloads"></a>
10
+ <a href="https://github.com/minex-labs/mintree/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/mintree.svg" alt="license"></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ Pick an issue, spin up an isolated worktree, and work it with Claude — for repos with an opinionated SDD+TDD flow.
15
+ </p>
16
+
17
+ ---
4
18
 
5
19
  mintree wraps the steps you do manually every time a feature begins:
6
20
 
@@ -7,10 +7,16 @@
7
7
  * bare digit run (GitHub issue number — "100") or a team-prefixed Linear
8
8
  * identifier ("FE-100"); `<desc>` is lower-case kebab-case.
9
9
  *
10
+ * The Linear team key is normalized to upper-case: `feat/be-256-x` is
11
+ * accepted and rewritten to `feat/BE-256-x` (issueId "BE-256"). Linear's
12
+ * canonical identifier is always upper-case, so this just rescues a branch
13
+ * a human (or a target-repo skill) typed in the wrong case.
14
+ *
10
15
  * Examples that PARSE: feat/100-readme-update, fix/55-upload-timeout,
11
- * feat/BACK-100-readme-update, fix/WEB-7-modal
16
+ * feat/BACK-100-readme-update, fix/WEB-7-modal,
17
+ * feat/back-100-x (normalized to feat/BACK-100-x)
12
18
  * Examples that REJECT: feat/abc-foo, /100-foo, gh-100-foo, feat/100,
13
- * feat/100-FooBar, feat/back-100-x (lowercase prefix)
19
+ * feat/100-FooBar, Feat/BE-1-x (upper-case type)
14
20
  */
15
21
  export declare const ALLOWED_TYPES: readonly ["feat", "fix", "docs", "chore", "refactor", "test", "build", "ci", "perf", "style", "revert"];
16
22
  export type BranchType = (typeof ALLOWED_TYPES)[number];
@@ -7,10 +7,16 @@
7
7
  * bare digit run (GitHub issue number — "100") or a team-prefixed Linear
8
8
  * identifier ("FE-100"); `<desc>` is lower-case kebab-case.
9
9
  *
10
+ * The Linear team key is normalized to upper-case: `feat/be-256-x` is
11
+ * accepted and rewritten to `feat/BE-256-x` (issueId "BE-256"). Linear's
12
+ * canonical identifier is always upper-case, so this just rescues a branch
13
+ * a human (or a target-repo skill) typed in the wrong case.
14
+ *
10
15
  * Examples that PARSE: feat/100-readme-update, fix/55-upload-timeout,
11
- * feat/BACK-100-readme-update, fix/WEB-7-modal
16
+ * feat/BACK-100-readme-update, fix/WEB-7-modal,
17
+ * feat/back-100-x (normalized to feat/BACK-100-x)
12
18
  * Examples that REJECT: feat/abc-foo, /100-foo, gh-100-foo, feat/100,
13
- * feat/100-FooBar, feat/back-100-x (lowercase prefix)
19
+ * feat/100-FooBar, Feat/BE-1-x (upper-case type)
14
20
  */
15
21
  export const ALLOWED_TYPES = [
16
22
  "feat",
@@ -26,11 +32,12 @@ export const ALLOWED_TYPES = [
26
32
  "revert",
27
33
  ];
28
34
  // `<type>/<issueId>-<desc>` where issueId is either `\d+` (GitHub) or
29
- // `<TEAM_PREFIX>-\d+` (Linear). The TEAM_PREFIX is uppercase letters/digits/
30
- // underscores starting with a letter, mirroring Linear's team-key
31
- // constraints. The full issueId captures group 2 verbatim so callers can
32
- // reuse it as the worktree dir name.
33
- const BRANCH_REGEX = /^([a-z]+)\/((?:[A-Z][A-Z0-9_]*-)?\d+)-([a-z0-9][a-z0-9-]*)$/;
35
+ // `<TEAM_PREFIX>-\d+` (Linear). The TEAM_PREFIX is letters/digits/underscores
36
+ // starting with a letter, mirroring Linear's team-key constraints. Both cases
37
+ // are accepted here and the captured team key is upper-cased on the way out
38
+ // (see `parseBranch`), so the worktree dir name and the created branch stay
39
+ // canonical regardless of how the caller typed it.
40
+ const BRANCH_REGEX = /^([a-z]+)\/((?:[A-Za-z][A-Za-z0-9_]*-)?\d+)-([a-z0-9][a-z0-9-]*)$/;
34
41
  export function parseBranch(branch) {
35
42
  const match = BRANCH_REGEX.exec(branch);
36
43
  if (!match) {
@@ -39,8 +46,8 @@ export function parseBranch(branch) {
39
46
  hint: "Expected `<type>/<issue>-<kebab-desc>`. Examples: feat/100-claude-md-inicial, feat/BACK-100-claude-md-inicial",
40
47
  };
41
48
  }
42
- const [, type, issueId, desc] = match;
43
- if (!type || !issueId || !desc) {
49
+ const [, type, rawIssueId, desc] = match;
50
+ if (!type || !rawIssueId || !desc) {
44
51
  return {
45
52
  error: `Invalid branch name: ${branch}`,
46
53
  hint: "Expected `<type>/<issue>-<kebab-desc>`. Examples: feat/100-claude-md-inicial, feat/BACK-100-claude-md-inicial",
@@ -52,8 +59,14 @@ export function parseBranch(branch) {
52
59
  hint: `Allowed types: ${ALLOWED_TYPES.join(", ")}`,
53
60
  };
54
61
  }
62
+ // Normalize the Linear team key to upper-case. `toUpperCase()` is a no-op
63
+ // for a bare-digit GitHub id ("256") and only touches the team prefix of a
64
+ // Linear id ("be-256" → "BE-256"). The returned `branch` is rebuilt from
65
+ // the normalized id so callers create/look up the canonical branch name.
66
+ const issueId = rawIssueId.toUpperCase();
67
+ const normalizedBranch = `${type}/${issueId}-${desc}`;
55
68
  return {
56
- branch,
69
+ branch: normalizedBranch,
57
70
  type: type,
58
71
  issueId,
59
72
  desc,
@@ -504,8 +504,17 @@ export class LinearProvider {
504
504
  }
505
505
  const targetStateName = cfg.inProgressStateName;
506
506
  let targetState = targetStateName ? states.find((s) => s.name === targetStateName) : undefined;
507
- if (!targetState)
508
- targetState = states.find((s) => s.type === "started");
507
+ if (!targetState) {
508
+ // Linear marks BOTH "In Progress" and "In Review" as type "started",
509
+ // and the bootstrap query returns states unordered — so a plain
510
+ // `find(type === "started")` can land on "In Review". Prefer a state
511
+ // literally named "In Progress", otherwise pick the leftmost started
512
+ // state by workflow position (lowest = earliest = "In Progress").
513
+ const started = states.filter((s) => s.type === "started");
514
+ targetState =
515
+ started.find((s) => s.name?.toLowerCase() === "in progress") ??
516
+ started.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))[0];
517
+ }
509
518
  if (!targetState) {
510
519
  return {
511
520
  kind: "skip-no-in-progress-option",
@@ -17,6 +17,10 @@ export type RemoveResult = {
17
17
  *
18
18
  * Branch and metadata are deliberately preserved so a later re-attach can
19
19
  * resume the same Claude session.
20
+ *
21
+ * The branch name is NOT validated against the naming convention here:
22
+ * removal is a cleanup op, and a worktree on a non-canonical branch (e.g.
23
+ * one with a lowercase Linear team key) must still be removable.
20
24
  */
21
25
  export declare function runRemove(branchArg: string, force: boolean): RemoveResult;
22
26
  /**
@@ -1,4 +1,3 @@
1
- import { parseBranch, isParseError } from "./branch.js";
2
1
  import { findMainRepoRoot, getMintreeDir, worktreeForBranch, isDirty, removeWorktree, pruneWorktrees, pathExists, } from "./git.js";
3
2
  /**
4
3
  * Removes the worktree backing `branchArg`. Same behavior as the CLI command:
@@ -8,6 +7,10 @@ import { findMainRepoRoot, getMintreeDir, worktreeForBranch, isDirty, removeWork
8
7
  *
9
8
  * Branch and metadata are deliberately preserved so a later re-attach can
10
9
  * resume the same Claude session.
10
+ *
11
+ * The branch name is NOT validated against the naming convention here:
12
+ * removal is a cleanup op, and a worktree on a non-canonical branch (e.g.
13
+ * one with a lowercase Linear team key) must still be removable.
11
14
  */
12
15
  export function runRemove(branchArg, force) {
13
16
  const root = findMainRepoRoot();
@@ -25,15 +28,11 @@ export function runRemove(branchArg, force) {
25
28
  hint: "Run `mintree init` first.",
26
29
  };
27
30
  }
28
- const parsed = parseBranch(branchArg);
29
- if (isParseError(parsed)) {
30
- return { ok: false, message: parsed.error, hint: parsed.hint };
31
- }
32
- const worktreePath = worktreeForBranch(root, parsed.branch);
31
+ const worktreePath = worktreeForBranch(root, branchArg);
33
32
  if (!worktreePath) {
34
33
  return {
35
34
  ok: false,
36
- message: `No worktree found for branch ${parsed.branch}.`,
35
+ message: `No worktree found for branch ${branchArg}.`,
37
36
  hint: "Use `mintree worktree list` to see existing worktrees.",
38
37
  };
39
38
  }
@@ -49,7 +48,7 @@ export function runRemove(branchArg, force) {
49
48
  }
50
49
  return {
51
50
  ok: true,
52
- branch: parsed.branch,
51
+ branch: branchArg,
53
52
  worktreePath,
54
53
  variant: "pruned-orphan",
55
54
  wasDirty: false,
@@ -76,7 +75,7 @@ export function runRemove(branchArg, force) {
76
75
  }
77
76
  return {
78
77
  ok: true,
79
- branch: parsed.branch,
78
+ branch: branchArg,
80
79
  worktreePath,
81
80
  variant: "removed",
82
81
  wasDirty: dirty,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
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>",