santree 0.6.3 → 0.7.2

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.
@@ -254,15 +254,37 @@ export async function loadDashboardData(repoRoot) {
254
254
  function flatten(g) {
255
255
  return g.flatMap((grp) => grp.statusGroups.flatMap((sg) => sg.issues.flatMap(flattenWithChildren)));
256
256
  }
257
- // ── Partition: Issues tab (backlog/planning) vs Trees tab (work in
258
- // progress). A tracker issue with no worktree is backlog; once it gains a
257
+ // ── Partition: Triage / Issues (backlog) / Trees (work in progress).
258
+ // A tracker issue with no worktree is backlog unless the active tracker
259
+ // has a triage inbox and the issue sits in it (state.type === "triage"), in
260
+ // which case it goes to the Triage tab instead. Once any issue gains a
259
261
  // worktree it moves to the Trees tab. Children always have a worktree, so
260
262
  // they only ever appear nested in Trees. Main-repo + orphaned worktrees
261
263
  // belong to Trees (they're active checkouts, not backlog).
262
- const backlogIssues = enriched.filter((di) => !di.worktree);
264
+ const triageEnabled = tracker.supportsTriage === true;
265
+ const isTriage = (di) => triageEnabled && !di.worktree && di.issue.state.type === "triage";
266
+ const triageIssues = enriched.filter(isTriage);
267
+ const backlogIssues = enriched.filter((di) => !di.worktree && !isTriage(di));
263
268
  const treeIssues = enriched.filter((di) => di.worktree);
269
+ // Surface the most pressing triage items first: by due date ascending
270
+ // (overdue/soonest first), undated last. `dueDate` is `YYYY-MM-DD` so a
271
+ // plain string compare is chronological. buildProjectGroups preserves this
272
+ // order within each status group.
273
+ triageIssues.sort((a, b) => {
274
+ const da = a.issue.dueDate ?? null;
275
+ const db = b.issue.dueDate ?? null;
276
+ if (da && db)
277
+ return da < db ? -1 : da > db ? 1 : 0;
278
+ if (da)
279
+ return -1;
280
+ if (db)
281
+ return 1;
282
+ return 0;
283
+ });
264
284
  const groups = buildProjectGroups(backlogIssues);
265
285
  const flatIssues = flatten(groups);
286
+ const triageGroups = buildProjectGroups(triageIssues);
287
+ const flatTriage = flatten(triageGroups);
266
288
  const treeGroups = buildProjectGroups(treeIssues);
267
289
  const topLevelOrphans = orphans.filter((di) => !childTicketIds.has(di.issue.identifier));
268
290
  if (topLevelOrphans.length > 0) {
@@ -286,7 +308,7 @@ export async function loadDashboardData(repoRoot) {
286
308
  });
287
309
  flatTrees.unshift(mainEntry);
288
310
  }
289
- return { groups, flatIssues, treeGroups, flatTrees };
311
+ return { groups, flatIssues, treeGroups, flatTrees, triageGroups, flatTriage };
290
312
  }
