team-toon-tack 1.0.11 → 1.6.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 +59 -8
- package/README.zh-TW.md +111 -22
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +87 -61
- package/dist/scripts/config.d.ts +2 -0
- package/dist/scripts/config.js +271 -0
- package/dist/scripts/done-job.d.ts +1 -0
- package/dist/scripts/done-job.js +215 -186
- package/dist/scripts/init.d.ts +2 -0
- package/dist/scripts/init.js +457 -278
- package/dist/scripts/status.d.ts +2 -0
- package/dist/scripts/status.js +251 -0
- package/dist/scripts/sync.d.ts +1 -0
- package/dist/scripts/sync.js +228 -152
- package/dist/scripts/utils.d.ts +109 -0
- package/dist/scripts/utils.js +116 -14766
- package/dist/scripts/work-on.d.ts +1 -0
- package/dist/scripts/work-on.js +132 -122
- package/package.json +52 -48
- package/templates/claude-code-commands/done-job.md +45 -0
- package/templates/claude-code-commands/sync-linear.md +32 -0
- package/templates/claude-code-commands/work-on.md +41 -0
- package/dist/cli-6rkvcjaj.js +0 -4923
- package/dist/cli-pyanjjwn.js +0 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/scripts/work-on.js
CHANGED
|
@@ -1,33 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
loadConfig,
|
|
10
|
-
loadCycleData,
|
|
11
|
-
loadLocalConfig,
|
|
12
|
-
saveCycleData
|
|
13
|
-
} from "./utils.js";
|
|
14
|
-
import {
|
|
15
|
-
__toESM
|
|
16
|
-
} from "../cli-pyanjjwn.js";
|
|
17
|
-
|
|
18
|
-
// scripts/work-on.ts
|
|
19
|
-
var import_prompts = __toESM(require_prompts(), 1);
|
|
20
|
-
var PRIORITY_LABELS = {
|
|
21
|
-
0: "⚪ None",
|
|
22
|
-
1: "\uD83D\uDD34 Urgent",
|
|
23
|
-
2: "\uD83D\uDFE0 High",
|
|
24
|
-
3: "\uD83D\uDFE1 Medium",
|
|
25
|
-
4: "\uD83D\uDFE2 Low"
|
|
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",
|
|
26
9
|
};
|
|
27
10
|
async function workOn() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
// Handle help flag
|
|
13
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
14
|
+
console.log(`Usage: ttt work-on [issue-id]
|
|
31
15
|
|
|
32
16
|
Arguments:
|
|
33
17
|
issue-id Issue ID (e.g., MP-624) or 'next' for auto-select
|
|
@@ -37,106 +21,132 @@ Examples:
|
|
|
37
21
|
ttt work-on # Interactive selection
|
|
38
22
|
ttt work-on MP-624 # Work on specific issue
|
|
39
23
|
ttt work-on next # Auto-select highest priority`);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
const userEmail = await getUserEmail();
|
|
50
|
-
const localConfig = await loadLocalConfig();
|
|
51
|
-
const excludedEmails = new Set((localConfig.exclude_assignees ?? []).map((key) => config.users[key]?.email).filter(Boolean));
|
|
52
|
-
const pendingTasks = data.tasks.filter((t) => t.localStatus === "pending" && !excludedEmails.has(t.assignee ?? "")).sort((a, b) => {
|
|
53
|
-
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
54
|
-
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
55
|
-
return pa - pb;
|
|
56
|
-
});
|
|
57
|
-
if (!issueId) {
|
|
58
|
-
if (pendingTasks.length === 0) {
|
|
59
|
-
console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
|
|
60
|
-
process.exit(0);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
let issueId = args[0];
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
const data = await loadCycleData();
|
|
29
|
+
if (!data) {
|
|
30
|
+
console.error("No cycle data found. Run /sync-linear first.");
|
|
31
|
+
process.exit(1);
|
|
61
32
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
33
|
+
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));
|
|
38
|
+
const pendingTasks = data.tasks
|
|
39
|
+
.filter((t) => t.localStatus === "pending" && !excludedEmails.has(t.assignee ?? ""))
|
|
40
|
+
.sort((a, b) => {
|
|
41
|
+
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
42
|
+
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
43
|
+
return pa - pb;
|
|
72
44
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
45
|
+
// Phase 0: Issue Resolution
|
|
46
|
+
if (!issueId) {
|
|
47
|
+
// Interactive selection
|
|
48
|
+
if (pendingTasks.length === 0) {
|
|
49
|
+
console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
const choices = pendingTasks.map((task) => ({
|
|
53
|
+
title: `${PRIORITY_LABELS[task.priority] || "⚪"} ${task.id}: ${task.title}`,
|
|
54
|
+
value: task.id,
|
|
55
|
+
description: task.labels.join(", "),
|
|
56
|
+
}));
|
|
57
|
+
const response = await prompts({
|
|
58
|
+
type: "select",
|
|
59
|
+
name: "issueId",
|
|
60
|
+
message: "選擇要處理的任務:",
|
|
61
|
+
choices: choices,
|
|
62
|
+
});
|
|
63
|
+
if (!response.issueId) {
|
|
64
|
+
console.log("已取消");
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
issueId = response.issueId;
|
|
76
68
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
else if (["next", "下一個", "下一個工作"].includes(issueId)) {
|
|
70
|
+
// Auto-select highest priority
|
|
71
|
+
if (pendingTasks.length === 0) {
|
|
72
|
+
console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
issueId = pendingTasks[0].id;
|
|
76
|
+
console.log(`Auto-selected: ${issueId}`);
|
|
82
77
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
task.localStatus
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
78
|
+
// Phase 1: Find task
|
|
79
|
+
const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
|
|
80
|
+
if (!task) {
|
|
81
|
+
console.error(`Issue ${issueId} not found in current cycle.`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// Phase 2: Availability Check
|
|
85
|
+
if (task.localStatus === "in-progress") {
|
|
86
|
+
console.log(`⚠️ 此任務 ${task.id} 已在進行中`);
|
|
87
|
+
}
|
|
88
|
+
else if (task.localStatus === "completed") {
|
|
89
|
+
console.log(`⚠️ 此任務 ${task.id} 已完成`);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
// Phase 3: Mark as In Progress
|
|
93
|
+
if (task.localStatus === "pending") {
|
|
94
|
+
task.localStatus = "in-progress";
|
|
95
|
+
await saveCycleData(data);
|
|
96
|
+
console.log(`Local: ${task.id} → in-progress`);
|
|
97
|
+
// Update Linear using stored linearId
|
|
98
|
+
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
|
+
}
|
|
111
117
|
}
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error("Failed to update Linear:", e);
|
|
114
|
-
}
|
|
115
118
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
${
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
${"─".repeat(50)}`);
|
|
140
|
-
console.log("Next: bun type-check && bun lint");
|
|
149
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
150
|
+
console.log("Next: bun type-check && bun lint");
|
|
141
151
|
}
|
|
142
152
|
workOn().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "team-toon-tack",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Linear task sync & management CLI with TOON format",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ttt": "./dist/bin/cli.js",
|
|
8
|
+
"team-toon-tack": "./dist/bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/scripts/utils.js",
|
|
12
|
+
"./utils": "./dist/scripts/utils.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"templates"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"lint": "biome lint .",
|
|
21
|
+
"format": "biome format --write .",
|
|
22
|
+
"type": "tsc --noEmit",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"release": "npm config set //registry.npmjs.org/:_authToken=$NPM_PUBLISH_KEY && npm publish"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@linear/sdk": "^69.0.0",
|
|
28
|
+
"@toon-format/toon": "^2.1.0",
|
|
29
|
+
"prompts": "^2.4.2"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@biomejs/biome": "^2.3.11",
|
|
33
|
+
"@types/node": "^25.0.3",
|
|
34
|
+
"@types/prompts": "^2.4.9",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"linear",
|
|
39
|
+
"task-management",
|
|
40
|
+
"cli",
|
|
41
|
+
"toon",
|
|
42
|
+
"sync",
|
|
43
|
+
"project-management"
|
|
44
|
+
],
|
|
45
|
+
"author": "wayne930242",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/wayne930242/team-toon-tack.git"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
50
54
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: done-job
|
|
3
|
+
description: Mark a Linear issue as done with AI summary comment
|
|
4
|
+
arguments:
|
|
5
|
+
- name: issue-id
|
|
6
|
+
description: Linear issue ID (e.g., MP-624). Optional if only one task is in-progress
|
|
7
|
+
required: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Complete Task
|
|
11
|
+
|
|
12
|
+
Mark a task as done and update Linear with commit details.
|
|
13
|
+
|
|
14
|
+
## Process
|
|
15
|
+
|
|
16
|
+
### 1. Determine Issue ID
|
|
17
|
+
|
|
18
|
+
Check `.toon/cycle.toon` for tasks with `localStatus: in-progress`.
|
|
19
|
+
|
|
20
|
+
### 2. Write Fix Summary
|
|
21
|
+
|
|
22
|
+
Prepare a concise summary (1-3 sentences) covering:
|
|
23
|
+
- Root cause
|
|
24
|
+
- How it was resolved
|
|
25
|
+
- Key code changes
|
|
26
|
+
|
|
27
|
+
### 3. Run Command
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ttt done -d .ttt $ARGUMENTS -m "修復說明"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What It Does
|
|
34
|
+
|
|
35
|
+
- Linear issue status → "Done"
|
|
36
|
+
- Adds comment with commit hash, message, and diff summary
|
|
37
|
+
- Parent issue (if exists) → "Testing"
|
|
38
|
+
- Local status → `completed` in `.toon/cycle.toon`
|
|
39
|
+
|
|
40
|
+
## Example Usage
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
/done-job MP-624
|
|
44
|
+
/done-job
|
|
45
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-linear
|
|
3
|
+
description: Sync Linear Frontend issues to local TOON file
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sync Linear Issues
|
|
7
|
+
|
|
8
|
+
Fetch current cycle's Frontend issues from Linear to `.toon/cycle.toon`.
|
|
9
|
+
|
|
10
|
+
## Process
|
|
11
|
+
|
|
12
|
+
### 1. Run Sync
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
ttt sync -d .ttt
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Review Output
|
|
19
|
+
|
|
20
|
+
Script displays a summary of tasks in the current cycle.
|
|
21
|
+
|
|
22
|
+
## When to Use
|
|
23
|
+
|
|
24
|
+
- Before starting a new work session
|
|
25
|
+
- When task list is missing or outdated
|
|
26
|
+
- After issues are updated in Linear
|
|
27
|
+
|
|
28
|
+
## Example Usage
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/sync-linear
|
|
32
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: work-on
|
|
3
|
+
description: Select and start working on a Linear issue
|
|
4
|
+
arguments:
|
|
5
|
+
- name: issue-id
|
|
6
|
+
description: "Issue ID (e.g., MP-624) or 'next' for auto-select"
|
|
7
|
+
required: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Start Working on Issue
|
|
11
|
+
|
|
12
|
+
Select a task and update status to "In Progress" on both local and Linear.
|
|
13
|
+
|
|
14
|
+
## Process
|
|
15
|
+
|
|
16
|
+
### 1. Run Command
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
ttt work-on -d .ttt $ARGUMENTS
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Review Issue Details
|
|
23
|
+
|
|
24
|
+
Script displays title, description, priority, labels, and attachments.
|
|
25
|
+
|
|
26
|
+
### 3. Implement
|
|
27
|
+
|
|
28
|
+
1. Read the issue description carefully
|
|
29
|
+
2. Explore related code
|
|
30
|
+
3. Implement the fix/feature
|
|
31
|
+
4. Run `bun type-check && bun lint`
|
|
32
|
+
5. Commit with conventional format
|
|
33
|
+
6. Use `/done-job` to complete
|
|
34
|
+
|
|
35
|
+
## Example Usage
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
/work-on
|
|
39
|
+
/work-on MP-624
|
|
40
|
+
/work-on next
|
|
41
|
+
```
|