@opentask/taskin-task-manager 2.0.1 → 3.0.1

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +244 -0
  2. package/dist/index.d.ts +4 -3
  3. package/dist/index.js +4 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/metrics.types.d.ts +64 -73
  6. package/dist/provider-stats.test.js.map +1 -1
  7. package/dist/task-manager.d.ts +23 -7
  8. package/dist/task-manager.js +31 -3
  9. package/dist/task-manager.js.map +1 -1
  10. package/dist/task-manager.mock.d.ts +3 -4
  11. package/dist/task-manager.mock.js +2 -3
  12. package/dist/task-manager.mock.js.map +1 -1
  13. package/dist/task-manager.test.js +58 -17
  14. package/dist/task-manager.test.js.map +1 -1
  15. package/dist/task-manager.types.d.ts +49 -37
  16. package/dist/user-registry.contract.d.ts +13 -0
  17. package/dist/user-registry.contract.js +130 -0
  18. package/dist/user-registry.contract.js.map +1 -0
  19. package/dist/user-registry.types.d.ts +51 -0
  20. package/dist/user-registry.types.js +2 -0
  21. package/dist/user-registry.types.js.map +1 -0
  22. package/package.json +17 -9
  23. package/src/index.ts +3 -3
  24. package/src/metrics.types.ts +2 -11
  25. package/src/provider-stats.test.ts +6 -3
  26. package/src/task-manager.mock.ts +4 -6
  27. package/src/task-manager.test.ts +83 -34
  28. package/src/task-manager.ts +44 -10
  29. package/src/task-manager.types.ts +56 -38
  30. package/src/user-registry.contract.ts +151 -0
  31. package/src/user-registry.types.ts +55 -0
  32. package/tsconfig.json +1 -0
  33. package/.turbo/turbo-build.log +0 -5
  34. package/.turbo/turbo-format.log +0 -18
  35. package/.turbo/turbo-install.log +0 -6
  36. package/.turbo/turbo-lint$colon$fix.log +0 -12
  37. package/.turbo/turbo-lint.log +0 -13
  38. package/.turbo/turbo-test.log +0 -15
  39. package/.turbo/turbo-typecheck.log +0 -5
  40. package/eslint.config.js +0 -8
  41. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,249 @@
