@opentask/taskin-task-manager 3.0.1 → 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 CHANGED
@@ -1,5 +1,69 @@
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
+
60
+ ## 3.0.2
61
+
62
+ ### Patch Changes
63
+
64
+ - Updated dependencies [2c402e9]
65
+ - @opentask/taskin-types@2.1.1
66
+
3
67
  ## 3.0.1
4
68
 
5
69
  ### 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=filter-tasks.types.js.map
@@ -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,2 @@
1
+ export { filterTasks, summarizeTask } from './filter-tasks.js';
2
+ export type { TaskFilterCriteria, TaskSummary } from './filter-tasks.types.js';
@@ -0,0 +1,2 @@
1
+ export { filterTasks, summarizeTask } from './filter-tasks.js';
2
+ //# sourceMappingURL=index.js.map
@@ -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
@@ -1,4 +1,5 @@
1
1
  export type { Task, TaskStatus, TaskType, User } from '@opentask/taskin-types';
2
+ export * from './filter-tasks/index.js';
2
3
  export * from './metrics.types.js';
3
4
  export * from './task-manager.js';
4
5
  export * from './task-manager.types.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './filter-tasks/index.js';
1
2
  export * from './metrics.types.js';
2
3
  export * from './task-manager.js';
3
4
  export * from './task-manager.types.js';
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"}
@@ -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>>;
@@ -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) {
@@ -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.1",
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.1.0"
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
+ }
@@ -0,0 +1,2 @@
1
+ export { filterTasks, summarizeTask } from './filter-tasks.js';
2
+ export type { TaskFilterCriteria, TaskSummary } from './filter-tasks.types.js';
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';
@@ -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.