mintree 0.4.9 → 0.4.10
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 +26 -0
- package/dist/commands/dashboard.js +15 -6
- package/dist/commands/worktree/work.d.ts +1 -1
- package/dist/commands/worktree/work.js +11 -7
- package/dist/lib/metadata.d.ts +3 -0
- package/dist/lib/metadata.js +11 -0
- package/dist/lib/promptTemplate.d.ts +18 -0
- package/dist/lib/promptTemplate.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,32 @@ chmod 600 ~/.mintree/credentials.json
|
|
|
126
126
|
|
|
127
127
|
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"`.
|
|
128
128
|
|
|
129
|
+
### Launch behaviour (optional)
|
|
130
|
+
|
|
131
|
+
Two top-level keys in `.mintree/metadata.json` tune how mintree launches Claude — both apply to GitHub and Linear repos alike:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"version": 1,
|
|
136
|
+
"provider": "linear",
|
|
137
|
+
"issues": {},
|
|
138
|
+
"defaultPermissionMode": "auto",
|
|
139
|
+
"promptTemplate": "Trabajá en el ticket {{id}} ({{title}}). Abrí {{url}} para el contexto completo y seguí las convenciones del repo.",
|
|
140
|
+
"linear": { "workspaceSlug": "my-team", "teams": [{ "key": "FE" }] }
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- **`defaultPermissionMode`** (`"default"` | `"auto"`): the Claude `--permission-mode` mintree uses when it launches a session — from the dashboard (`w` / `↵`), `worktree work`, or `worktree create --work`. Omitted (or `"default"`) keeps the stricter default mode; `"auto"` starts every session with auto-accept on. The `--permission-mode` / `-m` CLI flag still overrides it per launch.
|
|
145
|
+
- **`promptTemplate`**: the initial message seeded into the dashboard's `w` overlay (the text Claude receives as its first prompt). It replaces mintree's built-in default and supports these placeholders, substituted per issue:
|
|
146
|
+
|
|
147
|
+
| Placeholder | Replaced with |
|
|
148
|
+
|-------------|-------------------------------------------------------|
|
|
149
|
+
| `{{id}}` | Issue id — `100` (GitHub) or `FE-123` (Linear) |
|
|
150
|
+
| `{{title}}` | Issue title |
|
|
151
|
+
| `{{url}}` | Issue URL (GitHub issue page / Linear issue link) |
|
|
152
|
+
|
|
153
|
+
It's a single line on purpose — the overlay's prompt field is one line, and you can still edit it before launching. When omitted, mintree falls back to its provider-aware default (`gh issue view` for GitHub, the bare id + URL for Linear).
|
|
154
|
+
|
|
129
155
|
---
|
|
130
156
|
|
|
131
157
|
## Daily flow
|
|
@@ -14,6 +14,7 @@ import { runCreate, runCreateDetached, } from "../lib/worktreeCreate.js";
|
|
|
14
14
|
import { runRemove, runRemoveByPath } from "../lib/worktreeRemove.js";
|
|
15
15
|
import { buildCreateMarkers, emitMarkers } from "../lib/markers.js";
|
|
16
16
|
import { readMetadata } from "../lib/metadata.js";
|
|
17
|
+
import { renderPromptTemplate } from "../lib/promptTemplate.js";
|
|
17
18
|
import { createProvider } from "../lib/providers/index.js";
|
|
18
19
|
import { loadDashboard } from "../lib/dashboard.js";
|
|
19
20
|
import { priorityDisplay } from "../lib/priority.js";
|
|
@@ -121,11 +122,18 @@ function kebabize(title) {
|
|
|
121
122
|
* Default prompt seeded into the overlay's Prompt field when the user opens
|
|
122
123
|
* `w` for an issue. Single-line on purpose — `ink-text-input` is one-line,
|
|
123
124
|
* so multi-line templates render weirdly when the user tabs in to edit.
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
125
|
+
*
|
|
126
|
+
* When the repo configures a `promptTemplate` in `.mintree/metadata.json`,
|
|
127
|
+
* it wins: the `{{id}}`, `{{title}}` and `{{url}}` placeholders are rendered
|
|
128
|
+
* and the result seeds the field. Otherwise we fall back to the built-in,
|
|
129
|
+
* provider-aware default: GitHub issues get the `#<n>` + `gh issue view`
|
|
130
|
+
* form; Linear issues (id like `FE-123`) get the bare id + the issue URL,
|
|
131
|
+
* since `gh` can't read Linear and `#` isn't Linear's notation.
|
|
127
132
|
*/
|
|
128
|
-
function defaultPromptForIssue(id, title, url) {
|
|
133
|
+
function defaultPromptForIssue(id, title, url, template) {
|
|
134
|
+
if (template) {
|
|
135
|
+
return renderPromptTemplate(template, { id, title, url });
|
|
136
|
+
}
|
|
129
137
|
const isTeamPrefixed = /^[A-Z][A-Z0-9_]*-\d+$/.test(id);
|
|
130
138
|
if (isTeamPrefixed) {
|
|
131
139
|
return `Empezá a trabajar el ticket ${id} (${title}). Abrí ${url} para leer el contexto completo y seguí las convenciones del repo.`;
|
|
@@ -976,7 +984,8 @@ export default function Dashboard() {
|
|
|
976
984
|
// (its `branchName`) over the synthesised `<type>/<issue>-<desc>` form —
|
|
977
985
|
// that's the convention those repos actually follow. Falls back to the
|
|
978
986
|
// convention form when the issue has no branchName.
|
|
979
|
-
const
|
|
987
|
+
const meta = root ? readMetadata(root) : undefined;
|
|
988
|
+
const provider = meta?.provider;
|
|
980
989
|
const linearBranch = provider === "linear" && issue.issue.branchName ? issue.issue.branchName : null;
|
|
981
990
|
setState({
|
|
982
991
|
...state,
|
|
@@ -988,7 +997,7 @@ export default function Dashboard() {
|
|
|
988
997
|
type: "feat",
|
|
989
998
|
desc: kebabize(issue.issue.title) || `issue-${issue.issue.id}`,
|
|
990
999
|
linearBranch,
|
|
991
|
-
prompt: defaultPromptForIssue(issue.issue.id, issue.issue.title, issue.issue.url),
|
|
1000
|
+
prompt: defaultPromptForIssue(issue.issue.id, issue.issue.title, issue.issue.url, meta?.promptTemplate),
|
|
992
1001
|
field: "branchMode",
|
|
993
1002
|
error: null,
|
|
994
1003
|
conventionDoc: root ? findBranchConventionDoc(root) : null,
|
|
@@ -3,7 +3,7 @@ export declare const description = "Launch Claude in the current worktree (creat
|
|
|
3
3
|
export declare const options: z.ZodObject<{
|
|
4
4
|
prompt: z.ZodOptional<z.ZodString>;
|
|
5
5
|
promptFile: z.ZodOptional<z.ZodString>;
|
|
6
|
-
permissionMode: z.
|
|
6
|
+
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
7
7
|
default: "default";
|
|
8
8
|
auto: "auto";
|
|
9
9
|
}>>;
|
|
@@ -8,7 +8,7 @@ import { randomUUID } from "crypto";
|
|
|
8
8
|
import { readFileSync, unlinkSync } from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import { findMainRepoRoot, getMintreeDir, getWorktreesDir, getCurrentBranch, pathExists, } from "../../lib/git.js";
|
|
11
|
-
import { getSessionId, setSessionId } from "../../lib/metadata.js";
|
|
11
|
+
import { getSessionId, setSessionId, readMetadata } from "../../lib/metadata.js";
|
|
12
12
|
import { launchClaude, PERMISSION_MODES } from "../../lib/claude.js";
|
|
13
13
|
export const description = "Launch Claude in the current worktree (creates or resumes a session)";
|
|
14
14
|
export const options = z.object({
|
|
@@ -26,13 +26,13 @@ export const options = z.object({
|
|
|
26
26
|
})),
|
|
27
27
|
permissionMode: z
|
|
28
28
|
.enum(PERMISSION_MODES)
|
|
29
|
-
.
|
|
29
|
+
.optional()
|
|
30
30
|
.describe(option({
|
|
31
|
-
description: `Claude --permission-mode (one of: ${PERMISSION_MODES.join(", ")})
|
|
31
|
+
description: `Claude --permission-mode (one of: ${PERMISSION_MODES.join(", ")}). Defaults to metadata.defaultPermissionMode, else "default".`,
|
|
32
32
|
alias: "m",
|
|
33
33
|
})),
|
|
34
34
|
});
|
|
35
|
-
function resolve(cwd) {
|
|
35
|
+
function resolve(cwd, flagPermissionMode) {
|
|
36
36
|
const repoRoot = findMainRepoRoot(cwd);
|
|
37
37
|
if (!repoRoot) {
|
|
38
38
|
return {
|
|
@@ -93,6 +93,9 @@ function resolve(cwd) {
|
|
|
93
93
|
setSessionId(repoRoot, issueId, sessionId);
|
|
94
94
|
resume = false;
|
|
95
95
|
}
|
|
96
|
+
// Effective permission mode: explicit `--permission-mode` flag wins, else
|
|
97
|
+
// the repo's `metadata.defaultPermissionMode`, else the stricter "default".
|
|
98
|
+
const permissionMode = flagPermissionMode ?? readMetadata(repoRoot).defaultPermissionMode ?? "default";
|
|
96
99
|
return {
|
|
97
100
|
ok: true,
|
|
98
101
|
data: {
|
|
@@ -103,6 +106,7 @@ function resolve(cwd) {
|
|
|
103
106
|
issueId,
|
|
104
107
|
sessionId,
|
|
105
108
|
resume,
|
|
109
|
+
permissionMode,
|
|
106
110
|
},
|
|
107
111
|
};
|
|
108
112
|
}
|
|
@@ -118,7 +122,7 @@ export default function Work({ options }) {
|
|
|
118
122
|
});
|
|
119
123
|
return;
|
|
120
124
|
}
|
|
121
|
-
const result = resolve(process.cwd());
|
|
125
|
+
const result = resolve(process.cwd(), options.permissionMode);
|
|
122
126
|
if (!result.ok) {
|
|
123
127
|
setState({ phase: "error", message: result.message, hint: result.hint });
|
|
124
128
|
return;
|
|
@@ -151,7 +155,7 @@ export default function Work({ options }) {
|
|
|
151
155
|
}
|
|
152
156
|
try {
|
|
153
157
|
const child = launchClaude({
|
|
154
|
-
permissionMode:
|
|
158
|
+
permissionMode: resolved.permissionMode,
|
|
155
159
|
sessionId: resolved.sessionId,
|
|
156
160
|
resume: resolved.resume,
|
|
157
161
|
prompt: effectivePrompt,
|
|
@@ -184,7 +188,7 @@ export default function Work({ options }) {
|
|
|
184
188
|
const { resolved } = state;
|
|
185
189
|
const sessionShort = resolved.sessionId.slice(0, 8);
|
|
186
190
|
const action = resolved.resume ? "resuming" : "starting";
|
|
187
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "mintree worktree work" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", resolved.branch ?? `detached @ ${resolved.worktreeDirName}`] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "session: " }), _jsxs(Text, { children: [sessionShort, "\u2026"] }), _jsxs(Text, { dimColor: true, children: [" (", action, ")"] })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "permission-mode: " }), _jsx(Text, { children:
|
|
191
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "mintree worktree work" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", resolved.branch ?? `detached @ ${resolved.worktreeDirName}`] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "session: " }), _jsxs(Text, { children: [sessionShort, "\u2026"] }), _jsxs(Text, { dimColor: true, children: [" (", action, ")"] })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "permission-mode: " }), _jsx(Text, { children: resolved.permissionMode })] }), options.prompt && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "initial prompt: " }), _jsxs(Text, { children: ["\"", truncate(options.prompt, 60), "\""] })] })), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "cwd: " }), _jsx(Text, { dimColor: true, children: resolved.worktreePath })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", bold: true, children: "\u2713 Launching Claude..." }) })] }));
|
|
188
192
|
}
|
|
189
193
|
function truncate(s, max) {
|
|
190
194
|
if (s.length <= max)
|
package/dist/lib/metadata.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PermissionMode } from "./claude.js";
|
|
1
2
|
export type IssueMeta = {
|
|
2
3
|
base_branch?: string;
|
|
3
4
|
session_id?: string;
|
|
@@ -26,6 +27,8 @@ export type Metadata = {
|
|
|
26
27
|
issues: Record<string, IssueMeta>;
|
|
27
28
|
project?: ProjectMeta;
|
|
28
29
|
linear?: LinearMeta;
|
|
30
|
+
defaultPermissionMode?: PermissionMode;
|
|
31
|
+
promptTemplate?: string;
|
|
29
32
|
};
|
|
30
33
|
export declare function readMetadata(repoRoot: string): Metadata;
|
|
31
34
|
export declare function writeMetadata(repoRoot: string, data: Metadata): void;
|
package/dist/lib/metadata.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import { getMetadataPath } from "./git.js";
|
|
3
|
+
import { PERMISSION_MODES } from "./claude.js";
|
|
3
4
|
const EMPTY = { version: 1, issues: {} };
|
|
4
5
|
function sanitizeProvider(raw) {
|
|
5
6
|
if (raw === "github" || raw === "linear")
|
|
6
7
|
return raw;
|
|
7
8
|
return undefined;
|
|
8
9
|
}
|
|
10
|
+
function sanitizePermissionMode(raw) {
|
|
11
|
+
return PERMISSION_MODES.includes(raw) ? raw : undefined;
|
|
12
|
+
}
|
|
13
|
+
function sanitizePromptTemplate(raw) {
|
|
14
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw : undefined;
|
|
15
|
+
}
|
|
9
16
|
function sanitizeLinearTeam(raw) {
|
|
10
17
|
if (typeof raw !== "object" || raw === null)
|
|
11
18
|
return undefined;
|
|
@@ -77,6 +84,8 @@ export function readMetadata(repoRoot) {
|
|
|
77
84
|
const project = sanitizeProject(parsed.project);
|
|
78
85
|
const provider = sanitizeProvider(parsed.provider);
|
|
79
86
|
const linear = sanitizeLinear(parsed.linear);
|
|
87
|
+
const defaultPermissionMode = sanitizePermissionMode(parsed.defaultPermissionMode);
|
|
88
|
+
const promptTemplate = sanitizePromptTemplate(parsed.promptTemplate);
|
|
80
89
|
return {
|
|
81
90
|
version: 1,
|
|
82
91
|
issues: typeof parsed.issues === "object" && parsed.issues !== null
|
|
@@ -85,6 +94,8 @@ export function readMetadata(repoRoot) {
|
|
|
85
94
|
...(provider ? { provider } : {}),
|
|
86
95
|
...(project ? { project } : {}),
|
|
87
96
|
...(linear ? { linear } : {}),
|
|
97
|
+
...(defaultPermissionMode ? { defaultPermissionMode } : {}),
|
|
98
|
+
...(promptTemplate ? { promptTemplate } : {}),
|
|
88
99
|
};
|
|
89
100
|
}
|
|
90
101
|
catch {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variables available to a `promptTemplate` in `.mintree/metadata.json`.
|
|
3
|
+
* Kept intentionally small — the template seeds Claude's first message, it
|
|
4
|
+
* doesn't need the whole issue object.
|
|
5
|
+
*/
|
|
6
|
+
export type PromptVars = {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const PROMPT_PLACEHOLDERS: readonly ["{{id}}", "{{title}}", "{{url}}"];
|
|
12
|
+
/**
|
|
13
|
+
* Renders a `promptTemplate` by substituting the `{{id}}`, `{{title}}` and
|
|
14
|
+
* `{{url}}` placeholders with the issue's values. Whitespace inside the braces
|
|
15
|
+
* is tolerated (`{{ id }}`). Unknown placeholders are left untouched so a typo
|
|
16
|
+
* is visible in the launched prompt instead of silently vanishing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderPromptTemplate(template: string, vars: PromptVars): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Placeholder tokens a user can drop into their `promptTemplate`. Documented
|
|
2
|
+
// here so the README and any future `init`/help output stay in sync.
|
|
3
|
+
export const PROMPT_PLACEHOLDERS = ["{{id}}", "{{title}}", "{{url}}"];
|
|
4
|
+
/**
|
|
5
|
+
* Renders a `promptTemplate` by substituting the `{{id}}`, `{{title}}` and
|
|
6
|
+
* `{{url}}` placeholders with the issue's values. Whitespace inside the braces
|
|
7
|
+
* is tolerated (`{{ id }}`). Unknown placeholders are left untouched so a typo
|
|
8
|
+
* is visible in the launched prompt instead of silently vanishing.
|
|
9
|
+
*/
|
|
10
|
+
export function renderPromptTemplate(template, vars) {
|
|
11
|
+
return template
|
|
12
|
+
.replace(/\{\{\s*id\s*\}\}/g, vars.id)
|
|
13
|
+
.replace(/\{\{\s*title\s*\}\}/g, vars.title)
|
|
14
|
+
.replace(/\{\{\s*url\s*\}\}/g, vars.url);
|
|
15
|
+
}
|
package/package.json
CHANGED