@opentask/taskin-task-manager 3.1.0 → 3.2.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 +62 -0
- package/dist/completion-blockers.test.d.ts +1 -0
- package/dist/completion-blockers.test.js +57 -0
- package/dist/completion-blockers.test.js.map +1 -0
- package/dist/filter-tasks/filter-criteria.d.ts +181 -0
- package/dist/filter-tasks/filter-criteria.js +115 -0
- package/dist/filter-tasks/filter-criteria.js.map +1 -0
- package/dist/filter-tasks/filter-criteria.test.d.ts +1 -0
- package/dist/filter-tasks/filter-criteria.test.js +86 -0
- package/dist/filter-tasks/filter-criteria.test.js.map +1 -0
- package/dist/filter-tasks/filter-tasks.js +17 -2
- package/dist/filter-tasks/filter-tasks.js.map +1 -1
- package/dist/filter-tasks/filter-tasks.test.js +38 -0
- package/dist/filter-tasks/filter-tasks.test.js.map +1 -1
- package/dist/filter-tasks/filter-tasks.types.d.ts +5 -23
- package/dist/filter-tasks/index.d.ts +3 -1
- package/dist/filter-tasks/index.js +1 -0
- package/dist/filter-tasks/index.js.map +1 -1
- package/dist/group-registry.contract.d.ts +18 -0
- package/dist/group-registry.contract.js +73 -0
- package/dist/group-registry.contract.js.map +1 -0
- package/dist/group-registry.types.d.ts +44 -0
- package/dist/group-registry.types.js +2 -0
- package/dist/group-registry.types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/numerar-prioridade/index.d.ts +1 -0
- package/dist/numerar-prioridade/index.js +2 -0
- package/dist/numerar-prioridade/index.js.map +1 -0
- package/dist/numerar-prioridade/numerar-prioridade.d.ts +36 -0
- package/dist/numerar-prioridade/numerar-prioridade.js +75 -0
- package/dist/numerar-prioridade/numerar-prioridade.js.map +1 -0
- package/dist/numerar-prioridade/numerar-prioridade.test.d.ts +1 -0
- package/dist/numerar-prioridade/numerar-prioridade.test.js +55 -0
- package/dist/numerar-prioridade/numerar-prioridade.test.js.map +1 -0
- package/dist/ordenar-tarefas/index.d.ts +1 -0
- package/dist/ordenar-tarefas/index.js +2 -0
- package/dist/ordenar-tarefas/index.js.map +1 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.d.ts +70 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.js +79 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.js.map +1 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.test.d.ts +1 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.test.js +70 -0
- package/dist/ordenar-tarefas/ordenar-tarefas.test.js.map +1 -0
- package/dist/task-manager.d.ts +26 -1
- package/dist/task-manager.js +48 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/task-manager.types.d.ts +82 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +9 -0
- package/dist/testing.js.map +1 -0
- package/dist/user-registry.contract.js +6 -6
- package/dist/user-registry.contract.js.map +1 -1
- package/package.json +7 -6
- package/src/completion-blockers.test.ts +77 -0
- package/src/filter-tasks/filter-criteria.test.ts +113 -0
- package/src/filter-tasks/filter-criteria.ts +175 -0
- package/src/filter-tasks/filter-tasks.test.ts +45 -0
- package/src/filter-tasks/filter-tasks.ts +17 -2
- package/src/filter-tasks/filter-tasks.types.ts +5 -23
- package/src/filter-tasks/index.ts +15 -1
- package/src/group-registry.contract.ts +99 -0
- package/src/group-registry.types.ts +47 -0
- package/src/index.ts +3 -0
- package/src/numerar-prioridade/index.ts +1 -0
- package/src/numerar-prioridade/numerar-prioridade.test.ts +69 -0
- package/src/numerar-prioridade/numerar-prioridade.ts +80 -0
- package/src/ordenar-tarefas/index.ts +1 -0
- package/src/ordenar-tarefas/ordenar-tarefas.test.ts +92 -0
- package/src/ordenar-tarefas/ordenar-tarefas.ts +135 -0
- package/src/task-manager.ts +59 -0
- package/src/task-manager.types.ts +83 -0
- package/src/testing.ts +8 -0
- package/src/user-registry.contract.ts +6 -6
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { parseGroupId, parseTaskId } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { agruparTarefas, ordenarTarefas } from './ordenar-tarefas.js';
|
|
4
|
+
const t = (id, extra = {}) => ({ id: parseTaskId(id), title: `Tarefa ${id}`, status: 'pending', type: 'feat', ...extra });
|
|
5
|
+
const ids = (ts) => ts.map((x) => String(x.id));
|
|
6
|
+
/**
|
|
7
|
+
* A ordenacao que o dashboard ja usava, agora onde as tres superficies alcancam.
|
|
8
|
+
*
|
|
9
|
+
* Ela vivia em `use-prioritization.ts`, dentro do pacote **Vue** — inalcancavel
|
|
10
|
+
* para a CLI e para o servidor MCP. O `taskin list` devolvia as tarefas na
|
|
11
|
+
* ordem em que o provider as achava, que na pratica e por id: a coluna de
|
|
12
|
+
* prioridade subia e descia sem padrao, e quem lia precisava reordenar de
|
|
13
|
+
* cabeca. Foi assim que um agente autonomo escolheu uma tarefa de prioridade 30
|
|
14
|
+
* tendo uma de 255 na mesma lista.
|
|
15
|
+
*/
|
|
16
|
+
describe('ordenarTarefas', () => {
|
|
17
|
+
it('manual ordena por prioridade, com quem nao tem por ultimo', () => {
|
|
18
|
+
const tarefas = [t('001', { order: 30 }), t('002'), t('003', { order: 10 })];
|
|
19
|
+
expect(ids(ordenarTarefas(tarefas, 'manual'))).toEqual(['003', '001', '002']);
|
|
20
|
+
});
|
|
21
|
+
it('manual e estavel entre empatados', () => {
|
|
22
|
+
const tarefas = [t('001', { order: 10 }), t('002', { order: 10 }), t('003', { order: 10 })];
|
|
23
|
+
expect(ids(ordenarTarefas(tarefas, 'manual'))).toEqual(['001', '002', '003']);
|
|
24
|
+
});
|
|
25
|
+
it('difficulty ordena do mais facil ao mais dificil, e o inverso', () => {
|
|
26
|
+
const tarefas = [t('001', { difficulty: 5 }), t('002', { difficulty: 1 }), t('003', { difficulty: 3 })];
|
|
27
|
+
expect(ids(ordenarTarefas(tarefas, 'diff-asc'))).toEqual(['002', '003', '001']);
|
|
28
|
+
expect(ids(ordenarTarefas(tarefas, 'diff-desc'))).toEqual(['001', '003', '002']);
|
|
29
|
+
});
|
|
30
|
+
it('nao altera a lista recebida', () => {
|
|
31
|
+
const tarefas = [t('001', { order: 30 }), t('002', { order: 10 })];
|
|
32
|
+
ordenarTarefas(tarefas, 'manual');
|
|
33
|
+
expect(ids(tarefas)).toEqual(['001', '002']);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Agrupar e separado de ordenar, e de proposito.
|
|
38
|
+
*
|
|
39
|
+
* No dashboard as duas coisas vinham juntas porque a arvore era so para
|
|
40
|
+
* desenhar caixas. Na saida de maquina quem consome decide — e separar deixa a
|
|
41
|
+
* CLI ordenar sem agrupar quando ninguem pediu grupo.
|
|
42
|
+
*/
|
|
43
|
+
describe('agruparTarefas', () => {
|
|
44
|
+
const g1 = parseGroupId('g-1');
|
|
45
|
+
it('junta membros do mesmo grupo, esteja onde estiver na ordem', () => {
|
|
46
|
+
const tarefas = [t('001', { groupId: g1 }), t('002'), t('003', { groupId: g1 })];
|
|
47
|
+
const nos = agruparTarefas(tarefas, { 'g-1': 'Sprint' });
|
|
48
|
+
expect(nos).toHaveLength(2);
|
|
49
|
+
expect(nos[0]).toMatchObject({ kind: 'group', groupId: 'g-1', groupName: 'Sprint', hidden: 0 });
|
|
50
|
+
expect(nos[0]?.kind === 'group' && ids(nos[0].tasks)).toEqual(['001', '003']);
|
|
51
|
+
});
|
|
52
|
+
/*
|
|
53
|
+
* O grupo parcial, decidido: mostra os membros que casam, e diz quantos o
|
|
54
|
+
* filtro escondeu. Nunca alarga o filtro para trazer o grupo inteiro.
|
|
55
|
+
*/
|
|
56
|
+
it('diz quantos membros o filtro deixou de fora', () => {
|
|
57
|
+
const tarefas = [t('001', { groupId: g1 })];
|
|
58
|
+
const nos = agruparTarefas(tarefas, { 'g-1': 'Sprint' }, { 'g-1': 7 });
|
|
59
|
+
expect(nos[0]).toMatchObject({ kind: 'group', hidden: 6 });
|
|
60
|
+
});
|
|
61
|
+
it('tarefa sem grupo fica solta, na ordem em que veio', () => {
|
|
62
|
+
const nos = agruparTarefas([t('001'), t('002')], {});
|
|
63
|
+
expect(nos.map((n) => n.kind)).toEqual(['task', 'task']);
|
|
64
|
+
});
|
|
65
|
+
it('grupo sem nome no registro ainda aparece, pelo id', () => {
|
|
66
|
+
const nos = agruparTarefas([t('001', { groupId: g1 })], {});
|
|
67
|
+
expect(nos[0]).toMatchObject({ kind: 'group', groupId: 'g-1', groupName: undefined });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=ordenar-tarefas.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ordenar-tarefas.test.js","sourceRoot":"","sources":["../../src/ordenar-tarefas/ordenar-tarefas.test.ts"],"names":[],"mappings":"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"}
|
package/dist/task-manager.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Task, TaskId } from '@opentask/taskin-types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IGroupRegistry } from './group-registry.types.js';
|
|
3
|
+
import type { CreateTaskOptions, CreateTaskResult, CriterioEmAberto, ITaskManager, ITaskProvider, LintResult } from './task-manager.types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Orchestrates task state transitions on top of any {@link ITaskProvider}.
|
|
5
6
|
*
|
|
@@ -27,6 +28,30 @@ export declare class TaskManager<TTask extends Task = Task> implements ITaskMana
|
|
|
27
28
|
* the MCP server — answer "what work exists?".
|
|
28
29
|
*/
|
|
29
30
|
getAllTasks(): Promise<TTask[]>;
|
|
31
|
+
/** Repassa o registro do provider, quando ele tem um. */
|
|
32
|
+
get groupRegistry(): IGroupRegistry | undefined;
|
|
33
|
+
prioritizeAll(options?: {
|
|
34
|
+
dryRun?: boolean;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
total: number;
|
|
37
|
+
withoutPriority: number;
|
|
38
|
+
changed: number;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Conclui a tarefa e **relata** o que ficou em aberto.
|
|
42
|
+
*
|
|
43
|
+
* Avisa, e nao recusa. Fechar uma tarefa e um gesto que acontece uma vez,
|
|
44
|
+
* muitas vezes com pressa; recusar ali torna o comando fragil e ensina a
|
|
45
|
+
* contornar. O portao duro vive no `lint`, que roda em CI e quebra o build —
|
|
46
|
+
* aqui o papel e dizer, no momento em que a pessoa ainda esta olhando, o que
|
|
47
|
+
* ficou para tras.
|
|
48
|
+
*
|
|
49
|
+
* Um provider sem a capacidade conclui sem portao nenhum.
|
|
50
|
+
*/
|
|
51
|
+
finishTaskComRelato(taskId: TaskId): Promise<{
|
|
52
|
+
task: TTask;
|
|
53
|
+
blockers: CriterioEmAberto[];
|
|
54
|
+
}>;
|
|
30
55
|
finishTask(taskId: TaskId): Promise<TTask>;
|
|
31
56
|
reviewTask(taskId: TaskId): Promise<TTask>;
|
|
32
57
|
createTask(options: CreateTaskOptions): Promise<CreateTaskResult<TTask>>;
|
package/dist/task-manager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numerarPrioridade } from './numerar-prioridade/index.js';
|
|
1
2
|
/**
|
|
2
3
|
* Orchestrates task state transitions on top of any {@link ITaskProvider}.
|
|
3
4
|
*
|
|
@@ -56,6 +57,53 @@ export class TaskManager {
|
|
|
56
57
|
async getAllTasks() {
|
|
57
58
|
return await this.taskProvider.getAllTasks();
|
|
58
59
|
}
|
|
60
|
+
/** Repassa o registro do provider, quando ele tem um. */
|
|
61
|
+
get groupRegistry() {
|
|
62
|
+
return this.taskProvider.groupRegistry;
|
|
63
|
+
}
|
|
64
|
+
async prioritizeAll(options = {}) {
|
|
65
|
+
const tarefas = await this.taskProvider.getAllTasks();
|
|
66
|
+
/*
|
|
67
|
+
* Quem ja tem numero define a ordem; quem nao tem entra depois, na sequencia
|
|
68
|
+
* em que o provider devolveu.
|
|
69
|
+
*/
|
|
70
|
+
const ordenadas = [...tarefas].sort((a, b) => {
|
|
71
|
+
if (a.order === undefined && b.order === undefined)
|
|
72
|
+
return 0;
|
|
73
|
+
if (a.order === undefined)
|
|
74
|
+
return 1;
|
|
75
|
+
if (b.order === undefined)
|
|
76
|
+
return -1;
|
|
77
|
+
return a.order - b.order;
|
|
78
|
+
});
|
|
79
|
+
const mudancas = numerarPrioridade(ordenadas);
|
|
80
|
+
const semNumero = tarefas.filter((t) => t.order === undefined).length;
|
|
81
|
+
if (!options.dryRun) {
|
|
82
|
+
for (const tarefa of mudancas) {
|
|
83
|
+
await this.taskProvider.updateTask(tarefa);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { total: tarefas.length, withoutPriority: semNumero, changed: mudancas.length };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Conclui a tarefa e **relata** o que ficou em aberto.
|
|
90
|
+
*
|
|
91
|
+
* Avisa, e nao recusa. Fechar uma tarefa e um gesto que acontece uma vez,
|
|
92
|
+
* muitas vezes com pressa; recusar ali torna o comando fragil e ensina a
|
|
93
|
+
* contornar. O portao duro vive no `lint`, que roda em CI e quebra o build —
|
|
94
|
+
* aqui o papel e dizer, no momento em que a pessoa ainda esta olhando, o que
|
|
95
|
+
* ficou para tras.
|
|
96
|
+
*
|
|
97
|
+
* Um provider sem a capacidade conclui sem portao nenhum.
|
|
98
|
+
*/
|
|
99
|
+
async finishTaskComRelato(taskId) {
|
|
100
|
+
const task = await this.taskProvider.findTask(taskId);
|
|
101
|
+
if (!task)
|
|
102
|
+
throw new Error(`Task with ID '${taskId}' not found.`);
|
|
103
|
+
const blockers = (await this.taskProvider.getCompletionBlockers?.(task)) ?? [];
|
|
104
|
+
const atualizada = await this.finishTask(taskId);
|
|
105
|
+
return { task: atualizada, blockers };
|
|
106
|
+
}
|
|
59
107
|
async finishTask(taskId) {
|
|
60
108
|
const task = await this.taskProvider.findTask(taskId);
|
|
61
109
|
if (!task) {
|
package/dist/task-manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Task, TaskId, TaskType } from '@opentask/taskin-types';
|
|
2
|
+
import type { IGroupRegistry } from './group-registry.types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Options for creating a new task
|
|
4
5
|
* @public
|
|
@@ -36,6 +37,13 @@ export type ValidationSeverity = 'error' | 'warning' | 'info';
|
|
|
36
37
|
* A validation error or warning found during linting
|
|
37
38
|
* @public
|
|
38
39
|
*/
|
|
40
|
+
/** Um criterio que ainda impede a conclusao. */
|
|
41
|
+
export interface CriterioEmAberto {
|
|
42
|
+
/** O texto do item, como escrito. */
|
|
43
|
+
readonly texto: string;
|
|
44
|
+
/** Onde ele esta, quando a fonte sabe dizer. */
|
|
45
|
+
readonly linha?: number;
|
|
46
|
+
}
|
|
39
47
|
export interface ValidationIssue {
|
|
40
48
|
/** The file or task that has the issue */
|
|
41
49
|
file: string;
|
|
@@ -113,6 +121,19 @@ export interface ITaskProvider<TTask extends Task = Task> {
|
|
|
113
121
|
* @returns The lint result with any validation issues found
|
|
114
122
|
*/
|
|
115
123
|
lint: (fix?: boolean) => Promise<LintResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Os criterios de conclusao que ainda bloqueiam esta tarefa, se a fonte tiver
|
|
126
|
+
* o conceito.
|
|
127
|
+
*
|
|
128
|
+
* **Opcional de proposito.** Checklist e uma forma do provider de arquivos: o
|
|
129
|
+
* Jira tem subtarefas, o GitHub tem itens de lista na descricao. Um provider
|
|
130
|
+
* sem nada equivalente simplesmente nao implementa, e o `finishTask` conclui
|
|
131
|
+
* sem portao — em vez de chamar algo que falha.
|
|
132
|
+
*
|
|
133
|
+
* O conceito e de dominio ("o que falta para isto estar pronto?"); a
|
|
134
|
+
* representacao e de provider.
|
|
135
|
+
*/
|
|
136
|
+
getCompletionBlockers?: (task: TTask) => Promise<CriterioEmAberto[]>;
|
|
116
137
|
}
|
|
117
138
|
/**
|
|
118
139
|
* Interface for task management operations.
|
|
@@ -146,6 +167,54 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
146
167
|
* @returns The tasks, in whatever order the provider returns them
|
|
147
168
|
*/
|
|
148
169
|
getAllTasks: () => Promise<TTask[]>;
|
|
170
|
+
/**
|
|
171
|
+
* Da a cada tarefa um numero de prioridade, de uma vez.
|
|
172
|
+
*
|
|
173
|
+
* E operacao de dominio, e nao um `updateTask` generico, por dois motivos.
|
|
174
|
+
*
|
|
175
|
+
* O primeiro e de desenho: a regra da numeracao — preservar a ordem, manter o
|
|
176
|
+
* numero de quem ja tem, abrir espaco quando faltar — passa a viver num lugar
|
|
177
|
+
* so, em vez de ser reescrita pela CLI e pelo servidor MCP.
|
|
178
|
+
*
|
|
179
|
+
* O segundo e de tipo: um metodo que **consome** `TTask` torna a interface
|
|
180
|
+
* contravariante nele, e um `ITaskManager<TarefaEspecifica>` deixa de poder
|
|
181
|
+
* ser usado onde se espera `ITaskManager<Task>`. Uma operacao que so devolve
|
|
182
|
+
* numeros nao tem esse problema.
|
|
183
|
+
*
|
|
184
|
+
* Existe porque um projeto **meio numerado** cobra caro: mover uma tarefa do
|
|
185
|
+
* meio da regiao sem numero reescreve todos os antecessores — 124 arquivos num
|
|
186
|
+
* projeto de 500, medido. Depois desta operacao, todo movimento custa um.
|
|
187
|
+
*
|
|
188
|
+
* @param options - `dryRun` apenas conta, sem gravar
|
|
189
|
+
* @returns Quantas existem, quantas estavam sem numero, e quantas mudaram
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* Os grupos, quando a fonte tem o conceito — `undefined` quando nao tem.
|
|
193
|
+
*
|
|
194
|
+
* Opcional de proposito: o GitHub mapeia para milestone, o Redmine para
|
|
195
|
+
* categoria, e uma fonte sem nada equivalente simplesmente nao oferece. Quem
|
|
196
|
+
* consome descobre pela ausencia, em vez de chamar algo que falha.
|
|
197
|
+
*/
|
|
198
|
+
readonly groupRegistry?: IGroupRegistry;
|
|
199
|
+
/**
|
|
200
|
+
* Conclui a tarefa e relata os criterios que ficaram em aberto.
|
|
201
|
+
*
|
|
202
|
+
* Avisa, e nao recusa: fechar e um gesto unico, muitas vezes com pressa, e
|
|
203
|
+
* recusar ali torna o comando fragil. O portao duro vive no `lint`.
|
|
204
|
+
*
|
|
205
|
+
* O relato vem vazio quando o provider nao tem o conceito de checklist.
|
|
206
|
+
*/
|
|
207
|
+
finishTaskComRelato: (taskId: TaskId) => Promise<{
|
|
208
|
+
task: TTask;
|
|
209
|
+
blockers: CriterioEmAberto[];
|
|
210
|
+
}>;
|
|
211
|
+
prioritizeAll: (options?: {
|
|
212
|
+
dryRun?: boolean;
|
|
213
|
+
}) => Promise<{
|
|
214
|
+
total: number;
|
|
215
|
+
withoutPriority: number;
|
|
216
|
+
changed: number;
|
|
217
|
+
}>;
|
|
149
218
|
/**
|
|
150
219
|
* Mark a task as ready for review.
|
|
151
220
|
* Transitions the task from 'in-progress' to 'in-review' status.
|
|
@@ -185,4 +254,17 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
185
254
|
* @returns The lint result with any validation issues found
|
|
186
255
|
*/
|
|
187
256
|
lint: (fix?: boolean) => Promise<LintResult>;
|
|
257
|
+
/**
|
|
258
|
+
* Os criterios de conclusao que ainda bloqueiam esta tarefa, se a fonte tiver
|
|
259
|
+
* o conceito.
|
|
260
|
+
*
|
|
261
|
+
* **Opcional de proposito.** Checklist e uma forma do provider de arquivos: o
|
|
262
|
+
* Jira tem subtarefas, o GitHub tem itens de lista na descricao. Um provider
|
|
263
|
+
* sem nada equivalente simplesmente nao implementa, e o `finishTask` conclui
|
|
264
|
+
* sem portao — em vez de chamar algo que falha.
|
|
265
|
+
*
|
|
266
|
+
* O conceito e de dominio ("o que falta para isto estar pronto?"); a
|
|
267
|
+
* representacao e de provider.
|
|
268
|
+
*/
|
|
269
|
+
getCompletionBlockers?: (task: TTask) => Promise<CriterioEmAberto[]>;
|
|
188
270
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contratos que as implementacoes provam contra si mesmas.
|
|
3
|
+
*
|
|
4
|
+
* Exportado por `@opentask/taskin-task-manager/testing` para o `vitest` nunca
|
|
5
|
+
* entrar no grafo de execucao do pacote.
|
|
6
|
+
*/
|
|
7
|
+
export * from './group-registry.contract.js';
|
|
8
|
+
export * from './user-registry.contract.js';
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contratos que as implementacoes provam contra si mesmas.
|
|
3
|
+
*
|
|
4
|
+
* Exportado por `@opentask/taskin-task-manager/testing` para o `vitest` nunca
|
|
5
|
+
* entrar no grafo de execucao do pacote.
|
|
6
|
+
*/
|
|
7
|
+
export * from './group-registry.contract.js';
|
|
8
|
+
export * from './user-registry.contract.js';
|
|
9
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC"}
|
|
@@ -35,7 +35,7 @@ export function runUserRegistryContractTests(createSubject) {
|
|
|
35
35
|
const user = registry.resolveUser('joao.silva');
|
|
36
36
|
expect(user).toBeDefined();
|
|
37
37
|
expect(user.id).toBe('joao.silva');
|
|
38
|
-
expect(user.
|
|
38
|
+
expect(user.avatarHash).toMatch(/^[a-f0-9]{32}$/);
|
|
39
39
|
});
|
|
40
40
|
it('should return user by name (case-insensitive)', async () => {
|
|
41
41
|
const registry = await createSubject();
|
|
@@ -47,7 +47,7 @@ export function runUserRegistryContractTests(createSubject) {
|
|
|
47
47
|
const user = registry.resolveUser('Maria Souza');
|
|
48
48
|
expect(user).toBeDefined();
|
|
49
49
|
expect(user.id).toBe('maria');
|
|
50
|
-
expect(user.
|
|
50
|
+
expect(user.avatarHash).toMatch(/^[a-f0-9]{32}$/);
|
|
51
51
|
});
|
|
52
52
|
it('should return undefined for unknown user', async () => {
|
|
53
53
|
const registry = await createSubject();
|
|
@@ -56,19 +56,19 @@ export function runUserRegistryContractTests(createSubject) {
|
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
describe('createTemporaryUser', () => {
|
|
59
|
-
it('should create user with
|
|
59
|
+
it('should create user with an avatar identity hash', async () => {
|
|
60
60
|
const registry = await createSubject();
|
|
61
61
|
const user = registry.createTemporaryUser('Carlos');
|
|
62
62
|
expect(user.id).toBe('carlos');
|
|
63
63
|
expect(user.name).toBe('Carlos');
|
|
64
64
|
expect(user.email).toBe('carlos@example.com');
|
|
65
|
-
expect(user.
|
|
65
|
+
expect(user.avatarHash).toMatch(/^[a-f0-9]{32}$/);
|
|
66
66
|
});
|
|
67
|
-
it('should create different
|
|
67
|
+
it('should create different avatar hashes for different inputs', async () => {
|
|
68
68
|
const registry = await createSubject();
|
|
69
69
|
const user1 = registry.createTemporaryUser('Alice');
|
|
70
70
|
const user2 = registry.createTemporaryUser('Bob');
|
|
71
|
-
expect(user1.
|
|
71
|
+
expect(user1.avatarHash).not.toBe(user2.avatarHash);
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
describe('ensureCurrentUser', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-registry.contract.js","sourceRoot":"","sources":["../src/user-registry.contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAG9C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,aAA2C;IACtF,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;YAClC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;gBACtD,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;gBAClE,MAAM,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC9B,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAChD,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;gBACxD,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;gBACvC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACtD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;YAC3B,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;gBACpD,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;gBACvC,MAAM,QAAQ,CAAC,QAAQ,CAAC;oBACtB,EAAE,EAAE,YAAY;oBAChB,IAAI,EAAE,YAAY;oBAClB,KAAK,EAAE,kBAAkB;iBAC1B,CAAC,CAAC;gBAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;gBAChD,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;gBAC3B,MAAM,CAAC,IAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBACpC,MAAM,CAAC,IAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"user-registry.contract.js","sourceRoot":"","sources":["../src/user-registry.contract.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentask/taskin-task-manager",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Task lifecycle management for Taskin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"motivation": "To manage the lifecycle of tasks, including creation, status updates, and retrieval.",
|
|
@@ -33,14 +33,15 @@
|
|
|
33
33
|
"default": "./dist/index.js"
|
|
34
34
|
},
|
|
35
35
|
"./testing": {
|
|
36
|
-
"types": "./dist/
|
|
37
|
-
"import": "./dist/
|
|
38
|
-
"require": "./dist/
|
|
39
|
-
"default": "./dist/
|
|
36
|
+
"types": "./dist/testing.d.ts",
|
|
37
|
+
"import": "./dist/testing.js",
|
|
38
|
+
"require": "./dist/testing.js",
|
|
39
|
+
"default": "./dist/testing.js"
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
43
|
+
"zod": "^4.5.4",
|
|
44
|
+
"@opentask/taskin-types": "2.3.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"typescript": "^6.0.0"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseTaskId, type Task } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { TaskManager } from './task-manager.js';
|
|
4
|
+
import type { ITaskProvider } from './task-manager.types.js';
|
|
5
|
+
|
|
6
|
+
const tarefa = (extra: Partial<Task> = {}): Task =>
|
|
7
|
+
({ id: parseTaskId('001'), title: 'Uma tarefa', status: 'in-progress', type: 'feat', ...extra }) as Task;
|
|
8
|
+
|
|
9
|
+
function provider(overrides: Partial<ITaskProvider> = {}): ITaskProvider {
|
|
10
|
+
return {
|
|
11
|
+
initialize: vi.fn(async () => {}),
|
|
12
|
+
findTask: vi.fn(async () => tarefa()),
|
|
13
|
+
getAllTasks: vi.fn(async () => [tarefa()]),
|
|
14
|
+
updateTask: vi.fn(async () => {}),
|
|
15
|
+
createTask: vi.fn(),
|
|
16
|
+
lint: vi.fn(),
|
|
17
|
+
...overrides,
|
|
18
|
+
} as unknown as ITaskProvider;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* O aviso no `finish`, e por que ele **avisa** em vez de recusar.
|
|
23
|
+
*
|
|
24
|
+
* Fechar uma tarefa e um gesto que acontece uma vez, muitas vezes com pressa.
|
|
25
|
+
* Recusar ali torna o comando fragil e ensina a contornar. O portao duro vive no
|
|
26
|
+
* `lint` (task-075), que roda em CI e quebra o build; aqui o papel e outro —
|
|
27
|
+
* dizer, no momento em que a pessoa ainda esta olhando, o que ficou para tras.
|
|
28
|
+
*
|
|
29
|
+
* E uma **capacidade opcional** do provider: nem toda fonte tem checklist. Um
|
|
30
|
+
* provider sem ela conclui sem portao nenhum, em vez de receber uma chamada que
|
|
31
|
+
* falha.
|
|
32
|
+
*/
|
|
33
|
+
describe('finishTask e os criterios em aberto', () => {
|
|
34
|
+
it('conclui em silencio quando o provider nao tem a capacidade', async () => {
|
|
35
|
+
const manager = new TaskManager(provider());
|
|
36
|
+
|
|
37
|
+
const { blockers } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
38
|
+
|
|
39
|
+
expect(blockers).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('relata os itens em aberto que o provider aponta', async () => {
|
|
43
|
+
const manager = new TaskManager(
|
|
44
|
+
provider({
|
|
45
|
+
getCompletionBlockers: vi.fn(async () => [{ texto: 'Documentar', linha: 12 }]),
|
|
46
|
+
} as Partial<ITaskProvider>),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const { blockers } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
50
|
+
|
|
51
|
+
expect(blockers).toHaveLength(1);
|
|
52
|
+
expect(blockers[0]?.texto).toBe('Documentar');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('conclui mesmo assim: avisa, e nao recusa', async () => {
|
|
56
|
+
const updateTask = vi.fn(async () => {});
|
|
57
|
+
const manager = new TaskManager(
|
|
58
|
+
provider({
|
|
59
|
+
updateTask,
|
|
60
|
+
getCompletionBlockers: vi.fn(async () => [{ texto: 'Documentar', linha: 12 }]),
|
|
61
|
+
} as Partial<ITaskProvider>),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const { task } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
65
|
+
|
|
66
|
+
expect(task.status).toBe('done');
|
|
67
|
+
expect(updateTask).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('nao relata nada quando tudo esta feito ou adiado', async () => {
|
|
71
|
+
const manager = new TaskManager(
|
|
72
|
+
provider({ getCompletionBlockers: vi.fn(async () => []) } as Partial<ITaskProvider>),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect((await manager.finishTaskComRelato(parseTaskId('001'))).blockers).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
type CriterionSurface,
|
|
5
|
+
FILTER_CRITERIA_SURFACES,
|
|
6
|
+
FilterCriteriaSchema,
|
|
7
|
+
filterCriteriaCliOptions,
|
|
8
|
+
filterCriteriaJsonSchema,
|
|
9
|
+
parseFilterCriteria,
|
|
10
|
+
type TaskFilterCriteria,
|
|
11
|
+
} from './filter-criteria.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Um criterio sem superficie e **erro de compilacao**, e nao aviso em tempo de
|
|
15
|
+
* execucao. Esta linha e a prova: um mapa que cobre `TaskFilterCriteria` menos a
|
|
16
|
+
* chave `text` nao satisfaz o gate por tipo. O `@ts-expect-error` falha o
|
|
17
|
+
* `typecheck` se um dia a omissao passar a compilar — que e o defeito que a task
|
|
18
|
+
* fecha.
|
|
19
|
+
*/
|
|
20
|
+
const semText = { status: FILTER_CRITERIA_SURFACES.status };
|
|
21
|
+
// @ts-expect-error — falta a chave 'text': um criterio sem superficie nao compila.
|
|
22
|
+
const _gateIncompleto: Record<keyof TaskFilterCriteria, CriterionSurface> = semText;
|
|
23
|
+
void _gateIncompleto;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Uma definicao so para os criterios de filtro.
|
|
27
|
+
*
|
|
28
|
+
* A propriedade que vale nao afirma a forma do schema — afirma que um criterio
|
|
29
|
+
* definido num lugar so **aparece nas duas superficies** sem edicao manual. Por
|
|
30
|
+
* isso os geradores recebem um schema e superficies: o teste acrescenta um
|
|
31
|
+
* criterio ficticio e prova que ele emerge na CLI e no MCP, o que so e possivel
|
|
32
|
+
* se as superficies derivarem, em vez de repetir a lista a mao.
|
|
33
|
+
*/
|
|
34
|
+
describe('criterios de filtro derivam de uma definicao so', () => {
|
|
35
|
+
const schemaEstendido = FilterCriteriaSchema.extend({ fictional: z.string().optional() });
|
|
36
|
+
const superficiesEstendidas = {
|
|
37
|
+
...FILTER_CRITERIA_SURFACES,
|
|
38
|
+
fictional: { description: 'criterio ficticio para o teste', cli: { kind: 'flag' as const } },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
it('um criterio novo aparece no schema JSON do MCP sem editar o MCP', () => {
|
|
42
|
+
const json = filterCriteriaJsonSchema(schemaEstendido, superficiesEstendidas);
|
|
43
|
+
|
|
44
|
+
expect(json.properties).toHaveProperty('fictional');
|
|
45
|
+
expect((json.properties.fictional as { description?: string }).description).toBe('criterio ficticio para o teste');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('um criterio novo aparece nas opcoes da CLI sem editar a CLI', () => {
|
|
49
|
+
const opcoes = filterCriteriaCliOptions(schemaEstendido, superficiesEstendidas);
|
|
50
|
+
|
|
51
|
+
const ficticia = opcoes.find((o) => o.flags.includes('--fictional'));
|
|
52
|
+
expect(ficticia).toBeDefined();
|
|
53
|
+
expect(ficticia?.description).toBe('criterio ficticio para o teste');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('cada criterio do schema tem uma superficie — nenhum fica para tras', () => {
|
|
57
|
+
const doSchema = Object.keys(FilterCriteriaSchema.shape).sort();
|
|
58
|
+
const comSuperficie = Object.keys(FILTER_CRITERIA_SURFACES).sort();
|
|
59
|
+
|
|
60
|
+
expect(comSuperficie).toEqual(doSchema);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('o schema JSON derivado do MCP', () => {
|
|
65
|
+
it('descreve todo criterio, e usa enum onde o valor e fechado', () => {
|
|
66
|
+
const json = filterCriteriaJsonSchema();
|
|
67
|
+
|
|
68
|
+
expect(json.type).toBe('object');
|
|
69
|
+
expect(json.required).toEqual([]);
|
|
70
|
+
// status e um conjunto fechado: o schema JSON traz o enum, e nao string solta.
|
|
71
|
+
expect((json.properties.status as { enum?: string[] }).enum).toContain('pending');
|
|
72
|
+
expect((json.properties.open as { type?: string }).type).toBe('boolean');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('as opcoes derivadas da CLI', () => {
|
|
77
|
+
it('booleano nao leva valor; os demais levam <chave>', () => {
|
|
78
|
+
const opcoes = filterCriteriaCliOptions();
|
|
79
|
+
const porFlag = (frag: string) => opcoes.find((o) => o.flags.includes(frag));
|
|
80
|
+
|
|
81
|
+
expect(porFlag('--open')?.flags).toBe('--open');
|
|
82
|
+
expect(porFlag('--status')?.flags).toBe('-s, --status <status>');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('unifica a grafia: a flag do responsavel e --assignee, e nao --user', () => {
|
|
86
|
+
const opcoes = filterCriteriaCliOptions();
|
|
87
|
+
|
|
88
|
+
expect(opcoes.some((o) => o.flags.includes('--assignee'))).toBe(true);
|
|
89
|
+
expect(opcoes.some((o) => o.flags.includes('--user'))).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('o texto livre e argumento posicional, e nao vira flag', () => {
|
|
93
|
+
const opcoes = filterCriteriaCliOptions();
|
|
94
|
+
|
|
95
|
+
expect(opcoes.some((o) => o.flags.includes('--text'))).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('parseFilterCriteria', () => {
|
|
100
|
+
it('descarta chaves que nao sao criterio, como o --json', () => {
|
|
101
|
+
const criteria = parseFilterCriteria({ status: 'done', json: true, foo: 1 });
|
|
102
|
+
|
|
103
|
+
expect(criteria).toEqual({ status: 'done' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('falha barulhento num valor fora do conjunto, em vez de silenciar', () => {
|
|
107
|
+
expect(() => parseFilterCriteria({ status: 'inexistente' })).toThrow();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('objeto vazio e um criterio valido que nao restringe nada', () => {
|
|
111
|
+
expect(parseFilterCriteria({})).toEqual({});
|
|
112
|
+
});
|
|
113
|
+
});
|