@opentask/taskin-task-manager 1.0.6

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.
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @opentask/taskin-task-manager@1.0.6 build /Users/sidarta/repositorios/taskin/packages/task-manager
4
+ > tsc
5
+
@@ -0,0 +1,31 @@
1
+
2
+ 
3
+ > @opentask/taskin-task-manager@1.0.5 format /Users/sidarta/repositorios/taskin/packages/task-manager
4
+ > prettier --write .
5
+
6
+ .turbo/turbo-build.log.turbo/turbo-format.log.turbo/turbo-test.logdist/index.d.tsdist/index.d.ts 59ms (unchanged)
7
+ dist/index.jsdist/index.js 12ms (unchanged)
8
+ dist/index.js.mapdist/metrics.types.d.tsdist/metrics.types.d.ts 19ms (unchanged)
9
+ dist/metrics.types.jsdist/metrics.types.js 3ms (unchanged)
10
+ dist/metrics.types.js.mapdist/provider-stats.test.d.tsdist/provider-stats.test.d.ts 2ms (unchanged)
11
+ dist/provider-stats.test.jsdist/provider-stats.test.js 13ms (unchanged)
12
+ dist/provider-stats.test.js.mapdist/task-manager.d.tsdist/task-manager.d.ts 8ms (unchanged)
13
+ dist/task-manager.jsdist/task-manager.js 19ms (unchanged)
14
+ dist/task-manager.js.mapdist/task-manager.mock.d.tsdist/task-manager.mock.d.ts 8ms (unchanged)
15
+ dist/task-manager.mock.jsdist/task-manager.mock.js 6ms (unchanged)
16
+ dist/task-manager.mock.js.mapdist/task-manager.test.d.tsdist/task-manager.test.d.ts 5ms (unchanged)
17
+ dist/task-manager.test.jsdist/task-manager.test.js 25ms (unchanged)
18
+ dist/task-manager.test.js.mapdist/task-manager.types.d.tsdist/task-manager.types.d.ts 19ms (unchanged)
19
+ dist/task-manager.types.jsdist/task-manager.types.js 2ms (unchanged)
20
+ dist/task-manager.types.js.mapeslint.config.jseslint.config.js 9ms (unchanged)
21
+ package.jsonpackage.json 7ms (unchanged)
22
+ README.mdREADME.md 26ms (unchanged)
23
+ src/index.tssrc/index.ts 4ms (unchanged)
24
+ src/metrics.types.tssrc/metrics.types.ts 14ms (unchanged)
25
+ src/provider-stats.test.tssrc/provider-stats.test.ts 8ms (unchanged)
26
+ src/task-manager.mock.tssrc/task-manager.mock.ts 13ms (unchanged)
27
+ src/task-manager.test.tssrc/task-manager.test.ts 48ms (unchanged)
28
+ src/task-manager.tssrc/task-manager.ts 27ms (unchanged)
29
+ src/task-manager.types.tssrc/task-manager.types.ts 9ms (unchanged)
30
+ tsconfig.jsontsconfig.json 2ms (unchanged)
31
+ tsconfig.tsbuildinfovitest.config.tsvitest.config.ts 4ms (unchanged)
@@ -0,0 +1,15 @@
1
+
2
+ > @opentask/taskin-task-manager@1.0.5 test /Users/sidarta/repositorios/taskin/packages/task-manager
3
+ > vitest run
4
+
5
+
6
+  RUN  v4.0.16 /Users/sidarta/repositorios/taskin/packages/task-manager
7
+
8
+ ✓ src/provider-stats.test.ts (1 test) 1ms
9
+ ✓ src/task-manager.test.ts (9 tests) 60ms
10
+
11
+  Test Files  2 passed (2)
12
+  Tests  10 passed (10)
13
+  Start at  09:48:51
14
+  Duration  496ms (transform 59ms, setup 0ms, import 108ms, tests 62ms, environment 0ms)
15
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @opentask/taskin-task-manager
2
+
3
+ ## 1.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - chore: publish packages required by taskin CLI
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Task Manager — Metrics Interface
2
+
3
+ This document describes the new `IMetricsManager` interface introduced to
4
+ separate metrics/analytics responsibilities from the core `ITaskProvider`.
5
+
6
+ ## Motivation
7
+
8
+ Previously the `ITaskProvider` exposed optional methods for statistics
9
+ (`getUserStats`, `getTeamStats`, `getTaskStats`). To keep provider
10
+ implementations focused on storage concerns and to provide a clearer
11
+ contract for analytics, we introduced `IMetricsManager` as a dedicated
12
+ interface for metrics/analytics.
13
+
14
+ ## Interface
15
+
16
+ The interface lives at `packages/task-manager/src/metrics.types.ts`:
17
+
18
+ - `getUserMetrics(userId: string, query?: StatsQuery): Promise<UserStats>`
19
+ - `getTeamMetrics(teamId: string, query?: StatsQuery): Promise<TeamStats>`
20
+ - `getTaskMetrics(taskId: string, query?: StatsQuery): Promise<TaskStats>`
21
+
22
+ Types are re-used from `@opentask/taskin-types` (`UserStats`, `TeamStats`,
23
+ `TaskStats`, `StatsQuery`).
24
+
25
+ ## Migration guide
26
+
27
+ 1. Remove any optional stats methods from provider implementations. The
28
+ methods were removed from `ITaskProvider` to avoid mixing concerns.
29
+ 2. Implement an adapter/service that implements `IMetricsManager` and
30
+ register it alongside your provider (for example, provide it to the
31
+ application bootstrap or dependency injection container).
32
+ 3. If you need to preserve backward compatibility, expose a thin adapter
33
+ that translates the old provider-level methods to the new
34
+ `IMetricsManager` API until consumers migrate.
35
+
36
+ ## Example
37
+
38
+ Minimal example of an in-process metrics adapter (pseudo-code):
39
+
40
+ ```ts
41
+ import type { IMetricsManager } from '@opentask/taskin-task-manager';
42
+ import type { UserStats, StatsQuery } from '@opentask/taskin-types';
43
+
44
+ export class FsMetricsAdapter implements IMetricsManager {
45
+ constructor(private fsProvider: any) {}
46
+
47
+ async getUserMetrics(userId: string, query?: StatsQuery): Promise<UserStats> {
48
+ // Aggregate tasks from fsProvider and compute simple metrics
49
+ const tasks = await this.fsProvider.getAllTasks();
50
+ const userTasks = tasks.filter((t) => t.assignee?.id === userId);
51
+ return { userId, taskCount: userTasks.length } as any;
52
+ }
53
+
54
+ // getTeamMetrics / getTaskMetrics implementations...
55
+ }
56
+ ```
57
+
58
+ ## Compatibility and Versioning
59
+
60
+ - This change is a breaking API change for providers: `ITaskProvider`
61
+ no longer contains stats methods. New providers should implement
62
+ `IMetricsManager` instead.
63
+ - For consumers, prefer depending on `IMetricsManager` for analytics
64
+ features. If a provider exposes metrics through a different channel,
65
+ adapt it to `IMetricsManager`.
66
+
67
+ ## Tests
68
+
69
+ There is a small unit test validating the contract at
70
+ `packages/task-manager/src/provider-stats.test.ts` (now checks the
71
+ `IMetricsManager` shape).
72
+
73
+ ---
74
+
75
+ If you want, I can:
76
+
77
+ - Add an example adapter implementation in `file-system-task-provider`.
78
+ - Add documentation to the repo-level `docs/` folder and cross-links.
79
+
80
+ Tell me which and I’ll implement it.
81
+
82
+ # @taskin/task-manager
83
+
84
+ This package is responsible for the core logic of managing tasks.
@@ -0,0 +1,3 @@
1
+ export type { Task, TaskStatus, TaskType, User } from '@opentask/taskin-types';
2
+ export * from './task-manager';
3
+ export * from './task-manager.types';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './task-manager';
2
+ export * from './task-manager.types';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,92 @@
1
+ import type {
2
+ StatsQuery,
3
+ TaskStats,
4
+ TeamStats,
5
+ UserStats,
6
+ } from '@opentask/taskin-types';
7
+ /**
8
+ * Manages metrics and statistics for users, teams, and tasks.
9
+ * Aggregates data from Git history and task files to provide productivity insights.
10
+ *
11
+ * @remarks
12
+ * This interface separates analytics/reporting responsibilities from ITaskProvider,
13
+ * following the Single Responsibility Principle.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const metrics = new FileSystemMetricsAdapter(tasksDir, userRegistry, gitAnalyzer);
18
+ * const stats = await metrics.getUserMetrics('john-doe', { period: 'week' });
19
+ * console.log(`Commits: ${stats.codeMetrics.commits}`);
20
+ * ```
21
+ *
22
+ * @public
23
+ */
24
+ export interface IMetricsManager {
25
+ /**
26
+ * Get productivity metrics for a specific user.
27
+ *
28
+ * Includes code metrics (commits, lines of code), temporal patterns
29
+ * (day of week, time of day), and engagement statistics (completion rate, streaks).
30
+ *
31
+ * @param userId - User identifier (username or registry ID)
32
+ * @param query - Optional query parameters to filter results
33
+ * @param query.period - Time period to analyze ('day' | 'week' | 'month' | 'year')
34
+ * @returns Promise resolving to user statistics
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const stats = await metrics.getUserMetrics('alice', { period: 'month' });
39
+ * console.log(`Tasks completed: ${stats.contributionMetrics.tasksCompleted}`);
40
+ * console.log(`Completion rate: ${(stats.engagementMetrics.completionRate * 100).toFixed(1)}%`);
41
+ * ```
42
+ */
43
+ getUserMetrics(userId: string, query?: StatsQuery): Promise<UserStats>;
44
+ /**
45
+ * Get aggregated metrics for an entire team.
46
+ *
47
+ * Provides overview of team productivity with breakdown by individual contributors.
48
+ * Useful for team retrospectives, capacity planning, and identifying top contributors.
49
+ *
50
+ * @param teamId - Team identifier
51
+ * @param query - Optional query parameters to filter results
52
+ * @returns Promise resolving to team statistics with per-contributor breakdown
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const stats = await metrics.getTeamMetrics('frontend', { period: 'week' });
57
+ * console.log(`Total commits: ${stats.totalCommits}`);
58
+ * console.log(`Contributors: ${stats.totalContributors}`);
59
+ * stats.contributors.forEach(c => {
60
+ * console.log(`${c.username}: ${c.commits} commits, ${c.tasksCompleted} tasks`);
61
+ * });
62
+ * ```
63
+ */
64
+ getTeamMetrics(teamId: string, query?: StatsQuery): Promise<TeamStats>;
65
+ /**
66
+ * Get detailed metrics for a specific task.
67
+ *
68
+ * Includes timeline analysis, contributor activity, code impact metrics,
69
+ * and work patterns. Useful for task retrospectives and understanding
70
+ * effort distribution.
71
+ *
72
+ * @param taskId - Task identifier (task number like '015' or full ID like 'task-015')
73
+ * @param query - Optional query parameters
74
+ * @returns Promise resolving to task-specific statistics
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const stats = await metrics.getTaskMetrics('task-015', {});
79
+ * console.log(`Duration: ${stats.duration} days`);
80
+ * console.log(`Contributors: ${stats.contributors.map(c => c.name).join(', ')}`);
81
+ * if (stats.refactoringMetrics) {
82
+ * console.log(`Simplification ratio: ${stats.refactoringMetrics.simplificationRatio.toFixed(2)}x`);
83
+ * }
84
+ * ```
85
+ */
86
+ getTaskMetrics(taskId: string, query?: StatsQuery): Promise<TaskStats>;
87
+ }
88
+ export type {
89
+ TaskStats as TaskMetrics,
90
+ TeamStats as TeamMetrics,
91
+ UserStats as UserMetrics,
92
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=metrics.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.types.js","sourceRoot":"","sources":["../src/metrics.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ describe('IMetricsManager contract', () => {
3
+ it('accepts an object implementing IMetricsManager', async () => {
4
+ const metrics = {
5
+ getUserMetrics: async (userId) => ({ userId }),
6
+ getTeamMetrics: async (teamId) => ({ teamId }),
7
+ getTaskMetrics: async (taskId) => ({ taskId }),
8
+ };
9
+ expect(typeof metrics.getUserMetrics).toBe('function');
10
+ expect(typeof metrics.getTeamMetrics).toBe('function');
11
+ expect(typeof metrics.getTaskMetrics).toBe('function');
12
+ await expect(metrics.getUserMetrics('u1')).resolves.toBeDefined();
13
+ await expect(metrics.getTeamMetrics('t1')).resolves.toBeDefined();
14
+ await expect(metrics.getTaskMetrics('task1')).resolves.toBeDefined();
15
+ });
16
+ });
17
+ //# sourceMappingURL=provider-stats.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-stats.test.js","sourceRoot":"","sources":["../src/provider-stats.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG9C,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,OAAO,GAAoB;YAC/B,cAAc,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAQ;YAC7D,cAAc,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAQ;YAC7D,cAAc,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAQ;SAC9D,CAAC;QAEF,MAAM,CAAC,OAAO,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEvD,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAClE,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAClE,MAAM,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type {
2
+ CreateTaskOptions,
3
+ CreateTaskResult,
4
+ ITaskManager,
5
+ ITaskProvider,
6
+ LintResult,
7
+ TaskFile,
8
+ } from './task-manager.types';
9
+ export declare class TaskManager implements ITaskManager {
10
+ private taskProvider;
11
+ constructor(taskProvider: ITaskProvider);
12
+ startTask(taskId: string): Promise<TaskFile>;
13
+ finishTask(taskId: string): Promise<TaskFile>;
14
+ createTask(options: CreateTaskOptions): Promise<CreateTaskResult>;
15
+ lint(fix?: boolean): Promise<LintResult>;
16
+ }
@@ -0,0 +1,37 @@
1
+ export class TaskManager {
2
+ taskProvider;
3
+ constructor(taskProvider) {
4
+ this.taskProvider = taskProvider;
5
+ }
6
+ async startTask(taskId) {
7
+ const task = await this.taskProvider.findTask(taskId);
8
+ if (!task) {
9
+ throw new Error(`Task with ID '${taskId}' not found.`);
10
+ }
11
+ if (task.status === 'in-progress') {
12
+ throw new Error(`Task '${taskId}' is already in progress.`);
13
+ }
14
+ if (task.status === 'done') {
15
+ throw new Error(`Task '${taskId}' is already done.`);
16
+ }
17
+ const updatedTask = { ...task, status: 'in-progress' };
18
+ await this.taskProvider.updateTask(updatedTask);
19
+ return updatedTask;
20
+ }
21
+ async finishTask(taskId) {
22
+ const task = await this.taskProvider.findTask(taskId);
23
+ if (!task) {
24
+ throw new Error(`Task with ID '${taskId}' not found.`);
25
+ }
26
+ const updatedTask = { ...task, status: 'done' };
27
+ await this.taskProvider.updateTask(updatedTask);
28
+ return updatedTask;
29
+ }
30
+ async createTask(options) {
31
+ return this.taskProvider.createTask(options);
32
+ }
33
+ async lint(fix) {
34
+ return this.taskProvider.lint(fix);
35
+ }
36
+ }
37
+ //# sourceMappingURL=task-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,WAAW;IACF;IAApB,YAAoB,YAA2B;QAA3B,iBAAY,GAAZ,YAAY,CAAe;IAAG,CAAC;IAEnD,KAAK,CAAC,SAAS,CAAC,MAAc;QAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEtD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,MAAM,cAAc,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,2BAA2B,CAAC,CAAC;QAC9D,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,oBAAoB,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,WAAW,GAAa,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QACjE,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAEhD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEtD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,MAAM,cAAc,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,WAAW,GAAa,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1D,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAEhD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAA0B;QACzC,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAa;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ import type { ITaskProvider, TaskFile } from './task-manager.types';
2
+ export declare const createMockTask: (
3
+ overrides?: Partial<TaskFile>,
4
+ ) => TaskFile;
5
+ export declare const createMockTaskProvider: () => ITaskProvider;
@@ -0,0 +1,21 @@
1
+ import { vi } from 'vitest';
2
+ export const createMockTask = (overrides) => ({
3
+ content: '# Task 001 - Implement feature',
4
+ createdAt: new Date().toISOString(),
5
+ description: 'A test feature',
6
+ filePath: '/tasks/task-001.md',
7
+ id: 'task-001',
8
+ status: 'pending',
9
+ title: 'Implement feature',
10
+ type: 'feat',
11
+ userId: 'user-123',
12
+ ...overrides,
13
+ });
14
+ export const createMockTaskProvider = () => ({
15
+ findTask: vi.fn(),
16
+ getAllTasks: vi.fn(),
17
+ updateTask: vi.fn(),
18
+ createTask: vi.fn(),
19
+ lint: vi.fn(),
20
+ });
21
+ //# sourceMappingURL=task-manager.mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.mock.js","sourceRoot":"","sources":["../src/task-manager.mock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG5B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAA6B,EAAY,EAAE,CAAC,CAAC;IAC1E,OAAO,EAAE,gCAAgC;IACzC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IACnC,WAAW,EAAE,gBAAgB;IAC7B,QAAQ,EAAE,oBAAoB;IAC9B,EAAE,EAAE,UAAqC;IACzC,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,mBAAmB;IAC1B,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,UAAU;IAClB,GAAG,SAAS;CACb,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAkB,EAAE,CAAC,CAAC;IAC1D,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;IACjB,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE;IACpB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;CACd,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { TaskManager } from './task-manager';
3
+ import { createMockTask, createMockTaskProvider } from './task-manager.mock';
4
+ describe('TaskManager', () => {
5
+ let taskManager;
6
+ let mockTaskProvider;
7
+ let mockTask;
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ mockTaskProvider = createMockTaskProvider();
11
+ mockTask = createMockTask();
12
+ taskManager = new TaskManager(mockTaskProvider);
13
+ });
14
+ describe('startTask', () => {
15
+ it('should start a pending task', async () => {
16
+ mockTaskProvider.findTask.mockResolvedValue(mockTask);
17
+ const updatedTask = await taskManager.startTask('task-001');
18
+ expect(mockTaskProvider.findTask).toHaveBeenCalledWith('task-001');
19
+ expect(mockTaskProvider.updateTask).toHaveBeenCalledWith(
20
+ expect.objectContaining({ status: 'in-progress' }),
21
+ );
22
+ expect(updatedTask.status).toBe('in-progress');
23
+ });
24
+ it('should throw an error if task is not found', async () => {
25
+ mockTaskProvider.findTask.mockResolvedValue(undefined);
26
+ await expect(taskManager.startTask('not-found')).rejects.toThrow(
27
+ "Task with ID 'not-found' not found.",
28
+ );
29
+ });
30
+ it('should throw an error if task is already in progress', async () => {
31
+ const inProgressTask = { ...mockTask, status: 'in-progress' };
32
+ mockTaskProvider.findTask.mockResolvedValue(inProgressTask);
33
+ await expect(taskManager.startTask('task-001')).rejects.toThrow(
34
+ "Task 'task-001' is already in progress.",
35
+ );
36
+ });
37
+ });
38
+ describe('finishTask', () => {
39
+ it('should finish an in-progress task', async () => {
40
+ const inProgressTask = { ...mockTask, status: 'in-progress' };
41
+ mockTaskProvider.findTask.mockResolvedValue(inProgressTask);
42
+ const updatedTask = await taskManager.finishTask('task-001');
43
+ expect(mockTaskProvider.updateTask).toHaveBeenCalledWith(
44
+ expect.objectContaining({ status: 'done' }),
45
+ );
46
+ expect(updatedTask.status).toBe('done');
47
+ });
48
+ });
49
+ describe('createTask', () => {
50
+ it('should delegate task creation to provider', async () => {
51
+ const createOptions = { title: 'New Task', type: 'feat' };
52
+ const createResult = {
53
+ task: mockTask,
54
+ taskId: '001',
55
+ filePath: '/tasks/task-001.md',
56
+ };
57
+ mockTaskProvider.createTask.mockResolvedValue(createResult);
58
+ const result = await taskManager.createTask(createOptions);
59
+ expect(mockTaskProvider.createTask).toHaveBeenCalledWith(createOptions);
60
+ expect(result).toEqual(createResult);
61
+ });
62
+ it('should pass through description and assignee to provider', async () => {
63
+ const createOptions = {
64
+ title: 'New Task',
65
+ type: 'fix',
66
+ description: 'Fix something',
67
+ assignee: 'john-doe',
68
+ };
69
+ const createResult = {
70
+ task: mockTask,
71
+ taskId: '002',
72
+ filePath: '/tasks/task-002.md',
73
+ };
74
+ mockTaskProvider.createTask.mockResolvedValue(createResult);
75
+ const result = await taskManager.createTask(createOptions);
76
+ expect(mockTaskProvider.createTask).toHaveBeenCalledWith(createOptions);
77
+ expect(result.taskId).toBe('002');
78
+ });
79
+ });
80
+ describe('lint', () => {
81
+ it('should delegate lint without fix to provider', async () => {
82
+ const lintResult = { errors: 0, warnings: 2, details: [] };
83
+ mockTaskProvider.lint.mockResolvedValue(lintResult);
84
+ const result = await taskManager.lint();
85
+ expect(mockTaskProvider.lint).toHaveBeenCalledWith(undefined);
86
+ expect(result).toEqual(lintResult);
87
+ });
88
+ it('should delegate lint with fix=true to provider', async () => {
89
+ const lintResult = { errors: 0, warnings: 0, details: [] };
90
+ mockTaskProvider.lint.mockResolvedValue(lintResult);
91
+ const result = await taskManager.lint(true);
92
+ expect(mockTaskProvider.lint).toHaveBeenCalledWith(true);
93
+ expect(result).toEqual(lintResult);
94
+ });
95
+ it('should delegate lint with fix=false to provider', async () => {
96
+ const lintResult = { errors: 1, warnings: 2, details: [] };
97
+ mockTaskProvider.lint.mockResolvedValue(lintResult);
98
+ const result = await taskManager.lint(false);
99
+ expect(mockTaskProvider.lint).toHaveBeenCalledWith(false);
100
+ expect(result).toEqual(lintResult);
101
+ });
102
+ });
103
+ });
104
+ //# sourceMappingURL=task-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.test.js","sourceRoot":"","sources":["../src/task-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ import type { Task, TaskType, User } from '@opentask/taskin-types';
2
+ /**
3
+ * Options for creating a new task
4
+ * @public
5
+ */
6
+ export interface CreateTaskOptions {
7
+ /** Task title */
8
+ title: string;
9
+ /** Task type (feat, fix, chore, etc.) */
10
+ type: TaskType;
11
+ /** Optional task description */
12
+ description?: string;
13
+ /** Optional assignee user ID or name */
14
+ assignee?: string;
15
+ }
16
+ /**
17
+ * Result of creating a new task
18
+ * @public
19
+ */
20
+ export interface CreateTaskResult {
21
+ /** The created task */
22
+ task: TaskFile;
23
+ /** The generated task ID */
24
+ taskId: string;
25
+ /** The file path where the task was created */
26
+ filePath: string;
27
+ }
28
+ /**
29
+ * Severity level for validation issues
30
+ * @public
31
+ */
32
+ export type ValidationSeverity = 'error' | 'warning' | 'info';
33
+ /**
34
+ * A validation error or warning found during linting
35
+ * @public
36
+ */
37
+ export interface ValidationIssue {
38
+ /** The file or task that has the issue */
39
+ file: string;
40
+ /** Optional line number where the issue occurs */
41
+ line?: number;
42
+ /** Human-readable description of the issue */
43
+ message: string;
44
+ /** Severity level of the issue */
45
+ severity: ValidationSeverity;
46
+ /** Optional suggestion for fixing the issue */
47
+ suggestion?: string;
48
+ }
49
+ /**
50
+ * Result of a lint operation
51
+ * @public
52
+ */
53
+ export interface LintResult {
54
+ /** Whether the lint passed without errors */
55
+ valid: boolean;
56
+ /** List of validation issues found */
57
+ issues: ValidationIssue[];
58
+ /** Number of errors found */
59
+ errorCount: number;
60
+ /** Number of warnings found */
61
+ warningCount: number;
62
+ /** Number of info messages */
63
+ infoCount: number;
64
+ }
65
+ /**
66
+ * A task with additional file system metadata.
67
+ * Extends the base Task type with content and path information.
68
+ * @public
69
+ */
70
+ export type TaskFile = Task & {
71
+ /** The raw markdown content of the task file */
72
+ content: string;
73
+ /** Absolute or relative path to the task file */
74
+ filePath: string;
75
+ /** The type of work this task represents */
76
+ type: TaskType;
77
+ /** Optional user assigned to this task */
78
+ assignee?: User;
79
+ };
80
+ /**
81
+ * Interface for task storage providers.
82
+ * Implementations handle reading and writing tasks from different sources
83
+ * (e.g., file system, database, API).
84
+ * @public
85
+ */
86
+ export interface ITaskProvider {
87
+ /**
88
+ * Find a specific task by its ID.
89
+ * @param taskId - The unique identifier of the task
90
+ * @returns The task if found, undefined otherwise
91
+ */
92
+ findTask(taskId: string): Promise<TaskFile | undefined>;
93
+ /**
94
+ * Retrieve all tasks from the provider.
95
+ * @returns Array of all tasks
96
+ */
97
+ getAllTasks(): Promise<TaskFile[]>;
98
+ /**
99
+ * Update an existing task.
100
+ * @param task - The task with updated information
101
+ */
102
+ updateTask(task: TaskFile): Promise<void>;
103
+ /**
104
+ * Create a new task.
105
+ * @param options - Options for creating the task
106
+ * @returns The created task information
107
+ */
108
+ createTask(options: CreateTaskOptions): Promise<CreateTaskResult>;
109
+ /**
110
+ * Validate all tasks managed by this provider.
111
+ * Each provider knows its own format and validation rules.
112
+ * @param fix - If true, attempt to automatically fix validation issues
113
+ * @returns The lint result with any validation issues found
114
+ */
115
+ lint(fix?: boolean): Promise<LintResult>;
116
+ }
117
+ /**
118
+ * Interface for task management operations.
119
+ * Provides high-level methods for managing task workflow and state transitions.
120
+ * @public
121
+ */
122
+ export interface ITaskManager {
123
+ /**
124
+ * Mark a task as finished.
125
+ * Transitions the task to 'done' status.
126
+ * @param taskId - The unique identifier of the task
127
+ * @returns The updated task
128
+ * @throws Error if task is not found
129
+ */
130
+ finishTask(taskId: string): Promise<TaskFile>;
131
+ /**
132
+ * Start working on a task.
133
+ * Transitions the task to 'in-progress' status.
134
+ * @param taskId - The unique identifier of the task
135
+ * @returns The updated task
136
+ * @throws Error if task is not found, already in progress, or already done
137
+ */
138
+ startTask(taskId: string): Promise<TaskFile>;
139
+ /**
140
+ * Create a new task.
141
+ * @param options - Options for creating the task
142
+ * @returns The created task information
143
+ */
144
+ createTask(options: CreateTaskOptions): Promise<CreateTaskResult>;
145
+ /**
146
+ * Validate all tasks in the system.
147
+ * Delegates to the underlying provider's lint implementation.
148
+ * @param fix - If true, attempt to automatically fix validation issues
149
+ * @returns The lint result with any validation issues found
150
+ */
151
+ lint(fix?: boolean): Promise<LintResult>;
152
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=task-manager.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-manager.types.js","sourceRoot":"","sources":["../src/task-manager.types.ts"],"names":[],"mappings":""}