@opentask/taskin-task-manager 3.0.2 → 3.1.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 +57 -0
- package/dist/filter-tasks/filter-tasks.d.ts +26 -0
- package/dist/filter-tasks/filter-tasks.js +84 -0
- package/dist/filter-tasks/filter-tasks.js.map +1 -0
- package/dist/filter-tasks/filter-tasks.test.d.ts +1 -0
- package/dist/filter-tasks/filter-tasks.test.js +72 -0
- package/dist/filter-tasks/filter-tasks.test.js.map +1 -0
- package/dist/filter-tasks/filter-tasks.types.d.ts +45 -0
- package/dist/filter-tasks/filter-tasks.types.js +2 -0
- package/dist/filter-tasks/filter-tasks.types.js.map +1 -0
- package/dist/filter-tasks/index.d.ts +2 -0
- package/dist/filter-tasks/index.js +2 -0
- package/dist/filter-tasks/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/task-manager.d.ts +8 -0
- package/dist/task-manager.js +10 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/task-manager.types.d.ts +11 -0
- package/package.json +2 -2
- package/src/filter-tasks/filter-tasks.test.ts +84 -0
- package/src/filter-tasks/filter-tasks.ts +91 -0
- package/src/filter-tasks/filter-tasks.types.ts +44 -0
- package/src/filter-tasks/index.ts +2 -0
- package/src/index.ts +1 -0
- package/src/task-manager.ts +11 -0
- package/src/task-manager.types.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# @opentask/taskin-task-manager
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6bebe35: O taskin passa a entregar a lista de tarefas de forma que outra ferramenta
|
|
8
|
+
consuma — `taskin list --json` e `list_tasks` no servidor MCP.
|
|
9
|
+
|
|
10
|
+
## O que havia
|
|
11
|
+
|
|
12
|
+
O `list` só imprimia tabela colorida. O servidor MCP não tinha ferramenta de
|
|
13
|
+
listagem, e o recurso `taskin://tasks` **anunciava** a capacidade e respondia
|
|
14
|
+
com um espaço reservado:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{"message": "Task list would be here", "note": "Requires ITaskProvider integration"}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Pior que não oferecer: quem consome recebe algo com cara de dado.
|
|
21
|
+
|
|
22
|
+
## Uma seleção, não três
|
|
23
|
+
|
|
24
|
+
Havia duas implementações da mesma pergunta, já discordando — o comando `list`
|
|
25
|
+
casava o responsável por substring em nome ou id, e a classe `Taskin` casava
|
|
26
|
+
`userId` exato, além de projetar a task derrubando o `assignee`. A saída em
|
|
27
|
+
JSON e o MCP seriam a terceira e a quarta.
|
|
28
|
+
|
|
29
|
+
`filterTasks` e `summarizeTask` vivem no pacote agnóstico, e os três caminhos
|
|
30
|
+
perguntam ao mesmo lugar.
|
|
31
|
+
|
|
32
|
+
## Detalhes que importam para quem consome
|
|
33
|
+
|
|
34
|
+
`list --json` sai **sem cabeçalho, moldura ou aviso** — a saída inteira é JSON
|
|
35
|
+
válido, e lista vazia é `[]`. Não carrega `content` nem `description`: o
|
|
36
|
+
provider de arquivos guarda o markdown inteiro neles, e a listagem deste
|
|
37
|
+
repositório passaria de vinte mil linhas. O corpo se busca pelo id.
|
|
38
|
+
|
|
39
|
+
`getAllTasks` entrou no `ITaskManager`, delegando ao provider como `lint` já
|
|
40
|
+
fazia — era o que faltava para um consumidor que só tem o manager responder
|
|
41
|
+
"que trabalho existe?".
|
|
42
|
+
|
|
43
|
+
`ListTasksOptions.status` e `.type` passam a usar os tipos do domínio em vez de
|
|
44
|
+
`string`. Um valor fora do conjunto nunca casaria, e falhava em silêncio.
|
|
45
|
+
|
|
46
|
+
## Um defeito de transporte, corrigido junto
|
|
47
|
+
|
|
48
|
+
Exercitando o servidor por stdio, o SDK recusava a resposta com
|
|
49
|
+
`invalid_union: expected string, received array`. O invólucro fazia
|
|
50
|
+
`text: result.content`, embrulhando o arranjo de blocos dentro de um bloco cujo
|
|
51
|
+
`text` precisa ser string — então **`start_task` e `finish_task` nunca
|
|
52
|
+
funcionaram pelo transporte real**. Nenhum teste pegava porque todos chamavam
|
|
53
|
+
`callTool` direto, pulando o invólucro.
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- Updated dependencies [6bebe35]
|
|
58
|
+
- @opentask/taskin-types@2.2.0
|
|
59
|
+
|
|
3
60
|
## 3.0.2
|
|
4
61
|
|
|
5
62
|
### Patch Changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Task } from '@opentask/taskin-types';
|
|
2
|
+
import type { TaskFilterCriteria, TaskSummary } from './filter-tasks.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Seleciona tarefas por criterio, sem saber como elas serao exibidas.
|
|
5
|
+
*
|
|
6
|
+
* Vive aqui, no pacote agnostico, porque a mesma pergunta e feita de tres
|
|
7
|
+
* lugares: o comando `list`, a saida em JSON e o servidor MCP. Antes eram duas
|
|
8
|
+
* implementacoes que ja discordavam — o comando casava o responsavel por
|
|
9
|
+
* substring em nome ou id, e a classe `Taskin` casava `userId` exato.
|
|
10
|
+
*
|
|
11
|
+
* Nao ordena e nao muda o arranjo recebido: a ordem que entra e a que sai.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export declare function filterTasks(tasks: readonly Task[], criteria: TaskFilterCriteria): Task[];
|
|
16
|
+
/**
|
|
17
|
+
* O que identifica uma tarefa numa listagem, sem o que ela diz.
|
|
18
|
+
*
|
|
19
|
+
* O provider de arquivos carrega o markdown inteiro em `content` e
|
|
20
|
+
* `description`. Incluir isso faria a listagem deste repositorio passar de
|
|
21
|
+
* vinte mil linhas — e quem lista quer **escolher** uma tarefa, nao le-las
|
|
22
|
+
* todas. O corpo se busca depois, pelo id.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export declare function summarizeTask(task: Task): TaskSummary;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O que conta como "ainda em aberto".
|
|
3
|
+
*
|
|
4
|
+
* `paused` e `in-review` entram: a tarefa nao foi entregue nem descartada, so
|
|
5
|
+
* nao esta sendo tocada agora. Deixa-las de fora escondia trabalho em curso de
|
|
6
|
+
* quem filtrava por `--open`.
|
|
7
|
+
*/
|
|
8
|
+
const EM_ABERTO = ['pending', 'in-progress', 'paused', 'in-review', 'blocked'];
|
|
9
|
+
const ENCERRADOS = ['done', 'canceled'];
|
|
10
|
+
const contem = (valor, procurado) => valor !== undefined && valor.toLowerCase().includes(procurado);
|
|
11
|
+
/**
|
|
12
|
+
* Casa o responsavel por id ou nome, inteiro ou em parte.
|
|
13
|
+
*
|
|
14
|
+
* Tarefa sem responsavel nunca casa: filtrar por uma pessoa e perguntar "o que
|
|
15
|
+
* e dela", e "de ninguem" nao e resposta.
|
|
16
|
+
*/
|
|
17
|
+
function casaResponsavel(task, procurado) {
|
|
18
|
+
const alvo = procurado.toLowerCase();
|
|
19
|
+
return contem(task.assignee?.id, alvo) || contem(task.assignee?.name, alvo);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Seleciona tarefas por criterio, sem saber como elas serao exibidas.
|
|
23
|
+
*
|
|
24
|
+
* Vive aqui, no pacote agnostico, porque a mesma pergunta e feita de tres
|
|
25
|
+
* lugares: o comando `list`, a saida em JSON e o servidor MCP. Antes eram duas
|
|
26
|
+
* implementacoes que ja discordavam — o comando casava o responsavel por
|
|
27
|
+
* substring em nome ou id, e a classe `Taskin` casava `userId` exato.
|
|
28
|
+
*
|
|
29
|
+
* Nao ordena e nao muda o arranjo recebido: a ordem que entra e a que sai.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export function filterTasks(tasks, criteria) {
|
|
34
|
+
return tasks.filter((task) => {
|
|
35
|
+
if (criteria.status !== undefined) {
|
|
36
|
+
if (task.status !== criteria.status)
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
else if (criteria.open && !EM_ABERTO.includes(task.status)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
else if (criteria.closed && !ENCERRADOS.includes(task.status)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (criteria.type !== undefined && task.type !== criteria.type)
|
|
46
|
+
return false;
|
|
47
|
+
if (criteria.assignee !== undefined && !casaResponsavel(task, criteria.assignee))
|
|
48
|
+
return false;
|
|
49
|
+
if (criteria.text !== undefined) {
|
|
50
|
+
const procurado = criteria.text.toLowerCase();
|
|
51
|
+
const casa = task.id.toLowerCase().includes(procurado) ||
|
|
52
|
+
contem(task.title, procurado) ||
|
|
53
|
+
contem(task.status, procurado) ||
|
|
54
|
+
casaResponsavel(task, procurado);
|
|
55
|
+
if (!casa)
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* O que identifica uma tarefa numa listagem, sem o que ela diz.
|
|
63
|
+
*
|
|
64
|
+
* O provider de arquivos carrega o markdown inteiro em `content` e
|
|
65
|
+
* `description`. Incluir isso faria a listagem deste repositorio passar de
|
|
66
|
+
* vinte mil linhas — e quem lista quer **escolher** uma tarefa, nao le-las
|
|
67
|
+
* todas. O corpo se busca depois, pelo id.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
export function summarizeTask(task) {
|
|
72
|
+
return {
|
|
73
|
+
id: task.id,
|
|
74
|
+
title: task.title,
|
|
75
|
+
status: task.status,
|
|
76
|
+
type: task.type,
|
|
77
|
+
...(task.assignee && { assignee: { id: task.assignee.id, name: task.assignee.name } }),
|
|
78
|
+
...(task.order !== undefined && { priority: task.order }),
|
|
79
|
+
...(task.groupId && { groupId: task.groupId }),
|
|
80
|
+
...(task.groupName && { groupName: task.groupName }),
|
|
81
|
+
...(task.difficulty !== undefined && { difficulty: task.difficulty }),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=filter-tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-tasks.js","sourceRoot":"","sources":["../../src/filter-tasks/filter-tasks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseTaskId } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { filterTasks } from './filter-tasks.js';
|
|
4
|
+
function tarefa({ id, ...resto }) {
|
|
5
|
+
return {
|
|
6
|
+
createdAt: '2026-09-12T00:00:00.000Z',
|
|
7
|
+
status: 'pending',
|
|
8
|
+
title: `Tarefa ${id}`,
|
|
9
|
+
type: 'feat',
|
|
10
|
+
...resto,
|
|
11
|
+
id: parseTaskId(id),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const ANA = { id: 'ana-souza', name: 'Ana Souza', email: 'ana@example.com' };
|
|
15
|
+
const JOAO = { id: 'joao', name: 'João Silva', email: 'joao@example.com' };
|
|
16
|
+
/**
|
|
17
|
+
* A selecao de tarefas, isolada de como ela e exibida.
|
|
18
|
+
*
|
|
19
|
+
* Existiam duas implementacoes divergentes: o comando `list` casava assignee
|
|
20
|
+
* por substring em nome **ou** id, e o `Taskin.list` casava `userId` exato — e
|
|
21
|
+
* ainda projetava a task derrubando o proprio `assignee`. Duas respostas
|
|
22
|
+
* diferentes para a mesma pergunta, e o servidor MCP sem nenhuma.
|
|
23
|
+
*/
|
|
24
|
+
describe('filterTasks', () => {
|
|
25
|
+
const TAREFAS = [
|
|
26
|
+
tarefa({ id: '001', status: 'pending', type: 'feat', assignee: ANA, title: 'Criar login' }),
|
|
27
|
+
tarefa({ id: '002', status: 'done', type: 'fix', assignee: JOAO, title: 'Corrigir crash' }),
|
|
28
|
+
tarefa({ id: '003', status: 'in-progress', type: 'feat', assignee: ANA, title: 'Painel de metricas' }),
|
|
29
|
+
tarefa({ id: '004', status: 'blocked', type: 'chore', title: 'Sem responsavel' }),
|
|
30
|
+
tarefa({ id: '005', status: 'canceled', type: 'docs', assignee: JOAO, title: 'Guia antigo' }),
|
|
31
|
+
];
|
|
32
|
+
it('sem criterio, devolve tudo na ordem recebida', () => {
|
|
33
|
+
expect(filterTasks(TAREFAS, {}).map((t) => t.id)).toEqual(['001', '002', '003', '004', '005']);
|
|
34
|
+
});
|
|
35
|
+
it('filtra por status', () => {
|
|
36
|
+
expect(filterTasks(TAREFAS, { status: 'done' }).map((t) => t.id)).toEqual(['002']);
|
|
37
|
+
});
|
|
38
|
+
it('filtra por tipo', () => {
|
|
39
|
+
expect(filterTasks(TAREFAS, { type: 'feat' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
40
|
+
});
|
|
41
|
+
it('`open` traz o que ainda esta em aberto, e nao o que foi encerrado', () => {
|
|
42
|
+
expect(filterTasks(TAREFAS, { open: true }).map((t) => t.id)).toEqual(['001', '003', '004']);
|
|
43
|
+
});
|
|
44
|
+
it('`closed` traz done e canceled', () => {
|
|
45
|
+
expect(filterTasks(TAREFAS, { closed: true }).map((t) => t.id)).toEqual(['002', '005']);
|
|
46
|
+
});
|
|
47
|
+
it('casa o assignee pelo id, pelo nome e por parte dele', () => {
|
|
48
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana-souza' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
49
|
+
expect(filterTasks(TAREFAS, { assignee: 'Ana Souza' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
50
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
51
|
+
});
|
|
52
|
+
it('nao devolve tarefa sem responsavel quando se filtra por um', () => {
|
|
53
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana' }).map((t) => t.id)).not.toContain('004');
|
|
54
|
+
});
|
|
55
|
+
it('o texto livre procura em id, titulo, status e responsavel', () => {
|
|
56
|
+
expect(filterTasks(TAREFAS, { text: 'crash' }).map((t) => t.id)).toEqual(['002']);
|
|
57
|
+
expect(filterTasks(TAREFAS, { text: '003' }).map((t) => t.id)).toEqual(['003']);
|
|
58
|
+
expect(filterTasks(TAREFAS, { text: 'blocked' }).map((t) => t.id)).toEqual(['004']);
|
|
59
|
+
expect(filterTasks(TAREFAS, { text: 'joão' }).map((t) => t.id)).toEqual(['002', '005']);
|
|
60
|
+
});
|
|
61
|
+
it('combina criterios, exigindo todos', () => {
|
|
62
|
+
expect(filterTasks(TAREFAS, { type: 'feat', assignee: 'ana', status: 'pending' }).map((t) => t.id)).toEqual([
|
|
63
|
+
'001',
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
it('nao altera o arranjo recebido', () => {
|
|
67
|
+
const original = [...TAREFAS];
|
|
68
|
+
filterTasks(TAREFAS, { status: 'done' });
|
|
69
|
+
expect(TAREFAS).toEqual(original);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=filter-tasks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-tasks.test.js","sourceRoot":"","sources":["../../src/filter-tasks/filter-tasks.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { GroupId, TaskId, TaskStatus, TaskType } from '@opentask/taskin-types';
|
|
2
|
+
/**
|
|
3
|
+
* O que restringe uma listagem de tarefas.
|
|
4
|
+
*
|
|
5
|
+
* Todos os campos sao opcionais e **se somam**: informar dois exige os dois.
|
|
6
|
+
* Um criterio ausente nao restringe nada.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface TaskFilterCriteria {
|
|
11
|
+
/** Status exato. */
|
|
12
|
+
readonly status?: TaskStatus;
|
|
13
|
+
/** Tipo exato. */
|
|
14
|
+
readonly type?: TaskType;
|
|
15
|
+
/**
|
|
16
|
+
* Responsavel, casado por id ou nome — inteiro ou em parte, sem distinguir
|
|
17
|
+
* maiuscula. Tarefa sem responsavel nunca casa.
|
|
18
|
+
*/
|
|
19
|
+
readonly assignee?: string;
|
|
20
|
+
/** Apenas o que ainda esta em aberto. Ignorado quando `status` e informado. */
|
|
21
|
+
readonly open?: boolean;
|
|
22
|
+
/** Apenas o que foi encerrado. Ignorado quando `status` e informado. */
|
|
23
|
+
readonly closed?: boolean;
|
|
24
|
+
/** Busca livre em id, titulo, status e responsavel. */
|
|
25
|
+
readonly text?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A forma de uma tarefa numa listagem — o que identifica, nao o que ela diz.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export interface TaskSummary {
|
|
33
|
+
readonly id: TaskId;
|
|
34
|
+
readonly title: string;
|
|
35
|
+
readonly status: TaskStatus;
|
|
36
|
+
readonly type: TaskType;
|
|
37
|
+
readonly assignee?: {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
};
|
|
41
|
+
readonly priority?: number;
|
|
42
|
+
readonly groupId?: GroupId;
|
|
43
|
+
readonly groupName?: string;
|
|
44
|
+
readonly difficulty?: number;
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-tasks.types.js","sourceRoot":"","sources":["../../src/filter-tasks/filter-tasks.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/filter-tasks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
|
package/dist/task-manager.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ export declare class TaskManager<TTask extends Task = Task> implements ITaskMana
|
|
|
19
19
|
private withStatus;
|
|
20
20
|
startTask(taskId: TaskId): Promise<TTask>;
|
|
21
21
|
pauseTask(taskId: TaskId): Promise<TTask>;
|
|
22
|
+
/**
|
|
23
|
+
* Every task the configured provider knows about.
|
|
24
|
+
*
|
|
25
|
+
* Pass-through on purpose: the manager owns the state transitions, not the
|
|
26
|
+
* storage. Having it here is what lets a consumer holding only the manager —
|
|
27
|
+
* the MCP server — answer "what work exists?".
|
|
28
|
+
*/
|
|
29
|
+
getAllTasks(): Promise<TTask[]>;
|
|
22
30
|
finishTask(taskId: TaskId): Promise<TTask>;
|
|
23
31
|
reviewTask(taskId: TaskId): Promise<TTask>;
|
|
24
32
|
createTask(options: CreateTaskOptions): Promise<CreateTaskResult<TTask>>;
|
package/dist/task-manager.js
CHANGED
|
@@ -46,6 +46,16 @@ export class TaskManager {
|
|
|
46
46
|
await this.taskProvider.updateTask(updatedTask);
|
|
47
47
|
return updatedTask;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Every task the configured provider knows about.
|
|
51
|
+
*
|
|
52
|
+
* Pass-through on purpose: the manager owns the state transitions, not the
|
|
53
|
+
* storage. Having it here is what lets a consumer holding only the manager —
|
|
54
|
+
* the MCP server — answer "what work exists?".
|
|
55
|
+
*/
|
|
56
|
+
async getAllTasks() {
|
|
57
|
+
return await this.taskProvider.getAllTasks();
|
|
58
|
+
}
|
|
49
59
|
async finishTask(taskId) {
|
|
50
60
|
const task = await this.taskProvider.findTask(taskId);
|
|
51
61
|
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":"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"}
|
|
1
|
+
{"version":3,"file":"task-manager.js","sourceRoot":"","sources":["../src/task-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -135,6 +135,17 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
135
135
|
* @throws Error if task is not found
|
|
136
136
|
*/
|
|
137
137
|
finishTask: (taskId: TaskId) => Promise<TTask>;
|
|
138
|
+
/**
|
|
139
|
+
* Every task the configured provider knows about.
|
|
140
|
+
*
|
|
141
|
+
* Delegates to the provider, like {@link ITaskManager.lint} does. Listing is
|
|
142
|
+
* a domain question — "what work exists?" — and a consumer that only holds a
|
|
143
|
+
* manager should not need the provider to answer it. The MCP server did, and
|
|
144
|
+
* shipped a `taskin://tasks` resource that answered with a placeholder.
|
|
145
|
+
*
|
|
146
|
+
* @returns The tasks, in whatever order the provider returns them
|
|
147
|
+
*/
|
|
148
|
+
getAllTasks: () => Promise<TTask[]>;
|
|
138
149
|
/**
|
|
139
150
|
* Mark a task as ready for review.
|
|
140
151
|
* Transitions the task from 'in-progress' to 'in-review' status.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentask/taskin-task-manager",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.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.",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@opentask/taskin-types": "2.
|
|
43
|
+
"@opentask/taskin-types": "2.2.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"typescript": "^6.0.0"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { parseTaskId, type Task } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { filterTasks } from './filter-tasks.js';
|
|
4
|
+
|
|
5
|
+
function tarefa({ id, ...resto }: Omit<Partial<Task>, 'id'> & { id: string }): Task {
|
|
6
|
+
return {
|
|
7
|
+
createdAt: '2026-09-12T00:00:00.000Z',
|
|
8
|
+
status: 'pending',
|
|
9
|
+
title: `Tarefa ${id}`,
|
|
10
|
+
type: 'feat',
|
|
11
|
+
...resto,
|
|
12
|
+
id: parseTaskId(id),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ANA = { id: 'ana-souza', name: 'Ana Souza', email: 'ana@example.com' };
|
|
17
|
+
const JOAO = { id: 'joao', name: 'João Silva', email: 'joao@example.com' };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A selecao de tarefas, isolada de como ela e exibida.
|
|
21
|
+
*
|
|
22
|
+
* Existiam duas implementacoes divergentes: o comando `list` casava assignee
|
|
23
|
+
* por substring em nome **ou** id, e o `Taskin.list` casava `userId` exato — e
|
|
24
|
+
* ainda projetava a task derrubando o proprio `assignee`. Duas respostas
|
|
25
|
+
* diferentes para a mesma pergunta, e o servidor MCP sem nenhuma.
|
|
26
|
+
*/
|
|
27
|
+
describe('filterTasks', () => {
|
|
28
|
+
const TAREFAS: readonly Task[] = [
|
|
29
|
+
tarefa({ id: '001', status: 'pending', type: 'feat', assignee: ANA, title: 'Criar login' }),
|
|
30
|
+
tarefa({ id: '002', status: 'done', type: 'fix', assignee: JOAO, title: 'Corrigir crash' }),
|
|
31
|
+
tarefa({ id: '003', status: 'in-progress', type: 'feat', assignee: ANA, title: 'Painel de metricas' }),
|
|
32
|
+
tarefa({ id: '004', status: 'blocked', type: 'chore', title: 'Sem responsavel' }),
|
|
33
|
+
tarefa({ id: '005', status: 'canceled', type: 'docs', assignee: JOAO, title: 'Guia antigo' }),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
it('sem criterio, devolve tudo na ordem recebida', () => {
|
|
37
|
+
expect(filterTasks(TAREFAS, {}).map((t) => t.id)).toEqual(['001', '002', '003', '004', '005']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('filtra por status', () => {
|
|
41
|
+
expect(filterTasks(TAREFAS, { status: 'done' }).map((t) => t.id)).toEqual(['002']);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('filtra por tipo', () => {
|
|
45
|
+
expect(filterTasks(TAREFAS, { type: 'feat' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('`open` traz o que ainda esta em aberto, e nao o que foi encerrado', () => {
|
|
49
|
+
expect(filterTasks(TAREFAS, { open: true }).map((t) => t.id)).toEqual(['001', '003', '004']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('`closed` traz done e canceled', () => {
|
|
53
|
+
expect(filterTasks(TAREFAS, { closed: true }).map((t) => t.id)).toEqual(['002', '005']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('casa o assignee pelo id, pelo nome e por parte dele', () => {
|
|
57
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana-souza' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
58
|
+
expect(filterTasks(TAREFAS, { assignee: 'Ana Souza' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
59
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana' }).map((t) => t.id)).toEqual(['001', '003']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('nao devolve tarefa sem responsavel quando se filtra por um', () => {
|
|
63
|
+
expect(filterTasks(TAREFAS, { assignee: 'ana' }).map((t) => t.id)).not.toContain('004');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('o texto livre procura em id, titulo, status e responsavel', () => {
|
|
67
|
+
expect(filterTasks(TAREFAS, { text: 'crash' }).map((t) => t.id)).toEqual(['002']);
|
|
68
|
+
expect(filterTasks(TAREFAS, { text: '003' }).map((t) => t.id)).toEqual(['003']);
|
|
69
|
+
expect(filterTasks(TAREFAS, { text: 'blocked' }).map((t) => t.id)).toEqual(['004']);
|
|
70
|
+
expect(filterTasks(TAREFAS, { text: 'joão' }).map((t) => t.id)).toEqual(['002', '005']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('combina criterios, exigindo todos', () => {
|
|
74
|
+
expect(filterTasks(TAREFAS, { type: 'feat', assignee: 'ana', status: 'pending' }).map((t) => t.id)).toEqual([
|
|
75
|
+
'001',
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('nao altera o arranjo recebido', () => {
|
|
80
|
+
const original = [...TAREFAS];
|
|
81
|
+
filterTasks(TAREFAS, { status: 'done' });
|
|
82
|
+
expect(TAREFAS).toEqual(original);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Task, TaskStatus } from '@opentask/taskin-types';
|
|
2
|
+
import type { TaskFilterCriteria, TaskSummary } from './filter-tasks.types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* O que conta como "ainda em aberto".
|
|
6
|
+
*
|
|
7
|
+
* `paused` e `in-review` entram: a tarefa nao foi entregue nem descartada, so
|
|
8
|
+
* nao esta sendo tocada agora. Deixa-las de fora escondia trabalho em curso de
|
|
9
|
+
* quem filtrava por `--open`.
|
|
10
|
+
*/
|
|
11
|
+
const EM_ABERTO: readonly TaskStatus[] = ['pending', 'in-progress', 'paused', 'in-review', 'blocked'];
|
|
12
|
+
|
|
13
|
+
const ENCERRADOS: readonly TaskStatus[] = ['done', 'canceled'];
|
|
14
|
+
|
|
15
|
+
const contem = (valor: string | undefined, procurado: string): boolean =>
|
|
16
|
+
valor !== undefined && valor.toLowerCase().includes(procurado);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Casa o responsavel por id ou nome, inteiro ou em parte.
|
|
20
|
+
*
|
|
21
|
+
* Tarefa sem responsavel nunca casa: filtrar por uma pessoa e perguntar "o que
|
|
22
|
+
* e dela", e "de ninguem" nao e resposta.
|
|
23
|
+
*/
|
|
24
|
+
function casaResponsavel(task: Task, procurado: string): boolean {
|
|
25
|
+
const alvo = procurado.toLowerCase();
|
|
26
|
+
return contem(task.assignee?.id, alvo) || contem(task.assignee?.name, alvo);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Seleciona tarefas por criterio, sem saber como elas serao exibidas.
|
|
31
|
+
*
|
|
32
|
+
* Vive aqui, no pacote agnostico, porque a mesma pergunta e feita de tres
|
|
33
|
+
* lugares: o comando `list`, a saida em JSON e o servidor MCP. Antes eram duas
|
|
34
|
+
* implementacoes que ja discordavam — o comando casava o responsavel por
|
|
35
|
+
* substring em nome ou id, e a classe `Taskin` casava `userId` exato.
|
|
36
|
+
*
|
|
37
|
+
* Nao ordena e nao muda o arranjo recebido: a ordem que entra e a que sai.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export function filterTasks(tasks: readonly Task[], criteria: TaskFilterCriteria): Task[] {
|
|
42
|
+
return tasks.filter((task) => {
|
|
43
|
+
if (criteria.status !== undefined) {
|
|
44
|
+
if (task.status !== criteria.status) return false;
|
|
45
|
+
} else if (criteria.open && !EM_ABERTO.includes(task.status)) {
|
|
46
|
+
return false;
|
|
47
|
+
} else if (criteria.closed && !ENCERRADOS.includes(task.status)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (criteria.type !== undefined && task.type !== criteria.type) return false;
|
|
52
|
+
|
|
53
|
+
if (criteria.assignee !== undefined && !casaResponsavel(task, criteria.assignee)) return false;
|
|
54
|
+
|
|
55
|
+
if (criteria.text !== undefined) {
|
|
56
|
+
const procurado = criteria.text.toLowerCase();
|
|
57
|
+
const casa =
|
|
58
|
+
task.id.toLowerCase().includes(procurado) ||
|
|
59
|
+
contem(task.title, procurado) ||
|
|
60
|
+
contem(task.status, procurado) ||
|
|
61
|
+
casaResponsavel(task, procurado);
|
|
62
|
+
if (!casa) return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* O que identifica uma tarefa numa listagem, sem o que ela diz.
|
|
71
|
+
*
|
|
72
|
+
* O provider de arquivos carrega o markdown inteiro em `content` e
|
|
73
|
+
* `description`. Incluir isso faria a listagem deste repositorio passar de
|
|
74
|
+
* vinte mil linhas — e quem lista quer **escolher** uma tarefa, nao le-las
|
|
75
|
+
* todas. O corpo se busca depois, pelo id.
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
export function summarizeTask(task: Task): TaskSummary {
|
|
80
|
+
return {
|
|
81
|
+
id: task.id,
|
|
82
|
+
title: task.title,
|
|
83
|
+
status: task.status,
|
|
84
|
+
type: task.type,
|
|
85
|
+
...(task.assignee && { assignee: { id: task.assignee.id, name: task.assignee.name } }),
|
|
86
|
+
...(task.order !== undefined && { priority: task.order }),
|
|
87
|
+
...(task.groupId && { groupId: task.groupId }),
|
|
88
|
+
...(task.groupName && { groupName: task.groupName }),
|
|
89
|
+
...(task.difficulty !== undefined && { difficulty: task.difficulty }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GroupId, TaskId, TaskStatus, TaskType } from '@opentask/taskin-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* O que restringe uma listagem de tarefas.
|
|
5
|
+
*
|
|
6
|
+
* Todos os campos sao opcionais e **se somam**: informar dois exige os dois.
|
|
7
|
+
* Um criterio ausente nao restringe nada.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export interface TaskFilterCriteria {
|
|
12
|
+
/** Status exato. */
|
|
13
|
+
readonly status?: TaskStatus;
|
|
14
|
+
/** Tipo exato. */
|
|
15
|
+
readonly type?: TaskType;
|
|
16
|
+
/**
|
|
17
|
+
* Responsavel, casado por id ou nome — inteiro ou em parte, sem distinguir
|
|
18
|
+
* maiuscula. Tarefa sem responsavel nunca casa.
|
|
19
|
+
*/
|
|
20
|
+
readonly assignee?: string;
|
|
21
|
+
/** Apenas o que ainda esta em aberto. Ignorado quando `status` e informado. */
|
|
22
|
+
readonly open?: boolean;
|
|
23
|
+
/** Apenas o que foi encerrado. Ignorado quando `status` e informado. */
|
|
24
|
+
readonly closed?: boolean;
|
|
25
|
+
/** Busca livre em id, titulo, status e responsavel. */
|
|
26
|
+
readonly text?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A forma de uma tarefa numa listagem — o que identifica, nao o que ela diz.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export interface TaskSummary {
|
|
35
|
+
readonly id: TaskId;
|
|
36
|
+
readonly title: string;
|
|
37
|
+
readonly status: TaskStatus;
|
|
38
|
+
readonly type: TaskType;
|
|
39
|
+
readonly assignee?: { readonly id: string; readonly name: string };
|
|
40
|
+
readonly priority?: number;
|
|
41
|
+
readonly groupId?: GroupId;
|
|
42
|
+
readonly groupName?: string;
|
|
43
|
+
readonly difficulty?: number;
|
|
44
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Re-export commonly used types from @opentask/taskin-types for convenience
|
|
2
2
|
export type { Task, TaskStatus, TaskType, User } from '@opentask/taskin-types';
|
|
3
|
+
export * from './filter-tasks/index';
|
|
3
4
|
export * from './metrics.types';
|
|
4
5
|
export * from './task-manager';
|
|
5
6
|
export * from './task-manager.types';
|
package/src/task-manager.ts
CHANGED
|
@@ -65,6 +65,17 @@ export class TaskManager<TTask extends Task = Task> implements ITaskManager<TTas
|
|
|
65
65
|
return updatedTask;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Every task the configured provider knows about.
|
|
70
|
+
*
|
|
71
|
+
* Pass-through on purpose: the manager owns the state transitions, not the
|
|
72
|
+
* storage. Having it here is what lets a consumer holding only the manager —
|
|
73
|
+
* the MCP server — answer "what work exists?".
|
|
74
|
+
*/
|
|
75
|
+
async getAllTasks(): Promise<TTask[]> {
|
|
76
|
+
return await this.taskProvider.getAllTasks();
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
async finishTask(taskId: TaskId): Promise<TTask> {
|
|
69
80
|
const task = await this.taskProvider.findTask(taskId);
|
|
70
81
|
|
|
@@ -154,6 +154,18 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
154
154
|
*/
|
|
155
155
|
finishTask: (taskId: TaskId) => Promise<TTask>;
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Every task the configured provider knows about.
|
|
159
|
+
*
|
|
160
|
+
* Delegates to the provider, like {@link ITaskManager.lint} does. Listing is
|
|
161
|
+
* a domain question — "what work exists?" — and a consumer that only holds a
|
|
162
|
+
* manager should not need the provider to answer it. The MCP server did, and
|
|
163
|
+
* shipped a `taskin://tasks` resource that answered with a placeholder.
|
|
164
|
+
*
|
|
165
|
+
* @returns The tasks, in whatever order the provider returns them
|
|
166
|
+
*/
|
|
167
|
+
getAllTasks: () => Promise<TTask[]>;
|
|
168
|
+
|
|
157
169
|
/**
|
|
158
170
|
* Mark a task as ready for review.
|
|
159
171
|
* Transitions the task from 'in-progress' to 'in-review' status.
|