team-toon-tack 2.0.3 → 2.3.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.
@@ -24,12 +24,13 @@ export function extractLinearImageUrls(text) {
24
24
  /(https?:\/\/uploads\.linear\.app\/[^\s)>\]]+)/g, // Plain Linear upload URLs
25
25
  ];
26
26
  for (const pattern of patterns) {
27
- let match;
28
- while ((match = pattern.exec(text)) !== null) {
27
+ let match = pattern.exec(text);
28
+ while (match) {
29
29
  const url = match[1];
30
30
  if (isLinearImageUrl(url) && !urls.includes(url)) {
31
31
  urls.push(url);
32
32
  }
33
+ match = pattern.exec(text);
33
34
  }
34
35
  }
35
36
  return urls;
@@ -66,7 +67,7 @@ export async function downloadLinearFile(url, issueId, attachmentId, outputDir)
66
67
  // Linear files require authentication
67
68
  const headers = {};
68
69
  if (process.env.LINEAR_API_KEY) {
69
- headers["Authorization"] = process.env.LINEAR_API_KEY;
70
+ headers.Authorization = process.env.LINEAR_API_KEY;
70
71
  }
71
72
  const response = await fetch(url, { headers });
72
73
  if (!response.ok) {
@@ -1,11 +1,19 @@
1
1
  import type { LinearClient } from "@linear/sdk";
2
2
  import { type Config, type LocalConfig, type Task } from "../utils.js";
3
+ export interface FetchIssueOptions {
4
+ client?: LinearClient;
5
+ }
3
6
  export interface SyncIssueOptions {
4
7
  config: Config;
5
8
  localConfig: LocalConfig;
6
9
  client?: LinearClient;
7
10
  preserveLocalStatus?: boolean;
8
11
  }
12
+ /**
13
+ * Fetch issue details from Linear without saving to local data
14
+ * Returns Task object or null if issue not found
15
+ */
16
+ export declare function fetchIssueDetail(issueId: string, options?: FetchIssueOptions): Promise<Task | null>;
9
17
  /**
10
18
  * Sync a single issue from Linear and update local cycle data
11
19
  * Returns the updated task or null if issue not found
@@ -1,17 +1,15 @@
1
- import { getLinearClient, loadCycleData, saveCycleData, getPrioritySortIndex, } from "../utils.js";
1
+ import { getLinearClient, getPrioritySortIndex, loadCycleData, saveCycleData, } from "../utils.js";
2
2
  import { getStatusTransitions } from "./linear.js";
3
3
  /**
4
- * Sync a single issue from Linear and update local cycle data
5
- * Returns the updated task or null if issue not found
4
+ * Fetch issue details from Linear without saving to local data
5
+ * Returns Task object or null if issue not found
6
6
  */
7
- export async function syncSingleIssue(issueId, options) {
8
- const { config, localConfig: _localConfig, preserveLocalStatus = true, } = options;
7
+ export async function fetchIssueDetail(issueId, options = {}) {
9
8
  const client = options.client ?? getLinearClient();
10
9
  // Search for the issue
11
10
  const searchResult = await client.searchIssues(issueId);
12
11
  const matchingIssue = searchResult.nodes.find((i) => i.identifier === issueId);
13
12
  if (!matchingIssue) {
14
- console.error(`Issue ${issueId} not found in Linear.`);
15
13
  return null;
16
14
  }
17
15
  // Fetch full issue data
@@ -41,34 +39,12 @@ export async function syncSingleIssue(issueId, options) {
41
39
  user: user?.displayName ?? user?.email,
42
40
  };
43
41
  }));
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
42
  const task = {
67
43
  id: issue.identifier,
68
44
  linearId: issue.id,
69
45
  title: issue.title,
70
46
  status: state ? state.name : "Unknown",
71
- localStatus: localStatus,
47
+ localStatus: "pending", // Default, will be overridden by sync if needed
72
48
  assignee: assigneeEmail,
73
49
  priority: issue.priority,
74
50
  labels: labelNames,
@@ -79,6 +55,42 @@ export async function syncSingleIssue(issueId, options) {
79
55
  attachments: attachments.length > 0 ? attachments : undefined,
80
56
  comments: comments.length > 0 ? comments : undefined,
81
57
  };
58
+ return task;
59
+ }
60
+ /**
61
+ * Sync a single issue from Linear and update local cycle data
62
+ * Returns the updated task or null if issue not found
63
+ */
64
+ export async function syncSingleIssue(issueId, options) {
65
+ const { config, localConfig: _localConfig, preserveLocalStatus = true, } = options;
66
+ const client = options.client ?? getLinearClient();
67
+ // Fetch issue details using shared function
68
+ const task = await fetchIssueDetail(issueId, { client });
69
+ if (!task) {
70
+ console.error(`Issue ${issueId} not found in Linear.`);
71
+ return null;
72
+ }
73
+ // Determine local status
74
+ const existingData = await loadCycleData();
75
+ if (preserveLocalStatus && existingData) {
76
+ const existingTask = existingData.tasks.find((t) => t.id === issueId);
77
+ if (existingTask) {
78
+ task.localStatus = existingTask.localStatus;
79
+ }
80
+ }
81
+ // Map remote status to local status if not preserving
82
+ if (!preserveLocalStatus) {
83
+ const transitions = getStatusTransitions(config);
84
+ if (task.status === transitions.done) {
85
+ task.localStatus = "completed";
86
+ }
87
+ else if (task.status === transitions.in_progress) {
88
+ task.localStatus = "in-progress";
89
+ }
90
+ else {
91
+ task.localStatus = "pending";
92
+ }
93
+ }
82
94
  // Update cycle data
83
95
  if (existingData) {
84
96
  const existingTasks = existingData.tasks.filter((t) => t.id !== issueId);
@@ -1,5 +1,5 @@
1
- import { getLinearClient, getPaths, getPrioritySortIndex, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveConfig, saveCycleData, } from "./utils.js";
2
1
  import { clearIssueImages, downloadLinearImage, ensureOutputDir, extractLinearImageUrls, isLinearImageUrl, } from "./lib/images.js";
2
+ import { getLinearClient, getPaths, getPrioritySortIndex, getTeamId, loadConfig, loadCycleData, loadLocalConfig, saveConfig, saveCycleData, } from "./utils.js";
3
3
  async function sync() {
4
4
  const args = process.argv.slice(2);
5
5
  // Handle help flag
@@ -35,6 +35,11 @@ export interface StatusTransitions {
35
35
  testing?: string;
36
36
  blocked?: string;
37
37
  }
38
+ export interface QaPmTeamConfig {
39
+ team: string;
40
+ testing_status: string;
41
+ }
42
+ export type CompletionMode = "simple" | "strict_review" | "upstream_strict" | "upstream_not_strict";
38
43
  export interface Config {
39
44
  teams: Record<string, TeamConfig>;
40
45
  users: Record<string, UserConfig>;
@@ -94,11 +99,14 @@ export interface CycleData {
94
99
  export interface LocalConfig {
95
100
  current_user: string;
96
101
  team: string;
97
- teams?: string[];
98
- qa_pm_team?: string;
102
+ dev_testing_status?: string;
103
+ qa_pm_teams?: QaPmTeamConfig[];
104
+ completion_mode?: CompletionMode;
99
105
  exclude_labels?: string[];
100
106
  label?: string;
101
107
  status_source?: "remote" | "local";
108
+ teams?: string[];
109
+ qa_pm_team?: string;
102
110
  }
103
111
  export declare function fileExists(filePath: string): Promise<boolean>;
104
112
  export declare function loadConfig(): Promise<Config>;
@@ -1,5 +1,5 @@
1
1
  import prompts from "prompts";
2
- import { PRIORITY_LABELS, displayTaskFull } from "./lib/display.js";
2
+ import { displayTaskFull, PRIORITY_LABELS } from "./lib/display.js";
3
3
  import { getStatusTransitions, updateIssueStatus } from "./lib/linear.js";
4
4
  import { getPrioritySortIndex, loadConfig, loadCycleData, loadLocalConfig, saveCycleData, } from "./utils.js";
5
5
  async function workOn() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
- "version": "2.0.3",
3
+ "version": "2.3.0",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,12 +13,14 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
- "templates"
16
+ ".claude-plugin",
17
+ "commands",
18
+ "skills"
17
19
  ],
18
20
  "scripts": {
19
21
  "build": "tsc",
20
22
  "lint": "biome lint .",
21
- "format": "biome format --write .",
23
+ "format": "biome check --write .",
22
24
  "type": "tsc --noEmit",
23
25
  "prepublishOnly": "npm run build",
24
26
  "release": "npm config set //registry.npmjs.org/:_authToken=$NPM_PUBLISH_KEY && npm publish"
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: linear-task-manager
3
+ description: Linear task management expert using ttt CLI. Manages task workflow, syncs issues, tracks status. Use when working with Linear issues, starting tasks, completing work, or checking task status.
4
+ ---
5
+
6
+ # Linear Task Manager
7
+
8
+ You are a Linear task management expert using the team-toon-tack (ttt) CLI tool.
9
+
10
+ ## Your Role
11
+
12
+ Help developers efficiently manage their Linear workflow:
13
+ - Sync issues from Linear to local cache
14
+ - Start working on tasks
15
+ - Track and update task status
16
+ - Complete tasks with proper documentation
17
+ - Fetch issue details on demand
18
+
19
+ ## Prerequisites
20
+
21
+ Ensure the project has:
22
+ 1. `LINEAR_API_KEY` environment variable set
23
+ 2. `.ttt/` directory initialized (run `ttt init` if not)
24
+ 3. `ttt` CLI installed (`npm install -g team-toon-tack`)
25
+
26
+ ## Core Workflows
27
+
28
+ ### 1. Starting a Work Session
29
+
30
+ ```bash
31
+ # Sync latest issues from Linear
32
+ ttt sync
33
+
34
+ # Pick a task to work on (interactive or auto-select)
35
+ ttt work-on next
36
+ # or
37
+ ttt work-on MP-624
38
+ ```
39
+
40
+ ### 2. During Development
41
+
42
+ ```bash
43
+ # Check current task status
44
+ ttt status
45
+
46
+ # Get full issue details
47
+ ttt get-issue MP-624
48
+
49
+ # Mark task as blocked if waiting on dependency
50
+ ttt status MP-624 --set blocked
51
+ ```
52
+
53
+ ### 3. Completing Work
54
+
55
+ ```bash
56
+ # Ensure code is committed first
57
+ git add . && git commit -m "feat: implement feature"
58
+
59
+ # Mark task as done with message
60
+ ttt done -m "Implemented feature with full test coverage"
61
+ ```
62
+
63
+ ### 4. Syncing Changes
64
+
65
+ ```bash
66
+ # Pull latest from Linear
67
+ ttt sync
68
+
69
+ # Push local status changes to Linear (if using local mode)
70
+ ttt sync --update
71
+ ```
72
+
73
+ ## Status Flow
74
+
75
+ ```
76
+ pending → in-progress → completed
77
+
78
+ blocked
79
+ ```
80
+
81
+ ### Local Status vs Linear Status
82
+
83
+ | Local Status | Linear Status |
84
+ |--------------|---------------|
85
+ | pending | Todo |
86
+ | in-progress | In Progress |
87
+ | completed | Done / Testing |
88
+ | blocked | (configurable) |
89
+
90
+ ## Completion Modes
91
+
92
+ The `ttt done` command behaves differently based on configured mode:
93
+
94
+ | Mode | Task Action | Parent Action |
95
+ |------|-------------|---------------|
96
+ | `simple` | → Done | → Done |
97
+ | `strict_review` | → Testing | → QA Testing |
98
+ | `upstream_strict` | → Done | → Testing |
99
+ | `upstream_not_strict` | → Done | → Testing (no fallback) |
100
+
101
+ ## File Structure
102
+
103
+ ```
104
+ .ttt/
105
+ ├── config.toon # Team configuration
106
+ ├── local.toon # Personal settings
107
+ ├── cycle.toon # Current cycle tasks (auto-generated)
108
+ └── output/ # Downloaded attachments
109
+ ```
110
+
111
+ ## Best Practices
112
+
113
+ ### DO
114
+ - Always `ttt sync` before starting work
115
+ - Use `ttt work-on next` for auto-prioritization
116
+ - Include meaningful messages with `ttt done -m "..."`
117
+ - Check `ttt status` to verify state before commits
118
+
119
+ ### DON'T
120
+ - Don't manually edit `cycle.toon` - use CLI commands
121
+ - Don't skip sync - local data may be stale
122
+ - Don't forget to commit before `ttt done`
123
+
124
+ ## Troubleshooting
125
+
126
+ ### "No cycle data found"
127
+ Run `ttt sync` to fetch issues from Linear.
128
+
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
134
+
135
+ ### "LINEAR_API_KEY not set"
136
+ ```bash
137
+ export LINEAR_API_KEY="lin_api_xxxxx"
138
+ ```
139
+
140
+ ## Examples
141
+
142
+ ### Example: Start Daily Work
143
+ ```bash
144
+ ttt sync # Get latest issues
145
+ ttt work-on next # Auto-select highest priority
146
+ # Read task details displayed
147
+ git checkout -b feature/mp-624-new-feature
148
+ # Start coding...
149
+ ```
150
+
151
+ ### Example: Complete and Move On
152
+ ```bash
153
+ git add . && git commit -m "feat: add new feature"
154
+ ttt done -m "Added feature with tests"
155
+ ttt work-on next # Move to next task
156
+ ```
157
+
158
+ ### Example: Check Specific Issue
159
+ ```bash
160
+ ttt get-issue MP-624 # Fetch from Linear
161
+ ttt get-issue MP-624 --local # Show cached data
162
+ ```
163
+
164
+ ## Important Rules
165
+
166
+ - Always verify `LINEAR_API_KEY` is set before operations
167
+ - Run `ttt sync` at the start of each work session
168
+ - Commit code before running `ttt done`
169
+ - Use `--local` flag to avoid API calls when checking cached data
170
+ - Check `.ttt/output/` for downloaded attachments and images
@@ -1,45 +0,0 @@
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
- ```
@@ -1,32 +0,0 @@
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
- ```
@@ -1,62 +0,0 @@
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. Commit with conventional format
32
-
33
- ### 4. Handle Blockers (if any)
34
-
35
- If you encounter a blocker (waiting for backend, design, external dependency):
36
-
37
- ```bash
38
- ttt status --set blocked
39
- ```
40
-
41
- Add a comment explaining the blocker, then move to another task.
42
-
43
- ### 5. Verify
44
-
45
- Run project-required verification before completing:
46
-
47
- ```bash
48
- # Run verification procedure defined in project
49
- # (e.g., type-check, lint, test, build)
50
- ```
51
-
52
- ### 6. Complete
53
-
54
- Use `/done-job` to mark task as completed
55
-
56
- ## Example Usage
57
-
58
- ```
59
- /work-on
60
- /work-on MP-624
61
- /work-on next
62
- ```
@@ -1,37 +0,0 @@
1
- # Linear Team Configuration
2
- # Copy this file to config.toon and fill in your team's data
3
- # Run `ttt init` to generate this interactively
4
-
5
- teams:
6
- main:
7
- id: YOUR_TEAM_UUID
8
- name: Your Team Name
9
- icon: Terminal
10
-
11
- users:
12
- user1:
13
- id: USER1_UUID
14
- email: user1@example.com
15
- displayName: User One
16
- role: Developer
17
-
18
- # Status mapping for Linear integration
19
- status_transitions:
20
- todo: Todo
21
- in_progress: In Progress
22
- done: Done
23
- testing: Testing
24
-
25
- # Priority ordering (optional, defaults shown)
26
- priority_order[1]: urgent
27
- priority_order[2]: high
28
- priority_order[3]: medium
29
- priority_order[4]: low
30
- priority_order[5]: none
31
-
32
- # Current cycle info (auto-managed by sync)
33
- # current_cycle:
34
- # id: CYCLE_UUID
35
- # name: Cycle #1
36
- # start_date: 2026-01-01
37
- # end_date: 2026-01-07
@@ -1,16 +0,0 @@
1
- # Local Configuration
2
- # Copy this file to local.toon and customize for your setup
3
- # This file is gitignored and contains personal settings
4
-
5
- # Your user key (must match a key in config.toon users section)
6
- # Only tasks assigned to this user will be synced
7
- current_user: user1
8
-
9
- # Primary team (required)
10
- team: main
11
-
12
- # Filter issues by label (optional)
13
- label: Frontend
14
-
15
- # Exclude issues with these labels (optional)
16
- # exclude_labels[1]: Backend