diff --git a/.github/workflows/benchmark-latency.yml b/.github/workflows/benchmark-latency.yml index 04eee48e..233303ff 100644 --- a/.github/workflows/benchmark-latency.yml +++ b/.github/workflows/benchmark-latency.yml @@ -52,9 +52,7 @@ jobs: - name: Run latency benchmarks run: | cargo bench --locked -- \ - cli_cold_start \ - cli_command_latency \ - latency_budget \ + 'cli_cold_start|cli_command_latency|latency_budget' \ 2>&1 | tee target/criterion/latency-bench-output.txt # Parse Criterion output and check against latency budgets. @@ -117,9 +115,15 @@ jobs: } else { summary += '_No latency budget report was generated._'; } - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: summary - }); + try { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: summary + }); + } catch (err) { + // Fork PRs get a read-only GITHUB_TOKEN and can't post comments; + // don't fail the whole job just because the summary couldn't be posted. + core.warning(`Could not post latency budget comment: ${err.message}`); + } diff --git a/CODE_STYLE_STANDARDS.md b/CODE_STYLE_STANDARDS.md index 707f722a..9c1b88d6 100644 --- a/CODE_STYLE_STANDARDS.md +++ b/CODE_STYLE_STANDARDS.md @@ -481,27 +481,49 @@ cargo clippy -- -W clippy::needless_clone ### Project-Specific Allowances -The StarForge project allows these clippy rules in specific circumstances. See `src/main.rs` for the global allowlist: +**There is no crate-wide lint allowlist.** `src/lib.rs` and `src/main.rs` used to +carry a blanket `#![allow(dead_code, unused, clippy::all)]`, and `Cargo.toml` +carried a matching `[lints]` table — together these silenced essentially every +default Clippy lint group (correctness, suspicious, style, complexity, perf) +across the *entire* ~40k-line crate, not just the handful of patterns the +comments claimed to cover. That's exactly backwards: it hid real bugs (see +below) behind the same blanket that was meant to excuse a few CLI functions +with many arguments. + +Every remaining `#[allow(...)]` in the codebase is now **scoped to the single +item that needs it** — a function, struct, or field — with a comment +immediately above explaining *why*: ```rust -#![allow( - dead_code, // Some plugin infrastructure code is unused until plugins load it - clippy::needless_range_loop, // Sometimes more readable than alternatives - clippy::redundant_closure, // Used intentionally for clarity in some cases - clippy::too_many_arguments, // Complex CLI commands require many arguments - clippy::type_complexity, // Some type definitions are inherently complex - clippy::unnecessary_lazy_evaluations // Some expressions are evaluated for side effects -)] +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] +async fn monitor_contract( + contract_id: &str, + events_filter: Option<&str>, + // ... +) -> Result<()> { ``` -**When to add to this allowlist:** -- Only for **unavoidable** patterns -- Document *why* with a comment -- Discuss with maintainers before merging - -```rust -#![allow(clippy::too_many_arguments)] // Contract CLI requires many parameters for optimization context -``` +**When to add a scoped allow:** +- Only on the specific item that triggers it — never on a module, and never + crate-wide. +- Only for patterns that are genuinely **unavoidable or clearly intentional** + for that one item, not as a shortcut past a warning you haven't looked at. +- Always with a comment explaining *why*, not just restating the lint name. +- `dead_code` is the one lint where "unavoidable" usually means "not wired up + yet, but deleting it isn't this change's call to make" — that's a valid + reason, but say so explicitly rather than leaving the allow unexplained. + +**What restoring this signal found:** with the blanket removed, +`cargo clippy --all-features` went from silently clean to over 2000 +warnings — 94% of them were the single mechanical `uninlined_format_args` +style lint (fixed via `cargo clippy --fix`), but the rest included real +defects the blanket had been hiding, e.g. a constructed template changelog +entry that was built and then silently discarded (`changelog: None` instead +of `Some(changelog)`) and unreachable branches. Local, documented exceptions +don't have that failure mode: each one is small enough to actually read. **When NOT to add:** - "I don't want to refactor" — do the refactor diff --git a/Cargo.lock b/Cargo.lock index 682b5097..7bb8af22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3643,6 +3643,7 @@ dependencies = [ "stellar-strkey", "stellar-xdr", "tempfile", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "toml", diff --git a/Cargo.toml b/Cargo.toml index 888d6bff..f64d5b8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,29 +16,6 @@ name = "starforge" path = "src/lib.rs" crate-type = ["cdylib", "rlib"] -# Package-wide lint policy. These lints are intentionally relaxed for the crate -# (including integration tests, which use lightweight mock structs and helpers). -# Centralizing them here keeps `cargo clippy --all-targets -- -D warnings` green. -[lints.rust] -dead_code = "allow" -unused_imports = "allow" -unused_variables = "allow" - -[lints.clippy] -needless_range_loop = "allow" -redundant_closure = "allow" -too_many_arguments = "allow" -type_complexity = "allow" -unnecessary_lazy_evaluations = "allow" -items_after_test_module = "allow" -needless_borrow = "allow" -needless_borrows_for_generic_args = "allow" -empty_line_after_doc_comments = "allow" -doc_overindented_list_items = "allow" -expect_fun_call = "allow" -useless_vec = "allow" -single_match = "allow" - [dependencies] clap = { version = "=4.4.18", features = ["derive", "color"] } serde = { version = "1.0", features = ["derive"] } @@ -84,6 +61,7 @@ zip = "0.6" tempfile = "3.8" wasm-bindgen = "0.2" rusqlite = { version = "0.32", features = ["bundled"] } +thiserror = "1" csv = "1.0" minijinja = "1.0" serde_yaml = "0.9.34" diff --git a/deny.toml b/deny.toml index 84ed65a2..5e586f45 100644 --- a/deny.toml +++ b/deny.toml @@ -25,8 +25,6 @@ ignore = [ # rustls-pemfile unmaintained in 1.0.4; reqwest 0.11 requires it. # Superseded in reqwest 0.12, which is a breaking change for this crate. "RUSTSEC-2025-0134", - # anyhow downcast_mut - "RUSTSEC-2026-0190", ] [licenses] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index e3946d3a..532fb362 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -57,18 +57,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -158,13 +158,13 @@ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.0" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -711,9 +711,40 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] [[package]] name = "der" @@ -842,7 +873,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -886,9 +917,9 @@ dependencies = [ [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encode_unicode" @@ -929,9 +960,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.2" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" [[package]] name = "escape-bytes" @@ -987,9 +1018,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" @@ -1018,9 +1049,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1033,9 +1064,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1043,15 +1074,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1060,38 +1091,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1354,9 +1385,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1368,9 +1399,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1381,9 +1412,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1395,16 +1426,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1415,15 +1447,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -1508,9 +1540,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "is_terminal_polyfill" @@ -1533,6 +1565,59 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.35" @@ -1545,9 +1630,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -1588,9 +1673,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "libc", ] @@ -1614,9 +1699,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" @@ -1635,9 +1720,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "matchers" @@ -1873,9 +1958,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "polyval" @@ -1891,15 +1976,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -1955,9 +2049,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha", @@ -2005,22 +2099,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2037,9 +2131,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2163,7 +2257,7 @@ checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki 0.103.15", "subtle", "zeroize", ] @@ -2198,9 +2292,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2314,7 +2408,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2353,9 +2447,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", "bs58", @@ -2363,6 +2457,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.14.0", + "jiff", "schemars 0.9.0", "schemars 1.2.2", "serde_core", @@ -2373,9 +2468,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -2552,6 +2647,7 @@ dependencies = [ "stellar-strkey", "stellar-xdr", "tempfile", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "toml", @@ -2575,6 +2671,7 @@ dependencies = [ "serde_json", "sha2", "starforge", + "tempfile", ] [[package]] @@ -2639,9 +2736,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -2710,11 +2807,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -2730,13 +2827,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2750,9 +2847,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -2780,9 +2877,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -2828,7 +2925,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2940,7 +3037,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", "tracing-subscriber", ] @@ -3133,9 +3230,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -3178,9 +3275,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -3191,9 +3288,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -3201,9 +3298,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3211,9 +3308,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -3224,18 +3321,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -3534,9 +3631,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "yoke" @@ -3563,18 +3660,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -3610,9 +3707,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3621,9 +3718,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -3632,13 +3729,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] diff --git a/src/commands/ai.rs b/src/commands/ai.rs index 85d376d9..d3a6e7b2 100644 --- a/src/commands/ai.rs +++ b/src/commands/ai.rs @@ -475,6 +475,9 @@ async fn handle_ask(question: &str, model: &str, temperature: f32, max_tokens: u Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn handle_translate(text: &str, target: &str, model: &str) -> Result<()> { if text.trim().is_empty() { anyhow::bail!("Please provide text to translate."); diff --git a/src/commands/ai_accessibility.rs b/src/commands/ai_accessibility.rs index 5987c632..437a2875 100644 --- a/src/commands/ai_accessibility.rs +++ b/src/commands/ai_accessibility.rs @@ -190,13 +190,22 @@ fn handle_status(json: bool) -> Result<()> { p::header("Accessibility Configuration"); p::separator(); p::kv("Screen reader mode", &cfg.screen_reader_mode.to_string()); - p::kv("Simplified text mode", &cfg.simplified_text_mode.to_string()); + p::kv( + "Simplified text mode", + &cfg.simplified_text_mode.to_string(), + ); p::kv("High contrast mode", &cfg.high_contrast_mode.to_string()); p::kv("Voice commands", &cfg.voice_commands_enabled.to_string()); - p::kv("Keyboard shortcuts", &cfg.keyboard_shortcuts_enabled.to_string()); + p::kv( + "Keyboard shortcuts", + &cfg.keyboard_shortcuts_enabled.to_string(), + ); p::kv("Reduce motion", &cfg.reduce_motion.to_string()); p::kv("Announce progress", &cfg.announce_progress.to_string()); - p::kv("Verbose descriptions", &cfg.verbose_descriptions.to_string()); + p::kv( + "Verbose descriptions", + &cfg.verbose_descriptions.to_string(), + ); p::kv("Font size", &format!("{:?}", cfg.font_size)); p::separator(); Ok(()) @@ -234,9 +243,9 @@ fn handle_configure(args: ConfigureArgs) -> Result<()> { })?; p::success("Accessibility settings updated."); - if !args.screen_reader.is_none() - || !args.simplified_text.is_none() - || !args.high_contrast.is_none() + if args.screen_reader.is_some() + || args.simplified_text.is_some() + || args.high_contrast.is_some() { p::kv("Screen reader", &cfg.screen_reader_mode.to_string()); p::kv("Simplified text", &cfg.simplified_text_mode.to_string()); @@ -416,7 +425,11 @@ fn handle_toggle(setting: &str, enable: Option) -> Result<()> { _ => false, }; - p::success(&format!("{} mode: {}", label, if state { "enabled" } else { "disabled" })); + p::success(&format!( + "{} mode: {}", + label, + if state { "enabled" } else { "disabled" } + )); Ok(()) } diff --git a/src/commands/ai_audit.rs b/src/commands/ai_audit.rs index 226d3cb8..9920508b 100644 --- a/src/commands/ai_audit.rs +++ b/src/commands/ai_audit.rs @@ -4,9 +4,9 @@ //! with comprehensive coverage and minimal false positives. use crate::utils::print as p; -use crate::utils::security::{AiAuditService, AuditLevel, AuditRequest}; +use crate::utils::security::{AuditLevel, AuditRequest}; use anyhow::Result; -use clap::{Args, Subcommand}; +use clap::Args; use colored::*; use std::fs; use std::path::{Path, PathBuf}; diff --git a/src/commands/ai_cache_cmd.rs b/src/commands/ai_cache_cmd.rs index af95d45e..fd05c3b2 100644 --- a/src/commands/ai_cache_cmd.rs +++ b/src/commands/ai_cache_cmd.rs @@ -11,9 +11,9 @@ //! - `warm` – pre-warm cache with common operations use crate::utils::{ai_cache, print as p}; -use anyhow::{Context, Result}; -use clap::{Args, Subcommand}; -use std::path::PathBuf; +use anyhow::Result; +use clap::Subcommand; +use std::path::{Path, PathBuf}; // ─── Sub-command enum ───────────────────────────────────────────────────────── @@ -288,7 +288,7 @@ async fn handle_invalidate(tags: Option<&str>, model: Option<&str>) -> Result<() Ok(()) } -async fn handle_export(path: &PathBuf) -> Result<()> { +async fn handle_export(path: &Path) -> Result<()> { let cache = ai_cache::AiCache::open()?; p::header("Exporting AI Cache"); @@ -303,7 +303,7 @@ async fn handle_export(path: &PathBuf) -> Result<()> { Ok(()) } -async fn handle_import(path: &PathBuf) -> Result<()> { +async fn handle_import(path: &Path) -> Result<()> { let mut cache = ai_cache::AiCache::open()?; p::header("Importing AI Cache"); diff --git a/src/commands/ai_chat.rs b/src/commands/ai_chat.rs index 76610510..90321d41 100644 --- a/src/commands/ai_chat.rs +++ b/src/commands/ai_chat.rs @@ -13,9 +13,8 @@ use crate::utils::{ }; use anyhow::{Context, Result}; use clap::Subcommand; -use rustyline::{DefaultEditor, Editor}; +use rustyline::DefaultEditor; use std::collections::HashMap; -use std::path::PathBuf; #[derive(Subcommand)] pub enum AiChatCommands { @@ -332,7 +331,7 @@ async fn generate_ai_response(prompt: &str, model: &str) -> Result { let response = ollama::generate_cached( model, - &prompt, + prompt, Some(opts), Some(ai_cache::DEFAULT_CACHE_TTL_SECONDS), "ask", diff --git a/src/commands/ai_deployment_test.rs b/src/commands/ai_deployment_test.rs index cead9411..5f30ea12 100644 --- a/src/commands/ai_deployment_test.rs +++ b/src/commands/ai_deployment_test.rs @@ -99,10 +99,7 @@ fn resolve_phases(value: &str) -> Result> { return Ok(vec![Phase::Pre, Phase::Post]); } - let phases: Vec = value - .split(',') - .filter_map(|part| Phase::parse(part)) - .collect(); + let phases: Vec = value.split(',').filter_map(Phase::parse).collect(); if phases.is_empty() { anyhow::bail!("Unknown phase '{}'. Use pre, post, or all", value); diff --git a/src/commands/ai_error.rs b/src/commands/ai_error.rs index 89e41291..502a75dd 100644 --- a/src/commands/ai_error.rs +++ b/src/commands/ai_error.rs @@ -3,10 +3,7 @@ //! Provides commands for managing AI error handling, viewing analytics, //! and configuring fallback providers. -use crate::utils::{ - ai_error_handler::{AiErrorHandler, ErrorAnalytics, ProviderConfig}, - print as p, -}; +use crate::utils::{ai_error_handler::AiErrorHandler, print as p}; use anyhow::Result; use clap::Subcommand; diff --git a/src/commands/ai_feedback.rs b/src/commands/ai_feedback.rs index 75398b37..01897cda 100644 --- a/src/commands/ai_feedback.rs +++ b/src/commands/ai_feedback.rs @@ -4,8 +4,6 @@ use crate::utils::print as p; use anyhow::{Context, Result}; use clap::{Args, Subcommand}; use colored::*; -use std::fs; -use std::path::PathBuf; #[derive(Subcommand)] pub enum AiFeedbackCommands { diff --git a/src/commands/ai_model_router.rs b/src/commands/ai_model_router.rs index 5dbb7093..251aca11 100644 --- a/src/commands/ai_model_router.rs +++ b/src/commands/ai_model_router.rs @@ -135,9 +135,15 @@ fn handle_classify(args: ClassifyArgs) -> Result<()> { p::kv("Complexity", &classification.complexity.to_string()); p::kv("Category", &classification.category.to_string()); p::kv("Est. tokens", &classification.estimated_tokens.to_string()); - p::kv("Requires reasoning", &classification.requires_reasoning.to_string()); + p::kv( + "Requires reasoning", + &classification.requires_reasoning.to_string(), + ); p::kv("Requires code", &classification.requires_code.to_string()); - p::kv("Confidence", &format!("{:.0}%", classification.confidence * 100.0)); + p::kv( + "Confidence", + &format!("{:.0}%", classification.confidence * 100.0), + ); if !classification.signals.is_empty() { p::kv("Signals", &classification.signals.join(", ")); } @@ -257,7 +263,15 @@ fn handle_stats(args: StatsArgs) -> Result<()> { p::info("No model performance data recorded yet."); p::info("Enable AI telemetry with: starforge ai-telemetry enable"); } else { - let headers = &["Provider", "Model", "Feature", "Calls", "Success %", "Avg ms", "Avg tokens"]; + let headers = &[ + "Provider", + "Model", + "Feature", + "Calls", + "Success %", + "Avg ms", + "Avg tokens", + ]; let rows: Vec> = stats .iter() .take(20) diff --git a/src/commands/ai_plan.rs b/src/commands/ai_plan.rs index 9b12f4d2..23f80b7f 100644 --- a/src/commands/ai_plan.rs +++ b/src/commands/ai_plan.rs @@ -172,7 +172,11 @@ fn handle_architecture(args: ArchitectureArgs) -> Result<()> { let archs = planner::suggest_architectures(&args.description); output_or_print(&archs, args.json, "Architecture Suggestions", |archs| { for arch in archs { - let marker = if arch.recommended { " ★ recommended" } else { "" }; + let marker = if arch.recommended { + " ★ recommended" + } else { + "" + }; println!(); println!(" {}{}", arch.name, marker); println!(" {}", arch.description); @@ -225,7 +229,10 @@ fn handle_timeline(args: TimelineArgs) -> Result<()> { p::kv("Total days", &t.total_days.to_string()); p::kv("Buffer days", &t.buffer_days.to_string()); p::kv("Start", &t.start_date.format("%Y-%m-%d").to_string()); - p::kv("Target completion", &t.target_completion.format("%Y-%m-%d").to_string()); + p::kv( + "Target completion", + &t.target_completion.format("%Y-%m-%d").to_string(), + ); println!(); p::info("Milestones:"); for m in &t.milestones { @@ -379,7 +386,10 @@ fn handle_show(args: ShowArgs) -> Result<()> { } fn print_plan_summary(plan: &planner::ProjectPlan) { - p::kv("Generated", &plan.generated_at.format("%Y-%m-%d %H:%M UTC").to_string()); + p::kv( + "Generated", + &plan.generated_at.format("%Y-%m-%d %H:%M UTC").to_string(), + ); p::kv("Tasks", &plan.tasks.len().to_string()); p::kv("Phases", &plan.phases.len().to_string()); p::kv("Risks", &plan.risks.len().to_string()); diff --git a/src/commands/ai_property_test.rs b/src/commands/ai_property_test.rs index 06d5c04c..d9e24c4d 100644 --- a/src/commands/ai_property_test.rs +++ b/src/commands/ai_property_test.rs @@ -321,7 +321,7 @@ fn handle_edge_cases(args: EdgeCasesArgs) -> Result<()> { .filter(|p| { p.target_function .as_ref() - .map_or(false, |f| args.functions.contains(f)) + .is_some_and(|f| args.functions.contains(f)) }) .collect() }; @@ -394,11 +394,14 @@ async fn handle_shrink(args: ShrinkArgs) -> Result<()> { confidence: 0.5, }; let prop = properties.first().unwrap_or(&default_prop); - let shrink = - apt::generate_test_cases(&[prop.clone()], &[], &apt::PropertyTestConfig::default()) - .first() - .and_then(|tc| tc.shrink_strategy.clone()) - .unwrap_or_else(|| "shrink numerics toward 0, strings toward empty".to_string()); + let shrink = apt::generate_test_cases( + std::slice::from_ref(prop), + &[], + &apt::PropertyTestConfig::default(), + ) + .first() + .and_then(|tc| tc.shrink_strategy.clone()) + .unwrap_or_else(|| "shrink numerics toward 0, strings toward empty".to_string()); match args.format.as_str() { "json" => { diff --git a/src/commands/ai_recommend.rs b/src/commands/ai_recommend.rs index b490e458..6195137e 100644 --- a/src/commands/ai_recommend.rs +++ b/src/commands/ai_recommend.rs @@ -564,7 +564,7 @@ fn find_rust_sources(dir: &PathBuf) -> Result> { { files.extend(find_rust_sources(&path)?); } - } else if path.extension().map_or(false, |e| e == "rs") { + } else if path.extension().is_some_and(|e| e == "rs") { files.push(path); } } diff --git a/src/commands/ai_search.rs b/src/commands/ai_search.rs index f405b253..c62629a7 100644 --- a/src/commands/ai_search.rs +++ b/src/commands/ai_search.rs @@ -4,7 +4,6 @@ use crate::utils::print as p; use anyhow::{Context, Result}; use clap::{Args, Subcommand}; use colored::*; -use std::fs; use std::path::PathBuf; #[derive(Subcommand)] diff --git a/src/commands/ai_test.rs b/src/commands/ai_test.rs index afdf3d56..d4f9b8be 100644 --- a/src/commands/ai_test.rs +++ b/src/commands/ai_test.rs @@ -320,7 +320,7 @@ async fn generate_with_ai( ) -> Result { if !ollama::is_ollama_running().await { p::warn("Ollama is not running. Falling back to local generation."); - p::info(&ollama::cloud_fallback_message()); + p::info(ollama::cloud_fallback_message()); let analysis = ata::analyze_contract_for_testing(&request.contract_code)?; return generate_locally(request, &analysis); } @@ -404,8 +404,8 @@ fn generate_locally( description: format!("{} test for {}", test_type_str.replace('_', " "), func.name), code, priority: priority_suggestion.priority.clone(), - edge_cases_covered: generate_edge_case_descriptions(func), - security_checks: generate_security_checks(func), + edge_cases_covered: ata::generate_edge_case_descriptions(func), + security_checks: ata::generate_security_checks(func), }); } } @@ -420,7 +420,7 @@ fn generate_locally( analysis.public_functions ), estimated_coverage_improvement: estimated_improvement, - warnings: generate_warnings(analysis), + warnings: ata::generate_warnings(analysis), }) } @@ -452,10 +452,10 @@ fn test_{}_{}() {{ ) } -fn generate_setup_code(func: &ata::FunctionInfo, contract_name: &str) -> String { +fn generate_setup_code(func: &ata::FunctionInfo, _contract_name: &str) -> String { let mut lines = Vec::new(); - lines.push(format!("let contract_address = Address::random(&env);")); + lines.push("let contract_address = Address::random(&env);".to_string()); for param in &func.params { match param.param_type.as_str() { @@ -540,56 +540,6 @@ fn generate_assertions(func: &ata::FunctionInfo, test_type: &ata::TestType) -> S } } -fn generate_edge_case_descriptions(func: &ata::FunctionInfo) -> Vec { - let mut cases = Vec::new(); - for param in &func.params { - match param.param_type.as_str() { - t if t.contains("Address") => { - cases.push(format!("Zero address for {}", param.name)); - cases.push(format!("Self-referencing address for {}", param.name)); - cases.push(format!("Contract address for {}", param.name)); - } - t if t.contains("u64") || t.contains("i64") => { - cases.push(format!("Zero value for {}", param.name)); - cases.push(format!("Maximum value for {}", param.name)); - cases.push(format!("Minimum positive value for {}", param.name)); - } - t if t.contains("String") => { - cases.push(format!("Empty string for {}", param.name)); - cases.push(format!("Maximum length string for {}", param.name)); - cases.push(format!("Special characters for {}", param.name)); - } - _ => { - cases.push(format!("Default value for {}", param.name)); - } - } - } - if func.is_mutating { - cases.push("Unauthorized caller".to_string()); - cases.push("Double spend / replay".to_string()); - } - cases -} - -fn generate_security_checks(func: &ata::FunctionInfo) -> Vec { - let mut checks = Vec::new(); - if func.is_mutating { - checks.push("Authorization required for state changes".to_string()); - checks.push("Failed auth must not mutate state".to_string()); - checks.push("Replay protection verified".to_string()); - } - if func - .params - .iter() - .any(|p| p.param_type.contains("i64") || p.param_type.contains("u64")) - { - checks.push("Overflow/underflow protection".to_string()); - checks.push("Negative amount handling".to_string()); - } - checks.push("Input validation".to_string()); - checks -} - fn calculate_estimated_improvement( tests: &[ata::GeneratedTest], analysis: &ata::ContractAnalysis, @@ -603,28 +553,10 @@ fn calculate_estimated_improvement( base_improvement.min(50.0) } -fn generate_warnings(analysis: &ata::ContractAnalysis) -> Vec { - let mut warnings = Vec::new(); - if analysis.complex_functions > 3 { - warnings.push(format!( - "Contract has {} complex functions that may need additional test cases", - analysis.complex_functions - )); - } - if analysis.storage_accesses.len() > 5 { - warnings - .push("Contract has many storage accesses - ensure storage mock coverage".to_string()); - } - if !analysis.external_calls.is_empty() { - warnings.push("Contract makes external calls - consider integration tests".to_string()); - } - warnings -} - fn handle_generate_output( response: &ata::TestGenerationResponse, args: &GenerateArgs, - contract_name: &str, + _contract_name: &str, ) -> Result<()> { match args.format.as_str() { "json" => { @@ -1031,7 +963,7 @@ fn handle_coverage(args: CoverageArgs) -> Result<()> { } }; - let prompt = ata::build_coverage_improvement_prompt(&ata::CoverageAnalysisRequest { + let _prompt = ata::build_coverage_improvement_prompt(&ata::CoverageAnalysisRequest { source_code: source_code.clone(), test_code: test_code.clone(), coverage_data: coverage_data.clone(), @@ -1104,7 +1036,7 @@ fn handle_coverage(args: CoverageArgs) -> Result<()> { } fn analyze_coverage_gaps( - source_code: &str, + _source_code: &str, _test_code: &str, coverage: &ata::CoverageInput, ) -> Vec { @@ -1205,7 +1137,7 @@ fn handle_maintain(args: MaintainArgs) -> Result<()> { t.contains(source_func) || source_func .strip_prefix("test_") - .map_or(false, |stripped| t.contains(stripped)) + .is_some_and(|stripped| t.contains(stripped)) }); if !has_test { @@ -1603,7 +1535,7 @@ async fn handle_test_data(args: TestDataArgs) -> Result<()> { Ok(()) } -fn generate_local_test_data(suggestions: &[ata::TestDataSuggestion], count: u32) -> String { +fn generate_local_test_data(suggestions: &[ata::TestDataSuggestion], _count: u32) -> String { let mut code = String::from( "// Generated by StarForge AI Test Assistant\n// Test data generators and edge cases\n\n", ); diff --git a/src/commands/ai_test_gen.rs b/src/commands/ai_test_gen.rs index 486e6e7c..80a3360f 100644 --- a/src/commands/ai_test_gen.rs +++ b/src/commands/ai_test_gen.rs @@ -104,6 +104,10 @@ pub async fn handle(cmd: AiTestGenCommands) -> Result<()> { } } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn handle_generate( file: PathBuf, output: PathBuf, @@ -264,7 +268,7 @@ async fn handle_reset_analytics() -> Result<()> { p::header("Reset Test Generation Analytics"); p::separator(); - let generator = AiTestGenerator::new(); + let _generator = AiTestGenerator::new(); // Note: This would require adding a reset method to AiTestGenerator // For now, just inform the user p::info("Analytics reset functionality would be implemented here."); diff --git a/src/commands/ai_tutorial_cmd.rs b/src/commands/ai_tutorial_cmd.rs index d6e6abdc..a95b9f9c 100644 --- a/src/commands/ai_tutorial_cmd.rs +++ b/src/commands/ai_tutorial_cmd.rs @@ -4,10 +4,10 @@ //! personalized learning paths, and progress tracking. use crate::utils::{ - ai_tutorial::{SkillLevel, StepResult, TutorialManager, TutorialTopic}, + ai_tutorial::{SkillLevel, TutorialManager}, print as p, }; -use anyhow::{Context, Result}; +use anyhow::Result; use clap::Subcommand; use dialoguer::{Confirm, Input, Select}; diff --git a/src/commands/analytics.rs b/src/commands/analytics.rs index cc6da67c..93ef288a 100644 --- a/src/commands/analytics.rs +++ b/src/commands/analytics.rs @@ -3,7 +3,6 @@ use anyhow::Result; use chrono::Utc; use clap::{Args, Subcommand}; use colored::Colorize; -use colored::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -646,7 +645,7 @@ fn calculate_health_score(success_rate: f64, recent_failures: usize, trend: &str _ => {} } - score.max(0.0).min(100.0) + score.clamp(0.0, 100.0) } /// Calculate health score for a contract @@ -689,7 +688,7 @@ pub fn calculate_contract_health( let performance_score = if !fees.is_empty() { let avg_fee = fees.iter().sum::() as f64 / fees.len() as f64; // Lower fees = better performance score (baseline is 5000 stroops) - ((10000.0 - avg_fee) / 10000.0 * 100.0).max(0.0).min(100.0) + ((10000.0 - avg_fee) / 10000.0 * 100.0).clamp(0.0, 100.0) } else { 50.0 }; diff --git a/src/commands/approval.rs b/src/commands/approval.rs index 92baf6a6..cdb5fafb 100644 --- a/src/commands/approval.rs +++ b/src/commands/approval.rs @@ -316,7 +316,7 @@ fn handle_show_workflow(args: ShowWorkflowArgs) -> Result<()> { p::kv("Active", if workflow.active { "yes" } else { "no" }); p::kv( "Created", - &workflow + workflow .created_at .get(..19) .unwrap_or(&workflow.created_at), @@ -522,14 +522,14 @@ fn handle_show_request(args: ShowRequestArgs) -> Result<()> { p::kv("Level progress", &request.level_progress()); p::kv( "Created", - &request.created_at.get(..19).unwrap_or(&request.created_at), + request.created_at.get(..19).unwrap_or(&request.created_at), ); p::kv( "Updated", - &request.updated_at.get(..19).unwrap_or(&request.updated_at), + request.updated_at.get(..19).unwrap_or(&request.updated_at), ); if let Some(ref expiry) = request.expires_at { - p::kv("Expires", &expiry.get(..19).unwrap_or(expiry)); + p::kv("Expires", expiry.get(..19).unwrap_or(expiry)); } if let Some(ref wf) = workflow { @@ -701,7 +701,7 @@ fn handle_dashboard() -> Result<()> { let summary = get_approval_summary()?; let requests = list_requests(None, None)?; - let workflows = list_workflows(true)?; + let _workflows = list_workflows(true)?; p::separator(); p::kv("Total requests", &summary.total_requests.to_string()); diff --git a/src/commands/audit.rs b/src/commands/audit.rs index 773a512f..fcff21f2 100644 --- a/src/commands/audit.rs +++ b/src/commands/audit.rs @@ -502,7 +502,6 @@ mod tests { low: 0, info: 0, }, - ci_passed: true, }; let html = render_html_report(&result); assert!(html.contains("75.0/100")); diff --git a/src/commands/autocomplete.rs b/src/commands/autocomplete.rs index b9924651..d30d5029 100644 --- a/src/commands/autocomplete.rs +++ b/src/commands/autocomplete.rs @@ -1,6 +1,4 @@ -use crate::utils::history::{ - history_file_path, load_history, prune_history, save_history, HistoryEntry, -}; +use crate::utils::history::{load_history, prune_history, save_history, HistoryEntry}; use anyhow::Result; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/commands/bridge.rs b/src/commands/bridge.rs index 3beb03eb..9c40dee9 100644 --- a/src/commands/bridge.rs +++ b/src/commands/bridge.rs @@ -7,7 +7,7 @@ use crate::utils::bridge::{ save_config, security::SecurityVerifier, state::StateSynchronizer, - BridgeConfig, BridgeTransferRecord, + BridgeTransferRecord, }; use crate::utils::print as p; use anyhow::Result; diff --git a/src/commands/collab.rs b/src/commands/collab.rs index e9161b2a..f1dc183f 100644 --- a/src/commands/collab.rs +++ b/src/commands/collab.rs @@ -21,7 +21,7 @@ use chrono::{DateTime, Utc}; use clap::Subcommand; use serde::{Deserialize, Serialize}; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; // ─── Sub-command enum ────────────────────────────────────────────────────── @@ -145,7 +145,7 @@ fn save_store(store: &CollabStore) -> Result<()> { Ok(()) } -fn record_review(file: &PathBuf, kind: &str) { +fn record_review(file: &Path, kind: &str) { if let Ok(mut store) = load_store() { store.reviews.push(ReviewRecord { file: file.display().to_string(), @@ -371,7 +371,7 @@ fn handle_contributions(days: i64) -> Result<()> { let total: usize = counts.values().sum(); let mut rows: Vec<(String, usize)> = counts.into_iter().collect(); - rows.sort_by(|a, b| b.1.cmp(&a.1)); + rows.sort_by_key(|a| std::cmp::Reverse(a.1)); let headers = &["Author", "Commits", "Share"]; let table_rows: Vec> = rows diff --git a/src/commands/compliance.rs b/src/commands/compliance.rs index bb8a93bd..ad86b132 100644 --- a/src/commands/compliance.rs +++ b/src/commands/compliance.rs @@ -8,8 +8,6 @@ use crate::utils::print as p; use anyhow::Result; use clap::{Args, Subcommand}; use colored::Colorize; -use colored::*; -use std::collections::HashMap; #[derive(Subcommand)] pub enum ComplianceCommands { @@ -202,7 +200,7 @@ fn handle_init() -> Result<()> { enabled_mark, policy.name.white(), severity_color, - &policy.id[..12].cyan() + policy.id[..12].cyan() ); } @@ -244,7 +242,7 @@ fn handle_check(args: CheckArgs) -> Result<()> { p::kv("Network", &report.network); p::kv( "Timestamp", - &report.timestamp.get(..19).unwrap_or(&report.timestamp), + report.timestamp.get(..19).unwrap_or(&report.timestamp), ); println!(); @@ -252,7 +250,7 @@ fn handle_check(args: CheckArgs) -> Result<()> { println!( " {} {}\n", "Policy Checks".bright_white(), - &format!("({})", report.checks.len()).dimmed() + format!("({})", report.checks.len()).dimmed() ); p::separator(); @@ -277,7 +275,7 @@ fn handle_check(args: CheckArgs) -> Result<()> { println!( " {} {}\n", "Regulatory Checks".bright_white(), - &format!("({})", report.regulatory_checks.len()).dimmed() + format!("({})", report.regulatory_checks.len()).dimmed() ); p::separator(); @@ -309,7 +307,7 @@ fn handle_check(args: CheckArgs) -> Result<()> { println!( " {} {}\n", "Best Practices".bright_white(), - &format!("({})", report.best_practices.len()).dimmed() + format!("({})", report.best_practices.len()).dimmed() ); p::separator(); @@ -430,7 +428,7 @@ fn handle_list_policies() -> Result<()> { let type_str = format!("{:?}", policy.policy_type); println!( " {:<14} {:<36} {:<12} {:<10} {:<8}", - &policy.id[..12].cyan(), + policy.id[..12].cyan(), policy.name.truncate_or_pad(34), type_str, sev, @@ -461,11 +459,11 @@ fn handle_show_policy(args: ShowPolicyArgs) -> Result<()> { p::kv("Enabled", if policy.enabled { "yes" } else { "no" }); p::kv( "Created", - &policy.created_at.get(..19).unwrap_or(&policy.created_at), + policy.created_at.get(..19).unwrap_or(&policy.created_at), ); p::kv( "Updated", - &policy.updated_at.get(..19).unwrap_or(&policy.updated_at), + policy.updated_at.get(..19).unwrap_or(&policy.updated_at), ); if !policy.config.is_empty() { @@ -540,8 +538,8 @@ fn handle_list_reports(args: ListReportsArgs) -> Result<()> { let ts = report.timestamp.get(..16).unwrap_or(&report.timestamp); println!( " {:<14} {:<20} {:<12} {:<8} {:<8} {:<12}", - &report.request_id[..12].cyan(), - &report.contract_id.chars().take(18).collect::(), + report.request_id[..12].cyan(), + report.contract_id.chars().take(18).collect::(), report.network, status, report.blocking_count.to_string().red(), @@ -579,7 +577,7 @@ fn handle_show_report(args: ShowReportArgs) -> Result<()> { p::kv("Network", &report.network); p::kv( "Timestamp", - &report.timestamp.get(..19).unwrap_or(&report.timestamp), + report.timestamp.get(..19).unwrap_or(&report.timestamp), ); let status_str = if report.all_passed { format!("{}", "PASSED".green()) @@ -595,7 +593,7 @@ fn handle_show_report(args: ShowReportArgs) -> Result<()> { println!( " {} {}\n", "Check Results".bright_white(), - &format!("({})", report.checks.len()).dimmed() + format!("({})", report.checks.len()).dimmed() ); p::separator(); @@ -619,7 +617,7 @@ fn handle_show_report(args: ShowReportArgs) -> Result<()> { println!( " {} {}\n", "Regulatory Checks".bright_white(), - &format!("({})", report.regulatory_checks.len()).dimmed() + format!("({})", report.regulatory_checks.len()).dimmed() ); p::separator(); @@ -645,7 +643,7 @@ fn handle_show_report(args: ShowReportArgs) -> Result<()> { println!( " {} {}\n", "Best Practices".bright_white(), - &format!("({})", report.best_practices.len()).dimmed() + format!("({})", report.best_practices.len()).dimmed() ); p::separator(); @@ -864,7 +862,7 @@ fn handle_dashboard() -> Result<()> { println!( " {} {} | {} | {} | blocking: {}", status, - &report.request_id[..12].cyan(), + report.request_id[..12].cyan(), report.network, ts.dimmed(), report.blocking_count.to_string().red(), diff --git a/src/commands/config.rs b/src/commands/config.rs index f969e7b8..ecf8ca93 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -1,7 +1,7 @@ use crate::utils::database; use crate::utils::{config, print as p}; use anyhow::Result; -use clap::{Args, Subcommand}; +use clap::Subcommand; #[derive(Subcommand)] pub enum ConfigCommands { @@ -327,6 +327,9 @@ fn show() -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn set_value(key: &str, value: &str) -> Result<()> { let mut cfg = config::load()?; match key { @@ -349,6 +352,9 @@ fn set_value(key: &str, value: &str) -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn parse_bool(value: &str) -> Result { match value.to_ascii_lowercase().as_str() { "true" | "1" | "yes" | "on" | "enabled" => Ok(true), @@ -360,6 +366,9 @@ fn parse_bool(value: &str) -> Result { } } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn plugin_trust(cmd: PluginTrustCommands) -> Result<()> { match cmd { PluginTrustCommands::List => { @@ -400,6 +409,9 @@ fn plugin_trust(cmd: PluginTrustCommands) -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn print_plugin_trust_sources(cfg: &config::Config) { p::header("Trusted Plugin Sources"); if cfg.plugin_trust.trusted_sources.is_empty() { diff --git a/src/commands/deploy.rs b/src/commands/deploy.rs index 25e7bb74..8b90c3a4 100644 --- a/src/commands/deploy.rs +++ b/src/commands/deploy.rs @@ -5,8 +5,8 @@ use crate::utils::{ self, last_successful, record_deployment, set_contract_id, set_duration, update_status, DeployRecord, DeployStatus, }, - deployment_monitor, horizon, notifications, optimizer, output, print as p, simulation_resources, - soroban, wallet_signer, + deployment_monitor, horizon, notifications, optimizer, output, print as p, + simulation_resources, soroban, wallet_signer, wasm_hash::{compute_wasm_hash, BuildEnvironment}, wasm_preflight, }; diff --git a/src/commands/deployment_automate.rs b/src/commands/deployment_automate.rs index 62ed12bd..e4528db5 100644 --- a/src/commands/deployment_automate.rs +++ b/src/commands/deployment_automate.rs @@ -138,6 +138,10 @@ pub async fn handle(cmd: DeploymentAutomateCommands) -> Result<()> { } } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn handle_run( wasm: PathBuf, network: String, @@ -459,7 +463,7 @@ fn print_automation_result(result: &crate::utils::deployment_automation::Complet } } -fn handle_history(limit: usize) -> Result<()> { +fn handle_history(_limit: usize) -> Result<()> { p::header("Deployment Automation History"); p::separator(); diff --git a/src/commands/deployment_optimize.rs b/src/commands/deployment_optimize.rs index e9aead10..894788e4 100644 --- a/src/commands/deployment_optimize.rs +++ b/src/commands/deployment_optimize.rs @@ -364,7 +364,7 @@ fn print_optimization_result( } } -fn handle_history(limit: usize) -> Result<()> { +fn handle_history(_limit: usize) -> Result<()> { p::header("Deployment Optimization History"); p::separator(); diff --git a/src/commands/deployments.rs b/src/commands/deployments.rs index d1bb2aba..8d6516b5 100644 --- a/src/commands/deployments.rs +++ b/src/commands/deployments.rs @@ -1,5 +1,5 @@ use crate::utils::deploy_history::{ - get_record, last_successful, load_history, set_verified, update_status, DeployStatus, + get_record, last_successful, load_history, set_verified, DeployStatus, }; use crate::utils::deployment_monitor; use crate::utils::deployment_monitoring_service::{ @@ -449,7 +449,7 @@ fn handle_monitor(args: MonitorArgs) -> Result<()> { // Populate tracker with history for visual status let records = load_history().unwrap_or_default(); - for (idx, r) in records.iter().enumerate().take(5) { + for (_idx, r) in records.iter().enumerate().take(5) { let tr_id = format!("dep-{}", &r.id[..8.min(r.id.len())]); tracker.start_tracking(&tr_id, &r.network, &r.wallet); if r.status == DeployStatus::Success { diff --git a/src/commands/docs.rs b/src/commands/docs.rs index 19b3d5c7..af968885 100644 --- a/src/commands/docs.rs +++ b/src/commands/docs.rs @@ -293,6 +293,10 @@ fn maintain( Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn generate( contract: String, name: Option, diff --git a/src/commands/feature_flags_cmd.rs b/src/commands/feature_flags_cmd.rs index b2a97dfa..ea82c843 100644 --- a/src/commands/feature_flags_cmd.rs +++ b/src/commands/feature_flags_cmd.rs @@ -8,7 +8,7 @@ //! - `enable ` / `disable ` //! - `rollout --percent N` – set the global rollout percentage //! - `segment add|remove|list` – manage segment rules (allow-list, %, -//! attribute predicate) +//! attribute predicate) //! - `variant add|remove|list` – manage A/B variants //! - `override set|clear|list` – per-user overrides //! - `metrics show|prune [--days]` @@ -21,9 +21,7 @@ use crate::utils::config; use crate::utils::database::Database; -use crate::utils::feature_flags::{ - self, FlagCategory, FlagManager, MetricKind, SegmentRule, UserContext, Variant, -}; +use crate::utils::feature_flags::{FlagCategory, FlagManager, SegmentRule, UserContext, Variant}; use crate::utils::print as p; use anyhow::{bail, Context, Result}; use clap::{Args, Subcommand}; @@ -312,6 +310,9 @@ pub async fn handle(args: FeatureFlagsArgs) -> Result<()> { // ── Helpers shared by subcommands ───────────────────────────────────────────── +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn hydrate_state(mgr: &FlagManager, db: &Database, flag_name: &str) -> Result<()> { if db.get_definition(flag_name)?.is_none() { bail!( @@ -963,6 +964,7 @@ fn format_rule(rule: &SegmentRule) -> String { #[cfg(test)] mod tests { use super::*; + use crate::utils::feature_flags::MetricKind; #[test] fn format_rule_user_in_list() { diff --git a/src/commands/governance.rs b/src/commands/governance.rs index 82a51e7d..77c828cb 100644 --- a/src/commands/governance.rs +++ b/src/commands/governance.rs @@ -1,6 +1,4 @@ -use crate::utils::governance::{ - self, DashboardSummary, GovernanceConfig, GovernanceProposal, VoteChoice, -}; +use crate::utils::governance::{self, DashboardSummary, GovernanceProposal, VoteChoice}; use crate::utils::{config, confirmation, horizon, print as p}; use anyhow::Result; use clap::{Args, Subcommand}; diff --git a/src/commands/help.rs b/src/commands/help.rs index 5375753b..54e0ab67 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -172,7 +172,7 @@ async fn handle_command(cmd: &str, args: &HelpArgs) -> Result<()> { let canonical = cmd.trim().to_lowercase(); p::header(&format!("Help: {}", canonical)); let summary_line = - context_help::command_summary(&canonical).unwrap_or_else(|| help.description.as_str()); + context_help::command_summary(&canonical).unwrap_or(help.description.as_str()); println!(" {}", summary_line.dimmed()); println!(); @@ -338,12 +338,7 @@ fn expand_workflow(slug: &str) -> Result<()> { async fn handle_why(args: &HelpArgs) -> Result<()> { let error_text: Option = match args.error.clone() { Some(text) => Some(text), - None => match args.command.clone() { - // Treat `starforge help --why "some text"` (positional argument) - // as the error text too — it's the most ergonomic shape. - Some(text) => Some(text), - None => None, - }, + None => args.command.clone(), }; let error_text = match error_text { diff --git a/src/commands/migrate_ai.rs b/src/commands/migrate_ai.rs index ed091bea..150fea70 100644 --- a/src/commands/migrate_ai.rs +++ b/src/commands/migrate_ai.rs @@ -1,6 +1,6 @@ use crate::utils::migration_ai; use crate::utils::migration_ai::{AnalysisConfig, MigrationPlan}; -use crate::utils::{config, print as p}; +use crate::utils::print as p; use anyhow::{Context, Result}; use clap::{Args, Subcommand}; use colored::*; @@ -220,6 +220,10 @@ fn load_spec_entries( Ok(Vec::new()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn build_analysis_config( old_specs: &[String], new_specs: &[String], @@ -251,6 +255,10 @@ fn build_analysis_config( }) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn load_or_build_plan( old_wasm: Option<&PathBuf>, new_wasm: Option<&PathBuf>, @@ -404,7 +412,7 @@ fn handle_generate(args: GenerateArgs) -> Result<()> { let storage_changes = &plan.storage_changes; let contract_name = args.contract.unwrap_or_else(|| "Contract".to_string()); - let sdk_version = plan.to_version.clone(); + let _sdk_version = plan.to_version.clone(); let mut code = String::new(); code.push_str(&format!( @@ -527,7 +535,7 @@ fn handle_suggest(args: SuggestArgs) -> Result<()> { let priority_color = match suggestion.priority.as_str() { "high" => "HIGH".red().bold(), "medium" => "MEDIUM".yellow().bold(), - "low" | _ => "LOW".cyan(), + _ => "LOW".cyan(), }; println!( @@ -630,7 +638,7 @@ fn handle_plan(args: PlanArgs) -> Result<()> { } fn print_plan_summary(plan: &MigrationPlan) { - let compat_color = match plan.compatibility { + let _compat_color = match plan.compatibility { crate::utils::migration_ai::Compatibility::FullyCompatible => { "fully compatible".green().bold() } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b1e4615d..8893ca02 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -13,8 +13,8 @@ pub mod ai_feedback; pub mod ai_ide; pub mod ai_model_router; pub mod ai_navigate; -pub mod ai_profile; pub mod ai_plan; +pub mod ai_profile; pub mod ai_property_test; pub mod ai_quality_gate; pub mod ai_recommend; @@ -67,8 +67,8 @@ pub mod multi_network; pub mod multisig_builder; pub mod mutate; pub mod network; -pub mod nl; pub mod new; +pub mod nl; pub mod node; pub mod optimize; pub mod orchestrate; diff --git a/src/commands/monitor.rs b/src/commands/monitor.rs index 6be48479..e54f64c1 100644 --- a/src/commands/monitor.rs +++ b/src/commands/monitor.rs @@ -8,7 +8,7 @@ use crate::utils::{ }; use anyhow::Result; use clap::Args; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -162,6 +162,10 @@ pub async fn handle(args: MonitorArgs) -> Result<()> { } } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn monitor_contract( contract_id: &str, events_filter: Option<&str>, @@ -312,10 +316,14 @@ async fn monitor_contract( Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn replay_contract_events( contract_id: &str, network: &str, - replay_path: &PathBuf, + replay_path: &Path, legacy_filter_set: &Option>, stream_filters: &EventStreamFilters, router: &EventRouter, @@ -323,7 +331,7 @@ fn replay_contract_events( triggers: &[EventTrigger], dashboard: bool, ) -> Result<()> { - let store = EventStore::new(replay_path.clone()); + let store = EventStore::new(replay_path.to_path_buf()); let events = store.replay()?; notifications::info(&format!( "Replaying {} persisted event(s) from {}.", @@ -365,6 +373,10 @@ fn replay_contract_events( Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn process_contract_event( network: &str, contract_id: &str, diff --git a/src/commands/multi_network.rs b/src/commands/multi_network.rs index 2a495d86..aca03439 100644 --- a/src/commands/multi_network.rs +++ b/src/commands/multi_network.rs @@ -3,7 +3,7 @@ //! Provides CLI commands for AI-driven multi-network deployment support. use crate::utils::{ - multi_network_deploy::{DeploymentStrategy, MultiNetworkConfig, MultiNetworkDeployer}, + multi_network_deploy::{DeploymentStrategy, MultiNetworkDeployer}, print as p, }; use anyhow::Result; @@ -322,7 +322,7 @@ fn print_deployment_result( println!(); } -fn handle_compare(include_custom: bool) -> Result<()> { +fn handle_compare(_include_custom: bool) -> Result<()> { p::header("Network Comparison"); p::separator(); diff --git a/src/commands/network.rs b/src/commands/network.rs index a9d0a067..8c3bf8cd 100644 --- a/src/commands/network.rs +++ b/src/commands/network.rs @@ -229,7 +229,7 @@ async fn test_network(network_name: Option) -> Result<()> { p::info(&format!("Testing connectivity to '{}'…", test_network)); p::info(&format!("Horizon: {}", net_cfg.horizon_url)); - let client = reqwest::Client::builder() + let _client = reqwest::Client::builder() .timeout(Duration::from_secs(10)) .pool_max_idle_per_host(10) .build()?; @@ -237,7 +237,7 @@ async fn test_network(network_name: Option) -> Result<()> { // Test Horizon endpoint let client = http_client::get_client(); match client - .get(&format!("{}/health", net_cfg.horizon_url)) + .get(format!("{}/health", net_cfg.horizon_url)) .send() .await { diff --git a/src/commands/new.rs b/src/commands/new.rs index 67af8f59..8bc3e957 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -201,6 +201,10 @@ async fn scaffold_contract_interactive(default_name: String) -> Result<()> { .await } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn scaffold_contract( name: String, template: String, @@ -879,14 +883,23 @@ fn dapp_index(name: &str) -> String { ) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn dapp_tsconfig() -> String { r#"{"compilerOptions": {"target": "es2020", "module": "esnext", "moduleResolution": "node", "esModuleInterop": true}}"#.to_string() } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn dapp_tsconfig_node() -> String { r#"{"extends": "./tsconfig.json", "compilerOptions": {"module": "commonjs", "target": "es2020", "moduleResolution": "node", "esModuleInterop": true}}"#.to_string() } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn dapp_vite_env_types(wallet_kit: bool) -> String { if wallet_kit { r#"interface ImportMetaEnv { VITE_NETWORK: string; VITE_WALLET_KIT: boolean; }"#.to_string() @@ -977,6 +990,9 @@ Source: `{source}` // ── Template Marketplace ────────────────────────────────────────────────────── +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn handle_template_search(query: &str, tags: Option<&str>) -> Result<()> { p::header("Template Marketplace — Search"); p::kv("Query", query); @@ -1078,6 +1094,9 @@ impl Drop for PathCleanup { /// Run a single install step behind a spinner, finishing with a check mark on /// success or clearing the spinner and attaching an actionable message on /// failure. +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn install_step( label: &str, done: &str, @@ -1097,6 +1116,9 @@ fn install_step( } } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn scaffold_from_marketplace(name: String, template_name: String) -> Result<()> { p::header(&format!("Scaffolding from Marketplace: {}", template_name)); @@ -1222,6 +1244,9 @@ async fn scaffold_from_marketplace(name: String, template_name: String) -> Resul Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn copy_template_contents(src: &Path, dst: &Path, project_name: &str) -> Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; diff --git a/src/commands/nl.rs b/src/commands/nl.rs index 1c9ccc57..a58854ac 100644 --- a/src/commands/nl.rs +++ b/src/commands/nl.rs @@ -126,13 +126,15 @@ pub struct ExtractedEntities { pub keywords: Vec, } - // ── Pattern Definitions ──────────────────────────────────────────────────── struct Pattern { keywords: &'static [&'static str], intent_factory: fn(&ExtractedEntities) -> Intent, confidence: f64, + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] explanation: &'static str, } @@ -212,7 +214,8 @@ static PATTERNS: Lazy> = Lazy::new(|| { keywords: &["show", "network"], intent_factory: |_| Intent::ShowNetwork, confidence: 0.9, - explanation: "Shows the currently active network (testnet/mainnet) and its configuration.", + explanation: + "Shows the currently active network (testnet/mainnet) and its configuration.", }, Pattern { keywords: &["switch", "network"], @@ -287,14 +290,14 @@ static PATTERNS: Lazy> = Lazy::new(|| { const STOP_WORDS: &[&str] = &[ "a", "an", "the", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "shall", "should", "may", "might", "must", "can", - "could", "i", "you", "he", "she", "it", "we", "they", "me", "him", "her", "us", "them", - "my", "your", "his", "its", "our", "their", "this", "that", "these", "those", "am", "to", - "of", "in", "for", "on", "with", "at", "by", "from", "up", "about", "into", "through", - "during", "before", "after", "above", "below", "between", "out", "off", "over", "under", - "again", "further", "then", "once", "and", "but", "or", "nor", "not", "so", "very", "just", - "than", "too", "also", "here", "there", "when", "where", "why", "how", "all", "each", - "every", "both", "few", "more", "most", "other", "some", "such", "no", "only", "own", - "same", "now", "if", "please", "show", "me", "want", + "could", "i", "you", "he", "she", "it", "we", "they", "me", "him", "her", "us", "them", "my", + "your", "his", "its", "our", "their", "this", "that", "these", "those", "am", "to", "of", "in", + "for", "on", "with", "at", "by", "from", "up", "about", "into", "through", "during", "before", + "after", "above", "below", "between", "out", "off", "over", "under", "again", "further", + "then", "once", "and", "but", "or", "nor", "not", "so", "very", "just", "than", "too", "also", + "here", "there", "when", "where", "why", "how", "all", "each", "every", "both", "few", "more", + "most", "other", "some", "such", "no", "only", "own", "same", "now", "if", "please", "show", + "me", "want", ]; /// Extracts entities from the natural language input. @@ -306,14 +309,12 @@ fn extract_entities(input: &str) -> ExtractedEntities { // Extract wallet name (after "named", "called", "as", "name", or directly after "wallet") for i in 0..words.len() { - if matches!(words[i], "named" | "called" | "as" | "name") { - if i + 1 < words.len() { - let name = words[i + 1] - .trim_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-'); - if !name.is_empty() { - entities.wallet_name = Some(name.to_string()); - break; - } + if matches!(words[i], "named" | "called" | "as" | "name") && i + 1 < words.len() { + let name = + words[i + 1].trim_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-'); + if !name.is_empty() { + entities.wallet_name = Some(name.to_string()); + break; } } } @@ -370,33 +371,32 @@ fn extract_entities(input: &str) -> ExtractedEntities { // Extract amount (numbers after "fund", "send", etc.) for i in 0..words.len() { - if matches!(words[i], "fund" | "send" | "pay") { - if i + 1 < words.len() { - if words[i + 1].parse::().is_ok() { - entities.amount = Some(words[i + 1].to_string()); - break; - } - } + if matches!(words[i], "fund" | "send" | "pay") + && i + 1 < words.len() + && words[i + 1].parse::().is_ok() + { + entities.amount = Some(words[i + 1].to_string()); + break; } } // Extract function name (after "call", "run", "execute") for i in 0..words.len() { - if matches!(words[i], "call" | "run" | "execute" | "invoke") { - if i + 1 < words.len() { - let func = words[i + 1] - .trim_matches(|c: char| !c.is_alphanumeric() && c != '_'); - if !func.is_empty() { - entities.function_name = Some(func.to_string()); - break; - } + if matches!(words[i], "call" | "run" | "execute" | "invoke") && i + 1 < words.len() { + let func = words[i + 1].trim_matches(|c: char| !c.is_alphanumeric() && c != '_'); + if !func.is_empty() { + entities.function_name = Some(func.to_string()); + break; } } } // Collect meaningful keywords (exclude stop words) for word in &words { - let cleaned: String = word.chars().filter(|c| c.is_alphanumeric() || *c == '_').collect(); + let cleaned: String = word + .chars() + .filter(|c| c.is_alphanumeric() || *c == '_') + .collect(); if !cleaned.is_empty() && cleaned.len() > 2 && !STOP_WORDS.contains(&cleaned.as_str()) @@ -575,8 +575,7 @@ fn generate_explanation(intent: &Intent, command: &str) -> String { } Intent::ListWallets => { explanation.push_str("📋 I'll list all wallets saved locally.\n\n"); - explanation - .push_str("This shows wallet names, public keys, and networks.\n"); + explanation.push_str("This shows wallet names, public keys, and networks.\n"); } Intent::ShowWallet { name } => { explanation.push_str(&format!( @@ -592,8 +591,7 @@ fn generate_explanation(intent: &Intent, command: &str) -> String { "💰 I'll fund wallet '{}' via the testnet faucet.\n\n", name.as_deref().unwrap_or("wallet"), )); - explanation - .push_str("Friendbot sends 10,000 XLM to testnet accounts.\n"); + explanation.push_str("Friendbot sends 10,000 XLM to testnet accounts.\n"); } Intent::DeployContract { wallet, network } => { explanation.push_str("🚀 I'll deploy a compiled Soroban contract.\n\n"); @@ -626,23 +624,16 @@ fn generate_explanation(intent: &Intent, command: &str) -> String { ); } Intent::SwitchNetwork { network } => { - explanation.push_str(&format!( - "🔄 I'll switch to the {} network.\n\n", - network - )); - explanation - .push_str("This changes the active network for all subsequent commands.\n"); + explanation.push_str(&format!("🔄 I'll switch to the {} network.\n\n", network)); + explanation.push_str("This changes the active network for all subsequent commands.\n"); } Intent::StartNode => { - explanation - .push_str("🐳 I'll start a local Soroban devnet via Docker.\n\n"); + explanation.push_str("🐳 I'll start a local Soroban devnet via Docker.\n\n"); explanation.push_str("This launches a local Stellar node for testing.\n"); } Intent::RunDoctor => { explanation.push_str("🩺 I'll run diagnostics on your StarForge installation.\n\n"); - explanation.push_str( - "This checks for missing dependencies and connectivity issues.\n", - ); + explanation.push_str("This checks for missing dependencies and connectivity issues.\n"); } _ => { explanation.push_str("I'll execute the following command:\n\n"); @@ -728,10 +719,7 @@ pub async fn handle(args: NlArgs) -> Result<()> { println!(); p::kv("Input", input); p::kv("Intent", &format!("{:?}", intent)); - p::kv( - "Confidence", - &format!("{:.0}%", confidence * 100.0), - ); + p::kv("Confidence", &format!("{:.0}%", confidence * 100.0)); println!(); if !entities.keywords.is_empty() { @@ -807,9 +795,7 @@ pub async fn handle(args: NlArgs) -> Result<()> { println!(" {}. {}", i + 1, candidate.bright_white()); } println!(); - p::info( - "Please be more specific or use `starforge --help`.", - ); + p::info("Please be more specific or use `starforge --help`."); return Ok(()); } diff --git a/src/commands/optimize.rs b/src/commands/optimize.rs index f63db557..0470d66a 100644 --- a/src/commands/optimize.rs +++ b/src/commands/optimize.rs @@ -382,11 +382,12 @@ pub fn analyse_source(content: &str, file: &str) -> Vec { } // Suggest soroban_sdk::Vec instead of std::vec::Vec - if trimmed.contains("Vec<") && !trimmed.starts_with("//") { - if trimmed.contains("std::vec") - || (trimmed.contains("Vec<") && trimmed.contains("use std")) - { - suggestions.push(TransformSuggestion { + if trimmed.contains("Vec<") + && !trimmed.starts_with("//") + && (trimmed.contains("std::vec") + || (trimmed.contains("Vec<") && trimmed.contains("use std"))) + { + suggestions.push(TransformSuggestion { file: file.to_string(), line: line_no, category: TransformCategory::RedundantCode, @@ -394,7 +395,6 @@ pub fn analyse_source(content: &str, file: &str) -> Vec { suggested: line.replace("std::vec::Vec", "soroban_sdk::Vec").to_string(), reason: "Prefer soroban_sdk::Vec over std::vec::Vec in contract code for Soroban compatibility.".to_string(), }); - } } // Flag large string literals in contract code @@ -621,7 +621,7 @@ fn detect_storage_packing(content: &str, file: &str) -> Vec } if fields.len() >= 2 { let mut sorted = fields.clone(); - sorted.sort_by(|a, b| b.1.cmp(&a.1)); + sorted.sort_by_key(|a| std::cmp::Reverse(a.1)); if sorted != fields { suggestions.push(TransformSuggestion { file: file.to_string(), diff --git a/src/commands/perf.rs b/src/commands/perf.rs index e9dafc4b..03c33145 100644 --- a/src/commands/perf.rs +++ b/src/commands/perf.rs @@ -1,7 +1,6 @@ use crate::utils::{contract_profiler, performance as perf, print as p}; use anyhow::Result; use clap::Subcommand; -use std::collections::BTreeMap; use std::collections::HashMap; use std::path::PathBuf; diff --git a/src/commands/plugin.rs b/src/commands/plugin.rs index 00cd85b2..a15d1a67 100644 --- a/src/commands/plugin.rs +++ b/src/commands/plugin.rs @@ -273,6 +273,7 @@ fn list(json: bool) -> Result<()> { version: String, trust: String, source: String, + description: String, commands: Vec, } @@ -282,20 +283,20 @@ fn list(json: bool) -> Result<()> { description: String, } - let plugins: Vec = reg - .plugins - .iter() + let plugins: Vec = registry::plugin_list_entries(®) + .into_iter() .map(|entry| PluginSummary { - name: entry.name.clone(), - version: entry.plugin_version.clone(), + name: entry.name, + version: entry.plugin_version, trust: entry.trust.label().to_string(), - source: entry.source.clone(), + source: entry.source, + description: entry.description, commands: entry .commands - .iter() + .into_iter() .map(|cmd| PluginCommandSummary { - name: cmd.name.clone(), - description: cmd.description.clone(), + name: cmd.name, + description: cmd.description, }) .collect(), }) @@ -316,21 +317,22 @@ fn list(json: bool) -> Result<()> { p::kv("StarForge core version", CORE_VERSION); p::separator(); - let entries = reg.plugins.clone(); + let list_entries = registry::plugin_list_entries(®); - let plugin_rows: Vec> = entries + let plugin_rows: Vec> = list_entries .iter() .map(|entry| { vec![ entry.name.clone(), entry.plugin_version.clone(), entry.trust.label().to_string(), - "".to_string(), + entry.description.clone(), ] }) .collect(); p::table(&["Name", "Version", "Trust", "Description"], &plugin_rows); + let entries = reg.plugins.clone(); let command_rows: Vec> = entries .iter() .flat_map(|entry| { @@ -363,7 +365,7 @@ fn load() -> Result<()> { return Ok(()); } - let config = config::load().unwrap_or_default(); + let _config = config::load().unwrap_or_default(); // Warn about any unknown-trust plugins before loading. for pl in reg.plugins.iter().filter(|p| { @@ -486,6 +488,9 @@ fn uninstall(name: String, purge: bool, yes: bool) -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn discover_commands_from_library(lib_path: &str) -> Result> { let path = Path::new(lib_path); let mut pm = PluginManager::new(); @@ -516,7 +521,7 @@ fn update(name: Option, yes: bool) -> Result<()> { return Ok(()); } - let config = config::load().unwrap_or_default(); + let _config = config::load().unwrap_or_default(); let to_update: Vec<_> = match &name { Some(n) => { @@ -720,7 +725,7 @@ fn verify(name: Option, deep: bool, runtime_check: bool) -> Result<()> { None => reg.plugins.iter().collect(), }; - let config = config::load().unwrap_or_default(); + let _config = config::load().unwrap_or_default(); let mut all_ok = true; for pl in &to_check { diff --git a/src/commands/privacy.rs b/src/commands/privacy.rs index 781ff2bc..c0b830d9 100644 --- a/src/commands/privacy.rs +++ b/src/commands/privacy.rs @@ -1,7 +1,6 @@ use crate::utils::{print as p, privacy}; use anyhow::Result; -use clap::{Args, Subcommand}; -use serde_json::json; +use clap::Subcommand; #[derive(Subcommand)] pub enum PrivacyCommands { diff --git a/src/commands/project.rs b/src/commands/project.rs index 5c4b211e..b67bfc01 100644 --- a/src/commands/project.rs +++ b/src/commands/project.rs @@ -9,15 +9,8 @@ //! - `risk` – Assess and mitigate project risks using AI analysis //! - `timeline` – Manage project timelines, milestones, and deadlines -use crate::utils::{config, ollama, print as p}; -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; +use anyhow::Result; use clap::{Args, Subcommand}; -use colored::*; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; // ─── Top-level Subcommand ────────────────────────────────────────── diff --git a/src/commands/recommend.rs b/src/commands/recommend.rs index 57c49d38..010b4576 100644 --- a/src/commands/recommend.rs +++ b/src/commands/recommend.rs @@ -46,7 +46,7 @@ pub async fn handle( .collect(); let skill_level = match skill.as_deref() { - Some(s) => match rec::SkillLevel::from_str(s) { + Some(s) => match rec::SkillLevel::parse_lenient(s) { Some(level) => level, None => { p::warn(&format!( diff --git a/src/commands/registry.rs b/src/commands/registry.rs index a45cdd2f..e48ee3b1 100644 --- a/src/commands/registry.rs +++ b/src/commands/registry.rs @@ -381,6 +381,10 @@ fn logout() -> Result<()> { Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn publish( path: PathBuf, name: Option, diff --git a/src/commands/security.rs b/src/commands/security.rs index b67c88a9..abaac1cb 100644 --- a/src/commands/security.rs +++ b/src/commands/security.rs @@ -10,7 +10,6 @@ use crate::utils::stream::{EventStreamFilters, SorobanEventStream}; use crate::utils::{config, notifications, soroban}; use anyhow::Result; use clap::{Args, Subcommand}; -use colored::Colorize; use std::fs; use std::path::PathBuf; use std::sync::{ @@ -692,12 +691,9 @@ fn handle_threat_detect(args: ThreatDetectArgs) -> Result<()> { p::kv("Malicious", &summary.malicious.to_string()); p::kv("Suspicious", &summary.suspicious.to_string()); - match args.format.as_str() { - "json" => { - let json = serde_json::to_string_pretty(&event)?; - println!("{}", json); - } - _ => {} + if args.format.as_str() == "json" { + let json = serde_json::to_string_pretty(&event)?; + println!("{}", json); } if event.classification == crate::utils::security::ThreatClassification::Malicious { diff --git a/src/commands/social.rs b/src/commands/social.rs index b158bca0..b74714a4 100644 --- a/src/commands/social.rs +++ b/src/commands/social.rs @@ -1,7 +1,6 @@ use crate::utils::{config, print as p, social}; use anyhow::Result; use clap::{Args, Subcommand}; -use std::path::PathBuf; #[derive(Subcommand)] pub enum SocialCommands { diff --git a/src/commands/template.rs b/src/commands/template.rs index adb9e29b..9f4645d9 100644 --- a/src/commands/template.rs +++ b/src/commands/template.rs @@ -1,9 +1,8 @@ use crate::utils::template_integration; use crate::utils::template_performance; -use crate::utils::{output, print as p, registry, template_customization_ai, templates}; +use crate::utils::{output, print as p, template_customization_ai, templates}; use anyhow::{Context, Result}; use clap::Subcommand; -use colored::Colorize; use std::path::PathBuf; #[derive(Subcommand)] @@ -274,6 +273,9 @@ pub async fn handle(cmd: TemplateCommands) -> Result<()> { } } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn template_assist( template: String, project: PathBuf, @@ -322,6 +324,10 @@ async fn template_assist( } Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn import( path: PathBuf, name: Option, @@ -352,6 +358,10 @@ async fn import( Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn publish( path: PathBuf, name: Option, @@ -729,6 +739,9 @@ fn init() -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn optimize(path: PathBuf, name: Option) -> Result<()> { let analysis = template_performance::analyze_template_directory(&path, name.as_deref())?; @@ -895,6 +908,9 @@ async fn info(name: String) -> Result<()> { Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] async fn fetch( source: String, name: Option, diff --git a/src/commands/template_security.rs b/src/commands/template_security.rs index 5a56bac5..a85e8960 100644 --- a/src/commands/template_security.rs +++ b/src/commands/template_security.rs @@ -155,7 +155,7 @@ async fn handle_scan( fn print_scan_result(result: &crate::utils::template_security_scanner::TemplateSecurityScanResult) { // Overall score - let score_color = if result.security_score >= 80.0 { + let _score_color = if result.security_score >= 80.0 { "green" } else if result.security_score >= 60.0 { "yellow" @@ -284,7 +284,7 @@ fn print_scan_result(result: &crate::utils::template_security_scanner::TemplateS } } -fn handle_history(limit: usize) -> Result<()> { +fn handle_history(_limit: usize) -> Result<()> { p::header("Security Scan History"); p::separator(); diff --git a/src/commands/test.rs b/src/commands/test.rs index 141726b0..113d4436 100644 --- a/src/commands/test.rs +++ b/src/commands/test.rs @@ -1,6 +1,6 @@ use crate::utils::{ config, contract_testing, print as p, rollback_testing, test_automation, test_coverage, - test_generator, test_runner, + test_runner, }; use anyhow::Result; use clap::Args; @@ -496,7 +496,7 @@ pub async fn handle(args: TestArgs) -> Result<()> { } } - let timings: Vec = report + let _timings: Vec = report .results .iter() .map(|r| crate::utils::test_optimizer::TestCaseTiming { diff --git a/src/commands/upgrade_auto.rs b/src/commands/upgrade_auto.rs index 84ee760c..700b15e7 100644 --- a/src/commands/upgrade_auto.rs +++ b/src/commands/upgrade_auto.rs @@ -501,22 +501,20 @@ pub fn analyse_compat( let old_spec = decode_spec_model(old_bytes); let new_spec = decode_spec_model(new_bytes); - match (&old_spec, &new_spec) { - (Err(err), _) => issues.push(CompatIssue { + if let (Err(err), _) = (&old_spec, &new_spec) { + issues.push(CompatIssue { kind: "old-abi-metadata-missing".to_string(), severity: "warning".to_string(), description: format!("Unable to decode old contract ABI metadata: {err}"), - }), - _ => {} + }) } - match (&old_spec, &new_spec) { - (_, Err(err)) => issues.push(CompatIssue { + if let (_, Err(err)) = (&old_spec, &new_spec) { + issues.push(CompatIssue { kind: "new-abi-metadata-missing".to_string(), severity: "warning".to_string(), description: format!("Unable to decode new contract ABI metadata: {err}"), - }), - _ => {} + }) } let abi = match (&old_spec, &new_spec) { diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs index 9901c10d..81ec0edd 100644 --- a/src/commands/wallet.rs +++ b/src/commands/wallet.rs @@ -8,8 +8,7 @@ use clap::Subcommand; use colored::*; use ed25519_dalek::{Signer, SigningKey}; use rand::RngCore; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; +use serde::Serialize; use std::fs; use std::path::PathBuf; use stellar_strkey::ed25519::{PrivateKey as StellarPrivateKey, PublicKey as StellarPublicKey}; @@ -584,6 +583,10 @@ fn prompt_recovery_phrase() -> Result { Ok(phrase) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn create( name: String, fund: bool, @@ -1170,6 +1173,10 @@ fn rename(old_name: String, new_name: String) -> Result<()> { Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn rotate_wallet( name: String, fund: bool, @@ -1315,6 +1322,9 @@ async fn rotate_wallet( Ok(()) } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn wallet_history(name: String, reveal: bool) -> Result<()> { config::validate_wallet_name(&name)?; let cfg = config::load()?; @@ -1460,6 +1470,10 @@ fn export_wallet(name_opt: Option, all: bool, output: PathBuf, strict: b Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn import_wallet( name: Option, file: Option, diff --git a/src/lib.rs b/src/lib.rs index df34f074..94695b33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(dead_code, unused, clippy::all)] - pub mod commands; pub mod plugins; pub mod utils; diff --git a/src/main.rs b/src/main.rs index 4100713c..01da1ad8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![allow(dead_code, unused, clippy::all)] - pub use starforge::commands; pub mod curation; pub use starforge::plugins; @@ -777,11 +775,9 @@ fn recovery_hints(command: &str, err: &anyhow::Error) -> Vec { hints.push("Analyze a contract: starforge ai-recommend analyze src/lib.rs".into()); hints.push("Scan a project: starforge ai-recommend scan .".into()); } - "benchmark" | "test" => { - if msg.contains("wasm") || msg.contains("not found") { - hints.push("Build your contract first: stellar contract build".into()); - hints.push("Pass the correct --wasm path to the command.".into()); - } + "benchmark" | "test" if (msg.contains("wasm") || msg.contains("not found")) => { + hints.push("Build your contract first: stellar contract build".into()); + hints.push("Pass the correct --wasm path to the command.".into()); } _ => {} @@ -814,7 +810,7 @@ fn handle_external_plugin(args: Vec) -> anyhow::Result<()> { let plugin_name = &args[0]; let plugin_args = &args[1..]; - let cfg = starforge::utils::config::load()?; + let _cfg = starforge::utils::config::load()?; let reg = plugins::registry::load_registry().unwrap_or_default(); if reg.plugins.is_empty() { anyhow::bail!( diff --git a/src/plugins/loader.rs b/src/plugins/loader.rs index c9c07f29..0d6f470d 100644 --- a/src/plugins/loader.rs +++ b/src/plugins/loader.rs @@ -6,9 +6,8 @@ use crate::plugins::interface::{ use crate::plugins::manifest; use crate::plugins::registry::{load_registry, TrustLevel}; use anyhow::Result; -use libloading::{Library, Symbol}; +use libloading::Library; use std::collections::HashMap; -use std::ffi::OsStr; use std::path::Path; use std::rc::Rc; @@ -335,7 +334,7 @@ impl PluginManager { if let Some((plugin, _)) = self.plugins.get(name) { plugin.execute(args) } else { - return Err(format!("Plugin '{}' not found", name)); + Err(format!("Plugin '{}' not found", name)) } } } diff --git a/src/plugins/manifest.rs b/src/plugins/manifest.rs index 9602e331..b071dbdc 100644 --- a/src/plugins/manifest.rs +++ b/src/plugins/manifest.rs @@ -35,13 +35,14 @@ impl SupportedVersionPolicy { /// Evaluates compatibility of a plugin manifest against this supported-version policy. pub fn evaluate(&self, manifest: &PluginManifest) -> Result<()> { - let (plugin_major, _, _) = parse_version_parts(&manifest.starforge_version).ok_or_else(|| { - anyhow::anyhow!( - "Invalid 'starforge_version' format in manifest for '{}': '{}'", - manifest.name, - manifest.starforge_version - ) - })?; + let (plugin_major, _, _) = + parse_version_parts(&manifest.starforge_version).ok_or_else(|| { + anyhow::anyhow!( + "Invalid 'starforge_version' format in manifest for '{}': '{}'", + manifest.name, + manifest.starforge_version + ) + })?; if plugin_major != self.supported_major { anyhow::bail!( diff --git a/src/plugins/registry.rs b/src/plugins/registry.rs index 7d2f14d4..65e5c911 100644 --- a/src/plugins/registry.rs +++ b/src/plugins/registry.rs @@ -1,5 +1,4 @@ -use crate::plugins::manifest; -use crate::utils::config::{self, Config}; +use crate::utils::config::Config; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs; @@ -227,6 +226,12 @@ pub struct InstalledPlugin { /// Commands this plugin registers. #[serde(default)] pub commands: Vec, + /// Human-readable description from the plugin manifest, if any. Older + /// registry entries (installed before this field existed) default to + /// empty; use [`resolve_plugin_description`] to get a display-ready + /// value that falls back to the first command's description. + #[serde(default)] + pub description: String, } fn registry_path() -> Result { @@ -327,12 +332,55 @@ pub fn install_plugin( plugin_version: plugin_version.to_string(), installed_at: Some(now), commands, + description: description.to_string(), }); reg.plugins.sort_by(|a, b| a.name.cmp(&b.name)); save_registry(®)?; Ok(()) } +/// Resolve a display-ready description for a plugin: prefers the explicit +/// registry-recorded description, and falls back to the first registered +/// command's description when that's empty (e.g. for plugins installed +/// before `description` was tracked). +pub fn resolve_plugin_description(plugin: &InstalledPlugin) -> String { + if !plugin.description.is_empty() { + return plugin.description.clone(); + } + plugin + .commands + .first() + .map(|cmd| cmd.description.clone()) + .unwrap_or_default() +} + +/// A plugin entry with its description pre-resolved, for listing UIs. +#[derive(Debug, Clone)] +pub struct PluginListEntry { + pub name: String, + pub plugin_version: String, + pub trust: TrustLevel, + pub source: String, + pub description: String, + pub commands: Vec, +} + +/// Build display-ready entries for every installed plugin, with descriptions +/// resolved via [`resolve_plugin_description`]. +pub fn plugin_list_entries(reg: &PluginRegistry) -> Vec { + reg.plugins + .iter() + .map(|p| PluginListEntry { + name: p.name.clone(), + plugin_version: p.plugin_version.clone(), + trust: p.trust.clone(), + source: p.source.clone(), + description: resolve_plugin_description(p), + commands: p.commands.clone(), + }) + .collect() +} + /// Return all commands registered across all installed plugins (read from registry, no .so load). pub fn load_all_registered_commands() -> Vec { load_registry() diff --git a/src/utils/ai.rs b/src/utils/ai.rs index 99094e39..d49bfc6b 100644 --- a/src/utils/ai.rs +++ b/src/utils/ai.rs @@ -436,8 +436,11 @@ impl AIService for OllamaAdapter { } } +/// Registered AI service backends, keyed by provider. +type ProviderMap = RwLock>>>>; + pub struct AIServiceManager { - providers: RwLock>>>>, + providers: ProviderMap, circuit_breakers: RwLock>>>, fallback_order: Vec, provider_models: HashMap, @@ -740,7 +743,7 @@ mod tests { #[test] fn test_circuit_breaker_starts_closed() { - let cb = CircuitBreaker::new(3, 60); + let mut cb = CircuitBreaker::new(3, 60); assert!(cb.is_available()); } diff --git a/src/utils/ai_accessibility.rs b/src/utils/ai_accessibility.rs index 1704449f..1e07f156 100644 --- a/src/utils/ai_accessibility.rs +++ b/src/utils/ai_accessibility.rs @@ -451,10 +451,7 @@ pub fn screen_reader_format(text: &str, cfg: &AccessibilityConfig) -> String { let mut output = String::new(); if cfg.verbose_descriptions { - output.push_str(&format!( - "Document with {} lines. ", - lines.len() - )); + output.push_str(&format!("Document with {} lines. ", lines.len())); } for (i, line) in lines.iter().enumerate() { @@ -464,9 +461,17 @@ pub fn screen_reader_format(text: &str, cfg: &AccessibilityConfig) -> String { } if trimmed.starts_with("✓") || trimmed.starts_with("Success") { - output.push_str(&format!("Success notification, line {}: {}. ", i + 1, trimmed)); + output.push_str(&format!( + "Success notification, line {}: {}. ", + i + 1, + trimmed + )); } else if trimmed.starts_with("✗") || trimmed.starts_with("Error") { - output.push_str(&format!("Error notification, line {}: {}. ", i + 1, trimmed)); + output.push_str(&format!( + "Error notification, line {}: {}. ", + i + 1, + trimmed + )); } else if trimmed.starts_with("⚠") || trimmed.starts_with("Warning") { output.push_str(&format!("Warning, line {}: {}. ", i + 1, trimmed)); } else if trimmed.starts_with("→") { @@ -523,7 +528,11 @@ pub fn simplify_text_local(text: &str) -> String { let mut result = text.to_string(); for (from, to) in replacements { result = result.replace(from, to); - let capitalized = format!("{}{}", from.chars().next().unwrap().to_uppercase(), &from[1..]); + let capitalized = format!( + "{}{}", + from.chars().next().unwrap().to_uppercase(), + &from[1..] + ); let to_cap = format!("{}{}", to.chars().next().unwrap().to_uppercase(), &to[1..]); result = result.replace(&capitalized, &to_cap); } @@ -709,9 +718,7 @@ pub fn format_output(text: &str, cfg: &AccessibilityConfig) -> String { pub fn voice_commands_by_category() -> HashMap> { let mut map: HashMap> = HashMap::new(); for cmd in voice_commands() { - map.entry(cmd.category.clone()) - .or_default() - .push(cmd); + map.entry(cmd.category.clone()).or_default().push(cmd); } map } diff --git a/src/utils/ai_cache.rs b/src/utils/ai_cache.rs index 34a31b75..9517db28 100644 --- a/src/utils/ai_cache.rs +++ b/src/utils/ai_cache.rs @@ -12,14 +12,12 @@ //! - Support for cache prewarming //! - Manual cache invalidation commands -use crate::utils::database::{db_path, Database}; -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; -use rusqlite::{params, Connection, OptionalExtension}; +use crate::utils::database::Database; +use anyhow::Result; +use rusqlite::{params, OptionalExtension}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use std::path::PathBuf; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::{SystemTime, UNIX_EPOCH}; /// Default TTL for cached AI responses (7 days) pub const DEFAULT_CACHE_TTL_SECONDS: u64 = 7 * 24 * 60 * 60; diff --git a/src/utils/ai_context.rs b/src/utils/ai_context.rs index 8c4047bb..d2594d1f 100644 --- a/src/utils/ai_context.rs +++ b/src/utils/ai_context.rs @@ -2,7 +2,6 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; -use std::sync::Arc; use tokio::sync::RwLock; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -186,7 +185,7 @@ impl AIContextManager { items.extend(edits); } - items.sort_by(|a, b| b.priority.cmp(&a.priority)); + items.sort_by_key(|a| std::cmp::Reverse(a.priority)); Ok(items) } @@ -249,7 +248,7 @@ impl AIContextManager { if path.is_dir() && path .file_name() - .map_or(false, |n| n == "contracts" || n == "src") + .is_some_and(|n| n == "contracts" || n == "src") { if let Ok(contract_items) = collect_rust_files_sync(&path, &self.config) { items.extend(contract_items); @@ -391,7 +390,7 @@ fn collect_rust_files_sync( let entry = entry?; let path = entry.path(); - if path.is_file() && path.extension().map_or(false, |e| e == "rs") { + if path.is_file() && path.extension().is_some_and(|e| e == "rs") { if let Ok(metadata) = std::fs::metadata(&path) { if metadata.len() <= config.max_file_size_bytes { if let Ok(content) = std::fs::read_to_string(&path) { diff --git a/src/utils/ai_conversation.rs b/src/utils/ai_conversation.rs index c3a07f81..257c1d93 100644 --- a/src/utils/ai_conversation.rs +++ b/src/utils/ai_conversation.rs @@ -207,15 +207,9 @@ impl ConversationManager { // Trim context if too large if context.messages.len() > self.max_context_messages { - let remove_count = context.messages.len() - self.max_context_messages; + let _remove_count = context.messages.len() - self.max_context_messages; // Keep system messages, remove oldest user/assistant messages - context.messages.retain(|m| { - if m.role == MessageRole::System { - true - } else { - false - } - }); + context.messages.retain(|m| m.role == MessageRole::System); // Add back recent messages up to limit let recent_messages: Vec<_> = context @@ -371,20 +365,17 @@ impl ConversationManager { // Workflow-based suggestions if let Some(workflow) = &context.workflow_state { - match workflow.workflow_type { - WorkflowType::ContractDeployment => { - if !workflow.completed_steps.contains(&"compile".to_string()) { - suggestions.push(Suggestion { - title: "Compile contract".to_string(), - description: "Build the WASM file".to_string(), - action_type: SuggestionAction::Command( - "cargo build --target wasm32-unknown-unknown --release".to_string(), - ), - confidence: 0.95, - }); - } - } - _ => {} + if workflow.workflow_type == WorkflowType::ContractDeployment + && !workflow.completed_steps.contains(&"compile".to_string()) + { + suggestions.push(Suggestion { + title: "Compile contract".to_string(), + description: "Build the WASM file".to_string(), + action_type: SuggestionAction::Command( + "cargo build --target wasm32-unknown-unknown --release".to_string(), + ), + confidence: 0.95, + }); } } diff --git a/src/utils/ai_deployment_planner.rs b/src/utils/ai_deployment_planner.rs index a222f7b9..b3ef6726 100644 --- a/src/utils/ai_deployment_planner.rs +++ b/src/utils/ai_deployment_planner.rs @@ -206,6 +206,10 @@ pub enum PlanStatus { // ─── Planner Implementation ────────────────────────────────────────────────── +// `target_network`/`max_gas_price` are not currently read from any code path +// in this crate. Kept rather than removed since deleting them is a product +// decision, not a lint-scoping one. +#[allow(dead_code)] pub struct AiDeploymentPlanner { contract_path: PathBuf, target_network: String, @@ -311,7 +315,7 @@ Contract code: num_ctx: Some(8192), }; - let response = ollama::generate(&self.model, &prompt, Some(opts)) + let _response = ollama::generate(&self.model, &prompt, Some(opts)) .await .context("AI contract analysis failed")?; @@ -411,7 +415,7 @@ Contract code: upgrade_patterns, security_findings, optimization_suggestions, - readiness_score: score.max(0).min(100) as u8, + readiness_score: score.clamp(0, 100) as u8, }) } @@ -524,7 +528,7 @@ Contract code: // Suggest next weekday at 8 AM UTC let mut start_time = now; let days_to_add = 1; - start_time = start_time + chrono::Duration::days(days_to_add); + start_time += chrono::Duration::days(days_to_add); start_time = start_time .with_hour(8) .unwrap() @@ -536,9 +540,9 @@ Contract code: // If weekend, move to Monday let weekday = start_time.weekday(); if weekday == chrono::Weekday::Sat { - start_time = start_time + chrono::Duration::days(2); + start_time += chrono::Duration::days(2); } else if weekday == chrono::Weekday::Sun { - start_time = start_time + chrono::Duration::days(1); + start_time += chrono::Duration::days(1); } let end_time = start_time + chrono::Duration::hours(4); @@ -659,7 +663,7 @@ Contract code: Ok(RiskAssessment { overall, - score: score.max(0).min(100) as u8, + score: score.clamp(0, 100) as u8, categories, mitigations, }) @@ -668,7 +672,7 @@ Contract code: async fn create_rollback_plan( &self, analysis: &ContractAnalysis, - network: &NetworkRecommendation, + _network: &NetworkRecommendation, ) -> Result { let mut steps = Vec::new(); diff --git a/src/utils/ai_doc_qa.rs b/src/utils/ai_doc_qa.rs index 442758f0..0ac39a78 100644 --- a/src/utils/ai_doc_qa.rs +++ b/src/utils/ai_doc_qa.rs @@ -427,35 +427,34 @@ pub fn analyze_question(question: &str) -> QuestionAnalysis { let lower = question.to_lowercase(); let tokens = tokenize(question); - let intent = if lower.starts_with("how") - || lower.starts_with("what do i") - || lower.contains("steps to") - { - QuestionIntent::HowTo - } else if lower.starts_with("what is") - || lower.starts_with("what are") - || lower.starts_with("what's") - { - QuestionIntent::WhatIs - } else if lower.contains("error") - || lower.contains("fail") - || lower.contains("not work") - || lower.contains("fix") - || lower.contains("problem") - || lower.contains("issue") - { - QuestionIntent::Troubleshooting - } else if lower.starts_with("why") || lower.contains("reason") { - QuestionIntent::Why - } else if lower.contains(" vs ") - || lower.contains("difference") - || lower.contains("compare") - || lower.contains("better") - { - QuestionIntent::Comparison - } else { - QuestionIntent::General - }; + let intent = + if lower.starts_with("how") || lower.starts_with("what do i") || lower.contains("steps to") + { + QuestionIntent::HowTo + } else if lower.starts_with("what is") + || lower.starts_with("what are") + || lower.starts_with("what's") + { + QuestionIntent::WhatIs + } else if lower.contains("error") + || lower.contains("fail") + || lower.contains("not work") + || lower.contains("fix") + || lower.contains("problem") + || lower.contains("issue") + { + QuestionIntent::Troubleshooting + } else if lower.starts_with("why") || lower.contains("reason") { + QuestionIntent::Why + } else if lower.contains(" vs ") + || lower.contains("difference") + || lower.contains("compare") + || lower.contains("better") + { + QuestionIntent::Comparison + } else { + QuestionIntent::General + }; let mut topics = Vec::new(); for (domain, keywords) in TOPIC_INDEX { @@ -650,7 +649,7 @@ fn chunk_text(content: &str, chunk_size: usize, overlap: usize) -> Vec { if content.is_empty() { return vec![]; } - let step = chunk_size.saturating_sub(overlap).max(1); + let _step = chunk_size.saturating_sub(overlap).max(1); let mut chunks = Vec::new(); let mut start = 0usize; while start < content.len() { @@ -1111,7 +1110,7 @@ impl DocQaEngine { } let analysis = analyze_question(question); - let answer_language = language.unwrap_or_else(|| analysis.language); + let answer_language = language.unwrap_or(analysis.language); let tokens = analysis.tokens.clone(); let mut hits = self.index.retrieve(&tokens, 6, 1.0); @@ -1179,7 +1178,7 @@ impl DocQaEngine { language: answer_language, confidence: estimate_confidence(&hits, &analysis), mode: AnswerMode::Generated, - follow_up_suggestions: follow_up_suggestions(&question, &analysis), + follow_up_suggestions: follow_up_suggestions(question, &analysis), latency_ms: started.elapsed().as_millis(), }, Err(_) => extractive_answer(question, &hits, answer_language, started), diff --git a/src/utils/ai_docs.rs b/src/utils/ai_docs.rs index a1202962..f1c7a450 100644 --- a/src/utils/ai_docs.rs +++ b/src/utils/ai_docs.rs @@ -277,6 +277,10 @@ fn build_function_docs(extracted: &ExtractedDocs, languages: &[DocLanguage]) -> .collect() } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn build_sections( extracted: &ExtractedDocs, source_text: &str, diff --git a/src/utils/ai_error_handler.rs b/src/utils/ai_error_handler.rs index ed8f52de..7fcf4776 100644 --- a/src/utils/ai_error_handler.rs +++ b/src/utils/ai_error_handler.rs @@ -3,7 +3,7 @@ //! Provides robust error handling for AI operations with automatic recovery, //! fallback mechanisms, and user-friendly error messages. -use anyhow::{Context, Result}; +use anyhow::Result; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -160,7 +160,7 @@ impl ProviderConfig { } /// Error analytics tracker -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ErrorAnalytics { pub total_errors: u64, pub errors_by_category: HashMap, @@ -169,18 +169,6 @@ pub struct ErrorAnalytics { pub failed_recoveries: u64, } -impl Default for ErrorAnalytics { - fn default() -> Self { - ErrorAnalytics { - total_errors: 0, - errors_by_category: HashMap::new(), - errors_by_provider: HashMap::new(), - successful_recoveries: 0, - failed_recoveries: 0, - } - } -} - impl ErrorAnalytics { pub fn record_error(&mut self, error: &AiError) { self.total_errors += 1; diff --git a/src/utils/ai_feedback.rs b/src/utils/ai_feedback.rs index 35008925..fb0131b9 100644 --- a/src/utils/ai_feedback.rs +++ b/src/utils/ai_feedback.rs @@ -12,7 +12,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::utils::config; @@ -249,7 +249,7 @@ pub fn learn_preferences(store: &mut FeedbackStore) { CorrectionCategory::Performance => PreferenceType::PerformancePriority, _ => continue, }; - let map = pref_counts.entry(pref_type).or_insert_with(HashMap::new); + let map = pref_counts.entry(pref_type).or_default(); *map.entry(correction.corrected_output.clone()).or_insert(0) += 1; } } @@ -406,7 +406,7 @@ pub fn get_feature_stats(feature: &str) -> Result { let mut top_corrections: Vec<(CorrectionCategory, usize)> = correction_counts.into_iter().collect(); - top_corrections.sort_by(|a, b| b.1.cmp(&a.1)); + top_corrections.sort_by_key(|a| std::cmp::Reverse(a.1)); top_corrections.truncate(5); let metrics = calculate_quality_metrics(feature)?; diff --git a/src/utils/ai_gas_estimation.rs b/src/utils/ai_gas_estimation.rs index bcd24d3c..9d0324ec 100644 --- a/src/utils/ai_gas_estimation.rs +++ b/src/utils/ai_gas_estimation.rs @@ -32,6 +32,9 @@ pub struct AiGasHistoryEntry { } pub struct AiGasEstimator { + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] model_version: String, } diff --git a/src/utils/ai_model_router.rs b/src/utils/ai_model_router.rs index 9774e563..d0c8d639 100644 --- a/src/utils/ai_model_router.rs +++ b/src/utils/ai_model_router.rs @@ -292,7 +292,10 @@ pub fn classify_task(prompt: &str, category_hint: Option) -> TaskC || lower.contains("compare") || lower.contains("trade-off") || lower.contains("risk") - || matches!(category, TaskCategory::Planning | TaskCategory::SecurityAudit); + || matches!( + category, + TaskCategory::Planning | TaskCategory::SecurityAudit + ); if requires_reasoning { signals.push("reasoning_keywords".into()); @@ -301,19 +304,19 @@ pub fn classify_task(prompt: &str, category_hint: Option) -> TaskC signals.push("contains_code".into()); } - let complexity = if word_count > 800 || line_count > 60 || requires_reasoning && word_count > 300 - { - signals.push("high_token_count".into()); - TaskComplexity::Expert - } else if word_count > 300 || line_count > 25 || requires_reasoning { - TaskComplexity::Complex - } else if word_count > 80 || requires_code { - TaskComplexity::Moderate - } else { - TaskComplexity::Simple - }; + let complexity = + if word_count > 800 || line_count > 60 || requires_reasoning && word_count > 300 { + signals.push("high_token_count".into()); + TaskComplexity::Expert + } else if word_count > 300 || line_count > 25 || requires_reasoning { + TaskComplexity::Complex + } else if word_count > 80 || requires_code { + TaskComplexity::Moderate + } else { + TaskComplexity::Simple + }; - let estimated_tokens = (word_count as u32 * 2).max(256).min(8192); + let estimated_tokens = (word_count as u32 * 2).clamp(256, 8192); let confidence = if category_hint.is_some() { 0.95 } else if signals.len() >= 2 { @@ -362,8 +365,7 @@ fn infer_category(lower: &str, has_code: bool, signals: &mut Vec) -> Tas TaskCategory::Optimization } else if lower.contains("document") || lower.contains("readme") || lower.contains("explain") { TaskCategory::Documentation - } else if lower.contains("generate") || lower.contains("implement") || lower.contains("write") - { + } else if lower.contains("generate") || lower.contains("implement") || lower.contains("write") { if has_code { signals.push("code_generation".into()); TaskCategory::CodeGeneration @@ -438,7 +440,11 @@ pub async fn route_task( if prefs.prefer_local && ollama_available { if let Some(local) = candidates.iter().find(|m| m.is_local) { - return Ok(build_decision(local, &classification, "Local Ollama preferred by user")); + return Ok(build_decision( + local, + &classification, + "Local Ollama preferred by user", + )); } } @@ -458,12 +464,12 @@ pub async fn route_task( score_b.cmp(&score_a) }); - let best = candidates.first().context("No suitable model found for task")?; + let best = candidates + .first() + .context("No suitable model found for task")?; let reason = match (classification.complexity, classification.category) { - (TaskComplexity::Simple, _) if prefs.cost_sensitive => { - "Simple task — optimizing for cost" - } + (TaskComplexity::Simple, _) if prefs.cost_sensitive => "Simple task — optimizing for cost", (_, TaskCategory::CodeGeneration) => "Code generation — code-specialized model", (_, TaskCategory::SecurityAudit) => "Security audit — high-capability model", (TaskComplexity::Expert, _) => "Expert complexity — capable model selected", @@ -524,7 +530,7 @@ fn build_decision( None } else { ai_telemetry::estimate_cost( - &provider_name(&model.provider), + provider_name(&model.provider), &model.model, classification.estimated_tokens as u64, (classification.estimated_tokens / 2) as u64, @@ -573,12 +579,7 @@ pub fn config_from_decision(decision: &RoutingDecision) -> AIServiceConfig { providers.insert(decision.provider.clone(), provider_config); let fallback_order = std::iter::once(decision.provider.clone()) - .chain( - decision - .fallback_chain - .iter() - .map(|(p, _)| p.clone()), - ) + .chain(decision.fallback_chain.iter().map(|(p, _)| p.clone())) .collect(); AIServiceConfig { @@ -590,10 +591,15 @@ pub fn config_from_decision(decision: &RoutingDecision) -> AIServiceConfig { } } +/// (provider, model, feature) +type ModelKey = (String, String, String); +/// (call_count, success_count, total_latency_ms, total_tokens) +type ModelTotals = (u64, u64, u64, u64); + /// Aggregate model performance from local AI telemetry records. pub fn model_performance_stats(days: Option) -> Result> { let records = ai_telemetry::load_records(days)?; - let mut by_model: HashMap<(String, String, String), (u64, u64, u64, u64)> = HashMap::new(); + let mut by_model: HashMap = HashMap::new(); for r in &records { let key = (r.provider.clone(), r.model.clone(), r.feature.clone()); @@ -609,24 +615,26 @@ pub fn model_performance_stats(days: Option) -> Result = by_model .into_iter() - .map(|((provider, model, feature), (total, success, latency, tokens))| { - ModelPerformanceRecord { - provider, - model, - feature, - success_rate: if total > 0 { - success as f64 / total as f64 - } else { - 0.0 - }, - avg_latency_ms: if total > 0 { latency / total } else { 0 }, - avg_tokens: if total > 0 { tokens / total } else { 0 }, - total_calls: total, - } - }) + .map( + |((provider, model, feature), (total, success, latency, tokens))| { + ModelPerformanceRecord { + provider, + model, + feature, + success_rate: if total > 0 { + success as f64 / total as f64 + } else { + 0.0 + }, + avg_latency_ms: latency.checked_div(total).unwrap_or(0), + avg_tokens: tokens.checked_div(total).unwrap_or(0), + total_calls: total, + } + }, + ) .collect(); - stats.sort_by(|a, b| b.total_calls.cmp(&a.total_calls)); + stats.sort_by_key(|a| std::cmp::Reverse(a.total_calls)); Ok(stats) } diff --git a/src/utils/ai_navigation.rs b/src/utils/ai_navigation.rs index 66f1e03d..3ee163ed 100644 --- a/src/utils/ai_navigation.rs +++ b/src/utils/ai_navigation.rs @@ -488,10 +488,9 @@ fn parse_dependencies(root: &Path, file: &Path, source: &str) -> Vec let trimmed = line.trim(); let (kind, rest) = if let Some(rest) = trimmed.strip_prefix("use ") { ("use", rest) - } else if let Some(rest) = trimmed.strip_prefix("mod ") { - ("module", rest) } else { - return None; + let rest = trimmed.strip_prefix("mod ")?; + ("module", rest) }; let target = rest .trim_end_matches(';') diff --git a/src/utils/ai_project_planner.rs b/src/utils/ai_project_planner.rs index 12e7cdd3..972800e0 100644 --- a/src/utils/ai_project_planner.rs +++ b/src/utils/ai_project_planner.rs @@ -308,7 +308,8 @@ pub fn suggest_architectures(description: &str) -> Vec { { architectures.push(ArchitectureSuggestion { name: "Modular Multi-Contract".into(), - description: "Separate contracts for distinct domains with cross-contract calls.".into(), + description: "Separate contracts for distinct domains with cross-contract calls." + .into(), contract_modules: vec![ ContractModule { name: "core".into(), @@ -359,7 +360,10 @@ pub fn breakdown_tasks(description: &str, phases: &[DevelopmentPhase]) -> Vec Vec Vec Vec Vec Tim let milestones = phases .iter() .scan(start, |cursor, phase| { - *cursor = *cursor + Duration::days(phase.estimated_days as i64); + *cursor += Duration::days(phase.estimated_days as i64); Some(Milestone { name: phase.name.clone(), date: *cursor, @@ -607,7 +623,8 @@ pub fn identify_risks(description: &str) -> Vec { category: RiskCategory::Technical, severity: RiskSeverity::High, likelihood: RiskLikelihood::Possible, - mitigation: "Profile with starforge ai profile; optimize storage access patterns".into(), + mitigation: "Profile with starforge ai profile; optimize storage access patterns" + .into(), contingency: "Refactor hot paths and redeploy".into(), }, ProjectRisk { @@ -691,7 +708,8 @@ pub fn default_deployment_plan() -> DeploymentPlan { "Initialize contract state".into(), "Verify on-chain deployment".into(), ], - rollback_procedure: "Keep previous contract ID; redirect clients; migrate state if upgradeable".into(), + rollback_procedure: + "Keep previous contract ID; redirect clients; migrate state if upgradeable".into(), monitoring_setup: vec![ "Contract event monitoring".into(), "Gas usage alerts".into(), diff --git a/src/utils/ai_property_testing.rs b/src/utils/ai_property_testing.rs index 63de29ba..f77f65fa 100644 --- a/src/utils/ai_property_testing.rs +++ b/src/utils/ai_property_testing.rs @@ -9,8 +9,6 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::Path; use crate::utils::ai_test_assistant as ata; @@ -150,7 +148,7 @@ pub fn discover_properties(source_code: &str) -> Result> ), property_type: PropertyType::Postcondition, target_function: Some(func.name.clone()), - invariants: vec![format!("result is not panic")], + invariants: vec!["result is not panic".to_string()], confidence: 0.9, }); } @@ -385,7 +383,7 @@ fn generate_test_code_for_property( include_shrink: bool, ) -> String { let shrink_section = if include_shrink { - format!("\n // Shrink strategy: minimize counterexample to smallest failing input") + "\n // Shrink strategy: minimize counterexample to smallest failing input".to_string() } else { String::new() }; @@ -395,7 +393,7 @@ fn generate_test_code_for_property( .filter(|inv| { prop.target_function .as_ref() - .map_or(false, |f| inv.functions_affected.contains(f)) + .is_some_and(|f| inv.functions_affected.contains(f)) }) .map(|inv| format!(" // Invariant: {} — {}", inv.name, inv.expression)) .collect(); diff --git a/src/utils/ai_rate_limiter.rs b/src/utils/ai_rate_limiter.rs index 2f73955c..e947400c 100644 --- a/src/utils/ai_rate_limiter.rs +++ b/src/utils/ai_rate_limiter.rs @@ -262,7 +262,7 @@ impl AIRateLimiter { } queue.push(request); - queue.sort_by(|a, b| b.priority.cmp(&a.priority)); + queue.sort_by_key(|a| std::cmp::Reverse(a.priority)); metrics.queued_requests += 1; drop(queue); diff --git a/src/utils/ai_recommendations.rs b/src/utils/ai_recommendations.rs index 7aff6787..7a372bda 100644 --- a/src/utils/ai_recommendations.rs +++ b/src/utils/ai_recommendations.rs @@ -6,11 +6,9 @@ //! - Priority-ranked recommendations //! - Implementation guidance for each recommendation -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::fs; -use std::path::Path; use crate::utils::ai_test_assistant as ata; diff --git a/src/utils/ai_refactor.rs b/src/utils/ai_refactor.rs index fcb9fbee..9e3e3563 100644 --- a/src/utils/ai_refactor.rs +++ b/src/utils/ai_refactor.rs @@ -353,7 +353,7 @@ pub async fn handle(cmd: RefactorCommands) -> Result<()> { handle_refactor( &file, &model, - &name.as_deref().unwrap_or("extracted"), + name.as_deref().unwrap_or("extracted"), TaskType::ExtractFunction, output, ) @@ -491,10 +491,7 @@ async fn handle_refactor( let session_id = format!( "refactor-{}-{}", Utc::now().format("%Y%m%d-%H%M%S"), - sha256::hash(&refactored) - .chars() - .take(8) - .collect::() + sha256::hash(refactored).chars().take(8).collect::() ); // Save session for tracking/rollback diff --git a/src/utils/ai_search.rs b/src/utils/ai_search.rs index b14dd8b1..30615065 100644 --- a/src/utils/ai_search.rs +++ b/src/utils/ai_search.rs @@ -6,14 +6,11 @@ //! - Pattern discovery and similar code finding //! - Usage example generation and recommendation -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -use crate::utils::pattern_library::{self, PatternCategory}; - // ── Types ──────────────────────────────────────────────────────────────────── /// A code search result with relevance scoring. @@ -129,6 +126,9 @@ struct IndexEntry { content: String, tokens: Vec, is_test: bool, + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] is_contract: bool, } @@ -185,7 +185,7 @@ fn find_rust_files(dir: &Path) -> Result> { { files.extend(find_rust_files(&path)?); } - } else if path.extension().map_or(false, |e| e == "rs") { + } else if path.extension().is_some_and(|e| e == "rs") { files.push(path); } } diff --git a/src/utils/ai_telemetry.rs b/src/utils/ai_telemetry.rs index 287fd97e..7eacb308 100644 --- a/src/utils/ai_telemetry.rs +++ b/src/utils/ai_telemetry.rs @@ -104,12 +104,7 @@ fn price_per_1k_tokens(provider: &str, model: &str) -> Option<(f64, f64)> { } /// Estimate USD cost for a call given provider, model, and token counts. -pub fn estimate_cost( - provider: &str, - model: &str, - tokens_in: u64, - tokens_out: u64, -) -> Option { +pub fn estimate_cost(provider: &str, model: &str, tokens_in: u64, tokens_out: u64) -> Option { estimate_cost_usd(provider, model, Some(tokens_in), Some(tokens_out)) } diff --git a/src/utils/ai_test_analytics.rs b/src/utils/ai_test_analytics.rs index 158a5177..123a39f9 100644 --- a/src/utils/ai_test_analytics.rs +++ b/src/utils/ai_test_analytics.rs @@ -66,6 +66,12 @@ pub struct TestAnalyticsService { analytics: Arc>, } +impl Default for TestAnalyticsService { + fn default() -> Self { + Self::new() + } +} + impl TestAnalyticsService { pub fn new() -> Self { TestAnalyticsService { diff --git a/src/utils/ai_test_assistant.rs b/src/utils/ai_test_assistant.rs index bfa37d6b..a8704ce9 100644 --- a/src/utils/ai_test_assistant.rs +++ b/src/utils/ai_test_assistant.rs @@ -1,6 +1,5 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -356,11 +355,10 @@ pub struct ParamInfo { fn extract_functions_with_signatures(source: &str) -> Vec { let mut functions = Vec::new(); - let mut current_line = 1u32; let mut in_function = false; let mut brace_depth = 0u32; - for line in source.lines() { + for (current_line, line) in (1u32..).zip(source.lines()) { let trimmed = line.trim(); if !in_function { @@ -379,7 +377,6 @@ fn extract_functions_with_signatures(source: &str) -> Vec { in_function = false; } } - current_line += 1; } functions } @@ -448,8 +445,8 @@ fn parse_function_line(line: &str, line_num: u32) -> Option { Some(FunctionInfo { name, signature: line.trim().to_string(), - is_public: is_public, - is_entry_point: is_entry_point, + is_public, + is_entry_point, is_mutating, params, return_type, @@ -519,9 +516,7 @@ pub fn generate_test_priorities(analysis: &ContractAnalysis) -> Vec 3 { - TestPriority::High - } else if func.complexity_score > 5 { + } else if func.complexity_score > 5 || (func.is_mutating && func.complexity_score > 3) { TestPriority::High } else if func.is_mutating { TestPriority::Medium @@ -555,6 +550,74 @@ pub fn generate_test_priorities(analysis: &ContractAnalysis) -> Vec Vec { + let mut cases = Vec::new(); + for param in &func.params { + match param.param_type.as_str() { + t if t.contains("Address") => { + cases.push(format!("Zero address for {}", param.name)); + cases.push(format!("Self-referencing address for {}", param.name)); + cases.push(format!("Contract address for {}", param.name)); + } + t if t.contains("u64") || t.contains("i64") => { + cases.push(format!("Zero value for {}", param.name)); + cases.push(format!("Maximum value for {}", param.name)); + cases.push(format!("Minimum positive value for {}", param.name)); + } + t if t.contains("String") => { + cases.push(format!("Empty string for {}", param.name)); + cases.push(format!("Maximum length string for {}", param.name)); + cases.push(format!("Special characters for {}", param.name)); + } + _ => { + cases.push(format!("Default value for {}", param.name)); + } + } + } + if func.is_mutating { + cases.push("Unauthorized caller".to_string()); + cases.push("Double spend / replay".to_string()); + } + cases +} + +pub fn generate_security_checks(func: &FunctionInfo) -> Vec { + let mut checks = Vec::new(); + if func.is_mutating { + checks.push("Authorization required for state changes".to_string()); + checks.push("Failed auth must not mutate state".to_string()); + checks.push("Replay protection verified".to_string()); + } + if func + .params + .iter() + .any(|p| p.param_type.contains("i64") || p.param_type.contains("u64")) + { + checks.push("Overflow/underflow protection".to_string()); + checks.push("Negative amount handling".to_string()); + } + checks.push("Input validation".to_string()); + checks +} + +pub fn generate_warnings(analysis: &ContractAnalysis) -> Vec { + let mut warnings = Vec::new(); + if analysis.complex_functions > 3 { + warnings.push(format!( + "Contract has {} complex functions that may need additional test cases", + analysis.complex_functions + )); + } + if analysis.storage_accesses.len() > 5 { + warnings + .push("Contract has many storage accesses - ensure storage mock coverage".to_string()); + } + if !analysis.external_calls.is_empty() { + warnings.push("Contract makes external calls - consider integration tests".to_string()); + } + warnings +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestPrioritySuggestion { pub function_name: String, @@ -1095,7 +1158,7 @@ pub fn find_test_files(project_path: &Path) -> Vec { if let Ok(entries) = fs::read_dir(&tests_dir) { for entry in entries.flatten() { let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "rs") { + if path.extension().is_some_and(|ext| ext == "rs") { test_files.push(path); } } @@ -1108,7 +1171,7 @@ pub fn find_test_files(project_path: &Path) -> Vec { if let Ok(entries) = fs::read_dir(&src_dir) { for entry in entries.flatten() { let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "rs") { + if path.extension().is_some_and(|ext| ext == "rs") { if let Ok(content) = fs::read_to_string(&path) { if content.contains("#[cfg(test)]") { test_files.push(path); diff --git a/src/utils/ai_test_generator.rs b/src/utils/ai_test_generator.rs index 9ee7f4f3..59185351 100644 --- a/src/utils/ai_test_generator.rs +++ b/src/utils/ai_test_generator.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::RwLock; @@ -239,11 +239,7 @@ impl AiTestGenerator { } /// Generate comprehensive test suite - pub async fn generate_test_suite( - &self, - target_file: &PathBuf, - code: &str, - ) -> Result { + pub async fn generate_test_suite(&self, target_file: &Path, code: &str) -> Result { let start_time = std::time::Instant::now(); let analysis = self.analyze_code(code)?; @@ -302,7 +298,7 @@ impl AiTestGenerator { "{}_tests", target_file.file_stem().unwrap().to_string_lossy() ), - target_file: target_file.clone(), + target_file: target_file.to_path_buf(), tests, coverage_estimate, generated_at: Utc::now(), @@ -646,7 +642,7 @@ fn test_{}_regression() {{ "// Estimated coverage: {:.1}%\n", suite.coverage_estimate * 100.0 )); - output.push_str("\n"); + output.push('\n'); for test in &suite.tests { output.push_str(&format!("// {}\n", test.description)); @@ -655,7 +651,7 @@ fn test_{}_regression() {{ test.test_type, test.category )); output.push_str(&test.code); - output.push_str("\n"); + output.push('\n'); } std::fs::write(output_path, output).context("Failed to write test suite file")?; diff --git a/src/utils/ai_tutorial.rs b/src/utils/ai_tutorial.rs index 7dfa2f45..4709eb00 100644 --- a/src/utils/ai_tutorial.rs +++ b/src/utils/ai_tutorial.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -use uuid::Uuid; /// User skill level assessment #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -339,10 +338,11 @@ impl TutorialManager { for topic in learning_path { for tutorial in tutorials.values() { - if tutorial.topic == topic && !progress.completed_tutorials.contains(&tutorial.id) { - if tutorial.difficulty.clone() as i32 <= skill_level.clone() as i32 + 1 { - recommended.push(tutorial.clone()); - } + if tutorial.topic == topic + && !progress.completed_tutorials.contains(&tutorial.id) + && tutorial.difficulty.clone() as i32 <= skill_level.clone() as i32 + 1 + { + recommended.push(tutorial.clone()); } } } @@ -496,7 +496,7 @@ impl TutorialManager { answer .parse::() .ok() - .map_or(false, |idx| idx < options.len()) + .is_some_and(|idx| idx < options.len()) } } ExerciseType::CommandExecution => { diff --git a/src/utils/ai_validation.rs b/src/utils/ai_validation.rs index 55b6f347..0967b27c 100644 --- a/src/utils/ai_validation.rs +++ b/src/utils/ai_validation.rs @@ -1,6 +1,5 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValidationResult { diff --git a/src/utils/approval_engine.rs b/src/utils/approval_engine.rs index 92848cd0..e29129c2 100644 --- a/src/utils/approval_engine.rs +++ b/src/utils/approval_engine.rs @@ -223,6 +223,10 @@ pub fn deactivate_workflow(id: &str) -> Result<()> { } } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub fn create_request( workflow_id: &str, contract_id: &str, diff --git a/src/utils/audit.rs b/src/utils/audit.rs index 6acf0ed2..a2d6cc94 100644 --- a/src/utils/audit.rs +++ b/src/utils/audit.rs @@ -133,12 +133,12 @@ pub fn get_audit_report(start_time: Option<&str>, end_time: Option<&str>) -> Res .iter() .filter(|e| { if let Some(start) = start_time { - if e.timestamp < start.to_string() { + if e.timestamp.as_str() < start { return false; } } if let Some(end) = end_time { - if e.timestamp > end.to_string() { + if e.timestamp.as_str() > end { return false; } } @@ -189,6 +189,10 @@ pub fn export_audit_log_csv(entries: &[AuditEntry]) -> String { csv } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub fn log_approval_action( action: &str, actor: &str, diff --git a/src/utils/bindings.rs b/src/utils/bindings.rs index c90a0b1f..f2f148a8 100644 --- a/src/utils/bindings.rs +++ b/src/utils/bindings.rs @@ -3,7 +3,7 @@ use std::io::Cursor; use std::path::Path; use stellar_xdr::curr::{ Limited, Limits, ReadXdr, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScSpecUdtEnumV0, - ScSpecUdtStructV0, ScSpecUdtUnionV0, + ScSpecUdtStructV0, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -83,22 +83,6 @@ pub fn generate_bindings(wasm_path: &Path, language: BindingLanguage) -> Result< } } -#[cfg(test)] -pub fn read_spec_entries(wasm: &[u8]) -> Result> { - let spec = contract_spec_section(wasm)?; - let cursor = Cursor::new(spec); - let entries = ScSpecEntry::read_xdr_iter(&mut Limited::new( - cursor, - Limits { - depth: 500, - len: 0x1000000, - }, - )) - .collect::, _>>() - .context("Failed to decode contractspecv0 XDR metadata")?; - Ok(entries) -} - fn read_spec_entries(wasm: &[u8]) -> Result> { let spec = contract_spec_section(wasm)?; let cursor = Cursor::new(spec); @@ -337,7 +321,13 @@ fn generate_rust(metadata: &ContractMetadata) -> String { let params = function .inputs .iter() - .map(|input| format!("{}: {}", sanitize_ident(&input.name), rust_type(&input.type_name))) + .map(|input| { + format!( + "{}: {}", + sanitize_ident(&input.name), + rust_type(&input.type_name) + ) + }) .collect::>() .join(", "); let return_type = function @@ -346,7 +336,7 @@ fn generate_rust(metadata: &ContractMetadata) -> String { .map(rust_type) .unwrap_or_else(|| "()".to_string()); let comma = if params.is_empty() { "" } else { ", " }; - + out.push_str(&format!( "\tpub fn {rust_name}(&self{comma}{params}) -> Result<{return_type}> {{\n\ \t\tlet mut cmd = Command::new(\"starforge\");\n\ @@ -385,7 +375,7 @@ fn generate_rust(metadata: &ContractMetadata) -> String { \t{\n\ \t\tresult.parse().context(\"Failed to parse result\")\n\ \t}\n\n\ - }\n\n" + }\n\n", ); for struct_def in &metadata.structs { @@ -741,9 +731,12 @@ fn rust_type(type_name: &str) -> String { "I256" => "String".to_string(), _ => { // Handle complex types like Option, Result, Vec, etc. - if type_name.starts_with("Option<") || type_name.starts_with("Result<") || - type_name.starts_with("Vec<") || type_name.starts_with("Map<") || - type_name.starts_with("BytesN<") { + if type_name.starts_with("Option<") + || type_name.starts_with("Result<") + || type_name.starts_with("Vec<") + || type_name.starts_with("Map<") + || type_name.starts_with("BytesN<") + { type_name.to_string() } else { // Assume it's a custom type @@ -766,12 +759,12 @@ fn ts_type(type_name: &str) -> String { _ => { // Handle complex types if type_name.starts_with("Option<") { - let inner = &type_name[7..type_name.len()-1]; // Remove "Option<>" + let inner = &type_name[7..type_name.len() - 1]; // Remove "Option<>" format!("{} | null", ts_type(inner)) } else if type_name.starts_with("Result<") { "any".to_string() } else if type_name.starts_with("Vec<") { - let inner = &type_name[4..type_name.len()-1]; // Remove "Vec<>" + let inner = &type_name[4..type_name.len() - 1]; // Remove "Vec<>" format!("Array<{}>", ts_type(inner)) } else if type_name.starts_with("Map<") { "Record".to_string() @@ -801,12 +794,12 @@ fn python_type(type_name: &str) -> String { _ => { // Handle complex types if type_name.starts_with("Option<") { - let inner = &type_name[7..type_name.len()-1]; // Remove "Option<>" + let inner = &type_name[7..type_name.len() - 1]; // Remove "Option<>" format!("Optional[{}]", python_type(inner)) } else if type_name.starts_with("Result<") { "Any".to_string() } else if type_name.starts_with("Vec<") { - let inner = &type_name[4..type_name.len()-1]; // Remove "Vec<>" + let inner = &type_name[4..type_name.len() - 1]; // Remove "Vec<>" format!("List[{}]", python_type(inner)) } else if type_name.starts_with("Map<") { "Dict[str, Any]".to_string() @@ -842,12 +835,12 @@ fn go_type(type_name: &str) -> String { // Handle complex types if type_name.starts_with("Option<") { // In Go, we can use pointer types for optional - let inner = &type_name[7..type_name.len()-1]; // Remove "Option<>" + let inner = &type_name[7..type_name.len() - 1]; // Remove "Option<>" format!("*{}", go_type(inner)) } else if type_name.starts_with("Result<") { "interface{}".to_string() } else if type_name.starts_with("Vec<") { - let inner = &type_name[4..type_name.len()-1]; // Remove "Vec<>" + let inner = &type_name[4..type_name.len() - 1]; // Remove "Vec<>" format!("[]{}", go_type(inner)) } else if type_name.starts_with("Map<") { "map[string]interface{}".to_string() diff --git a/src/utils/bridge/providers.rs b/src/utils/bridge/providers.rs index 5a35a857..71b09496 100644 --- a/src/utils/bridge/providers.rs +++ b/src/utils/bridge/providers.rs @@ -81,7 +81,7 @@ pub fn default_providers() -> Vec { /// Initiate a cross-chain transfer through the configured provider. pub fn initiate_transfer( - provider: &BridgeProvider, + _provider: &BridgeProvider, request: &BridgeTransferRequest, ) -> anyhow::Result { let transfer_id = uuid::Uuid::new_v4().to_string(); diff --git a/src/utils/compliance.rs b/src/utils/compliance.rs index a9c92326..3cb26ab2 100644 --- a/src/utils/compliance.rs +++ b/src/utils/compliance.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; // Severity / Status helpers // ──────────────────────────────────────────────── -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ComplianceSeverity { Info, Warning, @@ -401,7 +401,7 @@ pub fn run_compliance_checks( policy_name: policy.name.clone(), passed: regulatory_checks.iter().all(|r| r.passed), severity: policy.severity.clone(), - message: format!("Regulatory compliance check complete"), + message: "Regulatory compliance check complete".to_string(), } } PolicyType::SecurityCompliance => { @@ -852,7 +852,7 @@ fn check_gdpr_compliance(network: &str, _contract_id: &str) -> Vec Vec { +fn check_soc2_compliance(_network: &str, _contract_id: &str) -> Vec { vec![ RegulatoryCheck { framework: RegulatoryFramework::Soc2, @@ -1477,7 +1477,7 @@ pub fn export_report_csv(report: &ComplianceReport) -> String { csv_escape("policy"), csv_escape(&check.policy_id), csv_escape(&check.policy_name), - csv_escape(&check.passed), + csv_escape(check.passed), csv_escape(&check.severity), csv_escape(&check.message), )); @@ -1488,7 +1488,7 @@ pub fn export_report_csv(report: &ComplianceReport) -> String { csv_escape("regulatory"), csv_escape(""), csv_escape(&check.requirement), - csv_escape(&check.passed), + csv_escape(check.passed), csv_escape(&check.severity), csv_escape(&check.message), csv_escape(&check.framework), @@ -1500,7 +1500,7 @@ pub fn export_report_csv(report: &ComplianceReport) -> String { csv_escape("best_practice"), csv_escape(""), csv_escape(&practice.check), - csv_escape(&practice.passed), + csv_escape(practice.passed), csv_escape(&practice.severity), csv_escape(&practice.recommendation), csv_escape(&practice.category), @@ -1517,6 +1517,9 @@ pub fn export_report_json(report: &ComplianceReport) -> Result { // Default policy initialization // ──────────────────────────────────────────────── +// Eight multi-line `create_policy(...)?` calls read far more clearly as +// sequential pushes than as one giant `vec![]` literal. +#[allow(clippy::vec_init_then_push)] pub fn build_default_policies() -> Result> { let existing = load_policies_raw()?; if !existing.is_empty() { diff --git a/src/utils/context_help.rs b/src/utils/context_help.rs index e5817f36..a66739e8 100644 --- a/src/utils/context_help.rs +++ b/src/utils/context_help.rs @@ -29,9 +29,10 @@ use crate::utils::history::HistoryEntry; // ── Public types ────────────────────────────────────────────────────────────── /// A coarse expertise tier used to tune tip verbosity. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Expertise { /// Few or no prior commands in this area — keep tips short and concrete. + #[default] Beginner, /// Some prior commands — surface intermediate tips and best practices. Intermediate, @@ -39,12 +40,6 @@ pub enum Expertise { Advanced, } -impl Default for Expertise { - fn default() -> Self { - Expertise::Beginner - } -} - impl Expertise { /// Lower-case label for printing in headers. pub fn label(self) -> &'static str { @@ -78,13 +73,13 @@ pub struct HelpContext<'a> { impl<'a> HelpContext<'a> { /// True when category `cat` should be considered enabled. pub fn category_enabled(&self, cat: &str) -> bool { - if self.disabled_categories.iter().any(|c| *c == cat) { + if self.disabled_categories.contains(&cat) { return false; } if self.enabled_categories.is_empty() { true } else { - self.enabled_categories.iter().any(|c| *c == cat) + self.enabled_categories.contains(&cat) } } } @@ -393,7 +388,7 @@ pub const PROACTIVE_BLOCKLIST: &[&str] = &["help", "info", "completions", "versi /// worth saying OR if the command is on the [[PROACTIVE_BLOCKLIST]]. pub fn proactive_tip(command: &str, history: &[HistoryEntry]) -> Option { let cmd = command.trim().to_lowercase(); - if PROACTIVE_BLOCKLIST.iter().any(|c| *c == cmd.as_str()) { + if PROACTIVE_BLOCKLIST.contains(&cmd.as_str()) { return None; } diff --git a/src/utils/contract_assertions.rs b/src/utils/contract_assertions.rs index dfb5488d..e73df54d 100644 --- a/src/utils/contract_assertions.rs +++ b/src/utils/contract_assertions.rs @@ -197,7 +197,7 @@ pub fn assert_storage_numeric( let actual = match actual_val .as_i64() .map(i128::from) - .or_else(|| actual_val.as_u64().map(|u| i128::from(u))) + .or_else(|| actual_val.as_u64().map(i128::from)) { Some(n) => n, None => { diff --git a/src/utils/contract_deps.rs b/src/utils/contract_deps.rs index 9d50cf8f..d6b634f7 100644 --- a/src/utils/contract_deps.rs +++ b/src/utils/contract_deps.rs @@ -3,7 +3,7 @@ use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ContractDependencies { diff --git a/src/utils/contract_health_monitor.rs b/src/utils/contract_health_monitor.rs index 16a1da1f..663b1fda 100644 --- a/src/utils/contract_health_monitor.rs +++ b/src/utils/contract_health_monitor.rs @@ -134,8 +134,7 @@ fn run_health_probes(contract_id: &str, network: &str) -> Vec { let last_deploy = deploy_history::load_history() .unwrap_or_default() .into_iter() - .filter(|r| r.contract_id.as_deref() == Some(contract_id) && r.network == network) - .last(); + .rfind(|r| r.contract_id.as_deref() == Some(contract_id) && r.network == network); let (deploy_status, deploy_msg) = match &last_deploy { Some(r) if r.status == deploy_history::DeployStatus::Success => ( ContractHealthStatus::Healthy, diff --git a/src/utils/contract_test_framework.rs b/src/utils/contract_test_framework.rs index 2587fb66..655f5c4b 100644 --- a/src/utils/contract_test_framework.rs +++ b/src/utils/contract_test_framework.rs @@ -1,20 +1,16 @@ use crate::utils::{ contract_assertions::{ - assert_balance_eq, assert_error_contains, assert_event_emitted, assert_event_not_emitted, - assert_ok, assert_return_value, assert_storage_eq, AssertionResult, AssertionStatus, - AssertionSuite, ContractAssertions, + assert_error_contains, assert_return_value, AssertionSuite, ContractAssertions, }, - contract_fixtures::{ContractFixture, FixtureContext, FixtureRegistry}, + contract_fixtures::{ContractFixture, FixtureContext}, contract_mocks::{MockAddress, MockContractClient, MockEnvironment, StorageKey}, contract_test_runner::{ContractTestRunner, TestRunConfig, TestRunSummary}, testnet_integration::{ - run_connectivity_smoke_test, SorobanNetwork, TestnetConfig, TestnetSession, - TestnetTestReport, TestnetTestResult, + run_connectivity_smoke_test, TestnetConfig, TestnetSession, TestnetTestReport, }, }; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use std::time::Instant; @@ -220,10 +216,8 @@ impl FrameworkTestSuite { let suite_start = Instant::now(); // Setup fixture - let fixture_ctx: Option = self - .fixture - .as_mut() - .and_then(|f| f.setup().ok().map(|ctx| ctx.clone())); + let fixture_ctx: Option = + self.fixture.as_mut().and_then(|f| f.setup().ok().cloned()); let mut results = Vec::new(); for case in &self.cases { @@ -231,7 +225,7 @@ impl FrameworkTestSuite { // Seed environment from fixture context if let Some(ref ctx) = fixture_ctx { - for (key, seed) in &ctx.storage { + for seed in ctx.storage.values() { env.storage.set( StorageKey { scope: format!("{:?}", seed.durability).to_lowercase(), @@ -240,7 +234,7 @@ impl FrameworkTestSuite { seed.value.clone(), ); } - for (_, account) in &ctx.accounts { + for account in ctx.accounts.values() { env.auth .auto_approve(MockAddress::new(account.address.clone())); } @@ -613,7 +607,7 @@ fn render_junit_report(result: &FrameworkRunResult) -> String { /// Standard test cases for any counter-style contract. pub fn counter_test_suite() -> FrameworkTestSuite { use crate::utils::contract_fixtures::counter_fixture; - use crate::utils::contract_mocks::{counter_env, MockAddress}; + use crate::utils::contract_mocks::MockAddress; let mut suite = FrameworkTestSuite::new("counter").with_fixture(counter_fixture()); diff --git a/src/utils/contract_versioning.rs b/src/utils/contract_versioning.rs index 3f4aa475..32fbbe5d 100644 --- a/src/utils/contract_versioning.rs +++ b/src/utils/contract_versioning.rs @@ -388,8 +388,8 @@ fn comparator_interval(c: &Comparator) -> Interval { inclusive: true, }), upper: Some(Bound { - value: if c.minor.is_some() { - Version::new(c.major, c.minor.unwrap() + 1, 0) + value: if let Some(minor) = c.minor { + Version::new(c.major, minor + 1, 0) } else { Version::new(c.major + 1, 0, 0) }, diff --git a/src/utils/cost_management.rs b/src/utils/cost_management.rs index b4b1f313..ad4711ee 100644 --- a/src/utils/cost_management.rs +++ b/src/utils/cost_management.rs @@ -202,7 +202,7 @@ pub fn budget_status(network: Option<&str>) -> Result> { let history = ce::load_cost_history()?; Ok(budgets .iter() - .filter(|b| network.map_or(true, |n| b.network == n)) + .filter(|b| network.is_none_or(|n| b.network == n)) .map(|b| budget_status_for(b, &history)) .collect()) } @@ -412,7 +412,7 @@ pub fn compare_networks( adjusted_total_xlm: adjusted as f64 / 10_000_000.0, }); } - results.sort_by(|a, b| a.adjusted_total_stroops.cmp(&b.adjusted_total_stroops)); + results.sort_by_key(|a| a.adjusted_total_stroops); Ok(results) } @@ -459,7 +459,7 @@ pub fn generate_cost_report_from( ) -> CostReport { let filtered: Vec<&CostHistoryEntry> = history .iter() - .filter(|e| network.map_or(true, |n| e.estimate.network == n)) + .filter(|e| network.is_none_or(|n| e.estimate.network == n)) .collect(); if filtered.is_empty() { diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs index 1bb0901b..054411b4 100644 --- a/src/utils/crypto.rs +++ b/src/utils/crypto.rs @@ -298,7 +298,10 @@ fn argon2_from_params(params: &Params) -> Argon2<'_> { Argon2::from(params.clone()) } -fn parse_encrypted_bundle(bundle: &str) -> Result<(Vec, Vec, Vec, Option)> { +/// (salt, nonce, ciphertext, KDF params if the bundle encodes non-default ones) +type EncryptedBundle = (Vec, Vec, Vec, Option); + +fn parse_encrypted_bundle(bundle: &str) -> Result { let parts: Vec<&str> = bundle.split(':').collect(); match parts.len() { 3 => { diff --git a/src/utils/database.rs b/src/utils/database.rs index 07598df2..6739f49e 100644 --- a/src/utils/database.rs +++ b/src/utils/database.rs @@ -1,9 +1,8 @@ use anyhow::{Context, Result}; use rusqlite::{params, Connection, OptionalExtension}; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::sync::Arc; use sha2::{Digest, Sha256}; +use std::path::PathBuf; pub fn db_path() -> PathBuf { crate::utils::config::config_dir().join("starforge.db") @@ -16,13 +15,13 @@ pub const CURRENT_SCHEMA_VERSION: i64 = 1; pub trait Migration: Send + Sync { /// Version number for this migration (must be unique) fn version(&self) -> i64; - + /// Description of what this migration does fn description(&self) -> &str; - + /// Apply the migration (upgrade) fn up(&self, conn: &Connection) -> Result<()>; - + /// Rollback the migration (downgrade) fn down(&self, conn: &Connection) -> Result<()>; } @@ -45,33 +44,30 @@ pub struct MigrationResult { } /// Error types for migration operations -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum MigrationError { + #[error("Migration version {0} is already applied")] AlreadyApplied(i64), + + #[error("Migration version {0} not found")] NotFound(i64), + + #[error("Cannot rollback: no migrations applied")] NothingToRollback, + + #[error("Migration version {0} depends on unapplied version {1}")] MissingDependency(i64, i64), + + #[error("Invalid migration sequence: versions must be consecutive")] InvalidSequence, + + #[error("Database schema version {0} is not supported (minimum: {1}, maximum: {2})")] UnsupportedVersion(i64, i64, i64), - MigrationFailed(String), -} -impl std::fmt::Display for MigrationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::AlreadyApplied(v) => write!(f, "Migration version {} is already applied", v), - Self::NotFound(v) => write!(f, "Migration version {} not found", v), - Self::NothingToRollback => write!(f, "Cannot rollback: no migrations applied"), - Self::MissingDependency(v, dep) => write!(f, "Migration version {} depends on unapplied version {}", v, dep), - Self::InvalidSequence => write!(f, "Invalid migration sequence: versions must be consecutive"), - Self::UnsupportedVersion(v, min, max) => write!(f, "Database schema version {} is not supported (minimum: {}, maximum: {})", v, min, max), - Self::MigrationFailed(msg) => write!(f, "Migration failed: {}", msg), - } - } + #[error("Migration failed: {0}")] + MigrationFailed(String), } -impl std::error::Error for MigrationError {} - pub struct Database { pub(crate) conn: Connection, } @@ -118,16 +114,16 @@ impl Database { self.conn.execute_batch(SCHEMA)?; self.ensure_column("wallets", "secret_key", "TEXT")?; self.ensure_column("wallets", "rotation_history", "TEXT NOT NULL DEFAULT '[]'")?; - + // Run migrations if this is not a fresh database - if self.get_meta("schema_version").is_ok() { + if self.get_meta("schema_version")?.is_some() { self.run_migrations()?; } else { // Fresh database - set initial version self.set_meta("schema_version", &CURRENT_SCHEMA_VERSION.to_string())?; self.record_migration(CURRENT_SCHEMA_VERSION, "initial_schema")?; } - + // The feature-flags schema is shipped alongside the rest of the // schema for first-startup convenience; subsequent startups hit the // idempotent `CREATE TABLE IF NOT EXISTS` guards and no-op. @@ -150,7 +146,7 @@ impl Database { /// Get all applied migrations from the database pub fn get_applied_migrations(&self) -> Result> { let mut stmt = self.conn.prepare( - "SELECT version, name, applied_at, checksum FROM schema_migrations ORDER BY version" + "SELECT version, name, applied_at, checksum FROM schema_migrations ORDER BY version", )?; let rows = stmt.query_map([], |row| { Ok(AppliedMigration { @@ -175,6 +171,9 @@ impl Database { } /// Remove a migration record from the database + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] fn remove_migration(&self, version: i64) -> Result<()> { self.conn.execute( "DELETE FROM schema_migrations WHERE version = ?1", @@ -188,17 +187,22 @@ impl Database { let mut hasher = Sha256::new(); hasher.update(version.to_string().as_bytes()); hasher.update(name.as_bytes()); - Ok(hasher.finalize().iter().map(|b| format!("{:02x}", b)).collect()) + Ok(hasher + .finalize() + .iter() + .map(|b| format!("{:02x}", b)) + .collect()) } /// Run pending migrations to bring the database to the current schema version pub fn run_migrations(&self) -> Result { let current_version = self.get_current_schema_version()?; let applied = self.get_applied_migrations()?; - let applied_versions: std::collections::HashSet = applied.iter().map(|m| m.version).collect(); - + let applied_versions: std::collections::HashSet = + applied.iter().map(|m| m.version).collect(); + let mut migrations_applied = Vec::new(); - + // Check if we need to upgrade if current_version < CURRENT_SCHEMA_VERSION { // Apply migrations from current_version + 1 to CURRENT_SCHEMA_VERSION @@ -209,7 +213,7 @@ impl Database { } } } - + Ok(MigrationResult { current_version: CURRENT_SCHEMA_VERSION, migrations_applied, @@ -219,11 +223,12 @@ impl Database { /// Apply a single migration within a transaction fn apply_migration(&self, version: i64) -> Result<()> { - let migration = self.get_migration(version) + let migration = self + .get_migration(version) .ok_or_else(|| anyhow::anyhow!("Migration version {} not found", version))?; - + let tx = self.conn.unchecked_transaction()?; - + // Apply the migration match migration.up(&tx) { Ok(()) => { @@ -234,13 +239,13 @@ impl Database { "INSERT INTO schema_migrations (version, name, applied_at, checksum) VALUES (?1, ?2, ?3, ?4)", params![version, migration.description(), applied_at, checksum], )?; - + // Update schema version tx.execute( "UPDATE meta SET value = ?1 WHERE key = 'schema_version'", params![version.to_string()], )?; - + tx.commit()?; Ok(()) } @@ -254,29 +259,37 @@ impl Database { /// Rollback a single migration within a transaction pub fn rollback_migration(&self, version: i64) -> Result<()> { let applied = self.get_applied_migrations()?; - let current_version = self.get_current_schema_version()?; - + let _current_version = self.get_current_schema_version()?; + // Check if the migration is applied if !applied.iter().any(|m| m.version == version) { - return Err(anyhow::anyhow!("Migration version {} is not applied", version)); + return Err(anyhow::anyhow!( + "Migration version {} is not applied", + version + )); } - + // Check if we can rollback (must be the latest applied migration) - let max_applied = applied.iter().map(|m| m.version).max() + let max_applied = applied + .iter() + .map(|m| m.version) + .max() .ok_or_else(|| anyhow::anyhow!("No migrations applied"))?; - + if version != max_applied { return Err(anyhow::anyhow!( "Can only rollback the latest migration ({}), tried to rollback {}", - max_applied, version + max_applied, + version )); } - - let migration = self.get_migration(version) + + let migration = self + .get_migration(version) .ok_or_else(|| anyhow::anyhow!("Migration version {} not found", version))?; - + let tx = self.conn.unchecked_transaction()?; - + // Rollback the migration match migration.down(&tx) { Ok(()) => { @@ -285,20 +298,24 @@ impl Database { "DELETE FROM schema_migrations WHERE version = ?1", params![version], )?; - + // Update schema version to previous version let previous_version = if version > 1 { version - 1 } else { 0 }; tx.execute( "UPDATE meta SET value = ?1 WHERE key = 'schema_version'", params![previous_version.to_string()], )?; - + tx.commit()?; Ok(()) } Err(e) => { let _ = tx.rollback(); - Err(anyhow::anyhow!("Rollback of migration {} failed: {}", version, e)) + Err(anyhow::anyhow!( + "Rollback of migration {} failed: {}", + version, + e + )) } } } @@ -873,7 +890,7 @@ impl Database { } ExportFormat::Csv => { let mut wtr = csv::Writer::from_writer(writer); - wtr.write_record(&[ + wtr.write_record([ "id", "event_type", "contract_id", @@ -884,7 +901,7 @@ impl Database { "network", ])?; for event in events { - wtr.write_record(&[ + wtr.write_record([ &event.id, &event.event_type, &event.contract_id, @@ -1108,16 +1125,16 @@ impl Migration for MigrationV1 { fn version(&self) -> i64 { 1 } - + fn description(&self) -> &str { "initial_schema" } - - fn up(&self, conn: &Connection) -> Result<()> { + + fn up(&self, _conn: &Connection) -> Result<()> { // This is a no-op since the initial schema is already applied in SCHEMA Ok(()) } - + fn down(&self, conn: &Connection) -> Result<()> { // Rollback: drop all tables conn.execute_batch( @@ -1128,7 +1145,7 @@ impl Migration for MigrationV1 { DROP TABLE IF EXISTS networks; DROP TABLE IF EXISTS wallets; DROP TABLE IF EXISTS schema_migrations; - DROP TABLE IF EXISTS meta;" + DROP TABLE IF EXISTS meta;", )?; Ok(()) } @@ -1295,13 +1312,13 @@ mod tests { fn migration_rollback_latest_migration() { let db = in_memory_db(); let version_before = db.get_current_schema_version().unwrap(); - + // Rollback the latest migration db.rollback_migration(version_before).unwrap(); - + let version_after = db.get_current_schema_version().unwrap(); assert_eq!(version_after, version_before - 1); - + let applied = db.get_applied_migrations().unwrap(); assert!(!applied.iter().any(|m| m.version == version_before)); } @@ -1360,7 +1377,7 @@ mod tests { let db = in_memory_db(); let migration = MigrationV1 {}; let mut conn = db.conn; - + // Verify tables exist before rollback let table_count: i64 = conn .query_row( @@ -1370,10 +1387,10 @@ mod tests { ) .unwrap(); assert!(table_count > 0); - + // Rollback migration.down(&mut conn).unwrap(); - + // Verify tables are dropped let table_count_after: i64 = conn .query_row( @@ -1397,11 +1414,13 @@ mod tests { fn migration_transaction_rollback_on_failure() { let db = in_memory_db(); // Set schema version to 0 to simulate an old database - db.conn.execute( - "UPDATE meta SET value = '0' WHERE key = 'schema_version'", - [], - ).unwrap(); - + db.conn + .execute( + "UPDATE meta SET value = '0' WHERE key = 'schema_version'", + [], + ) + .unwrap(); + // This should apply migration 1 let result = db.run_migrations().unwrap(); assert_eq!(result.current_version, CURRENT_SCHEMA_VERSION); diff --git a/src/utils/debugger.rs b/src/utils/debugger.rs index a2727dce..72f4ff39 100644 --- a/src/utils/debugger.rs +++ b/src/utils/debugger.rs @@ -195,6 +195,9 @@ impl Debugger { None } + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] fn evaluate_condition(&self, _condition: &str) -> bool { true } diff --git a/src/utils/deployment_automation.rs b/src/utils/deployment_automation.rs index 298c4b13..174a542d 100644 --- a/src/utils/deployment_automation.rs +++ b/src/utils/deployment_automation.rs @@ -3,7 +3,7 @@ //! Provides AI-driven automation for deployment processes, including //! pre-deployment checks, automated testing, and deployment execution. -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; use sha2::Digest; use std::path::Path; @@ -416,7 +416,7 @@ pub struct AutomatedTestRunner; impl AutomatedTestRunner { /// Run automated tests on the contract. - pub fn run_tests(wasm_path: &str) -> Result { + pub fn run_tests(_wasm_path: &str) -> Result { // Simulated test results let test_results = vec![ TestResult { @@ -475,7 +475,7 @@ impl DeploymentExecutor { status: "success".to_string(), contract_id: Some(format!( "C{}", - &hex::encode(&sha2::Sha256::digest(&wasm_bytes))[..56] + &hex::encode(sha2::Sha256::digest(&wasm_bytes))[..56] )), transaction_hash: Some(format!("tx_{}", uuid::Uuid::new_v4())), gas_used, diff --git a/src/utils/deployment_monitor.rs b/src/utils/deployment_monitor.rs index 5c300383..76000675 100644 --- a/src/utils/deployment_monitor.rs +++ b/src/utils/deployment_monitor.rs @@ -54,7 +54,7 @@ pub fn analyze_deployments( .into_iter() .filter(|record| { record.network == network - && contract_id.map_or(true, |cid| record.contract_id.as_deref() == Some(cid)) + && contract_id.is_none_or(|cid| record.contract_id.as_deref() == Some(cid)) }) .collect(); @@ -137,9 +137,7 @@ pub fn analyze_deployments( predictions.push(DeploymentPrediction { title: "Rollback risk is increasing".to_string(), confidence: 74, - detail: format!( - "The recent failure pattern suggests a higher chance of another deployment failure in the next rollout.", - ), + detail: "The recent failure pattern suggests a higher chance of another deployment failure in the next rollout.".to_string(), recommended_action: "Prepare a rollback plan, verify the artifact hash, and keep the previous deployment ready for immediate recovery.".to_string(), }); } @@ -148,9 +146,7 @@ pub fn analyze_deployments( predictions.push(DeploymentPrediction { title: "Performance degradation is likely".to_string(), confidence: 68, - detail: format!( - "The observed deployment latency trend points to slower execution than the recent baseline.", - ), + detail: "The observed deployment latency trend points to slower execution than the recent baseline.".to_string(), recommended_action: "Trim the deployment payload, validate the wallet setup, and review network congestion before launching the next deployment.".to_string(), }); } diff --git a/src/utils/deployment_monitoring_service.rs b/src/utils/deployment_monitoring_service.rs index e2b62ae0..dbd82b5d 100644 --- a/src/utils/deployment_monitoring_service.rs +++ b/src/utils/deployment_monitoring_service.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use chrono::Utc; use colored::*; use serde::{Deserialize, Serialize}; diff --git a/src/utils/doc_generator.rs b/src/utils/doc_generator.rs index 2f6a879e..8a9f5875 100644 --- a/src/utils/doc_generator.rs +++ b/src/utils/doc_generator.rs @@ -694,21 +694,21 @@ impl HtmlDocGenerator { .functions .iter() .filter(|f| f.visibility == Visibility::Public) - .map(|f| render_function_card(f)) + .map(render_function_card) .collect::>() .join("\n"); let structs_html = docs .structs .iter() - .map(|s| render_struct_card(s)) + .map(render_struct_card) .collect::>() .join("\n"); let enums_html = docs .enums .iter() - .map(|e| render_enum_card(e)) + .map(render_enum_card) .collect::>() .join("\n"); @@ -1114,7 +1114,7 @@ fn render_function_card(f: &ExtractedFn) -> String { .doc_comment .lines() .filter(|l| !l.starts_with("```")) - .map(|l| escape_html(l)) + .map(escape_html) .collect::>() .join("
"); diff --git a/src/utils/doc_templates.rs b/src/utils/doc_templates.rs index 822781c3..dbcae3af 100644 --- a/src/utils/doc_templates.rs +++ b/src/utils/doc_templates.rs @@ -9,7 +9,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; // ────────────────────────────────────────────────────────────────────────────── // Template context diff --git a/src/utils/docs.rs b/src/utils/docs.rs index dee6944b..92599f18 100644 --- a/src/utils/docs.rs +++ b/src/utils/docs.rs @@ -1,6 +1,5 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -115,6 +114,10 @@ fn contract_doc_dir(contract_id: &str) -> Result { Ok(dir) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub fn generate_documentation( contract_id: &str, name: &str, diff --git a/src/utils/documentation.rs b/src/utils/documentation.rs index 6bfe0a58..2e1897fa 100644 --- a/src/utils/documentation.rs +++ b/src/utils/documentation.rs @@ -1,6 +1,5 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -161,7 +160,10 @@ impl DocumentationGenerator { Ok(documentation) } - fn extract_functions_from_wasm(&self, wasm_bytes: &[u8]) -> Result> { + // Multi-field struct literals read more clearly as sequential pushes + // than as one large `vec![]` literal. + #[allow(clippy::vec_init_then_push)] + fn extract_functions_from_wasm(&self, _wasm_bytes: &[u8]) -> Result> { // Simplified function extraction - in production would use proper WASM parsing let mut functions = Vec::new(); @@ -228,7 +230,7 @@ impl DocumentationGenerator { }; // Check if contract already exists in index - if let Some(existing) = index + if let Some(_existing) = index .contracts .iter() .find(|c| c.contract_id == documentation.contract_id) @@ -589,7 +591,7 @@ impl DocumentationVersionManager { let entry = entry?; let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "json") { + if path.extension().is_some_and(|ext| ext == "json") { let content = fs::read_to_string(&path)?; let version: DocumentationVersion = serde_json::from_str(&content)?; versions.push(version); diff --git a/src/utils/event_monitoring.rs b/src/utils/event_monitoring.rs index fe843cc5..a67f333d 100644 --- a/src/utils/event_monitoring.rs +++ b/src/utils/event_monitoring.rs @@ -480,7 +480,7 @@ fn write_counts(out: &mut String, title: &str, counts: &HashMap) } let mut items: Vec<_> = counts.iter().collect(); - items.sort_by(|(left, _), (right, _)| left.cmp(right)); + items.sort_by_key(|(left, _)| *left); for (key, count) in items { let _ = writeln!(out, " - {}: {}", key, count); } diff --git a/src/utils/feature_flags.rs b/src/utils/feature_flags.rs index 8e7dae17..da696f3f 100644 --- a/src/utils/feature_flags.rs +++ b/src/utils/feature_flags.rs @@ -25,7 +25,6 @@ use crate::utils::database::Database; use anyhow::{bail, Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::sync::{Mutex, OnceLock}; @@ -119,7 +118,7 @@ impl SegmentRule { bucket < (*percent as u32).min(100) } SegmentRule::HasAttribute { key, any_of } => match ctx.attributes.get(key) { - Some(v) if any_of.is_empty() => true, + Some(_v) if any_of.is_empty() => true, Some(v) => any_of.iter().any(|cand| cand == v), None => false, }, @@ -631,7 +630,7 @@ impl Database { "SELECT flag_name, version, enabled, rollout_percent, segments_json, variants_json, note, created_at \ FROM flag_states WHERE flag_name = ?1 ORDER BY version ASC", )?; - let rows = stmt.query_map(rusqlite::params![flag_name], |row| row_to_state(row))?; + let rows = stmt.query_map(rusqlite::params![flag_name], row_to_state)?; rows.map(|r| r.map_err(anyhow::Error::from)).collect() } @@ -658,7 +657,7 @@ impl Database { ) latest ON latest.flag_name = s.flag_name AND latest.v = s.version \ ORDER BY s.flag_name", )?; - let rows = stmt.query_map([], |row| row_to_state(row))?; + let rows = stmt.query_map([], row_to_state)?; rows.map(|r| r.map_err(anyhow::Error::from)).collect() } @@ -1393,6 +1392,7 @@ pub fn load_or_create_install_id(db: &Database) -> Result { #[cfg(test)] mod tests { use super::*; + use std::collections::BTreeMap; fn db() -> Database { Database::open_in_memory().unwrap() diff --git a/src/utils/gas_analyzer.rs b/src/utils/gas_analyzer.rs index 4bcefc05..eeca72e3 100644 --- a/src/utils/gas_analyzer.rs +++ b/src/utils/gas_analyzer.rs @@ -601,7 +601,7 @@ pub fn generate_findings(bytes: &[u8], profile: &WasmSectionProfile) -> Vec 0 { + let instr_per_byte = if !bytes.is_empty() { profile.estimated_instruction_count as f64 / bytes.len() as f64 } else { 0.0 diff --git a/src/utils/hardware_wallet.rs b/src/utils/hardware_wallet.rs index e0ae90e8..a86ba5e9 100644 --- a/src/utils/hardware_wallet.rs +++ b/src/utils/hardware_wallet.rs @@ -1,3 +1,11 @@ +// The Ledger APDU constants and codec helpers below (build_apdu, +// frame_apdu_for_hid, parse_hd_path, encode_hd_path, extract_*_bytes) are +// exercised by the `hardware-wallet` feature's transport implementation and +// by this module's own unit tests, but not by a default build with the +// feature off and no tests compiled — that's the one configuration where +// they're genuinely unused, not a sign anything here is actually dead. +#![cfg_attr(not(any(test, feature = "hardware-wallet")), allow(dead_code))] + use anyhow::{Context, Result}; use clap::ValueEnum; diff --git a/src/utils/migration_ai.rs b/src/utils/migration_ai.rs index e889a483..61281732 100644 --- a/src/utils/migration_ai.rs +++ b/src/utils/migration_ai.rs @@ -242,7 +242,7 @@ fn analyze_type_changes( old_specs: &[String], new_specs: &[String], breaking_changes: &mut Vec, - _suggestions: &mut Vec, + _suggestions: &mut [MigrationSuggestion], ) { let old_types = extract_types(old_specs); let new_types = extract_types(new_specs); @@ -281,7 +281,7 @@ fn analyze_storage_layout( new_specs: &[String], storage_changes: &mut Vec, breaking_changes: &mut Vec, - _suggestions: &mut Vec, + _suggestions: &mut [MigrationSuggestion], ) { let old_storage = extract_storage_keys(old_specs); let new_storage = extract_storage_keys(new_specs); @@ -403,12 +403,11 @@ fn analyze_sdk_upgrade( fn analyze_protocol_upgrade( config: &AnalysisConfig, breaking_changes: &mut Vec, - _suggestions: &mut Vec, + _suggestions: &mut [MigrationSuggestion], ) { match (config.old_protocol_version, config.new_protocol_version) { - (Some(old), Some(new)) if old != new => { - if new > old { - breaking_changes.push(BreakingChange { + (Some(old), Some(new)) if old != new && new > old => { + breaking_changes.push(BreakingChange { category: "protocol_upgrade".into(), severity: Severity::Major, title: format!("Soroban protocol upgrade: v{} → v{}", old, new), @@ -425,7 +424,6 @@ fn analyze_protocol_upgrade( ), affected_items: vec!["protocol".into(), "host_functions".into()], }); - } } _ => {} } @@ -446,7 +444,7 @@ fn determine_compatibility(changes: &[BreakingChange]) -> Compatibility { fn build_migration_steps( config: &AnalysisConfig, - breaking_changes: &[BreakingChange], + _breaking_changes: &[BreakingChange], storage_changes: &[StorageChange], old_wasm_hash: &str, new_wasm_hash: &str, @@ -456,7 +454,7 @@ fn build_migration_steps( let mut order = 1usize; steps.push(MigrationStep { - order: order, + order, action: "backup".into(), description: "Create a backup of the current contract state and WASM".into(), command: Some("starforge backup create --contract ".into()), @@ -467,7 +465,7 @@ fn build_migration_steps( if has_storage_migration { steps.push(MigrationStep { - order: order, + order, action: "export_storage".into(), description: "Export current contract storage to a snapshot".into(), command: Some( @@ -514,7 +512,7 @@ fn build_migration_steps( ); steps.push(MigrationStep { - order: order, + order, action: "create_rules".into(), description: "Create migration rules file for storage transformation".into(), command: Some("starforge migrate init --from-version --to-version ".into()), @@ -524,7 +522,7 @@ fn build_migration_steps( order += 1; steps.push(MigrationStep { - order: order, + order, action: "test_migration".into(), description: "Dry-run migration to verify rules produce expected output".into(), command: Some( @@ -536,7 +534,7 @@ fn build_migration_steps( order += 1; steps.push(MigrationStep { - order: order, + order, action: "apply_migration".into(), description: "Apply storage migration to produce transformed snapshot".into(), command: Some("starforge migrate run --contract-id --snapshot snapshot.json --rules rules.json --output migrated-snapshot.json".into()), @@ -547,7 +545,7 @@ fn build_migration_steps( } steps.push(MigrationStep { - order: order, + order, action: "generate_migration_code".into(), description: "Generate on-chain migration function in the new contract".into(), command: Some("starforge migrate-ai generate --old-wasm --new-wasm --output migration.rs".into()), @@ -563,7 +561,7 @@ fn build_migration_steps( } steps.push(MigrationStep { - order: order, + order, action: "compatibility_check".into(), description: "Run final compatibility check between old and new WASM".into(), command: Some("starforge upgrade-auto compat --old-wasm --new-wasm ".into()), @@ -573,7 +571,7 @@ fn build_migration_steps( order += 1; steps.push(MigrationStep { - order: order, + order, action: "upgrade_contract".into(), description: "Upgrade the contract to the new WASM version".into(), command: Some("starforge upgrade execute --contract-id --wasm --wallet ".into()), @@ -595,9 +593,7 @@ fn generate_migration_code_stub( snippet.push_str(&format!("// Migration function for {}\n", contract_name)); snippet.push_str("// Generated by starforge migrate-ai\n\n"); snippet.push_str("#[allow(unused)]\n"); - snippet.push_str(&format!( - "pub fn migrate(env: &soroban_sdk::Env, admin: soroban_sdk::Address) {{\n" - )); + snippet.push_str("pub fn migrate(env: &soroban_sdk::Env, admin: soroban_sdk::Address) {\n"); snippet.push_str(" admin.require_auth();\n\n"); for sc in storage_changes { @@ -763,9 +759,7 @@ pub fn extract_spec_entries(wasm_bytes: &[u8]) -> Result> { let content = String::from_utf8_lossy(meta_section); for line in content.lines() { let trimmed = line.trim(); - if trimmed.starts_with("SDK_VERSION:") { - specs.push(format!("meta:{}", trimmed)); - } else if trimmed.starts_with("PROTOCOL_VERSION:") { + if trimmed.starts_with("SDK_VERSION:") || trimmed.starts_with("PROTOCOL_VERSION:") { specs.push(format!("meta:{}", trimmed)); } } diff --git a/src/utils/network_simulator/deterministic.rs b/src/utils/network_simulator/deterministic.rs index 925166e6..368ea7d3 100644 --- a/src/utils/network_simulator/deterministic.rs +++ b/src/utils/network_simulator/deterministic.rs @@ -7,7 +7,6 @@ use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use std::fmt; use std::sync::Mutex; // ── Deterministic Configuration ─────────────────────────────────────────────── diff --git a/src/utils/network_simulator/failure.rs b/src/utils/network_simulator/failure.rs index 8d857189..6b483ab8 100644 --- a/src/utils/network_simulator/failure.rs +++ b/src/utils/network_simulator/failure.rs @@ -4,7 +4,6 @@ //! simulator to test how contracts and clients handle errors. use serde::{Deserialize, Serialize}; -use std::collections::HashMap; // ── Failure Modes ───────────────────────────────────────────────────────────── diff --git a/src/utils/network_simulator/scenarios.rs b/src/utils/network_simulator/scenarios.rs index e0b6b46b..2216857e 100644 --- a/src/utils/network_simulator/scenarios.rs +++ b/src/utils/network_simulator/scenarios.rs @@ -3,7 +3,7 @@ //! Pre-built, parameterizable test scenarios that set up the simulator //! with realistic contract + account states for reproducible testing. -use crate::utils::network_simulator::deterministic::{derive_contract_id, derive_public_key}; +use crate::utils::network_simulator::deterministic::derive_public_key; use crate::utils::network_simulator::simulator::{NetworkSimulator, SimulatorConfig}; use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -314,7 +314,7 @@ impl ScenarioRunner { } } - fn load_test(seed: u64) -> Scenario { + fn load_test(_seed: u64) -> Scenario { let mut accounts = Vec::new(); for i in 0..10 { accounts.push(ScenarioAccount { diff --git a/src/utils/network_simulator/simulator.rs b/src/utils/network_simulator/simulator.rs index e722a72b..469054cb 100644 --- a/src/utils/network_simulator/simulator.rs +++ b/src/utils/network_simulator/simulator.rs @@ -6,12 +6,9 @@ use crate::utils::network_simulator::deterministic::{ derive_contract_id, derive_public_key, derive_tx_hash, DeterministicConfig, SeededRng, }; -use crate::utils::network_simulator::failure::{ - failure_to_rpc_error, FailureInjector, FailureMode, -}; +use crate::utils::network_simulator::failure::{failure_to_rpc_error, FailureInjector}; use crate::utils::network_simulator::state::SnapshotManager; -use crate::utils::network_simulator::time::{LedgerTime, TimeController}; -use chrono::Utc; +use crate::utils::network_simulator::time::TimeController; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::HashMap; @@ -740,6 +737,7 @@ impl Default for NetworkSimulator { #[cfg(test)] mod tests { use super::*; + use crate::utils::network_simulator::failure::FailureMode; #[test] fn new_simulator_has_default_state() { diff --git a/src/utils/network_simulator/state.rs b/src/utils/network_simulator/state.rs index 0cab93f9..30a25b7e 100644 --- a/src/utils/network_simulator/state.rs +++ b/src/utils/network_simulator/state.rs @@ -67,6 +67,10 @@ impl SnapshotManager { } /// Take a snapshot of the current simulator state. + // Each parameter is an independent, named input (CLI flags / distinct config + // values); bundling them into a struct here would add indirection without + // reducing real complexity. + #[allow(clippy::too_many_arguments)] pub fn take_snapshot( &mut self, label: &str, @@ -127,7 +131,7 @@ impl SnapshotManager { if path.exists() { if let Ok(json) = fs::read_to_string(&path) { if let Ok(snapshot) = serde_json::from_str::(&json) { - let label = snapshot.label.clone(); + let _label = snapshot.label.clone(); self.snapshots.insert(id.to_string(), snapshot); return self.snapshots.get(id); } diff --git a/src/utils/network_simulator/time.rs b/src/utils/network_simulator/time.rs index cefc22ac..ec60af06 100644 --- a/src/utils/network_simulator/time.rs +++ b/src/utils/network_simulator/time.rs @@ -3,7 +3,7 @@ //! Provides ledger time manipulation for testing – advance, freeze, rewind, //! and jump to specific timestamps. -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// Represents the simulated ledger time. diff --git a/src/utils/notifications.rs b/src/utils/notifications.rs index a1507924..cb836afe 100644 --- a/src/utils/notifications.rs +++ b/src/utils/notifications.rs @@ -151,9 +151,9 @@ pub fn send_notification( Ok(()) } -fn send_email(destination: &str, _template: &str, data: &HashMap) -> Result<()> { +fn send_email(destination: &str, _template: &str, _data: &HashMap) -> Result<()> { info(&format!("Email notification queued to {}", destination)); - return Ok(()); + Ok(()) } fn send_slack(destination: &str, _template: &str, data: &HashMap) -> Result<()> { diff --git a/src/utils/orchestration.rs b/src/utils/orchestration.rs index 7e4d7115..83243342 100644 --- a/src/utils/orchestration.rs +++ b/src/utils/orchestration.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::thread; @@ -200,7 +200,7 @@ impl OrchestrationEngine { let mut temp_visited = HashSet::new(); for contract_id in &contract_ids { - self.topological_sort( + Self::topological_sort( contract_id, &plan.dependencies, &mut visited, @@ -213,7 +213,6 @@ impl OrchestrationEngine { } fn topological_sort( - &self, contract_id: &str, dependencies: &HashMap>, visited: &mut HashSet, @@ -232,7 +231,7 @@ impl OrchestrationEngine { if let Some(deps) = dependencies.get(contract_id) { for dep in deps { - self.topological_sort(dep, dependencies, visited, temp_visited, sorted)?; + Self::topological_sort(dep, dependencies, visited, temp_visited, sorted)?; } } @@ -478,7 +477,7 @@ impl OrchestrationEngine { let entry = entry?; let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "json") { + if path.extension().is_some_and(|ext| ext == "json") { let content = fs::read_to_string(&path)?; let plan: DeploymentPlan = serde_json::from_str(&content)?; plans.push(plan); @@ -502,7 +501,7 @@ impl OrchestrationEngine { Ok(plan) } - fn load_plan_by_execution(&self, execution_id: &str) -> Result { + fn load_plan_by_execution(&self, _execution_id: &str) -> Result { // In production, would store execution-to-plan mapping // For now, load the first plan let plans = self.list_plans()?; diff --git a/src/utils/pattern_library.rs b/src/utils/pattern_library.rs index 557ab806..764dcb1d 100644 --- a/src/utils/pattern_library.rs +++ b/src/utils/pattern_library.rs @@ -10,11 +10,10 @@ //! indicators before the LLM call, giving the model a structured head-start. use anyhow::{Context, Result}; -use chrono::Utc; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::utils::config; diff --git a/src/utils/performance.rs b/src/utils/performance.rs index 6daa305a..0f692850 100644 --- a/src/utils/performance.rs +++ b/src/utils/performance.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; -use std::time::{Duration, Instant}; +use std::path::PathBuf; +use std::time::Instant; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractMetrics { @@ -369,7 +369,7 @@ pub fn analyze_bottlenecks(contract_id: &str) -> Result { .or_insert(record.gas_used); } - let total_gas: u64 = gas_history.iter().map(|r| r.gas_used).sum(); + let _total_gas: u64 = gas_history.iter().map(|r| r.gas_used).sum(); let total_executions = gas_history.len() as f64; let bottleneck_operations: Vec = operation_frequencies diff --git a/src/utils/profiler.rs b/src/utils/profiler.rs index 0373f894..b7ef48fd 100644 --- a/src/utils/profiler.rs +++ b/src/utils/profiler.rs @@ -1,4 +1,3 @@ -use std::mem::size_of; use std::time::{Duration, Instant}; #[cfg(feature = "memory-profiling")] @@ -125,6 +124,9 @@ pub struct Profiler { #[cfg(feature = "memory-profiling")] #[derive(Debug)] struct MemoryTracker { + // Not currently called from any code path in this crate. Kept rather than + // removed since deleting it is a product decision, not a lint-scoping one. + #[allow(dead_code)] start: Instant, current_memory: usize, peak_memory: usize, @@ -143,10 +145,6 @@ impl MemoryTracker { } } -#[cfg(not(feature = "memory-profiling"))] -#[derive(Debug)] -struct MemoryTracker; - impl Profiler { pub fn start() -> Self { #[cfg(feature = "memory-profiling")] @@ -156,8 +154,6 @@ impl Profiler { peak_memory: 0, samples: Vec::new(), }); - #[cfg(not(feature = "memory-profiling"))] - let memory_tracker: Option = None; Self { start: Instant::now(), diff --git a/src/utils/prompt_manager.rs b/src/utils/prompt_manager.rs index 42acbdbc..6da6bf88 100644 --- a/src/utils/prompt_manager.rs +++ b/src/utils/prompt_manager.rs @@ -4,6 +4,9 @@ use rusqlite::{params, Connection}; use serde_json::Value; use std::path::PathBuf; +/// (prompt_name, version_tag, uses, successes, failures, avg_rating) +pub type PromptStats = (String, String, i64, i64, i64, f64); + pub struct PromptManager { conn: Connection, } @@ -257,7 +260,7 @@ impl PromptManager { Ok(prompts) } - pub fn get_stats(&self) -> Result> { + pub fn get_stats(&self) -> Result> { let mut stmt = self.conn.prepare( "SELECT p.name, v.version_tag, a.uses, a.successes, a.failures, CAST(a.rating_sum AS REAL) / NULLIF(a.rating_count, 0) diff --git a/src/utils/quality_analysis.rs b/src/utils/quality_analysis.rs index 6d0b466c..33ebf2de 100644 --- a/src/utils/quality_analysis.rs +++ b/src/utils/quality_analysis.rs @@ -445,14 +445,15 @@ fn score_best_practices(source: &str, metrics: &CodeMetrics) -> QualityCategory ); } - if source.contains("Map<") || source.contains("Vec<") { - if !source.contains("enum DataKey") && !source.contains("enum StorageKey") { - score -= 2; - findings.push( + if (source.contains("Map<") || source.contains("Vec<")) + && !source.contains("enum DataKey") + && !source.contains("enum StorageKey") + { + score -= 2; + findings.push( "No dedicated storage-key enum (DataKey/StorageKey) — consider one for type-safe storage access" .to_string(), ); - } } QualityCategory { diff --git a/src/utils/registry.rs b/src/utils/registry.rs index 79d2ab2e..28ce4e6c 100644 --- a/src/utils/registry.rs +++ b/src/utils/registry.rs @@ -2,7 +2,6 @@ use crate::utils::http_client; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs; -use std::path::PathBuf; /// Configuration for the remote template registry. #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/utils/repl.rs b/src/utils/repl.rs index cfb60a57..5ce56bdf 100644 --- a/src/utils/repl.rs +++ b/src/utils/repl.rs @@ -4,7 +4,6 @@ use rustyline::completion::{Completer, Pair}; use rustyline::error::ReadlineError; use rustyline::highlight::Highlighter; use rustyline::hint::Hinter; -use rustyline::history::History; use rustyline::validate::Validator; use rustyline::{Context, Editor, Helper}; use std::collections::HashSet; diff --git a/src/utils/scheduler.rs b/src/utils/scheduler.rs index 9d4893c7..f7a66875 100644 --- a/src/utils/scheduler.rs +++ b/src/utils/scheduler.rs @@ -79,6 +79,10 @@ pub fn parse_when(when: &str) -> Result> { ) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub fn create( contract_id: String, wasm: PathBuf, diff --git a/src/utils/security/ai_audit.rs b/src/utils/security/ai_audit.rs index 8fba84ba..3f503c2c 100644 --- a/src/utils/security/ai_audit.rs +++ b/src/utils/security/ai_audit.rs @@ -3,9 +3,7 @@ //! Combines static pattern analysis with Claude AI for comprehensive //! vulnerability detection with < 15% false positive rate. -use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; /// Security vulnerability with full context. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -158,8 +156,8 @@ impl SecurityPatterns { // A state write *after* the external call is the CEI violation. // A transfer that happens last is the safe ordering. let mut found_storage_after = false; - for j in (i + 1)..std::cmp::min(i + 10, lines.len()) { - let candidate = lines[j].trim(); + for candidate in &lines[(i + 1)..std::cmp::min(i + 10, lines.len())] { + let candidate = candidate.trim(); if candidate.contains("storage") && candidate.contains("set") { found_storage_after = true; break; @@ -199,10 +197,10 @@ impl SecurityPatterns { { // Look for require_auth in next 20 lines let mut has_auth = false; - for j in (i + 1)..std::cmp::min(i + 20, lines.len()) { + for line_j in &lines[(i + 1)..std::cmp::min(i + 20, lines.len())] { // Ignore comments: a line reading `// Missing // require_auth() check` must not satisfy the check. - let code_only = lines[j].split("//").next().unwrap_or(lines[j]); + let code_only = line_j.split("//").next().unwrap_or(line_j); if code_only.contains("require_auth") { has_auth = true; break; @@ -322,8 +320,10 @@ impl SecurityPatterns { if line.contains("persistent()") && line.contains("set") { // Look for extend_ttl in nearby lines let mut has_ttl = false; - for j in std::cmp::max(0, i.saturating_sub(5))..std::cmp::min(i + 5, lines.len()) { - if lines[j].contains("extend_ttl") { + for nearby in + &lines[std::cmp::max(0, i.saturating_sub(5))..std::cmp::min(i + 5, lines.len())] + { + if nearby.contains("extend_ttl") { has_ttl = true; break; } diff --git a/src/utils/security/ai_audit_service.rs b/src/utils/security/ai_audit_service.rs index 3d18f62c..d44c7c0c 100644 --- a/src/utils/security/ai_audit_service.rs +++ b/src/utils/security/ai_audit_service.rs @@ -2,12 +2,11 @@ use super::ai_audit::{ build_fallback_report, build_system_prompt, build_user_prompt, run_static_checks, - AiAuditResponse, AuditLevel, AuditRequest, SecurityAuditReport, + AiAuditResponse, AuditRequest, SecurityAuditReport, }; use anyhow::{anyhow, Result}; use chrono::Utc; use reqwest::Client; -use serde_json::json; /// Anthropic API message format. #[derive(serde::Serialize)] @@ -252,6 +251,7 @@ fn classify_claude_error(err: &anyhow::Error) -> &'static str { #[cfg(test)] mod tests { use super::*; + use super::super::ai_audit::AuditLevel; #[test] fn test_validate_empty_contract_code() { diff --git a/src/utils/security/audit.rs b/src/utils/security/audit.rs index 31e3c895..c5c61248 100644 --- a/src/utils/security/audit.rs +++ b/src/utils/security/audit.rs @@ -112,6 +112,10 @@ pub fn run_audit(path: &Path, config: &AuditConfig) -> Result { }) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] fn collect_external_tool( tool: &str, env_vars: &[&str], diff --git a/src/utils/security/compliance.rs b/src/utils/security/compliance.rs index dd407fb3..7501f7d8 100644 --- a/src/utils/security/compliance.rs +++ b/src/utils/security/compliance.rs @@ -85,6 +85,12 @@ pub struct ComplianceEngine { rules: Vec, } +impl Default for ComplianceEngine { + fn default() -> Self { + Self::new() + } +} + impl ComplianceEngine { pub fn new() -> Self { let rules = vec![ @@ -259,7 +265,7 @@ impl ComplianceEngine { .map(|r| r.remediation.clone()) .collect(); - let overall_risk = if critical_gaps.len() > 0 { + let overall_risk = if !critical_gaps.is_empty() { "critical" } else if failed_count > total / 2 { "high" diff --git a/src/utils/security/data_protection.rs b/src/utils/security/data_protection.rs index 9b45993b..9241b853 100644 --- a/src/utils/security/data_protection.rs +++ b/src/utils/security/data_protection.rs @@ -127,6 +127,10 @@ pub struct DataProtectionSummary { pub integrity_score: f64, } +// Fields not currently read from any code path in this crate. Kept rather +// than removed since deleting them is a product decision, not a +// lint-scoping one. +#[allow(dead_code)] pub struct DataProtectionEngine { encryption_policy: EncryptionPolicy, access_policy: AccessPolicy, @@ -134,6 +138,12 @@ pub struct DataProtectionEngine { classifications: HashMap, } +impl Default for DataProtectionEngine { + fn default() -> Self { + Self::new() + } +} + impl DataProtectionEngine { pub fn new() -> Self { let mut classifications = HashMap::new(); @@ -347,7 +357,7 @@ impl DataProtectionEngine { } fn check_key_management(&self, source: &str) -> DataProtectionCheck { - let has_key_ops = + let _has_key_ops = source.contains("key") || source.contains("secret") || source.contains("private"); let has_hardcoded = source.contains("\"sk1\"") || source.contains("\"secret_key\"") diff --git a/src/utils/security_scanner.rs b/src/utils/security_scanner.rs index cf7f8624..f21095b8 100644 --- a/src/utils/security_scanner.rs +++ b/src/utils/security_scanner.rs @@ -10,7 +10,7 @@ use crate::utils::security::audit::VulnerabilityFinding; use anyhow::{Context, Result}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::Command; // --------------------------------------------------------------------------- diff --git a/src/utils/simulation_resources.rs b/src/utils/simulation_resources.rs index 1db4fbcc..012f5c2b 100644 --- a/src/utils/simulation_resources.rs +++ b/src/utils/simulation_resources.rs @@ -595,7 +595,7 @@ fn format_thousands(value: u64) -> String { let digits = value.to_string(); let mut out = String::with_capacity(digits.len() + digits.len() / 3); for (i, ch) in digits.chars().enumerate() { - if i > 0 && (digits.len() - i) % 3 == 0 { + if i > 0 && (digits.len() - i).is_multiple_of(3) { out.push(','); } out.push(ch); diff --git a/src/utils/social.rs b/src/utils/social.rs index b61eb691..ea138a91 100644 --- a/src/utils/social.rs +++ b/src/utils/social.rs @@ -1,8 +1,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Team { diff --git a/src/utils/soroban.rs b/src/utils/soroban.rs index b1a03e59..8994ac78 100644 --- a/src/utils/soroban.rs +++ b/src/utils/soroban.rs @@ -499,7 +499,7 @@ fn build_and_sign_transaction( function: &str, args: &[String], wallet: &WalletEntry, - network: &str, + _network: &str, signing: Option<&SigningRequest>, ) -> Result { let tx_xdr = build_transaction_xdr(contract_id, function, args)?; diff --git a/src/utils/state_diff.rs b/src/utils/state_diff.rs index 6514b143..d28430d7 100644 --- a/src/utils/state_diff.rs +++ b/src/utils/state_diff.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; diff --git a/src/utils/stream.rs b/src/utils/stream.rs index 82486b2d..ba8683cc 100644 --- a/src/utils/stream.rs +++ b/src/utils/stream.rs @@ -18,11 +18,12 @@ pub struct EventStreamFilters { } /// Transport used for Soroban event streaming. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum EventStreamTransport { /// Prefer a persistent WebSocket JSON-RPC connection, then fall back to HTTP polling. Auto, /// Use JSON-RPC over HTTP polling. + #[default] Http, /// Use JSON-RPC over a persistent WebSocket connection. WebSocket, @@ -42,12 +43,6 @@ impl EventStreamTransport { } } -impl Default for EventStreamTransport { - fn default() -> Self { - Self::Http - } -} - pub struct SorobanEventStream { rpc_url: String, websocket_url: String, @@ -274,9 +269,7 @@ impl SorobanEventStream { .websocket .as_mut() .ok_or_else(|| anyhow::anyhow!("WebSocket connection is not available"))?; - websocket - .send(Message::Text(request.to_string().into())) - .await + websocket.send(Message::Text(request.to_string())).await }; if let Err(err) = send_result { diff --git a/src/utils/template.rs b/src/utils/template.rs index a9ccd939..3dcc8d9c 100644 --- a/src/utils/template.rs +++ b/src/utils/template.rs @@ -1,4 +1,4 @@ -use crate::utils::{print as p, registry, template_analytics, templates}; +use crate::utils::{print as p, template_analytics, templates}; use anyhow::Result; use clap::Subcommand; use std::path::PathBuf; @@ -277,6 +277,10 @@ pub async fn handle(cmd: TemplateCommands) -> Result<()> { } } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn import( path: PathBuf, name: Option, @@ -307,6 +311,10 @@ async fn import( Ok(()) } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] async fn publish( path: PathBuf, name: Option, diff --git a/src/utils/template_analytics.rs b/src/utils/template_analytics.rs index c8b3db58..e99bc492 100644 --- a/src/utils/template_analytics.rs +++ b/src/utils/template_analytics.rs @@ -601,7 +601,7 @@ fn build_issue_detection(entries: &[TemplateEntry], feedback: &[FeedbackEntry]) } if let Some(sr) = &e.security_review { if let Some(findings) = &sr.findings { - if findings.len() > 0 { + if !findings.is_empty() { reasons.push(format!("{} unresolved security finding(s)", findings)); } } @@ -925,7 +925,10 @@ mod tests { homepage: None, documentation: None, security_review: None, - changelog: vec![], + changelog: None, + repository_url: None, + categories: vec![], + featured: false, } } @@ -1256,7 +1259,7 @@ mod tests { status: "audited".to_string(), audited_at: Some("2026-01-01".to_string()), auditor: Some("Auditor".to_string()), - findings: Some(2), + findings: Some(2.to_string()), score: Some(80.0), }); e.documented = true; diff --git a/src/utils/template_customization_ai.rs b/src/utils/template_customization_ai.rs index 1965143e..9ec8e7e8 100644 --- a/src/utils/template_customization_ai.rs +++ b/src/utils/template_customization_ai.rs @@ -2,7 +2,7 @@ use crate::utils::{ollama, template_vcs}; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CustomizationHistory { @@ -161,8 +161,8 @@ fn apply_ai_modifications(template_path: &Path, ai_response: &str) -> Result Vec { let mut sources = Vec::new(); @@ -119,9 +119,9 @@ pub fn analyze_template_directory( + external_call_score as u32 + batch_operations_score as u32) / 5) as u8; - let estimated_gas_reduction_percent = (100 - overall_score).max(5).min(40) as u8; - let estimated_speedup_percent = ((100 - overall_score) / 2).max(3).min(25) as u8; - let estimated_memory_savings_percent = ((100 - overall_score) / 3).max(2).min(15) as u8; + let estimated_gas_reduction_percent = (100 - overall_score).clamp(5, 40); + let estimated_speedup_percent = ((100 - overall_score) / 2).clamp(3, 25); + let estimated_memory_savings_percent = ((100 - overall_score) / 3).clamp(2, 15); Ok(TemplatePerformanceAnalysis { template_name: name, diff --git a/src/utils/template_recommender.rs b/src/utils/template_recommender.rs index 551dd13e..74d5cc8a 100644 --- a/src/utils/template_recommender.rs +++ b/src/utils/template_recommender.rs @@ -34,8 +34,11 @@ pub enum SkillLevel { } impl SkillLevel { - /// Parse from a case-insensitive string. - pub fn from_str(s: &str) -> Option { + /// Parse from a case-insensitive string, accepting common shorthand aliases. + /// + /// Not `FromStr::from_str` — this returns `Option`, not `Result`, since + /// there's no meaningful error type for "unrecognized skill level". + pub fn parse_lenient(s: &str) -> Option { match s.to_lowercase().as_str() { "beginner" | "b" | "novice" => Some(Self::Beginner), "intermediate" | "i" | "mid" | "medium" => Some(Self::Intermediate), @@ -307,9 +310,11 @@ fn skill_fit(entry: &templates::TemplateEntry, skill_level: SkillLevel) -> (&'st SkillLevel::Advanced => { if has_advanced { ("Excellent for advanced use", 15.0) - } else if entry.security_review.as_ref().map_or(false, |sr| { - sr.status == "audited" && sr.score.unwrap_or(0.0) >= 90.0 - }) { + } else if entry + .security_review + .as_ref() + .is_some_and(|sr| sr.status == "audited" && sr.score.unwrap_or(0.0) >= 90.0) + { ("Production-grade quality", 10.0) } else { ("Suitable", 0.0) @@ -515,7 +520,10 @@ mod tests { homepage: None, documentation: None, security_review: None, - changelog: vec![], + changelog: None, + repository_url: None, + categories: vec![], + featured: false, } } @@ -656,12 +664,18 @@ mod tests { #[test] fn test_skill_level_from_str() { - assert_eq!(SkillLevel::from_str("beginner"), Some(SkillLevel::Beginner)); assert_eq!( - SkillLevel::from_str("INTERMEDIATE"), + SkillLevel::parse_lenient("beginner"), + Some(SkillLevel::Beginner) + ); + assert_eq!( + SkillLevel::parse_lenient("INTERMEDIATE"), Some(SkillLevel::Intermediate) ); - assert_eq!(SkillLevel::from_str("expert"), Some(SkillLevel::Advanced)); - assert_eq!(SkillLevel::from_str("unknown"), None); + assert_eq!( + SkillLevel::parse_lenient("expert"), + Some(SkillLevel::Advanced) + ); + assert_eq!(SkillLevel::parse_lenient("unknown"), None); } } diff --git a/src/utils/template_security_scanner.rs b/src/utils/template_security_scanner.rs index 5eb3d8c9..30dab9bd 100644 --- a/src/utils/template_security_scanner.rs +++ b/src/utils/template_security_scanner.rs @@ -120,8 +120,8 @@ impl KnownVulnerabilities { if line.contains("transfer") && !line.trim().starts_with("//") { // Check if state update happens after transfer let mut state_after = false; - for j in (i + 1)..std::cmp::min(i + 10, lines.len()) { - if lines[j].contains("storage") && lines[j].contains("set") { + for nearby in &lines[(i + 1)..std::cmp::min(i + 10, lines.len())] { + if nearby.contains("storage") && nearby.contains("set") { state_after = true; break; } @@ -150,12 +150,12 @@ impl KnownVulnerabilities { && (line.contains("&mut") || line.contains("env:")) { let mut has_auth = false; - for j in i..std::cmp::min(i + 20, lines.len()) { - if lines[j].contains("require_auth") { + for nearby in &lines[i..std::cmp::min(i + 20, lines.len())] { + if nearby.contains("require_auth") { has_auth = true; break; } - if lines[j].contains("pub fn ") { + if nearby.contains("pub fn ") { break; } } @@ -268,12 +268,12 @@ impl KnownVulnerabilities { for (i, line) in lines.iter().enumerate() { if line.contains("pub fn ") && (line.contains("admin") || line.contains("owner")) { let mut has_check = false; - for j in i..std::cmp::min(i + 20, lines.len()) { - if lines[j].contains("require_auth") || lines[j].contains("assert") { + for nearby in &lines[i..std::cmp::min(i + 20, lines.len())] { + if nearby.contains("require_auth") || nearby.contains("assert") { has_check = true; break; } - if lines[j].contains("pub fn ") { + if nearby.contains("pub fn ") { break; } } diff --git a/src/utils/template_version_ai.rs b/src/utils/template_version_ai.rs index f2352a32..8625e4a0 100644 --- a/src/utils/template_version_ai.rs +++ b/src/utils/template_version_ai.rs @@ -2,7 +2,6 @@ use crate::utils::ollama; use crate::utils::template_vcs::{get_version_history, TemplateChangelog, TemplateVersion}; use anyhow::{Context, Result}; use semver::Version; -use std::fs; use std::path::Path; use std::process::Command; @@ -334,9 +333,9 @@ fn extract_list(text: &str, section: &str) -> Vec { }; let slice = &text[start + section.len()..]; - let mut lines = slice.lines().skip_while(|l| l.trim().is_empty()); + let lines = slice.lines().skip_while(|l| l.trim().is_empty()); - while let Some(line) = lines.next() { + for line in lines { let line = line.trim(); if line.is_empty() || line.contains(':') && !line.starts_with('-') { break; diff --git a/src/utils/templates.rs b/src/utils/templates.rs index af10b811..1ad13c42 100644 --- a/src/utils/templates.rs +++ b/src/utils/templates.rs @@ -842,11 +842,7 @@ pub async fn load_registry() -> Result { if let Ok(modified) = metadata.modified() { use std::time::{Duration, SystemTime}; let ttl = Duration::from_secs(24 * 60 * 60); // 24 hours - if SystemTime::now() - .duration_since(modified) - .unwrap_or_else(|_| ttl) - < ttl - { + if SystemTime::now().duration_since(modified).unwrap_or(ttl) < ttl { let contents = fs::read_to_string(&cache_path).with_context(|| { format!("Failed to read cached registry at {}", cache_path.display()) })?; @@ -1200,6 +1196,9 @@ pub async fn get_template_by_name_and_version( } } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn semver_cmp(a: &str, b: &str) -> std::cmp::Ordering { let parse_version = |v: &str| { v.strip_prefix('v') @@ -1423,6 +1422,10 @@ pub async fn publish_template( /// Like `publish_template` but also records optional CLI version constraints. /// Install a template from a directory or `.zip` archive into the local registry. +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub async fn install_template_package( package_path: &Path, name: String, @@ -1450,6 +1453,10 @@ pub async fn install_template_package( .await } +// Each parameter is an independent, named input (CLI flags / distinct config +// values); bundling them into a struct here would add indirection without +// reducing real complexity. +#[allow(clippy::too_many_arguments)] pub async fn publish_template_versioned( template_path: &Path, name: String, @@ -1501,16 +1508,15 @@ pub async fn publish_template_versioned( copy_dir_recursive(&source_root, &dest)?; let created_at = Utc::now().to_rfc3339(); - let mut changelog: Vec = Vec::new(); - changelog.push(ChangelogEntry { + let changelog = vec![ChangelogEntry { version: version.clone(), date: Utc::now().format("%Y-%m-%d").to_string(), notes: "Initial release".to_string(), - }); + }]; let entry = TemplateEntry { name: name.clone(), - changelog: None, + changelog: Some(changelog), repository: None, security_review: None, version: version.clone(), @@ -2041,6 +2047,16 @@ pub async fn rollback_installed_template(name: &str) -> Result TemplateEntry { TemplateEntry { name: name.to_string(), @@ -2067,6 +2083,9 @@ mod tests { documentation: None, categories: Vec::new(), featured: false, + changelog: None, + repository: None, + security_review: None, } } @@ -2401,6 +2420,9 @@ mod tests { documentation: None, categories: Vec::new(), featured: false, + changelog: None, + repository: None, + security_review: None, }); // Test name search @@ -2452,6 +2474,9 @@ mod tests { documentation: None, categories: Vec::new(), featured: false, + changelog: None, + repository: None, + security_review: None, }; let dest = tmp.path().join(&entry.name); @@ -2505,6 +2530,9 @@ mod tests { documentation: None, categories: Vec::new(), featured: false, + changelog: None, + repository: None, + security_review: None, } } diff --git a/src/utils/test_automation.rs b/src/utils/test_automation.rs index b3b0ef22..70142e13 100644 --- a/src/utils/test_automation.rs +++ b/src/utils/test_automation.rs @@ -1,9 +1,7 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -use std::process::Command; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Instant; @@ -114,7 +112,7 @@ impl TestCaseGenerator { } pub fn generate_from_contract(&self) -> Result { - let wasm_path = self + let _wasm_path = self .contract_path .join("target/wasm32-unknown-unknown/release"); @@ -208,7 +206,7 @@ impl TestCaseGenerator { &self, line: &str, line_num: usize, - file_path: &Path, + _file_path: &Path, ) -> Result { let function_name = line .trim_start() @@ -312,7 +310,7 @@ impl ParallelTestRunner { self.generate_report(suite, results, duration) } - fn run_single_test(test: &TestCase, wasm_path: &Path) -> TestResult { + fn run_single_test(test: &TestCase, _wasm_path: &Path) -> TestResult { let start = Instant::now(); // Simulate test execution diff --git a/src/utils/test_generator.rs b/src/utils/test_generator.rs index d22f7830..3f5aed17 100644 --- a/src/utils/test_generator.rs +++ b/src/utils/test_generator.rs @@ -210,6 +210,9 @@ fn safe_identifier(value: &str) -> String { identifier } +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] fn is_mutating(content: &str, func: &str) -> bool { let mut in_fn = false; for line in content.lines() { diff --git a/src/utils/test_optimizer.rs b/src/utils/test_optimizer.rs index d451cd06..09b2500b 100644 --- a/src/utils/test_optimizer.rs +++ b/src/utils/test_optimizer.rs @@ -4,9 +4,6 @@ use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::{Duration, Instant}; // ── Core Data Structures ───────────────────────────────────────────────────── @@ -218,6 +215,10 @@ impl TestOptimizer { } fn save_state(&self) -> Result<()> { + if !self.config_dir.exists() { + fs::create_dir_all(&self.config_dir) + .with_context(|| format!("Failed to create {}", self.config_dir.display()))?; + } let history_path = self.config_dir.join("history.json"); fs::write(&history_path, serde_json::to_string_pretty(&self.history)?) .with_context(|| format!("Failed to write {}", history_path.display()))?; @@ -324,17 +325,14 @@ impl TestOptimizer { ) -> Vec> { let mut batches: Vec> = Vec::new(); - let (io_bound, _other): (Vec, Vec) = tests + let (io_bound, other): (Vec, Vec) = tests .iter() .cloned() .partition(|t| t.resource_profile.io_intensity > 0.6); - let cpu_bound: Vec = vec![]; - let memory_bound: Vec = vec![]; - let general: Vec = vec![]; - let (cpu_only, general): (Vec<_>, Vec<_>) = general + let (cpu_only, other): (Vec<_>, Vec<_>) = other .into_iter() .partition(|t| t.resource_profile.cpu_intensity > 0.6); - let (mem_only, general): (Vec<_>, Vec<_>) = general + let (mem_only, general): (Vec<_>, Vec<_>) = other .into_iter() .partition(|t| t.resource_profile.memory_mb > 256); @@ -419,7 +417,7 @@ impl TestOptimizer { let mut score = stability * 60.0 + transition_ratio * 40.0; - if failure_rate < 0.1 || failure_rate > 0.9 { + if !(0.1..=0.9).contains(&failure_rate) { score *= 0.3; } @@ -643,7 +641,7 @@ impl TestOptimizer { let avg = total_duration as f64 / results.len() as f64; let mut sorted = results.to_vec(); - sorted.sort_by(|a, b| a.duration_ms.cmp(&b.duration_ms)); + sorted.sort_by_key(|a| a.duration_ms); let median = sorted[sorted.len() / 2].duration_ms as f64; let p95_idx = ((sorted.len() as f64 * 0.95) as usize).min(sorted.len() - 1); @@ -780,7 +778,7 @@ impl TestOptimizer { } }) .collect(); - category_summary.sort_by(|a, b| b.total_failures.cmp(&a.total_failures)); + category_summary.sort_by_key(|a| std::cmp::Reverse(a.total_failures)); let recurrence_ratio = if total_failing > 0 { all_failing diff --git a/src/utils/testnet_integration.rs b/src/utils/testnet_integration.rs index bf5ac782..330b087a 100644 --- a/src/utils/testnet_integration.rs +++ b/src/utils/testnet_integration.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result}; -use reqwest; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; @@ -173,6 +172,9 @@ pub struct LedgerEntryResult { // ── Raw JSON-RPC helpers ─────────────────────────────────────────────────── #[derive(Debug, Serialize)] +// Not currently called from any code path in this crate. Kept rather than +// removed since deleting it is a product decision, not a lint-scoping one. +#[allow(dead_code)] struct RpcRequest<'a> { jsonrpc: &'static str, id: u64, diff --git a/templates/registry.json b/templates/registry.json index 2f25471b..5027e308 100644 --- a/templates/registry.json +++ b/templates/registry.json @@ -23,7 +23,7 @@ "status": "audited", "audited_at": "2025-03-15T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 95 }, "changelog": [ @@ -54,7 +54,7 @@ "status": "audited", "audited_at": "2025-04-01T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 1, + "findings": "1", "score": 88 }, "changelog": [ @@ -142,7 +142,7 @@ "status": "audited", "audited_at": "2025-05-01T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 98 }, "changelog": [ @@ -172,7 +172,7 @@ "status": "audited", "audited_at": "2025-02-01T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 96 }, "changelog": [ @@ -200,7 +200,7 @@ "status": "audited", "audited_at": "2025-05-20T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 97 }, "changelog": [ @@ -229,7 +229,7 @@ "status": "audited", "audited_at": "2025-04-10T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 95 }, "changelog": [ @@ -257,7 +257,7 @@ "status": "audited", "audited_at": "2025-04-10T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 96 }, "changelog": [ @@ -285,7 +285,7 @@ "status": "audited", "audited_at": "2025-06-29T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 98 }, "changelog": [ @@ -313,7 +313,7 @@ "status": "audited", "audited_at": "2025-06-29T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 97 }, "changelog": [ @@ -341,7 +341,7 @@ "status": "audited", "audited_at": "2025-06-29T00:00:00Z", "auditor": "StarForge Security Team", - "findings": 0, + "findings": null, "score": 94 }, "changelog": [ diff --git a/tests/ai_test_assistant.rs b/tests/ai_test_assistant.rs index e48a5950..179cb729 100644 --- a/tests/ai_test_assistant.rs +++ b/tests/ai_test_assistant.rs @@ -393,7 +393,7 @@ fn coverage_input_serialization_roundtrip() { #[test] fn empty_contract_analysis() { - let analysis = ata::analyze_contract_for_testing("fn helper() {}"); + let analysis = ata::analyze_contract_for_testing("fn helper() {}").unwrap(); assert_eq!(analysis.total_functions, 0); assert_eq!(analysis.public_functions, 0); } diff --git a/tests/bindings_integration.rs b/tests/bindings_integration.rs index 8a6ff2cf..572cd380 100644 --- a/tests/bindings_integration.rs +++ b/tests/bindings_integration.rs @@ -1,9 +1,9 @@ // Integration test for the binding generator // This test demonstrates a complete workflow with a simple example +use starforge::utils::bindings::BindingLanguage; use std::path::Path; use tempfile::NamedTempFile; -use starforge::utils::bindings::BindingLanguage; /// Test that demonstrates the complete binding generation workflow #[test] @@ -13,7 +13,7 @@ fn test_complete_binding_workflow() { let wasm_bytes = create_example_wasm_with_metadata(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &wasm_bytes).unwrap(); - + // Test each language for lang in [ BindingLanguage::Rust, @@ -22,37 +22,55 @@ fn test_complete_binding_workflow() { BindingLanguage::Go, ] { println!("Testing binding generation for {:?}", lang); - + let result = starforge::utils::bindings::generate_bindings(temp_file.path(), lang); - + // For this test, we just verify that generation doesn't panic // In a real integration test with a proper contract, we would: // 1. Verify the generated code compiles // 2. Test that the generated client can be instantiated // 3. Verify type safety and method signatures - + match result { Ok(code) => { // Basic validation of generated code match lang { BindingLanguage::Rust => { - assert!(code.contains("pub struct ContractClient"), "Missing ContractClient in Rust"); + assert!( + code.contains("pub struct ContractClient"), + "Missing ContractClient in Rust" + ); assert!(code.contains("impl ContractClient"), "Missing impl in Rust"); } BindingLanguage::TypeScript => { - assert!(code.contains("export class ContractClient"), "Missing ContractClient in TS"); - assert!(code.contains("export interface"), "Missing interfaces in TS"); + assert!( + code.contains("export class ContractClient"), + "Missing ContractClient in TS" + ); + assert!( + code.contains("export interface"), + "Missing interfaces in TS" + ); } BindingLanguage::Python => { - assert!(code.contains("class ContractClient"), "Missing ContractClient in Python"); + assert!( + code.contains("class ContractClient"), + "Missing ContractClient in Python" + ); assert!(code.contains("@dataclass"), "Missing dataclass in Python"); } BindingLanguage::Go => { - assert!(code.contains("type ContractClient struct"), "Missing ContractClient in Go"); - assert!(code.contains("func NewContractClient"), "Missing constructor in Go"); + assert!( + code.contains("type ContractClient struct"), + "Missing ContractClient in Go" + ); + assert!( + code.contains("func NewContractClient"), + "Missing constructor in Go" + ); } } - + // Verify event generation (if events were in the metadata) if code.contains("Event") { println!("Generated code includes event definitions for {:?}", lang); @@ -69,25 +87,25 @@ fn test_complete_binding_workflow() { /// Create a minimal WASM with some example contract metadata fn create_example_wasm_with_metadata() -> Vec { let mut wasm = Vec::new(); - + // WASM magic and version wasm.extend(b"\0asm\x01\x00\x00\x00"); - + // For a real test, we would include a proper "contractspecv0" custom section // with XDR-encoded contract metadata. This is simplified for demonstration. - + // Add a custom section header wasm.push(0); // Custom section ID wasm.push(20); // Section length - + // Custom section name "contractspecv0" (simplified) let name = "contractspecv0"; wasm.push(name.len() as u8); wasm.extend(name.as_bytes()); - + // Simplified metadata - in reality this would be XDR-encoded wasm.extend(b"example metadata"); - + wasm } @@ -96,21 +114,27 @@ fn create_example_wasm_with_metadata() -> Vec { fn test_error_handling() { // Test with empty file let temp_file = NamedTempFile::new().unwrap(); - let result = starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); + let result = + starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); assert!(result.is_err(), "Should fail on empty file"); - + // Test with non-WASM data let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), b"not wasm at all").unwrap(); - let result = starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); + let result = + starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); assert!(result.is_err(), "Should fail on non-WASM data"); - + // Test with valid WASM but no contract metadata let minimal_wasm = b"\0asm\x01\x00\x00\x00"; let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), minimal_wasm).unwrap(); - let result = starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); - assert!(result.is_err(), "Should fail on WASM without contract metadata"); + let result = + starforge::utils::bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); + assert!( + result.is_err(), + "Should fail on WASM without contract metadata" + ); } /// Test that the binding generator produces idiomatic code for each language @@ -119,23 +143,33 @@ fn test_idiomatic_code_generation() { let test_wasm = create_example_wasm_with_metadata(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + // Test each language for basic idiomatic patterns let languages = [ (BindingLanguage::Rust, vec!["pub struct", "impl", "Result<"]), - (BindingLanguage::TypeScript, vec!["export class", "export interface", "type"]), - (BindingLanguage::Python, vec!["class", "def", "from typing import"]), + ( + BindingLanguage::TypeScript, + vec!["export class", "export interface", "type"], + ), + ( + BindingLanguage::Python, + vec!["class", "def", "from typing import"], + ), (BindingLanguage::Go, vec!["type", "func", "package"]), ]; - + for (lang, patterns) in languages { let result = starforge::utils::bindings::generate_bindings(temp_file.path(), lang); - + if let Ok(code) = result { for pattern in &patterns { - assert!(code.contains(pattern), - "Missing pattern '{}' in {:?} generated code", pattern, lang); + assert!( + code.contains(pattern), + "Missing pattern '{}' in {:?} generated code", + pattern, + lang + ); } } } -} \ No newline at end of file +} diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index db6a6209..d7fdcbed 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -1,21 +1,21 @@ +use starforge::utils::bindings::{self, BindingLanguage}; use std::path::Path; use tempfile::NamedTempFile; -use starforge::utils::bindings::{self, BindingLanguage}; // Create a minimal valid WASM with contract metadata section for testing fn create_test_wasm() -> Vec { // Create a simple WASM that will fail to parse but is valid structurally // This tests error handling paths let mut wasm = Vec::new(); - + // WASM magic and version wasm.extend(b"\0asm\x01\x00\x00\x00"); - + // Add a type section (minimum valid module) wasm.push(1); // section id for type section wasm.push(1); // section length: 1 byte wasm.push(0); // 0 function types - + wasm } @@ -24,14 +24,20 @@ fn test_generate_rust_bindings() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); // Note: This will fail because our test WASM doesn't have proper contract spec // But we're testing that the function handles it gracefully if result.is_ok() { let generated = result.unwrap(); - assert!(generated.contains("pub struct ContractClient"), "Missing ContractClient struct"); - assert!(generated.contains("impl ContractClient"), "Missing ContractClient implementation"); + assert!( + generated.contains("pub struct ContractClient"), + "Missing ContractClient struct" + ); + assert!( + generated.contains("impl ContractClient"), + "Missing ContractClient implementation" + ); } // Else: expected failure due to invalid spec data } @@ -41,11 +47,14 @@ fn test_generate_typescript_bindings() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::TypeScript); if result.is_ok() { let generated = result.unwrap(); - assert!(generated.contains("export class ContractClient"), "Missing ContractClient class"); + assert!( + generated.contains("export class ContractClient"), + "Missing ContractClient class" + ); assert!(generated.contains("export interface"), "Missing interfaces"); } } @@ -55,12 +64,18 @@ fn test_generate_python_bindings() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::Python); if result.is_ok() { let generated = result.unwrap(); - assert!(generated.contains("class ContractClient"), "Missing ContractClient class"); - assert!(generated.contains("@dataclass"), "Missing dataclass decorators"); + assert!( + generated.contains("class ContractClient"), + "Missing ContractClient class" + ); + assert!( + generated.contains("@dataclass"), + "Missing dataclass decorators" + ); } } @@ -69,12 +84,18 @@ fn test_generate_go_bindings() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::Go); if result.is_ok() { let generated = result.unwrap(); - assert!(generated.contains("type ContractClient struct"), "Missing ContractClient struct"); - assert!(generated.contains("func NewContractClient"), "Missing constructor"); + assert!( + generated.contains("type ContractClient struct"), + "Missing ContractClient struct" + ); + assert!( + generated.contains("func NewContractClient"), + "Missing constructor" + ); } } @@ -83,11 +104,11 @@ fn test_all_languages() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + // Test each language for lang in [ BindingLanguage::Rust, - BindingLanguage::TypeScript, + BindingLanguage::TypeScript, BindingLanguage::Python, BindingLanguage::Go, ] { @@ -102,9 +123,12 @@ fn test_empty_wasm_error() { let empty_wasm = b"\0asm\x01\x00\x00\x00"; // Minimal valid WASM header let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), empty_wasm).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); - assert!(result.is_err(), "Should fail on WASM without contract metadata"); + assert!( + result.is_err(), + "Should fail on WASM without contract metadata" + ); } #[test] @@ -112,7 +136,7 @@ fn test_invalid_wasm_error() { let invalid_data = b"not wasm at all"; let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), invalid_data).unwrap(); - + let result = bindings::generate_bindings(temp_file.path(), BindingLanguage::Rust); assert!(result.is_err(), "Should fail on invalid WASM"); } @@ -124,7 +148,7 @@ fn test_event_generation() { let test_wasm = create_test_wasm(); let temp_file = NamedTempFile::new().unwrap(); std::fs::write(temp_file.path(), &test_wasm).unwrap(); - + // Test each language for event generation for lang in [ BindingLanguage::Rust, @@ -136,4 +160,4 @@ fn test_event_generation() { // The generation should handle missing event data gracefully assert!(result.is_err() || result.is_ok()); } -} \ No newline at end of file +} diff --git a/tests/contract_property_tests.rs b/tests/contract_property_tests.rs index b1fd46d1..11848977 100644 --- a/tests/contract_property_tests.rs +++ b/tests/contract_property_tests.rs @@ -33,7 +33,7 @@ proptest! { /// Any byte sequence with a valid 4-byte magic header but < 8 bytes is rejected. #[test] - fn prop_magic_header_short_rejected(data in prop::collection::vec(any::(), 4..8)) { + fn prop_magic_header_short_rejected(data in prop::collection::vec(any::(), 0..4)) { let mut input = b"\0asm".to_vec(); input.extend(data); let result = validate_wasm(&input); @@ -287,7 +287,9 @@ proptest! { vec![serde_json::json!("test")], serde_json::json!({"data": 1}), ); - env.auth.auto_approve(MockAddress::account(1)); + let account = MockAddress::account(1); + env.auth.auto_approve(account.clone()); + env.auth.require_auth(&account, &MockAddress::contract(1), "test_fn"); prop_assert!(!env.storage.is_empty()); prop_assert!(!env.events.is_empty()); diff --git a/tests/plugin_version_compatibility_test.rs b/tests/plugin_version_compatibility_test.rs index 2219a016..c29a24ba 100644 --- a/tests/plugin_version_compatibility_test.rs +++ b/tests/plugin_version_compatibility_test.rs @@ -1,7 +1,7 @@ use starforge::plugins::interface::CORE_VERSION; use starforge::plugins::manifest::{ - load_manifest_for_library, require_compatible_manifest, PluginManifest, - SupportedVersionPolicy, MANIFEST_FILENAME, + load_manifest_for_library, require_compatible_manifest, PluginManifest, SupportedVersionPolicy, + MANIFEST_FILENAME, }; #[test] @@ -160,7 +160,9 @@ fn test_load_manifest_and_require_manifest_file_system() { // Absent manifest fails require_compatible_manifest let missing_err = require_compatible_manifest(&lib_path, "test-plugin").unwrap_err(); - assert!(missing_err.to_string().contains("Plugin manifest not found")); + assert!(missing_err + .to_string() + .contains("Plugin manifest not found")); // Write valid manifest std::fs::write( diff --git a/tests/template_recommendation.rs b/tests/template_recommendation.rs index 0adf2bf2..62a52e70 100644 --- a/tests/template_recommendation.rs +++ b/tests/template_recommendation.rs @@ -47,7 +47,10 @@ fn make_entry(name: &str, tags: &[&str], downloads: u32, verified: bool) -> Temp homepage: None, documentation: None, security_review: None, - changelog: vec![], + changelog: None, + repository_url: None, + categories: vec![], + featured: false, } } @@ -88,7 +91,7 @@ fn skill_level_parses_all_variants() { ("senior", SkillLevel::Advanced), ] { assert_eq!( - SkillLevel::from_str(input), + SkillLevel::parse_lenient(input), Some(expected), "Expected '{}' to parse correctly", input @@ -100,7 +103,7 @@ fn skill_level_parses_all_variants() { fn skill_level_rejects_unknown_strings() { for bad in ["", "pro", "newbie", "wizard", "123"] { assert_eq!( - SkillLevel::from_str(bad), + SkillLevel::parse_lenient(bad), None, "Expected '{}' to be rejected", bad