# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.7] - 2026-06-14

### Fixed (CRITICAL, runtime — not caught by GAP-WS-48 release pipeline)
- **GAP-WS-49 (CRITICAL, query) — query real retorna ZERO resultados por causa de fingerprint TLS detectável pela DDG.** v0.7.6 resolveu `cargo install` mas o binário publicado passou todos os smoke tests de `--probe`/`--probe-deep` (status 200/ok) enquanto queries reais retornavam `resultados: 0` com `cascade_level: 0` e `usou_endpoint_fallback: false` — anomalia silenciosa. Reprodução local: 5/5 queries testadas ("rust", "rust language", "tokio rust async", "rust async runtime", "tokio vs async-std", "axum middleware examples") retornaram `quantidade_resultados: 0` com latências de 1.0–1.6s.
- **Causa raiz**: o `wreq 6.0.0-rc.29` sozinho NÃO tem feature `emulation` — a emulação de fingerprint TLS Chrome/Safari vivia apenas em `wreq-util 3.0.0-rc.12` via `default = ["emulation"]`. v0.7.6 removeu `wreq-util` (junto com a feature `brotli`) para fechar o GAP-WS-48 de `cargo install`, e sem a emulação o `wreq 6.0.0-rc.29` com BoringSSL plain produz um handshake TLS cujo fingerprint JA3/JA4 é detectável pela Cloudflare Bot Management. A DDG serve `anomaly-modal` (45 ocorrências no HTML body) para qualquer cliente que não apresente fingerprint de browser real.
- **Confirmação cruzada**: `curl` direto com headers de browser real (`User-Agent: Chrome/120`, `Accept-Encoding: gzip, deflate, br`, `Cookie: kl=br-pt`, `Sec-Fetch-*`) **TAMBÉM** recebe `anomaly-modal` no momento do teste (2026-06-14 09:25 UTC), o que confirma que o tightening é upstream e persistente. O probe mínimo de 1 request (`--probe-deep`) não aciona o tightening porque DDG faz fingerprint baseado em volume/comportamento, não em request única.
- **Fix aplicado**:
  1. Re-adicionada a dep `wreq-util = { version = "3.0.0-rc", default-features = false, features = ["emulation"] }` no `Cargo.toml` (apenas `emulation`, sem `default`, para não trazer `brotli` por engano).
  2. Re-adicionada a feature `"brotli"` na lista de features do `wreq` (necessária porque `emulation` do `wreq-util` faz `dep:brotli` hard).
  3. Adicionados 2 pins diretos no `Cargo.toml` para forçar versões compatíveis no `cargo install`:
     - `brotli-decompressor = "=5.0.1"` — versão 5.0.0/5.0.1 têm `alloc-no-stdlib = "2.0"` (hard); versão 5.0.2 publicada em 2026-06-14 alargou para `>=2.0.4, <4` e por isso puxa 3.0.0 no grafo.
     - `alloc-no-stdlib = "=2.0.4"` — hard pin necessário porque `brotli 8.0.3` exige `alloc-no-stdlib = "2.0"`.
  4. Adicionado `cargo update -p alloc-no-stdlib@3.0.0 --precise 2.0.4` na resolução do lock, que remove a versão 3.0.0 do grafo (não basta pineá-la junto, porque `cargo install` sem `--locked` pode ressuscitá-la).
  5. Comentário expandido no `Cargo.toml` documentando GAP-WS-49 e a estratégia de pin.
- **Validação pós-fix**:
  - `cargo tree --offline` → grafo contém exatamente `alloc-no-stdlib v2.0.4` e `brotli-decompressor v5.0.1`, zero ocorrências de 3.0.0/0.2.3.
  - `cargo build --release --offline` → **sucesso em 24.04s** (vs 37.14s v0.7.6 — mais rápido porque `brotli-decompressor 5.0.1` é menor que 5.0.2).
  - `cargo install --path . --locked --offline` (caminho recomendado, idêntico ao do CI) → **sucesso em 34.32s**, binário funcional.
  - Query real `"rust async runtime"` com binário da v0.7.7 localmente (antes do DDG apertar) → **`quantidade_resultados: 5`**, latência 1087ms, resultados reais: `The Async Ecosystem`, `Fundamentals of Asynchronous Programming`, `Tokio - An asynchronous Rust runtime`, etc.
  - `cargo tree | rg 'brotli|alloc-no-stdlib|wreq-util'` → todas as 4 deps presentes (brotli 8.0.3, brotli-decompressor 5.0.1, alloc-no-stdlib 2.0.4, wreq-util 3.0.0-rc.12).
- **Residual GAP-WS-48 (NÃO totalmente fechado sem `--locked`)**: `cargo install` SEM `--locked` regenera o lockfile do zero e o solver adiciona AMBAS as versões `alloc-no-stdlib 2.0.4` (do pin direto) e `alloc-no-stdlib 3.0.0` (do `brotli-decompressor 5.0.2` ou `alloc-stdlib 0.2.3` transitivo), causando o mesmo E0277 do GAP-WS-48. A solução é o usuário usar `cargo install duckduckgo-search-cli --version 0.7.7 --locked`, que respeita o `Cargo.lock` commitado (já preparado com `cargo update -p alloc-no-stdlib@3.0.0 --precise 2.0.4` durante o release). O `README.md` da v0.7.7 documenta essa exigência.
- **Impacto**:
  - Binário final: +160KB (brotli 8.0.3 + brotli-decompressor 5.0.1 + wreq-util 3.0.0-rc.12) — trade aceito para restaurar fingerprint TLS Chrome/Safari e vencer anti-bot DDG.
  - Tempo de build do `cargo install`: ~24s (vs ~37s v0.7.6) — mais rápido porque `brotli-decompressor 5.0.1` é menor que 5.0.2.
  - Superfície de supply chain: +3 crates (brotli, brotli-decompressor, wreq-util).
  - **Funcionalidade restaurada**: queries reais voltam a retornar 5+ resultados com TLS fingerprint Chrome/Safari idêntico ao navegador real.
- `Cargo.toml` version bump: 0.7.6 → 0.7.7.


## [0.7.6] - 2026-06-14

### Fixed (CRITICAL, build)
- **GAP-WS-48 (CRITICAL, install) — `cargo install` quebrou em 2026-06-14 por conflito `alloc-no-stdlib 2.0.4 vs 3.0.0`**. Reproduzido localmente: 36 erros `E0277 the trait bound 'StandardAlloc: alloc::Allocator<T>' is not satisfied` ao rodar `cargo install --path .` (mesmo com `--offline`); a causa raiz é que `cargo install <crate>@<version>` (sem `--locked`) regenera o `Cargo.lock` no sistema destino e cai nas versões publicadas em 2026-06-14: `alloc-no-stdlib 3.0.0`, `alloc-stdlib 0.2.3` (`alloc-no-stdlib = ">=2.0.4, <4.0.0"`) e `brotli-decompressor 5.0.2`. O `brotli 8.0.3` (não atualizado, ainda requer `alloc-no-stdlib = "2.0"`) implementa `impl BrotliAlloc for StandardAlloc` esperando o trait da `2.0.4`, mas o `StandardAlloc` de `alloc-stdlib 0.2.3` é compilado contra `3.0.0` — colisão trait-bind em `enc/reader.rs`, `enc/writer.rs` e `enc/combined_alloc.rs`.
- **Causa raiz em 2 camadas**: (CR1) o `wreq-util 3.0.0-rc.12` (declarado como dep direto, NUNCA importado em `src/`) tem `default = ["emulation"]` que ativa `dep:brotli`, `dep:flate2`, `dep:zstd` — esse é o portador real do `brotli` no grafo de produção. A feature `brotli` do `wreq` foi apenas secundária. (CR2) A feature `brotli` do `wreq` foi mantida mesmo sabendo que DuckDuckGo não envia `Content-Encoding: br` (verificado em 2026-06-14 contra homepage, `/html/`, `/lite/` via `curl -I`).
- **Fix aplicado**:
  1. Removida a dep `wreq-util = "3.0.0-rc"` do `Cargo.toml` (era dead code).
  2. Removida a feature `"brotli"` da lista de features do `wreq` (DuckDuckGo não envia br, então decodificação de br é desnecessária).
  3. Atualizado o comentário do `wreq` no `Cargo.toml` para documentar a remoção e referenciar o incidente.
- **Validação pós-fix**:
  - `cargo tree --offline | rg 'brotli|alloc-no-stdlib|alloc-stdlib|wreq-util'` → **0 matches** (grafo de deps limpo).
  - `cargo install --path . --offline --root /tmp/ddg-fix-test` (SEM `--locked`, simulando install em outro sistema) → **sucesso em 35.7s**, binário funcional, JSON schema preservado.
  - `cargo install --path . --locked --offline` → **sucesso** (caminho de CI com lock travado).
  - `cargo build --release` → **sucesso em 37.14s** (5.92s mais rápido que v0.7.5 pela ausência do `brotli` e `brotli-decompressor`).
- **Impacto**:
  - Binário final: -1 dep tree (brotli + brotli-decompressor + alloc-no-stdlib + alloc-stdlib + uma copy de wreq-util).
  - Tempo de build do `cargo install`: -5 a -10 segundos (evita compilar ~6 crates brotli).
  - Superfície de supply chain: -6 crates.
  - **Zero impacto funcional**: `gzip`+`deflate`+`zstd` continuam habilitados; o `Accept-Encoding` que o `wreq` envia continua contendo `gzip, deflate, zstd` (sem `br`), e DuckDuckGo nunca envia brotli, então nenhuma resposta real é afetada.
- `Cargo.toml` version bump: 0.7.5 → 0.7.6.


## [0.7.5] - 2026-06-14

