diff --git a/PLAN.md b/PLAN.md index 6caa7d3..9593558 100644 --- a/PLAN.md +++ b/PLAN.md @@ -21,7 +21,7 @@ - [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages and invalid shards, propagates mutest failure, and mutation-tests `hemx-axum`, `hemx-build`, `hemx-core`, `hemx-js`, and the full `hemx-test` package cleanly; full package closure remains. - **User value:** maintainers can run one bounded repository command and trust that meaningful Rust logic across every mutation-applicable library is either killed or explicitly justified. - **Build:** add a capped `hemx-xtask` mutation command that invokes `/opt/repositories/mutest`/`mutest` through package-native test targets rather than the broken workspace-wide example path; enumerate only current mutation-applicable library/proc-macro packages; finish adversarial tests or simplify code until every survivor is classified; keep equivalent, invariant-only, and infrastructure-inapplicable classifications inspectable and minimal; document the exact local release command in the existing readiness surface. -- **Blocked by:** none; broad survivors currently remain in `hemx-derive` and `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. `hemx-derive` public shards `1/8` through `7/8` are clean. Shard `7/8` passed with 60 mutants (45 caught, 15 unviable) after exact coverage of all state/async/result registration shapes and the no-generated-component repair diagnostic. Shard `8/8` remains. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants. +- **Blocked by:** none; broad survivors currently remain in `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. All eight deterministic `hemx-derive` shards pass: 480 mutants total, 393 caught and 87 unviable, including final shard `8/8` with 54 mutants (42 caught, 12 unviable) after fail-closed malformed symbol-line handling, exact sorted/deduplicated component discovery, component filtering, non-handler exclusion, and direct quoted compile diagnostics. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants. - **Proof:** the new xtask mutation command exits zero within its documented bound, covers each applicable package, emits no unexplained missed mutant, and a deliberate adjacent mutation makes it fail. `cargo run -p hemx-xtask -- test` remains green. req: test/020 req: test/021 ## 3. Elect and enforce the release license policy diff --git a/hemx-derive/src/lib.rs b/hemx-derive/src/lib.rs index 2b2a1e4..c7988ea 100644 --- a/hemx-derive/src/lib.rs +++ b/hemx-derive/src/lib.rs @@ -1007,9 +1007,7 @@ fn symbol_component(symbol: &str) -> Option<&str> { } fn compile_error(message: &str) -> TokenStream { - format!("compile_error!({message:?});") - .parse() - .expect("compile_error expansion is valid") + quote!(compile_error!(#message);).into() } #[cfg(test)] @@ -1023,7 +1021,8 @@ mod tests { handler_syms_path, has_form_param, has_non_unit_return, is_type_named, join_contract_errors, missing_component_handlers, missing_form_generated_files_message, missing_handle_params, missing_handler_generated_files_message, parser_type, - returns_result, rust_ident, syms_contains_handle, ComponentHandler, HandlerPlacement, + returns_result, rust_ident, symbol_component, syms_components, syms_contains_handle, + syms_handles, ComponentHandler, HandlerPlacement, }; use quote::{quote, ToTokens}; use syn::{parse_quote, ItemFn, Type}; @@ -1345,7 +1344,7 @@ mod tests { let path = std::env::temp_dir().join("hemx-derive-syms-test.syms"); std::fs::write( &path, - "hemx-syms-v1\nslot\ttemplates/a.heml::count\tcount\t1\nhandle\ttemplates/a.heml::create\tcreate\t2\nhandle_form\tcreate\tnew_todo\nhandle_param\tcreate\ttodo_id\n", + "hemx-syms-v1\nslot\ttemplates/a.heml::count\tcount\t1\nhandle\ttemplates/z.heml::archive\tarchive\t3\nhandle\ttemplates/a.heml::create\tcreate\t2\nhandle\ttemplates/a.heml::delete\tdelete\t4\nhandle\ttemplates/a.heml::create\tcreate-again\t5\nhandle\thas-no-component\tignored\t6\nhandle\nhandle_form\tcreate\tnew_todo\nhandle_param\tcreate\ttodo_id\n", ) .unwrap(); @@ -1355,6 +1354,19 @@ mod tests { assert!(!handle_requires_form(&path, "missing")); assert_eq!(handle_params(&path, "create"), vec!["todo_id"]); assert!(handle_params(&path, "missing").is_empty()); + assert_eq!( + syms_handles(&path, Some("a")), + vec!["create", "delete", "create-again"] + ); + assert_eq!( + syms_handles(&path, None), + vec!["archive", "create", "delete", "create-again", "ignored"] + ); + assert_eq!(syms_components(&path), vec!["a", "z"]); + assert_eq!(symbol_component("templates/a.heml::create"), Some("a")); + assert_eq!(symbol_component("a.heml::create"), Some("a")); + assert_eq!(symbol_component("a.html::create"), None); + assert_eq!(symbol_component("a.heml"), None); let _ = std::fs::remove_file(&path); assert!( @@ -1531,6 +1543,8 @@ mod tests { mod component { #[hemx::handler] fn create() -> impl hemx::IntoEffect { hemx::advanced::EffectBatch::default() } + + fn helper() {} } }; let (_, items) = module.content.expect("inline module");