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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/commands/ttt-work-on.md +6 -7
- package/commands/ttt-write-work-on-skill.md +1 -3
- package/dist/bin/cli.js +14 -4
- package/dist/scripts/create.js +40 -11
- package/dist/scripts/edit.js +5 -4
- package/dist/scripts/lib/status-helpers.js +15 -0
- package/dist/scripts/lib/sync.js +1 -1
- package/package.json +1 -1
package/commands/ttt-work-on.md
CHANGED
|
@@ -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 <
|
|
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 →
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 === "
|
|
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
|
package/dist/scripts/create.js
CHANGED
|
@@ -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
|
|
62
|
+
async function resolveStatusId(config, adapter, teamId, statusName) {
|
|
63
|
+
if (!statusName)
|
|
64
64
|
return undefined;
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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[
|
|
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(
|
|
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(
|
|
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,
|
package/dist/scripts/edit.js
CHANGED
|
@@ -111,9 +111,10 @@ Examples:
|
|
|
111
111
|
const ids = [];
|
|
112
112
|
if (config.labels) {
|
|
113
113
|
for (const name of names) {
|
|
114
|
-
const
|
|
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[
|
|
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(([
|
|
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
|
/**
|
package/dist/scripts/lib/sync.js
CHANGED
|
@@ -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
|
|
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 });
|