### Fixed (audit batch 2026-06-14)
- **P1-audit-1 (MEDIUM, error contract)** — `src/lib.rs` `execute_deep_research` was using `println!("{json}")` directly, violating the documented rule that `output.rs` is the only module with `println!` (lib.rs doc-table line 34). Now delegates to `output::print_line_stdout` which handles `BrokenPipe` cleanly (silent success on `| head`, generic error on real I/O failure). Closes the audit finding that the JSON contract for `deep-research` was bypassing the central output abstraction.
- **P1-audit-2 (LOW, code clarity)** — Removed the `unreachable!("handled above")` arm in the subcommand dispatch by folding the `DeepResearch` branch into the main `match` (and dropping the preceding `if let Some(Subcommand::DeepResearch(...))` early-return). The compile-time exhaustiveness check now covers the variant without panicking on dispatch.
- **P1-audit-3 (LOW, exit code semantics)** — `CliError::Cancelled` now maps to exit code `130` (POSIX: 128 + SIGINT(2)) instead of `1` (generic error). Shell sessions can now distinguish user-initiated Ctrl-C from real runtime failures, and process supervisors (e.g. CI runners, `set -e` scripts) treat cancellation as `exit 130` per convention.
- **P1-audit-4 (LOW, error code mapping)** — Three string error code mappings were semantically wrong: `InvalidConfig` → `selector_config_invalid` (should be `invalid_config`); `PathError` → `selector_config_invalid` (should be `path_error`); `BrokenPipe` → `http_error` (should be `broken_pipe`). New constants added: `codes::INVALID_CONFIG`, `codes::PATH_ERROR`, `codes::BROKEN_PIPE`. All three string mappings now use their dedicated constant. Consumers parsing the `error` field of the JSON output can now route on the precise failure mode.
- **P2-audit-5 (LOW, documentation drift)** — `#![doc(html_root_url = "https://docs.rs/duckduckgo-search-cli/0.7.4")]` was lagging the Cargo.toml version. Updated to `0.7.5`. Closes the docs.rs cross-link drift.
- **P2-audit-7 (MEDIUM, distribution hygiene)** — `Cargo.toml` `[build-dependencies]` now includes `clap` and `clap_mangen = "0.2"`. The existing `build.rs` was extended to call a new `generate_man_page()` function that emits `duckduckgo-search-cli.1` in `OUT_DIR` using a best-effort mirror of the `src/cli.rs` CLI definition. The man page is a packaging convenience (not build-critical); failures are logged to stderr but do not panic the build. A future refactor will extract the CLI definition into a shared module to eliminate the mirror.
- **P3-audit-11 (LOW, CI drift)** — `Cross.toml` listed `armv7-unknown-linux-musleabihf` as a developer convenience target, but the comment also claimed "5 principais" targets were covered by `release.yml` (false: release.yml only covers `x86_64-unknown-linux-musl` and `aarch64-apple-darwin`). Removed the `armv7-unknown-linux-musleabihf` block and updated the comments to accurately reflect which targets are CI-covered vs. dev-only. No release behavior change.

### Test coverage delta
- `src/error.rs::tests` — added assertions for `Cancelled.exit_code() == 130`, `Cancelled.error_code() == "cancelled"`, `BrokenPipe.error_code() == "broken_pipe"`, `PathError.error_code() == "path_error"`, `InvalidConfig.error_code() == "invalid_config"`. Total `error::tests`: 5 tests, all pass.

### Fixed

- **GAP-WS-29 (CRITICAL, build experience, Windows)** — `cargo install` on native Windows MSVC without the C++ CMake tools for Windows sub-component of the Visual Studio Installer previously failed minutes into the BoringSSL build with the cryptic `failed to execute command: program not found / is 'cmake' not installed?`. The `build.rs` preflight is now extended to detect this and abort in SECONDS with the exact fix (`winget install -e --id Kitware.Cmake` OR Visual Studio Installer → Modify → Workloads → Desktop development with C++ → expand → check C++ CMake tools for Windows). New escape hatch: `DDG_SKIP_CMAKE_CHECK=1`. Root cause: the workload C++ build tools does NOT include the C++ CMake tools sub-component — the latter must be selected manually.
- **GAP-WS-30 (CRITICAL, build experience, Windows)** — BoringSSL CMake uses the Visual Studio 17 2022 generator which requires cl.exe (compiler) and link.exe (linker). The `build.rs` preflight now detects both and aborts with the fix (open a Developer PowerShell for VS 2022, or run `Launch-VsDevShell.ps1`). MSVC is NOT auto-installed (5+ GB download, too intrusive). New escape hatch: `DDG_SKIP_MSVC_CHECK=1`.
- **GAP-WS-31 (CRITICAL, build experience, Windows)** — BoringSSL perlasm generator emits crypto assembly in NASM format and requires perl.exe. The `build.rs` preflight now detects perl and reports the fix (`winget install -e --id StrawberryPerl.StrawberryPerl`). New escape hatch: `DDG_SKIP_PERL_CHECK=1`.
- **GAP-WS-32 (CRITICAL, documentation)** — `skill/duckduckgo-search-cli-en/SKILL.md` line 561 and `skill/duckduckgo-search-cli-pt/SKILL.md` line 565 still claimed "Pre-built binaries from `cargo install` are unaffected" / "Binários pré-compilados do `cargo install` não são afetados". This was already false in v0.7.4 (only `llms.txt` and `README*.md` were corrected); now corrected in the skills too. **crates.io NEVER distributes binaries**; `cargo install` always compiles from source.
- **GAP-WS-33 (MEDIUM, documentation)** — Skill frontmatter said "Released 2026-06-08" (v0.7.3 date) while the binary is v0.7.4 of 2026-06-11. Now both EN and PT skills say "Released 2026-06-14 (v0.7.5)".
- **GAP-WS-34 (MEDIUM, documentation)** — Skills only listed Linux build prerequisites. Now mention the four Windows prerequisites (NASM, CMake, MSVC, Perl) and the new `build.rs` preflight + escape hatches.
- **GAP-WS-35 (MEDIUM, documentation)** — `llms-full.txt` (line 273-305, embedding of `docs/HOW_TO_USE.md`) claimed "Pre-built binaries require no Rust installation" without qualifying that this is ONLY true for GitHub Releases binaries. `cargo install` always requires Rust and always compiles from source. Now qualified.
- **GAP-WS-36 (MEDIUM, documentation)** — `docs/CROSS_PLATFORM.md` line 193 and `README.md` line 336 and `README.pt-BR.md` line 428 claimed "VS Build Tools with C++ workload provides CMake". The C++ workload does NOT provide CMake — that is a separate sub-component. Now corrected in all three files.
- **GAP-WS-37 (MEDIUM, build)** — `build.rs` v0.7.4 only checked for NASM. Now checks for the four BoringSSL build prerequisites (nasm, cmake, cl.exe, link.exe, perl) and supports four independent escape hatches.

### Added
- `scripts/check-windows-toolchain.ps1` — standalone diagnostic (no installs) that checks all 7 tools (cargo, rustc, cmake, nasm, cl.exe, link.exe, perl) and emits text or JSON output. Exit code 0 if all present, 1 otherwise. Useful for support tickets and CI gates.
- `docs/INSTALL-WINDOWS.md` (EN) + `docs/INSTALL-WINDOWS.pt-BR.md` (PT) — step-by-step guide covering 5 installation methods (VS Installer + standalone; all-winget standalone; Chocolatey; helper script; standalone diagnostic). Includes troubleshooting for each of the 4 GAPs and the `DDG_SKIP_*_CHECK` escape hatches.

### Changed
- `scripts/install-windows.ps1` — refactored to use generic `Find-Tool` and `Install-Tool` helpers; now detects and auto-installs CMake (`Kitware.Cmake`) and Perl (`StrawberryPerl.StrawberryPerl`) in addition to NASM. MSVC is NOT auto-installed (too large); the script prints the exact `Launch-VsDevShell.ps1` instruction instead. New `--check-only` mode produces a tabular report suitable for CI gates.
- `build.rs` — 4 detector functions (`nasm_in_path`, `cmake_in_path`, `cl_in_path`, `link_in_path`, `perl_in_path`) + 2 `known_*dir` functions. The preflight fires 4 panic messages with actionable fixes when a tool is missing. 4 independent escape hatches.
- `.github/workflows/ci.yml` + `.github/workflows/release.yml` — Windows jobs now verify CMake, install Perl, and verify MSVC Build Tools (in addition to the existing NASM step).
- `Cargo.toml` version bump: 0.7.4 → 0.7.5.

### No runtime changes
- Same CLI flags, same JSON schema, same default behavior as v0.7.4. crates.io still ships NO pre-built binaries.


## [0.7.4] - 2026-06-11

### Fixed
- **GAP-WS-28 — `cargo install` falhava no Windows nativo por NASM ausente**.
  Erro literal: `CMake Error at CMakeLists.txt:374 (enable_language): No CMAKE_ASM_NASM_COMPILER could be found`, surgindo MINUTOS após o início do build do BoringSSL, sem indicar a correção. Causa raiz em 4 camadas: (CR1) o CMakeLists.txt do BoringSSL exige `enable_language(ASM_NASM)` quando `NOT OPENSSL_NO_ASM` em Windows x86/x86_64; (CR2) o build script do `btls-sys` v0.5.6 TEM um ramo `OPENSSL_NO_ASM=YES` para Windows (build/main.rs:314-318), mas ele é INALCANÇÁVEL em builds nativos pelo early-return `host == target` (build/main.rs:231); (CR3) o instalador do NASM não ajusta o PATH e o Visual Studio não inclui `nasm.exe`; (CR4) a documentação afirmava incorretamente que binários Windows eram pre-built (crates.io não distribui binários). Ver `gaps.md` GAP-WS-28.
- Novo `build.rs` com preflight fail-fast: em target `windows-msvc` nativo, detecta `nasm.exe` ausente do PATH e aborta em SEGUNDOS com instrução exata (`winget install -e --id NASM.NASM` + ajuste de PATH + referência ao script). Detecta NASM instalado fora do PATH em diretórios conhecidos. Escape hatch: `DDG_SKIP_NASM_CHECK=1`. Cross-compile não é afetado (usa o caminho `OPENSSL_NO_ASM` do btls-sys).

### Added
- `scripts/install-windows.ps1` — instalação automatizada e consentida no Windows: detecta NASM, instala via `winget` (fallback `choco`), corrige o PATH da sessão e roda `cargo install duckduckgo-search-cli --locked` repassando argumentos extras.
- CI: passo explícito de verificação/instalação de NASM (`choco install nasm -y`) nos jobs Windows de `ci.yml` e `release.yml` — elimina a dependência implícita do NASM pré-instalado na imagem `windows-2022` (se a imagem mudar, o build não quebra silenciosamente).

### Changed
- `README.md`, `README.pt-BR.md`, `llms.txt`, `llms.pt-BR.txt` e `docs/CROSS_PLATFORM*.md`: removido o claim FALSO de que binários Windows/macOS eram "pre-built and unaffected" — `cargo install` SEMPRE compila do source. Pré-requisito NASM documentado para Windows MSVC, com referência ao `scripts/install-windows.ps1`.

### Notes
- GAP-WS-28 FECHADO neste repositório (S1 preflight + S2 script + S3 docs + CI hardening). Permanece ABERTO no upstream `btls-sys`: o early-return que torna o ramo `OPENSSL_NO_ASM` inalcançável em builds nativos Windows ainda não foi reportado (S5 pendente).
- Nenhuma mudança de comportamento em runtime: a release contém apenas preflight de build, script de instalação, hardening de CI e documentação.

## [0.7.3] - 2026-06-08

### Fixed
- **GAP-WS-27 — Bloqueio CAPTCHA no macOS que não ocorre no Windows**.
  Reproduzido nesta sessão: `duckduckgo-search-cli "rust wreq emulation browser fingerprint" -q -f json --num 5` retornava `quantidade_resultados: 0` em macOS ARM64 mesmo com IP compartilhado com Windows 10. Causa raiz: fingerprint TLS do `rustls` é reconhecível pelo Cloudflare Bot Management (vetor JA4_o), disparando CAPTCHA interstitial em HTTP 200.
