taskin 1.0.3 → 1.0.4
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/README.md +50 -1
- package/dist/index.js +319 -47
- package/package.json +8 -1
- package/sounds/README.md +82 -0
- package/sounds/finish.mp3 +0 -0
- package/sounds/start.mp3 +0 -0
- package/dist/index.d.ts +0 -383
package/README.md
CHANGED
|
@@ -98,6 +98,7 @@ Taskin is built as a modular ecosystem. Besides the CLI, you can use individual
|
|
|
98
98
|
|
|
99
99
|
- `taskin init` - Initialize Taskin in your project with interactive setup
|
|
100
100
|
- `taskin list` - List all tasks
|
|
101
|
+
- `taskin new` - Create a new task (alias: `create`)
|
|
101
102
|
- `taskin start <id>` - Start working on a task
|
|
102
103
|
- `taskin pause <id>` - Pause work on a task
|
|
103
104
|
- `taskin finish <id>` - Complete a task
|
|
@@ -154,7 +155,55 @@ Taskin uses a **plugin-based architecture** with dynamic provider loading:
|
|
|
154
155
|
|
|
155
156
|
📚 See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed technical documentation.
|
|
156
157
|
|
|
157
|
-
##
|
|
158
|
+
## 📝 Creating Tasks
|
|
159
|
+
|
|
160
|
+
Use the `taskin new` command to create new task files:
|
|
161
|
+
|
|
162
|
+
### Interactive Mode (Recommended)
|
|
163
|
+
|
|
164
|
+
Simply run without arguments for a guided experience:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
taskin new
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
You'll be prompted to select:
|
|
171
|
+
|
|
172
|
+
- Task type (feat, fix, refactor, docs, test, chore)
|
|
173
|
+
- Title
|
|
174
|
+
- Description (optional)
|
|
175
|
+
- Assignee (optional)
|
|
176
|
+
|
|
177
|
+
### Command-line Mode
|
|
178
|
+
|
|
179
|
+
Or provide all options directly:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Create a new feature task
|
|
183
|
+
taskin new -t feat -T "Add user authentication" -d "Implement JWT-based auth" -u "John Doe"
|
|
184
|
+
|
|
185
|
+
# Create a bug fix task
|
|
186
|
+
taskin new --type fix --title "Fix login error" --user "Developer"
|
|
187
|
+
|
|
188
|
+
# Using the 'create' alias
|
|
189
|
+
taskin create -t docs -T "Update README"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Options:**
|
|
193
|
+
|
|
194
|
+
- `-t, --type <type>` - Task type: feat, fix, refactor, docs, test, chore
|
|
195
|
+
- `-T, --title <title>` - Task title (required in command-line mode)
|
|
196
|
+
- `-d, --description <description>` - Task description
|
|
197
|
+
- `-u, --user <user>` - Assigned user
|
|
198
|
+
|
|
199
|
+
The command will:
|
|
200
|
+
|
|
201
|
+
1. Auto-generate a task number (e.g., 05)
|
|
202
|
+
2. Create a markdown file in `TASKS/` directory
|
|
203
|
+
3. Use a slug from the title for the filename
|
|
204
|
+
4. Pre-populate with a standard template
|
|
205
|
+
|
|
206
|
+
## �📖 Examples
|
|
158
207
|
|
|
159
208
|
See [EXAMPLES.md](./EXAMPLES.md) for detailed usage examples and workflows.
|
|
160
209
|
|
package/dist/index.js
CHANGED
|
@@ -13,9 +13,13 @@ var __esm = (fn, res) => function __init() {
|
|
|
13
13
|
// ../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
|
+
var getFilename, getDirname, __dirname;
|
|
16
17
|
var init_esm_shims = __esm({
|
|
17
18
|
"../../node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
|
|
18
19
|
"use strict";
|
|
20
|
+
getFilename = () => fileURLToPath(import.meta.url);
|
|
21
|
+
getDirname = () => path.dirname(getFilename());
|
|
22
|
+
__dirname = /* @__PURE__ */ getDirname();
|
|
19
23
|
}
|
|
20
24
|
});
|
|
21
25
|
|
|
@@ -26,54 +30,52 @@ import { Command } from "commander";
|
|
|
26
30
|
// src/commands/finish.ts
|
|
27
31
|
init_esm_shims();
|
|
28
32
|
|
|
29
|
-
// ../fs-task-provider/
|
|
33
|
+
// ../fs-task-provider/dist/index.js
|
|
30
34
|
init_esm_shims();
|
|
31
35
|
|
|
32
|
-
// ../fs-task-provider/
|
|
36
|
+
// ../fs-task-provider/dist/fs-task-provider.js
|
|
33
37
|
init_esm_shims();
|
|
34
38
|
import { promises as fs } from "fs";
|
|
35
39
|
import path2 from "path";
|
|
36
40
|
var FileSystemTaskProvider = class {
|
|
41
|
+
tasksDirectory;
|
|
37
42
|
constructor(tasksDirectory) {
|
|
38
43
|
this.tasksDirectory = tasksDirectory;
|
|
39
44
|
}
|
|
40
45
|
async findTask(taskId) {
|
|
41
46
|
const files = await fs.readdir(this.tasksDirectory);
|
|
42
|
-
const taskFile = files.find(
|
|
43
|
-
(file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md")
|
|
44
|
-
);
|
|
47
|
+
const taskFile = files.find((file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md"));
|
|
45
48
|
if (!taskFile) {
|
|
46
49
|
return void 0;
|
|
47
50
|
}
|
|
48
51
|
const filePath = path2.join(this.tasksDirectory, taskFile);
|
|
49
52
|
const content = await fs.readFile(filePath, "utf-8");
|
|
50
|
-
const titleMatch = content.match(/^# Task
|
|
51
|
-
const title = titleMatch ? titleMatch[1] : "Untitled";
|
|
53
|
+
const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
|
|
54
|
+
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
55
|
+
const headerSection = content.split(/^##/m)[0];
|
|
56
|
+
const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
|
|
57
|
+
const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
|
|
58
|
+
const userMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
|
|
52
59
|
const task = {
|
|
53
60
|
id: taskId,
|
|
54
61
|
title,
|
|
55
62
|
content,
|
|
56
63
|
filePath,
|
|
57
|
-
userId:
|
|
58
|
-
status: "pending",
|
|
59
|
-
type: "feat",
|
|
64
|
+
userId: userMatch ? userMatch[1].trim() : void 0,
|
|
65
|
+
status: statusMatch ? statusMatch[1].trim().toLowerCase() : "pending",
|
|
66
|
+
type: typeMatch ? typeMatch[1].trim().toLowerCase() : "feat",
|
|
60
67
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
61
68
|
};
|
|
62
69
|
return task;
|
|
63
70
|
}
|
|
64
71
|
async updateTask(task) {
|
|
65
72
|
const currentContent = await fs.readFile(task.filePath, "utf-8");
|
|
66
|
-
const updatedContent = currentContent.replace(
|
|
67
|
-
/^Status:\s*.+$/im,
|
|
68
|
-
`Status: ${task.status}`
|
|
69
|
-
);
|
|
73
|
+
const updatedContent = currentContent.replace(/^Status:\s*.+$/im, `Status: ${task.status}`);
|
|
70
74
|
await fs.writeFile(task.filePath, updatedContent, "utf-8");
|
|
71
75
|
}
|
|
72
76
|
async getAllTasks() {
|
|
73
77
|
const files = await fs.readdir(this.tasksDirectory);
|
|
74
|
-
const taskFiles = files.filter(
|
|
75
|
-
(file) => file.startsWith("task-") && file.endsWith(".md")
|
|
76
|
-
);
|
|
78
|
+
const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md"));
|
|
77
79
|
const tasks = [];
|
|
78
80
|
for (const file of taskFiles) {
|
|
79
81
|
const filePath = path2.join(this.tasksDirectory, file);
|
|
@@ -102,12 +104,13 @@ var FileSystemTaskProvider = class {
|
|
|
102
104
|
}
|
|
103
105
|
};
|
|
104
106
|
|
|
105
|
-
// ../task-manager/
|
|
107
|
+
// ../task-manager/dist/index.js
|
|
106
108
|
init_esm_shims();
|
|
107
109
|
|
|
108
|
-
// ../task-manager/
|
|
110
|
+
// ../task-manager/dist/task-manager.js
|
|
109
111
|
init_esm_shims();
|
|
110
112
|
var TaskManager = class {
|
|
113
|
+
taskProvider;
|
|
111
114
|
constructor(taskProvider) {
|
|
112
115
|
this.taskProvider = taskProvider;
|
|
113
116
|
}
|
|
@@ -137,14 +140,14 @@ var TaskManager = class {
|
|
|
137
140
|
}
|
|
138
141
|
};
|
|
139
142
|
|
|
140
|
-
// ../task-manager/
|
|
143
|
+
// ../task-manager/dist/task-manager.mock.js
|
|
141
144
|
init_esm_shims();
|
|
142
145
|
|
|
143
|
-
// ../task-manager/
|
|
146
|
+
// ../task-manager/dist/task-manager.types.js
|
|
144
147
|
init_esm_shims();
|
|
145
148
|
|
|
146
149
|
// src/commands/finish.ts
|
|
147
|
-
import
|
|
150
|
+
import path5 from "path";
|
|
148
151
|
|
|
149
152
|
// src/lib/colors.ts
|
|
150
153
|
init_esm_shims();
|
|
@@ -184,6 +187,51 @@ function info(message) {
|
|
|
184
187
|
console.log(colors.info(`\u2139 ${message}`));
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
// src/lib/project-check.ts
|
|
191
|
+
init_esm_shims();
|
|
192
|
+
import { existsSync } from "fs";
|
|
193
|
+
import path3 from "path";
|
|
194
|
+
function isTaskinProject(cwd = process.cwd()) {
|
|
195
|
+
return existsSync(path3.join(cwd, ".taskin.json"));
|
|
196
|
+
}
|
|
197
|
+
function requireTaskinProject(cwd = process.cwd()) {
|
|
198
|
+
if (!isTaskinProject(cwd)) {
|
|
199
|
+
error(
|
|
200
|
+
'This directory is not initialized as a taskin project. Run "taskin init" first.'
|
|
201
|
+
);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/lib/sound-player.ts
|
|
207
|
+
init_esm_shims();
|
|
208
|
+
import { existsSync as existsSync2 } from "fs";
|
|
209
|
+
import path4 from "path";
|
|
210
|
+
import player from "play-sound";
|
|
211
|
+
var soundPlayer = player({});
|
|
212
|
+
function playSound(soundName) {
|
|
213
|
+
try {
|
|
214
|
+
const possiblePaths = [
|
|
215
|
+
// In development (from src)
|
|
216
|
+
path4.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
|
|
217
|
+
// In production (relative to dist)
|
|
218
|
+
path4.join(__dirname, "..", "sounds", `${soundName}.mp3`),
|
|
219
|
+
// Custom sound in project root
|
|
220
|
+
path4.join(process.cwd(), ".taskin", `${soundName}.mp3`)
|
|
221
|
+
];
|
|
222
|
+
const soundPath = possiblePaths.find((p) => existsSync2(p));
|
|
223
|
+
if (!soundPath) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
soundPlayer.play(soundPath, (err) => {
|
|
227
|
+
if (err) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
187
235
|
// src/commands/define-command/index.ts
|
|
188
236
|
init_esm_shims();
|
|
189
237
|
|
|
@@ -223,6 +271,10 @@ var finishCommand = defineCommand({
|
|
|
223
271
|
{
|
|
224
272
|
flags: "-s, --skip-update",
|
|
225
273
|
description: "Skip updating task status"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
flags: "--no-sound",
|
|
277
|
+
description: "Disable finish sound"
|
|
226
278
|
}
|
|
227
279
|
],
|
|
228
280
|
handler: async (taskId, options) => {
|
|
@@ -230,9 +282,10 @@ var finishCommand = defineCommand({
|
|
|
230
282
|
}
|
|
231
283
|
});
|
|
232
284
|
async function finishTask(taskId, options) {
|
|
285
|
+
requireTaskinProject();
|
|
233
286
|
printHeader(`Finishing Task ${taskId}`, "\u2705");
|
|
234
287
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
235
|
-
const tasksDir =
|
|
288
|
+
const tasksDir = path5.join(process.cwd(), "TASKS");
|
|
236
289
|
const taskProvider = new FileSystemTaskProvider(tasksDir);
|
|
237
290
|
const taskManager = new TaskManager(taskProvider);
|
|
238
291
|
const task = await taskProvider.findTask(normalizedId);
|
|
@@ -269,11 +322,14 @@ async function finishTask(taskId, options) {
|
|
|
269
322
|
console.log();
|
|
270
323
|
success("Great work! \u{1F680}");
|
|
271
324
|
console.log();
|
|
325
|
+
if (options.sound !== false) {
|
|
326
|
+
playSound("finish");
|
|
327
|
+
}
|
|
272
328
|
}
|
|
273
329
|
|
|
274
330
|
// src/commands/init.ts
|
|
275
331
|
init_esm_shims();
|
|
276
|
-
import { existsSync as
|
|
332
|
+
import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
|
|
277
333
|
import inquirer from "inquirer";
|
|
278
334
|
import { join } from "path";
|
|
279
335
|
|
|
@@ -283,12 +339,12 @@ init_esm_shims();
|
|
|
283
339
|
// src/lib/provider-installer/provider-installer.ts
|
|
284
340
|
init_esm_shims();
|
|
285
341
|
import { execSync } from "child_process";
|
|
286
|
-
import { existsSync } from "fs";
|
|
342
|
+
import { existsSync as existsSync3 } from "fs";
|
|
287
343
|
function detectPackageManager() {
|
|
288
|
-
if (
|
|
344
|
+
if (existsSync3("pnpm-lock.yaml")) {
|
|
289
345
|
return "pnpm";
|
|
290
346
|
}
|
|
291
|
-
if (
|
|
347
|
+
if (existsSync3("yarn.lock")) {
|
|
292
348
|
return "yarn";
|
|
293
349
|
}
|
|
294
350
|
return "npm";
|
|
@@ -483,6 +539,10 @@ var initCommand = defineCommand({
|
|
|
483
539
|
{
|
|
484
540
|
flags: "-f, --force",
|
|
485
541
|
description: "Force initialization (overwrite existing configuration)"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
flags: "-p, --provider <provider>",
|
|
545
|
+
description: "Provider to use (fs, redmine, jira, github) - skips interactive prompt"
|
|
486
546
|
}
|
|
487
547
|
],
|
|
488
548
|
handler: async (options) => {
|
|
@@ -493,7 +553,7 @@ async function initializeTaskin(options) {
|
|
|
493
553
|
printHeader("Initializing Taskin", "\u{1F3AF}");
|
|
494
554
|
const cwd = process.cwd();
|
|
495
555
|
const configFile = join(cwd, ".taskin.json");
|
|
496
|
-
if (
|
|
556
|
+
if (existsSync4(configFile) && !options.force) {
|
|
497
557
|
error("Taskin is already initialized in this project");
|
|
498
558
|
info("Use --force to reinitialize");
|
|
499
559
|
process.exit(1);
|
|
@@ -509,15 +569,25 @@ async function initializeTaskin(options) {
|
|
|
509
569
|
disabled: p.status === "coming-soon" ? "Not yet implemented" : false
|
|
510
570
|
};
|
|
511
571
|
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
572
|
+
let providerId;
|
|
573
|
+
if (options.provider) {
|
|
574
|
+
providerId = options.provider;
|
|
575
|
+
info(`Using provider from command line: ${colors.highlight(providerId)}`);
|
|
576
|
+
} else if (process.env.CI === "true") {
|
|
577
|
+
providerId = "fs";
|
|
578
|
+
info("CI environment detected, using default provider: fs");
|
|
579
|
+
} else {
|
|
580
|
+
const response = await inquirer.prompt([
|
|
581
|
+
{
|
|
582
|
+
type: "list",
|
|
583
|
+
name: "providerId",
|
|
584
|
+
message: "Select a task provider:",
|
|
585
|
+
choices,
|
|
586
|
+
default: "fs"
|
|
587
|
+
}
|
|
588
|
+
]);
|
|
589
|
+
providerId = response.providerId;
|
|
590
|
+
}
|
|
521
591
|
const selectedProvider = getProviderById(providerId);
|
|
522
592
|
if (!selectedProvider) {
|
|
523
593
|
error(`Provider "${providerId}" not found`);
|
|
@@ -542,7 +612,7 @@ async function initializeTaskin(options) {
|
|
|
542
612
|
writeFileSync(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
543
613
|
success(`\u2713 Created ${colors.highlight(".taskin.json")}`);
|
|
544
614
|
const gitignorePath = join(cwd, ".gitignore");
|
|
545
|
-
if (
|
|
615
|
+
if (existsSync4(gitignorePath)) {
|
|
546
616
|
const gitignoreContent = __require("fs").readFileSync(gitignorePath, "utf-8");
|
|
547
617
|
if (!gitignoreContent.includes(".taskin.json")) {
|
|
548
618
|
info("Adding .taskin.json to .gitignore...");
|
|
@@ -564,7 +634,7 @@ async function initializeTaskin(options) {
|
|
|
564
634
|
console.log(colors.secondary(" 1. Run: taskin list"));
|
|
565
635
|
console.log(
|
|
566
636
|
colors.secondary(
|
|
567
|
-
selectedProvider.id === "fs" ? " 2.
|
|
637
|
+
selectedProvider.id === "fs" ? " 2. Create a new task: taskin new (interactive mode)" : ` 2. Tasks will be synced with ${selectedProvider.name}`
|
|
568
638
|
)
|
|
569
639
|
);
|
|
570
640
|
console.log(colors.secondary(" 3. Start working: taskin start <task-id>"));
|
|
@@ -598,15 +668,16 @@ async function setupProviderConfig(provider, cwd) {
|
|
|
598
668
|
}
|
|
599
669
|
async function setupFileSystemProvider(cwd) {
|
|
600
670
|
const tasksDir = join(cwd, "TASKS");
|
|
601
|
-
if (!
|
|
671
|
+
if (!existsSync4(tasksDir)) {
|
|
602
672
|
info(`Creating TASKS directory...`);
|
|
603
673
|
mkdirSync(tasksDir, { recursive: true });
|
|
604
674
|
success(`\u2713 Created ${colors.highlight("TASKS/")} directory`);
|
|
605
675
|
} else {
|
|
606
676
|
info(`${colors.highlight("TASKS/")} directory already exists`);
|
|
677
|
+
success("\u2713 Directory is ready to use");
|
|
607
678
|
}
|
|
608
679
|
const sampleTaskFile = join(tasksDir, "task-001-setup-project.md");
|
|
609
|
-
if (!
|
|
680
|
+
if (!existsSync4(sampleTaskFile)) {
|
|
610
681
|
info("Creating sample task...");
|
|
611
682
|
const sampleTask = `# Task 001 \u2014 Setup Project
|
|
612
683
|
|
|
@@ -942,7 +1013,7 @@ async function executeLint(options) {
|
|
|
942
1013
|
|
|
943
1014
|
// src/commands/list.ts
|
|
944
1015
|
init_esm_shims();
|
|
945
|
-
import
|
|
1016
|
+
import path6 from "path";
|
|
946
1017
|
var listCommand = defineCommand({
|
|
947
1018
|
name: "list [filter]",
|
|
948
1019
|
description: "\u{1F4CA} List all tasks in the project",
|
|
@@ -966,8 +1037,9 @@ var listCommand = defineCommand({
|
|
|
966
1037
|
}
|
|
967
1038
|
});
|
|
968
1039
|
async function listTasks(filter, options) {
|
|
1040
|
+
requireTaskinProject();
|
|
969
1041
|
printHeader("Task List", "\u{1F4CA}");
|
|
970
|
-
const tasksDir =
|
|
1042
|
+
const tasksDir = path6.join(process.cwd(), "TASKS");
|
|
971
1043
|
const taskProvider = new FileSystemTaskProvider(tasksDir);
|
|
972
1044
|
const tasks = await taskProvider.getAllTasks();
|
|
973
1045
|
if (tasks.length === 0) {
|
|
@@ -1057,9 +1129,170 @@ function getTypeColor(type) {
|
|
|
1057
1129
|
}
|
|
1058
1130
|
}
|
|
1059
1131
|
|
|
1132
|
+
// src/commands/new.ts
|
|
1133
|
+
init_esm_shims();
|
|
1134
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1135
|
+
import inquirer2 from "inquirer";
|
|
1136
|
+
import path7 from "path";
|
|
1137
|
+
var createCommand = defineCommand({
|
|
1138
|
+
name: "new",
|
|
1139
|
+
description: "\u2795 Create a new task",
|
|
1140
|
+
alias: "create",
|
|
1141
|
+
options: [
|
|
1142
|
+
{
|
|
1143
|
+
flags: "-t, --type <type>",
|
|
1144
|
+
description: "Task type (feat, fix, refactor, docs, test, chore)"
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
flags: "-T, --title <title>",
|
|
1148
|
+
description: "Task title"
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
flags: "-d, --description <description>",
|
|
1152
|
+
description: "Task description"
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
flags: "-u, --user <user>",
|
|
1156
|
+
description: "Assignee user"
|
|
1157
|
+
}
|
|
1158
|
+
],
|
|
1159
|
+
handler: async (options) => {
|
|
1160
|
+
await createTask(options);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
async function createTask(options) {
|
|
1164
|
+
requireTaskinProject();
|
|
1165
|
+
printHeader("Create New Task", "\u2795");
|
|
1166
|
+
const validTypes = ["feat", "fix", "refactor", "docs", "test", "chore"];
|
|
1167
|
+
if (!options.type && !options.title) {
|
|
1168
|
+
info("Interactive mode - Answer the questions below:");
|
|
1169
|
+
console.log();
|
|
1170
|
+
const answers = await inquirer2.prompt([
|
|
1171
|
+
{
|
|
1172
|
+
type: "list",
|
|
1173
|
+
name: "type",
|
|
1174
|
+
message: "Select task type:",
|
|
1175
|
+
choices: [
|
|
1176
|
+
{ name: "\u2728 feat - New feature", value: "feat" },
|
|
1177
|
+
{ name: "\u{1F41B} fix - Bug fix", value: "fix" },
|
|
1178
|
+
{ name: "\u267B\uFE0F refactor - Code refactoring", value: "refactor" },
|
|
1179
|
+
{ name: "\u{1F4DD} docs - Documentation", value: "docs" },
|
|
1180
|
+
{ name: "\u2705 test - Tests", value: "test" },
|
|
1181
|
+
{ name: "\u{1F527} chore - Maintenance", value: "chore" }
|
|
1182
|
+
],
|
|
1183
|
+
default: "feat"
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
type: "input",
|
|
1187
|
+
name: "title",
|
|
1188
|
+
message: "Task title:",
|
|
1189
|
+
validate: (input) => {
|
|
1190
|
+
if (input.trim().length === 0) {
|
|
1191
|
+
return "Title is required";
|
|
1192
|
+
}
|
|
1193
|
+
return true;
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
type: "input",
|
|
1198
|
+
name: "description",
|
|
1199
|
+
message: "Task description (optional):"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
type: "input",
|
|
1203
|
+
name: "user",
|
|
1204
|
+
message: "Assignee (optional):",
|
|
1205
|
+
default: "A definir"
|
|
1206
|
+
}
|
|
1207
|
+
]);
|
|
1208
|
+
options.type = answers.type;
|
|
1209
|
+
options.title = answers.title;
|
|
1210
|
+
options.description = answers.description;
|
|
1211
|
+
options.user = answers.user;
|
|
1212
|
+
console.log();
|
|
1213
|
+
}
|
|
1214
|
+
if (!options.type) {
|
|
1215
|
+
error("Task type is required. Use --type <type>");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!options.title) {
|
|
1219
|
+
error("Task title is required. Use --title <title>");
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (!validTypes.includes(options.type)) {
|
|
1223
|
+
error(
|
|
1224
|
+
`Invalid task type: ${options.type}. Must be one of: ${validTypes.join(", ")}`
|
|
1225
|
+
);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const tasksDir = path7.join(process.cwd(), "TASKS");
|
|
1229
|
+
if (!existsSync5(tasksDir)) {
|
|
1230
|
+
mkdirSync2(tasksDir, { recursive: true });
|
|
1231
|
+
}
|
|
1232
|
+
const taskProvider = new FileSystemTaskProvider(tasksDir);
|
|
1233
|
+
const allTasks = await taskProvider.getAllTasks();
|
|
1234
|
+
const taskNumbers = allTasks.map((task) => {
|
|
1235
|
+
const match = task.id.match(/^(\d+)$/);
|
|
1236
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
1237
|
+
}).filter((num) => !isNaN(num));
|
|
1238
|
+
const nextNumber = taskNumbers.length > 0 ? Math.max(...taskNumbers) + 1 : 1;
|
|
1239
|
+
const taskId = String(nextNumber).padStart(3, "0");
|
|
1240
|
+
const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1241
|
+
const fileName = `task-${taskId}-${titleSlug}.md`;
|
|
1242
|
+
const filePath = path7.join(tasksDir, fileName);
|
|
1243
|
+
if (existsSync5(filePath)) {
|
|
1244
|
+
error(`Task file already exists: ${fileName}`);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const taskContent = generateTaskMarkdown({
|
|
1248
|
+
id: taskId,
|
|
1249
|
+
type: options.type,
|
|
1250
|
+
title: options.title,
|
|
1251
|
+
description: options.description || "",
|
|
1252
|
+
user: options.user || "A definir"
|
|
1253
|
+
});
|
|
1254
|
+
writeFileSync2(filePath, taskContent, "utf-8");
|
|
1255
|
+
console.log();
|
|
1256
|
+
success(`Task ${taskId} created successfully!`);
|
|
1257
|
+
console.log(colors.secondary(`\u{1F4C4} File: ${fileName}`));
|
|
1258
|
+
console.log(colors.secondary(`\u{1F4C1} Path: ${filePath}`));
|
|
1259
|
+
console.log();
|
|
1260
|
+
console.log(colors.info("Next steps:"));
|
|
1261
|
+
console.log(colors.normal(` 1. Edit the task file to add more details`));
|
|
1262
|
+
console.log(
|
|
1263
|
+
colors.normal(
|
|
1264
|
+
` 2. Run ${colors.highlight("taskin start " + taskId)} to begin working on it`
|
|
1265
|
+
)
|
|
1266
|
+
);
|
|
1267
|
+
console.log();
|
|
1268
|
+
}
|
|
1269
|
+
function generateTaskMarkdown(data) {
|
|
1270
|
+
return `# Task ${data.id} \u2014 ${data.title}
|
|
1271
|
+
|
|
1272
|
+
Status: pending
|
|
1273
|
+
Type: ${data.type}
|
|
1274
|
+
Assignee: ${data.user}
|
|
1275
|
+
|
|
1276
|
+
## Description
|
|
1277
|
+
|
|
1278
|
+
${data.description || "Add task description here..."}
|
|
1279
|
+
|
|
1280
|
+
## Tasks
|
|
1281
|
+
|
|
1282
|
+
- [ ] Task 1
|
|
1283
|
+
- [ ] Task 2
|
|
1284
|
+
- [ ] Task 3
|
|
1285
|
+
|
|
1286
|
+
## Notes
|
|
1287
|
+
|
|
1288
|
+
Add any relevant notes or links here.
|
|
1289
|
+
`;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1060
1292
|
// src/commands/pause.ts
|
|
1061
1293
|
init_esm_shims();
|
|
1062
|
-
import
|
|
1294
|
+
import { execSync as execSync2 } from "child_process";
|
|
1295
|
+
import path8 from "path";
|
|
1063
1296
|
var pauseCommand = defineCommand({
|
|
1064
1297
|
name: "pause <task-id>",
|
|
1065
1298
|
description: "\u23F8\uFE0F Pause work on a task",
|
|
@@ -1072,6 +1305,10 @@ var pauseCommand = defineCommand({
|
|
|
1072
1305
|
{
|
|
1073
1306
|
flags: "-s, --skip-commit",
|
|
1074
1307
|
description: "Skip commit (just show what would be done)"
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
flags: "--no-sound",
|
|
1311
|
+
description: "Disable pause sound"
|
|
1075
1312
|
}
|
|
1076
1313
|
],
|
|
1077
1314
|
handler: async (taskId, options) => {
|
|
@@ -1079,9 +1316,10 @@ var pauseCommand = defineCommand({
|
|
|
1079
1316
|
}
|
|
1080
1317
|
});
|
|
1081
1318
|
async function pauseTask(taskId, options) {
|
|
1319
|
+
requireTaskinProject();
|
|
1082
1320
|
printHeader(`Pausing Task ${taskId}`, "\u23F8\uFE0F");
|
|
1083
1321
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
1084
|
-
const tasksDir =
|
|
1322
|
+
const tasksDir = path8.join(process.cwd(), "TASKS");
|
|
1085
1323
|
const taskProvider = new FileSystemTaskProvider(tasksDir);
|
|
1086
1324
|
const task = await taskProvider.findTask(normalizedId);
|
|
1087
1325
|
if (!task) {
|
|
@@ -1104,21 +1342,35 @@ async function pauseTask(taskId, options) {
|
|
|
1104
1342
|
info("Creating commit...");
|
|
1105
1343
|
console.log(colors.secondary(` Message: "${commitMessage}"`));
|
|
1106
1344
|
console.log();
|
|
1345
|
+
try {
|
|
1346
|
+
execSync2("git add -A", { cwd: process.cwd(), stdio: "ignore" });
|
|
1347
|
+
execSync2(`git commit -m "${commitMessage}"`, {
|
|
1348
|
+
cwd: process.cwd(),
|
|
1349
|
+
stdio: "ignore"
|
|
1350
|
+
});
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
const updatedTask = { ...task, status: "pending" };
|
|
1354
|
+
await taskProvider.updateTask(updatedTask);
|
|
1107
1355
|
success("Task paused successfully!");
|
|
1108
1356
|
info("Commit created (not pushed)");
|
|
1357
|
+
info("Status updated to pending");
|
|
1109
1358
|
console.log();
|
|
1110
1359
|
info("Next steps:");
|
|
1111
1360
|
console.log(colors.secondary(" 1. Switch to another task"));
|
|
1112
1361
|
console.log(
|
|
1113
1362
|
colors.secondary(" 2. Or continue later with the same branch")
|
|
1114
1363
|
);
|
|
1364
|
+
if (options.sound !== false) {
|
|
1365
|
+
playSound("stop");
|
|
1366
|
+
}
|
|
1115
1367
|
}
|
|
1116
1368
|
console.log();
|
|
1117
1369
|
}
|
|
1118
1370
|
|
|
1119
1371
|
// src/commands/start.ts
|
|
1120
1372
|
init_esm_shims();
|
|
1121
|
-
import
|
|
1373
|
+
import path9 from "path";
|
|
1122
1374
|
var startCommand = defineCommand({
|
|
1123
1375
|
name: "start <task-id>",
|
|
1124
1376
|
description: "\u{1F680} Start working on a task",
|
|
@@ -1131,6 +1383,10 @@ var startCommand = defineCommand({
|
|
|
1131
1383
|
{
|
|
1132
1384
|
flags: "-b, --base <branch>",
|
|
1133
1385
|
description: "Base branch to create from (default: current)"
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
flags: "--no-sound",
|
|
1389
|
+
description: "Disable start sound"
|
|
1134
1390
|
}
|
|
1135
1391
|
],
|
|
1136
1392
|
handler: async (taskId, options) => {
|
|
@@ -1138,9 +1394,10 @@ var startCommand = defineCommand({
|
|
|
1138
1394
|
}
|
|
1139
1395
|
});
|
|
1140
1396
|
async function startTask(taskId, _options) {
|
|
1397
|
+
requireTaskinProject();
|
|
1141
1398
|
printHeader(`Starting Task ${taskId}`, "\u{1F680}");
|
|
1142
1399
|
const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
|
|
1143
|
-
const tasksDir =
|
|
1400
|
+
const tasksDir = path9.join(process.cwd(), "TASKS");
|
|
1144
1401
|
const taskProvider = new FileSystemTaskProvider(tasksDir);
|
|
1145
1402
|
const taskManager = new TaskManager(taskProvider);
|
|
1146
1403
|
const task = await taskProvider.findTask(normalizedId);
|
|
@@ -1172,6 +1429,9 @@ async function startTask(taskId, _options) {
|
|
|
1172
1429
|
console.log(colors.secondary(" 2. Start coding! \u{1F4BB}"));
|
|
1173
1430
|
console.log(colors.secondary(' 3. Use "taskin pause" to save progress'));
|
|
1174
1431
|
console.log();
|
|
1432
|
+
if (_options.sound !== false) {
|
|
1433
|
+
playSound("start");
|
|
1434
|
+
}
|
|
1175
1435
|
}
|
|
1176
1436
|
|
|
1177
1437
|
// src/lib/help.ts
|
|
@@ -1201,6 +1461,17 @@ function showCustomHelp() {
|
|
|
1201
1461
|
],
|
|
1202
1462
|
icon: "\u{1F4CA}"
|
|
1203
1463
|
},
|
|
1464
|
+
{
|
|
1465
|
+
name: colors.highlight("taskin new"),
|
|
1466
|
+
alias: colors.secondary("Alias: create"),
|
|
1467
|
+
description: "Create a new task",
|
|
1468
|
+
examples: [
|
|
1469
|
+
'taskin new -t feat -T "Add login" -d "Implement user authentication"',
|
|
1470
|
+
'taskin new --type fix --title "Fix bug" --user "John"',
|
|
1471
|
+
'taskin create -t docs -T "Update README"'
|
|
1472
|
+
],
|
|
1473
|
+
icon: "\u{1F4DD}"
|
|
1474
|
+
},
|
|
1204
1475
|
{
|
|
1205
1476
|
name: colors.highlight("taskin start") + colors.normal(" <task-id>"),
|
|
1206
1477
|
alias: colors.secondary("Alias: begin"),
|
|
@@ -1407,6 +1678,7 @@ program.name("taskin").description("\u{1F680} Task Management System").version(g
|
|
|
1407
1678
|
});
|
|
1408
1679
|
initCommand(program);
|
|
1409
1680
|
listCommand(program);
|
|
1681
|
+
createCommand(program);
|
|
1410
1682
|
startCommand(program);
|
|
1411
1683
|
pauseCommand(program);
|
|
1412
1684
|
finishCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Task management system integrated with Git workflows",
|
|
5
5
|
"motivation": "Provide a CLI tool for task management integrated with Git workflows",
|
|
6
6
|
"solve": "Simplifies task tracking and management directly from the command line",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"files": [
|
|
59
59
|
"dist",
|
|
60
|
+
"sounds",
|
|
60
61
|
"README.md",
|
|
61
62
|
"LICENSE"
|
|
62
63
|
],
|
|
@@ -75,10 +76,16 @@
|
|
|
75
76
|
"typecheck": "tsc --noEmit"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
79
|
+
"@opentask/taskin-fs-provider": "workspace:*",
|
|
80
|
+
"@opentask/taskin-task-manager": "workspace:*",
|
|
81
|
+
"@opentask/taskin-types": "workspace:*",
|
|
82
|
+
"@opentask/taskin-utils": "workspace:*",
|
|
78
83
|
"@types/inquirer": "^9.0.9",
|
|
84
|
+
"@types/play-sound": "^1.1.2",
|
|
79
85
|
"chalk": "^5.6.2",
|
|
80
86
|
"commander": "^12.1.0",
|
|
81
87
|
"inquirer": "^12.11.0",
|
|
88
|
+
"play-sound": "^1.1.6",
|
|
82
89
|
"zod": "^3.25.76"
|
|
83
90
|
},
|
|
84
91
|
"devDependencies": {
|
package/sounds/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Audio Sounds for Taskin
|
|
2
|
+
|
|
3
|
+
This directory contains optional audio files that can be played during CLI operations.
|
|
4
|
+
|
|
5
|
+
## Included Sounds
|
|
6
|
+
|
|
7
|
+
### `start.mp3`
|
|
8
|
+
|
|
9
|
+
Played when you start a task with `taskin start` command.
|
|
10
|
+
Sound effect: CS:GO "Go Go Go!"
|
|
11
|
+
|
|
12
|
+
### `finish.mp3`
|
|
13
|
+
|
|
14
|
+
Played when you finish a task with `taskin finish` or `taskin done` command.
|
|
15
|
+
Sound effect: CS:GO "Bomb has been planted"
|
|
16
|
+
|
|
17
|
+
### `stop.mp3` (TODO)
|
|
18
|
+
|
|
19
|
+
Played when you pause a task with `taskin pause` or `taskin stop` command.
|
|
20
|
+
**Note:** This file needs to be added. Sound effect: (to be defined)
|
|
21
|
+
|
|
22
|
+
## How to Add Custom Sounds
|
|
23
|
+
|
|
24
|
+
1. **Built-in sounds**: Place MP3 files in this directory (`packages/cli/sounds/`)
|
|
25
|
+
2. **Project-specific sounds**: Create a `.taskin/` directory in your project root and add MP3 files there
|
|
26
|
+
|
|
27
|
+
## Adding Your Own Sounds
|
|
28
|
+
|
|
29
|
+
To add custom sounds:
|
|
30
|
+
|
|
31
|
+
1. Create a `.taskin/` directory in your project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
mkdir .taskin
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Add your MP3 files (you can customize any of these):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cp /path/to/your/start-sound.mp3 .taskin/start.mp3
|
|
41
|
+
cp /path/to/your/finish-sound.mp3 .taskin/finish.mp3
|
|
42
|
+
cp /path/to/your/pause-sound.mp3 .taskin/stop.mp3
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. The sounds will play automatically on the respective commands!
|
|
46
|
+
|
|
47
|
+
## Disabling Sound
|
|
48
|
+
|
|
49
|
+
To disable sounds, use the `--no-sound` flag:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
taskin start 001 --no-sound
|
|
53
|
+
taskin finish 001 --no-sound
|
|
54
|
+
taskin pause 001 --no-sound
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
The sound player uses system audio utilities. Make sure you have one of these installed:
|
|
60
|
+
|
|
61
|
+
- **Linux**: `mpg123`, `ffplay`, or `mplayer`
|
|
62
|
+
- **macOS**: `afplay` (built-in)
|
|
63
|
+
- **Windows**: Built-in Windows Media Player
|
|
64
|
+
|
|
65
|
+
### Installing audio players on Linux:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Ubuntu/Debian
|
|
69
|
+
sudo apt-get install mpg123
|
|
70
|
+
|
|
71
|
+
# Fedora
|
|
72
|
+
sudo dnf install mpg123
|
|
73
|
+
|
|
74
|
+
# Arch
|
|
75
|
+
sudo pacman -S mpg123
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- Sounds are played asynchronously and won't block CLI operations
|
|
81
|
+
- If no sound file is found, the command executes silently
|
|
82
|
+
- Sound playback errors are silently ignored
|
|
Binary file
|
package/sounds/start.mp3
ADDED
|
Binary file
|
package/dist/index.d.ts
DELETED
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Task identifier schema.
|
|
6
|
-
* Represents a unique identifier for a task (UUID format).
|
|
7
|
-
*
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
declare const TaskIdSchema: z.ZodBranded<z.ZodString, "TaskId">;
|
|
11
|
-
/**
|
|
12
|
-
* Runtime validator for task type values.
|
|
13
|
-
* Use this to validate user input or external data.
|
|
14
|
-
*
|
|
15
|
-
* @public
|
|
16
|
-
*/
|
|
17
|
-
declare const TaskTypeSchema: z.ZodEnum<["feat", "fix", "refactor", "docs", "test", "chore"]>;
|
|
18
|
-
/**
|
|
19
|
-
* Runtime validator for task objects.
|
|
20
|
-
* Use this to parse and validate task data from external sources (API, database, forms).
|
|
21
|
-
*
|
|
22
|
-
* @public
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* // Parse and validate task data
|
|
26
|
-
* const task = TaskSchema.parse(userInput);
|
|
27
|
-
*
|
|
28
|
-
* // Safe parse with error handling
|
|
29
|
-
* const result = TaskSchema.safeParse(untrustedData);
|
|
30
|
-
* if (result.success) {
|
|
31
|
-
* console.log(result.data);
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
declare const TaskSchema: z.ZodObject<{
|
|
36
|
-
createdAt: z.ZodString;
|
|
37
|
-
id: z.ZodBranded<z.ZodString, "TaskId">;
|
|
38
|
-
status: z.ZodEnum<["pending", "in-progress", "done", "blocked"]>;
|
|
39
|
-
title: z.ZodString;
|
|
40
|
-
type: z.ZodEnum<["feat", "fix", "refactor", "docs", "test", "chore"]>;
|
|
41
|
-
assignee: z.ZodOptional<z.ZodString>;
|
|
42
|
-
completed: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
-
description: z.ZodOptional<z.ZodString>;
|
|
44
|
-
userId: z.ZodOptional<z.ZodString>;
|
|
45
|
-
}, "strip", z.ZodTypeAny, {
|
|
46
|
-
createdAt: string;
|
|
47
|
-
id: string & z.BRAND<"TaskId">;
|
|
48
|
-
status: "pending" | "in-progress" | "done" | "blocked";
|
|
49
|
-
title: string;
|
|
50
|
-
type: "feat" | "fix" | "refactor" | "docs" | "test" | "chore";
|
|
51
|
-
assignee?: string | undefined;
|
|
52
|
-
completed?: boolean | undefined;
|
|
53
|
-
description?: string | undefined;
|
|
54
|
-
userId?: string | undefined;
|
|
55
|
-
}, {
|
|
56
|
-
createdAt: string;
|
|
57
|
-
id: string;
|
|
58
|
-
status: "pending" | "in-progress" | "done" | "blocked";
|
|
59
|
-
title: string;
|
|
60
|
-
type: "feat" | "fix" | "refactor" | "docs" | "test" | "chore";
|
|
61
|
-
assignee?: string | undefined;
|
|
62
|
-
completed?: boolean | undefined;
|
|
63
|
-
description?: string | undefined;
|
|
64
|
-
userId?: string | undefined;
|
|
65
|
-
}>;
|
|
66
|
-
/**
|
|
67
|
-
* Runtime validator for user objects.
|
|
68
|
-
* Use this to parse and validate user data from external sources.
|
|
69
|
-
*
|
|
70
|
-
* @public
|
|
71
|
-
*/
|
|
72
|
-
declare const UserSchema: z.ZodObject<{
|
|
73
|
-
email: z.ZodString;
|
|
74
|
-
id: z.ZodString;
|
|
75
|
-
name: z.ZodString;
|
|
76
|
-
}, "strip", z.ZodTypeAny, {
|
|
77
|
-
id: string;
|
|
78
|
-
email: string;
|
|
79
|
-
name: string;
|
|
80
|
-
}, {
|
|
81
|
-
id: string;
|
|
82
|
-
email: string;
|
|
83
|
-
name: string;
|
|
84
|
-
}>;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Taskin Core Interface
|
|
88
|
-
* Main interface for task management operations
|
|
89
|
-
*/
|
|
90
|
-
|
|
91
|
-
interface ListTasksOptions {
|
|
92
|
-
assignee?: string;
|
|
93
|
-
status?: string;
|
|
94
|
-
type?: string;
|
|
95
|
-
}
|
|
96
|
-
interface StartTaskOptions {
|
|
97
|
-
base?: string;
|
|
98
|
-
force?: boolean;
|
|
99
|
-
}
|
|
100
|
-
interface PauseTaskOptions {
|
|
101
|
-
message?: string;
|
|
102
|
-
skipCommit?: boolean;
|
|
103
|
-
}
|
|
104
|
-
interface FinishTaskOptions {
|
|
105
|
-
push?: boolean;
|
|
106
|
-
skipUpdate?: boolean;
|
|
107
|
-
}
|
|
108
|
-
interface LintTasksOptions {
|
|
109
|
-
path?: string;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Validation issue found during task linting
|
|
113
|
-
* Provider-agnostic - works with any task storage backend
|
|
114
|
-
*/
|
|
115
|
-
interface TaskValidationIssue {
|
|
116
|
-
message: string;
|
|
117
|
-
severity: 'error' | 'warning';
|
|
118
|
-
taskId: string;
|
|
119
|
-
line?: number;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Task metadata extracted from task source
|
|
123
|
-
*/
|
|
124
|
-
interface TaskMetadata {
|
|
125
|
-
assignee?: string;
|
|
126
|
-
status?: string;
|
|
127
|
-
type?: string;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Result of task validation/linting operation
|
|
131
|
-
*/
|
|
132
|
-
interface LintResult {
|
|
133
|
-
errors: TaskValidationIssue[];
|
|
134
|
-
tasksChecked: number;
|
|
135
|
-
valid: boolean;
|
|
136
|
-
warnings: TaskValidationIssue[];
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* ITaskin - Core interface for task management system
|
|
140
|
-
*/
|
|
141
|
-
interface ITaskin {
|
|
142
|
-
/**
|
|
143
|
-
* Finish a task
|
|
144
|
-
*/
|
|
145
|
-
finish(taskId: TaskId, options?: FinishTaskOptions): Promise<void>;
|
|
146
|
-
/**
|
|
147
|
-
* Lint/validate tasks from the configured provider
|
|
148
|
-
*/
|
|
149
|
-
lint(options?: LintTasksOptions): Promise<LintResult>;
|
|
150
|
-
/**
|
|
151
|
-
* List all tasks with optional filters
|
|
152
|
-
*/
|
|
153
|
-
list(options?: ListTasksOptions): Promise<Task[]>;
|
|
154
|
-
/**
|
|
155
|
-
* Pause work on a task
|
|
156
|
-
*/
|
|
157
|
-
pause(taskId: TaskId, options?: PauseTaskOptions): Promise<void>;
|
|
158
|
-
/**
|
|
159
|
-
* Start working on a task
|
|
160
|
-
*/
|
|
161
|
-
start(taskId: TaskId, options?: StartTaskOptions): Promise<void>;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Unique identifier for a task (UUID branded type).
|
|
165
|
-
* Use this type for type-safe task ID handling across the system.
|
|
166
|
-
*
|
|
167
|
-
* @public
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* function getTask(id: TaskId): Task { ... }
|
|
171
|
-
* const taskId: TaskId = '550e8400-e29b-41d4-a716-446655440000' as TaskId;
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
type TaskId = z.infer<typeof TaskIdSchema>;
|
|
175
|
-
/**
|
|
176
|
-
* Type of task based on the kind of work being performed.
|
|
177
|
-
* Follows conventional commit types for consistency across the codebase.
|
|
178
|
-
*
|
|
179
|
-
* @public
|
|
180
|
-
*/
|
|
181
|
-
type TaskType = z.infer<typeof TaskTypeSchema>;
|
|
182
|
-
/**
|
|
183
|
-
* Represents a task in the Taskin system.
|
|
184
|
-
* Contains all core information needed to track and manage work items.
|
|
185
|
-
*
|
|
186
|
-
* @public
|
|
187
|
-
*/
|
|
188
|
-
type Task = z.infer<typeof TaskSchema>;
|
|
189
|
-
/**
|
|
190
|
-
* Represents a user in the Taskin system.
|
|
191
|
-
* Contains basic user identification and contact information.
|
|
192
|
-
*
|
|
193
|
-
* @public
|
|
194
|
-
*/
|
|
195
|
-
type User = z.infer<typeof UserSchema>;
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* A task with additional file system metadata.
|
|
199
|
-
* Extends the base Task type with content and path information.
|
|
200
|
-
* @public
|
|
201
|
-
*/
|
|
202
|
-
type TaskFile = Task & {
|
|
203
|
-
/** The raw markdown content of the task file */
|
|
204
|
-
content: string;
|
|
205
|
-
/** Absolute or relative path to the task file */
|
|
206
|
-
filePath: string;
|
|
207
|
-
/** The type of work this task represents */
|
|
208
|
-
type: TaskType;
|
|
209
|
-
/** Optional user assigned to this task */
|
|
210
|
-
assignee?: User;
|
|
211
|
-
};
|
|
212
|
-
/**
|
|
213
|
-
* Interface for task storage providers.
|
|
214
|
-
* Implementations handle reading and writing tasks from different sources
|
|
215
|
-
* (e.g., file system, database, API).
|
|
216
|
-
* @public
|
|
217
|
-
*/
|
|
218
|
-
interface ITaskProvider {
|
|
219
|
-
/**
|
|
220
|
-
* Find a specific task by its ID.
|
|
221
|
-
* @param taskId - The unique identifier of the task
|
|
222
|
-
* @returns The task if found, undefined otherwise
|
|
223
|
-
*/
|
|
224
|
-
findTask(taskId: string): Promise<TaskFile | undefined>;
|
|
225
|
-
/**
|
|
226
|
-
* Retrieve all tasks from the provider.
|
|
227
|
-
* @returns Array of all tasks
|
|
228
|
-
*/
|
|
229
|
-
getAllTasks(): Promise<TaskFile[]>;
|
|
230
|
-
/**
|
|
231
|
-
* Update an existing task.
|
|
232
|
-
* @param task - The task with updated information
|
|
233
|
-
*/
|
|
234
|
-
updateTask(task: TaskFile): Promise<void>;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Interface for task management operations.
|
|
238
|
-
* Provides high-level methods for managing task workflow and state transitions.
|
|
239
|
-
* @public
|
|
240
|
-
*/
|
|
241
|
-
interface ITaskManager {
|
|
242
|
-
/**
|
|
243
|
-
* Mark a task as finished.
|
|
244
|
-
* Transitions the task to 'done' status.
|
|
245
|
-
* @param taskId - The unique identifier of the task
|
|
246
|
-
* @returns The updated task
|
|
247
|
-
* @throws Error if task is not found
|
|
248
|
-
*/
|
|
249
|
-
finishTask(taskId: string): Promise<TaskFile>;
|
|
250
|
-
/**
|
|
251
|
-
* Start working on a task.
|
|
252
|
-
* Transitions the task to 'in-progress' status.
|
|
253
|
-
* @param taskId - The unique identifier of the task
|
|
254
|
-
* @returns The updated task
|
|
255
|
-
* @throws Error if task is not found, already in progress, or already done
|
|
256
|
-
*/
|
|
257
|
-
startTask(taskId: string): Promise<TaskFile>;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
declare class TaskManager implements ITaskManager {
|
|
261
|
-
private taskProvider;
|
|
262
|
-
constructor(taskProvider: ITaskProvider);
|
|
263
|
-
startTask(taskId: string): Promise<TaskFile>;
|
|
264
|
-
finishTask(taskId: string): Promise<TaskFile>;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
declare class FileSystemTaskProvider implements ITaskProvider {
|
|
268
|
-
private tasksDirectory;
|
|
269
|
-
constructor(tasksDirectory: string);
|
|
270
|
-
findTask(taskId: string): Promise<TaskFile | undefined>;
|
|
271
|
-
updateTask(task: TaskFile): Promise<void>;
|
|
272
|
-
getAllTasks(): Promise<TaskFile[]>;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* FileSystem task linter types
|
|
277
|
-
* Re-exports from @opentask/taskin-types + FileSystem-specific extensions
|
|
278
|
-
*/
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* FileSystem-specific validation error
|
|
282
|
-
* Extends TaskValidationIssue with file-specific information
|
|
283
|
-
*/
|
|
284
|
-
interface FileValidationError extends Omit<TaskValidationIssue, 'taskId'> {
|
|
285
|
-
file: string;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* FileSystem-specific lint result
|
|
289
|
-
* Contains file-based validation results
|
|
290
|
-
*/
|
|
291
|
-
interface FileLintResult {
|
|
292
|
-
errors: FileValidationError[];
|
|
293
|
-
filesChecked: number;
|
|
294
|
-
valid: boolean;
|
|
295
|
-
warnings: FileValidationError[];
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* FileSystem-specific task linter interface
|
|
299
|
-
* Defines validation operations for file-based task providers
|
|
300
|
-
*/
|
|
301
|
-
interface IFileSystemTaskLinter {
|
|
302
|
-
/**
|
|
303
|
-
* Lint all task files in a directory
|
|
304
|
-
* @param tasksDir - Directory containing task markdown files
|
|
305
|
-
* @returns Validation results for all files
|
|
306
|
-
*/
|
|
307
|
-
lintDirectory(tasksDir: string): Promise<FileLintResult>;
|
|
308
|
-
/**
|
|
309
|
-
* Lint a single task file
|
|
310
|
-
* @param filePath - Path to the task markdown file
|
|
311
|
-
* @returns Array of validation errors found in the file
|
|
312
|
-
*/
|
|
313
|
-
lintFile(filePath: string): Promise<FileValidationError[]>;
|
|
314
|
-
/**
|
|
315
|
-
* Validate task metadata
|
|
316
|
-
* @param metadata - Task metadata to validate
|
|
317
|
-
* @param fileName - Name of the file being validated
|
|
318
|
-
* @returns Array of validation errors
|
|
319
|
-
*/
|
|
320
|
-
validateMetadata(metadata: Record<string, unknown>, fileName: string): FileValidationError[];
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* FileSystem-specific task linter implementation
|
|
325
|
-
* Validates task markdown files in the local filesystem
|
|
326
|
-
*/
|
|
327
|
-
declare class FileSystemTaskLinter implements IFileSystemTaskLinter {
|
|
328
|
-
private errors;
|
|
329
|
-
private addError;
|
|
330
|
-
/**
|
|
331
|
-
* Validate task file name format (FileSystem-specific)
|
|
332
|
-
*/
|
|
333
|
-
validateFileName(fileName: string): FileValidationError | null;
|
|
334
|
-
private extractMetadata;
|
|
335
|
-
/**
|
|
336
|
-
* Validate task metadata (FileSystem-specific)
|
|
337
|
-
*/
|
|
338
|
-
validateMetadata(metadata: TaskMetadata, filePath: string): FileValidationError[];
|
|
339
|
-
private validateContent;
|
|
340
|
-
/**
|
|
341
|
-
* Lint a single task file (FileSystem-specific)
|
|
342
|
-
*/
|
|
343
|
-
lintFile(filePath: string): Promise<FileValidationError[]>;
|
|
344
|
-
/**
|
|
345
|
-
* Lint all task files in the specified directory (FileSystem-specific)
|
|
346
|
-
*/
|
|
347
|
-
lintDirectory(tasksDir: string): Promise<FileLintResult>;
|
|
348
|
-
static printResults(result: FileLintResult): void;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Taskin - Concrete implementation of ITaskin
|
|
353
|
-
* Main class that implements task management with dependency injection
|
|
354
|
-
*/
|
|
355
|
-
|
|
356
|
-
declare class Taskin implements ITaskin {
|
|
357
|
-
private readonly taskProvider;
|
|
358
|
-
private readonly taskManager;
|
|
359
|
-
private readonly linter;
|
|
360
|
-
constructor(taskProvider: FileSystemTaskProvider, taskManager: TaskManager, linter: FileSystemTaskLinter);
|
|
361
|
-
list(options?: ListTasksOptions): Promise<Task[]>;
|
|
362
|
-
start(taskId: TaskId, options?: StartTaskOptions): Promise<void>;
|
|
363
|
-
pause(taskId: TaskId, options?: PauseTaskOptions): Promise<void>;
|
|
364
|
-
finish(taskId: TaskId, _options?: FinishTaskOptions): Promise<void>;
|
|
365
|
-
lint(options?: LintTasksOptions): Promise<LintResult>;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Main entry point for Taskin CLI
|
|
370
|
-
* Handles dependency injection and initialization
|
|
371
|
-
*/
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Factory function to create a configured Taskin instance
|
|
375
|
-
*/
|
|
376
|
-
declare function createTaskin(tasksDir?: string): Taskin;
|
|
377
|
-
/**
|
|
378
|
-
* Get the default Taskin instance
|
|
379
|
-
* Uses current working directory TASKS folder
|
|
380
|
-
*/
|
|
381
|
-
declare function getTaskin(): Taskin;
|
|
382
|
-
|
|
383
|
-
export { type ITaskin, Taskin, createTaskin, getTaskin };
|