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.
@@ -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
+ }
@@ -1,12 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { getLinearClient, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
3
- const PRIORITY_LABELS = {
4
- 0: "⚪ None",
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
- const statusIcon = t.localStatus === "completed"
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
- // Map to Linear status
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
- statusTransitions[setStatus];
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 && newLocalStatus !== task.localStatus) {
162
- const oldStatus = task.localStatus;
163
- task.localStatus = newLocalStatus;
164
- await saveCycleData(data);
165
- console.log(`Local: ${task.id} ${oldStatus} → ${newLocalStatus}`);
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
- try {
170
- const client = getLinearClient();
171
- const teamId = getTeamId(config, localConfig.team);
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
- // Display task info
209
- console.log(`\n${"═".repeat(50)}`);
210
- const statusIcon = task.localStatus === "completed"
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
- console.log(`\n${"─".repeat(50)}`);
164
+ // Display task info using shared function
165
+ displayTaskWithStatus(task);
250
166
  }
251
167
  status().catch(console.error);
@@ -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
@@ -92,7 +92,6 @@ export interface LocalConfig {
92
92
  current_user: string;
93
93
  team: string;
94
94
  teams?: string[];
95
- exclude_assignees?: string[];
96
95
  exclude_labels?: string[];
97
96
  label?: string;
98
97
  }
@@ -1,15 +1,9 @@
1
1
  import prompts from "prompts";
2
- import { getLinearClient, getPrioritySortIndex, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
3
- const PRIORITY_LABELS = {
4
- 0: "⚪ None",
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
- // Build excluded emails list from user keys
35
- const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
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" && !excludedEmails.has(t.assignee ?? ""))
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
- // Phase 0: Issue Resolution
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
- // Phase 1: Find task
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
- // Phase 2: Availability Check
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
- // Phase 3: Mark as In Progress
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 using stored linearId
88
+ // Update Linear
98
89
  if (task.linearId && process.env.LINEAR_API_KEY) {
99
- try {
100
- const client = getLinearClient();
101
- const workflowStates = await client.workflowStates({
102
- filter: { team: { id: { eq: getTeamId(config, localConfig.team) } } },
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
- console.log(`\n${"─".repeat(50)}`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {