team-toon-tack 1.6.3 → 1.7.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.
package/README.md CHANGED
@@ -2,54 +2,68 @@
2
2
 
3
3
  [繁體中文](./README.zh-TW.md) | English
4
4
 
5
- CLI tool for syncing and managing Linear issues with local TOON format.
5
+ Optimized Linear workflow for Claude Code save 50%+ tokens compared to MCP.
6
6
 
7
- ## Installation
7
+ ## Features
8
+
9
+ - **Token Efficient** — Local cycle cache eliminates repeated API calls, saving 50%+ tokens vs Linear MCP
10
+ - **Smart Task Selection** — Auto-pick highest priority unassigned work with `/work-on next`
11
+ - **Multi-team Support** — Sync and filter issues across multiple teams
12
+ - **Cycle History** — Local `.toon` files preserve cycle data for AI context
13
+ - **User Filtering** — Only see issues assigned to you or unassigned
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Install & Initialize
8
18
 
9
19
  ```bash
10
- # npm (recommended)
11
20
  npm install -g team-toon-tack
21
+ export LINEAR_API_KEY="lin_api_xxxxx"
12
22
 
13
- # Or with bun
14
- bun add -g team-toon-tack
23
+ cd your-project
24
+ ttt init
25
+ ```
15
26
 
16
- # Or use npx/bunx without installing
17
- npx team-toon-tack <command>
18
- bunx team-toon-tack <command>
27
+ ### 2. Setup Claude Code Commands
19
28
 
20
- # Short alias after global install
21
- ttt <command>
29
+ ```bash
30
+ cp -r /path/to/team-toon-tack/templates/claude-code-commands/* .claude/commands/
22
31
  ```
23
32
 
24
- ## Quick Start
33
+ Edit `.claude/commands/work-on.md` lines 37-40 to add your project's verification steps:
25
34
 
26
35
  ```bash
27
- # 1. Set your Linear API key
28
- export LINEAR_API_KEY="lin_api_xxxxx"
36
+ # Example: Add your checks here
37
+ bun run typecheck
38
+ bun run lint
39
+ bun run test
40
+ ```
29
41
 
30
- # 2. Initialize in your project
31
- cd your-project
32
- ttt init
42
+ ### 3. Daily Workflow
33
43
 
34
- # 3. Sync issues from Linear
35
- ttt sync
44
+ In Claude Code:
36
45
 
37
- # 4. Start working
38
- ttt work-on
39
46
  ```
47
+ /sync # Fetch all Linear issues for current cycle
48
+ /work-on next # Pick highest priority task & start working
49
+ /done-job # Complete task with AI-generated summary
50
+ ```
51
+
52
+ That's it.
40
53
 
41
- ## Commands
54
+ ---
55
+
56
+ ## CLI Reference
42
57
 
43
58
  ### `ttt init`
44
59
 
45
- Initialize configuration files in current directory.
60
+ Initialize configuration in current directory.
46
61
 
47
62
  ```bash
48
63
  ttt init # Interactive mode
49
64
  ttt init --user alice@example.com # Pre-select user
50
65
  ttt init --label Frontend # Set default label
51
66
  ttt init --force # Overwrite existing config
52
- ttt init -y # Non-interactive mode
53
67
  ```
54
68
 
55
69
  ### `ttt sync`
@@ -58,7 +72,7 @@ Sync current cycle issues from Linear.
58
72
 
59
73
  ```bash
60
74
  ttt sync # Sync all matching issues
61
- ttt sync MP-123 # Sync only this specific issue
75
+ ttt sync MP-123 # Sync specific issue only
62
76
  ```
63
77
 
64
78
  ### `ttt work-on`
@@ -90,11 +104,8 @@ ttt status # Show current in-progress task
90
104
  ttt status MP-123 # Show specific issue status
91
105
  ttt status MP-123 --set +1 # Move to next status
92
106
  ttt status MP-123 --set done # Mark as done
93
- ttt status --set pending # Reset current task
94
107
  ```
95
108
 
96
- Status options: `+1`, `-1`, `+2`, `-2`, `pending`, `in-progress`, `completed`, `blocked`, `todo`, `done`, `testing`
97
-
98
109
  ### `ttt config`
99
110
 
100
111
  Configure settings.
@@ -110,130 +121,20 @@ ttt config teams # Configure multi-team selection
110
121
 
111
122
  ### Directory Structure
112
123
 
113
- After `ttt init`, your project will have:
114
-
115
124
  ```
116
125
  your-project/
