expxdev 0.4.1 → 0.5.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.
Files changed (76) hide show
  1. package/README.md +27 -9
  2. package/dist/cli/expx.js +2 -0
  3. package/dist/cli/expx.js.map +1 -1
  4. package/dist/cli/principal.js +30 -6
  5. package/dist/cli/principal.js.map +1 -1
  6. package/dist/cli/selecao.js +3 -0
  7. package/dist/cli/selecao.js.map +1 -1
  8. package/dist/cli/subcomandos.d.ts +1 -1
  9. package/dist/cli/subcomandos.js +3 -2
  10. package/dist/cli/subcomandos.js.map +1 -1
  11. package/dist/nucleo/caminhos.d.ts +1 -1
  12. package/dist/nucleo/catalogo.d.ts +1 -1
  13. package/dist/nucleo/catalogo.js +7 -1
  14. package/dist/nucleo/catalogo.js.map +1 -1
  15. package/dist/nucleo/layout.d.ts +2 -2
  16. package/dist/nucleo/versao.d.ts +1 -1
  17. package/dist/parser/esquema/enums.d.ts +3 -2
  18. package/dist/parser/esquema/enums.js +3 -2
  19. package/dist/parser/esquema/enums.js.map +1 -1
  20. package/dist/parser/esquema/evento.d.ts +1 -0
  21. package/dist/servidor/http.d.ts +11 -0
  22. package/dist/servidor/http.js +31 -1
  23. package/dist/servidor/http.js.map +1 -1
  24. package/dist/watch/desenho/arvore.d.ts +3 -0
  25. package/dist/watch/desenho/arvore.js +62 -0
  26. package/dist/watch/desenho/arvore.js.map +1 -0
  27. package/dist/watch/desenho/bloqueios.d.ts +5 -0
  28. package/dist/watch/desenho/bloqueios.js +41 -0
  29. package/dist/watch/desenho/bloqueios.js.map +1 -0
  30. package/dist/watch/desenho/cabecalho.d.ts +11 -0
  31. package/dist/watch/desenho/cabecalho.js +49 -0
  32. package/dist/watch/desenho/cabecalho.js.map +1 -0
  33. package/dist/watch/desenho/cor.d.ts +44 -0
  34. package/dist/watch/desenho/cor.js +66 -0
  35. package/dist/watch/desenho/cor.js.map +1 -0
  36. package/dist/watch/desenho/desenhar.d.ts +16 -0
  37. package/dist/watch/desenho/desenhar.js +54 -0
  38. package/dist/watch/desenho/desenhar.js.map +1 -0
  39. package/dist/watch/desenho/eventos.d.ts +3 -0
  40. package/dist/watch/desenho/eventos.js +37 -0
  41. package/dist/watch/desenho/eventos.js.map +1 -0
  42. package/dist/watch/desenho/largura.d.ts +23 -0
  43. package/dist/watch/desenho/largura.js +42 -0
  44. package/dist/watch/desenho/largura.js.map +1 -0
  45. package/dist/watch/desenho/lista.d.ts +9 -0
  46. package/dist/watch/desenho/lista.js +28 -0
  47. package/dist/watch/desenho/lista.js.map +1 -0
  48. package/dist/watch/fontes/estado-schema.d.ts +1 -0
  49. package/dist/watch/fontes/observar.d.ts +32 -0
  50. package/dist/watch/fontes/observar.js +101 -0
  51. package/dist/watch/fontes/observar.js.map +1 -0
  52. package/dist/watch/fontes/raiz.d.ts +12 -0
  53. package/dist/watch/fontes/raiz.js +25 -0
  54. package/dist/watch/fontes/raiz.js.map +1 -0
  55. package/dist/watch/opcoes.d.ts +25 -0
  56. package/dist/watch/opcoes.js +73 -0
  57. package/dist/watch/opcoes.js.map +1 -0
  58. package/dist/watch/principal.d.ts +12 -0
  59. package/dist/watch/principal.js +49 -0
  60. package/dist/watch/principal.js.map +1 -0
  61. package/dist/watch/terminal/restaurar.d.ts +24 -0
  62. package/dist/watch/terminal/restaurar.js +58 -0
  63. package/dist/watch/terminal/restaurar.js.map +1 -0
  64. package/dist/watch/terminal/tela.d.ts +17 -0
  65. package/dist/watch/terminal/tela.js +64 -0
  66. package/dist/watch/terminal/tela.js.map +1 -0
  67. package/dist/watch/visao/escolher.d.ts +18 -0
  68. package/dist/watch/visao/escolher.js +57 -0
  69. package/dist/watch/visao/escolher.js.map +1 -0
  70. package/dist/watch/visao/projetar.d.ts +51 -0
  71. package/dist/watch/visao/projetar.js +38 -0
  72. package/dist/watch/visao/projetar.js.map +1 -0
  73. package/dist/watch/watch.d.ts +26 -0
  74. package/dist/watch/watch.js +89 -0
  75. package/dist/watch/watch.js.map +1 -0
  76. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ /**
4
+ * A raiz do projeto: o diretório mais próximo que contém `.git`.
5
+ *
6
+ * O painel resolve isso com `--dir ./docs` e não precisa de mais nada. O watch
7
+ * precisa de `docs/` E `.expx/` ao mesmo tempo, e `.expx/` não é descendente
8
+ * de `docs/` — as duas só fazem sentido a partir da raiz do repositório
9
+ * (decisão D-17).
10
+ *
11
+ * Sem `.git` em nenhum ancestral, devolvemos o diretório recebido: um projeto
12
+ * ainda não versionado continua tendo `docs/` e `.expx/` no lugar certo.
13
+ */
14
+ export function descobrirRaiz(partida = process.cwd()) {
15
+ let atual = resolve(partida);
16
+ for (;;) {
17
+ if (existsSync(join(atual, ".git")))
18
+ return atual;
19
+ const pai = dirname(atual);
20
+ if (pai === atual)
21
+ return resolve(partida); // chegou na raiz do sistema
22
+ atual = pai;
23
+ }
24
+ }
25
+ //# sourceMappingURL=raiz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raiz.js","sourceRoot":"","sources":["../../../src/watch/fontes/raiz.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB,OAAO,CAAC,GAAG,EAAE;IAC3D,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7B,SAAS,CAAC;QACR,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,GAAG,KAAK,KAAK;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B;QACxE,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Contrato de linha de comando do `expx watch`.
3
+ *
4
+ * Segue o padrão de `src/cli/argumentos.ts`: interpretar devolve `ok` ou um
5
+ * erro em texto, e nunca lança. A diferença é o argumento posicional, que o
6
+ * painel não tem.
7
+ */
8
+ export type Opcoes = {
9
+ /** `expx watch <trabalho_id>` — segue um trabalho específico. */
10
+ trabalho: string | undefined;
11
+ /** `--todos` — lista os trabalhos abertos, um por linha, sem a árvore. */
12
+ todos: boolean;
13
+ ajuda: boolean;
14
+ /** Largura fixa, para teste e para quem quer saída estável. */
15
+ colunas: number | undefined;
16
+ };
17
+ export type Resultado = {
18
+ ok: true;
19
+ opcoes: Opcoes;
20
+ } | {
21
+ ok: false;
22
+ erro: string;
23
+ };
24
+ export declare function ajudaWatch(): string;
25
+ export declare function interpretarOpcoes(argv: readonly string[]): Resultado;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Contrato de linha de comando do `expx watch`.
3
+ *
4
+ * Segue o padrão de `src/cli/argumentos.ts`: interpretar devolve `ok` ou um
5
+ * erro em texto, e nunca lança. A diferença é o argumento posicional, que o
6
+ * painel não tem.
7
+ */
8
+ const AJUDA = `
9
+ expx watch — acompanha um trabalho no terminal, ao vivo (somente leitura)
10
+
11
+ expx watch segue o trabalho atual, lido do .expx/estado.json
12
+ expx watch <trabalho_id> segue um trabalho especifico
13
+ expx watch --todos lista os trabalhos abertos, um por linha, sem arvore
14
+
15
+ --colunas <n> largura fixa, em vez da largura do terminal
16
+ --ajuda mostra esta ajuda
17
+
18
+ Sai com Ctrl+C, devolvendo o terminal ao estado anterior.
19
+ O watch nunca escreve nos arquivos do projeto.
20
+ `.trim();
21
+ export function ajudaWatch() {
22
+ return AJUDA;
23
+ }
24
+ export function interpretarOpcoes(argv) {
25
+ const opcoes = {
26
+ trabalho: undefined,
27
+ todos: false,
28
+ ajuda: false,
29
+ colunas: undefined,
30
+ };
31
+ const posicionais = [];
32
+ for (let i = 0; i < argv.length; i++) {
33
+ const bruto = argv[i] ?? "";
34
+ const [nome, embutido] = bruto.includes("=")
35
+ ? [bruto.slice(0, bruto.indexOf("=")), bruto.slice(bruto.indexOf("=") + 1)]
36
+ : [bruto, undefined];
37
+ switch (nome) {
38
+ case "--todos":
39
+ opcoes.todos = true;
40
+ break;
41
+ case "--colunas": {
42
+ const v = embutido ?? argv[++i];
43
+ const n = Number(v);
44
+ if (v === undefined || !Number.isInteger(n) || n <= 0) {
45
+ return { ok: false, erro: `--colunas exige um numero inteiro positivo` };
46
+ }
47
+ opcoes.colunas = n;
48
+ break;
49
+ }
50
+ case "--ajuda":
51
+ case "--help":
52
+ case "-h":
53
+ opcoes.ajuda = true;
54
+ break;
55
+ default:
56
+ if (nome.startsWith("-")) {
57
+ return { ok: false, erro: `opcao desconhecida: ${nome}` };
58
+ }
59
+ posicionais.push(nome);
60
+ }
61
+ }
62
+ // Dois trabalhos é engano, e o último vencendo em silêncio esconderia o
63
+ // engano até a pessoa reparar que está olhando o trabalho errado.
64
+ if (posicionais.length > 1) {
65
+ return { ok: false, erro: `informe um trabalho por vez: ${posicionais.join(", ")}` };
66
+ }
67
+ opcoes.trabalho = posicionais[0];
68
+ if (opcoes.trabalho !== undefined && opcoes.todos) {
69
+ return { ok: false, erro: "--todos lista todos os trabalhos: nao combine com um trabalho nomeado" };
70
+ }
71
+ return { ok: true, opcoes };
72
+ }
73
+ //# sourceMappingURL=opcoes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opcoes.js","sourceRoot":"","sources":["../../src/watch/opcoes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH,MAAM,KAAK,GAAG;;;;;;;;;;;;CAYb,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,UAAU;IACxB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAuB;IACvD,MAAM,MAAM,GAAW;QACrB,QAAQ,EAAE,SAAS;QACnB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,SAAS;KACnB,CAAC;IAEF,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC1C,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3E,CAAC,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAEvB,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,SAAS;gBACZ,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,MAAM,CAAC,GAAG,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,4CAA4C,EAAE,CAAC;gBAC3E,CAAC;gBACD,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;gBACnB,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC;YACf,KAAK,QAAQ,CAAC;YACd,KAAK,IAAI;gBACP,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR;gBACE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,uBAAuB,IAAI,EAAE,EAAE,CAAC;gBAC5D,CAAC;gBACD,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,kEAAkE;IAClE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,gCAAgC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACvF,CAAC;IACD,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAEjC,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,uEAAuE,EAAE,CAAC;IACtG,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * O `expx watch` como comando: junta opções, terminal e loop.
3
+ *
4
+ * Segue o padrão de saída injetável de `src/cli/expx.ts` — capturar
5
+ * `process.stdout` global vaza entre testes paralelos e produz falha
6
+ * intermitente, e a alternativa adotada pelo projeto é receber a saída.
7
+ */
8
+ export type SaidaWatch = {
9
+ escrever: (texto: string) => void;
10
+ escreverErro: (texto: string) => void;
11
+ };
12
+ export declare function principalWatch(argv: readonly string[], saida: SaidaWatch, raizPedida?: string): Promise<number>;
@@ -0,0 +1,49 @@
1
+ import { descobrirRaiz } from "./fontes/raiz.js";
2
+ import { ajudaWatch, interpretarOpcoes } from "./opcoes.js";
3
+ import { criarRestaurador } from "./terminal/restaurar.js";
4
+ import { corAtiva, ambienteAtual } from "./desenho/cor.js";
5
+ import { executarWatch } from "./watch.js";
6
+ export async function principalWatch(argv, saida, raizPedida) {
7
+ const r = interpretarOpcoes(argv);
8
+ if (!r.ok) {
9
+ saida.escreverErro(`${r.erro}\n\n${ajudaWatch()}\n`);
10
+ return 1;
11
+ }
12
+ if (r.opcoes.ajuda) {
13
+ saida.escrever(`${ajudaWatch()}\n`);
14
+ return 0;
15
+ }
16
+ const raiz = raizPedida ?? descobrirRaiz();
17
+ // A cor sai por `stdout.isTTY` SOZINHO — em `expx watch | less` o stdin
18
+ // continua TTY, e usar o `ehInterativo()` do CLI sujaria o arquivo.
19
+ const amb = ambienteAtual();
20
+ const cor = corAtiva(amb);
21
+ // Controle de cursor é coisa de terminal, e é governado por `isTTY`, não
22
+ // por cor: com `NO_COLOR` num terminal de verdade o cursor ainda precisa
23
+ // ser escondido e restaurado. Com a saída redirecionada, nenhum escape pode
24
+ // sair — nem esse, que foi o único a vazar para o arquivo na primeira
25
+ // versão.
26
+ const restaurador = criarRestaurador({
27
+ escrever: amb.tty ? saida.escrever : () => undefined,
28
+ sair: (codigo) => {
29
+ process.exit(codigo);
30
+ },
31
+ });
32
+ const sessao = await executarWatch({
33
+ raiz,
34
+ opcoes: r.opcoes,
35
+ escrever: saida.escrever,
36
+ cor,
37
+ });
38
+ // Projeto sem `.expx/`: a mensagem já foi escrita e não há o que observar.
39
+ if (sessao.encerrou)
40
+ return sessao.codigo;
41
+ // Só escondemos o cursor depois de saber que o watch vai mesmo rodar, e a
42
+ // restauração cobre os quatro caminhos de saída (decisão D-21).
43
+ restaurador.registrar();
44
+ if (amb.tty)
45
+ restaurador.esconderCursor();
46
+ // O chokidar segura o event loop: o processo fica vivo até o sinal chegar.
47
+ return 0;
48
+ }
49
+ //# sourceMappingURL=principal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal.js","sourceRoot":"","sources":["../../src/watch/principal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAe3C,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAuB,EACvB,KAAiB,EACjB,UAAmB;IAEnB,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACV,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,UAAU,EAAE,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACnB,KAAK,CAAC,QAAQ,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,CAAC;QACpC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,IAAI,aAAa,EAAE,CAAC;IAE3C,wEAAwE;IACxE,oEAAoE;IACpE,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAE1B,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,sEAAsE;IACtE,UAAU;IACV,MAAM,WAAW,GAAG,gBAAgB,CAAC;QACnC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS;QACpD,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;YACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;QACjC,IAAI;QACJ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG;KACJ,CAAC,CAAC;IAEH,2EAA2E;IAC3E,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAE1C,0EAA0E;IAC1E,gEAAgE;IAChE,WAAW,CAAC,SAAS,EAAE,CAAC;IACxB,IAAI,GAAG,CAAC,GAAG;QAAE,WAAW,CAAC,cAAc,EAAE,CAAC;IAE1C,2EAA2E;IAC3E,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Devolver o terminal ao estado anterior — sempre, por qualquer caminho.
3
+ *
4
+ * "Nunca deixa o terminal quebrado" é definição de pronto do usuário. O
5
+ * precedente do painel (`src/cli/principal.ts`) cobre `SIGINT` e `SIGTERM`,
6
+ * o que basta para um servidor HTTP; aqui escondemos o cursor, e uma exceção
7
+ * não capturada deixaria o terminal sem cursor até o próximo `reset`. Daí os
8
+ * quatro caminhos (decisão D-21).
9
+ */
10
+ /** Os quatro caminhos pelos quais o watch pode terminar. */
11
+ export declare const CAMINHOS_DE_SAIDA: readonly ["SIGINT", "SIGTERM", "uncaughtException", "exit"];
12
+ export type CaminhoDeSaida = (typeof CAMINHOS_DE_SAIDA)[number];
13
+ export type Ambiente = {
14
+ escrever: (texto: string) => void;
15
+ sair: (codigo: number) => void;
16
+ };
17
+ export type Restaurador = {
18
+ registrar: () => void;
19
+ desregistrar: () => void;
20
+ esconderCursor: () => void;
21
+ /** Executa a restauração. Idempotente. */
22
+ disparar: (caminho: CaminhoDeSaida) => void;
23
+ };
24
+ export declare function criarRestaurador(amb: Ambiente): Restaurador;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Devolver o terminal ao estado anterior — sempre, por qualquer caminho.
3
+ *
4
+ * "Nunca deixa o terminal quebrado" é definição de pronto do usuário. O
5
+ * precedente do painel (`src/cli/principal.ts`) cobre `SIGINT` e `SIGTERM`,
6
+ * o que basta para um servidor HTTP; aqui escondemos o cursor, e uma exceção
7
+ * não capturada deixaria o terminal sem cursor até o próximo `reset`. Daí os
8
+ * quatro caminhos (decisão D-21).
9
+ */
10
+ const ESC = "";
11
+ const MOSTRAR_CURSOR = `${ESC}[?25h`;
12
+ const ESCONDER_CURSOR = `${ESC}[?25l`;
13
+ /** Os quatro caminhos pelos quais o watch pode terminar. */
14
+ export const CAMINHOS_DE_SAIDA = [
15
+ "SIGINT",
16
+ "SIGTERM",
17
+ "uncaughtException",
18
+ "exit",
19
+ ];
20
+ export function criarRestaurador(amb) {
21
+ let restaurado = false;
22
+ const ouvintes = new Map();
23
+ function restaurar() {
24
+ if (restaurado)
25
+ return; // idempotente: dois sinais não restauram duas vezes
26
+ restaurado = true;
27
+ amb.escrever(MOSTRAR_CURSOR + "\n");
28
+ }
29
+ function disparar(caminho) {
30
+ restaurar();
31
+ // `exit` já está saindo, e em `uncaughtException` quem decide é o Node.
32
+ // Só os dois sinais precisam encerrar o processo por conta própria.
33
+ if (caminho === "SIGINT" || caminho === "SIGTERM")
34
+ amb.sair(0);
35
+ }
36
+ return {
37
+ registrar() {
38
+ for (const caminho of CAMINHOS_DE_SAIDA) {
39
+ const ouvinte = () => {
40
+ disparar(caminho);
41
+ };
42
+ ouvintes.set(caminho, ouvinte);
43
+ process.on(caminho, ouvinte);
44
+ }
45
+ },
46
+ desregistrar() {
47
+ for (const [caminho, ouvinte] of ouvintes) {
48
+ process.off(caminho, ouvinte);
49
+ }
50
+ ouvintes.clear();
51
+ },
52
+ esconderCursor() {
53
+ amb.escrever(ESCONDER_CURSOR);
54
+ },
55
+ disparar,
56
+ };
57
+ }
58
+ //# sourceMappingURL=restaurar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restaurar.js","sourceRoot":"","sources":["../../../src/watch/terminal/restaurar.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,MAAM,cAAc,GAAG,GAAG,GAAG,OAAO,CAAC;AACrC,MAAM,eAAe,GAAG,GAAG,GAAG,OAAO,CAAC;AAEtC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,QAAQ;IACR,SAAS;IACT,mBAAmB;IACnB,MAAM;CACE,CAAC;AAiBX,MAAM,UAAU,gBAAgB,CAAC,GAAa;IAC5C,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEvD,SAAS,SAAS;QAChB,IAAI,UAAU;YAAE,OAAO,CAAC,oDAAoD;QAC5E,UAAU,GAAG,IAAI,CAAC;QAClB,GAAG,CAAC,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,SAAS,QAAQ,CAAC,OAAuB;QACvC,SAAS,EAAE,CAAC;QACZ,wEAAwE;QACxE,oEAAoE;QACpE,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO;QACL,SAAS;YACP,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;gBACxC,MAAM,OAAO,GAAG,GAAS,EAAE;oBACzB,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACpB,CAAC,CAAC;gBACF,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC/B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,YAAY;YACV,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,QAAQ,EAAE,CAAC;gBAC1C,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAChC,CAAC;YACD,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QAED,cAAc;YACZ,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAChC,CAAC;QAED,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A tela: redesenho incremental, sem piscar.
3
+ *
4
+ * "Redesenho não pode piscar a tela inteira." Limpar tudo e reimprimir produz
5
+ * exatamente o piscar que a especificação proíbe, e o buffer alternativo do
6
+ * terminal foi descartado por apagar o que estava na tela quando o watch sai
7
+ * (decisão D-20).
8
+ *
9
+ * A técnica: guardamos as linhas do último desenho, comparamos com as novas e
10
+ * reescrevemos SÓ as que mudaram, movendo o cursor até cada uma.
11
+ */
12
+ export type Tela = {
13
+ desenhar: (linhas: readonly string[]) => void;
14
+ /** Quantas linhas a tela ocupa agora — para quem precisa arrumar o cursor. */
15
+ altura: () => number;
16
+ };
17
+ export declare function criarTela(escrever: (texto: string) => void): Tela;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A tela: redesenho incremental, sem piscar.
3
+ *
4
+ * "Redesenho não pode piscar a tela inteira." Limpar tudo e reimprimir produz
5
+ * exatamente o piscar que a especificação proíbe, e o buffer alternativo do
6
+ * terminal foi descartado por apagar o que estava na tela quando o watch sai
7
+ * (decisão D-20).
8
+ *
9
+ * A técnica: guardamos as linhas do último desenho, comparamos com as novas e
10
+ * reescrevemos SÓ as que mudaram, movendo o cursor até cada uma.
11
+ */
12
+ const ESC = "";
13
+ /** Move o cursor N linhas para cima. */
14
+ const sobe = (n) => (n > 0 ? `${ESC}[${String(n)}A` : "");
15
+ /** Move o cursor N linhas para baixo. */
16
+ const desce = (n) => (n > 0 ? `${ESC}[${String(n)}B` : "");
17
+ /** Começo da linha. */
18
+ const COLUNA_1 = `${ESC}[G`;
19
+ /** Apaga da posição do cursor até o fim da linha. */
20
+ const LIMPA_LINHA = `${ESC}[K`;
21
+ export function criarTela(escrever) {
22
+ let anteriores = [];
23
+ // Onde o cursor está, contando do topo do bloco desenhado.
24
+ let cursor = 0;
25
+ function irPara(linha) {
26
+ const delta = linha - cursor;
27
+ cursor = linha;
28
+ return delta > 0 ? desce(delta) : sobe(-delta);
29
+ }
30
+ return {
31
+ altura: () => anteriores.length,
32
+ desenhar(linhas) {
33
+ const novas = [...linhas];
34
+ const partes = [];
35
+ const alturaAnterior = anteriores.length;
36
+ const total = Math.max(novas.length, alturaAnterior);
37
+ for (let i = 0; i < total; i++) {
38
+ const nova = novas[i];
39
+ const velha = anteriores[i];
40
+ if (nova === velha)
41
+ continue; // não mudou: não reescreve
42
+ partes.push(irPara(i), COLUNA_1);
43
+ // A tela encolheu? A linha que sobrou é apagada, não deixada para trás.
44
+ partes.push(nova === undefined ? LIMPA_LINHA : nova + LIMPA_LINHA);
45
+ }
46
+ if (partes.length === 0)
47
+ return; // nada mudou: nada a escrever
48
+ // Deixa o cursor logo abaixo do bloco, onde o próximo desenho o espera.
49
+ const fim = Math.max(0, novas.length - 1);
50
+ partes.push(irPara(fim));
51
+ // A primeira renderização precisa das quebras de linha para o bloco
52
+ // existir; a partir daí é só movimento de cursor.
53
+ if (alturaAnterior === 0) {
54
+ escrever(novas.join("\n"));
55
+ cursor = fim;
56
+ }
57
+ else {
58
+ escrever(partes.join(""));
59
+ }
60
+ anteriores = novas;
61
+ },
62
+ };
63
+ }
64
+ //# sourceMappingURL=tela.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tela.js","sourceRoot":"","sources":["../../../src/watch/terminal/tela.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,wCAAwC;AACxC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAC1E,yCAAyC;AACzC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAC3E,uBAAuB;AACvB,MAAM,QAAQ,GAAG,GAAG,GAAG,IAAI,CAAC;AAC5B,qDAAqD;AACrD,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC;AAQ/B,MAAM,UAAU,SAAS,CAAC,QAAiC;IACzD,IAAI,UAAU,GAAa,EAAE,CAAC;IAC9B,2DAA2D;IAC3D,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,SAAS,MAAM,CAAC,KAAa;QAC3B,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;QAC7B,MAAM,GAAG,KAAK,CAAC;QACf,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;IAED,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM;QAE/B,QAAQ,CAAC,MAAM;YACb,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YAC1B,MAAM,MAAM,GAAa,EAAE,CAAC;YAE5B,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YAErD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC5B,IAAI,IAAI,KAAK,KAAK;oBAAE,SAAS,CAAC,2BAA2B;gBAEzD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACjC,wEAAwE;gBACxE,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC;YACrE,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,CAAC,8BAA8B;YAE/D,wEAAwE;YACxE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAEzB,oEAAoE;YACpE,kDAAkD;YAClD,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC3B,MAAM,GAAG,GAAG,CAAC;YACf,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5B,CAAC;YAED,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { TrabalhoMontado } from "../../parser/projeto/montar.js";
2
+ import type { EstadoExpx } from "../fontes/estado-schema.js";
3
+ /**
4
+ * Qual trabalho o watch segue.
5
+ *
6
+ * O `estado.json` responde isso direto, no campo `trabalho`. O problema é o
7
+ * modo degradado: nenhum campo do plano marca um trabalho como "o atual"
8
+ * (lacuna L-01). A decisão D-05 fixou a derivação, e ela mora aqui.
9
+ *
10
+ * A ordem de precedência é deliberada:
11
+ *
12
+ * 1. o id que a pessoa pediu na linha de comando — se ela nomeou, é esse;
13
+ * 2. o `trabalho` do `estado.json`, quando ele existe e é válido;
14
+ * 3. a derivação do plano: `em_andamento`, desempatado por `atualizado_em`.
15
+ */
16
+ /** Trabalho aberto é todo status diferente de `concluido` (decisão D-16). */
17
+ export declare function abertos(trabalhos: readonly TrabalhoMontado[]): TrabalhoMontado[];
18
+ export declare function escolherTrabalho(trabalhos: readonly TrabalhoMontado[], estado: EstadoExpx | null, pedido?: string): TrabalhoMontado | null;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Qual trabalho o watch segue.
3
+ *
4
+ * O `estado.json` responde isso direto, no campo `trabalho`. O problema é o
5
+ * modo degradado: nenhum campo do plano marca um trabalho como "o atual"
6
+ * (lacuna L-01). A decisão D-05 fixou a derivação, e ela mora aqui.
7
+ *
8
+ * A ordem de precedência é deliberada:
9
+ *
10
+ * 1. o id que a pessoa pediu na linha de comando — se ela nomeou, é esse;
11
+ * 2. o `trabalho` do `estado.json`, quando ele existe e é válido;
12
+ * 3. a derivação do plano: `em_andamento`, desempatado por `atualizado_em`.
13
+ */
14
+ /** Trabalho aberto é todo status diferente de `concluido` (decisão D-16). */
15
+ export function abertos(trabalhos) {
16
+ return trabalhos.filter((t) => t.status !== "concluido");
17
+ }
18
+ /** O mais recentemente atualizado. Empate de data mantém a ordem recebida. */
19
+ function maisRecente(lista) {
20
+ let melhor = null;
21
+ for (const t of lista) {
22
+ // `atualizado_em` é AAAA-MM-DD (R4): comparação lexicográfica é cronológica
23
+ if (melhor === null || t.atualizado_em > melhor.atualizado_em)
24
+ melhor = t;
25
+ }
26
+ return melhor;
27
+ }
28
+ export function escolherTrabalho(trabalhos, estado, pedido) {
29
+ const por = (id) => trabalhos.find((t) => t.trabalho_id === id) ?? null;
30
+ // 1. `expx watch <trabalho_id>`: a pessoa mandou, acabou a discussão.
31
+ if (pedido !== undefined && pedido !== "")
32
+ return por(pedido);
33
+ // 2. O estado.json, quando pôde ser lido.
34
+ if (estado?.trabalho != null) {
35
+ const doEstado = por(estado.trabalho);
36
+ // O estado pode apontar um trabalho que não está no disco observado
37
+ // (pasta apagada, outro repositório). Nesse caso a derivação assume.
38
+ if (doEstado !== null)
39
+ return doEstado;
40
+ }
41
+ // 3. Derivação do plano (D-05): entre os em andamento, o mais recente.
42
+ const emAndamento = trabalhos.filter((t) => t.status === "em_andamento");
43
+ if (emAndamento.length > 0)
44
+ return maisRecente(emAndamento);
45
+ // Nenhum em andamento: o mais recente entre os abertos ainda é melhor
46
+ // resposta que nada — um trabalho bloqueado é exatamente o que a pessoa
47
+ // precisa ver.
48
+ const aberto = maisRecente(abertos(trabalhos));
49
+ if (aberto !== null)
50
+ return aberto;
51
+ // Nada aberto, mas existe trabalho concluído? Mostramos o mais recente.
52
+ // "Nenhum trabalho aberto" (D-19) é sobre a LISTA de acompanhamento; abrir o
53
+ // watch num projeto cujo único trabalho acabou de fechar e ver a tela vazia
54
+ // seria esconder justamente o que a pessoa quer conferir.
55
+ return maisRecente(trabalhos);
56
+ }
57
+ //# sourceMappingURL=escolher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escolher.js","sourceRoot":"","sources":["../../../src/watch/visao/escolher.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,SAAqC;IAC3D,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;AAC3D,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,KAAiC;IACpD,IAAI,MAAM,GAA2B,IAAI,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,4EAA4E;QAC5E,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa;YAAE,MAAM,GAAG,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAqC,EACrC,MAAyB,EACzB,MAAe;IAEf,MAAM,GAAG,GAAG,CAAC,EAAU,EAA0B,EAAE,CACjD,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;IAEtD,sEAAsE;IACtE,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC;IAE9D,0CAA0C;IAC1C,IAAI,MAAM,EAAE,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,oEAAoE;QACpE,qEAAqE;QACrE,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAC;IACzC,CAAC;IAED,uEAAuE;IACvE,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC;IACzE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;IAE5D,sEAAsE;IACtE,wEAAwE;IACxE,eAAe;IACf,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAEnC,wEAAwE;IACxE,6EAA6E;IAC7E,4EAA4E;IAC5E,0DAA0D;IAC1D,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { type BloqueioSituado, type TrabalhoMontado } from "../../parser/projeto/montar.js";
2
+ import type { LinhaEvento } from "../../parser/esquema/evento.js";
3
+ import type { EstadoExpx } from "../fontes/estado-schema.js";
4
+ /**
5
+ * A visão que o desenho consome — as três fontes reunidas numa estrutura só.
6
+ *
7
+ * O parser do painel é reaproveitado inteiro, sem refatoração: `montarProjeto`
8
+ * é função pura de sistema de arquivos, sem acoplamento a HTTP ou websocket
9
+ * (base/parser-de-artefatos.md, decisão D-02). O que se acrescenta aqui é o
10
+ * que ele não sabe: o `estado.json` e o rastro.
11
+ */
12
+ export type Visao = {
13
+ /** O trabalho que o watch está seguindo, ou `null` se não há nenhum. */
14
+ trabalho: TrabalhoMontado | null;
15
+ /** Os trabalhos abertos, para `--todos` e para a tela de "nenhum trabalho". */
16
+ abertos: TrabalhoMontado[];
17
+ /** O `estado.json`, ou `null` quando ausente, inválido ou de versão futura. */
18
+ estado: EstadoExpx | null;
19
+ /**
20
+ * Estamos sem o `estado.json`?
21
+ *
22
+ * NÃO é "o arquivo não existe": a decisão D-12 trata versão ≠ 1 e JSON
23
+ * inválido do mesmo jeito. No modo degradado, `raio`, `orcamento_*`, `branch`
24
+ * e `pr_estado` não têm fonte alguma e não podem ser exibidos — nenhum kind
25
+ * do `expx-schema` os declara (base/estado-json.md, risco 2).
26
+ */
27
+ degradado: boolean;
28
+ /** Tasks concluídas e total, do estado quando há, do plano quando não há. */
29
+ concluidas: number;
30
+ total: number;
31
+ /** Só os bloqueios abertos, que são os que sobem ao topo. */
32
+ bloqueiosAbertos: BloqueioSituado[];
33
+ /** As últimas linhas do rastro, da mais recente para a mais antiga. */
34
+ eventos: LinhaEvento[];
35
+ /** Violações em modo aviso desde que o watch subiu (decisão D-07). */
36
+ violacoesAviso: number;
37
+ /** Instante da leitura, para o rodapé calcular "tempo desde o último evento". */
38
+ lidoEm: Date;
39
+ };
40
+ export type OpcoesVisao = {
41
+ /** Id pedido em `expx watch <trabalho_id>`. */
42
+ trabalhoPedido?: string;
43
+ /**
44
+ * Quando o watch subiu. É a fronteira da "sessão" no rodapé: o rastro é
45
+ * append-only e não marca sessão nenhuma (lacuna L-03, decisão D-07).
46
+ */
47
+ subiuEm?: Date;
48
+ /** Quantas linhas do rastro exibir. */
49
+ linhasRastro?: number;
50
+ };
51
+ export declare function projetarVisao(raizProjeto: string, op?: OpcoesVisao, agora?: Date): Visao;
@@ -0,0 +1,38 @@
1
+ import { montarProjeto } from "../../parser/projeto/montar.js";
2
+ import { lerEstadoExpx } from "../fontes/estado.js";
3
+ import { lerRastro } from "../fontes/rastro.js";
4
+ import { abertos, escolherTrabalho } from "./escolher.js";
5
+ export function projetarVisao(raizProjeto, op = {}, agora = new Date()) {
6
+ const projeto = montarProjeto(raizProjeto, agora);
7
+ const estado = lerEstadoExpx(raizProjeto);
8
+ const trabalho = escolherTrabalho(projeto.trabalhos, estado, op.trabalhoPedido);
9
+ const tasks = trabalho?.sprints.flatMap((s) => s.tasks) ?? [];
10
+ // O par concluídas/total tem duas fontes: o estado.json traz os inteiros
11
+ // prontos, e o plano exige contagem — `progresso` do parser é fração 0..1,
12
+ // não o par (base/parser-de-artefatos.md, risco 4).
13
+ const doEstado = estado !== null && trabalho !== null;
14
+ const concluidas = doEstado
15
+ ? estado.tasks_concluidas
16
+ : tasks.filter((t) => t.status === "concluida").length;
17
+ const total = doEstado ? estado.tasks_total : tasks.length;
18
+ const eventos = trabalho === null ? [] : lerRastro(raizProjeto, trabalho.trabalho_id, op.linhasRastro);
19
+ // "Modo aviso" é vocabulário de hook e só existe no rastro: `regra_violada` é
20
+ // gravado por hook em modo aviso, `acao_bloqueada` em modo bloqueio. Não
21
+ // confundir com as `Violacao` da conformidade, que são defeitos do método nos
22
+ // arquivos do plano (lacuna L-03).
23
+ const fronteira = op.subiuEm?.toISOString().replace(/\.\d{3}Z$/, "Z");
24
+ const violacoesAviso = eventos.filter((e) => e.evento === "regra_violada" && (fronteira === undefined || e.ts >= fronteira)).length;
25
+ return {
26
+ trabalho,
27
+ abertos: abertos(projeto.trabalhos),
28
+ estado,
29
+ degradado: estado === null,
30
+ concluidas,
31
+ total,
32
+ bloqueiosAbertos: (trabalho?.bloqueios ?? []).filter((b) => b.aberto),
33
+ eventos,
34
+ violacoesAviso,
35
+ lidoEm: agora,
36
+ };
37
+ }
38
+ //# sourceMappingURL=projetar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projetar.js","sourceRoot":"","sources":["../../../src/watch/visao/projetar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA8C,MAAM,gCAAgC,CAAC;AAG3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAoD1D,MAAM,UAAU,aAAa,CAC3B,WAAmB,EACnB,KAAkB,EAAE,EACpB,QAAc,IAAI,IAAI,EAAE;IAExB,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC;IAEhF,MAAM,KAAK,GAAG,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAE9D,yEAAyE;IACzE,2EAA2E;IAC3E,oDAAoD;IACpD,MAAM,QAAQ,GAAG,MAAM,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,CAAC;IACtD,MAAM,UAAU,GAAG,QAAQ;QACzB,CAAC,CAAC,MAAM,CAAC,gBAAgB;QACzB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IAE3D,MAAM,OAAO,GACX,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IAEzF,8EAA8E;IAC9E,yEAAyE;IACzE,8EAA8E;IAC9E,mCAAmC;IACnC,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CACnC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,eAAe,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,IAAI,SAAS,CAAC,CACtF,CAAC,MAAM,CAAC;IAET,OAAO;QACL,QAAQ;QACR,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;QACnC,MAAM;QACN,SAAS,EAAE,MAAM,KAAK,IAAI;QAC1B,UAAU;QACV,KAAK;QACL,gBAAgB,EAAE,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QACrE,OAAO;QACP,cAAc;QACd,MAAM,EAAE,KAAK;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Opcoes } from "./opcoes.js";
2
+ /**
3
+ * O loop do watch: lê, desenha, observa, redesenha.
4
+ *
5
+ * A promessa que este arquivo existe para cumprir: "a árvore completa vem dos
6
+ * arquivos do plano, relida apenas quando eles mudam — não a cada redesenho".
7
+ * Por isso a visão é guardada e só o gatilho de plano a reconstrói; o gatilho
8
+ * de estado atualiza o barato e redesenha com a árvore que já estava em mãos.
9
+ */
10
+ export type SessaoWatch = {
11
+ parar: () => Promise<void>;
12
+ /** Código de saída, quando o watch terminou sozinho. */
13
+ codigo: number;
14
+ /** O watch encerrou por conta própria (projeto sem Expx)? */
15
+ encerrou: boolean;
16
+ };
17
+ export type OpcoesExecucao = {
18
+ raiz: string;
19
+ opcoes: Opcoes;
20
+ escrever: (texto: string) => void;
21
+ cor: boolean;
22
+ debounceMs?: number;
23
+ /** Gancho de teste: chamado toda vez que o PLANO é relido do disco. */
24
+ aoLerPlano?: () => void;
25
+ };
26
+ export declare function executarWatch(op: OpcoesExecucao): Promise<SessaoWatch>;