diff --git a/crates/aura-cli/src/graph_construct.rs b/crates/aura-cli/src/graph_construct.rs index a6a190e..439be80 100644 --- a/crates/aura-cli/src/graph_construct.rs +++ b/crates/aura-cli/src/graph_construct.rs @@ -376,18 +376,25 @@ fn gang_fault_prose(e: &CompileError) -> String { } } -/// (#185) When an unresolved type id LOOKS project-namespaced (`::`) but no -/// project was loaded for this invocation (`env.provenance()` is `None`), -/// append a hint pointing at the missing `Aura.toml` — the likeliest cause is -/// "ran outside the project directory", not "genuinely unknown std id". Bare -/// std-vocabulary ids (no `::`) and invocations that DO have a project loaded -/// get no hint. +/// (#185/#241) When an unresolved type id LOOKS project-namespaced (`::`), +/// the likeliest causes are environmental, tiered: outside any project the +/// hint points at the missing `Aura.toml`; inside a data-only project it +/// points at the missing node crate. Bare std ids (no `::`) and invocations +/// with a loaded node crate get no hint (the vocabulary error carries the +/// message). pub(crate) fn unresolved_namespace_hint(e: &LoadError, env: &crate::project::Env) -> Option { match e { - LoadError::UnknownNodeType(t) if t.contains("::") && env.provenance().is_none() => Some( - "type id looks project-namespaced but no Aura.toml was found — run inside a project directory" - .to_string(), - ), + LoadError::UnknownNodeType(t) if t.contains("::") => match env.provenance() { + None => Some( + "type id looks project-namespaced but no Aura.toml was found — run inside a project directory" + .to_string(), + ), + Some(p) if p.namespace.is_none() => Some( + "type id looks project-namespaced but this project binds no node crate — attach one with `aura nodes new `" + .to_string(), + ), + Some(_) => None, + }, _ => None, } } diff --git a/crates/aura-cli/tests/project_load.rs b/crates/aura-cli/tests/project_load.rs index df1e0ca..92acc5b 100644 --- a/crates/aura-cli/tests/project_load.rs +++ b/crates/aura-cli/tests/project_load.rs @@ -365,6 +365,27 @@ fn nodes_pointer_crate_resolves_and_stamps_its_namespace() { ); } +/// #241: inside a data-only project, a project-namespaced type id gets the +/// "binds no node crate" hint (not the outside-project "no Aura.toml" text). +#[test] +fn data_only_project_hints_attach_for_namespaced_ids() { + let tmp = std::env::temp_dir().join(format!("aura-dataonly-hint-{}", std::process::id())); + std::fs::create_dir_all(&tmp).unwrap(); + std::fs::write(tmp.join("Aura.toml"), "").unwrap(); + let bp = tmp.join("bp.json"); + std::fs::write(&bp, r#"{"format_version":1,"blueprint":{"name":"x","nodes":[{"primitive":{"type":"nosuch::Node"}}],"edges":[],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0}],"source":"F64"}],"output":[{"node":0,"field":0,"name":"bias"}]}}"#).unwrap(); + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args(["run", bp.to_str().unwrap()]) + .current_dir(&tmp) + .output() + .expect("spawn aura"); + assert_eq!(out.status.code(), Some(2), "stderr: {}", String::from_utf8_lossy(&out.stderr)); + let err = String::from_utf8_lossy(&out.stderr); + assert!(err.contains("binds no node crate"), "{err}"); + assert!(err.contains("aura nodes new"), "{err}"); + std::fs::remove_dir_all(&tmp).ok(); +} + #[test] fn missing_artifact_refuses_with_build_hint() { // A minimal never-built project: valid cargo metadata, no dylib on disk.