v0.7.5 — 4-tool Windows preflight + docs/INSTALL-WINDOWS + audit batch
Changelog
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.5] - 2026-06-14
Fixed (audit batch 2026-06-14)
- P1-audit-1 (MEDIUM, error contract) —
src/lib.rsexecute_deep_researchwas usingprintln!("{json}")directly, violating the documented rule thatoutput.rsis the only module withprintln!(lib.rs doc-table line 34). Now delegates tooutput::print_line_stdoutwhich handlesBrokenPipecleanly (silent success on| head, generic error on real I/O failure). Closes the audit finding that the JSON contract fordeep-researchwas bypassing the central output abstraction. - P1-audit-2 (LOW, code clarity) — Removed the
unreachable!("handled above")arm in the subcommand dispatch by folding theDeepResearchbranch into the mainmatch(and dropping the precedingif 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::Cancellednow maps to exit code130(POSIX: 128 + SIGINT(2)) instead of1(generic error). Shell sessions can now distinguish user-initiated Ctrl-C from real runtime failures, and process supervisors (e.g. CI runners,set -escripts) treat cancellation asexit 130per convention. - P1-audit-4 (LOW, error code mapping) — Three string error code mappings were semantically wrong:
InvalidConfig→selector_config_invalid(should beinvalid_config);PathError→selector_config_invalid(should bepath_error);BrokenPipe→http_error(should bebroken_pipe). New constants added:codes::INVALID_CONFIG,codes::PATH_ERROR,codes::BROKEN_PIPE. All three string mappings now use their dedicated constant. Consumers parsing theerrorfield 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 to0.7.5. Closes the docs.rs cross-link drift. - P2-audit-7 (MEDIUM, distribution hygiene) —
Cargo.toml[build-dependencies]now includesclapandclap_mangen = "0.2". The existingbuild.rswas extended to call a newgenerate_man_page()function that emitsduckduckgo-search-cli.1inOUT_DIRusing a best-effort mirror of thesrc/cli.rsCLI 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.tomllistedarmv7-unknown-linux-musleabihfas a developer convenience target, but the comment also claimed "5 principais" targets were covered byrelease.yml(false: release.yml only coversx86_64-unknown-linux-muslandaarch64-apple-darwin). Removed thearmv7-unknown-linux-musleabihfblock 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 forCancelled.exit_code() == 130,Cancelled.error_code() == "cancelled",BrokenPipe.error_code() == "broken_pipe",PathError.error_code() == "path_error",InvalidConfig.error_code() == "invalid_config". Totalerror::tests: 5 tests, all pass.
Fixed
- GAP-WS-29 (CRITICAL, build experience, Windows) —
cargo installon 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 crypticfailed to execute command: program not found / is 'cmake' not installed?. Thebuild.rspreflight is now extended to detect this and abort in SECONDS with the exact fix (winget install -e --id Kitware.CmakeOR 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.rspreflight now detects both and aborts with the fix (open a Developer PowerShell for VS 2022, or runLaunch-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.rspreflight 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.mdline 561 andskill/duckduckgo-search-cli-pt/SKILL.mdline 565 still claimed "Pre-built binaries fromcargo installare unaffected" / "Binários pré-compilados docargo installnão são afetados". This was already false in v0.7.4 (onlyllms.txtandREADME*.mdwere corrected); now corrected in the skills too. crates.io NEVER distributes binaries;cargo installalways 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.rspreflight + escape hatches. - GAP-WS-35 (MEDIUM, documentation) —
llms-full.txt(line 273-305, embedding ofdocs/HOW_TO_USE.md) claimed "Pre-built binaries require no Rust installation" without qualifying that this is ONLY true for GitHub Releases binaries.cargo installalways requires Rust and always compiles from source. Now qualified. - GAP-WS-36 (MEDIUM, documentation) —
docs/CROSS_PLATFORM.mdline 193 andREADME.mdline 336 andREADME.pt-BR.mdline 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.rsv0.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 theDDG_SKIP_*_CHECKescape hatches.
Changed
scripts/install-windows.ps1— refactored to use genericFind-ToolandInstall-Toolhelpers; 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 exactLaunch-VsDevShell.ps1instruction instead. New--check-onlymode 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) + 2known_*dirfunctions. 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.tomlversion 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 installfalhava 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 exigeenable_language(ASM_NASM)quandoNOT OPENSSL_NO_ASMem Windows x86/x86_64; (CR2) o build script dobtls-sysv0.5.6 TEM um ramoOPENSSL_NO_ASM=YESpara Windows (build/main.rs:314-318), mas ele é INALCANÇÁVEL em builds nativos pelo early-returnhost == target(build/main.rs:231); (CR3) o instalador do NASM não ajusta o PATH e o Visual Studio não incluinasm.exe; (CR4) a documentação afirmava incorretamente que binários Windows eram pre-built (crates.io não distribui binários). Vergaps.mdGAP-WS-28. - Novo
build.rscom preflight fail-fast: em targetwindows-msvcnativo, detectanasm.exeausente 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 caminhoOPENSSL_NO_ASMdo btls-sys).
Added
scripts/install-windows.ps1— instalação automatizada e consentida no Windows: detecta NASM, instala viawinget(fallbackchoco), corrige o PATH da sessão e rodacargo install duckduckgo-search-cli --lockedrepassando argumentos extras.- CI: passo explícito de verificação/instalação de NASM (
choco install nasm -y) nos jobs Windows deci.ymlerelease.yml— elimina a dependência implícita do NASM pré-instalado na imagemwindows-2022(se a imagem mudar, o build não quebra silenciosamente).
Changed
README.md,README.pt-BR.md,llms.txt,llms.pt-BR.txtedocs/CROSS_PLATFORM*.md: removido o claim FALSO de que binários Windows/macOS eram "pre-built and unaffected" —cargo installSEMPRE compila do source. Pré-requisito NASM documentado para Windows MSVC, com referência aoscripts/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 ramoOPENSSL_NO_ASMinalcançá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 5retornavaquantidade_resultados: 0em macOS ARM64 mesmo com IP compartilhado com Windows 10. Causa raiz: fingerprint TLS dorustlsé reconhecível pelo Cloudflare Bot Management (vetor JA4_o), disparando CAPTCHA interstitial em HTTP 200. - Substituído
reqwest 0.12+rustls-tlsporwreq 6.0.0-rc.29+ BoringSSL (boring2v4.15.11) +wreq-util 3.0.0-rc.12. BoringSSL embarcado produz JA4_o idêntico ao Chrome/Safari real, eliminando o CAPTCHA. Ver ADRdocs/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-warmuppara desabilitar a requisiçãoGET https://duckduckgo.com/de warm-up. - Flag
--no-cookie-persistencepara manter cookies apenas em memória. - Flag
--cookies-path <PATH>para sobrescrever o local padrão docookies.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) esrc/wreq_cookie_adapter.rs(JSON <->wreq::cookie::Jarbridge).
- Flag
- PR3 — feature
probe-deep(CAPTCHA interstitial detection):- Flag
--probe-deepque executa uma query real e classifica o body comookoucaptchabaseado em marcadores Cloudflare/DuckDuckGo. - Flag
--allow-lite-fallback(opt-in) para fallback automático do endpointhtmlparalitequando interstitial é detectado. - Módulo
src/probe_deep.rscomdetectar_interstitial()esugestao_mitigacao(). - Reporta JSON com
status,cascata_motivo,sugestao_mitigacao,http_status,latency_ms.
- Flag
Changed
- Stack TLS trocada de rustls para BoringSSL via wreq. Build agora requer
cmake,perl,pkg-config,libclang-devno Linux. Documentado emdocs/CROSS_PLATFORM.mde ADR-0001. - ADR
docs/decisions/0001-tls-boring-via-wreq.mdregistra 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 porwreq). time 0.3.47agora é puramente transitivo (vinha como dep direta para sobrescrever transitivo doreqwest).
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
IdentityPoolda v0.6.4 já geraAccept-Languagecoerente com--country, e a persistência de cookies reduz a frequência de sessões "cold". Ogaps.mdmantém o status "RESOLVIDO PARCIALMENTE" até validação cross-OS do operador. - O
time 0.3.47pin emCargo.tomlfoi removido.timeagora é transitivo puro dewreqe suas deps. CI deve continuar verde porquewreqpuxatime 0.3.47+. - Test count: 292 lib (vs 279 em v0.7.2) + 18 wiremock + outras integrações = 0 falhas.
- Build verificado:
cargo build --releaseverde (40s),cargo test --libverde,cargo test --testsverde,cargo clippy --all-targets -- -D warningsverde.
[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 — therandom_range/random_bool/randomconvenience
methods moved fromRngtoRngExtin rand 0.10.0). Updated the
uselines insrc/identity.rs,src/parallel.rs, and
src/search.rsto importRngExtinstead ofRng. This unblocks
cargo check,build,test,clippy,doc,publish --dry-run,
validate,musl smoke,msrv, andcoveragejobs (all cascading
failures of the same root cause). - CI:
supply chain (audit + deny)job failing on RUSTSEC-2026-0009
(time 0.3.40denial-of-service via stack exhaustion when parsing
RFC 2822 date headers, severity 6.8 medium). Resolved by upgrading
timeto0.3.47(the patched release). The defensive ignore in
deny.tomlfor this advisory is now obsolete and has been removed.
Changed
randbumped 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 theRngExttrait as
the new home for the convenience methods.rust-versionbumped from 1.75 to 1.88 (matchestime0.3.47
MSRV and therand0.10 ecosystem). All other crates still compile
on 1.88+.timepinned to0.3.47as a direct dependency to override the
transitivetime 0.3.40pulled in bycookie_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 torand 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
rand0.8 torand0.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 importsrand::RngExtfor the
random_range/random_bool/randommethods. rust-versionbumped from 1.75 to 1.88 (matchestime0.3.47 MSRV
and therand0.10 ecosystem). All other crates still compile on 1.88+.reqwestfeaturesgzipandbrotliremoved: reqwest 0.12 dropped
theClientBuilder::gzip/brotlibuilder methods. Decompression is now
enabled via the standardAccept-Encoding: gzip, brrequest header (which
reqwest handles transparently).- Replaced
rand::thread_rng()withrand::rng()in 4 sites (the
former is deprecated since rand 0.9). - Replaced
Rng::gen_range→RngExt::random_rangein 7 sites. - Replaced
Rng::gen_bool→RngExt::random_boolin 2 sites. - Replaced
Rng::gen::<T>()→RngExt::random::<T>()in 1 site. - Replaced
rand::seq::SliceRandomwithrand::seq::IndexedRandomfor
choosecalls on slices (thechoosemethod moved traits in 0.9).
IteratorRandom::chooseis still used forIteratortypes (e.g.
slice.iter().filter().choose). - Pinned
time = "0.3.47"as a direct dependency to override the
transitivetime 0.3.40pulled in bycookie_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 therand 0.10trait reorganisation (the convenience
methods moved fromRngtoRngExt). Updated theuselines in
src/identity.rs,src/parallel.rs, andsrc/search.rsto import
RngExtinstead ofRng. - CI:
supply chain (audit + deny)job failing on RUSTSEC-2026-0009
(time 0.3.40denial-of-service via stack exhaustion when parsing RFC
2822 date headers, severity 6.8 medium). Resolved by upgrading
timeto0.3.47(the patched release). The defensive ignore in
deny.tomlfor this advisory is now obsolete and has been removed. - CI: 5 jobs failing on
E0599 no method named choose(caused by the
trait move ofchoosefromIteratorRandomtoIndexedRandomin
rand 0.9). Updated import insrc/http.rsandsrc/identity.rs. - CI:
msrvjob failing onassert_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 manualflag is set; comments (#) and blank
lines are ignored. - New module
src/aggregation.rs—Rrf(K=60)andDedupeByUrl
strategies. URL canonicalisation stripsutm_*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 inaggregation.rs.regex = "1"— used bydecomposition::is_composite_queryto
detect composite-query signals and suppress redundant templates.proptest = "1"(dev) — property-based tests for new modules.
Changed
- Version bumped from
0.6.11to0.7.0(minor: new public
subcommanddeep-researchand four new public modules
deep_research,decomposition,aggregation,synthesis). No
breaking changes to the existingbuscarsubcommand or the default
SearchOutput/MultiSearchOutputschemas — additive only. Configconstruction inlib::execute_deep_researchbuilds 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 fullCliArgs.
Internal
- Cargo.toml
excludeblock —gaps.mdanddocs_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— addedproptest-regressions/,coverage/,
tarpaulin-report.html, and.cargo-deny-state.jsonto 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) andsynthesis(estimate_tokensmonotonicity,
trim_to_budgetceiling + idempotence).proptest-regressions/is
captured in.gitignore. regexintegrated indecomposition::is_composite_querywith
CompositeSignalenum (Comparison, Aspect, Timeline, Opinion, Cause,
Topic) andOnceLock-cached compiled patterns. The heuristic strategy
now suppresses redundant templates (e.g.Comparisonis skipped when
the query already containsvsoror).- 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 privatefloor_char_boundaryhelper.
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-existingRUSTSEC-2025-0057
onselectors 0.25.0is 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_iostep 6Check if version already publishedfailed withunbound variableexit 1 on tag v0.6.10- Root cause: the
VERSIONvariable was referenced asVERSION="${VERSION}"
on the first line of the script, but it was never defined in the step's
env:block. Withset -euo pipefailactive, accessing an undefined
variable causedbash: VERSION: unbound variablewith exit 1, marking
the step asconclusion: failureand short-circuiting the rest of the
publish path. crates.io v0.6.10 was published manually via
cargo publish --allow-dirtyas a workaround. - Solution: passed
VERSION: ${{ steps.detect_version.outputs.version }}
in the step'senv:block, mirroring the pattern already used by
Verify tag matches Cargo.toml version. Also hardened the script
withNO_COLOR=1and asedANSI-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.
- Root cause: the
-
CI:
cargo searchparsing is now resilient to ANSI color codes- The
cargo searchoutput is wrapped in ANSI escape codes when
CARGO_TERM_COLOR=alwaysis 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 setNO_COLOR=1to disable color
output explicitly. Both layers ensure the regex sees clean ASCII.
- The
[0.6.10] - 2026-06-05
Fixed
-
CI:
Publish to crates.iojob rejected by environment protection rules — tagv0.6.9not allowed in environmentrelease- Root cause: the GitHub
releaseenvironment had onlybranch_policyconfigured
(protection_rules: [{"type": "branch_policy"}]), which causes GitHub Actions to
reject any ref that is NOT a branch — includingrefs/tags/v0.6.9. The run ended
withconclusion: failureandsteps_count: 0(job never even started), showing
the annotationTag "v0.6.9" is not allowed to deploy to release due to environment protection rules. - Solution: created a new
release-publishenvironment (id16308925736) with no
protection_rules, which accepts ANY ref — including SemVer tags. Thecrates_io
job now usesenvironment: name: release-publish.
- Root cause: the GitHub
-
CI:
actionlintexit 3 —is a directoryerror when invokingactionlint .github/workflows/- Root cause:
actionlintv1.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 errorcould not read ".github/workflows/": is a directorywith
exit 3, marking theworkflow syntax check (actionlint)job as failed. - Solution: corrected the invocation to
actionlint(no arguments) in the
Run actionlintstep ofci.yml. Local validation confirmed exit 0 with
zero syntax errors.
- Root cause:
-
CI:
zizmorexit 13 — 2secrets-outside-envfindings (medium) in thegithub_releasejob- Root cause: the
github_releasejob referencedsecrets.GPG_PRIVATE_KEYand
secrets.GPG_PASSPHRASEinenv:without a dedicatedenvironment:. The
zizmor >= 1.24(personaauditor) detects this pattern assecrets-outside-env
(medium) and marks theworkflow 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_releaseenv:and added
theGPG_SIGNING_ENABLED: "false"gate at workflow level; (2) the
Sign SHA256SUMS with GPGstep was renamed to(DESABILITADO)and never
executes; (3) created a.github/zizmor.ymlconfig with
rules.secrets-outside-env.config.allowlistingCRATES_IO_TOKEN(which is
at repo level for compatibility). Cosign keyless (jobattest) already provides
cryptographic integrity via Sigstore, covering the role GPG signing would play.
- Root cause: the
-
CI: package list now includes
.github/zizmor.yml(intentional zizmor configuration)- Added
.github/zizmor.ymlwith allow rules for theCRATES_IO_TOKENsecret at
repo level. This file is a static config, contains no credentials and is safe
to version.
- Added
[0.6.9] - 2026-06-05
Fixed
-
CI: Windows
.ziprelease asset was empty (209 bytes) — bug inPackage (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-Itemfailed silently (source path becametarget//release/) and
Compress-Archiveproduced an almost-empty zip (onlySHA256SUMS.txt). - Solution: replaced all
${VAR}with$env:VARin PowerShellrun:blocks
(Package (Windows) and Generate SHA256SUMS (Windows)). - Reference: incident-jaq-not-found-runner-2026-06-05 + cross-cutting audit on 2026-06-05
- Root cause: the script used
-
CI:
sbom.cdx.jsonCycloneDX SBOM was 0 bytes (file not actually generated)- Root cause:
cargo cyclonedx --override-filename sbom.cdx.jsonactually writes
sbom.cdx.json.jsonbecause the--override-filenameflag auto-appends.json.
Thewc -c < sbom.cdx.jsonstep then read 0 bytes from the non-existent file and
theUpload SBOM as artifactstep uploaded an empty file (artifact ignored downstream). - Solution: changed invocation to
cargo cyclonedx --format json --override-filename sbom
(stem only), thenmv sbom.json sbom.cdx.jsonto match the expected filename.
- Root cause:
-
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.
- Root cause: the above two bugs combined meant the v0.6.8 release workflow produced
Unreleased
Fixed
-
CI: exit 101
crate already existsonPublish to crates.iojob (post-mortem 2026-06-05)- Root cause: trigger duplicado do workflow para tag v0.6.6 já publicada causou
cargo publish
exit 101 comerror: 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_ioguard 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 searchwith timeout + retry to detect already-published versioncargo publishskip 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
- Root cause: trigger duplicado do workflow para tag v0.6.6 já publicada causou
-
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-releasefrom 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
- Root cause: actions/checkout@v4, actions/upload-artifact@v4, actions/download-artifact@v4
-
CI: exit 141 SIGPIPE intermittent in
validate (ubuntu-latest)- Root cause:
cargo testwrites 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
- Root cause:
-
CI: exit 1 in
validate (windows-latest)from VS2022→VS2026 redirect- Root cause: GitHub redirects
windows-latesttowindows-2025-vs2026since 2025-06-15.
VS2026 has breaking changes in MSVC toolchain that affect Rust stable. - Solution: pinned
windows-2022inci.ymlmatrix andrelease.ymlbuild target - Re-evaluate pin after 2026-07-15 once VS2026 stabilizes
- Root cause: GitHub redirects
Added
- SBOM CycloneDX generation in release workflow —
cargo cyclonedx --format jsonproduces
sbom.cdx.jsonuploaded as artifact. Enables compliance with EU Cyber Resilience Act. - SLSA provenance attestation —
actions/attest-build-provenance@v2creates 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 —
sha256sumgenerated per target, combined
into singleSHA256SUMS.txt, uploaded as release asset and as part of every binary tarball/zip. - GPG tag signing — optional
gpg --detach-sign SHA256SUMS.txtifGPG_PRIVATE_KEYsecret
is configured.continue-on-error: trueto 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_updatejob runs Sundays 03:00 UTC,
executescargo 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.ymlcreates weekly PRs
for GitHub Actions updates and Rust crate updates. Groups by major/minor/patch. .gitattributesLF normalization — forces LF line endings in all text files,
preventing CRLF issues on Windows that breakcargo fmt --check.
Security
- Permissions hardened per job — top-level
permissions: contents: write packages: write id-token: write attestations: write checks: write discussions: writefor release;
per-jobpermissions:blocks in CI for least-privilege. continue-on-error: trueon GPG step — missing GPG key does not block release;
optional enhancement.- No
pull_request_targettriggers — workflows never run with write permissions
on PRs from forks.
[0.6.8] - 2026-06-05
Fixed
- CI: exit 127
jaq: command not foundingithub_releasejob of release workflow- Root cause:
release.yml(lines 625-626) usedjaq(Rust binary) to parse JSON
response from GitHub REST API, but the GitHub Actions Ubuntu 24.04 runner only
hasjq 1.7pre-installed —jaqis not part of the standard runner image.
Bug introduced by commit7f489b5(2026-06-05) when bypassing the broken
softprops/action-gh-releaseaction. - Solution: replaced
jaqwithjq(pre-installed, syntax-compatible) and added
explicit fail-fast validation for extractedUPLOAD_URLandRELEASE_IDvalues
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 listsjq 1.7,jaqis absent)
- Root cause:
[0.6.7] - 2026-06-05
Fixed
- CI: post-mortem completo do incident-publish-101-2026-06-05 (hardening release pipeline)
- Added
preflightjob validating tag==Cargo.toml, SemVer, CHANGELOG, no AI Co-authored-by - Added guard de versão duplicada em
crates_iojob (zizmor: secrets-outside-env resolvido) - cargo publish com timeout 300s + 3 retries (network resilience)
- Concurrency group por tag+sha (impede runs paralelos)
- Added
- 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_NODE24as 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
- browser.rs:25 já tem
- 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+)
- 5 blocos unsafe agora têm
- 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 jsonproduzsbom.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: truese 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")))]fromsrc/lib.rs:70 - Root cause: in Oct 2025 the Rust team merged
doc_auto_cfgintodoc_cfg(rust-lang/rust#43781),
making#[doc(cfg(...))]require# on the crate root.
The build failed witherror[E0658]: #[doc(cfg)] is experimentalon nightly1.98.0. - The feature gating itself is preserved:
#[cfg(feature = "chrome")]still excludes
pub mod browserfrom default builds. The module-level docstring insrc/browser.rs
already documents the feature requirement explicitly. cargo doc --all-featuresandRUSTDOCFLAGS="--cfg docsrs" cargo doc --all-features
both pass without warning or error.
- Removed
[0.6.5] - 2026-06-05
Fixed
- MP-26 — Windows HANDLE cast broken in
windows-sys 0.59+(src/platform.rs:51-63)HANDLEmudou deisizepara*mut c_voidupstream (microsoft/windows-rs,raw-window-handle#171)- Substituído
handle != 0 && handle != usize::MAXpor!handle.is_null() && handle != INVALID_HANDLE_VALUE - Removidos casts inválidos
handle as isize(a assinatura moderna aceitaHANDLEdireto) - Atualizado o
// SAFETY:comment para documentar nulidade e sentinela Win32
- CI:
validatefalhava em todos os 3 SOs (Linux/macOS/Windows) por 6 erros de clippy- 3×
clippy::doc_markdown(PowerShell,rules_rust.md,TempDir) emsrc/platform.rsesrc/browser.rs - 1×
clippy::needless_returnemsrc/browser.rs:149 - 2×
missing_debug_implementationsemsrc/browser.rs:223(ChromeBrowser) esrc/content_fetch.rs(CircuitBreakerMap)
- 3×
Added
- WS-11 — Property-based invariants for HTML parsers (
src/extraction.rs+5 testes)- Invariante: inputs vazios/quebrados retornam
Vecvazio 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])
- Invariante: inputs vazios/quebrados retornam
- 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_contentantes de cada fetch BreakerDecision::{Allow, Reject}para inspeção- Zero dependência nova (
std::sync::Mutex<HashMap>)
- WS-23 —
Retry-Afterheader test (tests/integration_wiremock.rs)- Mock retorna 429 com
retry-after: 2 - Asserção:
elapsed_ms >= 1500(delay mínimo respeitado) - Usa
wiremock0.6 já em dev-deps
- Mock retorna 429 com
- WS-25 —
indicatifProgressBar 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 --checkcleancargo clippy --all-targets --all-features --locked -- -D warningscleancargo publish --dry-run --locked --allow-dirtyclean
[0.6.4] - 2026-06-03
Added
- WS-26 — Adaptive anti-bot identity rotation (new
src/identity.rsmodule)- 12-identity pool (4 browser families × 3 platforms) for adaptive rotation
IdentityProfile::shuffled_headers()produces seed-deterministic header orderIdentityPool::rotate_on_block()implements a 5-level cascade: same identity → same family/different platform → different family/same platform → different family+platform → randomBrowserFamilyandPlatformenums 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).autois default.
- New JSON metadata fields (additive,
Option+skip_serializing_if = "Option::is_none")metadados.identidade_usada— string tag of the identity that produced the responsemetadados.nivel_cascata— cascade level reached during the request
Changed
- Version rollback:
0.7.0(unpublished) →0.6.4to 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 warningscleancargo fmt --checkclean
[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_conteudorenamed tocontent_fetch - Test files
integracao_*.rsrenamed tointegration_*.rs - Replaced
anyhowwith typedCliErroracross 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 compatibilitysearch.rs:RetryResultandAggregatedSearchResultfields renamed PT→ENtypes.rs:Configfieldsperfil_browser/corresponde_plataforma_ua/caminho_chrome→browser_profile/match_platform_ua/chrome_path
Added
- Loom concurrency tests (
tests/loom_atomics.rs) — validatesAtomicBoolvisibility across threads - Criterion benchmarks (
benches/extraction_bench.rs) — HTML extraction performance baselines - Doc comments for all 70 previously undocumented public items — zero
missing_docswarnings .ingest-queue.sqliteadded to.gitignoreandCargo.tomlexclude
Fixed
- RUSTSEC-2026-0097: updated
rand0.8.5 → 0.8.6 - RUSTSEC-2026-0104: updated
rustls-webpki0.103.12 → 0.103.13
Security
deny.toml: addedskip-treefor 30 transitive duplicate crates (chromiumoxide, scraper, console-subscriber ecosystems)
Known Limitations
- Loom tests require
RUSTFLAGS="--cfg loom"which conflicts withhyper-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 0now returns exit 2 (invalid config) instead of executing a search with zero timeout and returning exit 5.--output /tmp/../../etc/passwdnow returns exit 2 (invalid config) instead of exit 1 (runtime OS error) — path traversal validation moved tomontar_configuracoes(), before the pipeline starts.
Added
validar_timeout_segundos()method onCliArgs— rejects values of 0 with a descriptive error.- Early path traversal check in
montar_configuracoes()— callspaths::validate_output_path()at config validation time, not at write time. - 2 E2E regression tests:
timeout_zero_retorna_exit_2andoutput_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-Languagecom q-values RFC 7231 elimina fingerprint de UA genérico.- Detecção de bloqueio silencioso com limiar de 5 KB previne resultados truncados.
Added
BrowserFamilyenum — variantesChrome,Firefox,Edge,Safari.BrowserProfilestruct — encapsula família, versão e conjunto de headers por família.- Headers
Sec-Fetch-Dest,Sec-Fetch-Mode,Sec-Fetch-Sitepor família emhttp.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.rscom backoff exponencial automático. - Detecção de bloqueio silencioso — resposta com menos de 5 000 bytes é tratada como bloqueio.
BrowserProfilepropagado viaConfigpara todos os módulos da pipeline.- Headers de paginação com
Sec-Fetch-Site: same-originpara imitar navegação real.
Changed
Accept-Languageatualizado parapt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7conforme RFC 7231.Acceptheader 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@hostURLs.
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).ErroCliDdgenum withthiserror— 11 typed error variants withexit_code()andcodigo_erro()methods.mascarar_url_proxy()inhttp.rs— redacts credentials from proxy URLs in error context.- 21 new unit tests across
paths.rs,signals.rs,error.rs, andhttp.rs.
Changed
thiserror = "2"added to dependencies for structured domain errors.src/main.rsreduced from 63 to 23 lines — signal handling extracted tosignals.rs.src/output.rsfile writes now validate paths viapaths::validate_output_path()before I/O.deny.tomlupdated 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
--helpnow shows EXIT CODES (0–5) and PIPE USAGE sections viaafter_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 emskill/:
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.mde
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 comnameúnico por idioma edescription
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 comjaq, 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,
pipelinesjaq+xh+sdpreservados intactos.docs/INTEGRATIONS.md(1212 linhas, +1,3%) — 16 agentes com tabela
comparativa textual, snippets determinísticos por agente, zero emoji.
Meta
Cargo.tomlexclude ampliado para cobrirskill/eskill/**— 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+ rulesMUST/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
combinandoduckduckgo-search-cli+jaq+xh+sdpara 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--pagesnã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
--pagesparaceil(num/10)respeitando o teto de 5 páginas validado
porvalidar_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
--numemcli.rsdescrevendo 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 10explicitamente. O--pages 1explí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 2ou 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_relacionadasREMOVIDO deSearchOutpute
MultiSearchOutput.buscas[i]. O endpointhtml.duckduckgo.com/html/não
expõe related searches no DOM atual; manter o campo sempre vazio era ruído.
Pipelines que parseavam.buscas_relacionadasprecisam 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 faziatext()recursivo
capturar título+URL+snippet concatenados. Trocado por.result__snippet
puro. Pipelines comojaq '.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 pelourl_exibicao(ex:saofidelis.rj.gov.br).
O texto original é preservado no novo campo opcionaltitulo_original
para auditoria.
Added
- Campo
titulo_original: Option<String>emSearchResult. 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_relacionadasemsrc/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 tratarnull. - 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?
Usetitulo_original.as_deref() == Some("Official site"). - CONFIG EXTERNO LEGADO: usuários que rodaram
init-configem versões
anteriores possuem~/.config/duckduckgo-search-cli/{selectors,user-agents}.toml
com defaults antigos (snippet com.result__body+ UAsLynx/w3m/etc.).
Esses arquivos OVERRIDE os defaults embutidos. Para aplicar as correções
desta versão, execute APÓS atualizar:O flagduckduckgo-search-cli init-config --force--forcesobrescreve 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. Emv0.1.0retornavamnullpor divergência do schema
(bug reportado pelo usuário).
Unreleased
Added
LICENSE-MITandLICENSE-APACHE(dual-licensed perCargo.toml, aligning the tarball with the SPDX declaration)..pre-commit-config.yamlwith 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) localcommit-msghook blockingCo-authored-by:from AI agents (mirrors the CIcommit_checkjob). Reduces CI round-trips for trivial violations..gitattributesforcing LF on.rs/.toml/.sh/.yml/.md/ fixture HTML — prevents silent corruption when cloning on Windows withcore.autocrlf=true(which would otherwise break shebangs, rustfmt, and content-extraction tests). Binary extensions (.png,.woff2, etc.) marked explicitly.Cargo.lockandtarget/flaggedlinguist-generatedto exclude from GitHub language stats..editorconfignormalizing 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.mdwith the 10-gate checklist + project-specific constraints (no cache, no MCP, rustls-only,println!confined tooutput.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.ymlredirects security reports to Security Advisories and usage questions to Discussions.Cross.tomlenablingcross 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.mdwith the 10-gate validation matrix, coding standards (Brazilian Portuguese identifiers, rustls-only TLS,output.rsas the soleprintln!site), three-layer testing strategy, supply-chain guardrails, and the tag-driven release process..cargo/config.tomlexposing 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, andsearch::format_kl— compilable examples on docs.rs that double as regression tests. SECURITY.mddocumenting 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.ymlenabling weekly automatic dependency updates for bothcargoandgithub-actionsecosystems, with semantic grouping (dev-deps, tokio-ecosystem, tracing-ecosystem) and PR count limits.rust-toolchain.tomlpinningstablewithrustfmt+clippycomponents for reproducible dev/CI builds..github/workflows/release.ymltriggered byv*.*.*tags (andworkflow_dispatchwithdry_run) running the 5-stage release pipeline perrules_rust.md§19: validate → build_matrix (5 targets) → macos_universal (lipo) → github_release (with generated notes) → crates_io (publish gated onCRATES_IO_TOKENsecret).msrvjob inci.ymlextractingrust-versionfromCargo.tomland runningcargo checkon that toolchain to detect MSRV drift on every PR..github/workflows/ci.ymlenforcing the 10-gate validation matrix across Ubuntu, macOS, and Windows:cargo check/clippy -D warnings/fmt --check/doc -D warnings/test --all-featureson all three OSes.cargo llvm-cov --fail-under-lines 80dedicated job on Ubuntu.cargo audit+cargo deny check advisories licenses bans sourcessupply-chain gate.cargo publish --dry-run+cargo package --listsensitive-file guard.- Static musl binary smoke test (
x86_64-unknown-linux-musl) covering Alpine Linux and minimal containers. commit_checkjob blockingCo-authored-by:trailers from AI agents in PRs.
deny.tomlwith 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 foroutput.rscoveringemit_ndjson,emit_stream_text,emit_stream_markdown, and thePipelineResultvariants viatempfile.
Changed
parallel.rscoverage 50% → 81%;pipeline.rs55% → 82%;content_fetch.rs68% → 85%;output.rs70% → 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 litefor 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 bytokio::JoinSet+Semaphore. --pages(1..=5) to collect multiple result pages per query.--fetch-contentfetches 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 chromewith 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-pathflag 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 intokio::time::timeout.--per-host-limit(1..=10) rate-limits fetches per host via a per-hostSemaphoremap.--match-platform-uanarrows the user-agent pool to the current platform.--streamNDJSON mode emits one result per line as they are extracted.- Four output formats:
json(default),text,markdown,auto(TTY-aware). - External configuration files:
selectors.tomlanduser-agents.tomlunder XDG config dir, overriding embedded defaults. - Subcommand
init-configwith--forceand--dry-runto bootstrap user config files. - Exit codes:
0success,1runtime,2config,3block (HTTP 202 anomaly),4global timeout,5zero results. - UTF-8 console initialization on Windows via
SetConsoleOutputCP(65001). - Rustls-TLS everywhere for dependency-free cross-platform builds.
tracing+tracing-subscriberwithRUST_LOGhonored;--verbose/--quietflags.- 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.