team-toon-tack 1.6.1 → 1.6.3
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/dist/scripts/config/filters.js +0 -25
- package/dist/scripts/config/show.js +0 -1
- package/dist/scripts/config.js +1 -1
- package/dist/scripts/init.js +0 -2
- package/dist/scripts/lib/config-builder.d.ts +1 -1
- package/dist/scripts/lib/config-builder.js +11 -9
- package/dist/scripts/status.js +16 -3
- package/dist/scripts/sync.js +0 -8
- package/dist/scripts/utils.d.ts +0 -1
- package/dist/scripts/work-on.js +7 -7
- package/package.json +1 -1
- package/templates/claude-code-commands/work-on.md +14 -3
- package/templates/config.example.toon +20 -65
- package/templates/local.example.toon +7 -4
|
@@ -9,10 +9,6 @@ export async function configureFilters(config, localConfig) {
|
|
|
9
9
|
filter: { team: { id: { eq: teamId } } },
|
|
10
10
|
});
|
|
11
11
|
const labels = labelsData.nodes;
|
|
12
|
-
// Fetch users
|
|
13
|
-
const team = await client.team(teamId);
|
|
14
|
-
const members = await team.members();
|
|
15
|
-
const users = members.nodes;
|
|
16
12
|
// Label filter (optional)
|
|
17
13
|
const labelChoices = [
|
|
18
14
|
{ title: "(No filter - sync all labels)", value: "" },
|
|
@@ -38,34 +34,13 @@ export async function configureFilters(config, localConfig) {
|
|
|
38
34
|
selected: localConfig.exclude_labels?.includes(l.name),
|
|
39
35
|
})),
|
|
40
36
|
});
|
|
41
|
-
// Exclude users
|
|
42
|
-
const excludeUsersResponse = await prompts({
|
|
43
|
-
type: "multiselect",
|
|
44
|
-
name: "excludeUsers",
|
|
45
|
-
message: "Select users to exclude (space to select):",
|
|
46
|
-
choices: users.map((u) => {
|
|
47
|
-
const key = (u.displayName || u.name || u.email?.split("@")[0] || "user")
|
|
48
|
-
.toLowerCase()
|
|
49
|
-
.replace(/[^a-z0-9]/g, "_");
|
|
50
|
-
return {
|
|
51
|
-
title: `${u.displayName || u.name} (${u.email})`,
|
|
52
|
-
value: key,
|
|
53
|
-
selected: localConfig.exclude_assignees?.includes(key),
|
|
54
|
-
};
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
37
|
localConfig.label = labelResponse.label || undefined;
|
|
58
38
|
localConfig.exclude_labels =
|
|
59
39
|
excludeLabelsResponse.excludeLabels?.length > 0
|
|
60
40
|
? excludeLabelsResponse.excludeLabels
|
|
61
41
|
: undefined;
|
|
62
|
-
localConfig.exclude_assignees =
|
|
63
|
-
excludeUsersResponse.excludeUsers?.length > 0
|
|
64
|
-
? excludeUsersResponse.excludeUsers
|
|
65
|
-
: undefined;
|
|
66
42
|
await saveLocalConfig(localConfig);
|
|
67
43
|
console.log("\n✅ Filters updated:");
|
|
68
44
|
console.log(` Label: ${localConfig.label || "(all)"}`);
|
|
69
45
|
console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
|
|
70
|
-
console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
|
|
71
46
|
}
|
|
@@ -15,7 +15,6 @@ export function showConfig(config, localConfig) {
|
|
|
15
15
|
console.log("\nFilters:");
|
|
16
16
|
console.log(` Label: ${localConfig.label || "(all)"}`);
|
|
17
17
|
console.log(` Exclude labels: ${localConfig.exclude_labels?.join(", ") || "(none)"}`);
|
|
18
|
-
console.log(` Exclude users: ${localConfig.exclude_assignees?.join(", ") || "(none)"}`);
|
|
19
18
|
// Status Mappings
|
|
20
19
|
console.log("\nStatus Mappings:");
|
|
21
20
|
if (config.status_transitions) {
|
package/dist/scripts/config.js
CHANGED
|
@@ -12,7 +12,7 @@ async function config() {
|
|
|
12
12
|
Subcommands:
|
|
13
13
|
show Show current configuration
|
|
14
14
|
status Configure status mappings (todo, in_progress, done, testing)
|
|
15
|
-
filters Configure filters (label, exclude_labels
|
|
15
|
+
filters Configure filters (label, exclude_labels)
|
|
16
16
|
teams Configure team selection
|
|
17
17
|
|
|
18
18
|
Examples:
|
package/dist/scripts/init.js
CHANGED
|
@@ -328,8 +328,6 @@ async function init() {
|
|
|
328
328
|
localConfig.teams = existingLocal.teams;
|
|
329
329
|
if (existingLocal.label)
|
|
330
330
|
localConfig.label = existingLocal.label;
|
|
331
|
-
if (existingLocal.exclude_assignees)
|
|
332
|
-
localConfig.exclude_assignees = existingLocal.exclude_assignees;
|
|
333
331
|
if (existingLocal.exclude_labels)
|
|
334
332
|
localConfig.exclude_labels = existingLocal.exclude_labels;
|
|
335
333
|
}
|
|
@@ -38,4 +38,4 @@ export declare function getDefaultStatusTransitions(states: LinearState[]): Stat
|
|
|
38
38
|
export declare function buildConfig(teams: LinearTeam[], users: LinearUser[], labels: LinearLabel[], states: LinearState[], statusTransitions: StatusTransitions, currentCycle?: LinearCycle): Config;
|
|
39
39
|
export declare function findUserKey(usersConfig: Record<string, UserConfig>, userId: string): string;
|
|
40
40
|
export declare function findTeamKey(teamsConfig: Record<string, TeamConfig>, teamId: string): string;
|
|
41
|
-
export declare function buildLocalConfig(currentUserKey: string, primaryTeamKey: string, selectedTeamKeys: string[], defaultLabel?: string, excludeLabels?: string[]
|
|
41
|
+
export declare function buildLocalConfig(currentUserKey: string, primaryTeamKey: string, selectedTeamKeys: string[], defaultLabel?: string, excludeLabels?: string[]): LocalConfig;
|
|
@@ -50,19 +50,24 @@ export function buildStatusesConfig(states) {
|
|
|
50
50
|
}
|
|
51
51
|
return config;
|
|
52
52
|
}
|
|
53
|
+
// Helper to find status by keyword (case insensitive)
|
|
54
|
+
function findStatusByKeyword(states, keywords) {
|
|
55
|
+
const lowerKeywords = keywords.map((k) => k.toLowerCase());
|
|
56
|
+
return states.find((s) => lowerKeywords.some((k) => s.name.toLowerCase().includes(k)))?.name;
|
|
57
|
+
}
|
|
53
58
|
export function getDefaultStatusTransitions(states) {
|
|
54
59
|
const defaultTodo = states.find((s) => s.type === "unstarted")?.name ||
|
|
55
|
-
states
|
|
60
|
+
findStatusByKeyword(states, ["todo", "pending"]) ||
|
|
56
61
|
states[0]?.name ||
|
|
57
62
|
"Todo";
|
|
58
63
|
const defaultInProgress = states.find((s) => s.type === "started")?.name ||
|
|
59
|
-
states
|
|
64
|
+
findStatusByKeyword(states, ["in progress", "progress"]) ||
|
|
60
65
|
"In Progress";
|
|
61
66
|
const defaultDone = states.find((s) => s.type === "completed")?.name ||
|
|
62
|
-
states
|
|
67
|
+
findStatusByKeyword(states, ["done", "complete"]) ||
|
|
63
68
|
"Done";
|
|
64
|
-
const defaultTesting = states
|
|
65
|
-
|
|
69
|
+
const defaultTesting = findStatusByKeyword(states, ["testing", "review"]) ||
|
|
70
|
+
undefined;
|
|
66
71
|
return {
|
|
67
72
|
todo: defaultTodo,
|
|
68
73
|
in_progress: defaultInProgress,
|
|
@@ -102,15 +107,12 @@ export function findTeamKey(teamsConfig, teamId) {
|
|
|
102
107
|
return (Object.entries(teamsConfig).find(([_, t]) => t.id === teamId)?.[0] ||
|
|
103
108
|
Object.keys(teamsConfig)[0]);
|
|
104
109
|
}
|
|
105
|
-
export function buildLocalConfig(currentUserKey, primaryTeamKey, selectedTeamKeys, defaultLabel, excludeLabels
|
|
110
|
+
export function buildLocalConfig(currentUserKey, primaryTeamKey, selectedTeamKeys, defaultLabel, excludeLabels) {
|
|
106
111
|
return {
|
|
107
112
|
current_user: currentUserKey,
|
|
108
113
|
team: primaryTeamKey,
|
|
109
114
|
teams: selectedTeamKeys.length > 1 ? selectedTeamKeys : undefined,
|
|
110
115
|
label: defaultLabel,
|
|
111
116
|
exclude_labels: excludeLabels && excludeLabels.length > 0 ? excludeLabels : undefined,
|
|
112
|
-
exclude_assignees: excludeAssignees && excludeAssignees.length > 0
|
|
113
|
-
? excludeAssignees
|
|
114
|
-
: undefined,
|
|
115
117
|
};
|
|
116
118
|
}
|
package/dist/scripts/status.js
CHANGED
|
@@ -135,12 +135,13 @@ Examples:
|
|
|
135
135
|
console.error(`Unknown status: ${setStatus}`);
|
|
136
136
|
process.exit(1);
|
|
137
137
|
}
|
|
138
|
+
// Track if we need to save
|
|
139
|
+
let needsSave = false;
|
|
140
|
+
const oldLocalStatus = task.localStatus;
|
|
138
141
|
// Update local status
|
|
139
142
|
if (newLocalStatus && newLocalStatus !== task.localStatus) {
|
|
140
|
-
const oldStatus = task.localStatus;
|
|
141
143
|
task.localStatus = newLocalStatus;
|
|
142
|
-
|
|
143
|
-
console.log(`Local: ${task.id} ${oldStatus} → ${newLocalStatus}`);
|
|
144
|
+
needsSave = true;
|
|
144
145
|
}
|
|
145
146
|
// Update Linear status
|
|
146
147
|
if (newLinearStatus || newLocalStatus) {
|
|
@@ -151,10 +152,22 @@ Examples:
|
|
|
151
152
|
if (targetStateName) {
|
|
152
153
|
const success = await updateIssueStatus(task.linearId, targetStateName, config, localConfig.team);
|
|
153
154
|
if (success) {
|
|
155
|
+
task.status = targetStateName;
|
|
156
|
+
needsSave = true;
|
|
154
157
|
console.log(`Linear: ${task.id} → ${targetStateName}`);
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
}
|
|
161
|
+
// Save if anything changed
|
|
162
|
+
if (needsSave) {
|
|
163
|
+
await saveCycleData(data);
|
|
164
|
+
if (newLocalStatus && newLocalStatus !== oldLocalStatus) {
|
|
165
|
+
console.log(`Local: ${task.id} ${oldLocalStatus} → ${newLocalStatus}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (newLocalStatus) {
|
|
169
|
+
console.log(`Local: ${task.id} already ${newLocalStatus}`);
|
|
170
|
+
}
|
|
158
171
|
}
|
|
159
172
|
// Display task info using shared function
|
|
160
173
|
displayTaskWithStatus(task);
|
package/dist/scripts/sync.js
CHANGED
|
@@ -28,10 +28,6 @@ Examples:
|
|
|
28
28
|
const localConfig = await loadLocalConfig();
|
|
29
29
|
const client = getLinearClient();
|
|
30
30
|
const teamId = getTeamId(config, localConfig.team);
|
|
31
|
-
// Build excluded emails from local config
|
|
32
|
-
const excludedEmails = new Set((localConfig.exclude_assignees ?? [])
|
|
33
|
-
.map((key) => config.users[key]?.email)
|
|
34
|
-
.filter(Boolean));
|
|
35
31
|
// Build excluded labels set
|
|
36
32
|
const excludedLabels = new Set(localConfig.exclude_labels ?? []);
|
|
37
33
|
// Phase 1: Fetch active cycle directly from team
|
|
@@ -139,10 +135,6 @@ Examples:
|
|
|
139
135
|
const issue = await client.issue(issueNode.id);
|
|
140
136
|
const assignee = await issue.assignee;
|
|
141
137
|
const assigneeEmail = assignee?.email;
|
|
142
|
-
// Skip excluded assignees
|
|
143
|
-
if (assigneeEmail && excludedEmails.has(assigneeEmail)) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
138
|
const labels = await issue.labels();
|
|
147
139
|
const labelNames = labels.nodes.map((l) => l.name);
|
|
148
140
|
// Skip if any label is in excluded list
|
package/dist/scripts/utils.d.ts
CHANGED
package/dist/scripts/work-on.js
CHANGED
|
@@ -25,12 +25,11 @@ Examples:
|
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
const localConfig = await loadLocalConfig();
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
.map((key) => config.users[key]?.email)
|
|
31
|
-
.filter(Boolean));
|
|
28
|
+
// Get current user email for filtering
|
|
29
|
+
const currentUserEmail = config.users[localConfig.current_user]?.email;
|
|
32
30
|
const pendingTasks = data.tasks
|
|
33
|
-
.filter((t) => t.localStatus === "pending" &&
|
|
31
|
+
.filter((t) => t.localStatus === "pending" &&
|
|
32
|
+
(!currentUserEmail || t.assignee === currentUserEmail))
|
|
34
33
|
.sort((a, b) => {
|
|
35
34
|
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
36
35
|
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
@@ -84,16 +83,17 @@ Examples:
|
|
|
84
83
|
// Mark as In Progress
|
|
85
84
|
if (task.localStatus === "pending") {
|
|
86
85
|
task.localStatus = "in-progress";
|
|
87
|
-
await saveCycleData(data);
|
|
88
|
-
console.log(`Local: ${task.id} → in-progress`);
|
|
89
86
|
// Update Linear
|
|
90
87
|
if (task.linearId && process.env.LINEAR_API_KEY) {
|
|
91
88
|
const transitions = getStatusTransitions(config);
|
|
92
89
|
const success = await updateIssueStatus(task.linearId, transitions.in_progress, config, localConfig.team);
|
|
93
90
|
if (success) {
|
|
91
|
+
task.status = transitions.in_progress;
|
|
94
92
|
console.log(`Linear: ${task.id} → ${transitions.in_progress}`);
|
|
95
93
|
}
|
|
96
94
|
}
|
|
95
|
+
await saveCycleData(data);
|
|
96
|
+
console.log(`Local: ${task.id} → in-progress`);
|
|
97
97
|
}
|
|
98
98
|
// Display task info
|
|
99
99
|
displayTaskFull(task, "👷");
|
package/package.json
CHANGED
|
@@ -28,9 +28,20 @@ Script displays title, description, priority, labels, and attachments.
|
|
|
28
28
|
1. Read the issue description carefully
|
|
29
29
|
2. Explore related code
|
|
30
30
|
3. Implement the fix/feature
|
|
31
|
-
4.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
4. Commit with conventional format
|
|
32
|
+
|
|
33
|
+
### 4. Verify
|
|
34
|
+
|
|
35
|
+
Run project-required verification before completing:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Run verification procedure defined in project
|
|
39
|
+
# (e.g., type-check, lint, test, build)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 5. Complete
|
|
43
|
+
|
|
44
|
+
Use `/done-job` to mark task as completed
|
|
34
45
|
|
|
35
46
|
## Example Usage
|
|
36
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Linear Team Configuration
|
|
2
2
|
# Copy this file to config.toon and fill in your team's data
|
|
3
|
-
# Run `
|
|
3
|
+
# Run `ttt init` to generate this interactively
|
|
4
4
|
|
|
5
5
|
teams:
|
|
6
6
|
main:
|
|
@@ -12,71 +12,26 @@ users:
|
|
|
12
12
|
user1:
|
|
13
13
|
id: USER1_UUID
|
|
14
14
|
email: user1@example.com
|
|
15
|
-
displayName:
|
|
15
|
+
displayName: User One
|
|
16
16
|
role: Developer
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
frontend:
|
|
20
|
-
id: LABEL_UUID
|
|
21
|
-
name: Frontend
|
|
22
|
-
color: "#f2c94c"
|
|
23
|
-
backend:
|
|
24
|
-
id: LABEL_UUID
|
|
25
|
-
name: Backend
|
|
26
|
-
color: "#5e6ad2"
|
|
27
|
-
bug:
|
|
28
|
-
id: LABEL_UUID
|
|
29
|
-
name: Bug
|
|
30
|
-
color: "#EB5757"
|
|
31
|
-
feature:
|
|
32
|
-
id: LABEL_UUID
|
|
33
|
-
name: Feature
|
|
34
|
-
color: "#BB87FC"
|
|
35
|
-
|
|
36
|
-
priorities:
|
|
37
|
-
urgent:
|
|
38
|
-
value: 1
|
|
39
|
-
name: Urgent
|
|
40
|
-
high:
|
|
41
|
-
value: 2
|
|
42
|
-
name: High
|
|
43
|
-
medium:
|
|
44
|
-
value: 3
|
|
45
|
-
name: Medium
|
|
46
|
-
low:
|
|
47
|
-
value: 4
|
|
48
|
-
name: Low
|
|
49
|
-
|
|
50
|
-
statuses:
|
|
51
|
-
backlog:
|
|
52
|
-
name: Backlog
|
|
53
|
-
type: backlog
|
|
54
|
-
todo:
|
|
55
|
-
name: Todo
|
|
56
|
-
type: unstarted
|
|
57
|
-
in_progress:
|
|
58
|
-
name: In Progress
|
|
59
|
-
type: started
|
|
60
|
-
in_review:
|
|
61
|
-
name: In Review
|
|
62
|
-
type: started
|
|
63
|
-
testing:
|
|
64
|
-
name: Testing
|
|
65
|
-
type: started
|
|
66
|
-
done:
|
|
67
|
-
name: Done
|
|
68
|
-
type: completed
|
|
69
|
-
canceled:
|
|
70
|
-
name: Canceled
|
|
71
|
-
type: canceled
|
|
72
|
-
|
|
18
|
+
# Status mapping for Linear integration
|
|
73
19
|
status_transitions:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
77
31
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
# This file is gitignored and contains personal settings
|
|
4
4
|
|
|
5
5
|
# Your user key (must match a key in config.toon users section)
|
|
6
|
+
# Only tasks assigned to this user will be synced
|
|
6
7
|
current_user: user1
|
|
7
8
|
|
|
8
|
-
#
|
|
9
|
+
# Primary team (required)
|
|
10
|
+
team: main
|
|
11
|
+
|
|
12
|
+
# Filter issues by label (optional)
|
|
9
13
|
label: Frontend
|
|
10
14
|
|
|
11
|
-
# Exclude issues
|
|
12
|
-
#
|
|
13
|
-
# exclude_assignees[1]: backend_dev
|
|
15
|
+
# Exclude issues with these labels (optional)
|
|
16
|
+
# exclude_labels[1]: Backend
|