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.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/scripts/config/filters.js +19 -26
- package/dist/scripts/config/show.js +1 -1
- package/dist/scripts/config/status.js +19 -26
- package/dist/scripts/config/teams.js +24 -55
- package/dist/scripts/done-job.js +12 -17
- package/dist/scripts/init.js +3 -5
- package/dist/scripts/lib/adapters/linear-adapter.js +2 -2
- package/dist/scripts/lib/adapters/trello-adapter.js +3 -3
- package/dist/scripts/lib/adapters/types.d.ts +1 -1
- package/dist/scripts/lib/config-builder.d.ts +1 -1
- package/dist/scripts/lib/config-builder.js +2 -2
- package/dist/scripts/lib/done/linear-handler.js +2 -2
- package/dist/scripts/lib/done/trello-handler.js +2 -2
- package/dist/scripts/lib/done/types.d.ts +1 -1
- package/dist/scripts/lib/files.d.ts +4 -0
- package/dist/scripts/lib/files.js +18 -0
- package/dist/scripts/lib/git.d.ts +1 -1
- package/dist/scripts/lib/git.js +3 -3
- package/dist/scripts/lib/init/args.js +9 -7
- package/dist/scripts/lib/init/file-ops.js +3 -5
- package/dist/scripts/lib/init/linear-init.js +5 -4
- package/dist/scripts/lib/init/linear-prompts.js +49 -75
- package/dist/scripts/lib/init/prompts.d.ts +1 -1
- package/dist/scripts/lib/init/prompts.js +20 -31
- package/dist/scripts/lib/init/trello-init.js +36 -33
- package/dist/scripts/lib/init/trello-prompts.js +3 -9
- package/dist/scripts/lib/init/types.d.ts +1 -1
- package/dist/scripts/lib/linear.js +2 -1
- package/dist/scripts/lib/status-helpers.d.ts +30 -0
- package/dist/scripts/lib/status-helpers.js +58 -0
- package/dist/scripts/show.js +1 -1
- package/dist/scripts/status.js +7 -2
- package/dist/scripts/sync.js +27 -12
- package/dist/scripts/utils.d.ts +2 -2
- package/dist/scripts/work-on.js +5 -7
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
//
|
|
13
|
-
const labelChoices =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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:
|
|
32
|
-
title: l.name,
|
|
33
|
-
value: l.name,
|
|
34
|
-
selected: localConfig.exclude_labels?.includes(l.name),
|
|
35
|
-
})),
|
|
30
|
+
choices: excludeLabelChoices,
|
|
36
31
|
});
|
|
37
|
-
localConfig.
|
|
32
|
+
localConfig.labels = selectedLabels.length > 0 ? selectedLabels : undefined;
|
|
38
33
|
localConfig.exclude_labels =
|
|
39
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
29
|
+
default: defaultTodo,
|
|
29
30
|
});
|
|
30
|
-
const
|
|
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
|
-
|
|
34
|
+
default: defaultInProgress,
|
|
36
35
|
});
|
|
37
|
-
const
|
|
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
|
-
|
|
39
|
+
default: defaultDone,
|
|
43
40
|
});
|
|
44
41
|
// Testing is optional
|
|
45
42
|
const testingChoices = [
|
|
46
|
-
{
|
|
43
|
+
{ name: "(None)", value: undefined },
|
|
47
44
|
...stateChoices,
|
|
48
45
|
];
|
|
49
|
-
const
|
|
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
|
-
|
|
55
|
-
? testingChoices.findIndex((c) => c.value === defaultTesting)
|
|
56
|
-
: 0,
|
|
49
|
+
default: defaultTesting,
|
|
57
50
|
});
|
|
58
51
|
const statusTransitions = {
|
|
59
|
-
todo:
|
|
60
|
-
in_progress:
|
|
61
|
-
done:
|
|
62
|
-
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
|
|
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
|
|
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
|
|
37
|
+
const _key = teamKeyMap.get(t.id) || "";
|
|
40
38
|
return {
|
|
41
|
-
|
|
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
|
|
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
|
-
{
|
|
54
|
+
{ name: "(Skip - no testing status)", value: undefined },
|
|
60
55
|
...devStates.map((s) => ({
|
|
61
|
-
|
|
56
|
+
name: `${s.name} (${s.type})`,
|
|
62
57
|
value: s.name,
|
|
63
58
|
})),
|
|
64
59
|
],
|
|
65
|
-
|
|
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
|
|
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
|
-
|
|
73
|
+
name: t.name,
|
|
87
74
|
value: t.id,
|
|
88
|
-
|
|
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
|
-
|
|
90
|
+
name: `${s.name} (${s.type})`,
|
|
106
91
|
value: s.name,
|
|
107
92
|
}));
|
|
108
|
-
const
|
|
109
|
-
type: "select",
|
|
110
|
-
name: "testingStatus",
|
|
93
|
+
const testingStatus = await select({
|
|
111
94
|
message: `Select testing status for ${team.name}:`,
|
|
112
95
|
choices: [
|
|
113
|
-
{
|
|
96
|
+
{ name: "(Skip this team)", value: undefined },
|
|
114
97
|
...stateChoices,
|
|
115
98
|
],
|
|
116
|
-
|
|
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 (
|
|
101
|
+
if (testingStatus) {
|
|
123
102
|
qaPmTeams.push({
|
|
124
103
|
team: teamKey,
|
|
125
|
-
testing_status:
|
|
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
|
|
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
|
-
|
|
117
|
+
name: "Simple",
|
|
148
118
|
value: "simple",
|
|
149
119
|
description: "Mark task as done directly",
|
|
150
120
|
},
|
|
151
121
|
{
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/scripts/done-job.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
60
|
+
name: `${t.id}: ${t.title}`,
|
|
61
61
|
value: t.id,
|
|
62
62
|
description: t.labels.join(", "),
|
|
63
63
|
}));
|
|
64
|
-
const
|
|
65
|
-
type: "select",
|
|
66
|
-
name: "issueId",
|
|
64
|
+
const selectedId = await select({
|
|
67
65
|
message: "選擇要完成的任務:",
|
|
68
66
|
choices: choices,
|
|
69
67
|
});
|
|
70
|
-
if (!
|
|
68
|
+
if (!selectedId) {
|
|
71
69
|
console.log("已取消");
|
|
72
70
|
process.exit(0);
|
|
73
71
|
}
|
|
74
|
-
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
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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 (
|
|
170
|
-
console.log(
|
|
164
|
+
if (promptMessage) {
|
|
165
|
+
console.log(`🔧 說明: ${promptMessage}`);
|
|
171
166
|
}
|
|
172
167
|
console.log(`\n🎉 任務完成!`);
|
|
173
168
|
}
|
package/dist/scripts/init.js
CHANGED
|
@@ -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
|
|
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
|
|
38
|
-
type: "confirm",
|
|
39
|
-
name: "proceed",
|
|
37
|
+
const proceed = await confirm({
|
|
40
38
|
message: "Update existing configuration?",
|
|
41
|
-
|
|
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.
|
|
89
|
-
filter.labels = { name: {
|
|
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
|
|
94
|
-
if (options.
|
|
95
|
-
if (!labelNames.
|
|
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
|
}
|
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 留言`);
|
|
@@ -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,
|
|
10
|
+
export declare function buildCompletionComment(commit: CommitInfo, promptMessage?: string): string;
|
package/dist/scripts/lib/git.js
CHANGED
|
@@ -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,
|
|
60
|
+
export function buildCompletionComment(commit, promptMessage) {
|
|
61
61
|
const commitLink = formatCommitLink(commit);
|
|
62
62
|
const commentParts = [
|
|
63
63
|
"## ✅ 開發完成",
|
|
64
64
|
"",
|
|
65
|
-
"###
|
|
66
|
-
|
|
65
|
+
"### 🔧 修復說明",
|
|
66
|
+
promptMessage || "_No description provided_",
|
|
67
67
|
"",
|
|
68
68
|
"### 📝 Commit Info",
|
|
69
69
|
`**Commit:** ${commitLink}`,
|