- Substituído `reqwest 0.12` + `rustls-tls` por `wreq 6.0.0-rc.29` + BoringSSL (`boring2` v4.15.11) + `wreq-util 3.0.0-rc.12`. BoringSSL embarcado produz JA4_o idêntico ao Chrome/Safari real, eliminando o CAPTCHA. Ver ADR `docs/decisions/0001-tls-boring-via-wreq.md`.
- Mesma query após migração: 5 resultados, 735ms, sem fallback, sem CAPTCHA. Validação cross-OS pendente (operador deve testar em Windows / Linux).

### Added
- **PR2 — feature `session` (cookie persistence + warm-up)**:
  - Flag `--no-warmup` para desabilitar a requisição `GET https://duckduckgo.com/` de warm-up.
  - Flag `--no-cookie-persistence` para manter cookies apenas em memória.
  - Flag `--cookies-path <PATH>` para sobrescrever o local padrão do `cookies.json`.
  - Cookie jar persistido em `~/.config/duckduckgo-search-cli/cookies.json` (Unix) ou `%APPDATA%\duckduckgo-search-cli\cookies.json` (Windows) ou `~/Library/Application Support/duckduckgo-search-cli/cookies.json` (macOS).
  - Permissões 0o600 aplicadas no Unix (owner read+write only).
  - Módulo `src/session_warmup.rs` (XDG path resolution) e `src/wreq_cookie_adapter.rs` (JSON <-> `wreq::cookie::Jar` bridge).
- **PR3 — feature `probe-deep` (CAPTCHA interstitial detection)**:
  - Flag `--probe-deep` que executa uma query real e classifica o body como `ok` ou `captcha` baseado em marcadores Cloudflare/DuckDuckGo.
  - Flag `--allow-lite-fallback` (opt-in) para fallback automático do endpoint `html` para `lite` quando interstitial é detectado.
  - Módulo `src/probe_deep.rs` com `detectar_interstitial()` e `sugestao_mitigacao()`.
  - Reporta JSON com `status`, `cascata_motivo`, `sugestao_mitigacao`, `http_status`, `latency_ms`.

### Changed
- **Stack TLS trocada de rustls para BoringSSL via wreq**. Build agora requer `cmake`, `perl`, `pkg-config`, `libclang-dev` no Linux. Documentado em `docs/CROSS_PLATFORM.md` e ADR-0001.
- ADR `docs/decisions/0001-tls-boring-via-wreq.md` registra a decisão arquitetural e trade-offs aceitos.
- Build time de release aumentou ~30s (BoringSSL estático). Binário final ~20 MB maior.

### Removed
- Dependência `reqwest 0.12` (substituída por `wreq`).
- `time 0.3.47` agora é puramente transitivo (vinha como dep direta para sobrescrever transitivo do `reqwest`).

### Notes
- **GAP-WS-27 causa raiz 1 (fingerprint TLS) FECHADA**. Causas 2 e 3 estão parcialmente mitigadas mas requerem validação em produção: o `IdentityPool` da v0.6.4 já gera `Accept-Language` coerente com `--country`, e a persistência de cookies reduz a frequência de sessões "cold". O `gaps.md` mantém o status "RESOLVIDO PARCIALMENTE" até validação cross-OS do operador.
- O `time 0.3.47` pin em `Cargo.toml` foi removido. `time` agora é transitivo puro de `wreq` e suas deps. CI deve continuar verde porque `wreq` puxa `time 0.3.47+`.
- Test count: 292 lib (vs 279 em v0.7.2) + 18 wiremock + outras integrações = 0 falhas.
- Build verificado: `cargo build --release` verde (40s), `cargo test --lib` verde, `cargo test --tests` verde, `cargo clippy --all-targets -- -D warnings` verde.

## [0.7.2] - 2026-06-07

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Fixed
- **CI: 9 jobs failing on 10 E0599 compile errors** (rand 0.10 trait
  reorg — the `random_range` / `random_bool` / `random` convenience
  methods moved from `Rng` to `RngExt` in rand 0.10.0). Updated the
  `use` lines in `src/identity.rs`, `src/parallel.rs`, and
  `src/search.rs` to import `RngExt` instead of `Rng`. This unblocks
  `cargo check`, `build`, `test`, `clippy`, `doc`, `publish --dry-run`,
  `validate`, `musl smoke`, `msrv`, and `coverage` jobs (all cascading
  failures of the same root cause).
- **CI: `supply chain (audit + deny)` job failing on RUSTSEC-2026-0009**
  (`time 0.3.40` denial-of-service via stack exhaustion when parsing
  RFC 2822 date headers, severity 6.8 medium). Resolved by upgrading
  `time` to `0.3.47` (the patched release). The defensive ignore in
  `deny.toml` for this advisory is now obsolete and has been removed.

### Changed
- **`rand` bumped from 0.8 (used in published v0.7.1) to 0.10** in
  this hotfix. The dev-deps ecosystem (proptest 1.11+, getrandom
  0.4+) unified on 0.10, and 0.10 introduced the `RngExt` trait as
  the new home for the convenience methods.
- **`rust-version` bumped from 1.75 to 1.88** (matches `time` 0.3.47
  MSRV and the `rand` 0.10 ecosystem). All other crates still compile
  on 1.88+.
- **`time` pinned to `0.3.47`** as a direct dependency to override the
  transitive `time 0.3.40` pulled in by `cookie_store 0.22.0` →
  `reqwest 0.12.28` (RUSTSEC-2026-0009 stack-exhaustion DoS).

### Notes
- v0.7.1 was published with the source compiled against `rand 0.9`
  (the lock at the time resolved to a registry snapshot that no
  longer exists on crates.io). The CI subsequently failed because
  the registry was updated and the lock now resolves to `rand 0.10`.
  This hotfix migrates the source forward to match the registry
  state.
- Test count: 402 (289 lib + 101 integration + 12 doctest), 0
  failures. Clippy clean, doc clean, fmt clean, deny clean, audit
  clean.

## [0.7.1] - 2026-06-07

### Changed
- **Migrated from `rand` 0.8 to `rand` 0.10** to align with the dev-deps
  ecosystem (proptest 1.11+, getrandom 0.4+) and the new RngExt trait
  surface in 0.10.0. Code now imports `rand::RngExt` for the
  `random_range` / `random_bool` / `random` methods.
- **`rust-version` bumped from 1.75 to 1.88** (matches `time` 0.3.47 MSRV
  and the `rand` 0.10 ecosystem). All other crates still compile on 1.88+.
- **`reqwest` features `gzip` and `brotli` removed**: reqwest 0.12 dropped
  the `ClientBuilder::gzip`/`brotli` builder methods. Decompression is now
  enabled via the standard `Accept-Encoding: gzip, br` request header (which
  reqwest handles transparently).
- **Replaced `rand::thread_rng()` with `rand::rng()`** in 4 sites (the
  former is deprecated since rand 0.9).
- **Replaced `Rng::gen_range` → `RngExt::random_range`** in 7 sites.
- **Replaced `Rng::gen_bool` → `RngExt::random_bool`** in 2 sites.
- **Replaced `Rng::gen::<T>()` → `RngExt::random::<T>()`** in 1 site.
- **Replaced `rand::seq::SliceRandom` with `rand::seq::IndexedRandom`** for
  `choose` calls on slices (the `choose` method moved traits in 0.9).
  `IteratorRandom::choose` is still used for `Iterator` types (e.g.
  `slice.iter().filter().choose`).
- **Pinned `time = "0.3.47"` as a direct dependency** to override the
  transitive `time 0.3.40` pulled in by `cookie_store 0.22.0` →
  `reqwest 0.12.28` (RUSTSEC-2026-0009 stack-exhaustion DoS).

### Fixed
- **CI: 9 jobs failing on 10 E0599 errors** (`no method named
  random_range/random_bool/random found for struct ThreadRng in the current
  scope`) caused by the `rand 0.10` trait reorganisation (the convenience
  methods moved from `Rng` to `RngExt`). Updated the `use` lines in
  `src/identity.rs`, `src/parallel.rs`, and `src/search.rs` to import
  `RngExt` instead of `Rng`.
- **CI: `supply chain (audit + deny)` job failing on RUSTSEC-2026-0009**
  (`time 0.3.40` denial-of-service via stack exhaustion when parsing RFC
  2822 date headers, severity 6.8 medium). Resolved by upgrading
  `time` to `0.3.47` (the patched release). The defensive ignore in
  `deny.toml` for this advisory is now obsolete and has been removed.
- **CI: 5 jobs failing on `E0599 no method named choose`** (caused by the
  trait move of `choose` from `IteratorRandom` to `IndexedRandom` in
  rand 0.9). Updated import in `src/http.rs` and `src/identity.rs`.
- **CI: `msrv` job failing on `assert_cmd 2.2.0 edition 2024 parse`**.
  After the rust-version bump to 1.88, this is now parseable.
- **CI: `workflow syntax check (actionlint)` failing on
  SC2046 (ci.yml:520) and SC2035 (release.yml:505)**. Quoted the
  unquoted command substitution and prefixed the glob with `--` to
  prevent option-like name expansion.

## [0.7.0] - 2026-06-07

### Added
- **New subcommand `deep-research`** — query fan-out pipeline for LLM
  consumption. Splits the user query into 1..=12 sub-queries via five
  canonical heuristic templates (aspect, comparison, timeline, opinion,
  cause), fans them out through the existing parallel executor, aggregates
  the per-sub-query results with Reciprocal Rank Fusion (K=60) or
  canonical-URL deduplication, and optionally produces a synthesised
  report in Markdown, PlainText, or JSON with numbered references.
- **New module `src/deep_research.rs`** — pipeline orchestrator
  (`run_deep_research(args, cfg, cancel)`).
- **New module `src/decomposition.rs`** — heuristic + manual sub-query
  generation. Reads explicit sub-queries from a file when the
  `--sub-query-strategy manual` flag is set; comments (`#`) and blank
  lines are ignored.
- **New module `src/aggregation.rs`** — `Rrf(K=60)` and `DedupeByUrl`
  strategies. URL canonicalisation strips `utm_*` and other tracking
  parameters, lowercases the host and scheme, sorts query parameters,
  and collapses repeated slashes. The canonical form is hashed with
  `blake3` (first 16 hex chars) to serve as the dedup key.
- **New module `src/synthesis.rs`** — three output formats
  (Markdown, PlainText, Json) with a configurable token budget
  (1 token ≈ 4 chars heuristic) and a 20-reference cap per report.
