santree 0.1.4 → 0.2.0
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 +36 -2
- package/dist/cli.js +0 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +900 -0
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/helpers/template.d.ts +1 -1
- package/dist/commands/worktree/work.js +17 -1
- package/dist/lib/ai.d.ts +7 -0
- package/dist/lib/ai.js +10 -2
- package/dist/lib/dashboard/DetailPanel.d.ts +11 -0
- package/dist/lib/dashboard/DetailPanel.js +230 -0
- package/dist/lib/dashboard/IssueList.d.ts +13 -0
- package/dist/lib/dashboard/IssueList.js +112 -0
- package/dist/lib/dashboard/Overlays.d.ts +25 -0
- package/dist/lib/dashboard/Overlays.js +25 -0
- package/dist/lib/dashboard/data.d.ts +5 -0
- package/dist/lib/dashboard/data.js +75 -0
- package/dist/lib/dashboard/types.d.ts +150 -0
- package/dist/lib/dashboard/types.js +151 -0
- package/dist/lib/git.d.ts +19 -0
- package/dist/lib/git.js +32 -1
- package/dist/lib/github.d.ts +2 -1
- package/dist/lib/github.js +3 -2
- package/dist/lib/linear.d.ts +20 -0
- package/dist/lib/linear.js +53 -0
- package/package.json +2 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { PRInfo, PRCheck, PRReview } from "../github.js";
|
|
2
|
+
export interface LinearAssignedIssue {
|
|
3
|
+
identifier: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string | null;
|
|
6
|
+
url: string;
|
|
7
|
+
priority: number;
|
|
8
|
+
priorityLabel: string;
|
|
9
|
+
state: {
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
};
|
|
13
|
+
labels: string[];
|
|
14
|
+
projectId: string | null;
|
|
15
|
+
projectName: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface WorktreeInfo {
|
|
18
|
+
path: string;
|
|
19
|
+
branch: string;
|
|
20
|
+
dirty: boolean;
|
|
21
|
+
commitsAhead: number;
|
|
22
|
+
sessionId: string | null;
|
|
23
|
+
gitStatus: string;
|
|
24
|
+
}
|
|
25
|
+
export interface DashboardIssue {
|
|
26
|
+
issue: LinearAssignedIssue;
|
|
27
|
+
worktree: WorktreeInfo | null;
|
|
28
|
+
pr: PRInfo | null;
|
|
29
|
+
checks: PRCheck[] | null;
|
|
30
|
+
reviews: PRReview[] | null;
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectGroup {
|
|
33
|
+
name: string;
|
|
34
|
+
id: string | null;
|
|
35
|
+
issues: DashboardIssue[];
|
|
36
|
+
}
|
|
37
|
+
export type ActionOverlay = "mode-select" | "confirm-delete" | "commit" | "pr-create" | null;
|
|
38
|
+
export type CommitPhase = "idle" | "confirm-stage" | "awaiting-message" | "committing" | "pushing" | "done" | "error";
|
|
39
|
+
export type PrCreatePhase = "idle" | "choose-mode" | "pushing" | "creating" | "done" | "error";
|
|
40
|
+
export interface DashboardState {
|
|
41
|
+
groups: ProjectGroup[];
|
|
42
|
+
flatIssues: DashboardIssue[];
|
|
43
|
+
selectedIndex: number;
|
|
44
|
+
listScrollOffset: number;
|
|
45
|
+
detailScrollOffset: number;
|
|
46
|
+
loading: boolean;
|
|
47
|
+
refreshing: boolean;
|
|
48
|
+
error: string | null;
|
|
49
|
+
overlay: ActionOverlay;
|
|
50
|
+
actionMessage: string | null;
|
|
51
|
+
creatingForTicket: string | null;
|
|
52
|
+
creationLogs: string;
|
|
53
|
+
creationError: string | null;
|
|
54
|
+
deletingForTicket: string | null;
|
|
55
|
+
commitPhase: CommitPhase;
|
|
56
|
+
commitMessage: string;
|
|
57
|
+
commitError: string | null;
|
|
58
|
+
commitTicketId: string | null;
|
|
59
|
+
commitWorktreePath: string | null;
|
|
60
|
+
commitBranch: string | null;
|
|
61
|
+
commitGitStatus: string;
|
|
62
|
+
prCreatePhase: PrCreatePhase;
|
|
63
|
+
prCreateTicketId: string | null;
|
|
64
|
+
prCreateWorktreePath: string | null;
|
|
65
|
+
prCreateBranch: string | null;
|
|
66
|
+
prCreateError: string | null;
|
|
67
|
+
prCreateUrl: string | null;
|
|
68
|
+
}
|
|
69
|
+
export type DashboardAction = {
|
|
70
|
+
type: "SET_DATA";
|
|
71
|
+
groups: ProjectGroup[];
|
|
72
|
+
flatIssues: DashboardIssue[];
|
|
73
|
+
} | {
|
|
74
|
+
type: "SELECT";
|
|
75
|
+
index: number;
|
|
76
|
+
} | {
|
|
77
|
+
type: "SCROLL_LIST";
|
|
78
|
+
offset: number;
|
|
79
|
+
} | {
|
|
80
|
+
type: "SCROLL_DETAIL";
|
|
81
|
+
offset: number;
|
|
82
|
+
} | {
|
|
83
|
+
type: "REFRESH_START";
|
|
84
|
+
} | {
|
|
85
|
+
type: "REFRESH_DONE";
|
|
86
|
+
} | {
|
|
87
|
+
type: "SET_ERROR";
|
|
88
|
+
error: string;
|
|
89
|
+
} | {
|
|
90
|
+
type: "SET_OVERLAY";
|
|
91
|
+
overlay: ActionOverlay;
|
|
92
|
+
} | {
|
|
93
|
+
type: "SET_ACTION_MESSAGE";
|
|
94
|
+
message: string | null;
|
|
95
|
+
} | {
|
|
96
|
+
type: "CLEAR_ERROR";
|
|
97
|
+
} | {
|
|
98
|
+
type: "CREATION_START";
|
|
99
|
+
ticketId: string;
|
|
100
|
+
} | {
|
|
101
|
+
type: "CREATION_LOG";
|
|
102
|
+
logs: string;
|
|
103
|
+
} | {
|
|
104
|
+
type: "CREATION_DONE";
|
|
105
|
+
} | {
|
|
106
|
+
type: "CREATION_ERROR";
|
|
107
|
+
error: string;
|
|
108
|
+
} | {
|
|
109
|
+
type: "DELETE_START";
|
|
110
|
+
ticketId: string;
|
|
111
|
+
} | {
|
|
112
|
+
type: "DELETE_DONE";
|
|
113
|
+
} | {
|
|
114
|
+
type: "COMMIT_START";
|
|
115
|
+
ticketId: string;
|
|
116
|
+
worktreePath: string;
|
|
117
|
+
branch: string;
|
|
118
|
+
gitStatus: string;
|
|
119
|
+
} | {
|
|
120
|
+
type: "COMMIT_PHASE";
|
|
121
|
+
phase: CommitPhase;
|
|
122
|
+
} | {
|
|
123
|
+
type: "COMMIT_MESSAGE";
|
|
124
|
+
message: string;
|
|
125
|
+
} | {
|
|
126
|
+
type: "COMMIT_ERROR";
|
|
127
|
+
error: string;
|
|
128
|
+
} | {
|
|
129
|
+
type: "COMMIT_DONE";
|
|
130
|
+
} | {
|
|
131
|
+
type: "COMMIT_CANCEL";
|
|
132
|
+
} | {
|
|
133
|
+
type: "PR_CREATE_START";
|
|
134
|
+
ticketId: string;
|
|
135
|
+
worktreePath: string;
|
|
136
|
+
branch: string;
|
|
137
|
+
} | {
|
|
138
|
+
type: "PR_CREATE_PHASE";
|
|
139
|
+
phase: PrCreatePhase;
|
|
140
|
+
} | {
|
|
141
|
+
type: "PR_CREATE_ERROR";
|
|
142
|
+
error: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "PR_CREATE_DONE";
|
|
145
|
+
url: string;
|
|
146
|
+
} | {
|
|
147
|
+
type: "PR_CREATE_CANCEL";
|
|
148
|
+
};
|
|
149
|
+
export declare const initialState: DashboardState;
|
|
150
|
+
export declare function reducer(state: DashboardState, action: DashboardAction): DashboardState;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// ── State management ──────────────────────────────────────────────────
|
|
2
|
+
export const initialState = {
|
|
3
|
+
groups: [],
|
|
4
|
+
flatIssues: [],
|
|
5
|
+
selectedIndex: 0,
|
|
6
|
+
listScrollOffset: 0,
|
|
7
|
+
detailScrollOffset: 0,
|
|
8
|
+
loading: true,
|
|
9
|
+
refreshing: false,
|
|
10
|
+
error: null,
|
|
11
|
+
overlay: null,
|
|
12
|
+
actionMessage: null,
|
|
13
|
+
creatingForTicket: null,
|
|
14
|
+
creationLogs: "",
|
|
15
|
+
creationError: null,
|
|
16
|
+
deletingForTicket: null,
|
|
17
|
+
commitPhase: "idle",
|
|
18
|
+
commitMessage: "",
|
|
19
|
+
commitError: null,
|
|
20
|
+
commitTicketId: null,
|
|
21
|
+
commitWorktreePath: null,
|
|
22
|
+
commitBranch: null,
|
|
23
|
+
commitGitStatus: "",
|
|
24
|
+
prCreatePhase: "idle",
|
|
25
|
+
prCreateTicketId: null,
|
|
26
|
+
prCreateWorktreePath: null,
|
|
27
|
+
prCreateBranch: null,
|
|
28
|
+
prCreateError: null,
|
|
29
|
+
prCreateUrl: null,
|
|
30
|
+
};
|
|
31
|
+
export function reducer(state, action) {
|
|
32
|
+
switch (action.type) {
|
|
33
|
+
case "SET_DATA": {
|
|
34
|
+
// Preserve selection by identifier if possible
|
|
35
|
+
const prevId = state.flatIssues[state.selectedIndex]?.issue.identifier;
|
|
36
|
+
let newIndex = 0;
|
|
37
|
+
if (prevId) {
|
|
38
|
+
const found = action.flatIssues.findIndex((d) => d.issue.identifier === prevId);
|
|
39
|
+
if (found >= 0)
|
|
40
|
+
newIndex = found;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...state,
|
|
44
|
+
groups: action.groups,
|
|
45
|
+
flatIssues: action.flatIssues,
|
|
46
|
+
selectedIndex: newIndex,
|
|
47
|
+
loading: false,
|
|
48
|
+
refreshing: false,
|
|
49
|
+
error: null,
|
|
50
|
+
detailScrollOffset: 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
case "SELECT":
|
|
54
|
+
return { ...state, selectedIndex: action.index, detailScrollOffset: 0 };
|
|
55
|
+
case "SCROLL_LIST":
|
|
56
|
+
return { ...state, listScrollOffset: action.offset };
|
|
57
|
+
case "SCROLL_DETAIL":
|
|
58
|
+
return { ...state, detailScrollOffset: action.offset };
|
|
59
|
+
case "REFRESH_START":
|
|
60
|
+
return { ...state, refreshing: true };
|
|
61
|
+
case "REFRESH_DONE":
|
|
62
|
+
return { ...state, refreshing: false };
|
|
63
|
+
case "SET_ERROR":
|
|
64
|
+
return { ...state, error: action.error, loading: false, refreshing: false };
|
|
65
|
+
case "SET_OVERLAY":
|
|
66
|
+
return { ...state, overlay: action.overlay };
|
|
67
|
+
case "SET_ACTION_MESSAGE":
|
|
68
|
+
return { ...state, actionMessage: action.message };
|
|
69
|
+
case "CLEAR_ERROR":
|
|
70
|
+
return { ...state, error: null };
|
|
71
|
+
case "CREATION_START":
|
|
72
|
+
return {
|
|
73
|
+
...state,
|
|
74
|
+
creatingForTicket: action.ticketId,
|
|
75
|
+
creationLogs: "",
|
|
76
|
+
creationError: null,
|
|
77
|
+
};
|
|
78
|
+
case "CREATION_LOG":
|
|
79
|
+
return { ...state, creationLogs: state.creationLogs + action.logs };
|
|
80
|
+
case "CREATION_DONE":
|
|
81
|
+
return { ...state, creatingForTicket: null, creationLogs: "", creationError: null };
|
|
82
|
+
case "CREATION_ERROR":
|
|
83
|
+
return { ...state, creationError: action.error, creatingForTicket: null, creationLogs: "" };
|
|
84
|
+
case "DELETE_START":
|
|
85
|
+
return { ...state, deletingForTicket: action.ticketId };
|
|
86
|
+
case "DELETE_DONE":
|
|
87
|
+
return { ...state, deletingForTicket: null };
|
|
88
|
+
case "COMMIT_START":
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
overlay: "commit",
|
|
92
|
+
commitPhase: "confirm-stage",
|
|
93
|
+
commitMessage: "",
|
|
94
|
+
commitError: null,
|
|
95
|
+
commitTicketId: action.ticketId,
|
|
96
|
+
commitWorktreePath: action.worktreePath,
|
|
97
|
+
commitBranch: action.branch,
|
|
98
|
+
commitGitStatus: action.gitStatus,
|
|
99
|
+
};
|
|
100
|
+
case "COMMIT_PHASE":
|
|
101
|
+
return { ...state, commitPhase: action.phase };
|
|
102
|
+
case "COMMIT_MESSAGE":
|
|
103
|
+
return { ...state, commitMessage: action.message };
|
|
104
|
+
case "COMMIT_ERROR":
|
|
105
|
+
return { ...state, commitPhase: "error", commitError: action.error };
|
|
106
|
+
case "COMMIT_DONE":
|
|
107
|
+
return { ...state, commitPhase: "done" };
|
|
108
|
+
case "COMMIT_CANCEL":
|
|
109
|
+
return {
|
|
110
|
+
...state,
|
|
111
|
+
overlay: null,
|
|
112
|
+
commitPhase: "idle",
|
|
113
|
+
commitMessage: "",
|
|
114
|
+
commitError: null,
|
|
115
|
+
commitTicketId: null,
|
|
116
|
+
commitWorktreePath: null,
|
|
117
|
+
commitBranch: null,
|
|
118
|
+
commitGitStatus: "",
|
|
119
|
+
};
|
|
120
|
+
case "PR_CREATE_START":
|
|
121
|
+
return {
|
|
122
|
+
...state,
|
|
123
|
+
overlay: "pr-create",
|
|
124
|
+
prCreatePhase: "choose-mode",
|
|
125
|
+
prCreateTicketId: action.ticketId,
|
|
126
|
+
prCreateWorktreePath: action.worktreePath,
|
|
127
|
+
prCreateBranch: action.branch,
|
|
128
|
+
prCreateError: null,
|
|
129
|
+
prCreateUrl: null,
|
|
130
|
+
};
|
|
131
|
+
case "PR_CREATE_PHASE":
|
|
132
|
+
return { ...state, prCreatePhase: action.phase };
|
|
133
|
+
case "PR_CREATE_ERROR":
|
|
134
|
+
return { ...state, prCreatePhase: "error", prCreateError: action.error };
|
|
135
|
+
case "PR_CREATE_DONE":
|
|
136
|
+
return { ...state, prCreatePhase: "done", prCreateUrl: action.url };
|
|
137
|
+
case "PR_CREATE_CANCEL":
|
|
138
|
+
return {
|
|
139
|
+
...state,
|
|
140
|
+
overlay: null,
|
|
141
|
+
prCreatePhase: "idle",
|
|
142
|
+
prCreateTicketId: null,
|
|
143
|
+
prCreateWorktreePath: null,
|
|
144
|
+
prCreateBranch: null,
|
|
145
|
+
prCreateError: null,
|
|
146
|
+
prCreateUrl: null,
|
|
147
|
+
};
|
|
148
|
+
default:
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/dist/lib/git.d.ts
CHANGED
|
@@ -111,6 +111,15 @@ export declare function setRepoLinearOrg(repoRoot: string, orgSlug: string): voi
|
|
|
111
111
|
* Deletes the `_linear` key from .santree/metadata.json.
|
|
112
112
|
*/
|
|
113
113
|
export declare function removeRepoLinearOrg(repoRoot: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get the stored session ID for a given ticket from .santree/metadata.json.
|
|
116
|
+
* Returns null if no session ID is stored.
|
|
117
|
+
*/
|
|
118
|
+
export declare function getSessionId(repoRoot: string, ticketId: string): string | null;
|
|
119
|
+
/**
|
|
120
|
+
* Store a session ID for a given ticket in .santree/metadata.json.
|
|
121
|
+
*/
|
|
122
|
+
export declare function setSessionId(repoRoot: string, ticketId: string, sessionId: string): void;
|
|
114
123
|
/**
|
|
115
124
|
* Get the base branch for a given branch name.
|
|
116
125
|
* Looks up metadata first, falls back to the default branch.
|
|
@@ -147,6 +156,11 @@ export declare function hasUnstagedChanges(): boolean;
|
|
|
147
156
|
* Returns empty string on failure.
|
|
148
157
|
*/
|
|
149
158
|
export declare function getGitStatus(): string;
|
|
159
|
+
/**
|
|
160
|
+
* Get a short summary of the working tree status (async, with cwd).
|
|
161
|
+
* Returns empty string on failure.
|
|
162
|
+
*/
|
|
163
|
+
export declare function getGitStatusAsync(cwd: string): Promise<string>;
|
|
150
164
|
/**
|
|
151
165
|
* Get a diffstat of staged changes.
|
|
152
166
|
* Runs: `git diff --cached --stat`
|
|
@@ -165,6 +179,11 @@ export declare function getCommitsBehind(baseBranch: string): number;
|
|
|
165
179
|
* Returns 0 on failure.
|
|
166
180
|
*/
|
|
167
181
|
export declare function getCommitsAhead(baseBranch: string): number;
|
|
182
|
+
/**
|
|
183
|
+
* Count how many commits the current branch is ahead of baseBranch (async, with cwd).
|
|
184
|
+
* Returns 0 on failure.
|
|
185
|
+
*/
|
|
186
|
+
export declare function getCommitsAheadAsync(cwd: string, baseBranch: string): Promise<number>;
|
|
168
187
|
/**
|
|
169
188
|
* Check if a branch exists on the remote (origin).
|
|
170
189
|
* Runs: `git ls-remote --heads origin <branchName>`
|
package/dist/lib/git.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execSync, exec } from "child_process";
|
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import * as fs from "fs";
|
|
5
|
-
import { run } from "./exec.js";
|
|
5
|
+
import { run, runAsync } from "./exec.js";
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
7
|
/**
|
|
8
8
|
* Find the toplevel directory of the current git repository.
|
|
@@ -324,6 +324,22 @@ export function removeRepoLinearOrg(repoRoot) {
|
|
|
324
324
|
delete all._linear;
|
|
325
325
|
writeAllMetadata(repoRoot, all);
|
|
326
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Get the stored session ID for a given ticket from .santree/metadata.json.
|
|
329
|
+
* Returns null if no session ID is stored.
|
|
330
|
+
*/
|
|
331
|
+
export function getSessionId(repoRoot, ticketId) {
|
|
332
|
+
const all = readAllMetadata(repoRoot);
|
|
333
|
+
return all[ticketId]?.session_id ?? null;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Store a session ID for a given ticket in .santree/metadata.json.
|
|
337
|
+
*/
|
|
338
|
+
export function setSessionId(repoRoot, ticketId, sessionId) {
|
|
339
|
+
const all = readAllMetadata(repoRoot);
|
|
340
|
+
all[ticketId] = { ...all[ticketId], session_id: sessionId };
|
|
341
|
+
writeAllMetadata(repoRoot, all);
|
|
342
|
+
}
|
|
327
343
|
/**
|
|
328
344
|
* Get the base branch for a given branch name.
|
|
329
345
|
* Looks up metadata first, falls back to the default branch.
|
|
@@ -399,6 +415,13 @@ export function hasUnstagedChanges() {
|
|
|
399
415
|
export function getGitStatus() {
|
|
400
416
|
return run("git status --short") ?? "";
|
|
401
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Get a short summary of the working tree status (async, with cwd).
|
|
420
|
+
* Returns empty string on failure.
|
|
421
|
+
*/
|
|
422
|
+
export async function getGitStatusAsync(cwd) {
|
|
423
|
+
return (await runAsync(`git -C "${cwd}" status --short`)) ?? "";
|
|
424
|
+
}
|
|
402
425
|
/**
|
|
403
426
|
* Get a diffstat of staged changes.
|
|
404
427
|
* Runs: `git diff --cached --stat`
|
|
@@ -425,6 +448,14 @@ export function getCommitsAhead(baseBranch) {
|
|
|
425
448
|
const output = run(`git rev-list --count ${baseBranch}..HEAD`);
|
|
426
449
|
return output ? parseInt(output, 10) || 0 : 0;
|
|
427
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Count how many commits the current branch is ahead of baseBranch (async, with cwd).
|
|
453
|
+
* Returns 0 on failure.
|
|
454
|
+
*/
|
|
455
|
+
export async function getCommitsAheadAsync(cwd, baseBranch) {
|
|
456
|
+
const output = await runAsync(`git -C "${cwd}" rev-list --count ${baseBranch}..HEAD`);
|
|
457
|
+
return output ? parseInt(output, 10) || 0 : 0;
|
|
458
|
+
}
|
|
428
459
|
/**
|
|
429
460
|
* Check if a branch exists on the remote (origin).
|
|
430
461
|
* Runs: `git ls-remote --heads origin <branchName>`
|
package/dist/lib/github.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export interface PRInfo {
|
|
2
2
|
number: string;
|
|
3
3
|
state: "OPEN" | "MERGED" | "CLOSED";
|
|
4
|
+
isDraft: boolean;
|
|
4
5
|
url?: string;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* Get PR info for a branch using the GitHub CLI (async).
|
|
8
|
-
* Runs: `gh pr view "<branchName>" --json number,state,url`
|
|
9
|
+
* Runs: `gh pr view "<branchName>" --json number,state,url,isDraft`
|
|
9
10
|
* Returns null if no PR exists for the branch or gh CLI fails.
|
|
10
11
|
*/
|
|
11
12
|
export declare function getPRInfoAsync(branchName: string): Promise<PRInfo | null>;
|
package/dist/lib/github.js
CHANGED
|
@@ -4,16 +4,17 @@ import { run, runAsync } from "./exec.js";
|
|
|
4
4
|
const execAsync = promisify(exec);
|
|
5
5
|
/**
|
|
6
6
|
* Get PR info for a branch using the GitHub CLI (async).
|
|
7
|
-
* Runs: `gh pr view "<branchName>" --json number,state,url`
|
|
7
|
+
* Runs: `gh pr view "<branchName>" --json number,state,url,isDraft`
|
|
8
8
|
* Returns null if no PR exists for the branch or gh CLI fails.
|
|
9
9
|
*/
|
|
10
10
|
export async function getPRInfoAsync(branchName) {
|
|
11
11
|
try {
|
|
12
|
-
const { stdout } = await execAsync(`gh pr view "${branchName}" --json number,state,url`);
|
|
12
|
+
const { stdout } = await execAsync(`gh pr view "${branchName}" --json number,state,url,isDraft`);
|
|
13
13
|
const data = JSON.parse(stdout);
|
|
14
14
|
return {
|
|
15
15
|
number: String(data.number ?? ""),
|
|
16
16
|
state: data.state ?? "OPEN",
|
|
17
|
+
isDraft: data.isDraft ?? false,
|
|
17
18
|
url: data.url,
|
|
18
19
|
};
|
|
19
20
|
}
|
package/dist/lib/linear.d.ts
CHANGED
|
@@ -54,6 +54,26 @@ export interface AuthStatus {
|
|
|
54
54
|
* Get auth status for the current repo's Linear org (or any stored org).
|
|
55
55
|
*/
|
|
56
56
|
export declare function getAuthStatus(repoRoot: string | null): AuthStatus;
|
|
57
|
+
export interface LinearAssignedIssue {
|
|
58
|
+
identifier: string;
|
|
59
|
+
title: string;
|
|
60
|
+
description: string | null;
|
|
61
|
+
url: string;
|
|
62
|
+
priority: number;
|
|
63
|
+
priorityLabel: string;
|
|
64
|
+
state: {
|
|
65
|
+
name: string;
|
|
66
|
+
type: string;
|
|
67
|
+
};
|
|
68
|
+
labels: string[];
|
|
69
|
+
projectId: string | null;
|
|
70
|
+
projectName: string | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fetch all active issues assigned to the current user.
|
|
74
|
+
* Returns null if not authenticated or fetch fails.
|
|
75
|
+
*/
|
|
76
|
+
export declare function fetchAssignedIssues(repoRoot: string): Promise<LinearAssignedIssue[] | null>;
|
|
57
77
|
/**
|
|
58
78
|
* Fetch full ticket content for a given ticket ID.
|
|
59
79
|
* Looks up the repo's Linear org, gets valid tokens, fetches issue, downloads images.
|
package/dist/lib/linear.js
CHANGED
|
@@ -390,6 +390,59 @@ export function getAuthStatus(repoRoot) {
|
|
|
390
390
|
repoLinked: false,
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
|
+
// ── Assigned Issues Query ──────────────────────────────────────────────
|
|
394
|
+
const ASSIGNED_ISSUES_QUERY = `
|
|
395
|
+
query AssignedIssues {
|
|
396
|
+
viewer {
|
|
397
|
+
assignedIssues(
|
|
398
|
+
filter: { state: { type: { nin: ["completed", "cancelled"] } } }
|
|
399
|
+
orderBy: updatedAt
|
|
400
|
+
first: 100
|
|
401
|
+
) {
|
|
402
|
+
nodes {
|
|
403
|
+
identifier
|
|
404
|
+
title
|
|
405
|
+
description
|
|
406
|
+
url
|
|
407
|
+
priority
|
|
408
|
+
state { name type }
|
|
409
|
+
labels { nodes { name } }
|
|
410
|
+
project { id name }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
`;
|
|
416
|
+
/**
|
|
417
|
+
* Fetch all active issues assigned to the current user.
|
|
418
|
+
* Returns null if not authenticated or fetch fails.
|
|
419
|
+
*/
|
|
420
|
+
export async function fetchAssignedIssues(repoRoot) {
|
|
421
|
+
const orgSlug = getRepoLinearOrg(repoRoot);
|
|
422
|
+
if (!orgSlug)
|
|
423
|
+
return null;
|
|
424
|
+
const tokens = await getValidTokens(orgSlug);
|
|
425
|
+
if (!tokens)
|
|
426
|
+
return null;
|
|
427
|
+
const data = await graphqlQuery(ASSIGNED_ISSUES_QUERY, {}, tokens.access_token);
|
|
428
|
+
if (!data?.viewer?.assignedIssues?.nodes)
|
|
429
|
+
return null;
|
|
430
|
+
return data.viewer.assignedIssues.nodes.map((issue) => ({
|
|
431
|
+
identifier: issue.identifier,
|
|
432
|
+
title: issue.title,
|
|
433
|
+
description: issue.description ?? null,
|
|
434
|
+
url: issue.url,
|
|
435
|
+
priority: issue.priority,
|
|
436
|
+
priorityLabel: PRIORITY_MAP[issue.priority] ?? "No priority",
|
|
437
|
+
state: {
|
|
438
|
+
name: issue.state?.name ?? "Unknown",
|
|
439
|
+
type: issue.state?.type ?? "unstarted",
|
|
440
|
+
},
|
|
441
|
+
labels: (issue.labels?.nodes ?? []).map((l) => l.name),
|
|
442
|
+
projectId: issue.project?.id ?? null,
|
|
443
|
+
projectName: issue.project?.name ?? null,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
393
446
|
// ── High-Level Entry Point ─────────────────────────────────────────────
|
|
394
447
|
/**
|
|
395
448
|
* Fetch full ticket content for a given ticket ID.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "santree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Git worktree manager",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Santiago Toscanini",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"node": ">=20"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsc",
|
|
32
|
+
"build": "tsc && chmod +x dist/cli.js",
|
|
33
33
|
"dev": "tsc --watch",
|
|
34
34
|
"start": "node dist/cli.js",
|
|
35
35
|
"lint": "eslint source",
|