1
1
  # @opentask/taskin-task-manager
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [346f1d4]
8
+ - @opentask/taskin-types@2.1.0
9
+
10
+ ## 3.0.0
11
+
12
+ ### Major Changes
13
+
14
+ - b4b259e: Faz a marca `TaskId` valer algo e passa a validar id na borda.
15
+
16
+ `TaskIdSchema` exigia `.uuid()`, mas id de task é a parte numérica do nome do
17
+ arquivo — `task-020-foo.md` produz `020`. Nenhuma task real passava no schema:
18
+ `TaskSchema.safeParse({ id: '020' })` falhava com "Invalid uuid", e o único
19
+ caminho até um `TaskId` era `satisfies string as TaskId`, um cast. Uma marca que
20
+ só se alcança por cast não carrega informação: custa cast em toda fronteira e
21
+ não previne nada.
22
+
23
+ ## Ids
24
+
25
+ - `TaskIdSchema` passa a exigir `/^\d+$/`, a forma que o provider produz.
26
+ - `parseTaskId` e `parseGroupId` são o único caminho suportado até as marcas —
27
+ eles validam e lançam. Os casts saíram do provider.
28
+ - `groupId` no `TaskSchema` passa a ser `GroupId`, marca que já existia e não era
29
+ usada no domínio.
30
+
31
+ Ao trocar a regex, **11 testes do `types-ts` quebraram de uma vez** — todos por
32
+ fixture de UUID. As fixtures agora usam ids de verdade e o UUID entra como caso
33
+ rejeitado.
34
+
35
+ ## Contrato
36
+
37
+ `findTask`, `startTask`, `pauseTask`, `finishTask` e `reviewTask` passam a
38
+ receber `TaskId` em vez de `string`, em `ITaskProvider`, `ITaskManager`, na
39
+ classe `TaskManager` e no `FileSystemTaskProvider`. Na CLI, `normalizeTaskId` é a
40
+ fronteira: aceita o que a pessoa digita (`20`, `020`, `task-020`), normaliza e
41
+ valida. Substitui o `replace(/^task-/, '').padStart(3, '0')` que estava copiado
42
+ em quatro comandos.
43
+
44
+ Os membros de `ITaskProvider` e `ITaskManager` passaram a ser propriedades de
45
+ função em vez de métodos. TypeScript trata método como bivariante mesmo sob
46
+ `strictFunctionTypes`, e isso deixava `ITaskProvider<TaskFile>` ser atribuído a
47
+ `ITaskProvider<Task>` — compila e depois quebra em `updateTask`, que abre com
48
+ `fs.readFile(task.filePath)`.
49
+
50
+ ## Fronteiras que validam
51
+
52
+ - **WebSocket**: `handleUpdateRequest` fazia `message.payload as TTask` e
53
+ entregava JSON não confiável ao provider. Agora relê a task armazenada e aplica
54
+ só o bloco de priorização, via `applyTaskUpdate` (módulo novo, com teste). Os
55
+ campos são substituídos em bloco de propósito: `JSON.stringify` descarta
56
+ `undefined`, então "desagrupar" chega como chave ausente, não como `null`.
57
+ - **WebSocket e MCP**: id vindo do cliente passa por `safeParse` e devolve erro
58
+ claro, em vez de deixar `ZodError` vazar pelo catch genérico.
59
+ - `getAllTasks` ignora `task-*.md` sem id numérico. Antes viravam task fantasma
60
+ de id `'unknown'` — e duas delas colidiam no mesmo id.
61
+
62
+ ## Breaking changes
63
+
64
+ **`@opentask/taskin-types`**
65
+
66
+ - `TaskIdSchema` rejeita UUID e aceita `/^\d+$/`. Quem persistiu id em outro
67
+ formato não passa mais em `TaskSchema`.
68
+ - `TaskSchema.groupId` é `GroupId`, não `string`.
69
+
70
+ **`@opentask/taskin-task-manager`**
71
+
72
+ - Os parâmetros de id são `TaskId`. Um `string` solto não compila mais — use
73
+ `parseTaskId`.
74
+ - `CreateTaskResult.taskId` foi removido: duplicava `task.id` com tipo mais
75
+ fraco. O provider de arquivos segue devolvendo `filePath` em
76
+ `CreateTaskFileResult`.
77
+ - `ITaskProvider` e `ITaskManager` declaram propriedades de função. Classes que
78
+ os implementam continuam compilando; o que deixa de compilar é atribuir um
79
+ provider específico onde se espera o genérico — que era exatamente o bug.
80
+
81
+ **`@opentask/taskin-design-vue`**
82
+
83
+ - `TaskId` e `GroupId` são reexportados de `@opentask/taskin-types` em vez de
84
+ redeclarados. Duas marcas para o mesmo conceito obrigavam um cast em toda
85
+ fronteira, e foi um desses casts que engoliu campos calado.
86
+
87
+ ## API nova
88
+
89
+ `@opentask/taskin-types` exporta `parseTaskId`, `parseGroupId`,
90
+ `TaskPrioritizationUpdateSchema` (a fatia mutável de uma task, derivada de
91
+ `TaskSchema` para não poder divergir) e `NOTIFICATION_PROVIDERS` /
92
+ `NotificationProviderNameSchema`.
93
+ - 30b3e4a: Torna o provider e o manager genéricos sobre a forma da task, e promove `paused`
94
+ a status de primeira classe.
95
+
96
+ ## Provider agnóstico
97
+
98
+ `TaskFile` era declarado no pacote agnóstico e o `ITaskProvider` inteiro era
99
+ tipado nele, obrigando qualquer provider não-arquivo (GitHub, Redmine) a inventar
100
+ `content` e `filePath`. Agora `ITaskProvider` e `ITaskManager` são genéricos sobre
101
+ `TTask extends Task`, com default `Task`, e `TaskFile` mora no
102
+ `file-system-task-provider`.
103
+
104
+ Os call sites não mudam — `new TaskManager(provider)` infere a forma sozinho.
105
+
106
+ Junto disso:
107
+
108
+ - o strip em runtime (`toTask`) saiu do manager; campos específicos do provider
109
+ agora sobrevivem às transições de status
110
+ - corrigido broadcast do WebSocket que podia emitir `payload: undefined` após
111
+ start/finish, deixando os clientes dessincronizados sem erro nenhum
112
+ - `pinia` e dashboard passam a falar `Task`; o provider de arquivos projeta
113
+ `content` em `description` na fronteira
114
+
115
+ ## Status `paused`
116
+
117
+ O comando `pause` gravava `pending`, apagando a diferença entre "nunca começou" e
118
+ "começou e parou". Agora `paused` existe no domínio, com `ITaskManager.pauseTask`
119
+ e retomada via `startTask`.
120
+
121
+ As quatro listas de status que eram mantidas à mão (schema, linter da CLI,
122
+ `task-validator`, metrics adapter) agora derivam de `TASK_STATUSES`. A divergência
123
+ entre elas já era bug: `in-review`, que o próprio `reviewTask` grava, era
124
+ rejeitado pelo linter, ignorado pelo metrics adapter e sumia tanto de
125
+ `taskin list --open` quanto de `--closed`.
126
+
127
+ ## Breaking changes
128
+
129
+ **`@opentask/taskin-task-manager`**
130
+
131
+ - `ITaskProvider` e `ITaskManager` ganharam parâmetro de tipo. O default mantém o
132
+ uso comum compilando, mas quem *implementa* `ITaskManager` precisa adicionar
133
+ `pauseTask`.
134
+ - `TaskFile` saiu daqui. Importe de `@opentask/taskin-file-system-provider`.
135
+ - `CreateTaskResult` não tem mais `filePath`. O provider de arquivos devolve
136
+ `CreateTaskFileResult`, que o mantém.
137
+
138
+ **`@opentask/taskin-types`**
139
+
140
+ - `TaskStatus` e `TASK_STATUSES` ganharam `'paused'`. Consumidores exaustivos
141
+ (`Record<TaskStatus, T>`, `switch` sem `default`) precisam tratar o caso novo.
142
+
143
+ **`@opentask/taskin-design-vue`**
144
+
145
+ - `TaskStatus` ganhou `'in-review'` e `'canceled'` para alinhar com o domínio,
146
+ com o mesmo efeito sobre consumidores exaustivos.
147
+
148
+ **`@opentask/taskin-task-provider-pinia`**
149
+
150
+ - O store guarda `Task` em vez de `TaskFile`: `tasks`, `findTask`, `getAllTasks` e
151
+ `updateTask` não expõem mais `content` nem `filePath`. Use `description`.
152
+
153
+ **`@opentask/taskin-task-server-ws`**
154
+
155
+ - `TaskWebSocketServer` e `TaskServerConfig` ganharam parâmetro de tipo (com
156
+ default). `MockTaskProvider` recebe `Task[]` em vez de `TaskFile[]`.
157
+
158
+ **`@opentask/taskin-task-server-mcp`**
159
+
160
+ - `MockMCPTaskManager.getAllTasks()` e `getTask()` devolvem `Task`, sem `filePath`
161
+ nem `content`.
162
+
163
+ ### Minor Changes
164
+
165
+ - 2240253: Faz o `provider.type` do `.taskin.json` valer, e move a porta `IUserRegistry`
166
+ para o pacote agnostico.
167
+
168
+ `provider.type` era lido **apenas** pelo `init`. Todos os comandos instanciavam
169
+ `new FileSystemTaskProvider(...)` e `new UserRegistry(...)` direto, em treze
170
+ lugares, entao escolher outro provider na configuracao nao tinha efeito nenhum —
171
+ foi o que deixou a task-002 (Redmine) parada mesmo com a spec pronta.
172
+
173
+ ## Factory
174
+
175
+ `resolveTaskProvider()` (em `packages/cli/src/lib/provider-factory/`) e agora o
176
+ unico lugar que nomeia um provider concreto, e devolve o par provider + user
177
+ registry — os dois eram construidos juntos em cada comando, e drifitaram: `list`
178
+ apontava o registry para a raiz do projeto em vez de `.taskin/`, entao todo
179
+ assignee caia silenciosamente em `createTemporaryUser` e o registro real nunca
180
+ era lido.
181
+
182
+ O mapa `PROVIDER_BUILDERS` e injetavel, o que da o seam para testar que
183
+ `provider.type` e respeitado sem precisar de um provider real.
184
+
185
+ `init` e a unica excecao, documentada no codigo: ele roda antes de existir
186
+ `.taskin.json` e e ele quem escreve o `provider.type`, entao usar a factory ali
187
+ seria circular.
188
+
189
+ ## Porta `IUserRegistry`
190
+
191
+ A interface era declarada dentro do `file-system-task-provider`, entao qualquer
192
+ registry de outro provider (GitHub, Redmine) teria que importar do provider de
193
+ arquivos para implementa-la — mesmo defeito de fronteira que a task-031 corrigiu
194
+ para o `TaskFile`, na direcao inversa. Agora mora no `task-manager`, ao lado do
195
+ `ITaskProvider`, e o contract test viaja com ela: qualquer implementacao roda
196
+ `runUserRegistryContractTests` via o subpath novo
197
+ `@opentask/taskin-task-manager/testing` (subpath separado para que `vitest` nao
198
+ entre no grafo de import de runtime).
199
+
200
+ A implementacao file-backed (`UserRegistry`, `users-file-location`,
201
+ `.taskin/.taskin-users.json`) fica onde esta: e backing store de uma
202
+ implementacao, nao configuracao de projeto.
203
+
204
+ ## Criacao de task sai do comando
205
+
206
+ `new.ts` tinha uma copia inteira da criacao de task — numeracao `max(ids)+1`,
207
+ slug do titulo, template markdown — que e semantica de sistema de arquivos. Num
208
+ provider remoto o id vem do proprio store (o numero da issue). Agora o comando
209
+ chama `provider.createTask()` e so consome o resultado; o `generateTaskMarkdown`
210
+ duplicado saiu, ficando o do provider, que e i18n-aware.
211
+
212
+ ## Credencial via ambiente
213
+
214
+ A config do provider passa por expansao de `${VAR}` (reusando o resolver que a
215
+ config de notificacoes ja usava), porque todo provider remoto precisa de
216
+ credencial e o `.taskin.json` e versionado.
217
+
218
+ ## Breaking changes
219
+
220
+ **`taskin`**
221
+
222
+ - `createTaskin()` e `getTaskin()` agora devolvem `Promise<Taskin>`. Qual
223
+ provider usar e pergunta de runtime, e o user registry precisa ser carregado
224
+ antes de qualquer leitura de task.
225
+ - `provider.type` diferente de `fs` agora **falha com mensagem explicita** em vez
226
+ de silenciosamente usar o provider de arquivos. Quem tinha type errado na
227
+ configuracao passa a ver o erro.
228
+ - `Taskin` recebe `ITaskProvider`/`ITaskManager` em vez de
229
+ `FileSystemTaskProvider`/`TaskManager` concretos.
230
+
231
+ **`@opentask/taskin-file-system-provider`**
232
+
233
+ - `IUserRegistry` nao e mais exportada daqui. Importe de
234
+ `@opentask/taskin-task-manager`.
235
+ - `runUserRegistryContractTests` mudou de lugar: importe de
236
+ `@opentask/taskin-task-manager/testing`.
237
+ - `FileSystemTaskProvider` e `FileSystemMetricsAdapter` recebem `IUserRegistry`
238
+ em vez da classe `UserRegistry`.
239
+
240
+ ### Patch Changes
241
+
242
+ - Updated dependencies [b4b259e]
243
+ - Updated dependencies [30b3e4a]
244
+ - Updated dependencies [2f6d046]
245
+ - @opentask/taskin-types@2.0.0
246
+
3
247
  ## 2.0.1
