team-toon-tack 1.6.0 ā 1.6.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/scripts/config/filters.d.ts +2 -0
- package/dist/scripts/config/filters.js +46 -0
- package/dist/scripts/config/show.d.ts +2 -0
- package/dist/scripts/config/show.js +32 -0
- package/dist/scripts/config/status.d.ts +2 -0
- package/dist/scripts/config/status.js +73 -0
- package/dist/scripts/config/teams.d.ts +2 -0
- package/dist/scripts/config/teams.js +59 -0
- package/dist/scripts/config.js +21 -245
- package/dist/scripts/done-job.js +41 -119
- package/dist/scripts/init.js +138 -263
- package/dist/scripts/lib/config-builder.d.ts +41 -0
- package/dist/scripts/lib/config-builder.js +118 -0
- package/dist/scripts/lib/display.d.ts +12 -0
- package/dist/scripts/lib/display.js +91 -0
- package/dist/scripts/lib/git.d.ts +10 -0
- package/dist/scripts/lib/git.js +78 -0
- package/dist/scripts/lib/linear.d.ts +11 -0
- package/dist/scripts/lib/linear.js +61 -0
- package/dist/scripts/status.js +26 -110
- package/dist/scripts/sync.js +0 -8
- package/dist/scripts/utils.d.ts +0 -1
- package/dist/scripts/work-on.js +18 -69
- package/package.json +1 -1
- package/templates/config.example.toon +20 -65
- package/templates/local.example.toon +7 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Task } from "../utils.js";
|
|
2
|
+
export declare const PRIORITY_LABELS: Record<number, string>;
|
|
3
|
+
export declare function getStatusIcon(localStatus: Task["localStatus"]): string;
|
|
4
|
+
export declare function displayTaskHeader(task: Task, icon?: string): void;
|
|
5
|
+
export declare function displayTaskInfo(task: Task): void;
|
|
6
|
+
export declare function displayTaskStatus(task: Task): void;
|
|
7
|
+
export declare function displayTaskDescription(task: Task): void;
|
|
8
|
+
export declare function displayTaskAttachments(task: Task): void;
|
|
9
|
+
export declare function displayTaskComments(task: Task): void;
|
|
10
|
+
export declare function displayTaskFooter(): void;
|
|
11
|
+
export declare function displayTaskFull(task: Task, icon?: string): void;
|
|
12
|
+
export declare function displayTaskWithStatus(task: Task): void;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const PRIORITY_LABELS = {
|
|
2
|
+
0: "āŖ None",
|
|
3
|
+
1: "š“ Urgent",
|
|
4
|
+
2: "š High",
|
|
5
|
+
3: "š” Medium",
|
|
6
|
+
4: "š¢ Low",
|
|
7
|
+
};
|
|
8
|
+
export function getStatusIcon(localStatus) {
|
|
9
|
+
switch (localStatus) {
|
|
10
|
+
case "completed":
|
|
11
|
+
return "ā
";
|
|
12
|
+
case "in-progress":
|
|
13
|
+
return "š";
|
|
14
|
+
case "blocked-backend":
|
|
15
|
+
return "š«";
|
|
16
|
+
default:
|
|
17
|
+
return "š";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function displayTaskHeader(task, icon) {
|
|
21
|
+
const separator = "ā".repeat(50);
|
|
22
|
+
console.log(`\n${separator}`);
|
|
23
|
+
console.log(`${icon || getStatusIcon(task.localStatus)} ${task.id}: ${task.title}`);
|
|
24
|
+
console.log(separator);
|
|
25
|
+
}
|
|
26
|
+
export function displayTaskInfo(task) {
|
|
27
|
+
console.log(`Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
|
|
28
|
+
console.log(`Labels: ${task.labels.join(", ")}`);
|
|
29
|
+
if (task.assignee)
|
|
30
|
+
console.log(`Assignee: ${task.assignee}`);
|
|
31
|
+
console.log(`Branch: ${task.branch || "N/A"}`);
|
|
32
|
+
if (task.url)
|
|
33
|
+
console.log(`URL: ${task.url}`);
|
|
34
|
+
}
|
|
35
|
+
export function displayTaskStatus(task) {
|
|
36
|
+
console.log(`\nStatus:`);
|
|
37
|
+
console.log(` Local: ${task.localStatus}`);
|
|
38
|
+
console.log(` Linear: ${task.status}`);
|
|
39
|
+
}
|
|
40
|
+
export function displayTaskDescription(task) {
|
|
41
|
+
if (task.description) {
|
|
42
|
+
console.log(`\nš Description:\n${task.description}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function displayTaskAttachments(task) {
|
|
46
|
+
if (task.attachments && task.attachments.length > 0) {
|
|
47
|
+
console.log(`\nš Attachments:`);
|
|
48
|
+
for (const att of task.attachments) {
|
|
49
|
+
console.log(` - ${att.title}: ${att.url}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function displayTaskComments(task) {
|
|
54
|
+
if (task.comments && task.comments.length > 0) {
|
|
55
|
+
console.log(`\nš¬ Comments (${task.comments.length}):`);
|
|
56
|
+
for (const comment of task.comments) {
|
|
57
|
+
const date = new Date(comment.createdAt).toLocaleDateString();
|
|
58
|
+
console.log(`\n [${comment.user || "Unknown"} - ${date}]`);
|
|
59
|
+
const lines = comment.body.split("\n");
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
console.log(` ${line}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function displayTaskFooter() {
|
|
67
|
+
console.log(`\n${"ā".repeat(50)}`);
|
|
68
|
+
}
|
|
69
|
+
export function displayTaskFull(task, icon) {
|
|
70
|
+
displayTaskHeader(task, icon);
|
|
71
|
+
displayTaskInfo(task);
|
|
72
|
+
displayTaskDescription(task);
|
|
73
|
+
displayTaskAttachments(task);
|
|
74
|
+
displayTaskComments(task);
|
|
75
|
+
displayTaskFooter();
|
|
76
|
+
}
|
|
77
|
+
export function displayTaskWithStatus(task) {
|
|
78
|
+
displayTaskHeader(task);
|
|
79
|
+
displayTaskStatus(task);
|
|
80
|
+
console.log(`\nInfo:`);
|
|
81
|
+
console.log(` Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
|
|
82
|
+
console.log(` Labels: ${task.labels.join(", ")}`);
|
|
83
|
+
console.log(` Assignee: ${task.assignee || "Unassigned"}`);
|
|
84
|
+
console.log(` Branch: ${task.branch || "N/A"}`);
|
|
85
|
+
if (task.url)
|
|
86
|
+
console.log(` URL: ${task.url}`);
|
|
87
|
+
displayTaskDescription(task);
|
|
88
|
+
displayTaskAttachments(task);
|
|
89
|
+
displayTaskComments(task);
|
|
90
|
+
displayTaskFooter();
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CommitInfo {
|
|
2
|
+
shortHash: string;
|
|
3
|
+
fullHash: string;
|
|
4
|
+
message: string;
|
|
5
|
+
diffStat: string;
|
|
6
|
+
commitUrl: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function getLatestCommit(): CommitInfo | null;
|
|
9
|
+
export declare function formatCommitLink(commit: CommitInfo): string;
|
|
10
|
+
export declare function buildCompletionComment(commit: CommitInfo, aiMessage?: string): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
function getRemoteUrl() {
|
|
3
|
+
try {
|
|
4
|
+
return execSync("git remote get-url origin", {
|
|
5
|
+
encoding: "utf-8",
|
|
6
|
+
}).trim();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function buildCommitUrl(remoteUrl, fullHash) {
|
|
13
|
+
// Handle SSH or HTTPS URLs
|
|
14
|
+
// git@gitlab.com:org/repo.git -> https://gitlab.com/org/repo/-/commit/hash
|
|
15
|
+
// https://gitlab.com/org/repo.git -> https://gitlab.com/org/repo/-/commit/hash
|
|
16
|
+
if (remoteUrl.includes("gitlab")) {
|
|
17
|
+
const match = remoteUrl.match(/(?:git@|https:\/\/)([^:/]+)[:\\/](.+?)(?:\.git)?$/);
|
|
18
|
+
if (match) {
|
|
19
|
+
return `https://${match[1]}/${match[2]}/-/commit/${fullHash}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (remoteUrl.includes("github")) {
|
|
23
|
+
const match = remoteUrl.match(/(?:git@|https:\/\/)([^:/]+)[:\\/](.+?)(?:\.git)?$/);
|
|
24
|
+
if (match) {
|
|
25
|
+
return `https://${match[1]}/${match[2]}/commit/${fullHash}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function getLatestCommit() {
|
|
31
|
+
try {
|
|
32
|
+
const shortHash = execSync("git rev-parse --short HEAD", {
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
}).trim();
|
|
35
|
+
const fullHash = execSync("git rev-parse HEAD", {
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
}).trim();
|
|
38
|
+
const message = execSync("git log -1 --format=%s", {
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
}).trim();
|
|
41
|
+
const diffStat = execSync("git diff HEAD~1 --stat --stat-width=60", {
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
}).trim();
|
|
44
|
+
let commitUrl = null;
|
|
45
|
+
const remoteUrl = getRemoteUrl();
|
|
46
|
+
if (remoteUrl) {
|
|
47
|
+
commitUrl = buildCommitUrl(remoteUrl, fullHash);
|
|
48
|
+
}
|
|
49
|
+
return { shortHash, fullHash, message, diffStat, commitUrl };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function formatCommitLink(commit) {
|
|
56
|
+
return commit.commitUrl
|
|
57
|
+
? `[${commit.shortHash}](${commit.commitUrl})`
|
|
58
|
+
: `\`${commit.shortHash}\``;
|
|
59
|
+
}
|
|
60
|
+
export function buildCompletionComment(commit, aiMessage) {
|
|
61
|
+
const commitLink = formatCommitLink(commit);
|
|
62
|
+
const commentParts = [
|
|
63
|
+
"## ā
éē¼å®ę",
|
|
64
|
+
"",
|
|
65
|
+
"### š¤ AI 修復說ę",
|
|
66
|
+
aiMessage || "_No description provided_",
|
|
67
|
+
"",
|
|
68
|
+
"### š Commit Info",
|
|
69
|
+
`**Commit:** ${commitLink}`,
|
|
70
|
+
`**Message:** ${commit.message}`,
|
|
71
|
+
"",
|
|
72
|
+
"### š Changes",
|
|
73
|
+
"```",
|
|
74
|
+
commit.diffStat,
|
|
75
|
+
"```",
|
|
76
|
+
];
|
|
77
|
+
return commentParts.join("\n");
|
|
78
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Config, type StatusTransitions } from "../utils.js";
|
|
2
|
+
export interface WorkflowStateInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getWorkflowStates(config: Config, teamKey: string): Promise<WorkflowStateInfo[]>;
|
|
8
|
+
export declare function getStatusTransitions(config: Config): StatusTransitions;
|
|
9
|
+
export declare function updateIssueStatus(linearId: string, targetStatusName: string, config: Config, teamKey: string): Promise<boolean>;
|
|
10
|
+
export declare function addComment(issueId: string, body: string): Promise<boolean>;
|
|
11
|
+
export declare function mapLocalStatusToLinear(localStatus: "pending" | "in-progress" | "completed" | "blocked-backend", config: Config): string | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getLinearClient, getTeamId, } from "../utils.js";
|
|
2
|
+
export async function getWorkflowStates(config, teamKey) {
|
|
3
|
+
const client = getLinearClient();
|
|
4
|
+
const teamId = getTeamId(config, teamKey);
|
|
5
|
+
const statesData = await client.workflowStates({
|
|
6
|
+
filter: { team: { id: { eq: teamId } } },
|
|
7
|
+
});
|
|
8
|
+
return statesData.nodes.map((s) => ({
|
|
9
|
+
id: s.id,
|
|
10
|
+
name: s.name,
|
|
11
|
+
type: s.type,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export function getStatusTransitions(config) {
|
|
15
|
+
return (config.status_transitions || {
|
|
16
|
+
todo: "Todo",
|
|
17
|
+
in_progress: "In Progress",
|
|
18
|
+
done: "Done",
|
|
19
|
+
testing: "Testing",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function updateIssueStatus(linearId, targetStatusName, config, teamKey) {
|
|
23
|
+
try {
|
|
24
|
+
const client = getLinearClient();
|
|
25
|
+
const states = await getWorkflowStates(config, teamKey);
|
|
26
|
+
const targetState = states.find((s) => s.name === targetStatusName);
|
|
27
|
+
if (targetState) {
|
|
28
|
+
await client.updateIssue(linearId, { stateId: targetState.id });
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error("Failed to update Linear:", e);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function addComment(issueId, body) {
|
|
39
|
+
try {
|
|
40
|
+
const client = getLinearClient();
|
|
41
|
+
await client.createComment({ issueId, body });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.error("Failed to add comment:", e);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function mapLocalStatusToLinear(localStatus, config) {
|
|
50
|
+
const transitions = getStatusTransitions(config);
|
|
51
|
+
switch (localStatus) {
|
|
52
|
+
case "pending":
|
|
53
|
+
return transitions.todo;
|
|
54
|
+
case "in-progress":
|
|
55
|
+
return transitions.in_progress;
|
|
56
|
+
case "completed":
|
|
57
|
+
return transitions.done;
|
|
58
|
+
default:
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/scripts/status.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
1: "š“ Urgent",
|
|
6
|
-
2: "š High",
|
|
7
|
-
3: "š” Medium",
|
|
8
|
-
4: "š¢ Low",
|
|
9
|
-
};
|
|
2
|
+
import { displayTaskWithStatus, getStatusIcon } from "./lib/display.js";
|
|
3
|
+
import { getStatusTransitions, mapLocalStatusToLinear, updateIssueStatus, } from "./lib/linear.js";
|
|
4
|
+
import { loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
|
|
10
5
|
const LOCAL_STATUS_ORDER = [
|
|
11
6
|
"pending",
|
|
12
7
|
"in-progress",
|
|
@@ -29,7 +24,6 @@ function parseArgs(args) {
|
|
|
29
24
|
}
|
|
30
25
|
async function status() {
|
|
31
26
|
const args = process.argv.slice(2);
|
|
32
|
-
// Handle help flag
|
|
33
27
|
if (args.includes("--help") || args.includes("-h")) {
|
|
34
28
|
console.log(`Usage: ttt status [issue-id] [--set <status>]
|
|
35
29
|
|
|
@@ -55,7 +49,6 @@ Examples:
|
|
|
55
49
|
ttt status # Show current in-progress task
|
|
56
50
|
ttt status MP-624 # Show status of specific issue
|
|
57
51
|
ttt status MP-624 --set +1 # Move to next status
|
|
58
|
-
ttt status MP-624 --set done # Mark as done
|
|
59
52
|
ttt status --set pending # Reset current task to pending`);
|
|
60
53
|
process.exit(0);
|
|
61
54
|
}
|
|
@@ -71,20 +64,12 @@ Examples:
|
|
|
71
64
|
let task;
|
|
72
65
|
let issueId = argIssueId;
|
|
73
66
|
if (!issueId) {
|
|
74
|
-
// Find current in-progress task
|
|
75
67
|
const inProgressTasks = data.tasks.filter((t) => t.localStatus === "in-progress");
|
|
76
68
|
if (inProgressTasks.length === 0) {
|
|
77
69
|
console.log("No in-progress task found.");
|
|
78
70
|
console.log("\nAll tasks:");
|
|
79
71
|
for (const t of data.tasks) {
|
|
80
|
-
|
|
81
|
-
? "ā
"
|
|
82
|
-
: t.localStatus === "in-progress"
|
|
83
|
-
? "š"
|
|
84
|
-
: t.localStatus === "blocked-backend"
|
|
85
|
-
? "š«"
|
|
86
|
-
: "š";
|
|
87
|
-
console.log(` ${statusIcon} ${t.id}: ${t.title} [${t.localStatus}]`);
|
|
72
|
+
console.log(` ${getStatusIcon(t.localStatus)} ${t.id}: ${t.title} [${t.localStatus}]`);
|
|
88
73
|
}
|
|
89
74
|
process.exit(0);
|
|
90
75
|
}
|
|
@@ -133,16 +118,9 @@ Examples:
|
|
|
133
118
|
: setStatus;
|
|
134
119
|
}
|
|
135
120
|
else if (["todo", "in_progress", "done", "testing"].includes(setStatus)) {
|
|
136
|
-
|
|
137
|
-
const statusTransitions = config.status_transitions || {
|
|
138
|
-
todo: "Todo",
|
|
139
|
-
in_progress: "In Progress",
|
|
140
|
-
done: "Done",
|
|
141
|
-
testing: "Testing",
|
|
142
|
-
};
|
|
121
|
+
const transitions = getStatusTransitions(config);
|
|
143
122
|
newLinearStatus =
|
|
144
|
-
|
|
145
|
-
// Also update local status accordingly
|
|
123
|
+
transitions[setStatus] ?? undefined;
|
|
146
124
|
if (setStatus === "todo") {
|
|
147
125
|
newLocalStatus = "pending";
|
|
148
126
|
}
|
|
@@ -158,94 +136,32 @@ Examples:
|
|
|
158
136
|
process.exit(1);
|
|
159
137
|
}
|
|
160
138
|
// Update local status
|
|
161
|
-
if (newLocalStatus
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
139
|
+
if (newLocalStatus) {
|
|
140
|
+
if (newLocalStatus !== task.localStatus) {
|
|
141
|
+
const oldStatus = task.localStatus;
|
|
142
|
+
task.localStatus = newLocalStatus;
|
|
143
|
+
await saveCycleData(data);
|
|
144
|
+
console.log(`Local: ${task.id} ${oldStatus} ā ${newLocalStatus}`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.log(`Local: ${task.id} already ${newLocalStatus}`);
|
|
148
|
+
}
|
|
166
149
|
}
|
|
167
150
|
// Update Linear status
|
|
168
151
|
if (newLinearStatus || newLocalStatus) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const workflowStates = await client.workflowStates({
|
|
173
|
-
filter: { team: { id: { eq: teamId } } },
|
|
174
|
-
});
|
|
175
|
-
let targetStateName = newLinearStatus;
|
|
176
|
-
if (!targetStateName && newLocalStatus) {
|
|
177
|
-
// Map local status to Linear status
|
|
178
|
-
const statusTransitions = config.status_transitions || {
|
|
179
|
-
todo: "Todo",
|
|
180
|
-
in_progress: "In Progress",
|
|
181
|
-
done: "Done",
|
|
182
|
-
};
|
|
183
|
-
if (newLocalStatus === "pending") {
|
|
184
|
-
targetStateName = statusTransitions.todo;
|
|
185
|
-
}
|
|
186
|
-
else if (newLocalStatus === "in-progress") {
|
|
187
|
-
targetStateName = statusTransitions.in_progress;
|
|
188
|
-
}
|
|
189
|
-
else if (newLocalStatus === "completed") {
|
|
190
|
-
targetStateName = statusTransitions.done;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (targetStateName) {
|
|
194
|
-
const targetState = workflowStates.nodes.find((s) => s.name === targetStateName);
|
|
195
|
-
if (targetState) {
|
|
196
|
-
await client.updateIssue(task.linearId, {
|
|
197
|
-
stateId: targetState.id,
|
|
198
|
-
});
|
|
199
|
-
console.log(`Linear: ${task.id} ā ${targetStateName}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch (e) {
|
|
204
|
-
console.error("Failed to update Linear:", e);
|
|
152
|
+
let targetStateName = newLinearStatus;
|
|
153
|
+
if (!targetStateName && newLocalStatus) {
|
|
154
|
+
targetStateName = mapLocalStatusToLinear(newLocalStatus, config);
|
|
205
155
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
? "ā
"
|
|
212
|
-
: task.localStatus === "in-progress"
|
|
213
|
-
? "š"
|
|
214
|
-
: task.localStatus === "blocked-backend"
|
|
215
|
-
? "š«"
|
|
216
|
-
: "š";
|
|
217
|
-
console.log(`${statusIcon} ${task.id}: ${task.title}`);
|
|
218
|
-
console.log(`${"ā".repeat(50)}`);
|
|
219
|
-
console.log(`\nStatus:`);
|
|
220
|
-
console.log(` Local: ${task.localStatus}`);
|
|
221
|
-
console.log(` Linear: ${task.status}`);
|
|
222
|
-
console.log(`\nInfo:`);
|
|
223
|
-
console.log(` Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
|
|
224
|
-
console.log(` Labels: ${task.labels.join(", ")}`);
|
|
225
|
-
console.log(` Assignee: ${task.assignee || "Unassigned"}`);
|
|
226
|
-
console.log(` Branch: ${task.branch || "N/A"}`);
|
|
227
|
-
if (task.url)
|
|
228
|
-
console.log(` URL: ${task.url}`);
|
|
229
|
-
if (task.description) {
|
|
230
|
-
console.log(`\nš Description:\n${task.description}`);
|
|
231
|
-
}
|
|
232
|
-
if (task.attachments && task.attachments.length > 0) {
|
|
233
|
-
console.log(`\nš Attachments:`);
|
|
234
|
-
for (const att of task.attachments) {
|
|
235
|
-
console.log(` - ${att.title}: ${att.url}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (task.comments && task.comments.length > 0) {
|
|
239
|
-
console.log(`\nš¬ Comments (${task.comments.length}):`);
|
|
240
|
-
for (const comment of task.comments) {
|
|
241
|
-
const date = new Date(comment.createdAt).toLocaleDateString();
|
|
242
|
-
console.log(`\n [${comment.user || "Unknown"} - ${date}]`);
|
|
243
|
-
const lines = comment.body.split("\n");
|
|
244
|
-
for (const line of lines) {
|
|
245
|
-
console.log(` ${line}`);
|
|
156
|
+
if (targetStateName) {
|
|
157
|
+
const success = await updateIssueStatus(task.linearId, targetStateName, config, localConfig.team);
|
|
158
|
+
if (success) {
|
|
159
|
+
console.log(`Linear: ${task.id} ā ${targetStateName}`);
|
|
160
|
+
}
|
|
246
161
|
}
|
|
247
162
|
}
|
|
248
163
|
}
|
|
249
|
-
|
|
164
|
+
// Display task info using shared function
|
|
165
|
+
displayTaskWithStatus(task);
|
|
250
166
|
}
|
|
251
167
|
status().catch(console.error);
|
package/dist/scripts/sync.js
CHANGED
|
@@ -28,10 +28,6 @@ Examples:
|
|
|
28
28
|
const localConfig = await loadLocalConfig();
|
|
29
29
|
const client = getLinearClient();
|
|
30
30
|
const teamId = getTeamId(config, localConfig.team);
|
|
31
|
-
// Build excluded emails from local config
|
|
32
|
-
const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
|
|
33
|
-
.map((key) => config.users[key]?.email)
|
|
34
|
-
.filter(Boolean));
|
|
35
31
|
// Build excluded labels set
|
|
36
32
|
const excludedLabels = new Set(localConfig.exclude_labels ?? []);
|
|
37
33
|
// Phase 1: Fetch active cycle directly from team
|
|
@@ -139,10 +135,6 @@ Examples:
|
|
|
139
135
|
const issue = await client.issue(issueNode.id);
|
|
140
136
|
const assignee = await issue.assignee;
|
|
141
137
|
const assigneeEmail = assignee?.email;
|
|
142
|
-
// Skip excluded assignees
|
|
143
|
-
if (assigneeEmail && excludedEmails.has(assigneeEmail)) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
138
|
const labels = await issue.labels();
|
|
147
139
|
const labelNames = labels.nodes.map((l) => l.name);
|
|
148
140
|
// Skip if any label is in excluded list
|
package/dist/scripts/utils.d.ts
CHANGED
package/dist/scripts/work-on.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
1: "š“ Urgent",
|
|
6
|
-
2: "š High",
|
|
7
|
-
3: "š” Medium",
|
|
8
|
-
4: "š¢ Low",
|
|
9
|
-
};
|
|
2
|
+
import { PRIORITY_LABELS, displayTaskFull } from "./lib/display.js";
|
|
3
|
+
import { getStatusTransitions, updateIssueStatus } from "./lib/linear.js";
|
|
4
|
+
import { getPrioritySortIndex, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
|
|
10
5
|
async function workOn() {
|
|
11
6
|
const args = process.argv.slice(2);
|
|
12
|
-
// Handle help flag
|
|
13
7
|
if (args.includes("--help") || args.includes("-h")) {
|
|
14
8
|
console.log(`Usage: ttt work-on [issue-id]
|
|
15
9
|
|
|
@@ -31,20 +25,18 @@ Examples:
|
|
|
31
25
|
process.exit(1);
|
|
32
26
|
}
|
|
33
27
|
const localConfig = await loadLocalConfig();
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
.map((key) => config.users[key]?.email)
|
|
37
|
-
.filter(Boolean));
|
|
28
|
+
// Get current user email for filtering
|
|
29
|
+
const currentUserEmail = config.users[localConfig.current_user]?.email;
|
|
38
30
|
const pendingTasks = data.tasks
|
|
39
|
-
.filter((t) => t.localStatus === "pending" &&
|
|
31
|
+
.filter((t) => t.localStatus === "pending" &&
|
|
32
|
+
(!currentUserEmail || t.assignee === currentUserEmail))
|
|
40
33
|
.sort((a, b) => {
|
|
41
34
|
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
42
35
|
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
43
36
|
return pa - pb;
|
|
44
37
|
});
|
|
45
|
-
//
|
|
38
|
+
// Issue Resolution
|
|
46
39
|
if (!issueId) {
|
|
47
|
-
// Interactive selection
|
|
48
40
|
if (pendingTasks.length === 0) {
|
|
49
41
|
console.log("ā
ę²ęå¾
čēēä»»åļ¼ęęå·„ä½å·²å®ęęé²č”äø");
|
|
50
42
|
process.exit(0);
|
|
@@ -67,7 +59,6 @@ Examples:
|
|
|
67
59
|
issueId = response.issueId;
|
|
68
60
|
}
|
|
69
61
|
else if (["next", "äøäøå", "äøäøåå·„ä½"].includes(issueId)) {
|
|
70
|
-
// Auto-select highest priority
|
|
71
62
|
if (pendingTasks.length === 0) {
|
|
72
63
|
console.log("ā
ę²ęå¾
čēēä»»åļ¼ęęå·„ä½å·²å®ęęé²č”äø");
|
|
73
64
|
process.exit(0);
|
|
@@ -75,13 +66,13 @@ Examples:
|
|
|
75
66
|
issueId = pendingTasks[0].id;
|
|
76
67
|
console.log(`Auto-selected: ${issueId}`);
|
|
77
68
|
}
|
|
78
|
-
//
|
|
69
|
+
// Find task
|
|
79
70
|
const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
|
|
80
71
|
if (!task) {
|
|
81
72
|
console.error(`Issue ${issueId} not found in current cycle.`);
|
|
82
73
|
process.exit(1);
|
|
83
74
|
}
|
|
84
|
-
//
|
|
75
|
+
// Availability Check
|
|
85
76
|
if (task.localStatus === "in-progress") {
|
|
86
77
|
console.log(`ā ļø ę¤ä»»å ${task.id} å·²åØé²č”äø`);
|
|
87
78
|
}
|
|
@@ -89,64 +80,22 @@ Examples:
|
|
|
89
80
|
console.log(`ā ļø ę¤ä»»å ${task.id} å·²å®ę`);
|
|
90
81
|
process.exit(0);
|
|
91
82
|
}
|
|
92
|
-
//
|
|
83
|
+
// Mark as In Progress
|
|
93
84
|
if (task.localStatus === "pending") {
|
|
94
85
|
task.localStatus = "in-progress";
|
|
95
86
|
await saveCycleData(data);
|
|
96
87
|
console.log(`Local: ${task.id} ā in-progress`);
|
|
97
|
-
// Update Linear
|
|
88
|
+
// Update Linear
|
|
98
89
|
if (task.linearId && process.env.LINEAR_API_KEY) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
// Get status name from config or use default
|
|
105
|
-
const inProgressStatusName = config.status_transitions?.in_progress || "In Progress";
|
|
106
|
-
const inProgressState = workflowStates.nodes.find((s) => s.name === inProgressStatusName);
|
|
107
|
-
if (inProgressState) {
|
|
108
|
-
await client.updateIssue(task.linearId, {
|
|
109
|
-
stateId: inProgressState.id,
|
|
110
|
-
});
|
|
111
|
-
console.log(`Linear: ${task.id} ā ${inProgressStatusName}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
console.error("Failed to update Linear:", e);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Phase 4: Display task info
|
|
120
|
-
console.log(`\n${"ā".repeat(50)}`);
|
|
121
|
-
console.log(`š· ${task.id}: ${task.title}`);
|
|
122
|
-
console.log(`${"ā".repeat(50)}`);
|
|
123
|
-
console.log(`Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
|
|
124
|
-
console.log(`Labels: ${task.labels.join(", ")}`);
|
|
125
|
-
console.log(`Branch: ${task.branch || "N/A"}`);
|
|
126
|
-
if (task.url)
|
|
127
|
-
console.log(`URL: ${task.url}`);
|
|
128
|
-
if (task.description) {
|
|
129
|
-
console.log(`\nš Description:\n${task.description}`);
|
|
130
|
-
}
|
|
131
|
-
if (task.attachments && task.attachments.length > 0) {
|
|
132
|
-
console.log(`\nš Attachments:`);
|
|
133
|
-
for (const att of task.attachments) {
|
|
134
|
-
console.log(` - ${att.title}: ${att.url}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (task.comments && task.comments.length > 0) {
|
|
138
|
-
console.log(`\nš¬ Comments (${task.comments.length}):`);
|
|
139
|
-
for (const comment of task.comments) {
|
|
140
|
-
const date = new Date(comment.createdAt).toLocaleDateString();
|
|
141
|
-
console.log(`\n [${comment.user || "Unknown"} - ${date}]`);
|
|
142
|
-
// Indent comment body
|
|
143
|
-
const lines = comment.body.split("\n");
|
|
144
|
-
for (const line of lines) {
|
|
145
|
-
console.log(` ${line}`);
|
|
90
|
+
const transitions = getStatusTransitions(config);
|
|
91
|
+
const success = await updateIssueStatus(task.linearId, transitions.in_progress, config, localConfig.team);
|
|
92
|
+
if (success) {
|
|
93
|
+
console.log(`Linear: ${task.id} ā ${transitions.in_progress}`);
|
|
146
94
|
}
|
|
147
95
|
}
|
|
148
96
|
}
|
|
149
|
-
|
|
97
|
+
// Display task info
|
|
98
|
+
displayTaskFull(task, "š·");
|
|
150
99
|
console.log("Next: bun type-check && bun lint");
|
|
151
100
|
}
|
|
152
101
|
workOn().catch(console.error);
|