laiss-docs 1.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of laiss-docs might be problematic. Click here for more details.

@@ -0,0 +1,594 @@
1
+ Metadata-Version: 2.4
2
+ Name: laiss-docs
3
+ Version: 1.1.0
4
+ Summary: Documentos Laiss Labs em A4, a partir de JSON. Sistema de design, motor e CLI.
5
+ Author-email: Júlio Papel <julio@laisslabs.com>
6
+ Maintainer: Laiss Labs
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/JulioPapel/laiss-docs
9
+ Project-URL: Documentation, https://github.com/JulioPapel/laiss-docs/tree/main/docs
10
+ Project-URL: Issues, https://github.com/JulioPapel/laiss-docs/issues
11
+ Project-URL: Changelog, https://github.com/JulioPapel/laiss-docs/blob/main/CHANGELOG.md
12
+ Keywords: latex,lualatex,pdf,documentation,mcp,design-system
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Documentation
27
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.9
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Provides-Extra: dev
33
+ Requires-Dist: build>=1.0; extra == "dev"
34
+ Requires-Dist: twine>=5.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # laiss-docs
38
+
39
+ Documentos da Laiss Labs em A4, a partir de JSON. O sistema de design do guia
40
+ de contribuição, extraído para um pacote que se instala em qualquer máquina e
41
+ que um agente AI pode usar por MCP.
42
+
43
+ ```bash
44
+ pip install laiss-docs # instala o programa (5 MB)
45
+ laiss-docs build handbook # a primeira vez instala o que falta
46
+ ```
47
+
48
+ ```bash
49
+ laiss-docs doctor # o que falta nesta máquina
50
+ laiss-docs deps --dry-run # o que falta, e como se instala
51
+ laiss-docs new handbook --lang en-US # criar
52
+ laiss-docs build handbook # compilar e verificar
53
+ laiss-docs preview handbook # abrir
54
+ ```
55
+
56
+ Saída: `handbook/out/handbook.pdf`.
57
+
58
+ ---
59
+
60
+ ## O que isto é
61
+
62
+ Um conjunto de três peças que não se confundem:
63
+
64
+ | Peça | O que é | Onde |
65
+ |---|---|---|
66
+ | **Design** | `laissdoc.sty` + `cover.tex`. Tipografia, paleta, caixas, cabeçalhos. | `laissdocs/tex/` |
67
+ | **Motor** | `config.py`. Traduz `doc.json` em LaTeX. Escapa tudo. | `laissdocs/` |
68
+ | **Interface** | CLI, e um servidor MCP para agentes. | `laissdocs/cli.py`, `mcp.py` |
69
+
70
+ A regra: **o design não se edita num documento.** Se um documento precisa de um
71
+ aspecto diferente, ou é um parâmetro do `doc.json`, ou é uma alteração ao `.sty`
72
+ que beneficia todos. Um documento que foge ao sistema perde a razão de existir.
73
+
74
+ ## Instalação
75
+
76
+ ### Com pip
77
+
78
+ ```bash
79
+ pip install laiss-docs
80
+ ```
81
+
82
+ A primeira vez que correres um comando que precisa de compilar, o programa
83
+ verifica o que falta na máquina e instala o que souber instalar. Pergunta
84
+ antes de mexer no sistema, e diz o que vai fazer:
85
+
86
+ ```
87
+ $ laiss-docs build relatorio
88
+ Faltam dependencias do sistema para compilar.
89
+
90
+ ✗ LuaLaTeX em falta
91
+ e o motor de composicao; sem ele nao ha PDF
92
+ $ sudo apt-get install -y texlive-latex-extra texlive-fonts-extra
93
+ (pede a password de root)
94
+ ✗ Poppler em falta
95
+ falta pdftotext, pdfinfo
96
+ $ sudo apt-get install -y poppler-utils
97
+ (pede a password de root)
98
+
99
+ A instalar automaticamente. Isto corre uma vez.
100
+ ```
101
+
102
+ Antes de instalar, vê o plano:
103
+
104
+ ```bash
105
+ laiss-docs deps --dry-run # o que falta e o que seria corrido
106
+ laiss-docs deps # instalar, com confirmação
107
+ laiss-docs deps -y # instalar sem perguntar (CI)
108
+ ```
109
+
110
+ Em CI, ou num sítio sem terminal, **nada é instalado** sem
111
+ `LAISSDOCS_AUTO_INSTALL=1`. Um build não deve começar a escrever no sistema
112
+ porque lhe apeteceu.
113
+
114
+ O que o pip **não** instala, e porquê: o LuaLaTeX, o poppler, o Graphviz e o
115
+ mermaid-cli não são pacotes Python. Um pacote Python que tentasse instalá-los
116
+ seria um pacote que corre `sudo` às escondidas. O `deps` faz isso à vista.
117
+
118
+ ### Requisitos
119
+
120
+ - **Python 3.9+** — sem dependências externas, só a biblioteca padrão.
121
+ - **LuaLaTeX** — TeX Live, MacTeX ou MiKTeX. *Não* é opcional, e *não* pode
122
+ ser substituído por Tectonic (ver [Porquê LuaLaTeX](docs/porque-lualatex.md)).
123
+ - **poppler-utils** (`pdftotext`, `pdfinfo`) — para as verificações.
124
+
125
+ O `laiss-docs deps` instala estes três em Debian, Ubuntu, Arch, Fedora,
126
+ openSUSE, Alpine e macOS. No NixOS diz o que pôr no `shell.nix`, porque
127
+ instalar imperativamente numa máquina Nix é a coisa errada a fazer.
128
+
129
+ ### Sem pip
130
+
131
+ ```bash
132
+ git clone <este-repo> && cd laiss-docs
133
+ ./bin/laiss-docs doctor
134
+ ```
135
+
136
+ Funciona a partir do checkout, sem instalar nada.
137
+
138
+ ### O que o `doctor` verifica
139
+
140
+ ```bash
141
+ laiss-docs doctor
142
+ ```
143
+
144
+ ```
145
+ laiss-docs 1.1.0
146
+ instalado em /home/juliopapel/.local/share/laiss-docs
147
+
148
+ ✓ python3 3.15.2
149
+ ✓ lualatex This is LuaHBTeX, Version 1.25.7
150
+ ✓ pdftotext
151
+ ✓ pdfinfo
152
+ ✓ Inter
153
+ ✓ JetBrains Mono
154
+ ✓ pacotes LaTeX todos os ficheiros do estilo
155
+ ✓ idiomas LaTeX 5 idiomas
156
+ ```
157
+
158
+ A verificação dos pacotes pergunta ao `kpsewhich`, que é o mesmo resolvedor
159
+ que o `lualatex` usa: se ele não encontra um ficheiro, a compilação também
160
+ não. Custa 0,1 s, em vez dos segundos de uma sonda compilada. A sonda
161
+ continua a existir para as máquinas sem `kpsewhich`.
162
+
163
+ Se `doctor` disser que faltam as fontes, corre `laiss-docs init`. As fontes
164
+ vão dentro do pacote (~10 MB), por isso funciona sem rede.
165
+
166
+ ## Uso
167
+
168
+ ### Criar um documento
169
+
170
+ ```bash
171
+ laiss-docs new relatorio-tecnico \
172
+ --title "Relatório Técnico" \
173
+ --subtitle "Estado do motor de CMS em Novembro de 2026" \
174
+ --lang pt-PT
175
+ ```
176
+
177
+ Cria:
178
+
179
+ ```
180
+ relatorio-tecnico/
181
+ ├── doc.json metadados e estrutura
182
+ ├── src/
183
+ │ ├── main.tex gerado
184
+ │ ├── cover.tex copiado da instalação
185
+ │ ├── laissdoc.sty symlink para a instalação
186
+ │ ├── laissconfig.tex gerado a partir do doc.json
187
+ │ ├── part01.tex ← escreves aqui
188
+ │ └── colophon.tex
189
+ └── out/ o PDF
190
+ ```
191
+
192
+ **Escreves em `part*.tex` e em `doc.json`. Nunca em `main.tex`,
193
+ `laissconfig.tex` ou `laissdoc.sty`** — são gerados, e o `build` escreve por
194
+ cima.
195
+
196
+ ### Compilar
197
+
198
+ ```bash
199
+ laiss-docs build relatorio-tecnico
200
+ ```
201
+
202
+ ```
203
+ ✓ pages 12 paginas
204
+ ✓ a4 A4
205
+ ✓ text-layer sem glifos colados
206
+ ✓ refs todas as referencias resolvidas
207
+ ✓ toc 47 entradas conferidas
208
+ ```
209
+
210
+ Se alguma verificação falhar, o comando sai com código 1. É isso que o torna
211
+ utilizável em CI.
212
+
213
+ ### Verificações, e porque existem
214
+
215
+ | Verificação | O que apanha | Porque existe |
216
+ |---|---|---|
217
+ | `pages` | PDF vazio ou truncado | Um `\input` em falta produz um PDF de 1 página sem aviso |
218
+ | `a4` | Formato errado | Um `\documentclass` errado passa despercebido |
219
+ | `text-layer` | Glifos colados | As ligaduras do JetBrains Mono transformam `https://` em `https:^//` no texto copiado |
220
+ | `refs` | `??` no PDF | Uma referência por resolver compila sem erro |
221
+ | `toc` | Índice desalinhado | Entradas com número de página errado parecem bem até se tentar navegar |
222
+
223
+ A verificação `text-layer` é a mais importante e a menos óbvia. Um PDF pode
224
+ parecer perfeito no ecrã e ter comandos corrompidos quando alguém os copia. Num
225
+ documento técnico, isso é o defeito mais caro possível.
226
+
227
+ ### Uma excepção declarada: `allowTextLayer`
228
+
229
+ Há um caso em que um circunflexo no texto extraído é intencional: um documento
230
+ que **documente o próprio bug das ligaduras**. Nesse caso, declara o trecho:
231
+
232
+ ```json
233
+ { "allowTextLayer": ["https:^//github.com"] }
234
+ ```
235
+
236
+ A verificação continua a correr para tudo o resto. Sem esta opção, a única
237
+ alternativa seria desligar a verificação — e uma verificação que se desliga
238
+ quando incomoda não serve para nada.
239
+
240
+ ### Títulos e o colofão
241
+
242
+ Os títulos **não são hifenizados**. O CLI mede a largura real de cada palavra
243
+ com a fonte que vai ser usada (Inter Bold a 40pt na capa) e parte o título só
244
+ onde cabe — nunca a meio de uma palavra. Contar caracteres não servia: um
245
+ título de 22 caracteres pode medir 489pt numa área de 461pt.
246
+
247
+ O colofão final está **desligado por omissão**: o documento termina na última
248
+ secção. Para o incluir, `"colophon": true` no `doc.json`.
249
+
250
+ ## Resumo
251
+
252
+ Página entre o índice e o capítulo 1, sem numeração, com entrada no índice.
253
+
254
+ ```json
255
+ {"abstract": [{"type": "p", "text": "O que este documento trata."}],
256
+ "abstractTitle": "Resumo"}
257
+ ```
258
+
259
+ Ou por MCP: `laiss_set_abstract`, `laiss_get_abstract`, `laiss_clear_abstract`.
260
+
261
+ Acrescentar um resumo **não toca nas secções**: o texto já escrito fica
262
+ exactamente como estava. Um resumo é um campo do documento, não uma secção, e
263
+ não entra na numeração dos capítulos.
264
+
265
+ ## Figuras, diagramas e anexos
266
+
267
+ Três formas de figura, todas com `caption` obrigatória:
268
+
269
+ ```json
270
+ {"type": "mermaid", "caption": "Pipeline", "source": "flowchart LR\n A[Texto] --> B[IDs]"}
271
+ {"type": "graphviz", "caption": "Dependências", "rankdir": "LR", "source": "A -> B;"}
272
+ {"type": "chart", "caption": "Tokens por frase", "labels": ["PT","EN"], "values": [77,62]}
273
+ {"type": "figure", "path": "imagens/painel.png", "caption": "O painel"}
274
+ ```
275
+
276
+ - **`mermaid`** — fluxogramas, sequências, estados, gantt. Precisa de
277
+ `@mermaid-js/mermaid-cli` e de um browser headless.
278
+ - **`graphviz`** — grafos grandes, vectorial. Precisa de `dot`.
279
+ - **`chart`** — barras horizontais em TikZ, a partir de números. **Não precisa
280
+ de nada externo** e funciona sempre.
281
+ - **`figure`** — SVG, PNG, JPEG ou PDF que já exista.
282
+
283
+ `laiss-docs mcp` expõe `laiss_diagrams`, que diz o que está instalado antes de
284
+ o agente escrever um bloco que não vai compilar.
285
+
286
+ O índice de figuras é gerado automaticamente quando há figuras
287
+ (`"listOfFigures": false` desliga).
288
+
289
+ ### Anexos
290
+
291
+ Numerados por letra: ANEXO A, B. **A lista de referências não é um anexo**:
292
+ sai como página "Referências", sem letra, e não gasta a letra A. Um anexo pode
293
+ conter qualquer bloco.
294
+
295
+ ```json
296
+ {"title": "Referências", "blocks": [
297
+ {"type": "refs", "items": [
298
+ {"text": "Sennrich, R. (2016). Neural Machine Translation…",
299
+ "url": "https://arxiv.org/abs/1508.07909",
300
+ "note": "A origem do BPE."}
301
+ ]}
302
+ ]}
303
+ ```
304
+
305
+ ### O travessão
306
+
307
+ **Não uses o travessão longo entre frases.** Modelos de linguagem fazem-no por
308
+ hábito. O motor substitui por vírgula, mas o resultado automático é sempre pior
309
+ do que uma frase escrita de raiz. Ver [escrever.md](escrever.md).
310
+
311
+ ## Idiomas
312
+
313
+ ```bash
314
+ laiss-docs locales
315
+ ```
316
+
317
+ | Código | Idioma | Aliases |
318
+ |---|---|---|
319
+ | `pt-PT` | Português (Portugal) | `pt`, `pt_BR`, `portuguese` |
320
+ | `en-US` | English (US) | `en`, `english` |
321
+ | `es-ES` | Español (España) | `es`, `spanish` |
322
+ | `fr-FR` | Français (France) | `fr`, `french` |
323
+ | `de-DE` | Deutsch (Deutschland) | `de`, `german` |
324
+
325
+ O idioma muda: a hifenização, a data por extenso (`5 de Novembro de 2026` /
326
+ `November 5, 2026` / `5 novembre 2026`), o título do índice, as etiquetas da
327
+ capa (`DE`/`PARA` → `FROM`/`TO` → `VON`/`AN`) e os títulos por omissão das
328
+ caixas (`Nota`/`Atenção` → `Hinweis`/`Achtung`).
329
+
330
+ **O que o idioma não muda:** o design. Todos os documentos têm a mesma
331
+ tipografia, a mesma paleta, a mesma grelha. É esse o ponto.
332
+
333
+ ### Acrescentar um idioma
334
+
335
+ Em `laissdocs/locales.py`, copia um bloco de `LOCALES`, traduz os valores, e
336
+ acrescenta os acentos em `ACCENTS`. O teste `test_all_have_required_keys` diz-te
337
+ se faltou alguma chave.
338
+
339
+ ## Escrever o corpo
340
+
341
+ Ver [escrever.md](escrever.md) para a referência completa.
342
+
343
+ ```latex
344
+ \partlabel{PARTE 1}
345
+ \section{O ciclo de trabalho}
346
+
347
+ Texto corrido. Uma interface: \ui{Botões} \uiarrow\ \ui{Definições}.
348
+ Um caminho: \fpath{src/main.tex}. Código inline: \code{git push}.
349
+
350
+ \begin{nota}[O que isto significa]
351
+ Caixa azul, informação.
352
+ \end{nota}
353
+
354
+ \begin{atencao}[Isto parte coisas]
355
+ Caixa laranja, risco.
356
+ \end{atencao}
357
+
358
+ \begin{cmds}[terminal]
359
+ git status
360
+ git diff
361
+ \end{cmds}
362
+ ```
363
+
364
+ ## Agentes AI, por MCP
365
+
366
+ O servidor expõe 22 ferramentas desenhadas para um agente que não conhece o
367
+ sistema. A primeira que deve chamar é `laiss_guide`: explica o ciclo, todos os
368
+ tipos de bloco, a marcação permitida e os erros comuns.
369
+
370
+ ```bash
371
+ laiss-docs mcp # JSON-RPC sobre stdio
372
+ ```
373
+
374
+ No Cursor (`.cursor/mcp.json`):
375
+
376
+ ```json
377
+ {
378
+ "mcpServers": {
379
+ "laiss-docs": {
380
+ "command": "laiss-docs",
381
+ "args": ["mcp"]
382
+ }
383
+ }
384
+ }
385
+ ```
386
+
387
+ | Ferramenta | Para quê |
388
+ |---|---|
389
+ | `laiss_guide` | **Chamar primeiro.** O processo inteiro, em texto |
390
+ | `laiss_create` | Cria o documento e devolve o caminho |
391
+ | `laiss_outline` | Índice: ids, títulos, contagem de blocos |
392
+ | `laiss_read_section` | Conteúdo de uma secção, bloco a bloco |
393
+ | `laiss_add_section` | Nova secção, opcionalmente já com blocos |
394
+ | `laiss_add_blocks` | Acrescenta blocos a uma secção |
395
+ | `laiss_update_block` | Substitui um bloco pelo número |
396
+ | `laiss_remove_block` | Remove um bloco |
397
+ | `laiss_remove_section` | Remove uma secção |
398
+ | `laiss_update_section` | Muda título ou etiqueta |
399
+ | `laiss_move_section` | Reordena |
400
+ | `laiss_set_abstract` | Escreve o resumo (não toca nas secções) |
401
+ | `laiss_get_abstract` | Lê o resumo |
402
+ | `laiss_clear_abstract` | Remove o resumo |
403
+ | `laiss_add_appendix` | Novo anexo, com etiqueta por letra |
404
+ | `laiss_add_appendix_blocks` | Acrescenta blocos a um anexo |
405
+ | `laiss_remove_appendix` | Remove um anexo |
406
+ | `laiss_build` | Compila e verifica |
407
+ | `laiss_check` | Verifica sem recompilar |
408
+ | `laiss_schema` | JSON Schema do `doc.json` |
409
+ | `laiss_locales` | Idiomas |
410
+ | `laiss_diagrams` | O que está instalado para diagramas |
411
+
412
+ ### A decisão que faz isto funcionar
413
+
414
+ **O agente não escreve LaTeX. Escreve JSON.**
415
+
416
+ Um modelo de linguagem não sabe que um `_` dentro de ` path{}` parte o
417
+ compilador. Num documento, três builds falharam por isso — e o erro aparecia
418
+ longe da causa. Com blocos JSON, o modelo escolhe a estrutura e o texto; o motor
419
+ compõe. O formato deixa de ser responsabilidade de quem escreve.
420
+
421
+ ```json
422
+ {"type": "p", "text": "Um parágrafo com **forte** e `código`."}
423
+ {"type": "warn", "title": "Cuidado", "text": "Isto apaga trabalho."}
424
+ {"type": "code", "label": "terminal", "text": "git status"}
425
+ ```
426
+
427
+ Ver [escrever.md](escrever.md) para a referência, e `laiss_guide` para a versão
428
+ que o agente lê.
429
+
430
+ ## IA remota por endpoint
431
+
432
+ Gera o conteúdo com um modelo, por endpoint compatível com a API de chat
433
+ completions (OpenAI, Ollama, LM Studio, vLLM, llama.cpp).
434
+
435
+ ```bash
436
+ export LAISSDOCS_BASE_URL="http://localhost:1234/v1" # LM Studio
437
+ export LAISSDOCS_MODEL="laiss-tva-26-1-qat"
438
+ export LAISSDOCS_API_KEY="sk-lm-..." # se o servidor exigir
439
+
440
+ laiss-docs ai # testa a ligação
441
+ laiss-docs new estudo --brief "O que quero estudar" --sections 4
442
+ laiss-docs generate estudo --section introducao --instruction "acrescenta um exemplo"
443
+ ```
444
+
445
+ Precedência da configuração: **argumento > ambiente > `doc.json` > omissão**.
446
+ O ambiente ganha ao `doc.json` para poderes comparar modelos sem editar o
447
+ ficheiro.
448
+
449
+ O que o modelo devolve é validado antes de entrar no documento. Se um bloco vier
450
+ mal formado, o erro do validador volta para a conversa e o modelo corrige — até
451
+ três tentativas. Um erro que o validador não explica é um erro que o utilizador
452
+ tem de resolver à mão.
453
+
454
+ ## Estrutura do repositório
455
+
456
+ As quinze peças, por camada. A regra é que cada uma responde a uma pergunta,
457
+ e nenhuma sabe da camada de cima.
458
+
459
+ ```
460
+ laissdocs/
461
+ ├── tex/ o DESIGN
462
+ │ ├── laissdoc.sty o estilo inteiro: tipografia, paleta, caixas
463
+ │ └── cover.tex a capa
464
+ ├── assets/ os RECURSOS
465
+ │ ├── fonts/ Inter + JetBrains Mono (embutidas, ~10 MB)
466
+ │ └── mark.pdf a marca
467
+ │
468
+ ├── content.py o CONTEÚDO
469
+ │ blocos JSON → LaTeX. Escapa tudo. Valida antes
470
+ │ de compor: um bloco mal formado falha aqui, com
471
+ │ o caminho até ele, e não a meio do LaTeX.
472
+ ├── locales.py os IDIOMAS
473
+ │ nomes de meses, etiquetas da capa, título do
474
+ │ índice, pacote babel de cada língua.
475
+ ├── metrics.py as MEDIDAS
476
+ │ largura real de um texto na fonte final. É o
477
+ │ que impede um título de partir a meio.
478
+ │
479
+ ├── config.py a PONTE
480
+ │ doc.json → laissconfig.tex. É a fronteira entre
481
+ │ o que se escreve e o que se compõe.
482
+ ├── document.py a ESTRUTURA
483
+ │ editar o documento por operações (acrescentar,
484
+ │ mover, remover) em vez de reescrever o ficheiro.
485
+ ├── diagrams.py as FIGURAS
486
+ │ Mermaid, Graphviz e imagens. Cache por conteúdo.
487
+ │ Sabe o que está instalado antes de prometer.
488
+ │
489
+ ├── build.py a COMPILAÇÃO
490
+ │ LuaLaTeX três vezes, e cinco verificações no PDF
491
+ │ já produzido, não só no código de saída.
492
+ ├── install.py as DEPENDÊNCIAS
493
+ │ o que falta no sistema, como se instala nesta
494
+ │ distribuição, e a diferença entre saber e não
495
+ │ saber (devolve `None`, nunca uma lista vazia).
496
+ ├── paths.py os CAMINHOS
497
+ │ onde vivem as fontes e os estilos. Uma correcção
498
+ │ ao design chega a todos os documentos.
499
+ │
500
+ ├── cli.py a INTERFACE
501
+ │ quinze comandos. Todos aceitam --json.
502
+ ├── mcp.py a INTERFACE PARA AGENTES
503
+ │ vinte e duas ferramentas MCP sobre stdio, escritas à
504
+ │ mão em JSON-RPC 2.0.
505
+ ├── guide.py o GUIA
506
+ │ o texto que o agente lê primeiro: o processo
507
+ │ inteiro, sem assumir LaTeX.
508
+ ├── ai.py a IA REMOTA
509
+ │ cliente de chat completions em urllib. Pede
510
+ │ blocos JSON, não prosa.
511
+ └── __main__.py `python -m laissdocs`
512
+ ```
513
+
514
+ ### As camadas, e porque estão separadas
515
+
516
+ | Camada | Pergunta que responde | Módulos |
517
+ |---|---|---|
518
+ | **Design** | Como é que isto se parece? | `tex/`, `assets/` |
519
+ | **Conteúdo** | O que é que isto diz? | `content.py`, `locales.py`, `metrics.py` |
520
+ | **Estrutura** | Como é que isto se organiza? | `document.py`, `config.py`, `diagrams.py` |
521
+ | **Motor** | Como é que isto se compõe? | `build.py`, `install.py`, `paths.py` |
522
+ | **Interface** | Como é que alguém fala com isto? | `cli.py`, `mcp.py`, `guide.py`, `ai.py` |
523
+
524
+ A separação não é decorativa: é o que permite que o **conteúdo** (JSON) não
525
+ saiba que existe LaTeX, e que o **design** não saiba que documentos existem.
526
+ Um documento novo não toca em nenhuma das duas.
527
+
528
+ ## Desenvolvimento
529
+
530
+ ```bash
531
+ python3 -m unittest discover -s tests -v
532
+ ```
533
+
534
+ 163 testes, cerca de 16 segundos, quase todos em compilações reais. Os que
535
+ precisam de LuaLaTeX saltam se ele não estiver instalado.
536
+
537
+ | Ficheiro | Testes | O que protege |
538
+ |---|---|---|
539
+ | `test_content.py` | 135 | Blocos, escaping, legendas, figuras, instalação |
540
+ | `test_laissdocs.py` | 29 | Compilação, verificações, MCP, idiomas |
541
+ | `test_install.py` | 28 | Plano de dependências, comandos por distro, sudo, marcador |
542
+
543
+ Depois de mudar o `.sty` ou a capa:
544
+
545
+ ```bash
546
+ laiss-docs init # instala a nova versão
547
+ laiss-docs build <doc> # ressincroniza o documento e compila
548
+ ```
549
+
550
+ ### Publicar uma versão
551
+
552
+ A versão tem uma fonte só: `laissdocs/__init__.py`. O `pyproject.toml` lê-a
553
+ de lá. Duas cópias divergem sempre, e já divergiram: o pacote saiu como 1.1.0
554
+ e o CLI continuou a dizer 1.0.0.
555
+
556
+ ```bash
557
+ # 1. subir a versão
558
+ $EDITOR laissdocs/__init__.py
559
+
560
+ # 2. verificar o que sai
561
+ python -m build
562
+ twine check dist/*
563
+
564
+ # 3. provar num ambiente limpo antes de publicar
565
+ python -m venv /tmp/prova
566
+ /tmp/prova/bin/pip install dist/*.whl
567
+ /tmp/prova/bin/laiss-docs doctor
568
+
569
+ # 4. publicar
570
+ twine upload dist/*
571
+ ```
572
+
573
+ A prova em ambiente limpo não é opcional. É a única forma de saber que o
574
+ pacote está completo: um ficheiro de dados que fica fora do wheel não aparece
575
+ no `twine check`, e só se descobre quando alguém instala e o comando falha
576
+ com um ficheiro de estilo em falta.
577
+
578
+ ## Limites conhecidos
579
+
580
+ - **Só A4 retrato.** Paisagem e formatos maiores não estão previstos. Um
581
+ documento que precise disso é outro sistema.
582
+ - **O corpo é LaTeX, não Markdown.** Deliberado: quem escreve precisa de caixas,
583
+ de diagramas e de blocos de código com etiqueta. Markdown não chega, e o
584
+ conversor para LaTeX é onde os sistemas destes perdem a tipografia.
585
+ - **Sem índice remissivo nem bibliografia.** Se um documento os pedir, é sinal
586
+ de que cresceu para lá do que isto serve.
587
+ - **As fontes estão embutidas.** ~10 MB no pacote. A alternativa era depender
588
+ da rede na primeira compilação, e um documento que não compila offline não é
589
+ um documento.
590
+
591
+ ## Licença
592
+
593
+ MIT, Laiss Labs. As fontes mantêm as suas licenças: Inter (OFL 1.1),
594
+ JetBrains Mono (OFL 1.1).
@@ -0,0 +1,33 @@
1
+ laiss_docs-1.1.0.dist-info/licenses/LICENSE,sha256=2_tCdrXKhSQAvKLqk9QyhtBL2Jkn_RicVzBzO69sQxQ,1067
2
+ laissdocs/__init__.py,sha256=mO_MCqPWHHXKyaH61xnyPNcVrxilQop5PO0tHk7zvu8,198
3
+ laissdocs/__main__.py,sha256=YXHccPCkqOzVD52GU7O7pjXKSTDpqJe7vQmr7rm-WLg,149
4
+ laissdocs/ai.py,sha256=3gG8oM2NOSVqeYsPCdL7gYG1304QWDu4iWma3CIqids,11622
5
+ laissdocs/build.py,sha256=yQLacrjfIiEFOvwXQKwKKdp30U9PW9cblHVlT7PkJDo,18214
6
+ laissdocs/cli.py,sha256=fM3lE1HAwC024vAydrGgUAz8jXtc4eI9E_x3Jv7D2Fk,42555
7
+ laissdocs/config.py,sha256=4O1G6NJDkMkS8bxO0VTyiqPbHydWYclt3N5LSSCGmAQ,16136
8
+ laissdocs/content.py,sha256=-Z3wAUSKEIqdGFAHi_kEqJ0ReO9PMkRAel0i97UB734,32095
9
+ laissdocs/diagrams.py,sha256=y0pCK4tFmA-E11lFd8Yb68Zp5oeoW5e5c7yLxcHnzV0,11474
10
+ laissdocs/document.py,sha256=KC5UqmvCVLC1bB2YHB9hOWY8_sooMKHQ3oxeaXbvusk,21363
11
+ laissdocs/guide.py,sha256=kC-3tWkJxl7vnRD128we6wkXXOzN2viTJBC5Cy1IHMg,7605
12
+ laissdocs/install.py,sha256=lcV2ZgnA8cbo3Oo7dFoPDyYGXIQ2HgpcGFPqRLn9gWg,17874
13
+ laissdocs/locales.py,sha256=CBWhIIx6S0dk2R6tdiA8OEkemk3MW2c5rwMybR5iRrs,6658
14
+ laissdocs/mcp.py,sha256=kpbex9QSh7ZSXTJhUNsfqwOiliTitmPsA6CSJm55b4Q,27241
15
+ laissdocs/metrics.py,sha256=yzLjR7o6p8eLknF-XWjDbLAngUfqQj1EYTsE1H79MX4,6666
16
+ laissdocs/paths.py,sha256=qsPcisPiZjd-jH9s_Dj1dftpxLmegKdYDXlZTATSYjY,3645
17
+ laissdocs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ laissdocs/assets/mark.pdf,sha256=Gl_jZg7D4Ma9o7UKQPcUtNVOG1dEN-5eH1WfMTe1eEU,10206
19
+ laissdocs/assets/fonts/Inter-Bold.otf,sha256=AP1vRpHuiISjO0a_RYUbNBknw3wwBk9VjhvvskKXHEk,626056
20
+ laissdocs/assets/fonts/Inter-BoldItalic.otf,sha256=DJ9zeuRMwZ3Slicz9uGgCqSlN2jvx92YzSnTYJ2zEd0,604316
21
+ laissdocs/assets/fonts/Inter-Italic.otf,sha256=8GXrFIQxonaZW6iLeekiXkqgXwWzv-uFfPBFfcwQZKk,586752
22
+ laissdocs/assets/fonts/Inter-Regular.otf,sha256=vm1wnctzDdrwUMva5v6JvVbV0U3GaIVjm79vBr0DUhs,605092
23
+ laissdocs/assets/fonts/JetBrainsMonoNerdFont-Bold.ttf,sha256=5JBmCtdeCxUsk7FgTC6hpOpnXxuKP-DQBbGZhWj5nx8,2575364
24
+ laissdocs/assets/fonts/JetBrainsMonoNerdFont-Italic.ttf,sha256=cdVGbMOrMb7ji04sXMmcK0GnScSXPtRXm4FOyZKwc44,2573684
25
+ laissdocs/assets/fonts/JetBrainsMonoNerdFont-Regular.ttf,sha256=HGgOjN6fz4uIpWBc6NH7lN0_sVhB98p79MVWZIVeVhE,2571596
26
+ laissdocs/assets/fonts/LICENSE.txt,sha256=eMID7QPrkTnVmnwc0rEul_p68mXxuEaivbI6fEd97rQ,470
27
+ laissdocs/tex/cover.tex,sha256=3XJ4hx9WXyiTU4K-QU8Z9d0EJBR403Otu3GC_0c2p9E,2990
28
+ laissdocs/tex/laissdoc.sty,sha256=rdnFOETdHPkl10ADZxg6EHFBC_sdEx6i_3EDBuWy13M,20633
29
+ laiss_docs-1.1.0.dist-info/METADATA,sha256=4SdoyNU-ySl2_ToFnu8Q69TmNeJm9Tr35f_a_YHeYVU,21918
30
+ laiss_docs-1.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
31
+ laiss_docs-1.1.0.dist-info/entry_points.txt,sha256=7q9NTTPDIxIlgkeQ6PhUfpLJJ9xOmLgJAqQy1UtqkfQ,50
32
+ laiss_docs-1.1.0.dist-info/top_level.txt,sha256=x4EH8afxWBwQfol-UK3ZHoEEWy_hpjo3tAZ92rbjmTY,10
33
+ laiss_docs-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ laiss-docs = laissdocs.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laiss Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ laissdocs
laissdocs/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """laiss-docs — gerador de documentos Laiss Labs.
2
+
3
+ Modulo principal. Sem dependencias externas: so a biblioteca padrao, para
4
+ correr em qualquer maquina com Python 3.9+.
5
+ """
6
+
7
+ __version__ = "1.1.0"
laissdocs/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Permite `python -m laissdocs` e `python -m laissdocs.cli`."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())