4
248
 
5
249
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from './metrics.types';
2
- export * from './task-manager';
3
- export * from './task-manager.types';
4
1
  export type { Task, TaskStatus, TaskType, User } from '@opentask/taskin-types';
2
+ export * from './metrics.types.js';
3
+ export * from './task-manager.js';
4
+ export * from './task-manager.types.js';
5
+ export * from './user-registry.types.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export * from './metrics.types';
2
- export * from './task-manager';
3
- export * from './task-manager.types';
1
+ export * from './metrics.types.js';
2
+ export * from './task-manager.js';
3
+ export * from './task-manager.types.js';
4
+ export * from './user-registry.types.js';
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
@@ -1,9 +1,4 @@
1
- import type {
2
- StatsQuery,
3
- TaskStats,
4
- TeamStats,
5
- UserStats,
6
- } from '@opentask/taskin-types';
1
+ import type { StatsQuery, TaskStats, TeamStats, UserStats } from '@opentask/taskin-types';
7
2
  /**
8
3
  * Manages metrics and statistics for users, teams, and tasks.
9
4
  * Aggregates data from Git history and task files to provide productivity insights.
@@ -22,71 +17,67 @@ import type {
22
17
  * @public
23
18
  */
24
19
  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>;
20
+ /**
21
+ * Get productivity metrics for a specific user.
22
+ *
23
+ * Includes code metrics (commits, lines of code), temporal patterns
24
+ * (day of week, time of day), and engagement statistics (completion rate, streaks).
25
+ *
26
+ * @param userId - User identifier (username or registry ID)
27
+ * @param query - Optional query parameters to filter results
28
+ * @param query.period - Time period to analyze ('day' | 'week' | 'month' | 'year')
29
+ * @returns Promise resolving to user statistics
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const stats = await metrics.getUserMetrics('alice', { period: 'month' });
34
+ * console.log(`Tasks completed: ${stats.contributionMetrics.tasksCompleted}`);
35
+ * console.log(`Completion rate: ${(stats.engagementMetrics.completionRate * 100).toFixed(1)}%`);
36
+ * ```
37
+ */
38
+ getUserMetrics(userId: string, query?: StatsQuery): Promise<UserStats>;
39
+ /**
40
+ * Get aggregated metrics for an entire team.
41
+ *
42
+ * Provides overview of team productivity with breakdown by individual contributors.
43
+ * Useful for team retrospectives, capacity planning, and identifying top contributors.
44
+ *
45
+ * @param teamId - Team identifier
46
+ * @param query - Optional query parameters to filter results
47
+ * @returns Promise resolving to team statistics with per-contributor breakdown
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const stats = await metrics.getTeamMetrics('frontend', { period: 'week' });
52
+ * console.log(`Total commits: ${stats.totalCommits}`);
53
+ * console.log(`Contributors: ${stats.totalContributors}`);
54
+ * stats.contributors.forEach(c => {
55
+ * console.log(`${c.username}: ${c.commits} commits, ${c.tasksCompleted} tasks`);
56
+ * });
57
+ * ```
58
+ */
59
+ getTeamMetrics(teamId: string, query?: StatsQuery): Promise<TeamStats>;
60
+ /**
61
+ * Get detailed metrics for a specific task.
62
+ *
63
+ * Includes timeline analysis, contributor activity, code impact metrics,
64
+ * and work patterns. Useful for task retrospectives and understanding
65
+ * effort distribution.
66
+ *
67
+ * @param taskId - Task identifier (task number like '015' or full ID like 'task-015')
68
+ * @param query - Optional query parameters
69
+ * @returns Promise resolving to task-specific statistics
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const stats = await metrics.getTaskMetrics('task-015', {});
74
+ * console.log(`Duration: ${stats.duration} days`);
75
+ * console.log(`Contributors: ${stats.contributors.map(c => c.name).join(', ')}`);
76
+ * if (stats.refactoringMetrics) {
77
+ * console.log(`Simplification ratio: ${stats.refactoringMetrics.simplificationRatio.toFixed(2)}x`);
78
+ * }
79
+ * ```
80
+ */
81
+ getTaskMetrics(taskId: string, query?: StatsQuery): Promise<TaskStats>;
87
82
  }