117
- └── .ttt/ # Config directory
118
- ├── config.toon # Team configuration (gitignore recommended)
119
- ├── local.toon # Personal settings (gitignore)
120
- └── cycle.toon # Current cycle data (gitignore, auto-generated)
121
- ```
122
-
123
- ### Custom Config Directory
124
-
125
- ```bash
126
- # Use -d flag
127
- ttt sync -d ./team
128
-
129
- # Or set environment variable
130
- export TOON_DIR=./team
131
- ttt sync
126
+ └── .ttt/
127
+ ├── config.toon # Team config (gitignore recommended)
128
+ ├── local.toon # Personal settings (gitignore)
129
+ └── cycle.toon # Current cycle data (auto-generated)
132
130
  ```
133
131
 
134
- ### config.toon
135
-
136
- Team-wide configuration (fetched from Linear):
137
-
138
- ```toon
139
- teams:
140
- main:
141
- id: TEAM_UUID
142
- name: Team Name
143
-
144
- users:
145
- alice:
146
- id: USER_UUID
147
- email: alice@example.com
148
- displayName: Alice
149
-
150
- labels:
151
- frontend:
152
- id: LABEL_UUID
153
- name: Frontend
154
-
155
- current_cycle:
156
- id: CYCLE_UUID
157
- name: Cycle #1
158
- ```
159
-
160
- ### local.toon
161
-
162
- Personal settings:
163
-
164
- ```toon
165
- current_user: alice
166
- team: frontend
167
- teams[2]:
168
- - frontend
169
- - backend
170
- label: Frontend
171
- exclude_labels[1]:
172
- - Bug
173
- exclude_assignees[1]:
174
- - bob
175
- ```
176
-
177
- | Field | Description |
178
- |-------|-------------|
179
- | `current_user` | Your user key from config.toon |
180
- | `team` | Primary team key |
181
- | `teams` | Multiple teams to sync (optional) |
182
- | `label` | Filter issues by label (optional) |
183
- | `exclude_labels` | Exclude issues with these labels (optional) |
184
- | `exclude_assignees` | Hide issues from these users (optional) |
185
-
186
- ### config.toon - Status Mappings
187
-
188
- Configure which Linear statuses map to local states:
189
-
190
- ```toon
191
- status_transitions:
192
- todo: Todo
193
- in_progress: In Progress
194
- done: Done
195
- testing: Testing
196
- ```
197
-
198
- Run `ttt config status` to configure interactively.
199
-
200
- ## Environment Variables
132
+ ### Environment Variables
201
133
 
202
134
  | Variable | Description |
203
135
  |----------|-------------|
204
136
  | `LINEAR_API_KEY` | **Required.** Your Linear API key |
205
- | `TOON_DIR` | Config directory (default: current directory) |
206
-
207
- ## Integration Examples
208
-
209
- ### With Claude Code
210
-
211
- ```yaml
212
- # .claude/commands/sync.md
213
- ---
214
- description: Sync Linear issues
215
- ---
216
- ttt sync -d team
217
- ```
218
-
219
- ### As Git Submodule
220
-
221
- ```bash
222
- # Add config directory as submodule
223
- git submodule add https://github.com/your-org/team-config.git team
224
- cd team && ttt sync
225
- ```
226
-
227
- ### In package.json
228
-
229
- ```json
230
- {
231
- "scripts": {
232
- "sync": "ttt sync -d team",
233
- "work": "ttt work-on -d team"
234
- }
235
- }
236
- ```
137
+ | `TOON_DIR` | Config directory (default: `.ttt`) |
237
138
 
238
139
  ## License
239
140
 
package/README.zh-TW.md CHANGED
@@ -2,405 +2,77 @@
2
2
 
3
3
  繁體中文 | [English](./README.md)
4
4
 
5
- 使用 TOON 格式同步與管理 Linear 任務的 CLI 工具。
5
+ Claude Code 最佳化的 Linear 工作流 比 MCP 節省 50% 以上的 token。
6
6
 
7
- ## 為什麼需要這個工具?
7
+ ## 特色功能
8
8
 
9
- 在使用 Linear 管理專案任務時,常見的痛點:
9
+ - **節省 Token** — 本地 cycle 快取避免重複 API 呼叫,比 Linear MCP 省 50%+ token
10
+ - **智慧任務挑選** — `/work-on next` 自動選擇最高優先級的未指派工作
11
+ - **多團隊支援** — 跨多個團隊同步與過濾 issue
12
+ - **Cycle 歷史保存** — 本地 `.toon` 檔案保留 cycle 資料,方便 AI 檢閱
13
+ - **使用者過濾** — 只顯示指派給你或未指派的工作
10
14
 
11
- - **AI 助手整合困難**:Claude Code 等 AI 工具無法直接讀取 Linear 的任務上下文
12
- - **狀態同步繁瑣**:手動在 Linear 和本地之間切換更新狀態
13
- - **團隊協作不透明**:難以追蹤誰在做什麼、進度如何
14
-
15
- **team-toon-tack** 解決這些問題:將 Linear 任務同步到本地 TOON 檔案,讓 AI 助手能讀取任務內容,並自動同步狀態變更。
16
-
17
- ## 運作原理
18
-
19
- ```
20
- ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
21
- │ Linear │────▶│ ttt sync │────▶│ cycle.toon │
22
- │ (雲端) │ │ │ │ (本地) │
23
- └─────────────┘ └──────────────┘ └─────────────┘
24
-
25
-
26
- ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
27
- │ Linear │◀────│ ttt done │◀────│ Claude Code │
28
- │ 狀態更新 │ │ ttt work-on │ │ 讀取任務 │
29
- └─────────────┘ └──────────────┘ └─────────────┘
30
- ```
31
-
32
- ### 核心流程
33
-
34
- 1. **同步 (sync)**
35
- - 從 Linear API 抓取當前 Cycle 的任務
36
- - 根據 `local.ttt` 設定過濾(標籤、排除指派人)
37
- - 寫入 `cycle.toon`,包含完整任務資訊
38
-
39
- 2. **開始任務 (work-on)**
40
- - 讀取 `cycle.toon` 中的待處理任務
41
- - 更新本地狀態為 `in-progress`
42
- - 同步更新 Linear 狀態為 "In Progress"
43
-
44
- 3. **完成任務 (done)**
45
- - 更新本地狀態為 `completed`
46
- - 同步更新 Linear 狀態為 "Done"
47
- - 自動在 Linear 新增完成留言(含 commit 資訊)
48
- - 若有父任務,自動更新為 "Testing"
49
-
50
- ### 檔案結構與用途
51
-
52
- ```
53
- .ttt/ # 配置目錄(建議 gitignore)
54
- ├── config.toon # 團隊配置
55
- │ ├── teams # Linear 團隊 ID 映射
56
- │ ├── users # 成員 ID/email 映射
57
- │ ├── labels # 標籤 ID 映射
58
- │ ├── status_transitions # 狀態映射配置
59
- │ └── current_cycle # 當前 Cycle 資訊
60
-
61
- ├── local.toon # 個人設定(必須 gitignore)
62
- │ ├── current_user # 你的 user key
63
- │ ├── team # 主要團隊
64
- │ ├── teams # 多團隊同步(可選)
65
- │ ├── label # 過濾標籤(可選)
66
- │ ├── exclude_labels # 排除的標籤
67
- │ └── exclude_assignees # 排除的指派人
68
-
69
- └── cycle.toon # 任務資料(自動產生)
70
- ├── cycleId # Cycle UUID
71
- ├── cycleName # Cycle 名稱
72
- ├── updatedAt # 最後同步時間
73
- └── tasks[] # 任務列表
74
- ├── id # 任務編號 (MP-123)
75
- ├── linearId # Linear UUID
76
- ├── title # 標題
77
- ├── description # 描述(Markdown)
78
- ├── status # Linear 狀態
79
- ├── localStatus # 本地狀態
80
- ├── priority # 優先級 (1=Urgent, 4=Low)
81
- ├── labels # 標籤列表
82
- ├── branch # Git 分支名
83
- ├── attachments # 附件列表
84
- └── comments # 留言列表
85
- ```
15
+ ## 快速開始
86
16
 
87
- ## 安裝
17
+ ### 1. 安裝與初始化
88
18
 
89
19
  ```bash
