# ARTIFACT-GENERATION.md — Contrato de geração de artefatos SDD
Documentação técnica do pipeline unificado de geração de artefatos introduzido
pelas tasks T-01 a T-15 da orquestração
`geracao-mais-robusta-e-organizada-redesenhar-pipeline-de-geracao-de-artefatos-do-sd-7baa90d82a5a`.
---
## Visão geral
Antes do refactor (baseline T-01), o SDD tinha três funções paralelas de
renderização em `src/main.rs`:
- `render_stage_artifact_body` — renderizava o corpo Markdown de stages.
- `render_stage_artifact` — ponto de entrada do CLI para stages.
- `render_discovery_artifact_body` / `render_discovery_html_artifact` — discovery.
Cada uma duplicava lógica de rastreabilidade, telemetria e ordenação de seções.
O pipeline unificado centraliza esse contrato em `src/domain/artifact.rs` e
`src/domain/html.rs` (feature `html-gen`).
---
## Trait `ArtifactGenerator`
**Arquivo:** `src/domain/artifact.rs` (linha 98)
```rust
pub trait ArtifactGenerator {
fn generate_markdown(&self, ctx: &GenerationContext<'_>) -> Result<String>;
#[cfg(feature = "html-gen")]
fn generate_html(&self, ctx: &GenerationContext<'_>) -> Result<String>;
}
```
Contrato compartilhado por CLI e TUI (RF-12, RF-13). Qualquer gerador concreto
implementa `generate_markdown` (sempre disponível) e, quando a feature `html-gen`
está ativada, `generate_html`.
---
## `GenerationContext<'a>`
**Arquivo:** `src/domain/artifact.rs` (linha 69)
```rust
pub struct GenerationContext<'a> {
pub stage: &'a str, // ex: "idea", "prd", "tasks", "project-discovery"
pub name: &'a str, // nome da orquestração
pub input: &'a str, // entrada do usuário para o stage
pub root: &'a Path, // raiz do projeto
pub provider: &'a ProviderSelection,
pub format: ArtifactFormat, // Markdown (padrão) ou Html
pub real_telemetry: Option<Telemetry>, // telemetria real do provider; None = scaffolding local
pub no_telemetry: bool, // flag --no-telemetry
}
```
É o único objeto que os geradores recebem. Substitui os parâmetros avulsos que
cada função de renderização recebia antes do refactor.
---
## Quando telemetria real é emitida — `should_emit_telemetry`
**Arquivo:** `src/domain/artifact.rs` (linha 85)
```rust
pub fn should_emit_telemetry(&self) -> bool {
self.real_telemetry.is_some() && !self.no_telemetry
}
```
Regra única (RF-03, RN-01):
| `None` | qualquer | **não** — scaffolding local, sem provider |
| `Some(_)` | `false` | **sim** — provider real, sem flag de supressão |
| `Some(_)` | `true` | **não** — provider real + `--no-telemetry` |
Telemetria nunca é estimada localmente. O bloco só aparece quando variáveis
`SDD_USAGE_*` e `SDD_GENERATION_*` foram populadas pelo provider real (lidas
em `render_stage_artifact_with_ctx` — `src/main.rs:12543`).
---
## `DeterministicGenerator` vs `ProviderGenerator`
**Arquivo:** `src/domain/artifact.rs` (linhas 115–153)
| `DeterministicGenerator` | `real_telemetry = None` — scaffolding local | nunca emite |
| `ProviderGenerator` | `real_telemetry = Some(_)` — chamada real ao provider | emite quando `should_emit_telemetry() = true` |
Ambos delegam para `render_stage_artifact_with_ctx` (`src/main.rs:12543`), que
centraliza a leitura de env e a decisão de emissão. A diferença entre os dois
geradores é semântica (documenta a intenção no tipo) — a lógica de emissão está
em `should_emit_telemetry`, não duplicada nos dois.
`generate_html` (quando disponível) chama `generate_markdown` internamente e
converte o Markdown resultante via `StageHtmlGenerator` (`src/domain/html.rs:771`).
Isso garante paridade byte-semântica entre as saídas Markdown e HTML.
---
## `StageGenerator` — dispatch estático
**Arquivo:** `src/domain/artifact.rs` (linha 163)
```rust
pub enum StageGenerator {
Deterministic(DeterministicGenerator),
Provider(ProviderGenerator),
}
impl StageGenerator {
pub fn select(ctx: &GenerationContext<'_>) -> Self {
match ctx.real_telemetry {
Some(_) => StageGenerator::Provider(ProviderGenerator),
None => StageGenerator::Deterministic(DeterministicGenerator),
}
}
}
```
Evita `Box<dyn ArtifactGenerator>`. O dispatch é estático (monomorphization),
sem alocação de heap. CLI e TUI chamam `StageGenerator::select(&ctx)` e invocam
`generate_markdown` ou `generate_html` no resultado.
---
## Ordem canônica de seções (RN-07, RF-01)
A ordem dos blocos `## Section` em qualquer artefato gerado pelo pipeline:
```
# Título - Nome da orquestração
## Resumo
## [seções de conteúdo do stage — ex: Objetivos, Contexto, Critérios…]
## Diagramas
## Rastreabilidade ← penúltima (antes era a primeira — mudança de T-03)
## Histórico de revisão
```
**Exceção — ADR:** a seção `## Rastreabilidade` aparece no rodapé, como nas
demais etapas. Não há exceção estrutural para ADR na ordem canônica.
A ordem é definida em `schemas/sdd-contract.yaml` e validada por
`src/contract.rs::required_sections()`. O arquivo `schemas/artifact-sections.json`
espelha o contrato para validação externa (`sdd validate-artifact`).
---
## Flags de CLI
### `--format`
**Arquivo:** `src/main.rs` (enum `OutputFormat`, uso em `StageArgs`)
```
sdd idea --format html "minha feature"
sdd prd --format md "minha feature" # padrão
```
Valores aceitos: `html`, `md` (default), `json` (aceito pelo parser mas não
implementado para artefatos de stage — retorna Markdown).
Internamente, `OutputFormat` converte para `ArtifactFormat` via `impl From`:
```rust
// src/main.rs:848
impl From<OutputFormat> for crate::domain::artifact::ArtifactFormat {
fn from(f: OutputFormat) -> Self {
match f {
OutputFormat::Html => ArtifactFormat::Html,
OutputFormat::Md | OutputFormat::Json => ArtifactFormat::Markdown,
}
}
}
```
### `--no-telemetry`
**Arquivo:** `src/main.rs` (campo `no_telemetry` em `StageArgs`)
```
sdd idea --no-telemetry "minha feature"
```
Suprime o bloco de telemetria mesmo quando `SDD_USAGE_*` estão definidas.
Propagado para `GenerationContext.no_telemetry`; a decisão final é de
`should_emit_telemetry()`.
---
## Feature `html-gen`
**Arquivo:** `Cargo.toml`
```toml
[features]
html-gen = ["askama"]
[dependencies]
askama = { version = "0.12", optional = true }
```
Ativa:
- `ArtifactGenerator::generate_html` no trait e nas implementações.
- O módulo `src/domain/html.rs` completo (templates Askama, parser Markdown→HTML).
- O subcomando `sdd artifact render`.
Sem a feature, o binário é menor e nenhum símbolo HTML é compilado.
**Dependências transitivas introduzidas** (resultado de `cargo tree --features html-gen | grep askama`):
```
├── askama v0.12.1
│ ├── askama_derive v0.12.5 (proc-macro)
│ │ ├── askama_parser v0.2.1
│ ├── askama_escape v0.10.3
```
Quatro crates: `askama`, `askama_derive`, `askama_parser`, `askama_escape`.
Nenhuma dependência de runtime adicional além das já presentes no projeto.
---
## Templates Askama
**Diretório:** `templates/html/`
| `templates/html/base.html` | Template base: `<head>`, nav lateral, área de conteúdo, scripts Mermaid |
| `templates/html/discovery.html` | Estende `base.html`; injeita `content_html` com `\|safe` |
| `templates/html/stage.html` | Estende `base.html`; idem para stages genéricos |
| `templates/html/partials/toc.html` | Índice lateral (lista de `TocEntry`) |
| `templates/html/partials/traceability.html` | Bloco de rastreabilidade (lista de `TraceabilityItem`) |
| `templates/html/partials/mermaid_fallback.html` | `<pre class="mermaid-fallback">` emitido junto ao `<div class="mermaid">` |
O template base carrega o script Mermaid via CDN com fallback de `<noscript>`.
RF-11 exige que ambos os elementos (`<div class="mermaid">` e
`<pre class="mermaid-fallback">`) sejam emitidos para cada bloco de diagrama.
---
## Parser Markdown → HTML
**Arquivo:** `src/domain/html.rs` (função `md_to_html`, linha ~485)
Parser line-by-line em Rust puro, sem dependências externas. Implementa:
- Headings `#` a `######` → `<h2>` a `<h6>` com id de âncora (função `heading_id`).
- Listas `- item` → `<ul><li>`.
- Tabelas pipe → `<table><thead><tbody>`.
- Blocos mermaid (` ```mermaid `) → `<div class="mermaid">` + `<pre class="mermaid-fallback">`.
- Blocos de código genérico → `<pre><code class="language-X">`.
- Inline: `**bold**`, `*italic*`, `` `code` ``, `[text](url)`.
Limitações documentadas (não implementado): aninhamento de listas, blockquotes,
HTML inline, imagens, referências de link. Suficiente para os artefatos SDD
atuais. Se necessário, substituir por `pulldown-cmark` sem quebrar a API pública
(`render_markdown_file_to_html`, `md_to_html`).
---
## Subcomando `sdd artifact render`
**Arquivo:** `src/main.rs` (enum `ArtifactCommand::Render`, linha ~4531;
handler em `run_artifact`, linha ~28133)
Disponível apenas com a feature `html-gen`.
```
sdd artifact render --format html --stage prd --slug minha-feature [--output out.html]
```
| `--format html` | Único formato suportado; outros retornam erro. |
| `--stage` | Stage do artefato (ex: `prd`, `idea`, `tasks`). Mapeado via `contract::stage_file`. |
| `--slug` | Slug da orquestração (determina o diretório `docs/<slug>/`). |
| `--output` | Caminho de saída; default: mesmo caminho com extensão `.html`. |
Internamente chama `render_markdown_file_to_html` (`src/domain/html.rs:1129`),
que lê o arquivo Markdown e delega para `StageHtmlGenerator`.
---
## Paridade CLI / TUI
A TUI (`src/tui/runner.rs`, função `spawn_stage`) usa o mesmo
`GenerationContext` que o CLI. A saída Markdown é byte-idêntica entre CLI e TUI
para o mesmo conjunto de variáveis de ambiente e flags (RF-12, RF-13).
Validado pelo teste de integração `tui_and_cli_produce_identical_stage_output`
em `tests/cli.rs`.
---
## Rastreabilidade
- Origem: `docs/geracao-mais-robusta-e-organizada-redesenhar-pipeline-de-geracao-de-artefatos-do-sd-7baa90d82a5a/04-tasks.md` (T-14)
- Tech Spec: `03-techspec.md` §Contratos, §RF-01 a RF-13, §RN-01/07
- Estado: concluído (T-14 entregue junto com T-15)
- Evidências: `cargo clippy --all-targets --all-features -- -D warnings` → sem issues; `cargo test --features html-gen` → 577 unit + 8 characterization passando