291
313
  /** Build the synthetic dashboard row for the main repo checkout — the
292
314
  * non-worktree clone that the user typically commits master/main from.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Due-date formatting for the Triage tab. Turns Linear's `YYYY-MM-DD`
3
+ * `dueDate` string into a short, urgency-coded badge used by both the issue
4
+ * list (right column) and the detail panel.
5
+ *
6
+ * Urgency buckets (by whole calendar days from today, local time):
7
+ * - overdue (< 0) → red, "⚠ overdue Nd"
8
+ * - due today (0) → red, "⚠ due today"
9
+ * - soon (1–2 days) → yellow, "due in Nd"
10
+ * - later (≥ 3 days) → gray, "due Mon D"
11
+ *
12
+ * Returns null when there is no due date so callers can render nothing.
13
+ */
14
+ export interface DueInfo {
15
+ /** Short badge text, e.g. "⚠ overdue 3d" or "due Jun 12". */
16
+ label: string;
17
+ /** Ink color name keyed to urgency. */
18
+ color: "red" | "yellow" | "gray";
19
+ /** True for overdue/today — the cases worth a warning glyph. */
20
+ urgent: boolean;
21
+ /** Whole days until due (negative when overdue). */
22
+ days: number;
23
+ }
24
+ export declare function formatDueDate(dueDate: string | null | undefined, now?: Date): DueInfo | null;
@@ -0,0 +1,32 @@
1
+ /** Whole-day difference between two dates, ignoring time-of-day. */
2
+ function dayDiff(from, to) {
3
+ const a = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate());
4
+ const b = Date.UTC(to.getFullYear(), to.getMonth(), to.getDate());
5
+ return Math.round((b - a) / 86_400_000);
6
+ }
7
+ export function formatDueDate(dueDate, now = new Date()) {
8
+ if (!dueDate)
9
+ return null;
10
+ // Parse `YYYY-MM-DD` as a local date (not UTC) so "today" matches the user's
11
+ // wall clock. `new Date("2026-06-12")` would parse as UTC midnight; build the
12
+ // date from parts instead.
13
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(dueDate);
14
+ if (!m)
15
+ return null;
16
+ const due = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
17
+ if (Number.isNaN(due.getTime()))
18
+ return null;
19
+ const days = dayDiff(now, due);
20
+ const monthDay = due.toLocaleDateString("en-US", { month: "short", day: "numeric" });
21
+ if (days < 0) {
22
+ const n = Math.abs(days);
23
+ return { label: `⚠ overdue ${n}d`, color: "red", urgent: true, days };
24
+ }
25
+ if (days === 0) {
26
+ return { label: "⚠ due today", color: "red", urgent: true, days };
27
+ }
28
+ if (days <= 2) {
29
+ return { label: `due in ${days}d`, color: "yellow", urgent: false, days };
30
+ }
31
+ return { label: `due ${monthDay}`, color: "gray", urgent: false, days };
32
+ }
@@ -1,7 +1,7 @@
1
1
  import type { PRInfo, PRCheck, PRReview, PRConversationComment, SearchPR } from "../github.js";
2
2
  import type { ClaudeTodo } from "../claude-todos.js";
