team-toon-tack 1.6.3 → 1.7.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.
@@ -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}`);
@@ -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.0",
4
4
  "description": "Linear task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {