diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml index bb2007b..8475cd0 100644 --- a/.cargo/mutants.toml +++ b/.cargo/mutants.toml @@ -10,6 +10,9 @@ # are behaviorally equivalent at these validated boundaries. # - hemx-build source inspection delegates to hemplate's currently infallible # Surface parser; file I/O and invalid Rust-context errors remain explicitly proven. +# - write_if_changed propagates non-NotFound read errors; for ordinary filesystem +# paths, attempting the same write returns the same OS error, so the guard mutant +# is externally equivalent while create/update/no-op behavior is mutation-proven. exclude_re = [ "test_process_try_wait", "test_process_poll_delay", @@ -25,4 +28,5 @@ exclude_re = [ "replace surface_for_heml_source.* with surface_for_heml_source.*unwrap\\(\\) in diagnostics_for_heml_source", "replace surface_for_heml_source.* with surface_for_heml_source.*unwrap\\(\\) in generated_targets_for_heml_source", "replace surface_for_heml_source.* with surface_for_heml_source.*unwrap\\(\\) in template_context_facts_for_heml_source", + "replace match guard error.kind\\(\\) == io::ErrorKind::NotFound with true in write_if_changed", ] diff --git a/PLAN.md b/PLAN.md index 7ef1f6e..ad17894 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, propagates mutest failure, and mutation-tests `hemx-axum`, `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-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now adversarially proves static convention diagnostics plus public file/source diagnostics, generated-target extraction, Hemlate-derived context facts, missing-file propagation, and invalid Rust-context diagnostics mutation-clean; remaining generated-resource and internal diagnostic survivors remain. The complete 470-mutant `hemx-axum` package gate now 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-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now adversarially proves static convention diagnostics; public file/source diagnostics and context facts; and mutation-clean generated-artifact create, no-op, and stale-output refresh behavior through `AppBuilder::run`; remaining resource-extraction and internal diagnostic survivors remain. The complete 470-mutant `hemx-axum` package gate now 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-build/src/lib.rs b/hemx-build/src/lib.rs index 7798de9..b639904 100644 --- a/hemx-build/src/lib.rs +++ b/hemx-build/src/lib.rs @@ -236,15 +236,14 @@ impl AppBuilder { } } -fn write_if_changed(path: &Path, contents: &[u8]) -> io::Result { +fn write_if_changed(path: &Path, contents: &[u8]) -> io::Result<()> { match std::fs::read(path) { - Ok(existing) if existing == contents => return Ok(false), + Ok(existing) if existing == contents => return Ok(()), Ok(_) => {} Err(error) if error.kind() == io::ErrorKind::NotFound => {} Err(error) => return Err(error), } - std::fs::write(path, contents)?; - Ok(true) + std::fs::write(path, contents) } pub fn app() -> AppBuilder { @@ -2593,6 +2592,45 @@ mod tests { let _ = std::fs::remove_dir_all(&base); } + #[test] + fn changed_template_refreshes_generated_artifacts() { + let base = test_dir("hemx-build-refresh"); + let templates = base.join("templates"); + let out = base.join("out"); + let template = templates.join("panel.heml"); + let _ = std::fs::remove_dir_all(&base); + std::fs::create_dir_all(&templates).unwrap(); + std::fs::write( + &template, + r#""#, + ) + .unwrap(); + + app().template_dir(&templates).out_dir(&out).run().unwrap(); + let initial_generated = std::fs::read_to_string(out.join("hemx.generated.rs")).unwrap(); + let initial_symbols = std::fs::read_to_string(out.join("hemx.syms")).unwrap(); + assert!(initial_generated.contains("pub const save")); + assert!(initial_symbols.contains("save")); + + std::fs::write( + &template, + r#""#, + ) + .unwrap(); + app().template_dir(&templates).out_dir(&out).run().unwrap(); + + let refreshed_generated = std::fs::read_to_string(out.join("hemx.generated.rs")).unwrap(); + let refreshed_symbols = std::fs::read_to_string(out.join("hemx.syms")).unwrap(); + assert_ne!(refreshed_generated, initial_generated); + assert_ne!(refreshed_symbols, initial_symbols); + assert!(refreshed_generated.contains("pub const cancel")); + assert!(!refreshed_generated.contains("pub const save")); + assert!(refreshed_symbols.contains("cancel")); + assert!(!refreshed_symbols.contains("save")); + let _ = std::fs::remove_dir_all(&base); + // test req: build/009 + } + #[test] fn emits_generated_resources_from_heml() { // req: codegen/003