ModelCraft foundation: workspace, quality gates, FE model, command engine, geometry boundary

A pure-Rust CAE modeling workspace following the SolidCraft conventions:
- modelcraft-model: nodes and elements by label with hostile-input validation
- modelcraft-geometry: SolidCraft's kernel (pinned git dependency) behind our own API;
  tessellation into a face-tagged boundary surface for volume meshing
- modelcraft-engine: CommandSpec registry, Session::execute with rollback and panic guard,
  scripts and a journal
- modelcraft-cli: list commands, run scripts
- xtask: layers (only geometry may use solidcraft-*), assets, licences, tests, wasm, ci
- AGENTS.md, architecture, ADR 0001 (libraries and licences), roadmap, CI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 committed 2026-10-13 13:56:42 -04:00
commit b64ec3f698
38 files changed
+3931

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
//! `cargo xtask assets`: every non-code asset in the repository must have a row in
//! ATTRIBUTION.md (author, source, licence). Icons are drawn in code and need no file.
use std::path::Path;
const ASSET_EXT: &[&str] =
&["png", "jpg", "jpeg", "svg", "ico", "icns", "ttf", "otf", "woff", "woff2", "gif", "bmp", "webp", "wav", "mp3", "stl", "step", "stp", "obj"];
const SKIP_DIRS: &[&str] = &["target", ".git", "plan", "dist"];
fn walk(dir: &Path, root: &Path, out: &mut Vec<String>) {
let Ok(rd) = std::fs::read_dir(dir) else { return };
for e in rd.flatten() {
let p = e.path();
let name = e.file_name().to_string_lossy().to_string();
if p.is_dir() {
if !SKIP_DIRS.contains(&name.as_str()) && !name.starts_with('.') {
walk(&p, root, out);
}
} else if let Some(ext) = p.extension().map(|x| x.to_string_lossy().to_ascii_lowercase())
&& ASSET_EXT.contains(&ext.as_str())
&& let Ok(rel) = p.strip_prefix(root)
{
out.push(rel.to_string_lossy().replace('\\', "/"));
}
}
}
pub fn run(root: &Path) -> Result<(), String> {
let attribution = std::fs::read_to_string(root.join("ATTRIBUTION.md")).map_err(|e| format!("ATTRIBUTION.md: {e}"))?;
let mut files = Vec::new();
walk(root, root, &mut files);
files.sort();
let missing: Vec<&String> = files.iter().filter(|f| !attribution.contains(f.as_str())).collect();
if missing.is_empty() {
println!("assets: {} asset file(s), all attributed", files.len());
Ok(())
} else {
Err(format!("assets missing from ATTRIBUTION.md:\n{}", missing.iter().map(|m| format!(" {m}")).collect::<Vec<_>>().join("\n")))
}
}