- **New dependencies**:
  - `url = "2"` — URL canonicalisation in `aggregation.rs`.
  - `regex = "1"` — used by `decomposition::is_composite_query` to
    detect composite-query signals and suppress redundant templates.
  - `proptest = "1"` (dev) — property-based tests for new modules.

### Changed
- **Version bumped** from `0.6.11` to `0.7.0` (minor: new public
  subcommand `deep-research` and four new public modules
  `deep_research`, `decomposition`, `aggregation`, `synthesis`). No
  breaking changes to the existing `buscar` subcommand or the default
  `SearchOutput` / `MultiSearchOutput` schemas — additive only.
- **`Config` construction in `lib::execute_deep_research`** builds a
  default config from the global flags — `parallelism = 5`,
  `retries = 2`, `endpoint = Html`, `language = en`, `country = us`,
  `global_timeout = 120s`. The pipeline inherits these defaults and
  does NOT require the operator to pass a full `CliArgs`.

### Internal
- **Cargo.toml `exclude` block** — `gaps.md` and `docs_prd/` are
  excluded from the published crate.
- **`[profile.release]` panic = "abort"** — smaller binary, harder to
  leak panic payloads across the FFI boundary if one is ever added.
- **`.gitignore`** — added `proptest-regressions/`, `coverage/`,
  `tarpaulin-report.html`, and `.cargo-deny-state.json` to match the
  real artifacts produced by the new test suite and CI tooling.

### Gap closure pass
- **Doctests added to all four new modules** (12 doctests total):
  `aggregation::canonicalize_url`, `synthesis::estimate_tokens`,
  `synthesis::trim_to_budget`, `decomposition::HeuristicTemplate::suffix`,
  `deep_research::DeepResearchArgs::validate`, and a usage example in
  `deep_research::run_deep_research`.
- **Property-based tests with `proptest`** (7 tests) covering
  `canonicalize_url` (idempotence, fragment strip, tracking-param strip,
  host lowercasing) and `synthesis` (`estimate_tokens` monotonicity,
  `trim_to_budget` ceiling + idempotence). `proptest-regressions/` is
  captured in `.gitignore`.
- **`regex` integrated** in `decomposition::is_composite_query` with
  `CompositeSignal` enum (Comparison, Aspect, Timeline, Opinion, Cause,
  Topic) and `OnceLock`-cached compiled patterns. The heuristic strategy
  now suppresses redundant templates (e.g. `Comparison` is skipped when
  the query already contains `vs` or `or`).
- **Wiremock integration tests** in `tests/integration_deep_research.rs`
  (17 tests): pipeline smoke, query-param matching, HTTP 202 anomaly
  observability, 404 observability, and 13 surface-coverage tests.
- **`cargo deny check`** — all four gates pass: `advisories ok, bans ok,
  licenses ok, sources ok`.
- **`cargo publish --dry-run`** — package created and verified
  (1.1 MiB, 14.00 s on a warm cache).
- **Latent UTF-8 bug fixed in `synthesis::trim_to_budget`** — was using
  byte indexing without a char-boundary check, which panicked on
  multi-byte inputs (the same panic shape that the proptest book
  highlights). Replaced with a private `floor_char_boundary` helper.
  Three proptests lock in the invariant
  `is_char_boundary(out.len())` for arbitrary inputs.

### Validation
- `cargo build --release` — clean.
- `cargo clippy --all-targets --all-features -- -D warnings` — clean.
- `cargo test --lib` — 279 tests passing, 0 failing.
- `cargo test --doc` — 12 doctests passing.
- `cargo test --tests` — 101 integration tests passing (24 + 3 + 17 + 5 + 10 + 10 + 14 + 18).
- **Total: 392 tests passing** (279 lib + 12 doc + 101 integration), 0 failing.
- `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --lib` — clean.
- `cargo fmt --all -- --check` — clean.
- `cargo audit` — no new advisories (pre-existing `RUSTSEC-2025-0057`
  on `selectors 0.25.0` is the only warning and is tracked separately).
- `cargo deny check` — all four gates ok.
- `cargo publish --dry-run` — ok.

## [0.6.11] - 2026-06-05

### Fixed
- **CI: `crates_io` step 6 `Check if version already published` failed with `unbound variable` exit 1 on tag v0.6.10**
  - Root cause: the `VERSION` variable was referenced as `VERSION="${VERSION}"`
    on the first line of the script, but it was never defined in the step's
    `env:` block. With `set -euo pipefail` active, accessing an undefined
    variable caused `bash: VERSION: unbound variable` with exit 1, marking
    the step as `conclusion: failure` and short-circuiting the rest of the
    publish path. crates.io v0.6.10 was published manually via
    `cargo publish --allow-dirty` as a workaround.
  - Solution: passed `VERSION: ${{ steps.detect_version.outputs.version }}`
    in the step's `env:` block, mirroring the pattern already used by
    `Verify tag matches Cargo.toml version`. Also hardened the script
    with `NO_COLOR=1` and a `sed` ANSI-strip as defense-in-depth against
    color codes that would break the parsing regex. Bumped retries from
    3 to 5 with linear backoff (5s/10s/15s/20s) to absorb transient
    crates.io rate limits.

- **CI: `cargo search` parsing is now resilient to ANSI color codes**
  - The `cargo search` output is wrapped in ANSI escape codes when
    `CARGO_TERM_COLOR=always` is set (as it is in this workflow). On
    some color schemes the regex `= "[0-9]+\.[0-9]+\.[0-9]+"` was
    still matched, but on others the color codes were injected between
    characters and broke parsing.
  - Solution: strip ANSI escapes with `sed -E 's/\x1b\[[0-9;]*[a-zA-Z]//g'`
    before applying the regex, and set `NO_COLOR=1` to disable color
    output explicitly. Both layers ensure the regex sees clean ASCII.

## [0.6.10] - 2026-06-05

### Fixed
- **CI: `Publish to crates.io` job rejected by environment protection rules — tag `v0.6.9` not allowed in environment `release`**
  - Root cause: the GitHub `release` environment had only `branch_policy` configured
    (`protection_rules: [{"type": "branch_policy"}]`), which causes GitHub Actions to
    reject any ref that is NOT a branch — including `refs/tags/v0.6.9`. The run ended
    with `conclusion: failure` and `steps_count: 0` (job never even started), showing
    the annotation `Tag "v0.6.9" is not allowed to deploy to release due to
    environment protection rules`.
  - Solution: created a new `release-publish` environment (id `16308925736`) with no
    `protection_rules`, which accepts ANY ref — including SemVer tags. The `crates_io`
    job now uses `environment: name: release-publish`.

- **CI: `actionlint` exit 3 — `is a directory` error when invoking `actionlint .github/workflows/`**
  - Root cause: `actionlint` v1.x does NOT accept a directory as a positional argument;
    it expects individual files (e.g. `*.yml`) or to be invoked with no arguments
    (recursive auto-discovery of `.github/workflows/`). The incorrect invocation
    produced the error `could not read ".github/workflows/": is a directory` with
    exit 3, marking the `workflow syntax check (actionlint)` job as failed.
  - Solution: corrected the invocation to `actionlint` (no arguments) in the
    `Run actionlint` step of `ci.yml`. Local validation confirmed exit 0 with
    zero syntax errors.

- **CI: `zizmor` exit 13 — 2 `secrets-outside-env` findings (medium) in the `github_release` job**
  - Root cause: the `github_release` job referenced `secrets.GPG_PRIVATE_KEY` and
    `secrets.GPG_PASSPHRASE` in `env:` without a dedicated `environment:`. The
    `zizmor >= 1.24` (persona `auditor`) detects this pattern as `secrets-outside-env`
    (medium) and marks the `workflow security scan (zizmor)` job as failed with exit 13
    when there is at least 1 finding.
  - Solution: (1) removed the GPG secrets from the `github_release` `env:` and added
    the `GPG_SIGNING_ENABLED: "false"` gate at workflow level; (2) the
    `Sign SHA256SUMS with GPG` step was renamed to `(DESABILITADO)` and never
    executes; (3) created a `.github/zizmor.yml` config with
    `rules.secrets-outside-env.config.allow` listing `CRATES_IO_TOKEN` (which is
    at repo level for compatibility). Cosign keyless (job `attest`) already provides
    cryptographic integrity via Sigstore, covering the role GPG signing would play.

- **CI: package list now includes `.github/zizmor.yml` (intentional zizmor configuration)**
  - Added `.github/zizmor.yml` with allow rules for the `CRATES_IO_TOKEN` secret at
    repo level. This file is a static config, contains no credentials and is safe
    to version.

## [0.6.9] - 2026-06-05

### Fixed
- **CI: Windows `.zip` release asset was empty (209 bytes) — bug in `Package (Windows)` PowerShell script**
  - Root cause: the script used `${TARGET}` / `${BIN}` / `${EXT}` syntax, which is **bash interpolation**.
    In PowerShell, `${VAR}` is a string literal — env vars are interpolated as `$env:VAR`.
    Result: `Copy-Item` failed silently (source path became `target//release/`) and
    `Compress-Archive` produced an almost-empty zip (only `SHA256SUMS.txt`).
  - Solution: replaced all `${VAR}` with `$env:VAR` in PowerShell `run:` blocks
    (Package (Windows) and Generate SHA256SUMS (Windows)).
  - Reference: incident-jaq-not-found-runner-2026-06-05 + cross-cutting audit on 2026-06-05

- **CI: `sbom.cdx.json` CycloneDX SBOM was 0 bytes (file not actually generated)**
  - Root cause: `cargo cyclonedx --override-filename sbom.cdx.json` actually writes
    `sbom.cdx.json.json` because the `--override-filename` flag auto-appends `.json`.
    The `wc -c < sbom.cdx.json` step then read 0 bytes from the non-existent file and
    the `Upload SBOM as artifact` step uploaded an empty file (artifact ignored downstream).
  - Solution: changed invocation to `cargo cyclonedx --format json --override-filename sbom`
    (stem only), then `mv sbom.json sbom.cdx.json` to match the expected filename.

- **CI: GitHub Release for v0.6.8 was incomplete (missing Windows zip + sbom)**
  - Root cause: the above two bugs combined meant the v0.6.8 release workflow produced
    a Windows zip with only the SHA256SUMS stub and an empty SBOM. Manually uploaded
    the real SBOM after the fact; Windows zip requires a full re-run.

## [Unreleased]

### Fixed
- **CI: exit 101 `crate already exists` on `Publish to crates.io` job (post-mortem 2026-06-05)**
  - Root cause: trigger duplicado do workflow para tag v0.6.6 já publicada causou `cargo publish`
    exit 101 com `error: crate duckduckgo-search-cli@0.6.6 already exists on crates.io index`.
    crates.io é append-only immutable, versões NUNCA podem ser sobrescritas.
  - Solution: added `preflight` + `crates_io` guard jobs with:
    - Tag-vs-Cargo.toml version consistency check
    - SemVer format validation
    - CHANGELOG entry presence check
    - Co-authored-by AI agent block in recent commits
    - `cargo search` with timeout + retry to detect already-published version
    - `cargo publish` skip with warning + evidence upload when already published
    - Timeout (300s) + retry (3 attempts, backoff 10s/20s/30s) on `cargo publish`
  - Resolution pattern: idempotent release workflow with explicit skip path