90
- # npm(推薦)
91
20
  npm install -g team-toon-tack
92
-
93
- # 或用 bun
94
- bun add -g team-toon-tack
95
- ```
96
-
97
- ## 快速開始
98
-
99
- ```bash
100
- # 1. 設定 Linear API 金鑰
101
21
  export LINEAR_API_KEY="lin_api_xxxxx"
102
22
 
103
- # 2. 初始化(會從 Linear 抓取團隊資料)
104
- mkdir .ttt && cd .ttt
23
+ cd your-project
105
24
  ttt init
106
-
107
- # 3. 同步任務
108
- ttt sync
109
-
110
- # 4. 開始工作
111
- ttt work-on
112
25
  ```
113
26
 
114
- ## 使用情境
115
-
116
- ### 情境 1:每日開工流程
27
+ ### 2. 設定 Claude Code Commands
117
28
 
118
29
  ```bash
119
- # 早上開始工作前,同步最新任務
120
- ttt sync -d .ttt
121
-
122
- # 查看待處理任務並選擇一個開始
123
- ttt work-on -d .ttt
124
-
125
- # Claude Code 現在可以讀取任務內容
126
- # 在 .ttt/cycle.toon 中找到任務描述、附件等
127
- ```
128
-
129
- ### 情境 2:搭配 Claude Code 自動化
130
-
131
- 建立以下三個 slash command 檔案:
132
-
133
- #### `.claude/commands/sync-linear.md`
134
-
135
- ```markdown
136
- ---
137
- name: sync-linear
138
- description: Sync Linear issues to local TOON file
139
- ---
140
-
141
- # Sync Linear Issues
142
-
143
- Fetch current cycle's issues from Linear to `.ttt/cycle.toon`.
144
-
145
- ## Process
146
-
147
- ### 1. Run Sync
148
-
149
- \`\`\`bash
150
- ttt sync -d .ttt
151
- \`\`\`
152
-
153
- ### 2. Review Output
154
-
155
- Script displays a summary of tasks in the current cycle.
156
-
157
- ## When to Use
158
-
159
- - Before starting a new work session
160
- - When task list is missing or outdated
161
- - After issues are updated in Linear
162
- ```
163
-
164
- #### `.claude/commands/work-on.md`
165
-
166
- ```markdown
167
- ---
168
- name: work-on
169
- description: Select and start working on a Linear issue
170
- arguments:
171
- - name: issue-id
172
- description: "Issue ID (e.g., MP-624) or 'next' for auto-select"
173
- required: false
174
- ---
175
-
176
- # Start Working on Issue
177
-
178
- Select a task and update status to "In Progress" on both local and Linear.
179
-
180
- ## Process
181
-
182
- ### 1. Run Command
183
-
184
- \`\`\`bash
185
- ttt work-on -d .ttt $ARGUMENTS
186
- \`\`\`
187
-
188
- ### 2. Review Issue Details
189
-
190
- Script displays title, description, priority, labels, and attachments.
191
-
192
- ### 3. Implement
193
-
194
- 1. Read the issue description carefully
195
- 2. Explore related code
196
- 3. Implement the fix/feature
197
- 4. Run validation commands
198
- 5. Commit with conventional format
199
- 6. Use `/done-job` to complete
200
- ```
201
-
202
- #### `.claude/commands/done-job.md`
203
-
204
- ```markdown
205
- ---
206
- name: done-job
207
- description: Mark a Linear issue as done with AI summary comment
208
- arguments:
209
- - name: issue-id
210
- description: Linear issue ID (e.g., MP-624). Optional if only one task is in-progress
211
- required: false
212
- ---
213
-
214
- # Complete Task
215
-
216
- Mark a task as done and update Linear with commit details.
217
-
218
- ## Process
219
-
220
- ### 1. Determine Issue ID
221
-
222
- Check `.ttt/cycle.toon` for tasks with `localStatus: in-progress`.
223
-
224
- ### 2. Write Fix Summary
225
-
226
- Prepare a concise summary (1-3 sentences) covering:
227
- - Root cause
228
- - How it was resolved
229
- - Key code changes
230
-
231
- ### 3. Run Command
232
-
233
- \`\`\`bash
234
- ttt done -d .ttt $ARGUMENTS -m "修復說明"
235
- \`\`\`
236
-
237
- ## What It Does
238
-
239
- - Linear issue status → "Done"
240
- - Adds comment with commit hash, message, and diff summary
241
- - Parent issue (if exists) → "Testing"
242
- - Local status → `completed` in `.ttt/cycle.toon`
30
+ cp -r /path/to/team-toon-tack/templates/claude-code-commands/* .claude/commands/
243
31
  ```
