mintree 0.5.3 → 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,11 +136,12 @@ export default function Orchestrate({ args: ids, options }) {
124
136
  resume: false,
125
137
  prompt: resolved.prompt,
126
138
  cwd: resolved.repoRoot,
127
- // Suffix the fresh session id so two orchestrators (or an
128
- // orchestrator alongside a stale RC session) never share a name.
129
- // A literal "orchestrator" collides and the RC handshake fails to
130
- // register the newer session.
131
- remoteControlName: `orchestrator-${resolved.sessionId.slice(0, 8)}`,
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,
132
145
  });
133
146
  child.on("error", (err) => {
134
147
  setState({ phase: "error", message: `Failed to launch claude: ${err.message}` });
@@ -152,7 +165,7 @@ export default function Orchestrate({ args: ids, options }) {
152
165
  }
153
166
  const { resolved } = state;
154
167
  const sessionShort = resolved.sessionId.slice(0, 8);
155
- 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..." }) })] }));
156
169
  }
157
170
  function truncate(s, max) {
158
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,7 +21,9 @@ 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
- const DEFAULT_PROTECTED_STATE_TYPES = ["completed", "canceled"];
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.
26
+ const DEFAULT_PROTECTED_STATE_TYPES = ["completed", "canceled", "duplicate"];
25
27
  const STATUS_ORDER_UNSET = 999;
26
28
  // One query covers viewer + teams + issues; a single 20s budget comfortably
27
29
  // fits even the slowest cold-start response without making real failures
@@ -261,7 +263,7 @@ const BOOTSTRAP_QUERY = /* GraphQL */ `
261
263
  first: 100
262
264
  filter: {
263
265
  assignee: { isMe: { eq: true } }
264
- state: { type: { nin: ["completed", "canceled"] } }
266
+ state: { type: { nin: ["completed", "canceled", "duplicate"] } }
265
267
  team: { key: { in: $teamKeys } }
266
268
  }
267
269
  ) {
@@ -403,9 +405,11 @@ export class LinearProvider {
403
405
  const protectedTypes = new Set(cfg.protectedStateTypes ?? DEFAULT_PROTECTED_STATE_TYPES);
404
406
  const out = [];
405
407
  for (const wi of data.issues) {
406
- // Defensive — the bootstrap query already excludes completed/canceled
407
- // via state.type.nin, but a workspace could have custom state types
408
- // 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).
409
413
  const type = wi.state?.type;
410
414
  if (type && protectedTypes.has(type))
411
415
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.5.3",
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