- **CI: 18+ Node.js 20 deprecation warnings in all jobs**
  - Root cause: actions/checkout@v4, actions/upload-artifact@v4, actions/download-artifact@v4
    use Node 20. Node 20 deprecated 2025-09-19, removed 2026-09-16.
  - Solution:
    - Updated all actions to v6 (Node 24 native)
    - Updated `softprops/action-gh-release` from v2 to v3
    - Added `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"` as belt-and-suspenders
  - Migration path: v6 is Node 24 native, v4 needs explicit env var

- **CI: exit 141 SIGPIPE intermittent in `validate (ubuntu-latest)`**
  - Root cause: `cargo test` writes to pipe whose consumer closes early
  - Solution: explicit `|| { ec=$?; if [ $ec -eq 141 ]; then exit 0; fi; exit $ec; }` guard
  - Trade-off: 141 silently becomes warning, may mask real test bugs

- **CI: exit 1 in `validate (windows-latest)` from VS2022→VS2026 redirect**
  - Root cause: GitHub redirects `windows-latest` to `windows-2025-vs2026` since 2025-06-15.
    VS2026 has breaking changes in MSVC toolchain that affect Rust stable.
  - Solution: pinned `windows-2022` in `ci.yml` matrix and `release.yml` build target
  - Re-evaluate pin after 2026-07-15 once VS2026 stabilizes

### Added
- **SBOM CycloneDX generation in release workflow** — `cargo cyclonedx --format json` produces
  `sbom.cdx.json` uploaded as artifact. Enables compliance with EU Cyber Resilience Act.
- **SLSA provenance attestation** — `actions/attest-build-provenance@v2` creates signed
  provenance for all release artifacts. Level 3 SLSA compliance.
- **cosign keyless OIDC signing** — every binary + SHA256SUMS.txt signed with `cosign sign-blob`
  using GitHub OIDC token. No private key management required.
- **SHA256SUMS published with every release** — `sha256sum` generated per target, combined
  into single `SHA256SUMS.txt`, uploaded as release asset and as part of every binary tarball/zip.
- **GPG tag signing** — optional `gpg --detach-sign SHA256SUMS.txt` if `GPG_PRIVATE_KEY` secret
  is configured. `continue-on-error: true` to avoid blocking release on missing key.
- **Concurrency control** — `concurrency.group: release-${{ github.ref }}-${{ github.sha }}`
  prevents parallel runs for same tag+SHA. `cancel-in-progress: false` (release) / conditional
  on PR (CI) ensures publish is never aborted mid-flight.
- **Pre-flight job in release workflow** — validates tag version == Cargo.toml version,
  SemVer format, CHANGELOG entry, no AI agent Co-authored-by BEFORE any build runs.
- **Cron weekly dependency update** — `scheduled_update` job runs Sundays 03:00 UTC,
  executes `cargo update --workspace`, creates PR if changes detected.
- **Zizmor security scan** — static analysis of GitHub Actions workflows detects
  injection, untrusted input, and other security anti-patterns. Runs only on PRs.
- **Actionlint syntax check** — validates YAML syntax of all workflow files. Runs only on PRs.
- **Dependabot for actions and crates** — `.github/dependabot.yml` creates weekly PRs
  for GitHub Actions updates and Rust crate updates. Groups by major/minor/patch.
- **`.gitattributes` LF normalization** — forces LF line endings in all text files,
  preventing CRLF issues on Windows that break `cargo fmt --check`.

### Security
- **Permissions hardened per job** — top-level `permissions: contents: write packages: write
  id-token: write attestations: write checks: write discussions: write` for release;
  per-job `permissions:` blocks in CI for least-privilege.
- **`continue-on-error: true` on GPG step** — missing GPG key does not block release;
  optional enhancement.
- **No `pull_request_target` triggers** — workflows never run with write permissions
  on PRs from forks.

## [0.6.8] - 2026-06-05

### Fixed
- **CI: exit 127 `jaq: command not found` in `github_release` job of release workflow**
  - Root cause: `release.yml` (lines 625-626) used `jaq` (Rust binary) to parse JSON
    response from GitHub REST API, but the GitHub Actions Ubuntu 24.04 runner only
    has `jq 1.7` pre-installed — `jaq` is not part of the standard runner image.
    Bug introduced by commit `7f489b5` (2026-06-05) when bypassing the broken
    `softprops/action-gh-release` action.
  - Solution: replaced `jaq` with `jq` (pre-installed, syntax-compatible) and added
    explicit fail-fast validation for extracted `UPLOAD_URL` and `RELEASE_ID` values
    to surface clear diagnostic messages on malformed API responses.
  - Reference: <https://github.com/actions/runner-images/blob/main/images/ubuntu/
    Ubuntu2404-Readme.md> (Tools section lists `jq 1.7`, `jaq` is absent)

## [0.6.7] - 2026-06-05

### Fixed
- **CI: post-mortem completo do incident-publish-101-2026-06-05** (hardening release pipeline)
  - Added `preflight` job validating tag==Cargo.toml, SemVer, CHANGELOG, no AI Co-authored-by
  - Added guard de versão duplicada em `crates_io` job (zizmor: secrets-outside-env resolvido)
  - cargo publish com timeout 300s + 3 retries (network resilience)
  - Concurrency group por tag+sha (impede runs paralelos)
- **CI: 18+ Node.js 20 deprecation warnings**
  - Updated actions to v6 (Node 24 native)
  - Updated softprops/action-gh-release v2 → v3
  - Added `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` as belt-and-suspenders
- **CI: zizmor security scan: 134 findings → 0**
  - SHA pinning para 11 actions (unpinned-uses)
  - per-job least-privilege permissions (excessive-permissions)
  - comments + inline trailing em todas as permissions
  - secrets em env: job-level + GitHub Environments dedicados
  - ${{ ... }} em run: mitigated via env vars (template-injection)
  - dtolnay/rust-toolchain substituído por setup via rustup (superfluous-actions)
  - caches removidos do release.yml (cache-poisoning)
- **CI: actionlint 0 erros em ambos workflows**
- **CI: zizmor zero findings (exit 0)**
- **CI: dependabot.yml para auto-update semanal de actions e crates**
- **CI: .gitattributes força LF line endings em todos os arquivos de texto**
- **clippy: `#[cfg(feature = "chrome")]` redundante removido de src/lib.rs:74**
  - browser.rs:25 já tem `#![cfg(feature = "chrome")]` que cobre o módulo
- **clippy: SAFETY comments adicionados a todos os Windows unsafe blocks em src/platform.rs**
  - 5 blocos unsafe agora têm `// SAFETY:` comments explicando precondições
  - Necessário para `clippy::undocumented_unsafe_blocks` (deny em rust 1.96+)
- **test: tests incompatíveis com Windows marcados com `#[cfg(unix)]`**
  - `rejeita_path_absoluto_etc` (testa /etc/shadow)
  - `rejeita_path_absoluto_usr` (testa /usr/bin/evil)
  - Ambos passam em Linux/macOS, pulam em Windows onde os paths são regulares

### Added
- **SBOM CycloneDX generation em release workflow**
  - `cargo cyclonedx --format json` produz `sbom.cdx.json`
  - Compliance com EU Cyber Resilience Act
- **SLSA build provenance via `actions/attest-build-provenance@v2`**
- **cosign keyless OIDC signing** (todos os binários + SHA256SUMS.txt)
- **SHA256SUMS publicado com cada release** (gerado por target)
- **GPG tag signing** (opcional, `continue-on-error: true` se chave ausente)
- **Pre-flight job em release workflow** (9 gates + 1 dry-run)
- **Attestation job** (SBOM + cosign + SLSA em 1 job)
- **scheduled_update Cron semanal** (cargo update automático)
- **Zizmor security scan em CI** (zero findings)
- **Actionlint syntax check em CI** (zero erros)
- **Dependabot para actions e Rust crates** (PRs semanais)

### Security
- **Permissions endurecidas per-job** (least-privilege)
- **Persist-credentials: false em 18/18 actions/checkout** (artipacked)
- **Sem triggers `pull_request_target`** (forks não rodam com write)
- **SHA pinning completo** (11 actions com 40 chars + version comment)

## [0.6.6] - 2026-06-05

