team-toon-tack 3.0.0 → 3.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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/scripts/config/filters.js +19 -26
  4. package/dist/scripts/config/show.js +1 -1
  5. package/dist/scripts/config/status.js +19 -26
  6. package/dist/scripts/config/teams.js +24 -55
  7. package/dist/scripts/done-job.js +12 -17
  8. package/dist/scripts/init.js +3 -5
  9. package/dist/scripts/lib/adapters/linear-adapter.js +2 -2
  10. package/dist/scripts/lib/adapters/trello-adapter.js +3 -3
  11. package/dist/scripts/lib/adapters/types.d.ts +1 -1
  12. package/dist/scripts/lib/config-builder.d.ts +1 -1
  13. package/dist/scripts/lib/config-builder.js +2 -2
  14. package/dist/scripts/lib/done/linear-handler.js +2 -2
  15. package/dist/scripts/lib/done/trello-handler.js +2 -2
  16. package/dist/scripts/lib/done/types.d.ts +1 -1
  17. package/dist/scripts/lib/files.d.ts +4 -0
  18. package/dist/scripts/lib/files.js +18 -0
  19. package/dist/scripts/lib/git.d.ts +1 -1
  20. package/dist/scripts/lib/git.js +3 -3
  21. package/dist/scripts/lib/init/args.js +9 -7
  22. package/dist/scripts/lib/init/file-ops.js +3 -5
  23. package/dist/scripts/lib/init/linear-init.js +5 -4
  24. package/dist/scripts/lib/init/linear-prompts.js +49 -75
  25. package/dist/scripts/lib/init/prompts.d.ts +1 -1
  26. package/dist/scripts/lib/init/prompts.js +20 -31
  27. package/dist/scripts/lib/init/trello-init.js +36 -33
  28. package/dist/scripts/lib/init/trello-prompts.js +3 -9
  29. package/dist/scripts/lib/init/types.d.ts +1 -1
  30. package/dist/scripts/lib/linear.js +2 -1
  31. package/dist/scripts/lib/status-helpers.d.ts +30 -0
  32. package/dist/scripts/lib/status-helpers.js +58 -0
  33. package/dist/scripts/show.js +1 -1
  34. package/dist/scripts/status.js +7 -2
  35. package/dist/scripts/sync.js +27 -12
  36. package/dist/scripts/utils.d.ts +2 -2
  37. package/dist/scripts/work-on.js +5 -7
  38. package/package.json +5 -6
package/README.md CHANGED
@@ -48,7 +48,7 @@ During init, you'll be prompted to select your task source (Linear or Trello) an
48
48
  **For Trello:**
49
49
  - **Board**: The Trello board to sync
50
50
  - **User**: Your Trello username
51
- - **Status mappings**: Map Trello lists to Todo/In Progress/Done
51
+ - **Status mappings**: Map Trello lists to Todo (supports multiple lists)/In Progress/Done
52
52
  - **Label filter**: Optional label to filter cards
53
53
 
54
54
  ### Completion Modes (Linear only)
package/README.zh-TW.md CHANGED
@@ -48,7 +48,7 @@ ttt init
48
48
  **Trello:**
49
49
  - **看板**:要同步的 Trello 看板
50
50
  - **使用者**:你的 Trello 使用者名稱
51
- - **狀態映射**:將 Trello 列表映射到 Todo/In Progress/Done
51
+ - **狀態映射**:將 Trello 列表映射到 Todo(支援多個列表)/In Progress/Done
52
52
  - **標籤過濾**:可選的卡片過濾標籤
53
53
 
54
54
  ### 完成模式(僅 Linear)
@@ -1,4 +1,4 @@
1
- import prompts from "prompts";
1
+ import { checkbox } from "@inquirer/prompts";
2
2
  import { getLinearClient, getTeamId, saveLocalConfig, } from "../utils.js";