88
- export type {
89
- TaskStats as TaskMetrics,
90
- TeamStats as TeamMetrics,
91
- UserStats as UserMetrics,
92
- };
83
+ export type { TaskStats as TaskMetrics, TeamStats as TeamMetrics, UserStats as UserMetrics };
@@ -1 +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"}
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,CACvC,CAAC,EAAE,MAAM,EAAE,CAAsE;YACnF,cAAc,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE,CACvC,CAAC,EAAE,MAAM,EAAE,CAAsE;YACnF,cAAc,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE,CACvC,CAAC,EAAE,MAAM,EAAE,CAAsE;SACpF,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"}
@@ -1,10 +1,26 @@
1
- import type { CreateTaskOptions, CreateTaskResult, ITaskManager, ITaskProvider, LintResult, TaskFile } from './task-manager.types';
2
- export declare class TaskManager implements ITaskManager {
1
+ import type { Task, TaskId } from '@opentask/taskin-types';
2
+ import type { CreateTaskOptions, CreateTaskResult, ITaskManager, ITaskProvider, LintResult } from './task-manager.types.js';
3
+ /**
4
+ * Orchestrates task state transitions on top of any {@link ITaskProvider}.
5
+ *
6
+ * `TTask` is inferred from the provider passed to the constructor, so callers
7
+ * get their provider's task shape back without this class ever naming it.
8
+ */
9
+ export declare class TaskManager<TTask extends Task = Task> implements ITaskManager<TTask> {
3
10
  private taskProvider;
4
- constructor(taskProvider: ITaskProvider);
5
- startTask(taskId: string): Promise<TaskFile>;
6
- finishTask(taskId: string): Promise<TaskFile>;
7
- reviewTask(taskId: string): Promise<TaskFile>;
8
- createTask(options: CreateTaskOptions): Promise<CreateTaskResult>;
11
+ constructor(taskProvider: ITaskProvider<TTask>);
12
+ /**
13
+ * Returns a copy of `task` with a new status.
14
+ *
15
+ * TypeScript cannot prove that spreading a generic yields that same generic,
16
+ * so the assertion is required. It is sound here: `status` is a known key of
17
+ * `Task`, and every other field is carried over untouched.
18
+ */
19
+ private withStatus;
20
+ startTask(taskId: TaskId): Promise<TTask>;
21
+ pauseTask(taskId: TaskId): Promise<TTask>;
22
+ finishTask(taskId: TaskId): Promise<TTask>;
23
+ reviewTask(taskId: TaskId): Promise<TTask>;
24
+ createTask(options: CreateTaskOptions): Promise<CreateTaskResult<TTask>>;
9
25
  lint(fix?: boolean): Promise<LintResult>;
10
26
  }
@@ -1,8 +1,24 @@
1
+ /**
2
+ * Orchestrates task state transitions on top of any {@link ITaskProvider}.
3
+ *
4
+ * `TTask` is inferred from the provider passed to the constructor, so callers
5
+ * get their provider's task shape back without this class ever naming it.
6
+ */
1
7
  export class TaskManager {
2
8
  taskProvider;
3
9
  constructor(taskProvider) {
4
10
  this.taskProvider = taskProvider;
5
11
  }
12
+ /**
13
+ * Returns a copy of `task` with a new status.
14
+ *
15
+ * TypeScript cannot prove that spreading a generic yields that same generic,
16
+ * so the assertion is required. It is sound here: `status` is a known key of
17
+ * `Task`, and every other field is carried over untouched.
18
+ */
19
+ withStatus(task, status) {
20
+ return { ...task, status };
21
+ }
6
22
  async startTask(taskId) {
7
23
  const task = await this.taskProvider.findTask(taskId);
8
24
  if (!task) {
@@ -14,7 +30,19 @@ export class TaskManager {
14
30
  if (task.status === 'done') {
15
31
  throw new Error(`Task '${taskId}' is already done.`);
16
32
  }
17
- const updatedTask = { ...task, status: 'in-progress' };
33
+ const updatedTask = this.withStatus(task, 'in-progress');
34
+ await this.taskProvider.updateTask(updatedTask);
35
+ return updatedTask;
36
+ }
37
+ async pauseTask(taskId) {
38
+ const task = await this.taskProvider.findTask(taskId);
39
+ if (!task) {
40
+ throw new Error(`Task with ID '${taskId}' not found.`);
41
+ }
42
+ if (task.status !== 'in-progress') {
43
+ throw new Error(`Task '${taskId}' must be in 'in-progress' status to be paused. Current status: ${task.status}`);
44
+ }
45
+ const updatedTask = this.withStatus(task, 'paused');
18
46
  await this.taskProvider.updateTask(updatedTask);
19
47
  return updatedTask;
20
48
  }
@@ -23,7 +51,7 @@ export class TaskManager {
23
51
  if (!task) {
24
52
  throw new Error(`Task with ID '${taskId}' not found.`);
25
53
  }
26
- const updatedTask = { ...task, status: 'done' };
54
+ const updatedTask = this.withStatus(task, 'done');
27
55
  await this.taskProvider.updateTask(updatedTask);
28
56
  return updatedTask;
29
57
  }
@@ -35,7 +63,7 @@ export class TaskManager {
35
63
  if (task.status !== 'in-progress') {
36
64
  throw new Error(`Task '${taskId}' must be in 'in-progress' status to be reviewed. Current status: ${task.status}`);
37
65
  }
38
- const updatedTask = { ...task, status: 'in-review' };
66
+ const updatedTask = this.withStatus(task, 'in-review');
39
67
  await this.taskProvider.updateTask(updatedTask);
40
68
  return updatedTask;
41
69
  }
@@ -1 +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,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,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,SAAS,MAAM,qEAAqE,IAAI,CAAC,MAAM,EAAE,CAClG,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAa,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QAC/D,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"}
1
+ {"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import type { ITaskProvider, TaskFile } from './task-manager.types';
2
- export declare const createMockTask: (
3
- overrides?: Partial<TaskFile>,
4
- ) => TaskFile;
1
+ import { type Task } from '@opentask/taskin-types';
2
+ import type { ITaskProvider } from './task-manager.types.js';
3
+ export declare const createMockTask: (overrides?: Partial<Task>) => Task;
5
4
  export declare const createMockTaskProvider: () => ITaskProvider;
@@ -1,10 +1,9 @@
1
+ import { parseTaskId } from '@opentask/taskin-types';
1
2
  import { vi } from 'vitest';
2
3
  export const createMockTask = (overrides) => ({
3
- content: '# Task 001 - Implement feature',
4
4
  createdAt: new Date().toISOString(),
5
5
  description: 'A test feature',
6
- filePath: '/tasks/task-001.md',
7
- id: 'task-001',
6
+ id: parseTaskId('001'),
8
7
  status: 'pending',
9
8
  title: 'Implement feature',
10
9
  type: 'feat',
@@ -1 +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;IACb,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;CACpB,CAAC,CAAC"}
1
+ {"version":3,"file":"task-manager.mock.js","sourceRoot":"","sources":["../src/task-manager.mock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAa,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG5B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAyB,EAAQ,EAAE,CAAC,CAAC;IAClE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IACnC,WAAW,EAAE,gBAAgB;IAC7B,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC;IACtB,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;IACb,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;CACpB,CAAC,CAAC"}