mintree 0.5.2 → 0.5.4

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.
@@ -14,6 +14,7 @@ import { runCreate, runCreateDetached, } from "../lib/worktreeCreate.js";
14
14
  import { runRemove, runRemoveByPath } from "../lib/worktreeRemove.js";
15
15
  import { writePromptFile } from "../lib/worktreeCreate.js";
16
16
  import { buildCreateMarkers, buildOrchestrateMarkers, emitMarkers } from "../lib/markers.js";
17
+ import { buildOrchestratorRcName } from "../lib/orchestrate.js";
17
18
  import { readMetadata } from "../lib/metadata.js";
18
19
  import { defaultOrchestratorPrompt, renderOrchestratorTemplate, renderPromptTemplate, } from "../lib/promptTemplate.js";
19
20
  import { createProvider } from "../lib/providers/index.js";
@@ -1101,7 +1102,8 @@ export default function Dashboard() {
1101
1102
  : defaultOrchestratorPrompt(idList);
1102
1103
  const promptFile = writePromptFile(prompt);
1103
1104
  const permissionMode = meta.defaultPermissionMode ?? "default";
1104
- emitMarkers(buildOrchestrateMarkers({ repoRoot: root, promptFile, permissionMode }));
1105
+ const rcName = buildOrchestratorRcName(ids) ?? undefined;
1106
+ emitMarkers(buildOrchestrateMarkers({ repoRoot: root, promptFile, permissionMode, rcName }));
1105
1107
  exit();
1106
1108
  }
1107
1109
  function openCreateOverlay(issue) {
@@ -8,6 +8,7 @@ export declare const options: z.ZodObject<{
8
8
  default: "default";
9
9
  auto: "auto";
10
10
  }>>;
11
+ rcName: z.ZodOptional<z.ZodString>;
11
12
  }, z.core.$strip>;
12
13
  type Props = {
13
14
  args: z.infer<typeof args>;
@@ -10,6 +10,7 @@ import { findMainRepoRoot, getMintreeDir, pathExists } from "../lib/git.js";
10
10
  import { readMetadata } from "../lib/metadata.js";
11
11
  import { launchClaude, PERMISSION_MODES } from "../lib/claude.js";
12
12
  import { defaultOrchestratorPrompt, renderOrchestratorTemplate } from "../lib/promptTemplate.js";
13
+ import { buildOrchestratorRcName } from "../lib/orchestrate.js";
13
14
  export const description = "Launch a Claude orchestrator in the repo root to resolve a batch of tickets";
14
15
  export const args = z
15
16
  .array(z.string())
@@ -38,6 +39,12 @@ export const options = z.object({
38
39
  description: `Claude --permission-mode (one of: ${PERMISSION_MODES.join(", ")}). Defaults to metadata.defaultPermissionMode, else "default".`,
39
40
  alias: "m",
40
41
  })),
42
+ rcName: z
43
+ .string()
44
+ .optional()
45
+ .describe(option({
46
+ description: "Remote Control name for the session. Defaults to orchestrator-<ids> derived from the positional ids (used by the dashboard, which has no positional ids), else orchestrator-<session-hash>.",
47
+ })),
41
48
  });
42
49
  function resolve(cwd, ids, opts) {
43
50
  if (opts.prompt && opts.promptFile) {
@@ -96,9 +103,14 @@ function resolve(cwd, ids, opts) {
96
103
  };
97
104
  }
98
105
  const permissionMode = opts.permissionMode ?? readMetadata(repoRoot).defaultPermissionMode ?? "default";
106
+ const sessionId = randomUUID();
107
+ // RC name priority: explicit --rc-name (the dashboard passes the
108
+ // ids-derived name this way) > derive from positional ids > session hash
109
+ // fallback for the prompt-only path with no tickets to name after.
110
+ const remoteControlName = opts.rcName ?? buildOrchestratorRcName(ids) ?? `orchestrator-${sessionId.slice(0, 8)}`;
99
111
  return {
100
112
  ok: true,
101
- data: { repoRoot, sessionId: randomUUID(), permissionMode, prompt },
113
+ data: { repoRoot, sessionId, permissionMode, prompt, remoteControlName },
102
114
  };
103
115
  }