3
3
  export async function configureFilters(config, localConfig) {
4
4
  console.log("🔍 Configure Filters\n");
@@ -9,38 +9,31 @@ export async function configureFilters(config, localConfig) {
9
9
  filter: { team: { id: { eq: teamId } } },
10
10
  });
11
11
  const labels = labelsData.nodes;
12
- // Label filter (optional)
13
- const labelChoices = [
14
- { title: "(No filter - sync all labels)", value: "" },
15
- ...labels.map((l) => ({ title: l.name, value: l.name })),
16
- ];
17
- const labelResponse = await prompts({
18
- type: "select",
19
- name: "label",
20
- message: "Select label filter (optional):",
12
+ // Labels filter (multiselect, optional)
13
+ const labelChoices = labels.map((l) => ({
14
+ name: l.name,
15
+ value: l.name,
16
+ checked: localConfig.labels?.includes(l.name),
17
+ }));
18
+ const selectedLabels = await checkbox({
19
+ message: "Select label filters (space to select, enter to confirm):",
21
20
  choices: labelChoices,
22
- initial: localConfig.label
23
- ? labelChoices.findIndex((c) => c.value === localConfig.label)
24
- : 0,
25
21
  });
26
22
  // Exclude labels
27
- const excludeLabelsResponse = await prompts({
28
- type: "multiselect",
29
- name: "excludeLabels",
23
+ const excludeLabelChoices = labels.map((l) => ({
24
+ name: l.name,
25
+ value: l.name,
26
+ checked: localConfig.exclude_labels?.includes(l.name),
27
+ }));
28
+ const excludeLabels = await checkbox({
30
29
  message: "Select labels to exclude (space to select):",
31
- choices: labels.map((l) => ({
32
- title: l.name,
33
- value: l.name,
34
- selected: localConfig.exclude_labels?.includes(l.name),
35
- })),
30
+ choices: excludeLabelChoices,
36
31
  });
37
- localConfig.label = labelResponse.label || undefined;
32
+ localConfig.labels = selectedLabels.length > 0 ? selectedLabels : undefined;
38
33
  localConfig.exclude_labels =
39
- excludeLabelsResponse.excludeLabels?.length > 0
40
- ? excludeLabelsResponse.excludeLabels
41
- : undefined;
34
+ excludeLabels.length > 0 ? excludeLabels : undefined;
42
35
  await saveLocalConfig(localConfig);
43
36
  console.log("\n✅ Filters updated:");
44
- console.log(` Label: ${localConfig.label || "(all)"}`);
37
+ console.log(` Labels: ${localConfig.labels?.join(", ") || "(all)"}`);
45
38
  console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
46
39
  }
@@ -13,7 +13,7 @@ export function showConfig(config, localConfig) {
13
13
  console.log(`\nUser: ${localConfig.current_user}`);
14
14
  // Filters
15
15
  console.log("\nFilters:");
16
- console.log(` Label: ${localConfig.label || "(all)"}`);
16
+ console.log(` Labels: ${localConfig.labels?.join(", ") || "(all)"}`);
17
17
  console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
18
18
  // Status Mappings
19
19
  console.log("\nStatus Mappings:");
@@ -1,5 +1,6 @@
1
- import prompts from "prompts";
1
+ import { select } from "@inquirer/prompts";
2
2
  import { getWorkflowStates } from "../lib/linear.js";
3
+ import { getFirstTodoStatus } from "../lib/status-helpers.js";
3
4
  import { saveConfig, } from "../utils.js";
4
5
  export async function configureStatus(config, localConfig) {
5
6
  console.log("📊 Configure Status Mappings\n");
@@ -9,57 +10,49 @@ export async function configureStatus(config, localConfig) {
9
10
  process.exit(1);
10
11
  }
11
12
  const stateChoices = states.map((s) => ({
12
- title: `${s.name} (${s.type})`,
13
+ name: `${s.name} (${s.type})`,
13
14
  value: s.name,
14
15
  }));
15
16
  // Get current values or defaults
16
17
  const current = config.status_transitions || {};
17
- const defaultTodo = current.todo || states.find((s) => s.type === "unstarted")?.name || "Todo";
18
+ const defaultTodo = getFirstTodoStatus(current.todo) ||
19
+ states.find((s) => s.type === "unstarted")?.name ||
20
+ "Todo";
18
21
  const defaultInProgress = current.in_progress ||
19
22
  states.find((s) => s.type === "started")?.name ||
20
23
  "In Progress";
21
24
  const defaultDone = current.done || states.find((s) => s.type === "completed")?.name || "Done";
22
25
  const defaultTesting = current.testing || states.find((s) => s.name === "Testing")?.name;
23
- const todoResponse = await prompts({
24
- type: "select",
25
- name: "todo",
26
+ const todo = await select({
26
27
  message: 'Select status for "Todo" (pending tasks):',
27
28
  choices: stateChoices,
28
- initial: stateChoices.findIndex((c) => c.value === defaultTodo),
29
+ default: defaultTodo,
29
30
  });
30
- const inProgressResponse = await prompts({
31
- type: "select",
32
- name: "in_progress",
31
+ const in_progress = await select({
33
32
  message: 'Select status for "In Progress" (working tasks):',
34
33
  choices: stateChoices,
35
- initial: stateChoices.findIndex((c) => c.value === defaultInProgress),
34
+ default: defaultInProgress,
36
35
  });
37
- const doneResponse = await prompts({
38
- type: "select",
39
- name: "done",
36
+ const done = await select({
40
37
  message: 'Select status for "Done" (completed tasks):',
41
38
  choices: stateChoices,
42
- initial: stateChoices.findIndex((c) => c.value === defaultDone),
39
+ default: defaultDone,
43
40
  });
44
41
  // Testing is optional
45
42
  const testingChoices = [
46
- { title: "(None)", value: undefined },
43
+ { name: "(None)", value: undefined },
47
44
  ...stateChoices,
48
45
  ];
49
- const testingResponse = await prompts({
50
- type: "select",
51
- name: "testing",
46
+ const testing = await select({
52
47
  message: 'Select status for "Testing" (optional, for parent tasks):',
53
48
  choices: testingChoices,
54
- initial: defaultTesting
55
- ? testingChoices.findIndex((c) => c.value === defaultTesting)
56
- : 0,
49
+ default: defaultTesting,
57
50
  });
58
51
  const statusTransitions = {
59
- todo: todoResponse.todo || defaultTodo,
60
- in_progress: inProgressResponse.in_progress || defaultInProgress,
61
- done: doneResponse.done || defaultDone,
62
- testing: testingResponse.testing,
52
+ todo: todo || defaultTodo,
53
+ in_progress: in_progress || defaultInProgress,
54
+ done: done || defaultDone,
55
+ testing: testing,
63
56
  };
64
57
  config.status_transitions = statusTransitions;
65
58
  await saveConfig(config);
@@ -1,4 +1,4 @@
1
- import prompts from "prompts";
1
+ import { checkbox, select } from "@inquirer/prompts";
2
2
  import { getDefaultStatusTransitions } from "../lib/config-builder.js";
3
3
  import { getLinearClient, saveLocalConfig, } from "../utils.js";
4
4
  export async function configureTeams(_config, localConfig) {
@@ -31,66 +31,51 @@ export async function configureTeams(_config, localConfig) {
31
31
  }
32
32
  // 1. Select dev team (single)
33
33
  console.log("\n👨‍💻 Dev Team:");
34
- const devTeamResponse = await prompts({
35
- type: "select",
36
- name: "teamId",
34
+ const devTeamId = await select({
37
35
  message: "Select your dev team:",
38
36
  choices: teams.map((t) => {
39
- const key = teamKeyMap.get(t.id) || "";
37
+ const _key = teamKeyMap.get(t.id) || "";
40
38
  return {
41
- title: t.name,
39
+ name: t.name,
42
40
  value: t.id,
43
- selected: key === localConfig.team,
44
41
  };
45
42
  }),
43
+ default: teams.find((t) => teamKeyMap.get(t.id) === localConfig.team)?.id,
46
44
  });
47
- const devTeamId = devTeamResponse.teamId;
48
45
  const devTeamKey = teamKeyMap.get(devTeamId) || localConfig.team;
49
46
  const devTeam = teams.find((t) => t.id === devTeamId);
50
47
  // 2. Select dev team testing status
51
48
  const devStates = teamStatesMap.get(devTeamId) || [];
52
49
  const devDefaults = getDefaultStatusTransitions(devStates);
53
50
  console.log("\n🔍 Dev Team Testing/Review Status:");
54
- const devTestingResponse = await prompts({
55
- type: "select",
56
- name: "testingStatus",
51
+ const devTestingStatus = await select({
57
52
  message: "Select testing/review status for dev team:",
58
53
  choices: [
59
- { title: "(Skip - no testing status)", value: undefined },
54
+ { name: "(Skip - no testing status)", value: undefined },
60
55
  ...devStates.map((s) => ({
61
- title: `${s.name} (${s.type})`,
56
+ name: `${s.name} (${s.type})`,
62
57
  value: s.name,
63
58
  })),
64
59
  ],
65
- initial: localConfig.dev_testing_status
66
- ? devStates.findIndex((s) => s.name === localConfig.dev_testing_status) +
67
- 1
68
- : devDefaults.testing
69
- ? devStates.findIndex((s) => s.name === devDefaults.testing) + 1
70
- : 0,
60
+ default: localConfig.dev_testing_status || devDefaults.testing,
71
61
  });
72
- const devTestingStatus = devTestingResponse.testingStatus;
73
62
  // 3. Select QA/PM teams (multiple)
74
63
  const otherTeams = teams.filter((t) => t.id !== devTeamId);
75
64
  const qaPmTeams = [];
76
65
  if (otherTeams.length > 0) {
77
66
  console.log("\n🔗 QA/PM Teams:");
78
- const qaPmResponse = await prompts({
79
- type: "multiselect",
80
- name: "teamIds",
67
+ const selectedQaPmIds = await checkbox({
81
68
  message: "Select QA/PM teams for cross-team parent updates (space to select):",
82
69
  choices: otherTeams.map((t) => {
83
70
  const key = teamKeyMap.get(t.id) || "";
84
71
  const currentQaPm = localConfig.qa_pm_teams || [];
85
72
  return {
86
- title: t.name,
73
+ name: t.name,
87
74
  value: t.id,
88
- selected: currentQaPm.some((qp) => qp.team === key),
75
+ checked: currentQaPm.some((qp) => qp.team === key),
89
76
  };
90
77
  }),
91
- hint: "- Press space to select, enter to confirm. Leave empty to skip.",
92
78
  });
93
- const selectedQaPmIds = qaPmResponse.teamIds || [];
94
79
  // 4. For each QA/PM team, select testing status
95
80
  for (const teamId of selectedQaPmIds) {
96
81
  const team = teams.find((t) => t.id === teamId);
@@ -102,27 +87,21 @@ export async function configureTeams(_config, localConfig) {
102
87
  // Find existing config for this team
103
88
  const existingConfig = localConfig.qa_pm_teams?.find((qp) => qp.team === teamKey);
104
89
  const stateChoices = teamStates.map((s) => ({
105
- title: `${s.name} (${s.type})`,
90
+ name: `${s.name} (${s.type})`,
106
91
  value: s.name,
107
92
  }));
108
- const statusResponse = await prompts({
109
- type: "select",
110
- name: "testingStatus",
93
+ const testingStatus = await select({
111
94
  message: `Select testing status for ${team.name}:`,
112
95
  choices: [
113
- { title: "(Skip this team)", value: undefined },
96
+ { name: "(Skip this team)", value: undefined },
114
97
  ...stateChoices,
115
98
  ],
116
- initial: existingConfig?.testing_status
117
- ? stateChoices.findIndex((c) => c.value === existingConfig.testing_status) + 1
118
- : defaults.testing
119
- ? stateChoices.findIndex((c) => c.value === defaults.testing) + 1
120
- : 0,
99
+ default: existingConfig?.testing_status || defaults.testing,
121
100
  });
122
- if (statusResponse.testingStatus) {
101
+ if (testingStatus) {
123
102
  qaPmTeams.push({
124
103
  team: teamKey,
125
- testing_status: statusResponse.testingStatus,
104
+ testing_status: testingStatus,
126
105
  });
127
106
  }
128
107
  }
@@ -131,42 +110,32 @@ export async function configureTeams(_config, localConfig) {
131
110
  console.log("\n✅ Completion Mode:");
132
111
  const currentMode = localConfig.completion_mode ||
133
112
  (qaPmTeams.length > 0 ? "upstream_strict" : "simple");
134
- const defaultModeIndex = currentMode === "simple"
135
- ? 0
136
- : currentMode === "strict_review"
137
- ? 1
138
- : currentMode === "upstream_strict"
139
- ? 2
140
- : 3;
141
- const modeResponse = await prompts({
142
- type: "select",
143
- name: "mode",
113
+ const completionMode = await select({
144
114
  message: "How should tasks be completed?",
145
115
  choices: [
146
116
  {
147
- title: "Simple",
117
+ name: "Simple",
148
118
  value: "simple",
149
119
  description: "Mark task as done directly",
150
120
  },
151
121
  {
152
- title: "Strict Review",
122
+ name: "Strict Review",
153
123
  value: "strict_review",
154
124
  description: "Mark task to dev team's testing status",
155
125
  },
156
126
  {
157
- title: "Upstream Strict (recommended with QA/PM)",
127
+ name: "Upstream Strict (recommended with QA/PM)",
158
128
  value: "upstream_strict",
159
129
  description: "Done + parent to testing, fallback to testing if no parent",
160
130
  },
161
131
  {
162
- title: "Upstream Not Strict",
132
+ name: "Upstream Not Strict",
163
133
  value: "upstream_not_strict",
164
134
  description: "Done + parent to testing, no fallback",
165
135
  },
166
136
  ],
167
- initial: defaultModeIndex,
137
+ default: currentMode,
168
138
  });
169
- const completionMode = modeResponse.mode || (qaPmTeams.length > 0 ? "upstream_strict" : "simple");
170
139
  // Update local config
171
140
  localConfig.team = devTeamKey;
172
141
  localConfig.dev_testing_status = devTestingStatus;
@@ -3,7 +3,7 @@
3
3
  * ttt done - Complete a task
4
4
  * Entry point that delegates to source-specific completion handlers
5
5
  */
6
- import prompts from "prompts";
6
+ import { input, select } from "@inquirer/prompts";
7
7
  import { handleLinearCompletion, handleTrelloCompletion, parseArgs, printHelp, } from "./lib/done/index.js";
8
8
  import { getLatestCommit } from "./lib/git.js";
9
9
  import { fetchIssueDetail, syncSingleIssue } from "./lib/sync.js";
@@ -57,21 +57,19 @@ async function doneJob() {
57
57
  }
58
58
  else if (process.stdin.isTTY) {
59
59
  const choices = inProgressTasks.map((t) => ({
60
- title: `${t.id}: ${t.title}`,
60
+ name: `${t.id}: ${t.title}`,
61
61
  value: t.id,
62
62
  description: t.labels.join(", "),
63
63
  }));
64
- const response = await prompts({
65
- type: "select",
66
- name: "issueId",
64
+ const selectedId = await select({
67
65
  message: "選擇要完成的任務:",
68
66
  choices: choices,
69
67
  });
70
- if (!response.issueId) {
68
+ if (!selectedId) {
71
69
  console.log("已取消");
72
70
  process.exit(0);
73
71
  }
74
- issueId = response.issueId;
72
+ issueId = selectedId;
75
73
  }
76
74
  else {
77
75
  console.error("多個進行中任務,請指定 issue ID:");
@@ -97,14 +95,11 @@ async function doneJob() {
97
95
  // Get latest commit
98
96
  const commit = getLatestCommit();
99
97
  // Get AI summary message
100
- let aiMessage = argMessage || "";
101
- if (!aiMessage && process.stdin.isTTY) {
102
- const aiMsgResponse = await prompts({
103
- type: "text",
104
- name: "aiMessage",
105
- message: "AI 修復說明 (如何解決此問題):",
98
+ let promptMessage = argMessage || "";
99
+ if (!promptMessage && process.stdin.isTTY) {
100
+ promptMessage = await input({
101
+ message: "🔧 修復說明 (如何解決此問題):",
106
102
  });
107
- aiMessage = aiMsgResponse.aiMessage || "";
108
103
  }
109
104
  // Update remote (only if status_source is 'remote' or not set)
110
105
  const statusSource = localConfig.status_source || "remote";
@@ -117,7 +112,7 @@ async function doneJob() {
117
112
  config,
118
113
  localConfig,
119
114
  commit,
120
- aiMessage,
115
+ promptMessage,
121
116
  };
122
117
  // Branch based on source type
123
118
  if (sourceType === "trello") {
@@ -166,8 +161,8 @@ async function doneJob() {
166
161
  console.log(`URL: ${commit.commitUrl}`);
167
162
  }
168
163
  }
169
- if (aiMessage) {
170
- console.log(`AI: ${aiMessage}`);
164
+ if (promptMessage) {
165
+ console.log(`🔧 說明: ${promptMessage}`);
171
166
  }
172
167
  console.log(`\n🎉 任務完成!`);
173
168
  }
@@ -3,7 +3,7 @@
3
3
  * ttt init - Initialize configuration files
4
4
  * Entry point that delegates to source-specific initialization
5
5
  */
6
- import prompts from "prompts";
6
+ import { confirm } from "@inquirer/prompts";
7
7
  import { initLinear, initTrello, parseArgs, printHelp, selectTaskSource, } from "./lib/init/index.js";
8
8
  import { fileExists, getPaths } from "./utils.js";
9
9
  async function init() {
@@ -34,11 +34,9 @@ async function init() {
34
34
  if (localExists)
35
35
  console.log(` ✓ ${paths.localPath}`);
36
36
  if (options.interactive) {
37
- const { proceed } = await prompts({
38
- type: "confirm",
39
- name: "proceed",
37
+ const proceed = await confirm({
40
38
  message: "Update existing configuration?",
41
- initial: true,
39
+ default: true,
42
40
  });
43
41
  if (!proceed) {
44
42
  console.log("Cancelled.");
@@ -85,8 +85,8 @@ export class LinearAdapter {
85
85
  if (options.statusNames && options.statusNames.length > 0) {
86
86
  filter.state = { name: { in: options.statusNames } };
87
87
  }
88
- if (options.labelName) {
89
- filter.labels = { name: { eq: options.labelName } };
88
+ if (options.labelNames && options.labelNames.length > 0) {
89
+ filter.labels = { name: { in: options.labelNames } };
90
90
  }
91
91
  if (options.assigneeEmail) {
92
92
  filter.assignee = { email: { eq: options.assigneeEmail } };
@@ -90,9 +90,9 @@ export class TrelloAdapter {
90
90
  continue;
91
91
  }
92
92
  }
93
- // Filter by label if specified
94
- if (options.labelName) {
95
- if (!labelNames.includes(options.labelName)) {
93
+ // Filter by labels if specified (OR logic - card must have at least one)
94
+ if (options.labelNames && options.labelNames.length > 0) {
95
+ if (!labelNames.some((name) => options.labelNames?.includes(name))) {
96
96
  continue;
97
97
  }
98
98
  }
@@ -94,7 +94,7 @@ export interface GetIssuesOptions {
94
94
  teamId: string;
95
95
  cycleId?: string;
96
96
  statusNames?: string[];
97
- labelName?: string;
97
+ labelNames?: string[];
98
98
  assigneeEmail?: string;
99
99
  excludeLabels?: string[];
100
100
  limit?: number;
@@ -38,4 +38,4 @@ export declare function getDefaultStatusTransitions(states: LinearState[]): Stat
38
38
  export declare function buildConfig(teams: LinearTeam[], users: LinearUser[], labels: LinearLabel[], states: LinearState[], statusTransitions: StatusTransitions, currentCycle?: LinearCycle): Config;
39
39
  export declare function findUserKey(usersConfig: Record<string, UserConfig>, userId: string): string;
40
40
  export declare function findTeamKey(teamsConfig: Record<string, TeamConfig>, teamId: string): string;
41
- export declare function buildLocalConfig(currentUserKey: string, devTeamKey: string, devTestingStatus?: string, qaPmTeams?: QaPmTeamConfig[], completionMode?: CompletionMode, defaultLabel?: string, excludeLabels?: string[], statusSource?: "remote" | "local"): LocalConfig;
41
+ export declare function buildLocalConfig(currentUserKey: string, devTeamKey: string, devTestingStatus?: string, qaPmTeams?: QaPmTeamConfig[], completionMode?: CompletionMode, defaultLabels?: string[], excludeLabels?: string[], statusSource?: "remote" | "local"): LocalConfig;
@@ -108,14 +108,14 @@ export function findTeamKey(teamsConfig, teamId) {
108
108
  return (Object.entries(teamsConfig).find(([_, t]) => t.id === teamId)?.[0] ||
109
109
  Object.keys(teamsConfig)[0]);
110
110
  }
111
- export function buildLocalConfig(currentUserKey, devTeamKey, devTestingStatus, qaPmTeams, completionMode, defaultLabel, excludeLabels, statusSource) {
111
+ export function buildLocalConfig(currentUserKey, devTeamKey, devTestingStatus, qaPmTeams, completionMode, defaultLabels, excludeLabels, statusSource) {
112
112
  return {
113
113
  current_user: currentUserKey,
114
114
  team: devTeamKey,
115
115
  dev_testing_status: devTestingStatus,
116
116
  qa_pm_teams: qaPmTeams && qaPmTeams.length > 0 ? qaPmTeams : undefined,
117
117
  completion_mode: completionMode,
118
- label: defaultLabel,
118
+ labels: defaultLabels && defaultLabels.length > 0 ? defaultLabels : undefined,
119
119
  exclude_labels: excludeLabels && excludeLabels.length > 0 ? excludeLabels : undefined,
120
120
  status_source: statusSource,
121
121
  };
@@ -96,7 +96,7 @@ async function handleUpstreamCompletion(context, isStrict) {
96
96
  * Handle Linear task completion with complex completion modes
97
97
  */
98
98
  export async function handleLinearCompletion(context) {
99
- const { task, localConfig, commit, aiMessage } = context;
99
+ const { task, localConfig, commit, promptMessage } = context;
100
100
  // Determine completion mode
101
101
  const completionMode = localConfig.completion_mode ||
102
102
  (localConfig.qa_pm_teams && localConfig.qa_pm_teams.length > 0
@@ -122,7 +122,7 @@ export async function handleLinearCompletion(context) {
122
122
  }
123
123
  // Add comment with commit info
124
124
  if (commit) {
125
- const commentBody = buildCompletionComment(commit, aiMessage);
125
+ const commentBody = buildCompletionComment(commit, promptMessage);
126
126
  const commentSuccess = await addComment(task.linearId, commentBody);
127
127
  if (commentSuccess) {
128
128
  console.log(`Linear: 已新增 commit 留言`);
@@ -9,7 +9,7 @@ import { getStatusTransitions } from "../linear.js";
9
9
  * Trello uses simple completion: update status + add comment
10
10
  */
11
11
  export async function handleTrelloCompletion(context) {
12
- const { task, config, localConfig, commit, aiMessage } = context;
12
+ const { task, config, localConfig, commit, promptMessage } = context;
13
13
  const sourceId = task.sourceId ?? task.linearId;
14
14
  if (!sourceId) {
15
15
  return { success: false, message: "No source ID found" };
@@ -33,7 +33,7 @@ export async function handleTrelloCompletion(context) {
33
33
  }
34
34
  // Add comment with commit info
35
35
  if (commit) {
36
- const commentBody = buildCompletionComment(commit, aiMessage);
36
+ const commentBody = buildCompletionComment(commit, promptMessage);
37
37
  const commentResult = await adapter.addComment(sourceId, commentBody);
38
38
  if (commentResult.success) {
39
39
  console.log(`Trello: 已新增 commit 留言`);
@@ -13,7 +13,7 @@ export interface CompletionContext {
13
13
  config: Config;
14
14
  localConfig: LocalConfig;
15
15
  commit: CommitInfo | null;
16
- aiMessage: string;
16
+ promptMessage: string;
17
17
  }
18
18
  export interface CompletionResult {
19
19
  success: boolean;
@@ -7,3 +7,7 @@ export declare function downloadLinearFile(url: string, issueId: string, attachm
7
7
  export declare const downloadLinearImage: typeof downloadLinearFile;
8
8
  export declare function clearIssueImages(outputDir: string, issueId: string): Promise<void>;
9
9
  export declare function ensureOutputDir(outputDir: string): Promise<void>;
10
+ /**
11
+ * Clear all files in output directory (for fresh sync)
12
+ */
13
+ export declare function clearAllOutput(outputDir: string): Promise<void>;
@@ -134,3 +134,21 @@ export async function clearIssueImages(outputDir, issueId) {
134
134
  export async function ensureOutputDir(outputDir) {
135
135
  await fs.mkdir(outputDir, { recursive: true });
136
136
  }
137
+ /**
138
+ * Clear all files in output directory (for fresh sync)
139
+ */
140
+ export async function clearAllOutput(outputDir) {
141
+ try {
142
+ const files = await fs.readdir(outputDir);
143
+ for (const file of files) {
144
+ const filepath = path.join(outputDir, file);
145
+ const stat = await fs.stat(filepath);
146
+ if (stat.isFile()) {
147
+ await fs.unlink(filepath);
148
+ }
149
+ }
150
+ }
151
+ catch {
152
+ // Directory doesn't exist or other error, ignore
153
+ }
154
+ }
@@ -7,4 +7,4 @@ export interface CommitInfo {
7
7
  }
8
8
  export declare function getLatestCommit(): CommitInfo | null;
9
9
  export declare function formatCommitLink(commit: CommitInfo): string;
10
- export declare function buildCompletionComment(commit: CommitInfo, aiMessage?: string): string;
10
+ export declare function buildCompletionComment(commit: CommitInfo, promptMessage?: string): string;
@@ -57,13 +57,13 @@ export function formatCommitLink(commit) {
57
57
  ? `[${commit.shortHash}](${commit.commitUrl})`
58
58
  : `\`${commit.shortHash}\``;
59
59
  }
60
- export function buildCompletionComment(commit, aiMessage) {
60
+ export function buildCompletionComment(commit, promptMessage) {
61
61
  const commitLink = formatCommitLink(commit);
62
62
  const commentParts = [
63
63
  "## ✅ 開發完成",
64
64
  "",
65
- "### 🤖 AI 修復說明",
66
- aiMessage || "_No description provided_",
65
+ "### 🔧 修復說明",
66
+ promptMessage || "_No description provided_",
67
67
  "",
68
68
  "### 📝 Commit Info",
69
69
  `**Commit:** ${commitLink}`,