team-toon-tack 1.6.0 → 1.6.1

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.
@@ -1,51 +1,7 @@
1
- import { execSync } from "node:child_process";
2
1
  import prompts from "prompts";
3
- import { getLinearClient, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
4
- async function getLatestCommit() {
5
- try {
6
- const shortHash = execSync("git rev-parse --short HEAD", {
7
- encoding: "utf-8",
8
- }).trim();
9
- const fullHash = execSync("git rev-parse HEAD", {
10
- encoding: "utf-8",
11
- }).trim();
12
- const message = execSync("git log -1 --format=%s", {
13
- encoding: "utf-8",
14
- }).trim();
15
- const diffStat = execSync("git diff HEAD~1 --stat --stat-width=60", {
16
- encoding: "utf-8",
17
- }).trim();
18
- // Get remote URL and construct commit link
19
- let commitUrl = null;
20
- try {
21
- const remoteUrl = execSync("git remote get-url origin", {
22
- encoding: "utf-8",
23
- }).trim();
24
- // Handle SSH or HTTPS URLs
25
- // git@gitlab.com:org/repo.git -> https://gitlab.com/org/repo/-/commit/hash
26
- // https://gitlab.com/org/repo.git -> https://gitlab.com/org/repo/-/commit/hash
27
- if (remoteUrl.includes("gitlab")) {
28
- const match = remoteUrl.match(/(?:git@|https:\/\/)([^:/]+)[:\\/](.+?)(?:\.git)?$/);
29
- if (match) {
30
- commitUrl = `https://${match[1]}/${match[2]}/-/commit/${fullHash}`;
31
- }
32
- }
33
- else if (remoteUrl.includes("github")) {
34
- const match = remoteUrl.match(/(?:git@|https:\/\/)([^:/]+)[:\\/](.+?)(?:\.git)?$/);
35
- if (match) {
36
- commitUrl = `https://${match[1]}/${match[2]}/commit/${fullHash}`;
37
- }
38
- }
39
- }
40
- catch {
41
- // Ignore if can't get remote URL
42
- }
43
- return { shortHash, fullHash, message, diffStat, commitUrl };
44
- }
45
- catch {
46
- return null;
47
- }
48
- }
2
+ import { buildCompletionComment, getLatestCommit } from "./lib/git.js";
3
+ import { addComment, getStatusTransitions, getWorkflowStates, updateIssueStatus, } from "./lib/linear.js";
4
+ import { getLinearClient, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
49
5
  function parseArgs(args) {
50
6
  let issueId;
51
7
  let message;
@@ -62,7 +18,6 @@ function parseArgs(args) {
62
18
  }
63
19
  async function doneJob() {
64
20
  const args = process.argv.slice(2);
65
- // Handle help flag
66
21
  if (args.includes("--help") || args.includes("-h")) {
67
22
  console.log(`Usage: ttt done [issue-id] [-m message]
68
23
 
@@ -94,7 +49,7 @@ Examples:
94
49
  console.log("沒有進行中的任務");
95
50
  process.exit(0);
96
51
  }
97
- // Phase 0: Issue Resolution
52
+ // Issue Resolution
98
53
  if (!issueId) {
99
54
  if (inProgressTasks.length === 1) {
100
55
  issueId = inProgressTasks[0].id;
@@ -126,7 +81,7 @@ Examples:
126
81
  process.exit(1);
127
82
  }
128
83
  }
129
- // Phase 1: Find task
84
+ // Find task
130
85
  const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
131
86
  if (!task) {
132
87
  console.error(`Issue ${issueId} not found in current cycle.`);
@@ -136,9 +91,9 @@ Examples:
136
91
  console.log(`⚠️ 任務 ${task.id} 不在進行中狀態 (目前: ${task.localStatus})`);
137
92
  process.exit(1);
138
93
  }
139
- // Get latest commit for comment
140
- const commit = await getLatestCommit();
141
- // Phase 2: Get AI summary message
94
+ // Get latest commit
95
+ const commit = getLatestCommit();
96
+ // Get AI summary message
142
97
  let aiMessage = argMessage || "";
143
98
  if (!aiMessage && process.stdin.isTTY) {
144
99
  const aiMsgResponse = await prompts({
@@ -148,85 +103,52 @@ Examples:
148
103
  });
149
104
  aiMessage = aiMsgResponse.aiMessage || "";
150
105
  }
151
- // Phase 3: Update Linear
106
+ // Update Linear
152
107
  if (task.linearId && process.env.LINEAR_API_KEY) {
153
- try {
154
- const client = getLinearClient();
155
- const workflowStates = await client.workflowStates({
156
- filter: { team: { id: { eq: getTeamId(config, localConfig.team) } } },
157
- });
158
- // Get status names from config or use defaults
159
- const doneStatusName = config.status_transitions?.done || "Done";
160
- const testingStatusName = config.status_transitions?.testing || "Testing";
161
- const doneState = workflowStates.nodes.find((s) => s.name === doneStatusName);
162
- // Update issue to Done
163
- if (doneState) {
164
- await client.updateIssue(task.linearId, { stateId: doneState.id });
165
- console.log(`Linear: ${task.id} → ${doneStatusName}`);
166
- }
167
- // Add comment with commit info and AI summary
168
- if (commit) {
169
- const commitLink = commit.commitUrl
170
- ? `[${commit.shortHash}](${commit.commitUrl})`
171
- : `\`${commit.shortHash}\``;
172
- const commentParts = [
173
- "## ✅ 開發完成",
174
- "",
175
- "### 🤖 AI 修復說明",
176
- aiMessage || "_No description provided_",
177
- "",
178
- "### 📝 Commit Info",
179
- `**Commit:** ${commitLink}`,
180
- `**Message:** ${commit.message}`,
181
- "",
182
- "### 📊 Changes",
183
- "```",
184
- commit.diffStat,
185
- "```",
186
- ];
187
- await client.createComment({
188
- issueId: task.linearId,
189
- body: commentParts.join("\n"),
190
- });
108
+ const transitions = getStatusTransitions(config);
109
+ // Update issue to Done
110
+ const success = await updateIssueStatus(task.linearId, transitions.done, config, localConfig.team);
111
+ if (success) {
112
+ console.log(`Linear: ${task.id} → ${transitions.done}`);
113
+ }
114
+ // Add comment with commit info
115
+ if (commit) {
116
+ const commentBody = buildCompletionComment(commit, aiMessage);
117
+ const commentSuccess = await addComment(task.linearId, commentBody);
118
+ if (commentSuccess) {
191
119
  console.log(`Linear: 已新增 commit 留言`);
192
120
  }
193
- // Update parent to Testing if exists and testing status is configured
194
- if (task.parentIssueId && testingStatusName) {
195
- try {
196
- // Find parent issue by identifier
197
- const searchResult = await client.searchIssues(task.parentIssueId);
198
- const parentIssue = searchResult.nodes.find((issue) => issue.identifier === task.parentIssueId);
199
- if (parentIssue) {
200
- // Get parent's team workflow states
201
- const parentTeam = await parentIssue.team;
202
- if (parentTeam) {
203
- const parentWorkflowStates = await client.workflowStates({
204
- filter: { team: { id: { eq: parentTeam.id } } },
121
+ }
122
+ // Update parent to Testing if exists
123
+ if (task.parentIssueId && transitions.testing) {
124
+ try {
125
+ const client = getLinearClient();
126
+ const searchResult = await client.searchIssues(task.parentIssueId);
127
+ const parentIssue = searchResult.nodes.find((issue) => issue.identifier === task.parentIssueId);
128
+ if (parentIssue) {
129
+ const parentTeam = await parentIssue.team;
130
+ if (parentTeam) {
131
+ const parentStates = await getWorkflowStates(config, localConfig.team);
132
+ const testingState = parentStates.find((s) => s.name === transitions.testing);
133
+ if (testingState) {
134
+ await client.updateIssue(parentIssue.id, {
135
+ stateId: testingState.id,
205
136
  });
206
- const testingState = parentWorkflowStates.nodes.find((s) => s.name === testingStatusName);
207
- if (testingState) {
208
- await client.updateIssue(parentIssue.id, {
209
- stateId: testingState.id,
210
- });
211
- console.log(`Linear: Parent ${task.parentIssueId} → ${testingStatusName}`);
212
- }
137
+ console.log(`Linear: Parent ${task.parentIssueId} ${transitions.testing}`);
213
138
  }
214
139
  }
215
140
  }
216
- catch (parentError) {
217
- console.error("Failed to update parent issue:", parentError);
218
- }
219
141
  }
220
- }
221
- catch (e) {
222
- console.error("Failed to update Linear:", e);
142
+ catch (parentError) {
143
+ console.error("Failed to update parent issue:", parentError);
144
+ }
223
145
  }
224
146
  }
225
- // Phase 4: Update local status
147
+ // Update local status
226
148
  task.localStatus = "completed";
227
149
  await saveCycleData(data);
228
150
  console.log(`Local: ${task.id} → completed`);
229
- // Phase 5: Summary
151
+ // Summary
230
152
  console.log(`\n${"═".repeat(50)}`);
231
153
  console.log(`✅ ${task.id}: ${task.title}`);
232
154
  console.log(`${"═".repeat(50)}`);