104
116
  export default function Orchestrate({ args: ids, options }) {
@@ -124,7 +136,12 @@ export default function Orchestrate({ args: ids, options }) {
124
136
  resume: false,
125
137
  prompt: resolved.prompt,
126
138
  cwd: resolved.repoRoot,
127
- remoteControlName: "orchestrator",
139
+ // Name the RC session after the tickets it covers
140
+ // (orchestrator-VAL-12_BE-16_FE-3) so it's identifiable in the RC
141
+ // UI. Falls back to a session hash when there are no ids. Note:
142
+ // re-launching the exact same batch reuses the name, which can
143
+ // collide with a still-registered prior session.
144
+ remoteControlName: resolved.remoteControlName,
128
145
  });
129
146
  child.on("error", (err) => {
130
147
  setState({ phase: "error", message: `Failed to launch claude: ${err.message}` });
@@ -148,7 +165,7 @@ export default function Orchestrate({ args: ids, options }) {
148
165
  }
149
166
  const { resolved } = state;
150
167
  const sessionShort = resolved.sessionId.slice(0, 8);
151
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "mintree orchestrate" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", resolved.repoRoot] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "session: " }), _jsxs(Text, { children: [sessionShort, "\u2026"] }), _jsx(Text, { dimColor: true, children: " (starting)" })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "permission-mode: " }), _jsx(Text, { children: resolved.permissionMode })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "prompt: " }), _jsxs(Text, { children: ["\"", truncate(resolved.prompt.replace(/\n/g, " "), 60), "\""] })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", bold: true, children: "\u2713 Launching Claude orchestrator..." }) })] }));
168
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "mintree orchestrate" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", resolved.repoRoot] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "session: " }), _jsxs(Text, { children: [sessionShort, "\u2026"] }), _jsx(Text, { dimColor: true, children: " (starting)" })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "rc: " }), _jsx(Text, { children: resolved.remoteControlName })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "permission-mode: " }), _jsx(Text, { children: resolved.permissionMode })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "prompt: " }), _jsxs(Text, { children: ["\"", truncate(resolved.prompt.replace(/\n/g, " "), 60), "\""] })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", bold: true, children: "\u2713 Launching Claude orchestrator..." }) })] }));
152
169
  }
