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.
@@ -0,0 +1 @@
1
+ export {};
@@ -1,33 +1,17 @@
1
- import {
2
- require_prompts
3
- } from "../cli-6rkvcjaj.js";
4
- import {
5
- getLinearClient,
6
- getPrioritySortIndex,
7
- getTeamId,
8
- getUserEmail,
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
- const args = process.argv.slice(2);
29
- if (args.includes("--help") || args.includes("-h")) {
30
- console.log(`Usage: ttt work-on [issue-id]
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
- process.exit(0);
41
- }
42
- let issueId = args[0];
43
- const config = await loadConfig();
44
- const data = await loadCycleData();
45
- if (!data) {
46
- console.error("No cycle data found. Run /sync-linear first.");
47
- process.exit(1);
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 choices = pendingTasks.map((task2) => ({
63
- title: `${PRIORITY_LABELS[task2.priority] || "⚪"} ${task2.id}: ${task2.title}`,
64
- value: task2.id,
65
- description: task2.labels.join(", ")
66
- }));
67
- const response = await import_prompts.default({
68
- type: "select",
69
- name: "issueId",
70
- message: "選擇要處理的任務:",
71
- choices
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
- if (!response.issueId) {
74
- console.log("已取消");
75
- process.exit(0);
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
- issueId = response.issueId;
78
- } else if (["next", "下一個", "下一個工作"].includes(issueId)) {
79
- if (pendingTasks.length === 0) {
80
- console.log("✅ 沒有待處理的任務,所有工作已完成或進行中");
81
- process.exit(0);
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
- issueId = pendingTasks[0].id;
84
- console.log(`Auto-selected: ${issueId}`);
85
- }
86
- const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
87
- if (!task) {
88
- console.error(`Issue ${issueId} not found in current cycle.`);
89
- process.exit(1);
90
- }
91
- if (task.localStatus === "in-progress") {
92
- console.log(`⚠️ 此任務 ${task.id} 已在進行中`);
93
- } else if (task.localStatus === "completed") {
94
- console.log(`⚠️ 此任務 ${task.id} 已完成`);
95
- process.exit(0);
96
- }
97
- if (task.localStatus === "pending") {
98
- task.localStatus = "in-progress";
99
- await saveCycleData(data);
100
- console.log(`Local: ${task.id} → in-progress`);
101
- if (task.linearId && process.env.LINEAR_API_KEY) {
102
- try {
103
- const client = getLinearClient();
104
- const workflowStates = await client.workflowStates({
105
- filter: { team: { id: { eq: getTeamId(config, localConfig.team) } } }
106
- });
107
- const inProgressState = workflowStates.nodes.find((s) => s.name === "In Progress");
108
- if (inProgressState) {
109
- await client.updateIssue(task.linearId, { stateId: inProgressState.id });
110
- console.log(`Linear: ${task.id} In Progress`);
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
- console.log(`
118
- ${"═".repeat(50)}`);
119
- console.log(`\uD83D\uDC77 ${task.id}: ${task.title}`);
120
- console.log(`${"".repeat(50)}`);
121
- console.log(`Priority: ${PRIORITY_LABELS[task.priority] || "None"}`);
122
- console.log(`Labels: ${task.labels.join(", ")}`);
123
- console.log(`Branch: ${task.branch || "N/A"}`);
124
- if (task.url)
125
- console.log(`URL: ${task.url}`);
126
- if (task.description) {
127
- console.log(`
128
- \uD83D\uDCDD Description:
129
- ${task.description}`);
130
- }
131
- if (task.attachments && task.attachments.length > 0) {
132
- console.log(`
133
- \uD83D\uDCCE Attachments:`);
134
- for (const att of task.attachments) {
135
- console.log(` - ${att.title}: ${att.url}`);
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
- console.log(`
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
- "name": "team-toon-tack",
3
- "version": "1.0.11",
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
- "package.json"
18
- ],
19
- "scripts": {
20
- "build": "bun build bin/cli.ts scripts/init.ts scripts/sync.ts scripts/work-on.ts scripts/done-job.ts scripts/utils.ts --outdir dist --target node --splitting",
21
- "prepublishOnly": "bun run build",
22
- "release": "npm config set //registry.npmjs.org/:_authToken=$NPM_PUBLISH_KEY && npm publish"
23
- },
24
- "dependencies": {
25
- "@linear/sdk": "^29.0.0",
26
- "@toon-format/toon": "^2.0.0",
27
- "prompts": "^2.4.2"
28
- },
29
- "devDependencies": {
30
- "@types/bun": "^1.2.0",
31
- "@types/prompts": "^2.4.9"
32
- },
33
- "keywords": [
34
- "linear",
35
- "task-management",
36
- "cli",
37
- "toon",
38
- "sync",
39
- "project-management"
40
- ],
41
- "author": "wayne930242",
42
- "license": "MIT",
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/wayne930242/team-toon-tack.git"
46
- },
47
- "engines": {
48
- "node": ">=18.0.0"
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
+ ```