244
32
 
245
- #### 使用方式
246
-
247
- ```
248
- /sync-linear # 同步任務
249
- /work-on # 互動選擇任務
250
- /work-on MP-624 # 指定任務
251
- /work-on next # 自動選最高優先級
252
- /done-job # 完成當前任務
253
- /done-job MP-624 # 完成指定任務
254
- ```
255
-
256
- Claude Code 會自動:
257
- - 執行 `ttt work-on` 開始任務
258
- - 讀取任務描述和附件
259
- - 根據需求實作功能
260
- - 執行 `ttt done` 更新狀態並留言
261
-
262
- ### 情境 3:完成任務並自動留言
33
+ 編輯 `.claude/commands/work-on.md` 第 37-40 行,加入你專案的驗證步驟:
263
34
 
264
35
  ```bash
265
- # 完成開發後
266
- git add . && git commit -m "feat: implement feature X"
267
-
268
- # 標記任務完成,會自動在 Linear 新增留言
269
- ttt done -d .ttt -m "實作了 X 功能,修改了 Y 元件"
36
+ # 範例:加入你的檢查
37
+ bun run typecheck
38
+ bun run lint
39
+ bun run test
270
40
  ```
271
41
 
272
- Linear 上會自動新增留言:
273
- ```markdown
274
- ## ✅ 開發完成
275
-
276
- ### 🤖 AI 修復說明
277
- 實作了 X 功能,修改了 Y 元件
278
-
279
- ### 📝 Commit Info
280
- **Commit:** [abc1234](https://github.com/...)
281
- **Message:** feat: implement feature X
42
+ ### 3. 每日工作流
282
43
 
283
- ### 📊 Changes
284
- src/components/X.vue | 50 +++
285
- src/utils/Y.ts | 20 +-
286
- 2 files changed, 60 insertions(+), 10 deletions(-)
287
- ```
44
+ Claude Code 中:
288
45
 
