team-toon-tack 2.3.0 → 2.3.2

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.
@@ -13,7 +13,7 @@
13
13
  "name": "team-toon-tack",
14
14
  "source": "./",
15
15
  "description": "Linear task sync & management CLI with commands and skills",
16
- "version": "2.3.0"
16
+ "version": "2.3.2"
17
17
  }
18
18
  ]
19
19
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
3
  "description": "Linear task sync & management CLI for Claude Code - saves tokens vs MCP",
4
- "version": "2.3.0",
4
+ "version": "2.3.2",
5
5
  "author": {
6
6
  "name": "wayne930242",
7
7
  "email": "wayne930242@gmail.com"
package/README.md CHANGED
@@ -14,7 +14,7 @@ Optimized Linear workflow for Claude Code — saves significant tokens compared
14
14
  - **QA Team Support** — Auto-update parent issues in QA team to "Testing" when completing dev tasks
15
15
  - **Attachment Download** — Auto-download Linear images and files to local `.ttt/output/` for AI vision analysis
16
16
  - **Blocked Status** — Set tasks as blocked when waiting on external dependencies
17
- - **Claude Code Plugin** — Install plugin for `/ttt-*` commands and auto-activated skills
17
+ - **Claude Code Plugin** — Install plugin for `/ttt:*` commands and auto-activated skills
18
18
  - **Cycle History** — Local `.toon` files preserve cycle data for AI context
19
19
  - **User Filtering** — Only see issues assigned to you or unassigned
20
20
 
@@ -58,9 +58,9 @@ During init, you'll configure:
58
58
  In Claude Code (with plugin installed):
59
59
 
60
60
  ```
61
- /ttt-sync # Fetch all Linear issues for current cycle
62
- /ttt-work-on next # Pick highest priority task & start working
63
- /ttt-done # Complete task with AI-generated summary
61
+ /ttt:sync # Fetch all Linear issues for current cycle
62
+ /ttt:work-on next # Pick highest priority task & start working
63
+ /ttt:done # Complete task with AI-generated summary
64
64
  ```
65
65
 
66
66
  Or using CLI directly:
@@ -128,13 +128,18 @@ ttt status MP-123 --set done # Mark as done
128
128
  ttt status MP-123 --set blocked # Set as blocked (waiting on dependency)
129
129
  ```
130
130
 
131
- ### `ttt get-issue`
131
+ ### `ttt show`
132
132
 
133
- Fetch and display issue details from Linear.
133
+ Show issue details or search issues from local cycle data.
134
134
 
135
135
  ```bash
136
- ttt get-issue MP-123 # Fetch from Linear and display
137
- ttt get-issue MP-123 --local # Show from local data only
136
+ ttt show # Show all issues in local cycle data
137
+ ttt show MP-123 # Show specific issue from local data
138
+ ttt show MP-123 --remote # Fetch specific issue from Linear
139
+ ttt show --label frontend # Filter by label
140
+ ttt show --status "In Progress" --user me # My in-progress issues
141
+ ttt show --priority 1 # Show urgent issues
142
+ ttt show --export # Export as markdown
138
143
  ```
139
144
 
140
145
  ### `ttt config`
@@ -181,11 +186,11 @@ Install the plugin for Claude Code integration:
181
186
 
182
187
  | Command | Description |
183
188
  |---------|-------------|
184
- | `/ttt-sync` | Sync Linear issues to local cycle data |
185
- | `/ttt-work-on` | Start working on a task |
186
- | `/ttt-done` | Mark current task as completed |
187
- | `/ttt-status` | Show or modify task status |
188
- | `/ttt-get-issue` | Fetch and display issue details |
189
+ | `/ttt:sync` | Sync Linear issues to local cycle data |
190
+ | `/ttt:work-on` | Start working on a task |
191
+ | `/ttt:done` | Mark current task as completed |
192
+ | `/ttt:status` | Show or modify task status |
193
+ | `/ttt:show` | Show issue details or search issues |
189
194
 
190
195
  ### Auto-Activated Skill
191
196
 
package/README.zh-TW.md CHANGED
@@ -14,7 +14,7 @@
14
14
  - **QA 團隊支援** — 完成開發任務時自動將 QA 團隊的 parent issue 更新為「Testing」
15
15
  - **附件下載** — 自動下載 Linear 圖片和檔案到本地 `.ttt/output/`,供 AI 視覺分析
16
16
  - **阻塞狀態** — 等待外部依賴時可設定任務為 blocked
17
- - **Claude Code Plugin** — 安裝 plugin 即可使用 `/ttt-*` 指令和自動啟用的技能
17
+ - **Claude Code Plugin** — 安裝 plugin 即可使用 `/ttt:*` 指令和自動啟用的技能
18
18
  - **Cycle 歷史保存** — 本地 `.toon` 檔案保留 cycle 資料,方便 AI 檢閱
19
19
  - **使用者過濾** — 只顯示指派給你或未指派的工作
20
20
 
@@ -58,9 +58,9 @@ ttt init
58
58
  在 Claude Code 中(安裝 plugin 後):
59
59
 
60
60
  ```
61
- /ttt-sync # 從 Linear 取得當前 cycle 所有 issue
62
- /ttt-work-on next # 挑選最高優先級任務並開始工作
63
- /ttt-done # 完成任務,附上 AI 生成的摘要
61
+ /ttt:sync # 從 Linear 取得當前 cycle 所有 issue
62
+ /ttt:work-on next # 挑選最高優先級任務並開始工作
63
+ /ttt:done # 完成任務,附上 AI 生成的摘要
64
64
  ```
65
65
 
66
66
  或直接使用 CLI:
@@ -128,13 +128,18 @@ ttt status MP-123 --set done # 標記為完成
128
128
  ttt status MP-123 --set blocked # 設為阻塞(等待外部依賴)
129
129
  ```
130
130
 
131
- ### `ttt get-issue`
131
+ ### `ttt show`
132
132
 
133
- Linear 取得並顯示 issue 詳細資訊。
133
+ 顯示 issue 詳情或搜尋 issue
134
134
 
135
135
  ```bash
136
- ttt get-issue MP-123 # Linear 取得並顯示
137
- ttt get-issue MP-123 --local # 只從本地資料顯示
136
+ ttt show # 顯示本地 cycle 資料中的所有 issue
137
+ ttt show MP-123 # 顯示特定 issue(從本地資料)
138
+ ttt show MP-123 --remote # 從 Linear 取得特定 issue
139
+ ttt show --label frontend # 依標籤過濾
140
+ ttt show --status "In Progress" --user me # 我進行中的 issue
141
+ ttt show --priority 1 # 顯示緊急 issue
142
+ ttt show --export # 輸出為 markdown 格式
138
143
  ```
139
144
 
140
145
  ### `ttt config`
@@ -181,11 +186,11 @@ your-project/
181
186
 
182
187
  | 指令 | 說明 |
183
188
  |------|------|
184
- | `/ttt-sync` | 同步 Linear issue 到本地 |
185
- | `/ttt-work-on` | 開始處理任務 |
186
- | `/ttt-done` | 標記當前任務完成 |
187
- | `/ttt-status` | 顯示或修改任務狀態 |
188
- | `/ttt-get-issue` | 取得並顯示 issue 詳情 |
189
+ | `/ttt:sync` | 同步 Linear issue 到本地 |
190
+ | `/ttt:work-on` | 開始處理任務 |
191
+ | `/ttt:done` | 標記當前任務完成 |
192
+ | `/ttt:status` | 顯示或修改任務狀態 |
193
+ | `/ttt:show` | 顯示 issue 詳情或搜尋 issue |
189
194
 
190
195
  ### 自動啟用技能
191
196
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ttt-done
2
+ name: ttt:done
3
3
  description: Mark current task as completed
4
4
  arguments:
5
5
  - name: issue-id
@@ -57,6 +57,8 @@ Also:
57
57
  ## Before Running
58
58
 
59
59
  Ensure you have:
60
- 1. Committed your changes with a meaningful message
61
- 2. Run tests and lint checks
60
+ 1. **Run project validation** - Use `/validate` command or validation skill if available
61
+ 2. Committed your changes with a meaningful message
62
62
  3. Pushed to remote branch (if applicable)
63
+
64
+ **Important**: If validation fails, fix issues before marking as done.
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: ttt:show
3
+ description: Show issue details or search issues from local cycle data
4
+ arguments:
5
+ - name: issue-id
6
+ description: Issue ID to show (e.g., MP-624)
7
+ required: false
8
+ - name: label
9
+ description: Filter by label (--label <label>)
10
+ required: false
11
+ - name: status
12
+ description: Filter by status (--status <status>)
13
+ required: false
14
+ - name: user
15
+ description: Filter by assignee (--user <email|me|unassigned>)
16
+ required: false
17
+ - name: priority
18
+ description: Filter by priority (--priority <0-4>)
19
+ required: false
20
+ - name: remote
21
+ description: Fetch from Linear instead of local data (--remote)
22
+ required: false
23
+ - name: export
24
+ description: Output as markdown format (--export)
25
+ required: false
26
+ ---
27
+
28
+ # TTT Show Command
29
+
30
+ Show issue details or search issues from local cycle data.
31
+
32
+ ## Usage
33
+
34
+ ### Show All Issues
35
+ ```bash
36
+ ttt show
37
+ ```
38
+
39
+ ### Show Specific Issue
40
+ ```bash
41
+ ttt show {{ issue-id }}
42
+ ```
43
+
44
+ ### Search by Filters
45
+ ```bash
46
+ ttt show --label {{ label }}
47
+ ttt show --status "{{ status }}"
48
+ ttt show --user {{ user }}
49
+ ttt show --priority {{ priority }}
50
+ ```
51
+
52
+ ### Fetch from Linear (Remote)
53
+ ```bash
54
+ ttt show {{ issue-id }} --remote
55
+ ttt show --remote --status todo
56
+ ```
57
+
58
+ ## What This Does
59
+
60
+ 1. By default, reads from local cycle.toon data (no API calls)
61
+ 2. Supports filtering by label, status, user, priority
62
+ 3. Use --remote to fetch fresh data from Linear API
63
+ 4. Displays comprehensive information:
64
+ - Title and description
65
+ - Status (both Linear and local)
66
+ - Priority level
67
+ - Labels
68
+ - Assignee
69
+ - Branch name
70
+ - Parent issue (if subtask)
71
+ - Attachments with local paths
72
+ - Comments with timestamps
73
+
74
+ ## Filter Options
75
+
76
+ | Option | Description | Example |
77
+ |--------|-------------|---------|
78
+ | `--label` | Filter by label name | `--label frontend` |
79
+ | `--status` | Filter by Linear status | `--status "In Progress"` |
80
+ | `--user` | Filter by assignee | `--user me`, `--user unassigned` |
81
+ | `--priority` | Filter by priority (0-4) | `--priority 1` (Urgent) |
82
+ | `--remote` | Fetch from Linear API | `--remote` |
83
+ | `--export` | Output as markdown | `--export` |
84
+
85
+ ## Priority Values
86
+
87
+ - 0: None
88
+ - 1: Urgent
89
+ - 2: High
90
+ - 3: Medium
91
+ - 4: Low
92
+
93
+ ## Use Cases
94
+
95
+ - List all issues in current cycle
96
+ - Search issues by label or status
97
+ - Review issue details before starting work
98
+ - Check requirements and acceptance criteria
99
+ - View attachments and mockups
100
+ - Read comment history and discussions
101
+
102
+ ## Examples
103
+
104
+ ```bash
105
+ # Show all local issues
106
+ ttt show
107
+
108
+ # Show specific issue
109
+ ttt show MP-624
110
+
111
+ # My in-progress issues
112
+ ttt show --status "In Progress" --user me
113
+
114
+ # All urgent issues
115
+ ttt show --priority 1
116
+
117
+ # Frontend issues
118
+ ttt show --label frontend
119
+
120
+ # Fetch fresh data from Linear
121
+ ttt show MP-624 --remote
122
+
123
+ # Export as markdown
124
+ ttt show --export
125
+ ttt show MP-624 --export
126
+ ```
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ttt-status
2
+ name: ttt:status
3
3
  description: Show or modify task status
4
4
  arguments:
5
5
  - name: issue-id
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ttt-sync
2
+ name: ttt:sync
3
3
  description: Sync Linear issues to local cycle data
4
4
  arguments:
5
5
  - name: issue-id
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ttt-work-on
2
+ name: ttt:work-on
3
3
  description: Start working on a Linear task
4
4
  arguments:
5
5
  - name: issue-id
@@ -44,5 +44,36 @@ ttt work-on next
44
44
 
45
45
  1. Read the task description and requirements
46
46
  2. Check out the suggested branch: `git checkout -b <branch-name>`
47
- 3. Run type-check and lint before starting: `bun type-check && bun lint`
47
+ 3. **Run project validation** (see below)
48
48
  4. Begin implementation
49
+
50
+ ## Project Validation
51
+
52
+ Before starting work, check for validation tools:
53
+
54
+ ### 1. Check for Existing Validation
55
+
56
+ Look for:
57
+ - **Commands**: `/validate`, `/check`, `/lint` in available commands
58
+ - **Skills**: `validate`, `check`, `verify` in available skills
59
+ - **Scripts**: `lint`, `type-check`, `test` in `package.json` or `Makefile`
60
+
61
+ ### 2. If No Validation Found
62
+
63
+ Ask user:
64
+ ```
65
+ No validation command or skill found for this project.
66
+ Would you like to create one? (y/n)
67
+ ```
68
+
69
+ If yes, run:
70
+ ```
71
+ /ttt:write-validate
72
+ ```
73
+
74
+ ### 3. Run Validation
75
+
76
+ Once validation is available:
77
+ - Use `/validate` command if exists
78
+ - Or use validation skill if available
79
+ - Or run detected scripts directly
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: ttt:write-validate
3
+ description: Create a project validation command by detecting project type and existing lint/test configurations
4
+ arguments:
5
+ - name: command-name
6
+ description: Name for the command (default: validate)
7
+ required: false
8
+ ---
9
+
10
+ # TTT Write-Validate Command
11
+
12
+ Create a project-specific validation command by analyzing the codebase.
13
+
14
+ ## Process
15
+
16
+ ### 1. Detect Project Type
17
+
18
+ Check for project indicators:
19
+
20
+ | File | Project Type |
21
+ |------|--------------|
22
+ | `package.json` | Node.js / Bun |
23
+ | `Cargo.toml` | Rust |
24
+ | `go.mod` | Go |
25
+ | `pyproject.toml`, `requirements.txt` | Python |
26
+
27
+ ### 2. Find Existing Configurations
28
+
29
+ Look for static analysis configs:
30
+
31
+ **Linting**: `biome.json`, `.eslintrc.*`, `ruff.toml`
32
+ **Type Check**: `tsconfig.json`, `mypy.ini`
33
+ **Testing**: `jest.config.*`, `vitest.config.*`, `pytest.ini`
34
+
35
+ ### 3. Check package.json Scripts
36
+
37
+ If Node.js project:
38
+ ```bash
39
+ cat package.json | jq '.scripts | keys[]' 2>/dev/null
40
+ ```
41
+
42
+ Look for: `lint`, `type-check`, `test`, `check`, `validate`
43
+
44
+ ### 4. Create Command File
45
+
46
+ Create `.claude/commands/{{ $1 | default: "validate" }}.md`:
47
+
48
+ ```markdown
49
+ ---
50
+ name: {{ $1 | default: "validate" }}
51
+ description: Run project validation (lint, type-check, test)
52
+ ---
53
+
54
+ # Project Validation
55
+
56
+ Run validation checks for this project.
57
+
58
+ ## Process
59
+
60
+ ### 1. Lint
61
+ \`\`\`bash
62
+ {{ lint-command }}
63
+ \`\`\`
64
+
65
+ ### 2. Type Check
66
+ \`\`\`bash
67
+ {{ type-check-command }}
68
+ \`\`\`
69
+
70
+ ### 3. Test (optional)
71
+ \`\`\`bash
72
+ {{ test-command }}
73
+ \`\`\`
74
+
75
+ ## Quick Validate All
76
+
77
+ \`\`\`bash
78
+ {{ combined-command }}
79
+ \`\`\`
80
+
81
+ ## On Failure
82
+
83
+ 1. Show error output
84
+ 2. Identify failing file(s) and line(s)
85
+ 3. Suggest fixes
86
+ 4. Re-run validation after fixes
87
+ ```
88
+
89
+ ### 5. Output
90
+
91
+ After creating:
92
+ 1. Show created file path
93
+ 2. Display detected validation commands
94
+ 3. Suggest running `/{{ $1 | default: "validate" }}` to test
95
+
96
+ ## Examples
97
+
98
+ ### Node.js with Biome
99
+ ```
100
+ Detected: Node.js project with Biome + TypeScript
101
+ Created: .claude/commands/validate.md
102
+
103
+ Commands:
104
+ Lint: bun run lint
105
+ Type: bun run type-check
106
+ Test: bun run test
107
+
108
+ Try: /validate
109
+ ```
110
+
111
+ ### Python with Ruff
112
+ ```
113
+ Detected: Python project with Ruff + mypy
114
+ Created: .claude/commands/validate.md
115
+
116
+ Commands:
117
+ Lint: ruff check .
118
+ Type: mypy .
119
+ Test: pytest
120
+
121
+ Try: /validate
122
+ ```
package/dist/bin/cli.js CHANGED
@@ -12,7 +12,7 @@ const COMMANDS = [
12
12
  "work-on",
13
13
  "done",
14
14
  "status",
15
- "get-issue",
15
+ "show",
16
16
  "config",
17
17
  "help",
18
18
  "version",
@@ -30,7 +30,7 @@ COMMANDS:
30
30
  work-on Start working on a task (interactive or by ID)
31
31
  done Mark current task as completed
32
32
  status Show or modify task status
33
- get-issue Fetch and display issue details from Linear
33
+ show Show issue details or search issues by filters
34
34
  config Configure settings (status mappings, filters)
35
35
  help Show this help message
36
36
  version Show version
@@ -48,7 +48,9 @@ EXAMPLES:
48
48
  ttt work-on next # Auto-select highest priority
49
49
  ttt done # Complete current task
50
50
  ttt done -m "Fixed the bug" # With completion message
51
- ttt get-issue MP-123 # Fetch issue details from Linear
51
+ ttt show MP-123 # Show issue from local data
52
+ ttt show --label frontend # Search local issues by label
53
+ ttt show --status "In Progress" # Filter by status
52
54
 
53
55
  ENVIRONMENT:
54
56
  LINEAR_API_KEY Required. Your Linear API key
@@ -120,9 +122,9 @@ async function main() {
120
122
  process.argv = ["node", "status.js", ...commandArgs];
121
123
  await import(`${scriptDir}status.js`);
122
124
  break;
123
- case "get-issue":
124
- process.argv = ["node", "get-issue.js", ...commandArgs];
125
- await import(`${scriptDir}get-issue.js`);
125
+ case "show":
126
+ process.argv = ["node", "show.js", ...commandArgs];
127
+ await import(`${scriptDir}show.js`);
126
128
  break;
127
129
  case "config":
128
130
  process.argv = ["node", "config.js", ...commandArgs];
@@ -400,21 +400,9 @@ async function updateGitignore(tttDir, interactive) {
400
400
  // Silently ignore gitignore errors
401
401
  }
402
402
  }
403
- async function showPluginInstallInstructions(interactive) {
404
- if (!interactive) {
405
- return false;
406
- }
403
+ function showPluginInstallInstructions() {
407
404
  console.log("\n🤖 Claude Code Plugin:");
408
- const { showInstructions } = await prompts({
409
- type: "confirm",
410
- name: "showInstructions",
411
- message: "Show Claude Code plugin installation instructions? (provides /ttt-* commands)",
412
- initial: true,
413
- });
414
- if (!showInstructions) {
415
- return false;
416
- }
417
- console.log("\n┌─────────────────────────────────────────────────────────────┐");
405
+ console.log("┌─────────────────────────────────────────────────────────────┐");
418
406
  console.log("│ Install team-toon-tack plugin in Claude Code: │");
419
407
  console.log("├─────────────────────────────────────────────────────────────┤");
420
408
  console.log("│ │");
@@ -425,14 +413,13 @@ async function showPluginInstallInstructions(interactive) {
425
413
  console.log("│ /plugin install team-toon-tack@wayne930242 │");
426
414
  console.log("│ │");
427
415
  console.log("│ Available commands after install: │");
428
- console.log("│ /ttt-sync - Sync Linear issues │");
429
- console.log("│ /ttt-work-on - Start working on a task │");
430
- console.log("│ /ttt-done - Complete current task │");
431
- console.log("│ /ttt-status - Show/modify task status │");
432
- console.log("│ /ttt-get-issue - Fetch issue details │");
416
+ console.log("│ /ttt:sync - Sync Linear issues │");
417
+ console.log("│ /ttt:work-on - Start working on a task │");
418
+ console.log("│ /ttt:done - Complete current task │");
419
+ console.log("│ /ttt:status - Show/modify task status │");
420
+ console.log("│ /ttt:show - Show/search issues │");
433
421
  console.log("│ │");
434
422
  console.log("└─────────────────────────────────────────────────────────────┘");
435
- return true;
436
423
  }
437
424
  async function init() {
438
425
  const args = process.argv.slice(2);
@@ -625,7 +612,7 @@ async function init() {
625
612
  // Update .gitignore (always use relative path .ttt)
626
613
  await updateGitignore(".ttt", options.interactive ?? true);
627
614
  // Show Claude Code plugin installation instructions
628
- const pluginInstructionsShown = await showPluginInstallInstructions(options.interactive ?? true);
615
+ showPluginInstallInstructions();
629
616
  // Summary
630
617
  console.log("\n✅ Initialization complete!\n");
631
618
  console.log("Configuration summary:");
@@ -659,8 +646,5 @@ async function init() {
659
646
  console.log(` export LINEAR_API_KEY="${apiKey}"`);
660
647
  console.log(" 2. Run sync: ttt sync");
661
648
  console.log(" 3. Start working: ttt work-on");
662
- if (pluginInstructionsShown) {
663
- console.log("\n💡 Tip: Install the Claude Code plugin for /ttt-* commands.");
664
- }
665
649
  }
666
650
  init().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,319 @@
1
+ import { displayTaskFull, PRIORITY_LABELS, getStatusIcon } from "./lib/display.js";
2
+ import { fetchIssueDetail } from "./lib/sync.js";
3
+ import { getLinearClient, getTeamId, loadConfig, loadCycleData, loadLocalConfig, } from "./utils.js";
4
+ function taskToMarkdown(task) {
5
+ const lines = [];
6
+ const priority = PRIORITY_LABELS[task.priority] || "None";
7
+ lines.push(`## ${task.id}: ${task.title}`);
8
+ lines.push("");
9
+ lines.push(`- **Status**: ${task.status} (Local: ${task.localStatus})`);
10
+ lines.push(`- **Priority**: ${priority}`);
11
+ lines.push(`- **Labels**: ${task.labels.length > 0 ? task.labels.join(", ") : "-"}`);
12
+ lines.push(`- **Assignee**: ${task.assignee || "Unassigned"}`);
13
+ if (task.branch)
14
+ lines.push(`- **Branch**: \`${task.branch}\``);
15
+ if (task.url)
16
+ lines.push(`- **URL**: ${task.url}`);
17
+ if (task.parentIssueId)
18
+ lines.push(`- **Parent**: ${task.parentIssueId}`);
19
+ if (task.description) {
20
+ lines.push("");
21
+ lines.push("### Description");
22
+ lines.push("");
23
+ lines.push(task.description);
24
+ }
25
+ if (task.attachments && task.attachments.length > 0) {
26
+ lines.push("");
27
+ lines.push("### Attachments");
28
+ lines.push("");
29
+ for (const att of task.attachments) {
30
+ const path = att.localPath || att.url;
31
+ lines.push(`- ${att.title}: ${path}`);
32
+ }
33
+ }
34
+ if (task.comments && task.comments.length > 0) {
35
+ lines.push("");
36
+ lines.push("### Comments");
37
+ for (const comment of task.comments) {
38
+ const date = new Date(comment.createdAt).toLocaleDateString();
39
+ lines.push("");
40
+ lines.push(`**${comment.user || "Unknown"}** - ${date}`);
41
+ lines.push("");
42
+ lines.push(comment.body);
43
+ }
44
+ }
45
+ return lines.join("\n");
46
+ }
47
+ function tasksToMarkdownList(tasks) {
48
+ if (tasks.length === 0) {
49
+ return "No issues found.";
50
+ }
51
+ const lines = [];
52
+ lines.push(`# Issues (${tasks.length})`);
53
+ lines.push("");
54
+ for (const task of tasks) {
55
+ const icon = getStatusIcon(task.localStatus);
56
+ const priority = PRIORITY_LABELS[task.priority] || "None";
57
+ const assignee = task.assignee ? task.assignee.split("@")[0] : "unassigned";
58
+ lines.push(`## ${icon} ${task.id}: ${task.title}`);
59
+ lines.push("");
60
+ lines.push(`| Field | Value |`);
61
+ lines.push(`|-------|-------|`);
62
+ lines.push(`| Status | ${task.status} |`);
63
+ lines.push(`| Priority | ${priority} |`);
64
+ lines.push(`| Assignee | ${assignee} |`);
65
+ lines.push(`| Labels | ${task.labels.length > 0 ? task.labels.join(", ") : "-"} |`);
66
+ if (task.url)
67
+ lines.push(`| URL | ${task.url} |`);
68
+ lines.push("");
69
+ }
70
+ return lines.join("\n");
71
+ }
72
+ function displayTaskList(tasks) {
73
+ if (tasks.length === 0) {
74
+ console.log("No issues found.");
75
+ return;
76
+ }
77
+ console.log(`\nFound ${tasks.length} issue(s):\n`);
78
+ console.log("─".repeat(80));
79
+ for (const task of tasks) {
80
+ const icon = getStatusIcon(task.localStatus);
81
+ const priority = PRIORITY_LABELS[task.priority] || "⚪ None";
82
+ const assignee = task.assignee ? task.assignee.split("@")[0] : "unassigned";
83
+ const labels = task.labels.length > 0 ? task.labels.join(", ") : "-";
84
+ console.log(`${icon} ${task.id}: ${task.title}`);
85
+ console.log(` Status: ${task.status} | Priority: ${priority} | Assignee: ${assignee}`);
86
+ console.log(` Labels: ${labels}`);
87
+ console.log("─".repeat(80));
88
+ }
89
+ }
90
+ async function searchIssuesFromLinear(filters) {
91
+ const config = await loadConfig();
92
+ const localConfig = await loadLocalConfig();
93
+ const client = getLinearClient();
94
+ const teamId = getTeamId(config, localConfig.team);
95
+ // Build filter
96
+ const issueFilter = {
97
+ team: { id: { eq: teamId } },
98
+ };
99
+ if (filters.label) {
100
+ issueFilter.labels = { name: { containsIgnoreCase: filters.label } };
101
+ }
102
+ if (filters.status) {
103
+ issueFilter.state = { name: { containsIgnoreCase: filters.status } };
104
+ }
105
+ if (filters.assignee) {
106
+ if (filters.assignee.toLowerCase() === "me") {
107
+ issueFilter.assignee = { email: { eq: localConfig.current_user } };
108
+ }
109
+ else if (filters.assignee.toLowerCase() === "unassigned") {
110
+ issueFilter.assignee = { null: true };
111
+ }
112
+ else {
113
+ issueFilter.assignee = { email: { containsIgnoreCase: filters.assignee } };
114
+ }
115
+ }
116
+ if (filters.priority !== undefined) {
117
+ issueFilter.priority = { eq: filters.priority };
118
+ }
119
+ const issues = await client.issues({
120
+ filter: issueFilter,
121
+ first: 50,
122
+ });
123
+ const tasks = [];
124
+ for (const issueNode of issues.nodes) {
125
+ const issue = await client.issue(issueNode.id);
126
+ const assignee = await issue.assignee;
127
+ const labels = await issue.labels();
128
+ const state = await issue.state;
129
+ const parent = await issue.parent;
130
+ const task = {
131
+ id: issue.identifier,
132
+ linearId: issue.id,
133
+ title: issue.title,
134
+ status: state ? state.name : "Unknown",
135
+ localStatus: "pending",
136
+ assignee: assignee?.email,
137
+ priority: issue.priority,
138
+ labels: labels.nodes.map((l) => l.name),
139
+ branch: issue.branchName,
140
+ description: issue.description ?? undefined,
141
+ parentIssueId: parent ? parent.identifier : undefined,
142
+ url: issue.url,
143
+ };
144
+ tasks.push(task);
145
+ }
146
+ return tasks;
147
+ }
148
+ async function searchIssuesFromLocal(data, filters) {
149
+ let tasks = data.tasks;
150
+ if (filters.label) {
151
+ const labelLower = filters.label.toLowerCase();
152
+ tasks = tasks.filter((t) => t.labels.some((l) => l.toLowerCase().includes(labelLower)));
153
+ }
154
+ if (filters.status) {
155
+ const statusLower = filters.status.toLowerCase();
156
+ tasks = tasks.filter((t) => t.status.toLowerCase().includes(statusLower));
157
+ }
158
+ if (filters.assignee) {
159
+ const assigneeLower = filters.assignee.toLowerCase();
160
+ if (assigneeLower === "unassigned") {
161
+ tasks = tasks.filter((t) => !t.assignee);
162
+ }
163
+ else if (assigneeLower === "me") {
164
+ const localConfig = await loadLocalConfig();
165
+ const config = await loadConfig();
166
+ const userEmail = config.users[localConfig.current_user]?.email?.toLowerCase();
167
+ if (userEmail) {
168
+ tasks = tasks.filter((t) => t.assignee?.toLowerCase() === userEmail);
169
+ }
170
+ }
171
+ else {
172
+ tasks = tasks.filter((t) => t.assignee?.toLowerCase().includes(assigneeLower));
173
+ }
174
+ }
175
+ if (filters.priority !== undefined) {
176
+ tasks = tasks.filter((t) => t.priority === filters.priority);
177
+ }
178
+ return tasks;
179
+ }
180
+ async function show() {
181
+ const args = process.argv.slice(2);
182
+ if (args.includes("--help") || args.includes("-h")) {
183
+ console.log(`Usage: ttt show [issue-id] [options]
184
+
185
+ Show issue details or search for issues from local cycle data.
186
+
187
+ Arguments:
188
+ issue-id Optional. Show specific issue (e.g., MP-624)
189
+
190
+ Options:
191
+ --remote Fetch from Linear instead of local data
192
+ --export Output as markdown format
193
+ --label <label> Filter by label
194
+ --status <status> Filter by status
195
+ --user <email> Filter by assignee (use "me" for yourself, "unassigned" for no assignee)
196
+ --priority <n> Filter by priority (0=None, 1=Urgent, 2=High, 3=Medium, 4=Low)
197
+
198
+ Examples:
199
+ ttt show # Show all issues in local cycle data
200
+ ttt show MP-624 # Show specific issue from local data
201
+ ttt show MP-624 --remote # Fetch specific issue from Linear
202
+ ttt show MP-624 --export # Export issue as markdown
203
+ ttt show --label frontend # Filter local issues by label
204
+ ttt show --status "In Progress" --user me # My in-progress issues
205
+ ttt show --priority 1 # Show all urgent issues
206
+ ttt show --export # Export all issues as markdown`);
207
+ process.exit(0);
208
+ }
209
+ const useRemote = args.includes("--remote");
210
+ const exportMarkdown = args.includes("--export");
211
+ // Parse filters
212
+ const filters = {};
213
+ for (let i = 0; i < args.length; i++) {
214
+ const arg = args[i];
215
+ if (arg === "--label" && args[i + 1]) {
216
+ filters.label = args[++i];
217
+ }
218
+ else if (arg === "--status" && args[i + 1]) {
219
+ filters.status = args[++i];
220
+ }
221
+ else if (arg === "--user" && args[i + 1]) {
222
+ filters.assignee = args[++i];
223
+ }
224
+ else if (arg === "--priority" && args[i + 1]) {
225
+ filters.priority = parseInt(args[++i], 10);
226
+ }
227
+ }
228
+ // Check if this is a search (has filters) or single issue lookup
229
+ const hasFilters = Object.keys(filters).length > 0;
230
+ // Find issue ID (argument that doesn't start with -)
231
+ const issueId = args.find((arg) => !arg.startsWith("-") && arg.match(/^[A-Z]+-\d+$/i));
232
+ // If no issue ID and no filters, show all local issues
233
+ if (!issueId && !hasFilters) {
234
+ const data = await loadCycleData();
235
+ if (!data) {
236
+ console.error("No cycle data found. Run ttt sync first.");
237
+ process.exit(1);
238
+ }
239
+ if (exportMarkdown) {
240
+ console.log(tasksToMarkdownList(data.tasks));
241
+ }
242
+ else {
243
+ displayTaskList(data.tasks);
244
+ }
245
+ return;
246
+ }
247
+ // Search mode: has filters but no specific issue ID
248
+ if (hasFilters && !issueId) {
249
+ let tasks;
250
+ if (useRemote) {
251
+ console.error("Searching issues from Linear...");
252
+ tasks = await searchIssuesFromLinear(filters);
253
+ }
254
+ else {
255
+ const data = await loadCycleData();
256
+ if (!data) {
257
+ console.error("No cycle data found. Run ttt sync first.");
258
+ process.exit(1);
259
+ }
260
+ tasks = await searchIssuesFromLocal(data, filters);
261
+ }
262
+ if (exportMarkdown) {
263
+ console.log(tasksToMarkdownList(tasks));
264
+ }
265
+ else {
266
+ displayTaskList(tasks);
267
+ }
268
+ return;
269
+ }
270
+ // Single issue mode
271
+ if (!issueId) {
272
+ console.error("Issue ID is required for single issue lookup.");
273
+ console.error("Usage: ttt show <issue-id> or ttt show --label <label>");
274
+ process.exit(1);
275
+ }
276
+ // Fetch from remote Linear
277
+ if (useRemote) {
278
+ console.error(`Fetching ${issueId} from Linear...`);
279
+ const task = await fetchIssueDetail(issueId);
280
+ if (!task) {
281
+ console.error(`Issue ${issueId} not found in Linear.`);
282
+ process.exit(1);
283
+ }
284
+ // Check local data for local status
285
+ const data = await loadCycleData();
286
+ if (data) {
287
+ const localTask = data.tasks.find((t) => t.id === issueId);
288
+ if (localTask) {
289
+ task.localStatus = localTask.localStatus;
290
+ }
291
+ }
292
+ if (exportMarkdown) {
293
+ console.log(taskToMarkdown(task));
294
+ }
295
+ else {
296
+ displayTaskFull(task, "📋");
297
+ }
298
+ return;
299
+ }
300
+ // Default: get from local cycle data
301
+ const data = await loadCycleData();
302
+ if (!data) {
303
+ console.error("No cycle data found. Run ttt sync first.");
304
+ process.exit(1);
305
+ }
306
+ const task = data.tasks.find((t) => t.id === issueId || t.id === issueId.toUpperCase());
307
+ if (!task) {
308
+ console.error(`Issue ${issueId} not found in local data.`);
309
+ console.error("Use --remote to fetch from Linear.");
310
+ process.exit(1);
311
+ }
312
+ if (exportMarkdown) {
313
+ console.log(taskToMarkdown(task));
314
+ }
315
+ else {
316
+ displayTaskFull(task, "📋");
317
+ }
318
+ }
319
+ show().catch(console.error);
@@ -17,12 +17,13 @@ Options:
17
17
 
18
18
  What it does:
19
19
  - Fetches active cycle from Linear
20
- - Downloads all issues matching configured filters
20
+ - Downloads all issues in the cycle (regardless of status)
21
+ - Filters by label if configured
21
22
  - Preserves local status for existing tasks
22
23
  - Updates config with new cycle info
23
24
 
24
25
  Examples:
25
- ttt sync # Sync all matching issues
26
+ ttt sync # Sync all issues in current cycle
26
27
  ttt sync MP-624 # Sync only this specific issue
27
28
  ttt sync --update # Push local changes to Linear, then sync
28
29
  ttt sync -d .ttt # Sync using .ttt directory`);
@@ -150,7 +151,6 @@ Examples:
150
151
  const existingTasksMap = new Map(existingData?.tasks.map((t) => [t.id, t]));
151
152
  // Phase 4: Fetch current issues with full content
152
153
  const filterLabel = localConfig.label;
153
- const syncStatuses = [statusTransitions.todo, statusTransitions.in_progress];
154
154
  let issues;
155
155
  if (singleIssueId) {
156
156
  // Sync single issue by ID
@@ -165,12 +165,11 @@ Examples:
165
165
  }
166
166
  else {
167
167
  // Sync all matching issues
168
- console.log(`Fetching issues with status: ${syncStatuses.join(", ")}${filterLabel ? ` and label: ${filterLabel}` : ""}...`);
169
- // Build filter - label is optional
168
+ console.log(`Fetching all issues in current cycle${filterLabel ? ` with label: ${filterLabel}` : ""}...`);
169
+ // Build filter - label is optional, no status filter
170
170
  const issueFilter = {
171
171
  team: { id: { eq: teamId } },
172
172
  cycle: { id: { eq: cycleId } },
173
- state: { name: { in: syncStatuses } },
174
173
  };
175
174
  if (filterLabel) {
176
175
  issueFilter.labels = { name: { eq: filterLabel } };
@@ -181,7 +180,7 @@ Examples:
181
180
  });
182
181
  }
183
182
  if (issues.nodes.length === 0) {
184
- console.log(`No issues found in current cycle with ${syncStatuses.join("/")} status${filterLabel ? ` and label: ${filterLabel}` : ""}.`);
183
+ console.log(`No issues found in current cycle${filterLabel ? ` with label: ${filterLabel}` : ""}.`);
185
184
  }
186
185
  const tasks = [];
187
186
  let updatedCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,8 +43,14 @@ ttt work-on MP-624
43
43
  # Check current task status
44
44
  ttt status
45
45
 
46
- # Get full issue details
47
- ttt get-issue MP-624
46
+ # Show issue details from local data
47
+ ttt show MP-624
48
+
49
+ # Search issues by filters
50
+ ttt show --status "In Progress" --user me
51
+
52
+ # Export issues as markdown
53
+ ttt show --export
48
54
 
49
55
  # Mark task as blocked if waiting on dependency
50
56
  ttt status MP-624 --set blocked
@@ -108,10 +114,20 @@ The `ttt done` command behaves differently based on configured mode:
108
114
  └── output/ # Downloaded attachments
109
115
  ```
110
116
 
117
+ ## Project Validation
118
+
119
+ Before starting or completing tasks, run project validation:
120
+
121
+ 1. **Check for validation command/skill**: Look for `/validate`, `/check` commands or validation skills
122
+ 2. **Check package.json/Makefile**: Look for `lint`, `type-check`, `test` scripts
123
+
124
+ If no validation exists, suggest running `/ttt:write-validate` to create a project-specific validation command.
125
+
111
126
  ## Best Practices
112
127
 
113
128
  ### DO
114
129
  - Always `ttt sync` before starting work
130
+ - **Run project validation** before starting and before completing tasks
115
131
  - Use `ttt work-on next` for auto-prioritization
116
132
  - Include meaningful messages with `ttt done -m "..."`
117
133
  - Check `ttt status` to verify state before commits
@@ -120,17 +136,18 @@ The `ttt done` command behaves differently based on configured mode:
120
136
  - Don't manually edit `cycle.toon` - use CLI commands
121
137
  - Don't skip sync - local data may be stale
122
138
  - Don't forget to commit before `ttt done`
139
+ - Don't mark tasks done without running validation
123
140
 
124
141
  ## Troubleshooting
125
142
 
126
143
  ### "No cycle data found"
127
144
  Run `ttt sync` to fetch issues from Linear.
128
145
 
129
- ### "Issue not found in current cycle"
130
- The issue may not match filters. Check:
131
- - Issue is in active cycle
132
- - Issue status is Todo or In Progress
133
- - Issue matches configured label filter
146
+ ### "Issue not found in local data"
147
+ The issue may not be synced. Try:
148
+ - Run `ttt sync` to fetch latest issues
149
+ - Use `ttt show MP-624 --remote` to fetch directly from Linear
150
+ - Check if issue is in active cycle
134
151
 
135
152
  ### "LINEAR_API_KEY not set"
136
153
  ```bash
@@ -157,8 +174,9 @@ ttt work-on next # Move to next task
157
174
 
158
175
  ### Example: Check Specific Issue
159
176
  ```bash
160
- ttt get-issue MP-624 # Fetch from Linear
161
- ttt get-issue MP-624 --local # Show cached data
177
+ ttt show MP-624 # Show from local data
178
+ ttt show MP-624 --remote # Fetch from Linear
179
+ ttt show MP-624 --export # Export as markdown
162
180
  ```
163
181
 
164
182
  ## Important Rules
@@ -166,5 +184,5 @@ ttt get-issue MP-624 --local # Show cached data
166
184
  - Always verify `LINEAR_API_KEY` is set before operations
167
185
  - Run `ttt sync` at the start of each work session
168
186
  - Commit code before running `ttt done`
169
- - Use `--local` flag to avoid API calls when checking cached data
187
+ - Use `ttt show` (default) to read from local data; use `--remote` only when needed
170
188
  - Check `.ttt/output/` for downloaded attachments and images
@@ -1,71 +0,0 @@
1
- ---
2
- name: ttt-get-issue
3
- description: Fetch and display issue details from Linear
4
- arguments:
5
- - name: issue-id
6
- description: Issue ID to fetch (e.g., MP-624)
7
- required: true
8
- - name: local
9
- description: Only show from local data (add --local flag)
10
- required: false
11
- ---
12
-
13
- # TTT Get-Issue Command
14
-
15
- Fetch and display full issue details from Linear.
16
-
17
- ## Usage
18
-
19
- ### Fetch from Linear
20
- ```bash
21
- ttt get-issue {{ issue-id }}
22
- ```
23
-
24
- ### Show from Local Data Only
25
- If `local` is "true" or "--local":
26
- ```bash
27
- ttt get-issue {{ issue-id }} --local
28
- ```
29
-
30
- ## What This Does
31
-
32
- 1. Fetches full issue details from Linear API (or local cache if --local)
33
- 2. Displays comprehensive information:
34
- - Title and description
35
- - Status (both Linear and local)
36
- - Priority level
37
- - Labels
38
- - Assignee
39
- - Branch name
40
- - Parent issue (if subtask)
41
- - Attachments with local paths
42
- - Comments with timestamps
43
-
44
- ## Use Cases
45
-
46
- - Review issue details before starting work
47
- - Check requirements and acceptance criteria
48
- - View attachments and mockups
49
- - Read comment history and discussions
50
- - Verify issue status without full sync
51
-
52
- ## Output Format
53
-
54
- ```
55
- 📋 MP-624: Issue Title
56
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
57
- Status: In Progress | Local: in-progress
58
- Priority: 🔴 Urgent
59
- Labels: frontend, bug
60
- Assignee: developer@example.com
61
- Branch: feature/mp-624-fix-bug
62
-
63
- Description:
64
- [Full description content]
65
-
66
- Attachments:
67
- - screenshot.png (local: .ttt/output/MP-624/...)
68
-
69
- Comments:
70
- [Comment history]
71
- ```