mintree 0.5.13 → 0.5.14
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 +10 -6
- package/dist/lib/metadata.js +1 -1
- package/dist/lib/worktreeCreate.js +18 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,11 +161,11 @@ Three top-level keys in `.mintree/metadata.json` tune how mintree launches Claud
|
|
|
161
161
|
|
|
162
162
|
When omitted, mintree uses a built-in default that asks Claude to orchestrate the selected tickets with minimal intervention — parallelising via subagents unless dependencies force sequential work, creating a worktree per ticket with mintree, using the repo's skills, and moving each ticket to *in progress* on start and closing it when done.
|
|
163
163
|
|
|
164
|
-
###
|
|
164
|
+
### Copying gitignored files into worktrees (optional)
|
|
165
165
|
|
|
166
166
|
Git worktrees don't share **untracked** files: a new worktree is a fresh working directory, so gitignored config like `.env` lives only in your main checkout and is **absent** in every worktree mintree creates. That breaks per-worktree tooling that needs it — e.g. running an E2E suite that reads `.env` for staging credentials.
|
|
167
167
|
|
|
168
|
-
The `linkFiles` top-level key (valid on GitHub and Linear repos) lists repo-root-relative paths that mintree **
|
|
168
|
+
The `linkFiles` top-level key (valid on GitHub and Linear repos) lists repo-root-relative paths that mintree **copies** into each new worktree, right after creating it:
|
|
169
169
|
|
|
170
170
|
```json
|
|
171
171
|
{
|
|
@@ -177,10 +177,11 @@ The `linkFiles` top-level key (valid on GitHub and Linear repos) lists repo-root
|
|
|
177
177
|
}
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
- **
|
|
180
|
+
- **Copy, not symlink** — each worktree gets its **own** file. Editing the worktree's `.env` (a port, a feature flag, a per-worktree tweak) stays local and never mutates the main checkout's. The trade-off: it's a snapshot taken at create time, so rotating a credential in the main `.env` does **not** propagate to worktrees already created — re-copy by hand if you need it.
|
|
181
|
+
> Up to 0.5.13 these were **symlinks**, so editing a worktree's `.env` wrote through to the main checkout. The switch to copies only affects worktrees created from 0.5.14 on — existing worktrees keep their old symlink. To convert one, replace the link with a real copy: `rm <worktree>/.env && cp <repo-root>/.env <worktree>/.env`.
|
|
181
182
|
- **Best-effort, never fatal** — an entry that doesn't exist in the repo root is skipped, and so is one whose target is already present in the worktree (e.g. a tracked file). Both show up as `skip` steps in the create log.
|
|
182
|
-
- **Runs before `.mintree/init.sh`** — so the post-create hook (if any) can rely on the
|
|
183
|
-
- **Sandboxed paths** — entries must be repo-root-relative; absolute paths and `..` escapes are dropped on read, so a stray `metadata.json` can't make mintree
|
|
183
|
+
- **Runs before `.mintree/init.sh`** — so the post-create hook (if any) can rely on the copied files being there.
|
|
184
|
+
- **Sandboxed paths** — entries must be repo-root-relative; absolute paths and `..` escapes are dropped on read, so a stray `metadata.json` can't make mintree copy something outside the worktree.
|
|
184
185
|
|
|
185
186
|
This applies to `worktree create` (CLI), the dashboard `w` overlay, and the detached-worktree flow alike. For more involved per-worktree setup (installing deps, copying templated files), use `.mintree/init.sh` instead — see [What gets stored where](#what-gets-stored-where).
|
|
186
187
|
|
|
@@ -226,6 +227,9 @@ Same building blocks, scriptable from any shell:
|
|
|
226
227
|
mintree worktree create feat/100-validar-patente
|
|
227
228
|
mintree worktree create feat/FE-123-validar-patente --work --prompt "empezar FE-123"
|
|
228
229
|
|
|
230
|
+
# Fork from a specific base instead of origin/HEAD (defaults to main/master)
|
|
231
|
+
mintree worktree create fix/55-hotfix --base release/2.1
|
|
232
|
+
|
|
229
233
|
# On a Linear repo you can pass the issue's own Linear branch name
|
|
230
234
|
mintree worktree create martinmineo/val-68-landing-publica --work
|
|
231
235
|
|
|
@@ -314,7 +318,7 @@ Linear authentication lives in `~/.mintree/credentials.json` (user-scoped, not p
|
|
|
314
318
|
|
|
315
319
|
- **Sessions persist by issue**: each issue gets a UUID stored in `metadata.json`. Subsequent `worktree work` calls pass `--resume <uuid>` so Claude reopens the same conversation.
|
|
316
320
|
- **Live state** (optional): the four hooks installed by `mintree helpers session-signal install` write the current Claude state to `.mintree/session-states/<issue>.json` on every prompt / stop / notification / session-end. The dashboard reads those files to colour each row in real time.
|
|
317
|
-
- **Remote Control** (optional): `mintree doctor` checks `~/.claude.json` for `remoteControlAtStartup: true`.
|
|
321
|
+
- **Remote Control** (optional): `mintree doctor` checks `~/.claude.json` for `remoteControlAtStartup: true`. Enable it by running `/config` inside Claude Code and turning on *Enable Remote Control for all sessions* — it lets you continue a local session from a different device.
|
|
318
322
|
- **iTerm2 session badge** (automatic): when you launch on [iTerm2](https://iterm2.com), mintree sets the terminal **badge** — the large translucent label drawn over the session — to the session name (the worktree issue id like `VAL-68`, or the orchestrator's name) so each tab stays identifiable at a glance. It uses the badge rather than the tab title because Claude Code overwrites the title while it runs; the badge is independent of it and persists for the whole session, then clears on exit. No-op on other terminals (detected via `TERM_PROGRAM` / `LC_TERMINAL`).
|
|
319
323
|
|
|
320
324
|
---
|
package/dist/lib/metadata.js
CHANGED
|
@@ -20,7 +20,7 @@ function sanitizeOrchestratorPromptTemplate(raw) {
|
|
|
20
20
|
/**
|
|
21
21
|
* Keeps only safe, repo-root-relative paths. Drops non-strings, blanks,
|
|
22
22
|
* absolute paths and any entry that escapes the repo root via `..` — a
|
|
23
|
-
* malicious / fat-fingered `metadata.json` must never make mintree
|
|
23
|
+
* malicious / fat-fingered `metadata.json` must never make mintree copy
|
|
24
24
|
* something outside the worktree. Normalises and de-dupes the survivors.
|
|
25
25
|
*/
|
|
26
26
|
function sanitizeLinkFiles(raw) {
|
|
@@ -50,23 +50,25 @@ function tryRunInitScript(scriptPath, worktreePath, repoRoot) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Copies each `metadata.linkFiles` entry from the main repo into the freshly
|
|
54
54
|
* created worktree. Git worktrees don't share untracked files, so gitignored
|
|
55
|
-
* config like `.env` is absent in a new worktree; this
|
|
55
|
+
* config like `.env` is absent in a new worktree; this copies it in so the
|
|
56
56
|
* worktree's tooling finds the same secrets/config as the main checkout.
|
|
57
57
|
*
|
|
58
|
-
* A
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* A copy (not a symlink) gives each worktree its OWN file — editing the
|
|
59
|
+
* worktree's `.env` no longer mutates the main checkout's, so a per-worktree
|
|
60
|
+
* tweak (a port, a feature flag) stays local. The trade-off is no single source
|
|
61
|
+
* of truth: rotating a credential in the main `.env` does NOT propagate to
|
|
62
|
+
* already-created worktrees. Entries are repo-root-relative (already validated
|
|
63
|
+
* by `sanitizeLinkFiles`). Each entry is best-effort: a missing source or an
|
|
62
64
|
* already-present target is skipped, never fatal.
|
|
63
65
|
*/
|
|
64
|
-
function
|
|
66
|
+
function copyFilesIntoWorktree(repoRoot, worktreePath, linkFiles, pushStep) {
|
|
65
67
|
for (const rel of linkFiles) {
|
|
66
68
|
const source = path.join(repoRoot, rel);
|
|
67
69
|
const target = path.join(worktreePath, rel);
|
|
68
70
|
if (!pathExists(source)) {
|
|
69
|
-
pushStep({ kind: "skip", label: `skipped
|
|
71
|
+
pushStep({ kind: "skip", label: `skipped copy ${rel}`, detail: "not present in repo root" });
|
|
70
72
|
continue;
|
|
71
73
|
}
|
|
72
74
|
// lstat (not pathExists) so an existing symlink/file/dir already at the
|
|
@@ -83,20 +85,20 @@ function linkFilesIntoWorktree(repoRoot, worktreePath, linkFiles, pushStep) {
|
|
|
83
85
|
if (targetTaken) {
|
|
84
86
|
pushStep({
|
|
85
87
|
kind: "skip",
|
|
86
|
-
label: `skipped
|
|
88
|
+
label: `skipped copy ${rel}`,
|
|
87
89
|
detail: "already present in worktree",
|
|
88
90
|
});
|
|
89
91
|
continue;
|
|
90
92
|
}
|
|
91
93
|
try {
|
|
92
94
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
93
|
-
fs.
|
|
94
|
-
pushStep({ kind: "ok", label: `
|
|
95
|
+
fs.copyFileSync(source, target);
|
|
96
|
+
pushStep({ kind: "ok", label: `copied ${rel}`, detail: `from ${source}` });
|
|
95
97
|
}
|
|
96
98
|
catch (err) {
|
|
97
99
|
pushStep({
|
|
98
100
|
kind: "warn",
|
|
99
|
-
label: `failed to
|
|
101
|
+
label: `failed to copy ${rel}`,
|
|
100
102
|
detail: err instanceof Error ? err.message : String(err),
|
|
101
103
|
});
|
|
102
104
|
}
|
|
@@ -260,11 +262,11 @@ export async function runCreate(branchArg, opts) {
|
|
|
260
262
|
upsertIssue(root, parsed.issueId, base ? { base_branch: base } : {});
|
|
261
263
|
pushStep({ kind: "ok", label: "metadata updated", detail: `issue ${parsed.issueId}` });
|
|
262
264
|
await nextFrame(progress);
|
|
263
|
-
//
|
|
265
|
+
// Copy gitignored config (e.g. .env) before init.sh, so the hook can rely
|
|
264
266
|
// on those files being present.
|
|
265
267
|
const linkFiles = readMetadata(root).linkFiles;
|
|
266
268
|
if (linkFiles && linkFiles.length > 0) {
|
|
267
|
-
|
|
269
|
+
copyFilesIntoWorktree(root, worktreePath, linkFiles, pushStep);
|
|
268
270
|
await nextFrame(progress);
|
|
269
271
|
}
|
|
270
272
|
const initShPath = getInitScriptPath(root);
|
|
@@ -422,11 +424,11 @@ export async function runCreateDetached(opts) {
|
|
|
422
424
|
upsertIssue(root, opts.issueId, { base_branch: currentBranch });
|
|
423
425
|
pushStep({ kind: "ok", label: "metadata updated", detail: `issue ${opts.issueId}` });
|
|
424
426
|
await nextFrame(progress);
|
|
425
|
-
//
|
|
427
|
+
// Copy gitignored config (e.g. .env) before init.sh, so the hook can rely
|
|
426
428
|
// on those files being present.
|
|
427
429
|
const linkFiles = readMetadata(root).linkFiles;
|
|
428
430
|
if (linkFiles && linkFiles.length > 0) {
|
|
429
|
-
|
|
431
|
+
copyFilesIntoWorktree(root, worktreePath, linkFiles, pushStep);
|
|
430
432
|
await nextFrame(progress);
|
|
431
433
|
}
|
|
432
434
|
const initShPath = getInitScriptPath(root);
|
package/package.json
CHANGED