289
- ### 情境 4:團隊協作過濾
290
-
291
- 前端工程師只想看前端任務:
292
- ```toon
293
- # local.toon
294
- current_user: alice
295
- team: frontend
296
- label: Frontend
297
- exclude_labels[1]:
298
- - Bug
299
- exclude_assignees[2]:
300
- - bob # 排除後端同事的任務
301
- - charlie
302
46
  ```
303
-
304
- 後端工程師的設定(多團隊支援):
305
- ```toon
306
- # local.toon
307
- current_user: bob
308
- team: backend
309
- teams[2]:
310
- - backend
311
- - devops
312
- label: Backend
47
+ /sync # 從 Linear 取得當前 cycle 所有 issue
48
+ /work-on next # 挑選最高優先級任務並開始工作
49
+ /done-job # 完成任務,附上 AI 生成的摘要
313
50
  ```
314
51
 
315
- ### 情境 5:狀態管理
316
-
317
- ```bash
318
- # 查看當前進行中任務的詳細資訊
319
- ttt status
320
-
321
- # 查看特定任務狀態
322
- ttt status MP-624
323
-
324
- # 快速移動狀態
325
- ttt status MP-624 --set +1 # 移動到下一狀態
326
-
327
- # 配置 Linear 狀態映射
328
- ttt config status # 互動式配置
52
+ 就這樣。
329
53
 
330
- # 配置過濾器
331
- ttt config filters # 設定 exclude_labels, exclude_assignees
332
- ```
333
-
334
- ### 情境 6:多專案管理
335
-
336
- ```bash
337
- # 專案 A
338
- cd project-a
339
- ttt sync -d .ttt
340
-
341
- # 專案 B(不同 Linear 團隊)
342
- cd ../project-b
343
- ttt init -d .ttt # 初始化不同的配置
344
- ttt sync -d .ttt
345
- ```
346
-
347
- ### 情境 7:CI/CD 整合
348
-
349
- ```yaml
350
- # .github/workflows/sync.yml
351
- name: Sync Linear Tasks
352
- on:
353
- schedule:
354
- - cron: '0 9 * * 1-5' # 週一到週五早上 9 點
355
- jobs:
356
- sync:
357
- runs-on: ubuntu-latest
358
- steps:
359
- - uses: actions/checkout@v4
360
- - run: npm install -g team-toon-tack
361
- - run: ttt sync -d .ttt
362
- env:
363
- LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
364
- - run: |
365
- git add .ttt/cycle.toon
366
- git commit -m "chore: sync linear tasks" || true
367
- git push
368
- ```
54
+ ---
369
55
 
370
- ## 指令參考
56
+ ## CLI 參考
371
57
 
372
58
  ### `ttt init`
373
59
 
374
- 初始化配置檔,從 Linear 抓取團隊資料。
60
+ 在當前目錄初始化配置。
375
61
 
376
62
  ```bash
377
- ttt init [options]
378
-
379
- 選項:
380
- -d, --dir <path> 配置目錄(預設:當前目錄)
381
- -k, --api-key <key> Linear API 金鑰
382
- -u, --user <email> 預選使用者
383
- -l, --label <name> 預設標籤過濾
384
- -f, --force 覆蓋現有配置
385
- -y, --yes 非互動模式
63
+ ttt init # 互動模式
64
+ ttt init --user alice@example.com # 預選使用者
65
+ ttt init --label Frontend # 設定預設標籤
66
+ ttt init --force # 覆蓋現有配置
386
67
  ```
387
68
 
388
69
  ### `ttt sync`
389
70
 
390
- 從 Linear 同步任務到本地。
71
+ 從 Linear 同步當前 cycle 的 issue。
391
72
 
392
73
  ```bash
393
- ttt sync [issue-id] [options]
394
-
395
- 參數:
396
- issue-id 可選。只同步此特定任務(如 MP-624)
397
-
398
- 選項:
399
- -d, --dir <path> 配置目錄
400
-
401
- 範例:
402
- ttt sync # 同步所有符合條件的任務
403
- ttt sync MP-624 # 只同步此特定任務
74
+ ttt sync # 同步所有符合條件的 issue
75
+ ttt sync MP-123 # 只同步特定 issue
404
76
  ```
405
77
 
406
78
  ### `ttt work-on`
@@ -408,13 +80,9 @@ ttt sync [issue-id] [options]
408
80
  開始處理任務。
409
81
 
410
82
  ```bash
411
- ttt work-on [issue-id] [options]
412
-
413
- 參數:
414
- issue-id 任務編號(如 MP-624)或 "next"
415
-
416
- 選項:
417
- -d, --dir <path> 配置目錄
83
+ ttt work-on # 互動選擇
84
+ ttt work-on MP-123 # 指定 issue
85
+ ttt work-on next # 自動選擇最高優先級
418
86
  ```
419
87
 
420
88
  ### `ttt done`