153
170
  function truncate(s, max) {
154
171
  if (s.length <= max)
@@ -23,6 +23,7 @@ export type OrchestrateMarkers = {
23
23
  repoRoot: string;
24
24
  promptFile: string;
25
25
  permissionMode?: string;
26
+ rcName?: string;
26
27
  };
27
28
  /**
28
29
  * Builds the marker block emitted when the dashboard launches the orchestrator
@@ -55,5 +55,8 @@ export function buildOrchestrateMarkers(input) {
55
55
  if (input.permissionMode) {
56
56
  lines.push(`MINTREE_PERMISSION_MODE:${input.permissionMode}`);
57
57
  }
58
+ if (input.rcName) {
59
+ lines.push(`MINTREE_ORCHESTRATE_RC_NAME:${input.rcName}`);
60
+ }
58
61
  return lines;
59
62
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Derives the Remote Control name for an orchestrator session from the ticket
3
+ * ids it covers, e.g. ["VAL-12", "BE-16", "FE-3"] -> "orchestrator-VAL-12_BE-16_FE-3".
4
+ *
5
+ * The ids are joined with "_" (not spaces) so the result is a single
6
+ * shell-safe token — it travels through the dashboard markers and the shell
7
+ * wrapper as one `--rc-name` argument without quoting.
8
+ *
9
+ * Returns null when there are no ids, letting the caller fall back to a
10
+ * session-hash name (the `mintree orchestrate --prompt "..."` path, which has
11
+ * no tickets to name the session after).
12
+ */
13
+ export declare function buildOrchestratorRcName(ids: string[]): string | null;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Derives the Remote Control name for an orchestrator session from the ticket
3
+ * ids it covers, e.g. ["VAL-12", "BE-16", "FE-3"] -> "orchestrator-VAL-12_BE-16_FE-3".
4
+ *
5
+ * The ids are joined with "_" (not spaces) so the result is a single
6
+ * shell-safe token — it travels through the dashboard markers and the shell
7
+ * wrapper as one `--rc-name` argument without quoting.
8
+ *
9
+ * Returns null when there are no ids, letting the caller fall back to a
10
+ * session-hash name (the `mintree orchestrate --prompt "..."` path, which has
11
+ * no tickets to name the session after).
12
+ */
13
+ export function buildOrchestratorRcName(ids) {
14
+ if (ids.length === 0)
15
+ return null;
16
+ return `orchestrator-${ids.join("_")}`;
17
+ }
@@ -21,9 +21,8 @@ import { readMetadata } from "../metadata.js";
21
21
  const DEFAULT_API_URL = "https://api.linear.app/graphql";
22
22
  // Linear state types we treat as "done" — work in these states is excluded
23
23
  // from the assigned list and protected from transitions back to In Progress.
24
- // "duplicate" is Linear's own state type for issues closed as duplicates
25
- // (e.g. a "Duplicate" workflow state); it must be excluded alongside
26
- // completed/canceled or those tickets keep showing up in the dashboard.
24
+ // "duplicate" is its own terminal state type in Linear (separate from
25
+ // "canceled"), so it has to be listed explicitly or those issues leak in.
27
26
  const DEFAULT_PROTECTED_STATE_TYPES = ["completed", "canceled", "duplicate"];
28
27
  const STATUS_ORDER_UNSET = 999;
29
28
  // One query covers viewer + teams + issues; a single 20s budget comfortably
@@ -406,9 +405,11 @@ export class LinearProvider {
406
405
  const protectedTypes = new Set(cfg.protectedStateTypes ?? DEFAULT_PROTECTED_STATE_TYPES);
407
406
  const out = [];
408
407
  for (const wi of data.issues) {
409
- // Defensive — the bootstrap query already excludes completed/canceled
410
- // via state.type.nin, but a workspace could have custom state types
411
- // the user added to the protected list locally.
408
+ // Defensive — the bootstrap query already excludes
409
+ // completed/canceled/duplicate via state.type.nin, but a workspace
410
+ // could have custom state types the user added to the protected list
411
+ // locally (and a stale snapshot cache predating the query change
412
+ // still gets filtered here).
412
413
  const type = wi.state?.type;
413
414
  if (type && protectedTypes.has(type))
414
415
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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>",
package/shell/init.bash CHANGED
@@ -55,6 +55,9 @@ function mintree() {
55
55
  orch_prompt_file=$(echo "$clean_output" | grep "MINTREE_ORCHESTRATE_PROMPT_FILE:" | sed 's/.*MINTREE_ORCHESTRATE_PROMPT_FILE://')
56
56
  [[ -n "$orch_prompt_file" ]] && extra+=(--prompt-file "$orch_prompt_file")
57
57
  [[ -n "$perm_mode" ]] && extra+=(--permission-mode "$perm_mode")
58
+ local orch_rc_name
59
+ orch_rc_name=$(echo "$clean_output" | grep "MINTREE_ORCHESTRATE_RC_NAME:" | sed 's/.*MINTREE_ORCHESTRATE_RC_NAME://')
60
+ [[ -n "$orch_rc_name" ]] && extra+=(--rc-name "$orch_rc_name")
58
61
  command mintree orchestrate "${extra[@]}"
59
62
  return $?
60
63
  fi
package/shell/init.zsh CHANGED
@@ -65,6 +65,9 @@ function mintree() {
65
65
  orch_prompt_file=$(echo "$clean_output" | grep "MINTREE_ORCHESTRATE_PROMPT_FILE:" | sed 's/.*MINTREE_ORCHESTRATE_PROMPT_FILE://')
66
66
  [[ -n "$orch_prompt_file" ]] && extra+=(--prompt-file "$orch_prompt_file")
67
67
  [[ -n "$perm_mode" ]] && extra+=(--permission-mode "$perm_mode")
68
+ local orch_rc_name
69
+ orch_rc_name=$(echo "$clean_output" | grep "MINTREE_ORCHESTRATE_RC_NAME:" | sed 's/.*MINTREE_ORCHESTRATE_RC_NAME://')
70
+ [[ -n "$orch_rc_name" ]] && extra+=(--rc-name "$orch_rc_name")
68
71
  command mintree orchestrate "${extra[@]}"
69
72
  return $?
70
73
  fi