team-toon-tack 2.1.0 ā 2.3.2
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 +19 -0
- package/.claude-plugin/plugin.json +18 -0
- package/README.md +57 -8
- package/README.zh-TW.md +57 -8
- package/commands/ttt:done.md +64 -0
- package/commands/ttt:show.md +126 -0
- package/commands/ttt:status.md +65 -0
- package/commands/ttt:sync.md +51 -0
- package/commands/ttt:work-on.md +79 -0
- package/commands/ttt:write-validate.md +122 -0
- package/dist/bin/cli.js +17 -8
- package/dist/scripts/config/teams.js +1 -1
- package/dist/scripts/get-issue.d.ts +1 -0
- package/dist/scripts/get-issue.js +61 -0
- package/dist/scripts/init.js +23 -155
- package/dist/scripts/lib/config-builder.d.ts +1 -1
- package/dist/scripts/lib/sync.d.ts +8 -0
- package/dist/scripts/lib/sync.js +41 -29
- package/dist/scripts/show.d.ts +1 -0
- package/dist/scripts/show.js +319 -0
- package/dist/scripts/sync.js +7 -8
- package/dist/scripts/work-on.js +1 -1
- package/package.json +5 -3
- package/skills/linear-task-manager/SKILL.md +188 -0
- package/templates/claude-code-commands/done-job.md +0 -45
- package/templates/claude-code-commands/sync-linear.md +0 -32
- package/templates/claude-code-commands/work-on.md +0 -62
- package/templates/config.example.toon +0 -37
- package/templates/local.example.toon +0 -16
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ttt:write-validate
|
|
3
|
+
description: Create a project validation command by detecting project type and existing lint/test configurations
|
|
4
|
+
arguments:
|
|
5
|
+
- name: command-name
|
|
6
|
+
description: Name for the command (default: validate)
|
|
7
|
+
required: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TTT Write-Validate Command
|
|
11
|
+
|
|
12
|
+
Create a project-specific validation command by analyzing the codebase.
|
|
13
|
+
|
|
14
|
+
## Process
|
|
15
|
+
|
|
16
|
+
### 1. Detect Project Type
|
|
17
|
+
|
|
18
|
+
Check for project indicators:
|
|
19
|
+
|
|
20
|
+
| File | Project Type |
|
|
21
|
+
|------|--------------|
|
|
22
|
+
| `package.json` | Node.js / Bun |
|
|
23
|
+
| `Cargo.toml` | Rust |
|
|
24
|
+
| `go.mod` | Go |
|
|
25
|
+
| `pyproject.toml`, `requirements.txt` | Python |
|
|
26
|
+
|
|
27
|
+
### 2. Find Existing Configurations
|
|
28
|
+
|
|
29
|
+
Look for static analysis configs:
|
|
30
|
+
|
|
31
|
+
**Linting**: `biome.json`, `.eslintrc.*`, `ruff.toml`
|
|
32
|
+
**Type Check**: `tsconfig.json`, `mypy.ini`
|
|
33
|
+
**Testing**: `jest.config.*`, `vitest.config.*`, `pytest.ini`
|
|
34
|
+
|
|
35
|
+
### 3. Check package.json Scripts
|
|
36
|
+
|
|
37
|
+
If Node.js project:
|
|
38
|
+
```bash
|
|
39
|
+
cat package.json | jq '.scripts | keys[]' 2>/dev/null
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Look for: `lint`, `type-check`, `test`, `check`, `validate`
|
|
43
|
+
|
|
44
|
+
### 4. Create Command File
|
|
45
|
+
|
|
46
|
+
Create `.claude/commands/{{ $1 | default: "validate" }}.md`:
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
---
|
|
50
|
+
name: {{ $1 | default: "validate" }}
|
|
51
|
+
description: Run project validation (lint, type-check, test)
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
# Project Validation
|
|
55
|
+
|
|
56
|
+
Run validation checks for this project.
|
|
57
|
+
|
|
58
|
+
## Process
|
|
59
|
+
|
|
60
|
+
### 1. Lint
|
|
61
|
+
\`\`\`bash
|
|
62
|
+
{{ lint-command }}
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
### 2. Type Check
|
|
66
|
+
\`\`\`bash
|
|
67
|
+
{{ type-check-command }}
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
### 3. Test (optional)
|
|
71
|
+
\`\`\`bash
|
|
72
|
+
{{ test-command }}
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
## Quick Validate All
|
|
76
|
+
|
|
77
|
+
\`\`\`bash
|
|
78
|
+
{{ combined-command }}
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
## On Failure
|
|
82
|
+
|
|
83
|
+
1. Show error output
|
|
84
|
+
2. Identify failing file(s) and line(s)
|
|
85
|
+
3. Suggest fixes
|
|
86
|
+
4. Re-run validation after fixes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 5. Output
|
|
90
|
+
|
|
91
|
+
After creating:
|
|
92
|
+
1. Show created file path
|
|
93
|
+
2. Display detected validation commands
|
|
94
|
+
3. Suggest running `/{{ $1 | default: "validate" }}` to test
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
### Node.js with Biome
|
|
99
|
+
```
|
|
100
|
+
Detected: Node.js project with Biome + TypeScript
|
|
101
|
+
Created: .claude/commands/validate.md
|
|
102
|
+
|
|
103
|
+
Commands:
|
|
104
|
+
Lint: bun run lint
|
|
105
|
+
Type: bun run type-check
|
|
106
|
+
Test: bun run test
|
|
107
|
+
|
|
108
|
+
Try: /validate
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Python with Ruff
|
|
112
|
+
```
|
|
113
|
+
Detected: Python project with Ruff + mypy
|
|
114
|
+
Created: .claude/commands/validate.md
|
|
115
|
+
|
|
116
|
+
Commands:
|
|
117
|
+
Lint: ruff check .
|
|
118
|
+
Type: mypy .
|
|
119
|
+
Test: pytest
|
|
120
|
+
|
|
121
|
+
Try: /validate
|
|
122
|
+
```
|
package/dist/bin/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ const COMMANDS = [
|
|
|
12
12
|
"work-on",
|
|
13
13
|
"done",
|
|
14
14
|
"status",
|
|
15
|
+
"show",
|
|
15
16
|
"config",
|
|
16
17
|
"help",
|
|
17
18
|
"version",
|
|
@@ -24,14 +25,15 @@ USAGE:
|
|
|
24
25
|
ttt <command> [options]
|
|
25
26
|
|
|
26
27
|
COMMANDS:
|
|
27
|
-
init
|
|
28
|
-
sync
|
|
29
|
-
work-on
|
|
30
|
-
done
|
|
31
|
-
status
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
init Initialize config files in current directory
|
|
29
|
+
sync Sync issues from Linear to local cycle.ttt
|
|
30
|
+
work-on Start working on a task (interactive or by ID)
|
|
31
|
+
done Mark current task as completed
|
|
32
|
+
status Show or modify task status
|
|
33
|
+
show Show issue details or search issues by filters
|
|
34
|
+
config Configure settings (status mappings, filters)
|
|
35
|
+
help Show this help message
|
|
36
|
+
version Show version
|
|
35
37
|
|
|
36
38
|
GLOBAL OPTIONS:
|
|
37
39
|
-d, --dir <path> Config directory (default: .ttt)
|
|
@@ -46,6 +48,9 @@ EXAMPLES:
|
|
|
46
48
|
ttt work-on next # Auto-select highest priority
|
|
47
49
|
ttt done # Complete current task
|
|
48
50
|
ttt done -m "Fixed the bug" # With completion message
|
|
51
|
+
ttt show MP-123 # Show issue from local data
|
|
52
|
+
ttt show --label frontend # Search local issues by label
|
|
53
|
+
ttt show --status "In Progress" # Filter by status
|
|
49
54
|
|
|
50
55
|
ENVIRONMENT:
|
|
51
56
|
LINEAR_API_KEY Required. Your Linear API key
|
|
@@ -117,6 +122,10 @@ async function main() {
|
|
|
117
122
|
process.argv = ["node", "status.js", ...commandArgs];
|
|
118
123
|
await import(`${scriptDir}status.js`);
|
|
119
124
|
break;
|
|
125
|
+
case "show":
|
|
126
|
+
process.argv = ["node", "show.js", ...commandArgs];
|
|
127
|
+
await import(`${scriptDir}show.js`);
|
|
128
|
+
break;
|
|
120
129
|
case "config":
|
|
121
130
|
process.argv = ["node", "config.js", ...commandArgs];
|
|
122
131
|
await import(`${scriptDir}config.js`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
|
-
import { getLinearClient, saveLocalConfig, } from "../utils.js";
|
|
3
2
|
import { getDefaultStatusTransitions } from "../lib/config-builder.js";
|
|
3
|
+
import { getLinearClient, saveLocalConfig, } from "../utils.js";
|
|
4
4
|
export async function configureTeams(_config, localConfig) {
|
|
5
5
|
console.log("š„ Configure Teams\n");
|
|
6
6
|
const client = getLinearClient();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { displayTaskFull } from "./lib/display.js";
|
|
2
|
+
import { fetchIssueDetail } from "./lib/sync.js";
|
|
3
|
+
import { loadCycleData } from "./utils.js";
|
|
4
|
+
async function getIssue() {
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
7
|
+
console.log(`Usage: ttt get-issue <issue-id> [--local]
|
|
8
|
+
|
|
9
|
+
Fetch and display issue details from Linear.
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
issue-id Issue ID (e.g., MP-624). Required.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--local Only show from local cycle data, don't fetch from Linear
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
ttt get-issue MP-624 # Fetch from Linear and display
|
|
19
|
+
ttt get-issue MP-624 --local # Show from local data only`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const localOnly = args.includes("--local");
|
|
23
|
+
const issueId = args.find((arg) => !arg.startsWith("-"));
|
|
24
|
+
if (!issueId) {
|
|
25
|
+
console.error("Issue ID is required.");
|
|
26
|
+
console.error("Usage: ttt get-issue <issue-id>");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// If local only, get from cycle data
|
|
30
|
+
if (localOnly) {
|
|
31
|
+
const data = await loadCycleData();
|
|
32
|
+
if (!data) {
|
|
33
|
+
console.error("No cycle data found. Run ttt sync first.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const task = data.tasks.find((t) => t.id === issueId || t.id === `MP-${issueId}`);
|
|
37
|
+
if (!task) {
|
|
38
|
+
console.error(`Issue ${issueId} not found in local data.`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
displayTaskFull(task, "š");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Fetch from Linear
|
|
45
|
+
console.log(`Fetching ${issueId} from Linear...`);
|
|
46
|
+
const task = await fetchIssueDetail(issueId);
|
|
47
|
+
if (!task) {
|
|
48
|
+
console.error(`Issue ${issueId} not found in Linear.`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Check local data for local status
|
|
52
|
+
const data = await loadCycleData();
|
|
53
|
+
if (data) {
|
|
54
|
+
const localTask = data.tasks.find((t) => t.id === issueId);
|
|
55
|
+
if (localTask) {
|
|
56
|
+
task.localStatus = localTask.localStatus;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
displayTaskFull(task, "š");
|
|
60
|
+
}
|
|
61
|
+
getIssue().catch(console.error);
|
package/dist/scripts/init.js
CHANGED
|
@@ -400,147 +400,26 @@ async function updateGitignore(tttDir, interactive) {
|
|
|
400
400
|
// Silently ignore gitignore errors
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
console.log("
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
choices: [
|
|
424
|
-
{
|
|
425
|
-
title: "No prefix (recommended)",
|
|
426
|
-
value: "",
|
|
427
|
-
description: "/work-on, /done-job, /sync-linear",
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
title: "ttt:",
|
|
431
|
-
value: "ttt:",
|
|
432
|
-
description: "/ttt:work-on, /ttt:done-job, /ttt:sync-linear",
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
title: "linear:",
|
|
436
|
-
value: "linear:",
|
|
437
|
-
description: "/linear:work-on, /linear:done-job, /linear:sync-linear",
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
title: "Custom...",
|
|
441
|
-
value: "custom",
|
|
442
|
-
description: "Enter your own prefix",
|
|
443
|
-
},
|
|
444
|
-
],
|
|
445
|
-
initial: 0,
|
|
446
|
-
});
|
|
447
|
-
let prefix = prefixChoice || "";
|
|
448
|
-
if (prefixChoice === "custom") {
|
|
449
|
-
const { customPrefix } = await prompts({
|
|
450
|
-
type: "text",
|
|
451
|
-
name: "customPrefix",
|
|
452
|
-
message: "Enter custom prefix (e.g., 'my:'):",
|
|
453
|
-
initial: "",
|
|
454
|
-
});
|
|
455
|
-
prefix = customPrefix || "";
|
|
456
|
-
}
|
|
457
|
-
// Find templates directory
|
|
458
|
-
// Try multiple locations: installed package, local dev
|
|
459
|
-
const possibleTemplatePaths = [
|
|
460
|
-
path.join(__dirname, "..", "templates", "claude-code-commands"),
|
|
461
|
-
path.join(__dirname, "..", "..", "templates", "claude-code-commands"),
|
|
462
|
-
path.join(process.cwd(), "templates", "claude-code-commands"),
|
|
463
|
-
];
|
|
464
|
-
let templateDir = null;
|
|
465
|
-
for (const p of possibleTemplatePaths) {
|
|
466
|
-
try {
|
|
467
|
-
await fs.access(p);
|
|
468
|
-
templateDir = p;
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
// Try next path
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
if (!templateDir) {
|
|
476
|
-
// Try to get repo URL from package.json
|
|
477
|
-
let repoUrl = "https://github.com/wayne930242/team-toon-tack";
|
|
478
|
-
try {
|
|
479
|
-
const pkgPaths = [
|
|
480
|
-
path.join(__dirname, "..", "package.json"),
|
|
481
|
-
path.join(__dirname, "..", "..", "package.json"),
|
|
482
|
-
];
|
|
483
|
-
for (const pkgPath of pkgPaths) {
|
|
484
|
-
try {
|
|
485
|
-
const pkgContent = await fs.readFile(pkgPath, "utf-8");
|
|
486
|
-
const pkg = JSON.parse(pkgContent);
|
|
487
|
-
if (pkg.repository?.url) {
|
|
488
|
-
// Parse git+https://github.com/user/repo.git format
|
|
489
|
-
repoUrl = pkg.repository.url
|
|
490
|
-
.replace(/^git\+/, "")
|
|
491
|
-
.replace(/\.git$/, "");
|
|
492
|
-
}
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
catch {
|
|
496
|
-
// Try next path
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
// Use default URL
|
|
502
|
-
}
|
|
503
|
-
console.log(" ā Could not find command templates. Please copy manually from:");
|
|
504
|
-
console.log(` ${repoUrl}/tree/main/templates/claude-code-commands`);
|
|
505
|
-
return { installed: false, prefix };
|
|
506
|
-
}
|
|
507
|
-
// Create .claude/commands directory
|
|
508
|
-
const commandsDir = path.join(process.cwd(), ".claude", "commands");
|
|
509
|
-
await fs.mkdir(commandsDir, { recursive: true });
|
|
510
|
-
// Copy and rename template files
|
|
511
|
-
const templateFiles = await fs.readdir(templateDir);
|
|
512
|
-
const commandFiles = templateFiles.filter((f) => f.endsWith(".md"));
|
|
513
|
-
for (const file of commandFiles) {
|
|
514
|
-
const baseName = file.replace(".md", "");
|
|
515
|
-
const newFileName = prefix ? `${prefix}${baseName}.md` : file;
|
|
516
|
-
const srcPath = path.join(templateDir, file);
|
|
517
|
-
const destPath = path.join(commandsDir, newFileName);
|
|
518
|
-
// Read template content
|
|
519
|
-
let content = await fs.readFile(srcPath, "utf-8");
|
|
520
|
-
// Update the name in frontmatter if prefix is used
|
|
521
|
-
if (prefix) {
|
|
522
|
-
content = content.replace(/^(---\s*\n[\s\S]*?name:\s*)(\S+)/m, `$1${prefix}${baseName}`);
|
|
523
|
-
}
|
|
524
|
-
// Modify content based on statusSource for work-on and done-job
|
|
525
|
-
if (statusSource === "local") {
|
|
526
|
-
if (baseName === "work-on" || baseName.endsWith("work-on")) {
|
|
527
|
-
// Update description for local mode
|
|
528
|
-
content = content.replace(/Select a task and update status to "In Progress" on both local and Linear\./, 'Select a task and update local status to "In Progress". (Linear will be updated when you run `sync --update`)');
|
|
529
|
-
// Add reminder after Complete section
|
|
530
|
-
content = content.replace(/Use `?\/done-job`? to mark task as completed/, "Use `/done-job` to mark task as completed\n\n### 7. Sync to Linear\n\nWhen ready to update Linear with all your changes:\n\n```bash\nttt sync --update\n```");
|
|
531
|
-
}
|
|
532
|
-
if (baseName === "done-job" || baseName.endsWith("done-job")) {
|
|
533
|
-
// Update description for local mode
|
|
534
|
-
content = content.replace(/Mark a task as done and update Linear with commit details\./, "Mark a task as done locally. (Run `ttt sync --update` to push changes to Linear)");
|
|
535
|
-
// Add reminder at the end
|
|
536
|
-
content = content.replace(/## What It Does\n\n- Linear issue status ā "Done"/, "## What It Does\n\n- Local status ā `completed`");
|
|
537
|
-
content += `\n## Sync to Linear\n\nAfter completing tasks, push all changes to Linear:\n\n\`\`\`bash\nttt sync --update\n\`\`\`\n`;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
await fs.writeFile(destPath, content, "utf-8");
|
|
541
|
-
console.log(` ā .claude/commands/${newFileName}`);
|
|
542
|
-
}
|
|
543
|
-
return { installed: true, prefix };
|
|
403
|
+
function showPluginInstallInstructions() {
|
|
404
|
+
console.log("\nš¤ Claude Code Plugin:");
|
|
405
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
406
|
+
console.log("ā Install team-toon-tack plugin in Claude Code: ā");
|
|
407
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤");
|
|
408
|
+
console.log("ā ā");
|
|
409
|
+
console.log("ā 1. Add marketplace: ā");
|
|
410
|
+
console.log("ā /plugin marketplace add wayne930242/team-toon-tack ā");
|
|
411
|
+
console.log("ā ā");
|
|
412
|
+
console.log("ā 2. Install plugin: ā");
|
|
413
|
+
console.log("ā /plugin install team-toon-tack@wayne930242 ā");
|
|
414
|
+
console.log("ā ā");
|
|
415
|
+
console.log("ā Available commands after install: ā");
|
|
416
|
+
console.log("ā /ttt:sync - Sync Linear issues ā");
|
|
417
|
+
console.log("ā /ttt:work-on - Start working on a task ā");
|
|
418
|
+
console.log("ā /ttt:done - Complete current task ā");
|
|
419
|
+
console.log("ā /ttt:status - Show/modify task status ā");
|
|
420
|
+
console.log("ā /ttt:show - Show/search issues ā");
|
|
421
|
+
console.log("ā ā");
|
|
422
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
544
423
|
}
|
|
545
424
|
async function init() {
|
|
546
425
|
const args = process.argv.slice(2);
|
|
@@ -732,8 +611,8 @@ async function init() {
|
|
|
732
611
|
console.log(` ā ${paths.localPath}`);
|
|
733
612
|
// Update .gitignore (always use relative path .ttt)
|
|
734
613
|
await updateGitignore(".ttt", options.interactive ?? true);
|
|
735
|
-
//
|
|
736
|
-
|
|
614
|
+
// Show Claude Code plugin installation instructions
|
|
615
|
+
showPluginInstallInstructions();
|
|
737
616
|
// Summary
|
|
738
617
|
console.log("\nā
Initialization complete!\n");
|
|
739
618
|
console.log("Configuration summary:");
|
|
@@ -762,21 +641,10 @@ async function init() {
|
|
|
762
641
|
if (statusTransitions.blocked) {
|
|
763
642
|
console.log(` Blocked: ${statusTransitions.blocked}`);
|
|
764
643
|
}
|
|
765
|
-
if (commandsInstalled) {
|
|
766
|
-
const cmdPrefix = commandPrefix ? `${commandPrefix}` : "";
|
|
767
|
-
console.log(` Claude commands: /${cmdPrefix}work-on, /${cmdPrefix}done-job, /${cmdPrefix}sync-linear`);
|
|
768
|
-
}
|
|
769
644
|
console.log("\nNext steps:");
|
|
770
645
|
console.log(" 1. Set LINEAR_API_KEY in your shell profile:");
|
|
771
646
|
console.log(` export LINEAR_API_KEY="${apiKey}"`);
|
|
772
647
|
console.log(" 2. Run sync: ttt sync");
|
|
773
|
-
|
|
774
|
-
const cmdPrefix = commandPrefix ? `${commandPrefix}` : "";
|
|
775
|
-
console.log(` 3. In Claude Code: /${cmdPrefix}work-on next`);
|
|
776
|
-
console.log(`\nš” Tip: Edit .claude/commands/${cmdPrefix}work-on.md to customize the "Verify" section for your project.`);
|
|
777
|
-
}
|
|
778
|
-
else {
|
|
779
|
-
console.log(" 3. Start working: ttt work-on");
|
|
780
|
-
}
|
|
648
|
+
console.log(" 3. Start working: ttt work-on");
|
|
781
649
|
}
|
|
782
650
|
init().catch(console.error);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompletionMode, Config, LocalConfig, QaPmTeamConfig, StatusTransitions, TeamConfig, UserConfig
|
|
1
|
+
import type { CompletionMode, Config, LabelConfig, LocalConfig, QaPmTeamConfig, StatusTransitions, TeamConfig, UserConfig } from "../utils.js";
|
|
2
2
|
export interface LinearTeam {
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
@@ -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
|
package/dist/scripts/lib/sync.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { getLinearClient, loadCycleData, saveCycleData,
|
|
1
|
+
import { getLinearClient, getPrioritySortIndex, loadCycleData, saveCycleData, } from "../utils.js";
|
|
2
2
|
import { getStatusTransitions } from "./linear.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Returns
|
|
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
|
|
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:
|
|
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);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|