Skip to content

v0.7.6 — fix cargo install break (GAP-WS-48, alloc-no-stdlib conflict)

Choose a tag to compare

@daniloaguiarbr daniloaguiarbr released this 14 Jun 09:09
· 2 commits to main since this release

Changelog

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

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[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 --offlinesucesso (caminho de CI com lock travado).
    • cargo build --releasesucesso 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: InvalidConfigselector_config_invalid (should be invalid_config); PathErrorselector_config_invalid (should be path_error); BrokenPipehttp_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,
and this project adheres to Semantic Versioning.

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_rangeRngExt::random_range in 7 sites.
  • Replaced Rng::gen_boolRngExt::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.rsRrf(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 blockgaps.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 workflowcargo cyclonedx --format json produces
    sbom.cdx.json uploaded as artifact. Enables compliance with EU Cyber Resilience Act.
  • SLSA provenance attestationactions/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 releasesha256sum 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 controlconcurrency.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 updatescheduled_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
    • clippy::doc_markdown (PowerShell, rules_rust.md, TempDir) em src/platform.rs e src/browser.rs
    • clippy::needless_return em src/browser.rs:149
    • 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_chromebrowser_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.