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.
- package/dist/commands/dashboard.js +463 -78
- package/dist/lib/ai.d.ts +13 -0
- package/dist/lib/ai.js +16 -0
- package/dist/lib/dashboard/DetailPanel.d.ts +18 -2
- package/dist/lib/dashboard/DetailPanel.js +150 -3
- package/dist/lib/dashboard/IssueList.d.ts +11 -1
- package/dist/lib/dashboard/IssueList.js +44 -9
- package/dist/lib/dashboard/Overlays.d.ts +2 -1
- package/dist/lib/dashboard/Overlays.js +23 -8
- package/dist/lib/dashboard/TriageScheduleOverlay.d.ts +16 -0
- package/dist/lib/dashboard/TriageScheduleOverlay.js +78 -0
- package/dist/lib/dashboard/data.d.ts +2 -0
- package/dist/lib/dashboard/data.js +26 -4
- package/dist/lib/dashboard/due.d.ts +24 -0
- package/dist/lib/dashboard/due.js +32 -0
- package/dist/lib/dashboard/types.d.ts +93 -5
- package/dist/lib/dashboard/types.js +133 -4
- package/dist/lib/git.d.ts +1 -1
- package/dist/lib/git.js +7 -1
- package/dist/lib/trackers/linear/api.d.ts +2 -1
- package/dist/lib/trackers/linear/api.js +137 -1
- package/dist/lib/trackers/linear/index.js +17 -1
- package/dist/lib/trackers/types.d.ts +58 -0
- package/dist/lib/trackers/types.js +5 -1
- package/package.json +1 -1
- package/prompts/ask.njk +10 -0
|
@@ -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
|
|
258
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
219
|
-
|
|
220
|
-
|
|
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>;
|