@opentask/taskin-task-manager 2.0.0 → 3.0.0

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 +248 -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 -10
  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 -4
  40. package/eslint.config.js +0 -8
  41. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,253 @@
1
1
  # @opentask/taskin-task-manager
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - b4b259e: Faz a marca `TaskId` valer algo e passa a validar id na borda.
8
+
9
+ `TaskIdSchema` exigia `.uuid()`, mas id de task é a parte numérica do nome do
10
+ arquivo — `task-020-foo.md` produz `020`. Nenhuma task real passava no schema:
11
+ `TaskSchema.safeParse({ id: '020' })` falhava com "Invalid uuid", e o único
12
+ caminho até um `TaskId` era `satisfies string as TaskId`, um cast. Uma marca que
13
+ só se alcança por cast não carrega informação: custa cast em toda fronteira e
14
+ não previne nada.
15
+
16
+ ## Ids
17
+
18
+ - `TaskIdSchema` passa a exigir `/^\d+$/`, a forma que o provider produz.
19
+ - `parseTaskId` e `parseGroupId` são o único caminho suportado até as marcas —
20
+ eles validam e lançam. Os casts saíram do provider.
21
+ - `groupId` no `TaskSchema` passa a ser `GroupId`, marca que já existia e não era
22
+ usada no domínio.
23
+
24
+ Ao trocar a regex, **11 testes do `types-ts` quebraram de uma vez** — todos por
25
+ fixture de UUID. As fixtures agora usam ids de verdade e o UUID entra como caso
26
+ rejeitado.
27
+
28
+ ## Contrato
29
+
30
+ `findTask`, `startTask`, `pauseTask`, `finishTask` e `reviewTask` passam a
31
+ receber `TaskId` em vez de `string`, em `ITaskProvider`, `ITaskManager`, na
32
+ classe `TaskManager` e no `FileSystemTaskProvider`. Na CLI, `normalizeTaskId` é a
33
+ fronteira: aceita o que a pessoa digita (`20`, `020`, `task-020`), normaliza e
34
+ valida. Substitui o `replace(/^task-/, '').padStart(3, '0')` que estava copiado
35
+ em quatro comandos.
36
+
37
+ Os membros de `ITaskProvider` e `ITaskManager` passaram a ser propriedades de
38
+ função em vez de métodos. TypeScript trata método como bivariante mesmo sob
39
+ `strictFunctionTypes`, e isso deixava `ITaskProvider<TaskFile>` ser atribuído a
40
+ `ITaskProvider<Task>` — compila e depois quebra em `updateTask`, que abre com
41
+ `fs.readFile(task.filePath)`.
42
+
43
+ ## Fronteiras que validam
44
+
45
+ - **WebSocket**: `handleUpdateRequest` fazia `message.payload as TTask` e
46
+ entregava JSON não confiável ao provider. Agora relê a task armazenada e aplica
47
+ só o bloco de priorização, via `applyTaskUpdate` (módulo novo, com teste). Os
48
+ campos são substituídos em bloco de propósito: `JSON.stringify` descarta
49
+ `undefined`, então "desagrupar" chega como chave ausente, não como `null`.
50
+ - **WebSocket e MCP**: id vindo do cliente passa por `safeParse` e devolve erro
51
+ claro, em vez de deixar `ZodError` vazar pelo catch genérico.
52
+ - `getAllTasks` ignora `task-*.md` sem id numérico. Antes viravam task fantasma
53
+ de id `'unknown'` — e duas delas colidiam no mesmo id.
54
+
55
+ ## Breaking changes
56
+
57
+ **`@opentask/taskin-types`**
58
+
59
+ - `TaskIdSchema` rejeita UUID e aceita `/^\d+$/`. Quem persistiu id em outro
60
+ formato não passa mais em `TaskSchema`.
61
+ - `TaskSchema.groupId` é `GroupId`, não `string`.
62
+
63
+ **`@opentask/taskin-task-manager`**
64
+
65
+ - Os parâmetros de id são `TaskId`. Um `string` solto não compila mais — use
66
+ `parseTaskId`.
67
+ - `CreateTaskResult.taskId` foi removido: duplicava `task.id` com tipo mais
68
+ fraco. O provider de arquivos segue devolvendo `filePath` em
69
+ `CreateTaskFileResult`.
70
+ - `ITaskProvider` e `ITaskManager` declaram propriedades de função. Classes que
71
+ os implementam continuam compilando; o que deixa de compilar é atribuir um
72
+ provider específico onde se espera o genérico — que era exatamente o bug.
73
+
74
+ **`@opentask/taskin-design-vue`**
75
+
76
+ - `TaskId` e `GroupId` são reexportados de `@opentask/taskin-types` em vez de
77
+ redeclarados. Duas marcas para o mesmo conceito obrigavam um cast em toda
78
+ fronteira, e foi um desses casts que engoliu campos calado.
79
+
80
+ ## API nova
81
+
82
+ `@opentask/taskin-types` exporta `parseTaskId`, `parseGroupId`,
83
+ `TaskPrioritizationUpdateSchema` (a fatia mutável de uma task, derivada de
84
+ `TaskSchema` para não poder divergir) e `NOTIFICATION_PROVIDERS` /
85
+ `NotificationProviderNameSchema`.
86
+ - 30b3e4a: Torna o provider e o manager genéricos sobre a forma da task, e promove `paused`
87
+ a status de primeira classe.
88
+
89
+ ## Provider agnóstico
90
+
91
+ `TaskFile` era declarado no pacote agnóstico e o `ITaskProvider` inteiro era
92
+ tipado nele, obrigando qualquer provider não-arquivo (GitHub, Redmine) a inventar
93
+ `content` e `filePath`. Agora `ITaskProvider` e `ITaskManager` são genéricos sobre
94
+ `TTask extends Task`, com default `Task`, e `TaskFile` mora no
95
+ `file-system-task-provider`.
96
+
97
+ Os call sites não mudam — `new TaskManager(provider)` infere a forma sozinho.
98
+
99
+ Junto disso:
100
+
101
+ - o strip em runtime (`toTask`) saiu do manager; campos específicos do provider
102
+ agora sobrevivem às transições de status
103
+ - corrigido broadcast do WebSocket que podia emitir `payload: undefined` após
104
+ start/finish, deixando os clientes dessincronizados sem erro nenhum
105
+ - `pinia` e dashboard passam a falar `Task`; o provider de arquivos projeta
106
+ `content` em `description` na fronteira
107
+
108
+ ## Status `paused`
109
+
110
+ O comando `pause` gravava `pending`, apagando a diferença entre "nunca começou" e
111
+ "começou e parou". Agora `paused` existe no domínio, com `ITaskManager.pauseTask`
112
+ e retomada via `startTask`.
113
+
114
+ As quatro listas de status que eram mantidas à mão (schema, linter da CLI,
115
+ `task-validator`, metrics adapter) agora derivam de `TASK_STATUSES`. A divergência
116
+ entre elas já era bug: `in-review`, que o próprio `reviewTask` grava, era
117
+ rejeitado pelo linter, ignorado pelo metrics adapter e sumia tanto de
118
+ `taskin list --open` quanto de `--closed`.
119
+
120
+ ## Breaking changes
121
+
122
+ **`@opentask/taskin-task-manager`**
123
+
124
+ - `ITaskProvider` e `ITaskManager` ganharam parâmetro de tipo. O default mantém o
125
+ uso comum compilando, mas quem *implementa* `ITaskManager` precisa adicionar
126
+ `pauseTask`.
127
+ - `TaskFile` saiu daqui. Importe de `@opentask/taskin-file-system-provider`.
128
+ - `CreateTaskResult` não tem mais `filePath`. O provider de arquivos devolve
129
+ `CreateTaskFileResult`, que o mantém.
130
+
131
+ **`@opentask/taskin-types`**
132
+
133
+ - `TaskStatus` e `TASK_STATUSES` ganharam `'paused'`. Consumidores exaustivos
134
+ (`Record<TaskStatus, T>`, `switch` sem `default`) precisam tratar o caso novo.
135
+
136
+ **`@opentask/taskin-design-vue`**
137
+
138
+ - `TaskStatus` ganhou `'in-review'` e `'canceled'` para alinhar com o domínio,
139
+ com o mesmo efeito sobre consumidores exaustivos.
140
+
141
+ **`@opentask/taskin-task-provider-pinia`**
142
+
143
+ - O store guarda `Task` em vez de `TaskFile`: `tasks`, `findTask`, `getAllTasks` e
144
+ `updateTask` não expõem mais `content` nem `filePath`. Use `description`.
145
+
146
+ **`@opentask/taskin-task-server-ws`**
147
+
148
+ - `TaskWebSocketServer` e `TaskServerConfig` ganharam parâmetro de tipo (com
149
+ default). `MockTaskProvider` recebe `Task[]` em vez de `TaskFile[]`.
150
+
151
+ **`@opentask/taskin-task-server-mcp`**
152
+
153
+ - `MockMCPTaskManager.getAllTasks()` e `getTask()` devolvem `Task`, sem `filePath`
154
+ nem `content`.
155
+
156
+ ### Minor Changes
157
+
158
+ - 2240253: Faz o `provider.type` do `.taskin.json` valer, e move a porta `IUserRegistry`
159
+ para o pacote agnostico.
160
+
161
+ `provider.type` era lido **apenas** pelo `init`. Todos os comandos instanciavam
162
+ `new FileSystemTaskProvider(...)` e `new UserRegistry(...)` direto, em treze
163
+ lugares, entao escolher outro provider na configuracao nao tinha efeito nenhum —
164
+ foi o que deixou a task-002 (Redmine) parada mesmo com a spec pronta.
165
+
166
+ ## Factory
167
+
168
+ `resolveTaskProvider()` (em `packages/cli/src/lib/provider-factory/`) e agora o
169
+ unico lugar que nomeia um provider concreto, e devolve o par provider + user
170
+ registry — os dois eram construidos juntos em cada comando, e drifitaram: `list`
171
+ apontava o registry para a raiz do projeto em vez de `.taskin/`, entao todo
172
+ assignee caia silenciosamente em `createTemporaryUser` e o registro real nunca
173
+ era lido.
174
+
175
+ O mapa `PROVIDER_BUILDERS` e injetavel, o que da o seam para testar que
176
+ `provider.type` e respeitado sem precisar de um provider real.
177
+
178
+ `init` e a unica excecao, documentada no codigo: ele roda antes de existir
179
+ `.taskin.json` e e ele quem escreve o `provider.type`, entao usar a factory ali
180
+ seria circular.
181
+
182
+ ## Porta `IUserRegistry`
183
+
184
+ A interface era declarada dentro do `file-system-task-provider`, entao qualquer
185
+ registry de outro provider (GitHub, Redmine) teria que importar do provider de
186
+ arquivos para implementa-la — mesmo defeito de fronteira que a task-031 corrigiu
187
+ para o `TaskFile`, na direcao inversa. Agora mora no `task-manager`, ao lado do
188
+ `ITaskProvider`, e o contract test viaja com ela: qualquer implementacao roda
189
+ `runUserRegistryContractTests` via o subpath novo
190
+ `@opentask/taskin-task-manager/testing` (subpath separado para que `vitest` nao
191
+ entre no grafo de import de runtime).
192
+
193
+ A implementacao file-backed (`UserRegistry`, `users-file-location`,
194
+ `.taskin/.taskin-users.json`) fica onde esta: e backing store de uma
195
+ implementacao, nao configuracao de projeto.
196
+
197
+ ## Criacao de task sai do comando
198
+
199
+ `new.ts` tinha uma copia inteira da criacao de task — numeracao `max(ids)+1`,
200
+ slug do titulo, template markdown — que e semantica de sistema de arquivos. Num
201
+ provider remoto o id vem do proprio store (o numero da issue). Agora o comando
202
+ chama `provider.createTask()` e so consome o resultado; o `generateTaskMarkdown`
203
+ duplicado saiu, ficando o do provider, que e i18n-aware.
204
+
205
+ ## Credencial via ambiente
206
+
207
+ A config do provider passa por expansao de `${VAR}` (reusando o resolver que a
208
+ config de notificacoes ja usava), porque todo provider remoto precisa de
209
+ credencial e o `.taskin.json` e versionado.
210
+
211
+ ## Breaking changes
212
+
213
+ **`taskin`**
214
+
215
+ - `createTaskin()` e `getTaskin()` agora devolvem `Promise<Taskin>`. Qual
216
+ provider usar e pergunta de runtime, e o user registry precisa ser carregado
217
+ antes de qualquer leitura de task.
218
+ - `provider.type` diferente de `fs` agora **falha com mensagem explicita** em vez
219
+ de silenciosamente usar o provider de arquivos. Quem tinha type errado na
220
+ configuracao passa a ver o erro.
221
+ - `Taskin` recebe `ITaskProvider`/`ITaskManager` em vez de
222
+ `FileSystemTaskProvider`/`TaskManager` concretos.
223
+
224
+ **`@opentask/taskin-file-system-provider`**
225
+
226
+ - `IUserRegistry` nao e mais exportada daqui. Importe de
227
+ `@opentask/taskin-task-manager`.
228
+ - `runUserRegistryContractTests` mudou de lugar: importe de
229
+ `@opentask/taskin-task-manager/testing`.
230
+ - `FileSystemTaskProvider` e `FileSystemMetricsAdapter` recebem `IUserRegistry`
231
+ em vez da classe `UserRegistry`.
232
+
233
+ ### Patch Changes
234
+
235
+ - Updated dependencies [b4b259e]
236
+ - Updated dependencies [30b3e4a]
237
+ - Updated dependencies [2f6d046]
238
+ - @opentask/taskin-types@2.0.0
239
+
240
+ ## 2.0.1
241
+
242
+ ### Patch Changes
243
+
244
+ - Remove unnecessary install scripts that caused pnpm build script warnings
245
+
246
+ Removed `install` scripts from all packages that only printed echo messages. These scripts were unnecessary since packages are already pre-built and included in the published bundle. This eliminates the "Ignored build scripts" warning when installing taskin in external projects.
247
+
248
+ - Updated dependencies
249
+ - @opentask/taskin-types@1.1.1
250
+
3
251
  ## 1.1.0
4
252
 
5
253
  ### Minor 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"}