3
- import type { AssignedIssue, Issue } from "../trackers/types.js";
4
- export type { AssignedIssue, Issue } from "../trackers/types.js";
3
+ import type { AssignedIssue, Comment, Issue, TriageSchedule } from "../trackers/types.js";
4
+ export type { AssignedIssue, Comment, Issue, TriageSchedule } from "../trackers/types.js";
5
5
  export interface WorktreeInfo {
6
6
  path: string;
7
7
  branch: string;
@@ -72,8 +72,11 @@ export interface EnrichedReviewPR {
72
72
  * Null when the user hasn't set one or the lookup failed. */
73
73
  authorName: string | null;
74
74
  }
75
- export type DashboardTab = "issues" | "trees" | "reviews";
76
- export type ActionOverlay = "mode-select" | "context-input" | "base-select" | "confirm-delete" | "confirm-setup" | "commit" | "pr-create" | "diff" | "help" | "tracker-select" | "issue-form" | "confirm-delete-issue" | null;
75
+ export type DashboardTab = "triage" | "issues" | "trees" | "reviews";
76
+ export type ActionOverlay = "mode-select" | "context-input" | "base-select" | "confirm-delete" | "confirm-setup" | "commit" | "pr-create" | "diff" | "help" | "tracker-select" | "issue-form" | "confirm-delete-issue" | "triage-ask" | "triage-schedule" | null;
77
+ /** Triage "ask Claude" flow sub-phase. "input" is owned by MultilineTextArea
78
+ * (Ctrl+D submit / Ctrl+G cancel); the rest are driven by the outer handler. */
79
+ export type TriageAskPhase = "input" | "running" | "answer" | "error";
77
80
  /** Tracker-selection overlay sub-phase: pick a tracker, then (for Linear with
78
81
  * multiple authenticated workspaces) pick the org. */
79
82
  export type TrackerSelectPhase = "root" | "linear-org";
@@ -96,6 +99,15 @@ export interface DiffFile {
96
99
  }
97
100
  export type CommitPhase = "idle" | "confirm-stage" | "choose-mode" | "filling" | "awaiting-message" | "committing" | "pushing" | "done" | "error";
98
101
  export type PrCreatePhase = "idle" | "choose-mode" | "pushing" | "filling" | "review" | "confirm" | "creating" | "done" | "error";
102
+ /** Per-worktree deletion progress. Deletions run concurrently (fire `d`, move
103
+ * on, fire `d` again) so each tracks its own staged log + phase; the detail
104
+ * pane renders the entry for the selected worktree. Entries are pruned on the
105
+ * next data refresh once the worktree is gone (see SET_DATA). */
106
+ export interface DeleteStatus {
107
+ logs: string;
108
+ phase: "removing" | "done" | "error";
109
+ error: string | null;
110
+ }
99
111
  export interface DashboardState {
100
112
  activeTab: DashboardTab;
101
113
  groups: ProjectGroup[];
@@ -112,6 +124,25 @@ export interface DashboardState {
112
124
  treeSelectedIndex: number;
113
125
  treeListScrollOffset: number;
114
126
  treeDetailScrollOffset: number;
127
+ triageGroups: ProjectGroup[];
128
+ flatTriage: DashboardIssue[];
129
+ triageSelectedIndex: number;
130
+ triageListScrollOffset: number;
131
+ triageDetailScrollOffset: number;
132
+ /** Lazily-fetched comments per triage issue identifier. Absent = not yet
133
+ * loaded (detail panel shows "loading…"); present (possibly empty) = loaded. */
134
+ triageCommentsById: Record<string, Comment[]>;
135
+ /** Triage on-call rotations for the viewer's teams (Linear). Loaded on
136
+ * refresh; shown via the `triage-schedule` overlay and a compact line in the
137
+ * triage detail pane. */
138
+ triageSchedules: TriageSchedule[];
139
+ triageScheduleScrollOffset: number;
140
+ triageAskTicketId: string | null;
141
+ triageAskPhase: TriageAskPhase;
142
+ triageAskQuestion: string;
143
+ triageAskAnswer: string | null;
144
+ triageAskError: string | null;
145
+ triageAskScrollOffset: number;
115
146
  trackerSelectPhase: TrackerSelectPhase;
116
147
  trackerSelectIndex: number;
117
148
  trackerSelectOrgs: TrackerOrgOption[];
@@ -130,7 +161,8 @@ export interface DashboardState {
130
161
  creatingForTicket: string | null;
131
162
  creationLogs: string;
132
163
  creationError: string | null;
133
- deletingForTicket: string | null;
164
+ /** In-flight (and just-finished) worktree deletions, keyed by ticket id. */
165
+ deletingTickets: Record<string, DeleteStatus>;
134
166
  commitPhase: CommitPhase;
135
167
  commitMessage: string;
136
168
  commitError: string | null;
@@ -146,6 +178,7 @@ export interface DashboardState {
146
178
  prCreateUrl: string | null;
147
179
  prCreateBody: string | null;
148
180
  prCreateTitle: string | null;
181
+ prCreateDraft: boolean;
149
182
  setupMode: "plan" | "implement" | null;
150
183
  baseSelectOptions: string[];
151
184
  baseSelectIndex: number;
@@ -182,6 +215,8 @@ export type DashboardAction = {
182
215
  flatIssues: DashboardIssue[];
183
216
  treeGroups: ProjectGroup[];
184
217
  flatTrees: DashboardIssue[];
218
+ triageGroups: ProjectGroup[];
219
+ flatTriage: DashboardIssue[];
185
220
  } | {
186
221
  type: "SELECT";
187
222
  index: number;
@@ -194,6 +229,48 @@ export type DashboardAction = {
194
229
  } | {
195
230
  type: "TREE_SCROLL_DETAIL";
196
231
  offset: number;
232
+ } | {
233
+ type: "TRIAGE_SELECT";
234
+ index: number;
235
+ } | {
236
+ type: "TRIAGE_SCROLL_LIST";
237
+ offset: number;
238
+ } | {
239
+ type: "TRIAGE_SCROLL_DETAIL";
240
+ offset: number;
241
+ } | {
242
+ type: "TRIAGE_COMMENTS_LOADED";
243
+ id: string;
244
+ comments: Comment[];
245
+ } | {
246
+ type: "SET_TRIAGE_SCHEDULES";
247
+ schedules: TriageSchedule[];
248
+ } | {
249
+ type: "TRIAGE_SCHEDULE_OPEN";
250
+ } | {
251
+ type: "TRIAGE_SCHEDULE_SCROLL";
252
+ offset: number;
253
+ } | {
254
+ type: "TRIAGE_SCHEDULE_CLOSE";
255
+ } | {
256
+ type: "TRIAGE_ASK_OPEN";
257
+ ticketId: string;
258
+ } | {
259
+ type: "TRIAGE_ASK_CHANGE";
260
+ value: string;
261
+ } | {
262
+ type: "TRIAGE_ASK_RUN";
263
+ } | {
264
+ type: "TRIAGE_ASK_ANSWER";
265
+ answer: string;
266
+ } | {
267
+ type: "TRIAGE_ASK_ERROR";
268
+ error: string;
269
+ } | {
270
+ type: "TRIAGE_ASK_SCROLL";
271
+ offset: number;
272
+ } | {
273
+ type: "TRIAGE_ASK_CLOSE";
197
274
  } | {
198
275
  type: "TRACKER_SELECT_OPEN";
199
276
  } | {
@@ -267,8 +344,17 @@ export type DashboardAction = {
267
344
  } | {
268
345
  type: "DELETE_START";
269
346
  ticketId: string;
347
+ } | {
348
+ type: "DELETE_LOG";
349
+ ticketId: string;
350
+ logs: string;
270
351
  } | {
271
352
  type: "DELETE_DONE";
353
+ ticketId: string;
354
+ } | {
355
+ type: "DELETE_ERROR";
356
+ ticketId: string;
357
+ error: string;
272
358
  } | {
273
359
  type: "COMMIT_START";
274
360
  /** Null when committing on a non-ticket branch (e.g. the main
@@ -312,6 +398,8 @@ export type DashboardAction = {
312
398
  type: "PR_CREATE_CONFIRM";
313
399
  } | {
314
400
  type: "PR_CREATE_EDIT";
401
+ } | {
402
+ type: "PR_CREATE_TOGGLE_DRAFT";
315
403
  } | {
316
404
  type: "PR_CREATE_DONE";
317
405
  url: string;
@@ -15,6 +15,20 @@ export const initialState = {
15
15
  treeSelectedIndex: 0,
16
16
  treeListScrollOffset: 0,
17
17
  treeDetailScrollOffset: 0,
18
+ triageGroups: [],
19
+ flatTriage: [],
20
+ triageSelectedIndex: 0,
21
+ triageListScrollOffset: 0,
22
+ triageDetailScrollOffset: 0,
23
+ triageCommentsById: {},
24
+ triageSchedules: [],
25
+ triageScheduleScrollOffset: 0,
26
+ triageAskTicketId: null,
27
+ triageAskPhase: "input",
28
+ triageAskQuestion: "",
29
+ triageAskAnswer: null,
30
+ triageAskError: null,
31
+ triageAskScrollOffset: 0,
18
32
  trackerSelectPhase: "root",
19
33
  trackerSelectIndex: 0,
20
34
  trackerSelectOrgs: [],
@@ -33,7 +47,7 @@ export const initialState = {
33
47
  creatingForTicket: null,
34
48
  creationLogs: "",
35
49
  creationError: null,
36
- deletingForTicket: null,
50
+ deletingTickets: {},
37
51
  commitPhase: "idle",
38
52
  commitMessage: "",
39
53
  commitError: null,
@@ -49,6 +63,7 @@ export const initialState = {
49
63
  prCreateUrl: null,
50
64
  prCreateBody: null,
51
65
  prCreateTitle: null,
66
+ prCreateDraft: false,
52
67
  setupMode: null,
53
68
  baseSelectOptions: [],
54
69
  baseSelectIndex: 0,
@@ -90,19 +105,36 @@ export function reducer(state, action) {
90
105
  if (found >= 0)
91
106
  newTreeIndex = found;
92
107
  }
108
+ const prevTriageId = state.flatTriage[state.triageSelectedIndex]?.issue.identifier;
109
+ let newTriageIndex = 0;
110
+ if (prevTriageId) {
111
+ const found = action.flatTriage.findIndex((d) => d.issue.identifier === prevTriageId);
112
+ if (found >= 0)
113
+ newTriageIndex = found;
114
+ }
115
+ // Prune delete-progress entries whose worktree is gone (a successful
116
+ // removal). In-progress ("removing") and failed ("error") deletions
117
+ // keep their row, so their entries survive and stay visible.
118
+ const presentTreeIds = new Set(action.flatTrees.map((d) => d.issue.identifier));
119
+ const deletingTickets = Object.fromEntries(Object.entries(state.deletingTickets).filter(([id]) => presentTreeIds.has(id)));
93
120
  return {
94
121
  ...state,
95
122
  groups: action.groups,
96
123
  flatIssues: action.flatIssues,
97
124
  treeGroups: action.treeGroups,
98
125
  flatTrees: action.flatTrees,
126
+ triageGroups: action.triageGroups,
127
+ flatTriage: action.flatTriage,
128
+ deletingTickets,
99
129
  selectedIndex: newIndex,
100
130
  treeSelectedIndex: newTreeIndex,
131
+ triageSelectedIndex: newTriageIndex,
101
132
  loading: false,
102
133
  refreshing: false,
103
134
  error: null,
104
135
  detailScrollOffset: 0,
105
136
  treeDetailScrollOffset: 0,
137
+ triageDetailScrollOffset: 0,
106
138
  };
107
139
  }
108
140
  case "TREE_SELECT":
@@ -111,6 +143,62 @@ export function reducer(state, action) {
111
143
  return { ...state, treeListScrollOffset: action.offset };
112
144
  case "TREE_SCROLL_DETAIL":
113
145
  return { ...state, treeDetailScrollOffset: action.offset };
146
+ case "TRIAGE_SELECT":
147
+ return { ...state, triageSelectedIndex: action.index, triageDetailScrollOffset: 0 };
148
+ case "TRIAGE_SCROLL_LIST":
149
+ return { ...state, triageListScrollOffset: action.offset };
150
+ case "TRIAGE_SCROLL_DETAIL":
151
+ return { ...state, triageDetailScrollOffset: action.offset };
152
+ case "TRIAGE_COMMENTS_LOADED":
153
+ return {
154
+ ...state,
155
+ triageCommentsById: { ...state.triageCommentsById, [action.id]: action.comments },
156
+ };
157
+ case "SET_TRIAGE_SCHEDULES":
158
+ return { ...state, triageSchedules: action.schedules };
159
+ case "TRIAGE_SCHEDULE_OPEN":
160
+ return { ...state, overlay: "triage-schedule", triageScheduleScrollOffset: 0 };
161
+ case "TRIAGE_SCHEDULE_SCROLL":
162
+ return { ...state, triageScheduleScrollOffset: action.offset };
163
+ case "TRIAGE_SCHEDULE_CLOSE":
164
+ return { ...state, overlay: null };
165
+ case "TRIAGE_ASK_OPEN":
166
+ return {
167
+ ...state,
168
+ overlay: "triage-ask",
169
+ triageAskTicketId: action.ticketId,
170
+ triageAskPhase: "input",
171
+ triageAskQuestion: "",
172
+ triageAskAnswer: null,
173
+ triageAskError: null,
174
+ triageAskScrollOffset: 0,
175
+ };
176
+ case "TRIAGE_ASK_CHANGE":
177
+ return { ...state, triageAskQuestion: action.value };
178
+ case "TRIAGE_ASK_RUN":
179
+ return { ...state, triageAskPhase: "running", triageAskError: null };
180
+ case "TRIAGE_ASK_ANSWER":
181
+ return {
182
+ ...state,
183
+ triageAskPhase: "answer",
184
+ triageAskAnswer: action.answer,
185
+ triageAskScrollOffset: 0,
186
+ };
187
+ case "TRIAGE_ASK_ERROR":
188
+ return { ...state, triageAskPhase: "error", triageAskError: action.error };
189
+ case "TRIAGE_ASK_SCROLL":
190
+ return { ...state, triageAskScrollOffset: action.offset };
191
+ case "TRIAGE_ASK_CLOSE":
192
+ return {
193
+ ...state,
194
+ overlay: null,
195
+ triageAskTicketId: null,
196
+ triageAskPhase: "input",
197
+ triageAskQuestion: "",
198
+ triageAskAnswer: null,
199
+ triageAskError: null,
200
+ triageAskScrollOffset: 0,
201
+ };
114
202
  case "TRACKER_SELECT_OPEN":
115
203
  return {
116
204
  ...state,
@@ -215,9 +303,47 @@ export function reducer(state, action) {
215
303
  baseSelectChosen: null,
216
304
  };
217
305
  case "DELETE_START":
218
- return { ...state, deletingForTicket: action.ticketId };
219
- case "DELETE_DONE":
220
- return { ...state, deletingForTicket: null };
306
+ return {
307
+ ...state,
308
+ deletingTickets: {
309
+ ...state.deletingTickets,
310
+ [action.ticketId]: { logs: "", phase: "removing", error: null },
311
+ },
312
+ };
313
+ case "DELETE_LOG": {
314
+ const prev = state.deletingTickets[action.ticketId];
315
+ if (!prev)
316
+ return state;
317
+ return {
318
+ ...state,
319
+ deletingTickets: {
320
+ ...state.deletingTickets,
321
+ [action.ticketId]: { ...prev, logs: prev.logs + action.logs },
322
+ },
323
+ };
324
+ }
325
+ case "DELETE_DONE": {
326
+ const prev = state.deletingTickets[action.ticketId];
327
+ if (!prev)
328
+ return state;
329
+ return {
330
+ ...state,
331
+ deletingTickets: {
332
+ ...state.deletingTickets,
333
+ [action.ticketId]: { ...prev, phase: "done" },
334
+ },
335
+ };
336
+ }
337
+ case "DELETE_ERROR": {
338
+ const prev = state.deletingTickets[action.ticketId] ?? { logs: "" };
339
+ return {
340
+ ...state,
341
+ deletingTickets: {
342
+ ...state.deletingTickets,
343
+ [action.ticketId]: { logs: prev.logs, phase: "error", error: action.error },
344
+ },
345
+ };
346
+ }
221
347
  case "COMMIT_START":
222
348
  return {
223
349
  ...state,
@@ -260,6 +386,7 @@ export function reducer(state, action) {
260
386
  prCreateBranch: action.branch,
261
387
  prCreateError: null,
262
388
  prCreateUrl: null,
389
+ prCreateDraft: false,
263
390
  };
264
391
  case "PR_CREATE_PHASE":
265
392
  return { ...state, prCreatePhase: action.phase };
@@ -277,6 +404,8 @@ export function reducer(state, action) {
277
404
  return { ...state, prCreateBody: action.body };
278
405
  case "PR_CREATE_CONFIRM":
279
406
  return { ...state, prCreatePhase: "confirm" };
407
+ case "PR_CREATE_TOGGLE_DRAFT":
408
+ return { ...state, prCreateDraft: !state.prCreateDraft };
280
409
  case "PR_CREATE_EDIT":
281
410
  return { ...state, prCreatePhase: "review" };
282
411
  case "PR_CREATE_DONE":
package/dist/lib/git.d.ts CHANGED
@@ -74,7 +74,7 @@ export declare function createWorktree(branchName: string, baseBranch: string, r
74
74
  * Runs: `git worktree remove [--force] <path>` then `git branch -d|-D <branchName>`
75
75
  * Returns { success: false, error } if worktree not found or git fails.
76
76
  */
77
- export declare function removeWorktree(branchName: string, repoRoot: string, force?: boolean): Promise<{
77
+ export declare function removeWorktree(branchName: string, repoRoot: string, force?: boolean, onProgress?: (message: string) => void): Promise<{
78
78
  success: boolean;
79
79
  error?: string;
80
80
  }>;
package/dist/lib/git.js CHANGED
@@ -191,13 +191,15 @@ export async function createWorktree(branchName, baseBranch, repoRoot) {
191
191
  * Runs: `git worktree remove [--force] <path>` then `git branch -d|-D <branchName>`
192
192
  * Returns { success: false, error } if worktree not found or git fails.
193
193
  */
194
- export async function removeWorktree(branchName, repoRoot, force = false) {
194
+ export async function removeWorktree(branchName, repoRoot, force = false, onProgress) {
195
+ const report = onProgress ?? (() => { });
195
196
  // Find the worktree by branch name using git's worktree tracking
196
197
  const worktreePath = getWorktreePath(branchName);
197
198
  if (!worktreePath) {
198
199
  return { success: false, error: `Worktree not found: ${branchName}` };
199
200
  }
200
201
  try {
202
+ report("Removing worktree…");
201
203
  const forceFlag = force ? "--force" : "";
202
204
  await execAsync(`git worktree remove ${forceFlag} "${worktreePath}"`, {
203
205
  cwd: repoRoot,
@@ -205,6 +207,7 @@ export async function removeWorktree(branchName, repoRoot, force = false) {
205
207
  // Clean up any remaining files (untracked files, node_modules, etc.)
206
208
  // git worktree remove doesn't delete untracked files
207
209
  if (fs.existsSync(worktreePath)) {
210
+ report("Cleaning up leftover files…");
208
211
  // Fix permissions first (node_modules often has restricted perms)
209
212
  try {
210
213
  execSync(`chmod -R u+w "${worktreePath}"`, { stdio: "ignore" });
@@ -225,6 +228,7 @@ export async function removeWorktree(branchName, repoRoot, force = false) {
225
228
  clearSessionState(repoRoot, ticketId);
226
229
  }
227
230
  // Also delete the branch
231
+ report("Deleting branch…");
228
232
  const deleteFlag = force ? "-D" : "-d";
229
233
  try {
230
234
  await execAsync(`git branch ${deleteFlag} "${branchName}"`, {
@@ -233,7 +237,9 @@ export async function removeWorktree(branchName, repoRoot, force = false) {
233
237
  }
234
238
  catch {
235
239
  // Branch deletion failed, but worktree was removed
240
+ report("Worktree removed (branch delete skipped)");
236
241
  }
242
+ report("Done");
237
243
  return { success: true };
238
244
  }
239
245
  catch (e) {
@@ -1,4 +1,5 @@
1
- import type { AssignedIssue, Issue } from "../types.js";
1
+ import type { AssignedIssue, Issue, TriageSchedule } from "../types.js";
2
2
  export declare const PRIORITY_MAP: Record<number, string>;
3
3
  export declare function fetchIssue(ticketId: string, accessToken: string): Promise<Issue | null>;
4
+ export declare function fetchTriageSchedules(accessToken: string): Promise<TriageSchedule[]>;
4
5
  export declare function fetchAssignedIssues(accessToken: string): Promise<AssignedIssue[] | null>;