mintree 0.4.5 → 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 +21 -0
- package/README.md +16 -2
- package/dist/lib/branch.d.ts +8 -2
- package/dist/lib/branch.js +23 -10
- package/dist/lib/providers/linear.js +11 -2
- package/dist/lib/worktreeRemove.d.ts +4 -0
- package/dist/lib/worktreeRemove.js +8 -9
- package/package.json +1 -1
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
|
-
|
|
1
|
+
<h1 align="center">mintree</h1>
|
|
2
2
|
|
|
3
|
-
>
|
|
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
|
|
package/dist/lib/branch.d.ts
CHANGED
|
@@ -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,
|
|
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];
|
package/dist/lib/branch.js
CHANGED
|
@@ -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,
|
|
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
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
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,
|
|
43
|
-
if (!type || !
|
|
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
|
-
|
|
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
|
|
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 ${
|
|
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:
|
|
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:
|
|
78
|
+
branch: branchArg,
|
|
80
79
|
worktreePath,
|
|
81
80
|
variant: "removed",
|
|
82
81
|
wasDirty: dirty,
|
package/package.json
CHANGED