@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.
- package/CHANGELOG.md +248 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/metrics.types.d.ts +64 -73
- package/dist/provider-stats.test.js.map +1 -1
- package/dist/task-manager.d.ts +23 -7
- package/dist/task-manager.js +31 -3
- package/dist/task-manager.js.map +1 -1
- package/dist/task-manager.mock.d.ts +3 -4
- package/dist/task-manager.mock.js +2 -3
- package/dist/task-manager.mock.js.map +1 -1
- package/dist/task-manager.test.js +58 -17
- package/dist/task-manager.test.js.map +1 -1
- package/dist/task-manager.types.d.ts +49 -37
- package/dist/user-registry.contract.d.ts +13 -0
- package/dist/user-registry.contract.js +130 -0
- package/dist/user-registry.contract.js.map +1 -0
- package/dist/user-registry.types.d.ts +51 -0
- package/dist/user-registry.types.js +2 -0
- package/dist/user-registry.types.js.map +1 -0
- package/package.json +17 -10
- package/src/index.ts +3 -3
- package/src/metrics.types.ts +2 -11
- package/src/provider-stats.test.ts +6 -3
- package/src/task-manager.mock.ts +4 -6
- package/src/task-manager.test.ts +83 -34
- package/src/task-manager.ts +44 -10
- package/src/task-manager.types.ts +56 -38
- package/src/user-registry.contract.ts +151 -0
- package/src/user-registry.types.ts +55 -0
- package/tsconfig.json +1 -0
- package/.turbo/turbo-build.log +0 -5
- package/.turbo/turbo-format.log +0 -18
- package/.turbo/turbo-install.log +0 -6
- package/.turbo/turbo-lint$colon$fix.log +0 -12
- package/.turbo/turbo-lint.log +0 -13
- package/.turbo/turbo-test.log +0 -15
- package/.turbo/turbo-typecheck.log +0 -4
- package/eslint.config.js +0 -8
- 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":"
|
|
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"}
|
package/dist/metrics.types.d.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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,
|
|
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"}
|
package/dist/task-manager.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
}
|
package/dist/task-manager.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
66
|
+
const updatedTask = this.withStatus(task, 'in-review');
|
|
39
67
|
await this.taskProvider.updateTask(updatedTask);
|
|
40
68
|
return updatedTask;
|
|
41
69
|
}
|
package/dist/task-manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,WAAW;IACF;IAApB,YAAoB,
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|