@@ -422,14 +90,9 @@ ttt work-on [issue-id] [options]
422
90
  標記任務完成。
423
91
 
424
92
  ```bash
425
- ttt done [issue-id] [options]
426
-
427
- 參數:
428
- issue-id 任務編號(可選)
429
-
430
- 選項:
431
- -d, --dir <path> 配置目錄
432
- -m, --message <msg> 完成說明
93
+ ttt done # 若只有一個進行中,自動選擇
94
+ ttt done MP-123 # 指定 issue
95
+ ttt done -m "修復了錯誤" # 附上完成說明
433
96
  ```
434
97
 
435
98
  ### `ttt status`
@@ -437,27 +100,10 @@ ttt done [issue-id] [options]
437
100
  顯示或修改任務狀態。
438
101
 
439
102
  ```bash
440
- ttt status [issue-id] [--set <status>]
441
-
442
- 參數:
443
- issue-id 任務編號(可選,預設顯示進行中任務)
444
-
445
- 選項:
446
- -s, --set <status> 設定狀態
447
-
448
- 狀態選項:
449
- +1, -1, +2, -2 相對移動狀態
450
- pending 設為待處理
451
- in-progress 設為進行中
452
- completed 設為已完成
453
- blocked 設為後端阻擋
454
- todo, done, testing 設定 Linear 狀態
455
-
456
- 範例:
457
- ttt status # 顯示當前進行中任務
458
- ttt status MP-624 # 顯示特定任務狀態
459
- ttt status MP-624 --set +1 # 移動到下一狀態
460
- ttt status MP-624 --set done # 標記為完成
103
+ ttt status # 顯示當前進行中的任務
104
+ ttt status MP-123 # 顯示特定 issue 狀態
105
+ ttt status MP-123 --set +1 # 移動到下一狀態
106
+ ttt status MP-123 --set done # 標記為完成
461
107
  ```
462
108
 
463
109
  ### `ttt config`
@@ -465,60 +111,30 @@ ttt status [issue-id] [--set <status>]
465
111
  配置設定。
466
112
 
467
113
  ```bash
468
- ttt config [subcommand]
469
-
470
- 子指令:
471
- show 顯示當前配置(預設)
472
- status 配置狀態映射(todo, in_progress, done, testing)
473
- filters 配置過濾器(label, exclude_labels, exclude_assignees)
474
- teams 配置團隊選擇(多團隊支援)
475
-
476
- 範例:
477
- ttt config # 顯示當前配置
478
- ttt config status # 配置狀態映射
479
- ttt config filters # 配置過濾器
480
- ttt config teams # 配置團隊選擇
114
+ ttt config # 顯示當前配置
115
+ ttt config status # 配置狀態映射
116
+ ttt config filters # 配置標籤/使用者過濾
117
+ ttt config teams # 配置多團隊選擇
481
118
  ```
482
119
 
483
- ## 環境變數
484
-
485
- | 變數 | 說明 |
486
- |------|------|
487
- | `LINEAR_API_KEY` | **必填**。Linear API 金鑰([取得方式](https://linear.app/settings/api)) |
488
- | `TOON_DIR` | 配置目錄路徑(可取代 `-d` 參數) |
489
-
490
- ## 常見問題
491
-
492
- ### Q: 為什麼用 TOON 格式?
493
-
494
- TOON 是一種人類可讀的資料格式,類似 YAML 但更簡潔。相比 JSON:
495
- - 更容易手動編輯
496
- - 支援註解
497
- - AI 助手更容易理解
120
+ ## 配置說明
498
121
 
499
- ### Q: config.ttt 可以提交到 Git 嗎?
122
+ ### 目錄結構
500
123
 
501
- 可以,但建議 gitignore。因為包含:
502
- - 團隊成員的 email
503
- - Linear 內部 UUID
504
-
505
- 如果是私有倉庫且團隊成員都有 Linear 存取權,提交是安全的。
506
-
507
- ### Q: 如何處理衝突?
508
-
509
- `cycle.toon` 是自動產生的,直接用 `ttt sync` 重新同步即可。
124
+ ```
125
+ your-project/
126
+ └── .ttt/
127
+ ├── config.toon # 團隊配置(建議 gitignore)
128
+ ├── local.toon # 個人設定(gitignore)
129
+ └── cycle.toon # 當前 cycle 資料(自動產生)
130
+ ```
510
131
 
511
- ### Q: 支援哪些 Linear 功能?
132
+ ### 環境變數
512
133
 
513
- - Cycle 任務同步
514
- - ✅ 單一任務同步 (`ttt sync MP-624`)
515
- - 狀態雙向同步
516
- - 自訂狀態映射(`ttt config status`)
517
- - ✅ 附件和留言讀取
518
- - ✅ 父子任務關聯
519
- - ✅ 優先級排序
520
- - ✅ 多團隊支援
521
- - ✅ 標籤/用戶過濾
134
+ | 變數 | 說明 |
135
+ |------|------|
136
+ | `LINEAR_API_KEY` | **必填**。你的 Linear API 金鑰 |
137
+ | `TOON_DIR` | 配置目錄(預設:`.ttt`) |
522
138
 
523
139
  ## 授權
524
140
 
@@ -1,7 +1,8 @@
1
1
  import prompts from "prompts";
2
2
  import { buildCompletionComment, getLatestCommit } from "./lib/git.js";
3
3
  import { addComment, getStatusTransitions, getWorkflowStates, updateIssueStatus, } from "./lib/linear.js";
4
- import { getLinearClient, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
4
+ import { syncSingleIssue } from "./lib/sync.js";
5
+ import { getLinearClient, loadConfig, loadCycleData, loadLocalConfig, } from "./utils.js";
5
6
  function parseArgs(args) {
6
7
  let issueId;
7
8
  let message;
@@ -144,10 +145,15 @@ Examples:
144
145
  }
145
146
  }
146
147
  }
147
- // Update local status
148
- task.localStatus = "completed";
149
- await saveCycleData(data);
150
- console.log(`Local: ${task.id} → completed`);
148
+ // Sync full issue data from Linear (including new comment)
149
+ const syncedTask = await syncSingleIssue(task.id, {
150
+ config,
151
+ localConfig,
152
+ preserveLocalStatus: false, // Let remote status determine local status
153
+ });
154
+ if (syncedTask) {
155
+ console.log(`Synced: ${syncedTask.id} → ${syncedTask.status} (local: ${syncedTask.localStatus})`);
156
+ }
151
157
  // Summary
152
158
  console.log(`\n${"═".repeat(50)}`);
153
159
  console.log(`✅ ${task.id}: ${task.title}`);
@@ -208,6 +208,52 @@ async function selectStatusMappings(states, options) {
208
208
  testing: testingResponse.testing,
209
209
  };
210
210
  }
