@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# @opentask/taskin-task-manager
|
|
2
2
|
|
|
3
|
+
## 3.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d5fafdd: A third filter: `active` — tasks that started and have not finished.
|
|
8
|
+
|
|
9
|
+
`open` includes `pending`, which is work nobody has begun; on a board you are watching while work happens, that is noise. `status: in-progress` is the opposite problem: a task vanishes the moment someone pauses it or sends it for review. `active` is the middle that was missing — `in-progress`, `paused` and `in-review`.
|
|
10
|
+
|
|
11
|
+
`blocked` stays out, deliberately: it is work that started, but nobody is moving it right now.
|
|
12
|
+
|
|
13
|
+
Available as `taskin list --active`, as `active` on the MCP `list_tasks` tool, and as `taskin dashboard --active` / `?filter=active`.
|
|
14
|
+
- f84d9c6: Task groups are an entity now: the name lives in one place, and a task carries only the group id.
|
|
15
|
+
|
|
16
|
+
Until now every member of a group carried its own copy of the name. Renaming meant writing N files with no transaction, so a failure halfway left the group answering to two names — and the write path deleted the name whenever it arrived empty, which is how a real project ended up with four grouped tasks and no name at all.
|
|
17
|
+
|
|
18
|
+
- **`Group { id, name }`** in `@opentask/taskin-types`, and `groupName` is gone from `Task`.
|
|
19
|
+
- **`IGroupRegistry`** with a contract suite any provider proves itself against. Deleting a group says where its tasks go — `deleteGroup(id, { reassignTo })`, the same shape Redmine and Jira offer — so nothing is ever left pointing at a group that no longer exists.
|
|
20
|
+
- **A provider without groups simply does not expose the registry**, and callers find out by its absence rather than by an operation that fails.
|
|
21
|
+
- **`taskin group`** — `list`, `add`, `rename`, `remove` — plus `list_groups` over MCP, and the dashboard resolving names from the server instead of from each task.
|
|
22
|
+
|
|
23
|
+
Renaming a three-member group used to be three writes. It is one, and no task file is touched.
|
|
24
|
+
- 9d11a3d: Listings can be ordered: `taskin list --sort <mode>` and a `sort` argument on the MCP `list_tasks` tool, using the same vocabulary the prioritization board already uses — `manual` (by priority), `diff-asc`, `diff-desc`.
|
|
25
|
+
|
|
26
|
+
Until now `taskin list` returned tasks in whatever order the provider found them, which in practice is by id: the priority column went up and down with no pattern, and whoever read the output had to reorder it in their head. That cost is not hypothetical — an autonomous agent reading the list picked a task with priority 30 while one with 255 sat in the same output.
|
|
27
|
+
|
|
28
|
+
`taskin list --json` now also emits **groups as groups**. A group node carries its id, its name, the members that matched the filter, and how many the filter left out — so a partial group says so instead of quietly looking whole, and a consumer never has to reimplement the grouping rule to get it back.
|
|
29
|
+
|
|
30
|
+
Sorting and grouping are separate functions in `@opentask/taskin-task-manager`, so a caller that wants order without grouping gets exactly that.
|
|
31
|
+
- 6e1c7fa: Three loose ends closed: duplicate priorities are flagged, the dashboard stops carrying its own copies of the domain rules, and the prioritisation warning reaches the screen.
|
|
32
|
+
|
|
33
|
+
**`taskin lint` flags duplicate priorities.** Two tasks on the same number corrupt nothing — ordering breaks the tie by input order — but they mean a decision was lost somewhere. It only shows up looking at the whole set, so it is a pass of its own. Absence is not duplication: tasks with no priority are never compared against each other.
|
|
34
|
+
|
|
35
|
+
**The dashboard consumes the domain rules instead of copying them.** Both the filter sets and the manual sort were byte-for-byte copies living in the Vue packages, because importing the domain package appeared to be blocked by the build. It was not the declaration files: the repo's base config marks every package as a composite project, and a composite project consuming another has to declare the reference. Two lines of configuration.
|
|
36
|
+
|
|
37
|
+
`ordenarTarefas` now asks for `OrdenavelPorPrioridade` — the two fields it reads — instead of a whole `Task`, which is what lets the board's own view model share the rule.
|
|
38
|
+
|
|
39
|
+
**The board warns before it costs you.** On a project where only some tasks carry a priority, the first drag rewrites every file above it. The dashboard now says how many are unnumbered and offers to number them once, and the warning disappears when the state it warns about does.
|
|
40
|
+
- 93a60fe: A task marked `done` has to say what was actually done.
|
|
41
|
+
|
|
42
|
+
This comes from a real audit: of eight tasks closed by autonomous agents over two days, **four** read `done` with the whole checklist untouched. In every one of them the work was genuinely finished and covered by tests — but the file showed none of it, so whoever reviewed had nowhere to start. In one, the audit found an item that had in fact **not** been done, hidden among five that had.
|
|
43
|
+
|
|
44
|
+
Three spellings, and only three: `- [x] item` is done, `- [ ] item` is open, and `- [ ] item — adiado: <reason>` is dropped on purpose, with the decision written down. An empty reason does not count — otherwise the convention would be theatre.
|
|
45
|
+
|
|
46
|
+
Two places ask for it, and they ask differently. **`taskin finish` warns**: it names the open items and their lines, then closes the task anyway, because finishing is a one-shot gesture and refusing there only teaches people to route around it. **`taskin lint` refuses**: a `done` task with an unjustified open item is an error, and CI is where the demand can afford to be hard. `canceled` is exempt — an abandoned task owes nobody a ticked box.
|
|
47
|
+
|
|
48
|
+
Both read the checklist through the same parser, so the linter can never refuse what `finish` just accepted. Providers without the concept of a checklist simply do not implement the capability, and close with no gate at all.
|
|
49
|
+
|
|
50
|
+
Evidence belongs next to the ticked item — a test name, a command, a file — and `TASKS/README.md` now documents the whole vocabulary, including where it came from.
|
|
51
|
+
- 0aa99db: `taskin prioritize` gives every task a priority number, once and on purpose — and `prioritize_tasks` does the same over MCP.
|
|
52
|
+
|
|
53
|
+
A project where only some tasks carry a priority is expensive to reorder: a task with no number sorts last, so giving one to a task in the middle means numbering every task before it. On a 500-task project, moving one from the middle of the unnumbered stretch rewrote **124 files**. After this command, the same move rewrites **one**.
|
|
54
|
+
|
|
55
|
+
It preserves what you already decided: tasks that carry a number keep it, and the gaps around them are filled. Running it again writes nothing, so it is safe in a script. `--dry-run` reports how many would be numbered without touching anything.
|
|
56
|
+
|
|
57
|
+
The rule itself lives in one place — `ITaskManager.prioritizeAll()` — so the CLI and the MCP server share it rather than each carrying a copy.
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- Updated dependencies [f84d9c6]
|
|
62
|
+
- Updated dependencies [0f83ec2]
|
|
63
|
+
- @opentask/taskin-types@2.3.0
|
|
64
|
+
|
|
65
|
+
## 3.1.0
|
|
66
|
+
|
|
67
|
+
### Minor Changes
|
|
68
|
+
|
|
69
|
+
- 6bebe35: O taskin passa a entregar a lista de tarefas de forma que outra ferramenta
|
|
70
|
+
consuma — `taskin list --json` e `list_tasks` no servidor MCP.
|
|
71
|
+
|
|
72
|
+
## O que havia
|
|
73
|
+
|
|
74
|
+
O `list` só imprimia tabela colorida. O servidor MCP não tinha ferramenta de
|
|
75
|
+
listagem, e o recurso `taskin://tasks` **anunciava** a capacidade e respondia
|
|
76
|
+
com um espaço reservado:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{"message": "Task list would be here", "note": "Requires ITaskProvider integration"}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Pior que não oferecer: quem consome recebe algo com cara de dado.
|
|
83
|
+
|
|
84
|
+
## Uma seleção, não três
|
|
85
|
+
|
|
86
|
+
Havia duas implementações da mesma pergunta, já discordando — o comando `list`
|
|
87
|
+
casava o responsável por substring em nome ou id, e a classe `Taskin` casava
|
|
88
|
+
`userId` exato, além de projetar a task derrubando o `assignee`. A saída em
|
|
89
|
+
JSON e o MCP seriam a terceira e a quarta.
|
|
90
|
+
|
|
91
|
+
`filterTasks` e `summarizeTask` vivem no pacote agnóstico, e os três caminhos
|
|
92
|
+
perguntam ao mesmo lugar.
|
|
93
|
+
|
|
94
|
+
## Detalhes que importam para quem consome
|
|
95
|
+
|
|
96
|
+
`list --json` sai **sem cabeçalho, moldura ou aviso** — a saída inteira é JSON
|
|
97
|
+
válido, e lista vazia é `[]`. Não carrega `content` nem `description`: o
|
|
98
|
+
provider de arquivos guarda o markdown inteiro neles, e a listagem deste
|
|
99
|
+
repositório passaria de vinte mil linhas. O corpo se busca pelo id.
|
|
100
|
+
|
|
101
|
+
`getAllTasks` entrou no `ITaskManager`, delegando ao provider como `lint` já
|
|
102
|
+
fazia — era o que faltava para um consumidor que só tem o manager responder
|
|
103
|
+
"que trabalho existe?".
|
|
104
|
+
|
|
105
|
+
`ListTasksOptions.status` e `.type` passam a usar os tipos do domínio em vez de
|
|
106
|
+
`string`. Um valor fora do conjunto nunca casaria, e falhava em silêncio.
|
|
107
|
+
|
|
108
|
+
## Um defeito de transporte, corrigido junto
|
|
109
|
+
|
|
110
|
+
Exercitando o servidor por stdio, o SDK recusava a resposta com
|
|
111
|
+
`invalid_union: expected string, received array`. O invólucro fazia
|
|
112
|
+
`text: result.content`, embrulhando o arranjo de blocos dentro de um bloco cujo
|
|
113
|
+
`text` precisa ser string — então **`start_task` e `finish_task` nunca
|
|
114
|
+
funcionaram pelo transporte real**. Nenhum teste pegava porque todos chamavam
|
|
115
|
+
`callTool` direto, pulando o invólucro.
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- Updated dependencies [6bebe35]
|
|
120
|
+
- @opentask/taskin-types@2.2.0
|
|
121
|
+
|
|
3
122
|
## 3.0.2
|
|
4
123
|
|
|
5
124
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parseTaskId } from '@opentask/taskin-types';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { TaskManager } from './task-manager.js';
|
|
4
|
+
const tarefa = (extra = {}) => ({ id: parseTaskId('001'), title: 'Uma tarefa', status: 'in-progress', type: 'feat', ...extra });
|
|
5
|
+
function provider(overrides = {}) {
|
|
6
|
+
return {
|
|
7
|
+
initialize: vi.fn(async () => { }),
|
|
8
|
+
findTask: vi.fn(async () => tarefa()),
|
|
9
|
+
getAllTasks: vi.fn(async () => [tarefa()]),
|
|
10
|
+
updateTask: vi.fn(async () => { }),
|
|
11
|
+
createTask: vi.fn(),
|
|
12
|
+
lint: vi.fn(),
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* O aviso no `finish`, e por que ele **avisa** em vez de recusar.
|
|
18
|
+
*
|
|
19
|
+
* Fechar uma tarefa e um gesto que acontece uma vez, muitas vezes com pressa.
|
|
20
|
+
* Recusar ali torna o comando fragil e ensina a contornar. O portao duro vive no
|
|
21
|
+
* `lint` (task-075), que roda em CI e quebra o build; aqui o papel e outro —
|
|
22
|
+
* dizer, no momento em que a pessoa ainda esta olhando, o que ficou para tras.
|
|
23
|
+
*
|
|
24
|
+
* E uma **capacidade opcional** do provider: nem toda fonte tem checklist. Um
|
|
25
|
+
* provider sem ela conclui sem portao nenhum, em vez de receber uma chamada que
|
|
26
|
+
* falha.
|
|
27
|
+
*/
|
|
28
|
+
describe('finishTask e os criterios em aberto', () => {
|
|
29
|
+
it('conclui em silencio quando o provider nao tem a capacidade', async () => {
|
|
30
|
+
const manager = new TaskManager(provider());
|
|
31
|
+
const { blockers } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
32
|
+
expect(blockers).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
it('relata os itens em aberto que o provider aponta', async () => {
|
|
35
|
+
const manager = new TaskManager(provider({
|
|
36
|
+
getCompletionBlockers: vi.fn(async () => [{ texto: 'Documentar', linha: 12 }]),
|
|
37
|
+
}));
|
|
38
|
+
const { blockers } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
39
|
+
expect(blockers).toHaveLength(1);
|
|
40
|
+
expect(blockers[0]?.texto).toBe('Documentar');
|
|
41
|
+
});
|
|
42
|
+
it('conclui mesmo assim: avisa, e nao recusa', async () => {
|
|
43
|
+
const updateTask = vi.fn(async () => { });
|
|
44
|
+
const manager = new TaskManager(provider({
|
|
45
|
+
updateTask,
|
|
46
|
+
getCompletionBlockers: vi.fn(async () => [{ texto: 'Documentar', linha: 12 }]),
|
|
47
|
+
}));
|
|
48
|
+
const { task } = await manager.finishTaskComRelato(parseTaskId('001'));
|
|
49
|
+
expect(task.status).toBe('done');
|
|
50
|
+
expect(updateTask).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
it('nao relata nada quando tudo esta feito ou adiado', async () => {
|
|
53
|
+
const manager = new TaskManager(provider({ getCompletionBlockers: vi.fn(async () => []) }));
|
|
54
|
+
expect((await manager.finishTaskComRelato(parseTaskId('001'))).blockers).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=completion-blockers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-blockers.test.js","sourceRoot":"","sources":["../src/completion-blockers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Os criterios que restringem uma listagem, definidos **uma vez so**.
|
|
4
|
+
*
|
|
5
|
+
* Antes cada criterio existia em cinco lugares escritos a mao: o tipo, a flag da
|
|
6
|
+
* CLI, o mapeamento da CLI, o schema JSON do MCP e o mapeamento do MCP.
|
|
7
|
+
* Acrescentar um exigia lembrar dos cinco, e esquecer nao quebrava nada — so
|
|
8
|
+
* fazia uma superficie ficar para tras, em silencio. O sintoma ja tinha
|
|
9
|
+
* aparecido: o criterio chamava-se `assignee` e a flag da CLI chamava-se
|
|
10
|
+
* `--user`, duas grafias para a mesma coisa que ninguem decidiu.
|
|
11
|
+
*
|
|
12
|
+
* Aqui o schema e a fonte. As superficies **derivam** dele:
|
|
13
|
+
*
|
|
14
|
+
* - `TaskFilterCriteria` e `z.infer` deste schema — o tipo nao se escreve a mao.
|
|
15
|
+
* - O schema JSON do `list_tasks` sai de `filterCriteriaJsonSchema`.
|
|
16
|
+
* - As opcoes do `taskin list` saem de `filterCriteriaCliOptions`.
|
|
17
|
+
* - A validacao e a conversao saem de `parseFilterCriteria` — os dois
|
|
18
|
+
* mapeamentos a mao desaparecem.
|
|
19
|
+
*
|
|
20
|
+
* O portao que fecha o circuito e o `satisfies Record<keyof TaskFilterCriteria,
|
|
21
|
+
* ...>` em {@link FILTER_CRITERIA_SURFACES}: acrescentar um criterio ao schema e
|
|
22
|
+
* **nao** ligar a superficie vira erro de compilacao, que e o unico lembrete que
|
|
23
|
+
* nao depende de ninguem lembrar.
|
|
24
|
+
*
|
|
25
|
+
* **Como acrescentar um criterio novo.** Duas linhas, e o compilador cobra a
|
|
26
|
+
* segunda:
|
|
27
|
+
*
|
|
28
|
+
* 1. uma propriedade `.optional()` em {@link FilterCriteriaSchema};
|
|
29
|
+
* 2. a entrada correspondente em {@link FILTER_CRITERIA_SURFACES} (descricao +
|
|
30
|
+
* onde mora na CLI).
|
|
31
|
+
*
|
|
32
|
+
* A CLI, o schema JSON do MCP e a validacao passam a conhece-lo sem mais nenhuma
|
|
33
|
+
* edicao. Esquecer o passo 2 nao compila.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FilterCriteriaSchema: z.ZodObject<{
|
|
36
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
37
|
+
pending: "pending";
|
|
38
|
+
"in-progress": "in-progress";
|
|
39
|
+
paused: "paused";
|
|
40
|
+
"in-review": "in-review";
|
|
41
|
+
done: "done";
|
|
42
|
+
blocked: "blocked";
|
|
43
|
+
canceled: "canceled";
|
|
44
|
+
}>>;
|
|
45
|
+
type: z.ZodOptional<z.ZodEnum<{
|
|
46
|
+
feat: "feat";
|
|
47
|
+
fix: "fix";
|
|
48
|
+
refactor: "refactor";
|
|
49
|
+
docs: "docs";
|
|
50
|
+
test: "test";
|
|
51
|
+
chore: "chore";
|
|
52
|
+
}>>;
|
|
53
|
+
assignee: z.ZodOptional<z.ZodString>;
|
|
54
|
+
open: z.ZodOptional<z.ZodBoolean>;
|
|
55
|
+
closed: z.ZodOptional<z.ZodBoolean>;
|
|
56
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
57
|
+
text: z.ZodOptional<z.ZodString>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
/**
|
|
60
|
+
* O que restringe uma listagem de tarefas.
|
|
61
|
+
*
|
|
62
|
+
* Todos os campos sao opcionais e **se somam**: informar dois exige os dois. Um
|
|
63
|
+
* criterio ausente nao restringe nada. Derivado de {@link FilterCriteriaSchema}
|
|
64
|
+
* — mudar o schema muda o tipo.
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export type TaskFilterCriteria = z.infer<typeof FilterCriteriaSchema>;
|
|
69
|
+
/** Onde um criterio aparece na linha de comando. */
|
|
70
|
+
export type CriterionCli = {
|
|
71
|
+
readonly kind: 'positional';
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: 'flag';
|
|
74
|
+
readonly short?: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* O que uma superficie precisa saber de um criterio alem da sua forma: o texto
|
|
78
|
+
* de ajuda (compartilhado entre `--help` da CLI e a propriedade do MCP) e onde
|
|
79
|
+
* ele mora na linha de comando.
|
|
80
|
+
*/
|
|
81
|
+
export interface CriterionSurface {
|
|
82
|
+
readonly description: string;
|
|
83
|
+
readonly cli: CriterionCli;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A superficie de cada criterio, gateada a cobrir **todos** os campos de
|
|
87
|
+
* {@link TaskFilterCriteria}.
|
|
88
|
+
*
|
|
89
|
+
* O `satisfies Record<keyof TaskFilterCriteria, ...>` e o portao: acrescentar um
|
|
90
|
+
* criterio ao schema sem uma entrada aqui nao compila.
|
|
91
|
+
*/
|
|
92
|
+
export declare const FILTER_CRITERIA_SURFACES: {
|
|
93
|
+
status: {
|
|
94
|
+
description: string;
|
|
95
|
+
cli: {
|
|
96
|
+
kind: "flag";
|
|
97
|
+
short: string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
type: {
|
|
101
|
+
description: string;
|
|
102
|
+
cli: {
|
|
103
|
+
kind: "flag";
|
|
104
|
+
short: string;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
assignee: {
|
|
108
|
+
description: string;
|
|
109
|
+
cli: {
|
|
110
|
+
kind: "flag";
|
|
111
|
+
short: string;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
open: {
|
|
115
|
+
description: string;
|
|
116
|
+
cli: {
|
|
117
|
+
kind: "flag";
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
closed: {
|
|
121
|
+
description: string;
|
|
122
|
+
cli: {
|
|
123
|
+
kind: "flag";
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
active: {
|
|
127
|
+
description: string;
|
|
128
|
+
cli: {
|
|
129
|
+
kind: "flag";
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
text: {
|
|
133
|
+
description: string;
|
|
134
|
+
cli: {
|
|
135
|
+
kind: "positional";
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
type SurfaceMap = Record<string, CriterionSurface>;
|
|
140
|
+
/** O schema JSON de entrada de uma ferramenta MCP: objeto com propriedades. */
|
|
141
|
+
export interface FilterJsonSchema {
|
|
142
|
+
readonly type: 'object';
|
|
143
|
+
readonly properties: Record<string, unknown>;
|
|
144
|
+
readonly required: string[];
|
|
145
|
+
}
|
|
146
|
+
/** Uma opcao de CLI, na forma que o `commander` (e o `defineCommand`) consome. */
|
|
147
|
+
export interface FilterCliOption {
|
|
148
|
+
readonly flags: string;
|
|
149
|
+
readonly description: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* O schema JSON das propriedades de filtro, derivado do zod.
|
|
153
|
+
*
|
|
154
|
+
* Nao e aposta: o `packages/types-ts` ja gera JSON Schema de schemas zod pelo
|
|
155
|
+
* mesmo `z.toJSONSchema()` desde a migracao para o zod 4. Aqui e o mesmo
|
|
156
|
+
* mecanismo, aplicado ao schema de filtro, com a descricao de cada campo vinda
|
|
157
|
+
* da superficie unica.
|
|
158
|
+
*
|
|
159
|
+
* Recebe schema e superficies para poder ser exercitado com um criterio
|
|
160
|
+
* ficticio nos testes — a prova de que deriva, em vez de repetir a mao.
|
|
161
|
+
*/
|
|
162
|
+
export declare function filterCriteriaJsonSchema(schema?: z.ZodObject, surfaces?: SurfaceMap): FilterJsonSchema;
|
|
163
|
+
/**
|
|
164
|
+
* As opcoes do `taskin list`, derivadas das superficies.
|
|
165
|
+
*
|
|
166
|
+
* Um criterio `positional` (o texto livre) nao vira flag: ele e o argumento
|
|
167
|
+
* `[filter]` do comando. Um criterio booleano nao leva valor (`--open`); os
|
|
168
|
+
* demais levam (`-s, --status <status>`). O tipo — booleano ou nao — sai do
|
|
169
|
+
* proprio schema JSON, e nao de uma segunda lista.
|
|
170
|
+
*/
|
|
171
|
+
export declare function filterCriteriaCliOptions(schema?: z.ZodObject, surfaces?: SurfaceMap): FilterCliOption[];
|
|
172
|
+
/**
|
|
173
|
+
* Valida os argumentos crus e os converte em {@link TaskFilterCriteria}.
|
|
174
|
+
*
|
|
175
|
+
* E o `parse` do proprio schema, sem mapeamento a mao. Chaves desconhecidas sao
|
|
176
|
+
* descartadas (o `--json` do CLI, por exemplo, nao e criterio); um valor com o
|
|
177
|
+
* tipo errado — um status fora do conjunto — passa a **falhar barulhento**, em
|
|
178
|
+
* vez de devolver lista vazia como se nao houvesse tarefa.
|
|
179
|
+
*/
|
|
180
|
+
export declare function parseFilterCriteria(raw: unknown): TaskFilterCriteria;
|
|
181
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { TaskStatusSchema, TaskTypeSchema } from '@opentask/taskin-types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Os criterios que restringem uma listagem, definidos **uma vez so**.
|
|
5
|
+
*
|
|
6
|
+
* Antes cada criterio existia em cinco lugares escritos a mao: o tipo, a flag da
|
|
7
|
+
* CLI, o mapeamento da CLI, o schema JSON do MCP e o mapeamento do MCP.
|
|
8
|
+
* Acrescentar um exigia lembrar dos cinco, e esquecer nao quebrava nada — so
|
|
9
|
+
* fazia uma superficie ficar para tras, em silencio. O sintoma ja tinha
|
|
10
|
+
* aparecido: o criterio chamava-se `assignee` e a flag da CLI chamava-se
|
|
11
|
+
* `--user`, duas grafias para a mesma coisa que ninguem decidiu.
|
|
12
|
+
*
|
|
13
|
+
* Aqui o schema e a fonte. As superficies **derivam** dele:
|
|
14
|
+
*
|
|
15
|
+
* - `TaskFilterCriteria` e `z.infer` deste schema — o tipo nao se escreve a mao.
|
|
16
|
+
* - O schema JSON do `list_tasks` sai de `filterCriteriaJsonSchema`.
|
|
17
|
+
* - As opcoes do `taskin list` saem de `filterCriteriaCliOptions`.
|
|
18
|
+
* - A validacao e a conversao saem de `parseFilterCriteria` — os dois
|
|
19
|
+
* mapeamentos a mao desaparecem.
|
|
20
|
+
*
|
|
21
|
+
* O portao que fecha o circuito e o `satisfies Record<keyof TaskFilterCriteria,
|
|
22
|
+
* ...>` em {@link FILTER_CRITERIA_SURFACES}: acrescentar um criterio ao schema e
|
|
23
|
+
* **nao** ligar a superficie vira erro de compilacao, que e o unico lembrete que
|
|
24
|
+
* nao depende de ninguem lembrar.
|
|
25
|
+
*
|
|
26
|
+
* **Como acrescentar um criterio novo.** Duas linhas, e o compilador cobra a
|
|
27
|
+
* segunda:
|
|
28
|
+
*
|
|
29
|
+
* 1. uma propriedade `.optional()` em {@link FilterCriteriaSchema};
|
|
30
|
+
* 2. a entrada correspondente em {@link FILTER_CRITERIA_SURFACES} (descricao +
|
|
31
|
+
* onde mora na CLI).
|
|
32
|
+
*
|
|
33
|
+
* A CLI, o schema JSON do MCP e a validacao passam a conhece-lo sem mais nenhuma
|
|
34
|
+
* edicao. Esquecer o passo 2 nao compila.
|
|
35
|
+
*/
|
|
36
|
+
export const FilterCriteriaSchema = z.object({
|
|
37
|
+
status: TaskStatusSchema.optional(),
|
|
38
|
+
type: TaskTypeSchema.optional(),
|
|
39
|
+
assignee: z.string().optional(),
|
|
40
|
+
open: z.boolean().optional(),
|
|
41
|
+
closed: z.boolean().optional(),
|
|
42
|
+
active: z.boolean().optional(),
|
|
43
|
+
text: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* A superficie de cada criterio, gateada a cobrir **todos** os campos de
|
|
47
|
+
* {@link TaskFilterCriteria}.
|
|
48
|
+
*
|
|
49
|
+
* O `satisfies Record<keyof TaskFilterCriteria, ...>` e o portao: acrescentar um
|
|
50
|
+
* criterio ao schema sem uma entrada aqui nao compila.
|
|
51
|
+
*/
|
|
52
|
+
export const FILTER_CRITERIA_SURFACES = {
|
|
53
|
+
status: { description: 'Exact status (pending, in-progress, done, ...)', cli: { kind: 'flag', short: 's' } },
|
|
54
|
+
type: { description: 'Exact type (feat, fix, chore, ...)', cli: { kind: 'flag', short: 't' } },
|
|
55
|
+
assignee: { description: 'Assignee id or name, whole or in part', cli: { kind: 'flag', short: 'u' } },
|
|
56
|
+
open: { description: 'Only tasks still open', cli: { kind: 'flag' } },
|
|
57
|
+
closed: { description: 'Only tasks already closed', cli: { kind: 'flag' } },
|
|
58
|
+
active: {
|
|
59
|
+
description: 'Only tasks started and not finished (in-progress, paused, in-review)',
|
|
60
|
+
cli: { kind: 'flag' },
|
|
61
|
+
},
|
|
62
|
+
text: { description: 'Free text over id, title, status and assignee', cli: { kind: 'positional' } },
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* O schema JSON das propriedades de filtro, derivado do zod.
|
|
66
|
+
*
|
|
67
|
+
* Nao e aposta: o `packages/types-ts` ja gera JSON Schema de schemas zod pelo
|
|
68
|
+
* mesmo `z.toJSONSchema()` desde a migracao para o zod 4. Aqui e o mesmo
|
|
69
|
+
* mecanismo, aplicado ao schema de filtro, com a descricao de cada campo vinda
|
|
70
|
+
* da superficie unica.
|
|
71
|
+
*
|
|
72
|
+
* Recebe schema e superficies para poder ser exercitado com um criterio
|
|
73
|
+
* ficticio nos testes — a prova de que deriva, em vez de repetir a mao.
|
|
74
|
+
*/
|
|
75
|
+
export function filterCriteriaJsonSchema(schema = FilterCriteriaSchema, surfaces = FILTER_CRITERIA_SURFACES) {
|
|
76
|
+
const described = z.object(Object.fromEntries(Object.entries(schema.shape).map(([key, prop]) => [
|
|
77
|
+
key,
|
|
78
|
+
prop.describe(surfaces[key]?.description ?? ''),
|
|
79
|
+
])));
|
|
80
|
+
const json = z.toJSONSchema(described, { target: 'draft-7', io: 'output' });
|
|
81
|
+
return { type: 'object', properties: json.properties ?? {}, required: [] };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* As opcoes do `taskin list`, derivadas das superficies.
|
|
85
|
+
*
|
|
86
|
+
* Um criterio `positional` (o texto livre) nao vira flag: ele e o argumento
|
|
87
|
+
* `[filter]` do comando. Um criterio booleano nao leva valor (`--open`); os
|
|
88
|
+
* demais levam (`-s, --status <status>`). O tipo — booleano ou nao — sai do
|
|
89
|
+
* proprio schema JSON, e nao de uma segunda lista.
|
|
90
|
+
*/
|
|
91
|
+
export function filterCriteriaCliOptions(schema = FilterCriteriaSchema, surfaces = FILTER_CRITERIA_SURFACES) {
|
|
92
|
+
const { properties } = filterCriteriaJsonSchema(schema, surfaces);
|
|
93
|
+
const options = [];
|
|
94
|
+
for (const [key, surface] of Object.entries(surfaces)) {
|
|
95
|
+
if (surface.cli.kind !== 'flag')
|
|
96
|
+
continue;
|
|
97
|
+
const short = surface.cli.short ? `-${surface.cli.short}, ` : '';
|
|
98
|
+
const takesValue = properties[key]?.type !== 'boolean';
|
|
99
|
+
const flags = takesValue ? `${short}--${key} <${key}>` : `${short}--${key}`;
|
|
100
|
+
options.push({ flags, description: surface.description });
|
|
101
|
+
}
|
|
102
|
+
return options;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Valida os argumentos crus e os converte em {@link TaskFilterCriteria}.
|
|
106
|
+
*
|
|
107
|
+
* E o `parse` do proprio schema, sem mapeamento a mao. Chaves desconhecidas sao
|
|
108
|
+
* descartadas (o `--json` do CLI, por exemplo, nao e criterio); um valor com o
|
|
109
|
+
* tipo errado — um status fora do conjunto — passa a **falhar barulhento**, em
|
|
110
|
+
* vez de devolver lista vazia como se nao houvesse tarefa.
|
|
111
|
+
*/
|
|
112
|
+
export function parseFilterCriteria(raw) {
|
|
113
|
+
return FilterCriteriaSchema.parse(raw);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=filter-criteria.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-criteria.js","sourceRoot":"","sources":["../../src/filter-tasks/filter-criteria.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { FILTER_CRITERIA_SURFACES, FilterCriteriaSchema, filterCriteriaCliOptions, filterCriteriaJsonSchema, parseFilterCriteria, } from './filter-criteria.js';
|
|
4
|
+
/**
|
|
5
|
+
* Um criterio sem superficie e **erro de compilacao**, e nao aviso em tempo de
|
|
6
|
+
* execucao. Esta linha e a prova: um mapa que cobre `TaskFilterCriteria` menos a
|
|
7
|
+
* chave `text` nao satisfaz o gate por tipo. O `@ts-expect-error` falha o
|
|
8
|
+
* `typecheck` se um dia a omissao passar a compilar — que e o defeito que a task
|
|
9
|
+
* fecha.
|
|
10
|
+
*/
|
|
11
|
+
const semText = { status: FILTER_CRITERIA_SURFACES.status };
|
|
12
|
+
// @ts-expect-error — falta a chave 'text': um criterio sem superficie nao compila.
|
|
13
|
+
const _gateIncompleto = semText;
|
|
14
|
+
void _gateIncompleto;
|
|
15
|
+
/**
|
|
16
|
+
* Uma definicao so para os criterios de filtro.
|
|
17
|
+
*
|
|
18
|
+
* A propriedade que vale nao afirma a forma do schema — afirma que um criterio
|
|
19
|
+
* definido num lugar so **aparece nas duas superficies** sem edicao manual. Por
|
|
20
|
+
* isso os geradores recebem um schema e superficies: o teste acrescenta um
|
|
21
|
+
* criterio ficticio e prova que ele emerge na CLI e no MCP, o que so e possivel
|
|
22
|
+
* se as superficies derivarem, em vez de repetir a lista a mao.
|
|
23
|
+
*/
|
|
24
|
+
describe('criterios de filtro derivam de uma definicao so', () => {
|
|
25
|
+
const schemaEstendido = FilterCriteriaSchema.extend({ fictional: z.string().optional() });
|
|
26
|
+
const superficiesEstendidas = {
|
|
27
|
+
...FILTER_CRITERIA_SURFACES,
|
|
28
|
+
fictional: { description: 'criterio ficticio para o teste', cli: { kind: 'flag' } },
|
|
29
|
+
};
|
|
30
|
+
it('um criterio novo aparece no schema JSON do MCP sem editar o MCP', () => {
|
|
31
|
+
const json = filterCriteriaJsonSchema(schemaEstendido, superficiesEstendidas);
|
|
32
|
+
expect(json.properties).toHaveProperty('fictional');
|
|
33
|
+
expect(json.properties.fictional.description).toBe('criterio ficticio para o teste');
|
|
34
|
+
});
|
|
35
|
+
it('um criterio novo aparece nas opcoes da CLI sem editar a CLI', () => {
|
|
36
|
+
const opcoes = filterCriteriaCliOptions(schemaEstendido, superficiesEstendidas);
|
|
37
|
+
const ficticia = opcoes.find((o) => o.flags.includes('--fictional'));
|
|
38
|
+
expect(ficticia).toBeDefined();
|
|
39
|
+
expect(ficticia?.description).toBe('criterio ficticio para o teste');
|
|
40
|
+
});
|
|
41
|
+
it('cada criterio do schema tem uma superficie — nenhum fica para tras', () => {
|
|
42
|
+
const doSchema = Object.keys(FilterCriteriaSchema.shape).sort();
|
|
43
|
+
const comSuperficie = Object.keys(FILTER_CRITERIA_SURFACES).sort();
|
|
44
|
+
expect(comSuperficie).toEqual(doSchema);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('o schema JSON derivado do MCP', () => {
|
|
48
|
+
it('descreve todo criterio, e usa enum onde o valor e fechado', () => {
|
|
49
|
+
const json = filterCriteriaJsonSchema();
|
|
50
|
+
expect(json.type).toBe('object');
|
|
51
|
+
expect(json.required).toEqual([]);
|
|
52
|
+
// status e um conjunto fechado: o schema JSON traz o enum, e nao string solta.
|
|
53
|
+
expect(json.properties.status.enum).toContain('pending');
|
|
54
|
+
expect(json.properties.open.type).toBe('boolean');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('as opcoes derivadas da CLI', () => {
|
|
58
|
+
it('booleano nao leva valor; os demais levam <chave>', () => {
|
|
59
|
+
const opcoes = filterCriteriaCliOptions();
|
|
60
|
+
const porFlag = (frag) => opcoes.find((o) => o.flags.includes(frag));
|
|
61
|
+
expect(porFlag('--open')?.flags).toBe('--open');
|
|
62
|
+
expect(porFlag('--status')?.flags).toBe('-s, --status <status>');
|
|
63
|
+
});
|
|
64
|
+
it('unifica a grafia: a flag do responsavel e --assignee, e nao --user', () => {
|
|
65
|
+
const opcoes = filterCriteriaCliOptions();
|
|
66
|
+
expect(opcoes.some((o) => o.flags.includes('--assignee'))).toBe(true);
|
|
67
|
+
expect(opcoes.some((o) => o.flags.includes('--user'))).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
it('o texto livre e argumento posicional, e nao vira flag', () => {
|
|
70
|
+
const opcoes = filterCriteriaCliOptions();
|
|
71
|
+
expect(opcoes.some((o) => o.flags.includes('--text'))).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('parseFilterCriteria', () => {
|
|
75
|
+
it('descarta chaves que nao sao criterio, como o --json', () => {
|
|
76
|
+
const criteria = parseFilterCriteria({ status: 'done', json: true, foo: 1 });
|
|
77
|
+
expect(criteria).toEqual({ status: 'done' });
|
|
78
|
+
});
|
|
79
|
+
it('falha barulhento num valor fora do conjunto, em vez de silenciar', () => {
|
|
80
|
+
expect(() => parseFilterCriteria({ status: 'inexistente' })).toThrow();
|
|
81
|
+
});
|
|
82
|
+
it('objeto vazio e um criterio valido que nao restringe nada', () => {
|
|
83
|
+
expect(parseFilterCriteria({})).toEqual({});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=filter-criteria.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-criteria.test.js","sourceRoot":"","sources":["../../src/filter-tasks/filter-criteria.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|