@opentask/taskin-task-manager 3.0.2 → 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 +119 -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.d.ts +26 -0
- package/dist/filter-tasks/filter-tasks.js +99 -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 +110 -0
- package/dist/filter-tasks/filter-tasks.test.js.map +1 -0
- package/dist/filter-tasks/filter-tasks.types.d.ts +27 -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 +4 -0
- package/dist/filter-tasks/index.js +3 -0
- package/dist/filter-tasks/index.js.map +1 -0
- 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 +4 -0
- package/dist/index.js +4 -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 +34 -1
- package/dist/task-manager.js +58 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/task-manager.types.d.ts +93 -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 +129 -0
- package/src/filter-tasks/filter-tasks.ts +106 -0
- package/src/filter-tasks/filter-tasks.types.ts +26 -0
- package/src/filter-tasks/index.ts +16 -0
- package/src/group-registry.contract.ts +99 -0
- package/src/group-registry.types.ts +47 -0
- package/src/index.ts +4 -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 +70 -0
- package/src/task-manager.types.ts +95 -0
- package/src/testing.ts +8 -0
- package/src/user-registry.contract.ts +6 -6
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { parseTaskId } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { numerarPrioridade } from './numerar-prioridade.js';
|
|
4
|
+
const tarefa = (id, order) => ({ id: parseTaskId(id), title: `Tarefa ${id}`, status: 'pending', type: 'feat', order });
|
|
5
|
+
const ids = (ts) => ts.map((t) => String(t.id));
|
|
6
|
+
/**
|
|
7
|
+
* Numeracao inicial de prioridade.
|
|
8
|
+
*
|
|
9
|
+
* Existe para eliminar o estado que custa caro: um projeto **meio numerado**.
|
|
10
|
+
* Enquanto metade das tarefas nao tem `order`, dar numero a uma do meio obriga a
|
|
11
|
+
* numerar todos os antecessores — 124 arquivos num projeto de 500, medido. Com o
|
|
12
|
+
* projeto inteiro numerado de uma vez, num ato deliberado, todo movimento
|
|
13
|
+
* seguinte custa um arquivo.
|
|
14
|
+
*
|
|
15
|
+
* A funcao devolve **so** o que precisa mudar, e nao a lista inteira: quem chama
|
|
16
|
+
* grava exatamente isso.
|
|
17
|
+
*/
|
|
18
|
+
describe('numerarPrioridade', () => {
|
|
19
|
+
it('num projeto sem nenhuma prioridade, numera todas preservando a ordem', () => {
|
|
20
|
+
const tarefas = [tarefa('001'), tarefa('002'), tarefa('003')];
|
|
21
|
+
const mudancas = numerarPrioridade(tarefas);
|
|
22
|
+
expect(ids(mudancas)).toEqual(['001', '002', '003']);
|
|
23
|
+
expect(mudancas.map((t) => t.order)).toEqual([100, 200, 300]);
|
|
24
|
+
});
|
|
25
|
+
/*
|
|
26
|
+
* Quem ja priorizou tomou uma decisao, e o comando preenche as lacunas em
|
|
27
|
+
* volta dela em vez de reescreve-la.
|
|
28
|
+
*/
|
|
29
|
+
it('mantem o numero de quem ja tem, e so preenche as lacunas', () => {
|
|
30
|
+
const tarefas = [tarefa('001', 50), tarefa('002'), tarefa('003', 900)];
|
|
31
|
+
const mudancas = numerarPrioridade(tarefas);
|
|
32
|
+
expect(ids(mudancas)).toEqual(['002']);
|
|
33
|
+
const nova = mudancas[0]?.order ?? 0;
|
|
34
|
+
expect(nova).toBeGreaterThan(50);
|
|
35
|
+
expect(nova).toBeLessThan(900);
|
|
36
|
+
});
|
|
37
|
+
/*
|
|
38
|
+
* Seguro de pôr num script ou num hook: a segunda passada nao escreve nada.
|
|
39
|
+
*/
|
|
40
|
+
it('rodar de novo nao muda nada', () => {
|
|
41
|
+
const tarefas = [tarefa('001'), tarefa('002'), tarefa('003')];
|
|
42
|
+
const primeira = numerarPrioridade(tarefas);
|
|
43
|
+
const segunda = numerarPrioridade(primeira.length ? aplicar(tarefas, primeira) : tarefas);
|
|
44
|
+
expect(segunda).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
it('nao devolve nada quando todas ja estao numeradas e em ordem', () => {
|
|
47
|
+
expect(numerarPrioridade([tarefa('001', 100), tarefa('002', 200)])).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
/** Aplica as mudancas devolvidas, como quem grava faria. */
|
|
51
|
+
function aplicar(tarefas, mudancas) {
|
|
52
|
+
const por = new Map(mudancas.map((t) => [String(t.id), t]));
|
|
53
|
+
return tarefas.map((t) => por.get(String(t.id)) ?? t);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=numerar-prioridade.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numerar-prioridade.test.js","sourceRoot":"","sources":["../../src/numerar-prioridade/numerar-prioridade.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ordenar-tarefas.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ordenar-tarefas/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Task } from '@opentask/taskin-types';
|
|
2
|
+
/**
|
|
3
|
+
* Como uma listagem pode ser ordenada.
|
|
4
|
+
*
|
|
5
|
+
* O mesmo vocabulario que o quadro de priorizacao do dashboard usa. Manter as
|
|
6
|
+
* grafias iguais entre as superficies vale mais que elegancia local: quem le a
|
|
7
|
+
* documentacao de uma reconhece a outra.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export type ModoDeOrdenacao = 'manual' | 'diff-asc' | 'diff-desc';
|
|
12
|
+
/**
|
|
13
|
+
* O que a ordenacao precisa saber, e nada alem disso.
|
|
14
|
+
*
|
|
15
|
+
* Exigir `Task` inteira deixaria a funcao inalcancavel para quem tem outra
|
|
16
|
+
* forma de tarefa — o quadro de priorizacao do dashboard, por exemplo, usa um
|
|
17
|
+
* modelo de tela proprio. Pedir so os dois campos que ela le e o que permite as
|
|
18
|
+
* duas superficies compartilharem a regra em vez de cada uma ter a sua copia.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export interface OrdenavelPorPrioridade {
|
|
23
|
+
readonly order?: number;
|
|
24
|
+
readonly difficulty?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Um no da listagem: uma tarefa solta, ou um grupo com os membros que casaram. */
|
|
27
|
+
export type NoDaListagem<TTask extends Task = Task> = {
|
|
28
|
+
readonly kind: 'task';
|
|
29
|
+
readonly task: TTask;
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: 'group';
|
|
32
|
+
readonly groupId: string;
|
|
33
|
+
/** `undefined` quando o registro nao conhece o grupo. */
|
|
34
|
+
readonly groupName: string | undefined;
|
|
35
|
+
readonly tasks: readonly TTask[];
|
|
36
|
+
/** Quantos membros o filtro deixou de fora. Zero quando o grupo veio inteiro. */
|
|
37
|
+
readonly hidden: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Ordena uma listagem, sem alterar a lista recebida.
|
|
41
|
+
*
|
|
42
|
+
* Vivia dentro do pacote Vue do dashboard, inalcancavel para a CLI e para o
|
|
43
|
+
* servidor MCP — e por isso `taskin list` devolvia as tarefas na ordem em que o
|
|
44
|
+
* provider as achava, que na pratica e por id.
|
|
45
|
+
*
|
|
46
|
+
* `manual` usa o `order`, com quem nao tem por ultimo e estabilidade no resto.
|
|
47
|
+
* Os dois modos de dificuldade usam o `difficulty`.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export declare function ordenarTarefas<TTask extends OrdenavelPorPrioridade>(tarefas: readonly TTask[], modo?: ModoDeOrdenacao): TTask[];
|
|
52
|
+
/**
|
|
53
|
+
* Junta os membros de um grupo num no so, **por identidade**.
|
|
54
|
+
*
|
|
55
|
+
* Separado de {@link ordenarTarefas} de proposito. No dashboard as duas coisas
|
|
56
|
+
* vinham juntas porque a arvore existia para desenhar caixas; aqui quem consome
|
|
57
|
+
* decide se quer agrupar.
|
|
58
|
+
*
|
|
59
|
+
* Agrupa pelo `groupId`, e nao por adjacencia: membros separados por um filtro
|
|
60
|
+
* ou por uma ordenacao continuam sendo **um** grupo. Agrupar por adjacencia era
|
|
61
|
+
* o defeito da task-072, que partia o mesmo grupo em dois nos com o mesmo nome.
|
|
62
|
+
*
|
|
63
|
+
* @param tarefas - Ja ordenadas; a ordem dos grupos segue a do primeiro membro
|
|
64
|
+
* @param nomes - O que o registro de grupos sabe, por id
|
|
65
|
+
* @param totais - Quantos membros cada grupo tem **antes** do filtro. O que
|
|
66
|
+
* falta vira `hidden`, para o leitor saber que nao esta vendo o grupo inteiro
|
|
67
|
+
* em vez de o filtro se alargar sozinho e trazer o resto.
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
export declare function agruparTarefas<TTask extends Task>(tarefas: readonly TTask[], nomes?: Readonly<Record<string, string>>, totais?: Readonly<Record<string, number>>): NoDaListagem<TTask>[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordena uma listagem, sem alterar a lista recebida.
|
|
3
|
+
*
|
|
4
|
+
* Vivia dentro do pacote Vue do dashboard, inalcancavel para a CLI e para o
|
|
5
|
+
* servidor MCP — e por isso `taskin list` devolvia as tarefas na ordem em que o
|
|
6
|
+
* provider as achava, que na pratica e por id.
|
|
7
|
+
*
|
|
8
|
+
* `manual` usa o `order`, com quem nao tem por ultimo e estabilidade no resto.
|
|
9
|
+
* Os dois modos de dificuldade usam o `difficulty`.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export function ordenarTarefas(tarefas, modo = 'manual') {
|
|
14
|
+
const comIndice = tarefas.map((task, index) => ({ task, index }));
|
|
15
|
+
const campo = (t) => (modo === 'manual' ? t.order : t.difficulty);
|
|
16
|
+
const sinal = modo === 'diff-desc' ? -1 : 1;
|
|
17
|
+
comIndice.sort((a, b) => {
|
|
18
|
+
const va = campo(a.task);
|
|
19
|
+
const vb = campo(b.task);
|
|
20
|
+
// Quem nao tem valor vai para o fim, qualquer que seja a direcao.
|
|
21
|
+
if (va === undefined && vb === undefined)
|
|
22
|
+
return a.index - b.index;
|
|
23
|
+
if (va === undefined)
|
|
24
|
+
return 1;
|
|
25
|
+
if (vb === undefined)
|
|
26
|
+
return -1;
|
|
27
|
+
if (va !== vb)
|
|
28
|
+
return (va - vb) * sinal;
|
|
29
|
+
return a.index - b.index;
|
|
30
|
+
});
|
|
31
|
+
return comIndice.map(({ task }) => task);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Junta os membros de um grupo num no so, **por identidade**.
|
|
35
|
+
*
|
|
36
|
+
* Separado de {@link ordenarTarefas} de proposito. No dashboard as duas coisas
|
|
37
|
+
* vinham juntas porque a arvore existia para desenhar caixas; aqui quem consome
|
|
38
|
+
* decide se quer agrupar.
|
|
39
|
+
*
|
|
40
|
+
* Agrupa pelo `groupId`, e nao por adjacencia: membros separados por um filtro
|
|
41
|
+
* ou por uma ordenacao continuam sendo **um** grupo. Agrupar por adjacencia era
|
|
42
|
+
* o defeito da task-072, que partia o mesmo grupo em dois nos com o mesmo nome.
|
|
43
|
+
*
|
|
44
|
+
* @param tarefas - Ja ordenadas; a ordem dos grupos segue a do primeiro membro
|
|
45
|
+
* @param nomes - O que o registro de grupos sabe, por id
|
|
46
|
+
* @param totais - Quantos membros cada grupo tem **antes** do filtro. O que
|
|
47
|
+
* falta vira `hidden`, para o leitor saber que nao esta vendo o grupo inteiro
|
|
48
|
+
* em vez de o filtro se alargar sozinho e trazer o resto.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export function agruparTarefas(tarefas, nomes = {}, totais = {}) {
|
|
52
|
+
const nos = [];
|
|
53
|
+
const porGrupo = new Map();
|
|
54
|
+
for (const task of tarefas) {
|
|
55
|
+
const id = task.groupId === undefined ? undefined : String(task.groupId);
|
|
56
|
+
if (id === undefined) {
|
|
57
|
+
nos.push({ kind: 'task', task });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const existente = porGrupo.get(id);
|
|
61
|
+
if (existente) {
|
|
62
|
+
existente.push(task);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const membros = [task];
|
|
66
|
+
porGrupo.set(id, membros);
|
|
67
|
+
nos.push({
|
|
68
|
+
kind: 'group',
|
|
69
|
+
groupId: id,
|
|
70
|
+
groupName: nomes[id],
|
|
71
|
+
tasks: membros,
|
|
72
|
+
hidden: 0,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return nos.map((no) => no.kind === 'group'
|
|
76
|
+
? { ...no, hidden: Math.max(0, (totais[no.groupId] ?? no.tasks.length) - no.tasks.length) }
|
|
77
|
+
: no);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ordenar-tarefas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ordenar-tarefas.js","sourceRoot":"","sources":["../../src/ordenar-tarefas/ordenar-tarefas.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
*
|
|
@@ -19,6 +20,38 @@ export declare class TaskManager<TTask extends Task = Task> implements ITaskMana
|
|
|
19
20
|
private withStatus;
|
|
20
21
|
startTask(taskId: TaskId): Promise<TTask>;
|
|
21
22
|
pauseTask(taskId: TaskId): Promise<TTask>;
|
|
23
|
+
/**
|
|
24
|
+
* Every task the configured provider knows about.
|
|
25
|
+
*
|
|
26
|
+
* Pass-through on purpose: the manager owns the state transitions, not the
|
|
27
|
+
* storage. Having it here is what lets a consumer holding only the manager —
|
|
28
|
+
* the MCP server — answer "what work exists?".
|
|
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
|
+
}>;
|
|
22
55
|
finishTask(taskId: TaskId): Promise<TTask>;
|
|
23
56
|
reviewTask(taskId: TaskId): Promise<TTask>;
|
|
24
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
|
*
|
|
@@ -46,6 +47,63 @@ export class TaskManager {
|
|
|
46
47
|
await this.taskProvider.updateTask(updatedTask);
|
|
47
48
|
return updatedTask;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Every task the configured provider knows about.
|
|
52
|
+
*
|
|
53
|
+
* Pass-through on purpose: the manager owns the state transitions, not the
|
|
54
|
+
* storage. Having it here is what lets a consumer holding only the manager —
|
|
55
|
+
* the MCP server — answer "what work exists?".
|
|
56
|
+
*/
|
|
57
|
+
async getAllTasks() {
|
|
58
|
+
return await this.taskProvider.getAllTasks();
|
|
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
|
+
}
|
|
49
107
|
async finishTask(taskId) {
|
|
50
108
|
const task = await this.taskProvider.findTask(taskId);
|
|
51
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.
|
|
@@ -135,6 +156,65 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
135
156
|
* @throws Error if task is not found
|
|
136
157
|
*/
|
|
137
158
|
finishTask: (taskId: TaskId) => Promise<TTask>;
|
|
159
|
+
/**
|
|
160
|
+
* Every task the configured provider knows about.
|
|
161
|
+
*
|
|
162
|
+
* Delegates to the provider, like {@link ITaskManager.lint} does. Listing is
|
|
163
|
+
* a domain question — "what work exists?" — and a consumer that only holds a
|
|
164
|
+
* manager should not need the provider to answer it. The MCP server did, and
|
|
165
|
+
* shipped a `taskin://tasks` resource that answered with a placeholder.
|
|
166
|
+
*
|
|
167
|
+
* @returns The tasks, in whatever order the provider returns them
|
|
168
|
+
*/
|
|
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
|
+
}>;
|
|
138
218
|
/**
|
|
139
219
|
* Mark a task as ready for review.
|
|
140
220
|
* Transitions the task from 'in-progress' to 'in-review' status.
|
|
@@ -174,4 +254,17 @@ export interface ITaskManager<TTask extends Task = Task> {
|
|
|
174
254
|
* @returns The lint result with any validation issues found
|
|
175
255
|
*/
|
|
176
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[]>;
|
|
177
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', () => {
|