211
+ async function updateGitignore(tttDir, interactive) {
212
+ const gitignorePath = ".gitignore";
213
+ const entry = `${tttDir}/`;
214
+ try {
215
+ let content = "";
216
+ let exists = false;
217
+ try {
218
+ content = await fs.readFile(gitignorePath, "utf-8");
219
+ exists = true;
220
+ }
221
+ catch {
222
+ // .gitignore doesn't exist
223
+ }
224
+ // Check if already ignored
225
+ const lines = content.split("\n");
226
+ const alreadyIgnored = lines.some((line) => line.trim() === entry ||
227
+ line.trim() === tttDir ||
228
+ line.trim() === `/${entry}` ||
229
+ line.trim() === `/${tttDir}`);
230
+ if (alreadyIgnored) {
231
+ return;
232
+ }
233
+ // Ask user in interactive mode
234
+ if (interactive) {
235
+ const { addToGitignore } = await prompts({
236
+ type: "confirm",
237
+ name: "addToGitignore",
238
+ message: `Add ${entry} to .gitignore?`,
239
+ initial: true,
240
+ });
241
+ if (!addToGitignore)
242
+ return;
243
+ }
244
+ // Add to .gitignore
245
+ const newContent = exists
246
+ ? content.endsWith("\n")
247
+ ? `${content}${entry}\n`
248
+ : `${content}\n${entry}\n`
249
+ : `${entry}\n`;
250
+ await fs.writeFile(gitignorePath, newContent, "utf-8");
251
+ console.log(` ✓ Added ${entry} to .gitignore`);
252
+ }
253
+ catch (_error) {
254
+ // Silently ignore gitignore errors
255
+ }
256
+ }
211
257
  async function init() {
212
258
  const args = process.argv.slice(2);
213
259
  const options = parseArgs(args);
@@ -338,6 +384,9 @@ async function init() {
338
384
  }
339
385
  await fs.writeFile(paths.localPath, encode(localConfig), "utf-8");
340
386
  console.log(` ✓ ${paths.localPath}`);
387
+ // Update .gitignore
388
+ const tttDir = paths.baseDir.replace(/^\.\//, "");
389
+ await updateGitignore(tttDir, options.interactive ?? true);
341
390
  // Summary
342
391
  console.log("\n✅ Initialization complete!\n");
343
392
  console.log("Configuration summary:");
@@ -66,8 +66,7 @@ export function getDefaultStatusTransitions(states) {
66
66
  const defaultDone = states.find((s) => s.type === "completed")?.name ||
67
67
  findStatusByKeyword(states, ["done", "complete"]) ||
68
68
  "Done";
69
- const defaultTesting = findStatusByKeyword(states, ["testing", "review"]) ||
70
- undefined;
69
+ const defaultTesting = findStatusByKeyword(states, ["testing", "review"]) || undefined;
71
70
  return {
72
71
  todo: defaultTodo,
73
72
  in_progress: defaultInProgress,
@@ -0,0 +1,13 @@
1
+ import type { LinearClient } from "@linear/sdk";
2
+ import { type Config, type LocalConfig, type Task } from "../utils.js";
3
+ export interface SyncIssueOptions {
4
+ config: Config;
5
+ localConfig: LocalConfig;
6
+ client?: LinearClient;
7
+ preserveLocalStatus?: boolean;
8
+ }
9
+ /**
10
+ * Sync a single issue from Linear and update local cycle data
11
+ * Returns the updated task or null if issue not found
12
+ */
13
+ export declare function syncSingleIssue(issueId: string, options: SyncIssueOptions): Promise<Task | null>;
@@ -0,0 +1,97 @@
1
+ import { getLinearClient, loadCycleData, saveCycleData, getPrioritySortIndex, } from "../utils.js";
2
+ import { getStatusTransitions } from "./linear.js";
3
+ /**
4
+ * Sync a single issue from Linear and update local cycle data
5
+ * Returns the updated task or null if issue not found
6
+ */
7
+ export async function syncSingleIssue(issueId, options) {
8
+ const { config, localConfig: _localConfig, preserveLocalStatus = true, } = options;
9
+ const client = options.client ?? getLinearClient();
10
+ // Search for the issue
11
+ const searchResult = await client.searchIssues(issueId);
12
+ const matchingIssue = searchResult.nodes.find((i) => i.identifier === issueId);
13
+ if (!matchingIssue) {
14
+ console.error(`Issue ${issueId} not found in Linear.`);
15
+ return null;
16
+ }
17
+ // Fetch full issue data
18
+ const issue = await client.issue(matchingIssue.id);
19
+ const assignee = await issue.assignee;
20
+ const assigneeEmail = assignee?.email;
21
+ const labels = await issue.labels();
22
+ const labelNames = labels.nodes.map((l) => l.name);
23
+ const state = await issue.state;
24
+ const parent = await issue.parent;
25
+ const attachmentsData = await issue.attachments();
26
+ const commentsData = await issue.comments();
27
+ // Build attachments list
28
+ const attachments = attachmentsData.nodes.map((a) => ({
29
+ id: a.id,
30
+ title: a.title,
31
+ url: a.url,
32
+ sourceType: a.sourceType ?? undefined,
33
+ }));
34
+ // Build comments list
35
+ const comments = await Promise.all(commentsData.nodes.map(async (c) => {
36
+ const user = await c.user;
37
+ return {
38
+ id: c.id,
39
+ body: c.body,
40
+ createdAt: c.createdAt.toISOString(),
41
+ user: user?.displayName ?? user?.email,
42
+ };
43
+ }));
44
+ // Determine local status
45
+ let localStatus = "pending";
46
+ const existingData = await loadCycleData();
47
+ if (preserveLocalStatus && existingData) {
48
+ const existingTask = existingData.tasks.find((t) => t.id === issueId);
49
+ if (existingTask) {
50
+ localStatus = existingTask.localStatus;
51
+ }
52
+ }
53
+ // Map remote status to local status if not preserving
54
+ if (!preserveLocalStatus && state) {
55
+ const transitions = getStatusTransitions(config);
56
+ if (state.name === transitions.done) {
57
+ localStatus = "completed";
58
+ }
59
+ else if (state.name === transitions.in_progress) {
60
+ localStatus = "in-progress";
61
+ }
62
+ else {
63
+ localStatus = "pending";
64
+ }
65
+ }
66
+ const task = {
67
+ id: issue.identifier,
68
+ linearId: issue.id,
69
+ title: issue.title,
70
+ status: state ? state.name : "Unknown",
71
+ localStatus: localStatus,
72
+ assignee: assigneeEmail,
73
+ priority: issue.priority,
74
+ labels: labelNames,
75
+ branch: issue.branchName,
76
+ description: issue.description ?? undefined,
77
+ parentIssueId: parent ? parent.identifier : undefined,
78
+ url: issue.url,
79
+ attachments: attachments.length > 0 ? attachments : undefined,
80
+ comments: comments.length > 0 ? comments : undefined,
81
+ };
82
+ // Update cycle data
83
+ if (existingData) {
84
+ const existingTasks = existingData.tasks.filter((t) => t.id !== issueId);
85
+ const finalTasks = [...existingTasks, task];
86
+ // Sort by priority
87
+ finalTasks.sort((a, b) => {
88
+ const pa = getPrioritySortIndex(a.priority, config.priority_order);
89
+ const pb = getPrioritySortIndex(b.priority, config.priority_order);
90
+ return pa - pb;
91
+ });
92
+ existingData.tasks = finalTasks;
93
+ existingData.updatedAt = new Date().toISOString();
94
+ await saveCycleData(existingData);
95
+ }
96
+ return task;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
- "version": "1.6.3",
3
+ "version": "1.7.1",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {