team-toon-tack 3.7.5 → 3.7.7

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/Trello task sync & management CLI with commands and skills",
16
- "version": "2.8.1"
16
+ "version": "2.8.5"
17
17
  }
18
18
  ]
19
19
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
3
  "description": "Linear/Trello task sync & management CLI for Claude Code - saves tokens vs MCP",
4
- "version": "2.8.1",
4
+ "version": "2.8.5",
5
5
  "author": {
6
6
  "name": "wayne930242",
7
7
  "email": "wayne930242@gmail.com"
@@ -55,15 +55,18 @@ Skipping phases is not "pragmatic", it is debt.
55
55
 
56
56
  ### 1. Branch
57
57
 
58
+ Check current branch and repo convention first (e.g. recent `git log --oneline -10`, existing branch naming pattern).
59
+ If unclear whether to branch or what name to use, ask the user instead of assuming.
60
+
58
61
  ```bash
59
- git checkout -b <suggested-branch-name>
62
+ git checkout -b <branch-name-matching-user-convention>
60
63
  ```
61
64
 
62
65
  ### 2. Plan
63
66
 
64
67
  Scope decides depth.
65
68
 
66
- - Unclear requirements or 3+ files touched → invoke `superpowers:brainstorming`, then `superpowers:writing-plans`.
69
+ - Unclear requirements or 3+ files touched → if your environment has a brainstorming/planning skill, use it; otherwise clarify scope with the user and write a short plan before coding.
67
70
  - Clear and small (≤2 files, obvious change) → state a 2–3 bullet plan inline before coding.
68
71
 
69
72
  Never go straight to code.
@@ -71,8 +74,6 @@ Planning collapses ambiguity before implementation.
71
74
 
72
75
  ### 3. Test First (TDD)
73
76
 
74
- Invoke `superpowers:test-driven-development`.
75
-
76
77
  Red → Green → Refactor per behavior:
77
78
 
78
79
  1. Write one failing test naming the behavior.
@@ -85,9 +86,7 @@ No production code without a failing test first.
85
86
 
86
87
  ### 4. Review (before `/ttt:done`)
87
88
 
88
- Invoke `superpowers:verification-before-completion`.
89
-
90
- Run and confirm output:
89
+ Verify before claiming completion — run and confirm output:
91
90
 
92
91
  - Full test suite passes.
93
92
  - `npm run lint` / `npm run type` clean (or project-specific commands).
@@ -59,7 +59,7 @@ Skipping phases is debt, not pragmatism.
59
59
 
60
60
  ## 1. Plan
61
61
 
62
- - Unclear scope or 3+ files → use `superpowers:brainstorming` `superpowers:writing-plans`.
62
+ - Unclear scope or 3+ files → use a brainstorming/planning skill if your environment has one, else clarify scope with the user first.
63
63
  - Small, clear change → state a 2–3 bullet plan inline before coding.
64
64
 
65
65
  Branch naming:
@@ -69,7 +69,6 @@ Branch naming:
69
69
 
70
70
  ## 2. Test First (TDD)
71
71
 
72
- Invoke `superpowers:test-driven-development`.
73
72
  No production code without a failing test first.
74
73
 
75
74
  Red → Green → Refactor:
@@ -89,7 +88,6 @@ Match existing style — do not refactor adjacent code.
89
88
 
90
89
  ## 4. Review (before `/ttt:done`)
91
90
 
92
- Invoke `superpowers:verification-before-completion`.
93
91
  Run every command and confirm the actual output.
94
92
 
95
93
  \`\`\`bash
package/dist/bin/cli.js CHANGED
@@ -49,8 +49,10 @@ COMMANDS:
49
49
  version Show version
50
50
 
51
51
  GLOBAL OPTIONS:
52
- -d, --dir <path> Config directory (default: .ttt)
52
+ --dir <path> Config directory (default: .ttt)
53
53
  Can also set via TOON_DIR environment variable
54
+ -d <path> Shortcut for --dir; ignored for create/edit/comment
55
+ (where -d means --description)
54
56
 
55
57
  EXAMPLES:
56
58
  ttt init # Initialize .ttt directory
@@ -76,12 +78,20 @@ More info: https://github.com/wayne930242/team-toon-tack
76
78
  function printVersion() {
77
79
  console.log(`team-toon-tack v${VERSION}`);
78
80
  }
79
- function parseGlobalArgs(args) {
81
+ // Subcommands where `-d` is a subcommand flag (e.g. --description) — never
82
+ // consume `-d` as the global config-dir flag for these. `--dir` always works.
83
+ const SHORT_D_RESERVED_FOR_SUBCOMMAND = new Set([
84
+ "create",
85
+ "edit",
86
+ "comment",
87
+ ]);
88
+ function parseGlobalArgs(command, args) {
80
89
  let dir = process.env.TOON_DIR || resolve(process.cwd(), ".ttt");
81
90
  const commandArgs = [];
91
+ const allowShortD = !SHORT_D_RESERVED_FOR_SUBCOMMAND.has(command);
82
92
  for (let i = 0; i < args.length; i++) {
83
93
  const arg = args[i];
84
- if (arg === "-d" || arg === "--dir") {
94
+ if (arg === "--dir" || (allowShortD && arg === "-d")) {
85
95
  dir = resolve(args[++i] || ".");
86
96
  }
87
97
  else {
@@ -105,7 +115,7 @@ async function main() {
105
115
  }
106
116
  const command = args[0];
107
117
  const restArgs = args.slice(1);
108
- const { dir, commandArgs } = parseGlobalArgs(restArgs);
118
+ const { dir, commandArgs } = parseGlobalArgs(command, restArgs);
109
119
  // Set TOON_DIR for scripts to use
110
120
  process.env.TOON_DIR = dir;
111
121
  // Load .ttt/.env (if present) and resolve configured Linear API key env
@@ -59,11 +59,39 @@ function resolveAssigneeId(config, assigneeKey) {
59
59
  }
60
60
  return user.id;
61
61
  }
62
- function resolveStatusId(config, statusName) {
63
- if (!statusName || !config.statuses)
62
+ async function resolveStatusId(config, adapter, teamId, statusName) {
63
+ if (!statusName)
64
64
  return undefined;
65
- const entry = Object.entries(config.statuses).find(([, s]) => s.name.toLowerCase() === statusName.toLowerCase());
66
- return entry ? entry[0] : undefined;
65
+ // Resolve `status_transitions` aliases (e.g. "todo" → "Todo", "in_progress" "In Progress")
66
+ const transitions = config.status_transitions;
67
+ let targetName = statusName;
68
+ if (transitions) {
69
+ const aliasKey = statusName.toLowerCase().replace(/[\s-]+/g, "_");
70
+ const aliased = aliasKey === "todo"
71
+ ? Array.isArray(transitions.todo)
72
+ ? transitions.todo[0]
73
+ : transitions.todo
74
+ : aliasKey === "in_progress"
75
+ ? transitions.in_progress
76
+ : aliasKey === "done"
77
+ ? transitions.done
78
+ : aliasKey === "testing"
79
+ ? transitions.testing
80
+ : aliasKey === "blocked"
81
+ ? transitions.blocked
82
+ : undefined;
83
+ if (aliased)
84
+ targetName = aliased;
85
+ }
86
+ const statuses = await adapter.getStatuses(teamId);
87
+ const target = statuses.find((s) => s.name.toLowerCase() === targetName.toLowerCase());
88
+ if (!target) {
89
+ console.error(`Status "${statusName}" not found. Available: ${statuses
90
+ .map((s) => s.name)
91
+ .join(", ")}`);
92
+ process.exit(1);
93
+ }
94
+ return target.id;
67
95
  }
68
96
  function resolveLabelIds(config, labelArg) {
69
97
  if (!labelArg || !config.labels)
@@ -71,9 +99,11 @@ function resolveLabelIds(config, labelArg) {
71
99
  const names = labelArg.split(",").map((n) => n.trim());
72
100
  const ids = [];
73
101
  for (const name of names) {
74
- const entry = Object.entries(config.labels).find(([, l]) => l.name.toLowerCase() === name.toLowerCase());
102
+ const lower = name.toLowerCase();
103
+ // Match by config key (e.g. "backend_v3") OR display name (e.g. "Backend-v3")
104
+ const entry = Object.entries(config.labels).find(([key, l]) => key.toLowerCase() === lower || l.name.toLowerCase() === lower);
75
105
  if (entry) {
76
- ids.push(entry[0]);
106
+ ids.push(entry[1].id);
77
107
  }
78
108
  else {
79
109
  console.error(`Warning: label "${name}" not found in config, skipping.`);
@@ -81,10 +111,9 @@ function resolveLabelIds(config, labelArg) {
81
111
  }
82
112
  return ids.length > 0 ? ids : undefined;
83
113
  }
84
- async function resolveParentSourceId(config, parentIdentifier) {
114
+ async function resolveParentSourceId(adapter, parentIdentifier) {
85
115
  if (!parentIdentifier)
86
116
  return undefined;
87
- const adapter = createAdapter(config);
88
117
  const parent = await adapter.searchIssue(parentIdentifier);
89
118
  if (!parent) {
90
119
  console.error(`Parent issue "${parentIdentifier}" not found.`);
@@ -174,11 +203,11 @@ Examples:
174
203
  }
175
204
  }
176
205
  console.log("Creating issue...");
206
+ const adapter = createAdapter(config);
177
207
  const assigneeId = resolveAssigneeId(config, assigneeKey);
178
- const statusId = resolveStatusId(config, statusName);
208
+ const statusId = await resolveStatusId(config, adapter, teamId, statusName);
179
209
  const labelIds = resolveLabelIds(config, labelArg);
180
- const parentSourceId = await resolveParentSourceId(config, parentId);
181
- const adapter = createAdapter(config);
210
+ const parentSourceId = await resolveParentSourceId(adapter, parentId);
182
211
  const currentCycle = await adapter.getCurrentCycle(teamId);
183
212
  const options = {
184
213
  teamId,
@@ -111,9 +111,10 @@ Examples:
111
111
  const ids = [];
112
112
  if (config.labels) {
113
113
  for (const name of names) {
114
- const entry = Object.entries(config.labels).find(([, l]) => l.name.toLowerCase() === name.toLowerCase());
114
+ const lower = name.toLowerCase();
115
+ const entry = Object.entries(config.labels).find(([key, l]) => key.toLowerCase() === lower || l.name.toLowerCase() === lower);
115
116
  if (entry) {
116
- ids.push(entry[0]);
117
+ ids.push(entry[1].id);
117
118
  }
118
119
  else {
119
120
  console.error(`Warning: label "${name}" not found in config, skipping.`);
@@ -173,9 +174,9 @@ Examples:
173
174
  }
174
175
  case "labels": {
175
176
  if (config.labels) {
176
- const labelChoices = Object.entries(config.labels).map(([id, l]) => ({
177
+ const labelChoices = Object.entries(config.labels).map(([, l]) => ({
177
178
  name: l.name,
178
- value: id,
179
+ value: l.id,
179
180
  checked: task?.labels.includes(l.name) ?? false,
180
181
  }));
181
182
  fields.labelIds = await checkbox({
@@ -76,6 +76,21 @@ export function resolveLocalStatus(existingLocalStatus, remoteStatus, transition
76
76
  remoteLocalStatus === "blocked") {
77
77
  return remoteLocalStatus;
78
78
  }
79
+ // Remote already advanced past a stale local "pending" cache (e.g. another
80
+ // checkout/session started the task first) — adopt it, otherwise the task
81
+ // stays selectable by `work-on next` even though it's already claimed.
82
+ if (remoteLocalStatus === "in-progress" &&
83
+ existingLocalStatus === "pending") {
84
+ return remoteLocalStatus;
85
+ }
86
+ // "in-review" is never set by a local command (only by a prior sync), so
87
+ // it can't be an unconfirmed optimistic write. If remote has since moved
88
+ // off it (e.g. sent back to in-progress after failing review), that's an
89
+ // explicit remote change — adopt it instead of leaving the task stuck
90
+ // showing "in-review" forever.
91
+ if (existingLocalStatus === "in-review") {
92
+ return remoteLocalStatus;
93
+ }
79
94
  return existingLocalStatus;
80
95
  }
81
96
  /**
@@ -62,7 +62,7 @@ export async function fetchIssueDetail(issueId, options = {}) {
62
62
  * Returns the updated task or null if issue not found
63
63
  */
64
64
  export async function syncSingleIssue(issueId, options) {
65
- const { config, localConfig, preserveLocalStatus = true, } = options;
65
+ const { config, localConfig, preserveLocalStatus = true } = options;
66
66
  const client = options.client ?? getLinearClient();
67
67
  // Fetch issue details using shared function
68
68
  let task = await fetchIssueDetail(issueId, { client });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-toon-tack",
3
- "version": "3.7.5",
3
+ "version": "3.7.7",
4
4
  "description": "Linear & Trello task sync & management CLI with TOON format",
5
5
  "type": "module",
6
6
  "bin": {