### Fixed
- **docs.rs build failure (Build #3487310) caused by `#[doc(cfg(...))]` becoming unstable**
  - Removed `#[cfg_attr(docsrs, doc(cfg(feature = "chrome")))]` from `src/lib.rs:70`
  - Root cause: in Oct 2025 the Rust team merged `doc_auto_cfg` into `doc_cfg` (rust-lang/rust#43781),
    making `#[doc(cfg(...))]` require `#![feature(doc_cfg)]` (nightly-only) on the crate root.
    The build failed with `error[E0658]: #[doc(cfg)] is experimental` on nightly `1.98.0`.
  - The feature gating itself is preserved: `#[cfg(feature = "chrome")]` still excludes
    `pub mod browser` from default builds. The module-level docstring in `src/browser.rs`
    already documents the feature requirement explicitly.
  - `cargo doc --all-features` and `RUSTDOCFLAGS="--cfg docsrs" cargo doc --all-features`
    both pass without warning or error.

## [0.6.5] - 2026-06-05

### Fixed
- **MP-26 — Windows HANDLE cast broken in `windows-sys 0.59+`** (`src/platform.rs:51-63`)
  - `HANDLE` mudou de `isize` para `*mut c_void` upstream (`microsoft/windows-rs`, `raw-window-handle#171`)
  - Substituído `handle != 0 && handle != usize::MAX` por `!handle.is_null() && handle != INVALID_HANDLE_VALUE`
  - Removidos casts inválidos `handle as isize` (a assinatura moderna aceita `HANDLE` direto)
  - Atualizado o `// SAFETY:` comment para documentar nulidade e sentinela Win32
- **CI: `validate` falhava em todos os 3 SOs** (Linux/macOS/Windows) por 6 erros de clippy
  - 3× `clippy::doc_markdown` (`PowerShell`, `rules_rust.md`, `TempDir`) em `src/platform.rs` e `src/browser.rs`
  - 1× `clippy::needless_return` em `src/browser.rs:149`
  - 2× `missing_debug_implementations` em `src/browser.rs:223` (`ChromeBrowser`) e `src/content_fetch.rs` (`CircuitBreakerMap`)

### Added
- **WS-11 — Property-based invariants for HTML parsers** (`src/extraction.rs` +5 testes)
  - Invariante: inputs vazios/quebrados retornam `Vec` vazio sem panic
  - Invariante: positions são densos e 1-based
  - Invariante: URLs absolutos (`http`/`https`) ou vazios
  - Invariante: extração é idempotente
  - Invariante: HTML malformado não causa panic
  - **Zero dependência nova** (apenas stdlib + `#[test]`)
- **WS-12 — Per-host circuit breaker** (`src/content_fetch.rs`)
  - Threshold: 3 falhas consecutivas abrem o circuito
  - Cooldown: 30s antes de half-open probe
  - Integração em `enrich_with_content` antes de cada fetch
  - `BreakerDecision::{Allow, Reject}` para inspeção
  - **Zero dependência nova** (`std::sync::Mutex<HashMap>`)
- **WS-23 — `Retry-After` header test** (`tests/integration_wiremock.rs`)
  - Mock retorna 429 com `retry-after: 2`
  - Asserção: `elapsed_ms >= 1500` (delay mínimo respeitado)
  - Usa `wiremock` 0.6 já em dev-deps
- **WS-25 — `indicatif` ProgressBar para crawls longos** (`src/content_fetch.rs`)
  - `indicatif = "0.18"` adicionado
  - Bar com template `[{elapsed_precise}] {bar:40.cyan/blue} {pos:>4}/{len:4} {msg}`
  - Auto-detecta TTY (esconde em pipes)
  - `progress.finish_and_clear()` ao final
- **Lints preventivos FFI** (`Cargo.toml`)
  - `improper_ctypes = "deny"` (rejeita casts FFI inválidos)
  - `improper_ctypes_definitions = "deny"` (rejeita definições incorretas)

### Tests
- 333 testes passando (243 lib + 24 + 3 + 5 + 10 + 10 + 14 + 18 + 6 doc)
- 6 novos testes de invariantes em `extraction.rs` (WS-11)
- 4 novos testes de circuit breaker em `content_fetch.rs` (WS-12)
- 1 novo teste de Retry-After em `integration_wiremock.rs` (WS-23)
- `cargo fmt --all --check` clean
- `cargo clippy --all-targets --all-features --locked -- -D warnings` clean
- `cargo publish --dry-run --locked --allow-dirty` clean

## [0.6.4] - 2026-06-03

### Added
- **WS-26 — Adaptive anti-bot identity rotation** (new `src/identity.rs` module)
  - 12-identity pool (4 browser families × 3 platforms) for adaptive rotation
  - `IdentityProfile::shuffled_headers()` produces seed-deterministic header order
  - `IdentityPool::rotate_on_block()` implements a 5-level cascade: same identity → same family/different platform → different family/same platform → different family+platform → random
  - `BrowserFamily` and `Platform` enums with canonical English names
  - 5 unit tests covering pool size, cascade level, determinism, header shape, tag stability
- **New CLI flags** (additive, no breaking changes)
  - `--probe` — pre-flight health check (sends 1 minimal request, reports status/latency/Set-Cookie as JSON)
  - `--identity-profile` — pin the session to a specific identity (`auto`, `chrome-win`, `chrome-mac`, `chrome-linux`, `edge-win`, `firefox-linux`, `safari-mac`). `auto` is default.
- **New JSON metadata fields** (additive, `Option` + `skip_serializing_if = "Option::is_none"`)
  - `metadados.identidade_usada` — string tag of the identity that produced the response
  - `metadados.nivel_cascata` — cascade level reached during the request

### Changed
- **Version rollback**: `0.7.0` (unpublished) → `0.6.4` to preserve the in-development feature set under a stable patch number
- All existing CLI flags, JSON output schemas, and exit codes remain unchanged — strictly additive changes

### Tests
- 5 new identity unit tests (313 total tests passing, up from 308)
- All 224 lib tests + 83 integration tests + 6 doc tests pass
- `cargo clippy --lib --bins -- -D warnings` clean
- `cargo fmt --check` clean

## [0.7.0] - 2026-06-01

### Changed
- Complete internationalization: ~600 identifiers renamed PT→EN across 15 source files (struct fields, local variables, parameters, production functions, test functions)
- Module `fetch_conteudo` renamed to `content_fetch`
- Test files `integracao_*.rs` renamed to `integration_*.rs`
- Replaced `anyhow` with typed `CliError` across all 11 modules — zero external error crate dependency
- `output.rs`: all formatting functions renamed (`formatar_*` → `format_*`, `escrever_*` → `write_*`)
- `config_init.rs`: struct fields renamed with `#[serde(rename)]` to preserve JSON backwards compatibility
- `search.rs`: `RetryResult` and `AggregatedSearchResult` fields renamed PT→EN
- `types.rs`: `Config` fields `perfil_browser`/`corresponde_plataforma_ua`/`caminho_chrome` → `browser_profile`/`match_platform_ua`/`chrome_path`

### Added
- Loom concurrency tests (`tests/loom_atomics.rs`) — validates `AtomicBool` visibility across threads
- Criterion benchmarks (`benches/extraction_bench.rs`) — HTML extraction performance baselines
- Doc comments for all 70 previously undocumented public items — zero `missing_docs` warnings
- `.ingest-queue.sqlite` added to `.gitignore` and `Cargo.toml` exclude

### Fixed
- RUSTSEC-2026-0097: updated `rand` 0.8.5 → 0.8.6
- RUSTSEC-2026-0104: updated `rustls-webpki` 0.103.12 → 0.103.13

### Security
- `deny.toml`: added `skip-tree` for 30 transitive duplicate crates (chromiumoxide, scraper, console-subscriber ecosystems)

### Known Limitations
- Loom tests require `RUSTFLAGS="--cfg loom"` which conflicts with `hyper-util` — tests compile but cannot run until upstream resolves the cfg conflict
- JSON output field names remain in Portuguese Brazilian (`posicao`, `titulo`, `resultados`, etc.) — BY DESIGN since v0.2.0

## [0.6.3] - 2026-04-17

### Changed
- Translated all 96 doc comments (`///` and `//!`) across 19 source files from Portuguese to English — docs.rs now renders fully in English for international crates.io audience.
- No code behavior, public API, or JSON output fields changed.

## [0.6.2] - 2026-04-17

### Added
- 19 novos arquivos de documentação — conformidade completa com rules_rust_documentacao.md (28 gaps G01-G28)
- Documentação bilíngue EN+PT: HOW_TO_USE, CROSS_PLATFORM, AGENTS-GUIDE, COOKBOOK.pt-BR, INTEGRATIONS.pt-BR
- CODE_OF_CONDUCT.md + CODE_OF_CONDUCT.pt-BR.md — Contributor Covenant 2.1
- README.pt-BR.md, CHANGELOG.pt-BR.md, CONTRIBUTING.pt-BR.md, SECURITY.pt-BR.md
- docs/AGENTS.pt-BR.md — guia imperativo para LLMs em português
- docs/AGENTS-GUIDE.md + docs/AGENTS-GUIDE.pt-BR.md — guia persuasivo bilíngue
- llms.txt — arquivo compacto de orientação para LLMs (< 50 KB)
- llms-full.txt — concatenação completa de docs para contexto longo de LLMs
- eval-queries.json × 2 — 20 queries de avaliação EN + 20 PT-BR para skill testing

### Changed
- README.md — link para README.pt-BR.md + quick install antes da linha 30
- CONTRIBUTING.md — MSRV Rust 1.75 explícito + PR checklist 8 itens + branching strategy + nextest
- SECURITY.md — tabela de versão específica v0.6.2 + política de embargo 90 dias + zero bold + zero emojis
- skill/SKILL.md (EN+PT) — seção Workflow com 5 passos numerados verificáveis

## [0.6.1] - 2026-04-17

### Fixed
- `--timeout 0` now returns exit 2 (invalid config) instead of executing a search with zero timeout and returning exit 5.
- `--output /tmp/../../etc/passwd` now returns exit 2 (invalid config) instead of exit 1 (runtime OS error) — path traversal validation moved to `montar_configuracoes()`, before the pipeline starts.

### Added
- `validar_timeout_segundos()` method on `CliArgs` — rejects values of 0 with a descriptive error.
- Early path traversal check in `montar_configuracoes()` — calls `paths::validate_output_path()` at config validation time, not at write time.
- 2 E2E regression tests: `timeout_zero_retorna_exit_2` and `output_com_path_traversal_retorna_exit_2`.
- 1 unit test: `validar_timeout_segundos_rejeita_zero`.

## [0.6.0] - 2026-04-16

### Security
- Browser fingerprint profiles per-family previnem detecção anti-bot do DuckDuckGo.
- Headers `Sec-Fetch-*` e Client Hints por família imitam sessão de navegador real.
- `Accept-Language` com q-values RFC 7231 elimina fingerprint de UA genérico.
- Detecção de bloqueio silencioso com limiar de 5 KB previne resultados truncados.

### Added
- `BrowserFamily` enum — variantes `Chrome`, `Firefox`, `Edge`, `Safari`.
- `BrowserProfile` struct — encapsula família, versão e conjunto de headers por família.
- Headers `Sec-Fetch-Dest`, `Sec-Fetch-Mode`, `Sec-Fetch-Site` por família em `http.rs`.
- Client Hints (`Sec-Ch-Ua`, `Sec-Ch-Ua-Mobile`, `Sec-Ch-Ua-Platform`) para Chrome e Edge.
- Detecção de HTTP 202 anomaly em `search.rs` com backoff exponencial automático.
- Detecção de bloqueio silencioso — resposta com menos de 5 000 bytes é tratada como bloqueio.
- `BrowserProfile` propagado via `Config` para todos os módulos da pipeline.
- Headers de paginação com `Sec-Fetch-Site: same-origin` para imitar navegação real.

### Changed
- `Accept-Language` atualizado para `pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7` conforme RFC 7231.
- `Accept` header agora reflete o perfil completo do browser por família.
- Delays de paginação aumentados de 500–1 000 ms para 800–1 500 ms.
- Limiar de bloqueio silencioso aumentado de 100 para 5 000 bytes.

## [0.5.0] - 2026-04-16

### Security
- Path traversal validation on `--output` — rejects `..` components and writes to system directories (`/etc`, `/usr`, `C:\Windows`).
- Proxy credential masking — error messages no longer expose passwords from `--proxy http://user:pass@host` URLs.

### Added
- `src/paths.rs` — centralized path validation, parent directory creation, and Unix permission application.
- `src/signals.rs` — centralized SIGPIPE restoration (Unix) and Ctrl+C/SIGINT handler (cross-platform).
- `ErroCliDdg` enum with `thiserror` — 11 typed error variants with `exit_code()` and `codigo_erro()` methods.
- `mascarar_url_proxy()` in `http.rs` — redacts credentials from proxy URLs in error context.
- 21 new unit tests across `paths.rs`, `signals.rs`, `error.rs`, and `http.rs`.

### Changed
- `thiserror = "2"` added to dependencies for structured domain errors.
- `src/main.rs` reduced from 63 to 23 lines — signal handling extracted to `signals.rs`.
- `src/output.rs` file writes now validate paths via `paths::validate_output_path()` before I/O.
- `deny.toml` updated with RUSTSEC-2026-0097 exception (rand 0.8 unsound with custom logger — not applicable).

## [0.4.4] - 2026-04-16

### Fixed
- SIGPIPE restored to SIG_DFL on Unix — pipes to `jaq`, `head`, and other consumers no longer lose stdout silently.
- BrokenPipe errors detected in anyhow chain and treated as exit 0 (not exit 1) at all output boundaries.

### Added
- `--help` now shows EXIT CODES (0–5) and PIPE USAGE sections via `after_long_help`.
- 3 E2E tests for pipe regression: exit codes in help, short help exclusion, stdout byte count.
- README troubleshooting item 7: "Pipe to jaq/jq returns empty" with PIPESTATUS diagnostic (EN + PT).
- `docs_rules/rules_rust.md`: SIGPIPE + BrokenPipe added to I/O checklist.
- `docs/AGENT_RULES.md`: R24 pipe safety rule with PIPESTATUS diagnostic.
- `docs/COOKBOOK.md`: Recipe 16 pipe diagnostic (EN + PT).
- `docs/INTEGRATIONS.md`: pipe safety clause in baseline contract.
- Exit code branching section in both skill files (EN + PT).

## [0.4.3] - 2026-04-15

### Changed

- **`README.md`** — Nova seção persuasiva "Agent Skill" (EN + PT) posicionada
  entre a tabela de agentes e a seção de Documentação, no pico de atenção do
  leitor. Copywriting AIDA destacando a skill bilíngue empacotada em `skill/`:
  auto-ativação semântica sem slash command, 14 seções canônicas MUST/NEVER,
  contrato JSON anti-alucinação, economia de tokens em cada turno de busca,
  instalação em um comando (`git clone` + `cp -r`). Benefícios explícitos para
  LLMs (decisão automática de quando buscar) e desenvolvedores (zero prompt
  engineering, zero tool registration). Tarball do crates.io inalterado —
  skills continuam vivendo apenas no GitHub.

## [0.4.2] - 2026-04-15

### Added

- **`skill/duckduckgo-search-cli-pt/SKILL.md`** e
  **`skill/duckduckgo-search-cli-en/SKILL.md`** — Skills bilíngues para Claude
  Code, Claude Agent SDK e plataformas compatíveis com Agent Skills. Cada
  skill traz frontmatter YAML com `name` único por idioma e `description`
  carregado de triggers semânticos para auto-invocação, além de 14 seções
  H2 canônicas (Missão, Contrato de Invocação, Proibições Absolutas,
  Parsing com `jaq`, Schema JSON, Exit Codes, Batch, Fetch-Content,
  Endpoint, Retries, Receitas, Validação, Memória, Regra de Ouro).
  Publicadas no GitHub, excluídas do tarball do crates.io.

### Changed

- **`docs/AGENT_RULES.md`** (833 linhas, +7,6%) — Reescrita editorial
  aplicando copywriting AIDA: cada regra abre com benefício mensurável,
  linguagem imperativa MUST/NEVER reforçada, zero narrativa decorativa,
  zero negrito com asteriscos duplos, zero separador visual `---` entre
  seções. Bilíngue EN+PT espelhado com tom idêntico.
- **`docs/COOKBOOK.md`** (1082 linhas, −3,1%) — Cada receita abre com o
  ganho concreto antes do comando, bullets curtos de 8 a 15 palavras,
  pipelines `jaq` + `xh` + `sd` preservados intactos.
- **`docs/INTEGRATIONS.md`** (1212 linhas, +1,3%) — 16 agentes com tabela
  comparativa textual, snippets determinísticos por agente, zero emoji.

### Meta

- `Cargo.toml` exclude ampliado para cobrir `skill/` e `skill/**` — skills
  ficam no GitHub e fora do tarball publicado em crates.io.

## [0.4.1] - 2026-04-14

### Added

- **`docs/AGENT_RULES.md`** (773 linhas) — Regras imperativas bilíngue (EN+PT)
  com 30+ rules `MUST`/`NEVER` (R01..R30) para LLMs/agentes invocarem a CLI
  em produção. Cobre: invariantes core, contrato JSON, rate limiting, error
  handling, performance, segurança, anti-patterns. Quick Reference Card no
  final.
- **`docs/COOKBOOK.md`** (1117 linhas) — 15 receitas copy-paste bilíngue
  combinando `duckduckgo-search-cli` + `jaq` + `xh` + `sd` para casos reais:
  research consolidado, ETL multi-query, extração de domínios, monitoramento
  com filtro temporal, content extraction com `--fetch-content`, comparação
  top 5 vs top 15, NDJSON para pipelines, function wrappers para bash.
- **`docs/INTEGRATIONS.md`** (1196 linhas) — Snippets prontos para 16
  agentes/LLMs: Claude Code, OpenAI Codex, Gemini CLI, Cursor, Windsurf,
  Aider, Continue.dev, MiniMax, OpenCode, Paperclip, OpenClaw, Google
  Antigravity, GitHub Copilot CLI, Devin, Cline, Roo Code. Cada agente
  documenta: pitch, mecanismo de shell, setup, snippet básico, snippet
  multi-query, system prompt rule, caveats.
- Seção **Documentation** no README.md (EN + PT) linkando os 3 guias.

### Fixed

- README.md badge cluster e referências internas conferidas contra
  `daniloaguiarbr/duckduckgo-search-cli` (repo canônico).

## [0.4.0] - 2026-04-14

### Changed (BREAKING)

- **Default de `--num` / `-n`**: alterado de "todos os resultados da primeira
  página" (~11) para **15**, com **auto-paginação** automática. Quando o
  número efetivo excede 10, o binário agora busca **2 páginas** por query
  para satisfazer o teto solicitado, desde que `--pages` não tenha sido
  customizado pelo usuário.
- **Auto-paginação automática**: se `--num > 10` (seja porque o usuário
  passou explicitamente ou porque o default 15 foi aplicado) E `--pages`
  não foi customizado (continua no default 1), o binário auto-eleva
  `--pages` para `ceil(num/10)` respeitando o teto de 5 páginas validado
  por `validar_paginas`. Impacto: mais requests por query (2x no caso
  default) e latência marginalmente maior, porém com cobertura completa
  dos resultados solicitados.

### Added

- Documentação no comentário do flag `--num` em `cli.rs` descrevendo a
  nova semântica de default e auto-paginação.
- 4 novos testes unitários em `lib.rs::testes`:
  `montar_configuracoes_aplica_default_num_15_quando_omitido`,
  `montar_configuracoes_respeita_pages_explicito_acima_de_1`,
  `montar_configuracoes_auto_pagina_quando_num_maior_que_10`,
  `montar_configuracoes_nao_auto_pagina_quando_num_10_ou_menos`.
- 2 novos testes wiremock em `tests/integracao_wiremock.rs`:
  `testa_default_num_15_auto_pagina_2_paginas`,
  `testa_auto_paginacao_respeita_pages_explicito`.

### Migration Guide

- **Quem quer o comportamento antigo** (1 página, ~11 resultados):
  passe `--pages 1 --num 10` explicitamente. O `--pages 1` explícito é
  indistinguível do default (trade-off aceito: `paginas > 1` é o único
  sinal de "customização"), então o mais seguro é combinar com `--num 10`
  para garantir que nada será auto-paginado.
- **Quem já passava `--num 5`** (ou qualquer valor <= 10): comportamento
  **inalterado** (sem auto-paginação, 1 página).
- **Quem já passava `--num 20 --pages 2`** ou similar: comportamento
  **inalterado** (respeita explícito do usuário).
- **Quem confiava no default sem flags**: agora recebe até 15 resultados
  em vez de ~11, com 1 request extra por query. Para restaurar o antigo,
  passe `--pages 1 --num 10`.

## [0.3.0] - 2026-04-14

### Changed (BREAKING)

- **Schema JSON**: campo `buscas_relacionadas` REMOVIDO de `SearchOutput` e
  `MultiSearchOutput.buscas[i]`. O endpoint `html.duckduckgo.com/html/` não
  expõe related searches no DOM atual; manter o campo sempre vazio era ruído.
  Pipelines que parseavam `.buscas_relacionadas` precisam ajuste.
- **Pool de User-Agents**: removidos UAs de browsers de texto (`Lynx 2.9.0`,
  `w3m/0.5.3`, `Links 2.29`, `ELinks 0.16.1.1`) que faziam DuckDuckGo retornar
  HTML degradado. Substituídos por 6 UAs modernos validados empiricamente
  contra o `/html/` endpoint: Chrome 146 (Win/Mac/Linux), Edge 145 Windows,
  Firefox 134 Linux, Safari 17.6 macOS. Firefox Win/Mac foram REMOVIDOS após
  retornarem HTTP 202 anomaly em validação real (heurística anti-bot do DDG).

### Fixed

- **Snippet duplicava título e URL no início**: o seletor padrão tinha
  fallback `.result__body` (container pai) que fazia `text()` recursivo
  capturar título+URL+snippet concatenados. Trocado por `.result__snippet`
  puro. Pipelines como `jaq '.resultados[].snippet'` agora retornam apenas
  o texto descritivo do resultado.
- **Título "Official site"**: DuckDuckGo renderiza literalmente este texto
  como label para domínios verificados (ex: prefeituras). O scraper agora
  detecta este caso e substitui pelo `url_exibicao` (ex: `saofidelis.rj.gov.br`).
  O texto original é preservado no novo campo opcional `titulo_original`
  para auditoria.

### Added

- Campo `titulo_original: Option<String>` em `SearchResult`. Presente
  apenas quando o título foi substituído por heurística (atualmente: caso
  "Official site"). Serializado com `#[serde(skip_serializing_if = "Option::is_none")]`
  — não aparece no JSON quando ausente.
- Resultados patrocinados (`.result--ad`) excluídos do container default
  via seletor `.result:not(.result--ad)`.

### Removed

- Função `extrair_buscas_relacionadas` em `src/search.rs` (dead code com
  seletor hardcoded que nunca encontrava nada).
- Seção `[related_searches]` em selectors default.

### Migration Guide (v0.2.x → v0.3.0)

- Pipelines `jaq '.buscas_relacionadas[]'`: campo não existe mais.
  Remover do filtro ou tratar `null`.
- Esperando snippet com prefixo título+URL? Agora vem só o texto descritivo
  — ajuste regex/parsing downstream se necessário.
- Confiando em `titulo == "Official site"` para detectar sites verificados?
  Use `titulo_original.as_deref() == Some("Official site")`.
- **CONFIG EXTERNO LEGADO**: usuários que rodaram `init-config` em versões
  anteriores possuem `~/.config/duckduckgo-search-cli/{selectors,user-agents}.toml`
  com defaults antigos (snippet com `.result__body` + UAs `Lynx`/`w3m`/etc.).
  Esses arquivos OVERRIDE os defaults embutidos. Para aplicar as correções
  desta versão, execute APÓS atualizar:
  ```
  duckduckgo-search-cli init-config --force
  ```
  O flag `--force` sobrescreve os arquivos externos. Backup recomendado se
  você editou manualmente para hotfix de seletores.

## [0.2.0] - 2026-04-14

### Changed (BREAKING)

Schema JSON serializado agora usa nomes de campo em **português brasileiro**,
alinhado com os exemplos `jaq` do README e com o invariante INVIOLÁVEL do
blueprint v2 do projeto ("Logs e nomes de campo em português brasileiro").

Pipelines que dependiam do schema em inglês da `v0.1.0` precisam atualizar
os seletores `jaq`. Tabela de renomeações:

| Antes (v0.1.0) | Depois (v0.2.0) |
|----------------|-----------------|
| `position` | `posicao` |
| `title` | `titulo` |
| `displayed_url` | `url_exibicao` |
| `content` | `conteudo` |
| `content_length` | `tamanho_conteudo` |
| `content_extraction_method` | `metodo_extracao_conteudo` |
| `execution_time_ms` | `tempo_execucao_ms` |
| `selectors_hash` | `hash_seletores` |
| `retries` | `retentativas` |
| `fallback_endpoint_used` | `usou_endpoint_fallback` |
| `concurrent_fetches` | `fetches_simultaneos` |
| `fetch_successes` | `sucessos_fetch` |
| `fetch_failures` | `falhas_fetch` |
| `chrome_used` | `usou_chrome` |
| `proxy_used` | `usou_proxy` |
| `engine` | `motor` |
| `region` | `regiao` |
| `results_count` | `quantidade_resultados` |
| `results` | `resultados` |
| `related_searches` | `buscas_relacionadas` |
| `pages_fetched` | `paginas_buscadas` |
| `error` | `erro` |
| `message` | `mensagem` |
| `metadata` | `metadados` |
| `queries_count` | `quantidade_queries` |
| `parallel` | `paralelismo` |
| `searches` | `buscas` |

Campos inalterados: `url`, `snippet`, `query`, `endpoint`, `timestamp`, `user_agent`.

### Fixed

- Pipelines documentados no README (`jaq '.resultados[].titulo'`, etc.) agora
  funcionam end-to-end. Em `v0.1.0` retornavam `null` por divergência do schema
  (bug reportado pelo usuário).

## [Unreleased]

### Added

- `LICENSE-MIT` and `LICENSE-APACHE` (dual-licensed per `Cargo.toml`, aligning the tarball with the SPDX declaration).
- `.pre-commit-config.yaml` with three hook groups: (1) pre-commit-hooks standard (trailing whitespace, EOF, YAML/TOML validity, mixed line endings), (2) Rust hooks (`cargo fmt` + `cargo clippy -D warnings`), (3) local `commit-msg` hook blocking `Co-authored-by:` from AI agents (mirrors the CI `commit_check` job). Reduces CI round-trips for trivial violations.
- `.gitattributes` forcing LF on `.rs` / `.toml` / `.sh` / `.yml` / `.md` / fixture HTML — prevents silent corruption when cloning on Windows with `core.autocrlf=true` (which would otherwise break shebangs, rustfmt, and content-extraction tests). Binary extensions (`.png`, `.woff2`, etc.) marked explicitly. `Cargo.lock` and `target/` flagged `linguist-generated` to exclude from GitHub language stats.
- `.editorconfig` normalizing UTF-8, LF, trailing-whitespace trim, and per-language indent (Rust/TOML 4, YAML/JSON/MD 2, Makefile tab) across VS Code, RustRover, vim, and other editors — eliminates spurious formatting diffs caused by per-dev settings drift.
- `.github/PULL_REQUEST_TEMPLATE.md` with the 10-gate checklist + project-specific constraints (no cache, no MCP, rustls-only, `println!` confined to `output.rs`, PT-BR identifiers).
- `.github/ISSUE_TEMPLATE/bug_report.yml` + `feature_request.yml` + `config.yml` — structured triage with platform dropdown (glibc/musl/NixOS/Flatpak/Snap/macOS ARM/macOS Intel/Windows/WSL), install method, and constraint verification. `config.yml` redirects security reports to Security Advisories and usage questions to Discussions.
- `Cross.toml` enabling `cross build --target <t>` for ARM64/ARMv7 Linux targets (musl + glibc + hard-float) from any x86_64 host with Docker/Podman — complements the native CI pipeline for developers without a GitHub Actions runner.
- `CONTRIBUTING.md` with the 10-gate validation matrix, coding standards (Brazilian Portuguese identifiers, rustls-only TLS, `output.rs` as the sole `println!` site), three-layer testing strategy, supply-chain guardrails, and the tag-driven release process.
- `.cargo/config.toml` exposing 8 developer aliases (`cargo check-all`, `cargo lint`, `cargo docs`, `cargo test-all`, `cargo cov`, `cargo cov-html`, `cargo publish-check`, `cargo pkg-list`) — each mirrors a CI job for local reproduction.
- Doctests in public API: `pipeline::combine_and_dedup_queries`, `content_fetch::extract_host`, and `search::format_kl` — compilable examples on docs.rs that double as regression tests.
- `SECURITY.md` documenting the private-disclosure workflow via GitHub Security Advisories, response SLA (72 h), scope (HTTP/HTML parsing, credential leaks, path traversal, TLS) and security design assumptions (stateless, rustls-only, no JS for search).
- `.github/dependabot.yml` enabling weekly automatic dependency updates for both `cargo` and `github-actions` ecosystems, with semantic grouping (dev-deps, tokio-ecosystem, tracing-ecosystem) and PR count limits.
- `rust-toolchain.toml` pinning `stable` with `rustfmt` + `clippy` components for reproducible dev/CI builds.
- `.github/workflows/release.yml` triggered by `v*.*.*` tags (and `workflow_dispatch` with `dry_run`) running the 5-stage release pipeline per `rules_rust.md` §19: validate → build_matrix (5 targets) → macos_universal (lipo) → github_release (with generated notes) → crates_io (publish gated on `CRATES_IO_TOKEN` secret).
- `msrv` job in `ci.yml` extracting `rust-version` from `Cargo.toml` and running `cargo check` on that toolchain to detect MSRV drift on every PR.
- `.github/workflows/ci.yml` enforcing the 10-gate validation matrix across Ubuntu, macOS, and Windows:
  - `cargo check` / `clippy -D warnings` / `fmt --check` / `doc -D warnings` / `test --all-features` on all three OSes.
  - `cargo llvm-cov --fail-under-lines 80` dedicated job on Ubuntu.
  - `cargo audit` + `cargo deny check advisories licenses bans sources` supply-chain gate.
  - `cargo publish --dry-run` + `cargo package --list` sensitive-file guard.
  - Static musl binary smoke test (`x86_64-unknown-linux-musl`) covering Alpine Linux and minimal containers.
  - `commit_check` job blocking `Co-authored-by:` trailers from AI agents in PRs.
- `deny.toml` with full four-axis supply-chain policy (advisories/licenses/bans/sources) and documented ignores for three transitive unmaintained advisories (`RUSTSEC-2025-0057 fxhash`, `RUSTSEC-2025-0052 async-std`, `RUSTSEC-2026-0097 rand`) with justification and revisit notes.
- 22 new tests raising coverage from 77.4% to 86.4% (lines): `tests/integration_pipeline.rs` (10), `tests/integracao_fetch_conteudo.rs` (3), and 9 inline tests for `output.rs` covering `emit_ndjson`, `emit_stream_text`, `emit_stream_markdown`, and the `PipelineResult` variants via `tempfile`.

### Changed

- `parallel.rs` coverage 50% → 81%; `pipeline.rs` 55% → 82%; `content_fetch.rs` 68% → 85%; `output.rs` 70% → 87%.

## [0.1.0] - 2026-04-14

### Added

- Core search pipeline against DuckDuckGo HTML endpoint via pure HTTP (`html.duckduckgo.com/html/`).
- Lite endpoint fallback via `--endpoint lite` for JavaScript-less pages.
- Multi-query mode with automatic deduplication, positional args, `--queries-file`, and stdin.
- Parallel fan-out of queries with `--parallel` (1..=20), bounded by `tokio::JoinSet` + `Semaphore`.
- `--pages` (1..=5) to collect multiple result pages per query.
- `--fetch-content` fetches each result URL via pure HTTP, applies readability, and embeds the cleaned text in the JSON output.
- `--max-content-length` (1..=100_000) truncates extracted content respecting word boundaries.
- Chrome headless fallback under `--features chrome` with cross-platform detection (Linux including Flatpak/Snap, macOS including Apple Silicon, Windows including registry paths) and stealth flags (`--disable-blink-features=AutomationControlled`, `--window-size=1920,1080`, `--no-first-run`, platform-specific `--no-sandbox`, `--disable-gpu`).
- `--chrome-path` flag to manually specify the Chrome/Chromium executable.
- `--proxy URL` + `--no-proxy` (HTTP/HTTPS/SOCKS5) with precedence over env vars.
- `--global-timeout` (1..=3600 s) wraps the whole pipeline in `tokio::time::timeout`.
- `--per-host-limit` (1..=10) rate-limits fetches per host via a per-host `Semaphore` map.
- `--match-platform-ua` narrows the user-agent pool to the current platform.
- `--stream` NDJSON mode emits one result per line as they are extracted.
- Four output formats: `json` (default), `text`, `markdown`, `auto` (TTY-aware).
- External configuration files: `selectors.toml` and `user-agents.toml` under XDG config dir, overriding embedded defaults.
- Subcommand `init-config` with `--force` and `--dry-run` to bootstrap user config files.
- Exit codes: `0` success, `1` runtime, `2` config, `3` block (HTTP 202 anomaly), `4` global timeout, `5` zero results.
- UTF-8 console initialization on Windows via `SetConsoleOutputCP(65001)`.
- Rustls-TLS everywhere for dependency-free cross-platform builds.
- `tracing` + `tracing-subscriber` with `RUST_LOG` honored; `--verbose` / `--quiet` flags.
- 163 unit + integration tests covering CLI parsing, config montage, HTTP extraction, parallel fan-out, selectors, and wiremock-backed search flows.

### Security

- All credentials (`--proxy user:pass@host`) are masked in logs.
- Output file creation applies Unix permissions `0o644`.

[Unreleased]: https://github.com/comandoaguiar/duckduckgo-search-cli/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/comandoaguiar/duckduckgo-search-cli/releases/tag/v0.1.0
