use super::*;
fn write_skill(root: &std::path::Path, name: &str, desc: &str) {
let dir = root.join(name);
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join("SKILL.md"),
format!("---\nname: {name}\ndescription: {desc}\n---\nFull body of {name}.\n"),
)
.unwrap();
}
#[serial_test::serial(real_fs)]
#[test]
fn system_prompt_index_includes_discovered_skill_name_and_description() {
let tmp = tempfile::TempDir::new().unwrap();
write_skill(tmp.path(), "commit-style", "How this repo writes commits");
let block = skills_index_for_prompt(&[tmp.path().to_path_buf()]).expect("an index block");
assert!(block.contains("Available skills (call `use_skill` to load one):"));
assert!(block.contains("commit-style: How this repo writes commits"));
assert!(!block.contains("Full body of commit-style."));
}
#[serial_test::serial(real_fs)]
#[test]
fn system_prompt_index_carries_the_install_hint_when_no_skills() {
let tmp = tempfile::TempDir::new().unwrap();
let block =
skills_index_for_prompt(&[tmp.path().to_path_buf()]).expect("install hint survives");
assert!(block.contains("newt skills install"));
assert!(!block.contains("Available skills"));
}
#[serial_test::serial(real_fs)]
#[test]
fn system_prompt_index_unions_search_path_first_dir_wins() {
let a = tempfile::TempDir::new().unwrap();
let b = tempfile::TempDir::new().unwrap();
write_skill(a.path(), "commit-style", "newt copy");
write_skill(b.path(), "commit-style", "claude copy");
write_skill(b.path(), "judge", "scoring");
let block = skills_index_for_prompt(&[a.path().to_path_buf(), b.path().to_path_buf()])
.expect("an index block");
assert!(block.contains("commit-style: newt copy"));
assert!(!block.contains("claude copy"));
assert!(block.contains("judge: scoring"));
}
#[serial_test::serial(real_fs)]
#[test]
fn system_prompt_fallback_uses_canonical_default_soul() {
let tmp = tempfile::TempDir::new().unwrap();
let prompt = build_system_prompt_with_soul(tmp.path().to_str().unwrap(), None, "test-plan.md");
assert!(
prompt.contains(newt_core::DEFAULT_SOUL),
"fallback must embed newt_core::DEFAULT_SOUL verbatim"
);
}
#[serial_test::serial(real_fs)]
#[test]
fn system_prompt_names_the_per_session_plan_path() {
let tmp = tempfile::TempDir::new().unwrap();
let ws = tmp.path().to_str().unwrap();
let path_a = newt_core::session_plan_path("sess-aaaa");
let path_a = path_a.to_string_lossy();
let prompt_a = build_system_prompt_with_soul(ws, None, &path_a);
assert!(
prompt_a.contains(path_a.as_ref()),
"prompt must name the session plan path"
);
assert!(
prompt_a.contains("Plan before coding"),
"the plan instruction must still be present (now injected, not in DEFAULT_SOUL)"
);
let path_b = newt_core::session_plan_path("sess-bbbb");
let prompt_b = build_system_prompt_with_soul(ws, None, &path_b.to_string_lossy());
assert!(prompt_b.contains(&*path_b.to_string_lossy()));
assert!(
!prompt_b.contains(path_a.as_ref()),
"sessions must not share a path"
);
}
#[serial_test::serial(real_fs)]
#[test]
fn default_soul_no_longer_hardcodes_a_plan_path() {
assert!(!newt_core::DEFAULT_SOUL.contains("plan.md"));
let tmp = tempfile::TempDir::new().unwrap();
let prompt = build_system_prompt_with_soul(
tmp.path().to_str().unwrap(),
Some("You are a custom agent."),
".scratch/sessions/xyz/plan.md",
);
assert!(prompt.contains("You are a custom agent."));
assert!(prompt.contains(".scratch/sessions/xyz/plan.md"));
}
#[serial_test::serial(real_fs)]
#[tokio::test]
async fn registered_agents_provider_block_reaches_prompt() {
let _guard = newt_core::test_guard::GlobalSettingsGuard::acquire();
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(dir.path().join("AGENTS.md"), "Run just check before PRs.").unwrap();
let mut memory = newt_core::MemoryManager::new();
memory.add_provider(newt_core::AgentsProvider::new(true, None));
let ctx = newt_core::SessionContext {
workspace: dir.path().to_string_lossy().into_owned(),
session_id: "s".into(),
};
memory.initialize_all(&ctx).await;
let prompt = rebuild_system_prompt(
dir.path().to_str().unwrap(),
&memory,
None,
"test-conversation",
);
assert!(prompt.contains("# Project instructions"));
assert!(prompt.contains("Run just check before PRs."));
}
#[serial_test::serial(real_fs)]
#[tokio::test]
async fn historical_notes_preserve_content_without_redefining_the_live_workspace() {
let _guard = newt_core::test_guard::GlobalSettingsGuard::acquire();
let dir = tempfile::TempDir::new().unwrap();
let old_workspace = dir.path().join("previous-repository");
let workspace = dir.path().join("current-repository");
fs::create_dir_all(&old_workspace).unwrap();
fs::create_dir_all(&workspace).unwrap();
let note = format!(
"The workspace root is {} on branch previous-work; use the retired_git tool.",
old_workspace.display()
);
let notes_path = dir.path().join("NOTES.md");
let original_notes = format!("{note}\n§\n");
fs::write(¬es_path, &original_notes).unwrap();
let mut memory = newt_core::MemoryManager::new();
memory.add_provider(newt_core::NoteStore::new(¬es_path, 2200));
let ctx = newt_core::SessionContext {
workspace: workspace.to_string_lossy().into_owned(),
session_id: "current-session".into(),
};
memory.initialize_all(&ctx).await;
let prompt = rebuild_system_prompt(&ctx.workspace, &memory, None, &ctx.session_id);
assert_eq!(fs::read_to_string(¬es_path).unwrap(), original_notes);
assert_eq!(prompt.matches(¬e).count(), 1, "retain the note verbatim");
let note_position = prompt.find(¬e).unwrap();
let history_position = prompt
.find("Historical notes from prior sessions")
.expect("cross-session notes must be framed as historical context");
assert!(history_position < note_position);
assert!(prompt.contains("Workspace, branch, and tool availability may be stale"));
assert!(prompt.contains("do not redefine current runtime state"));
let live_workspace_position = prompt
.find(&format!("Workspace: {}", workspace.display()))
.expect("the prompt must identify the actual initialized workspace");
assert!(note_position < live_workspace_position);
let live_context = &prompt[live_workspace_position..];
assert!(live_context.contains("current runtime root"));
assert!(live_context.contains("Relative tool paths resolve here"));
assert!(live_context
.contains("run_command starts here unless its cwd or command selects another directory"));
assert!(
live_context.contains("Other requested paths remain governed by the session's permissions")
);
}
struct SkillRootSandbox {
vars: Vec<(&'static str, Option<std::ffi::OsString>)>,
cwd: std::path::PathBuf,
}
impl SkillRootSandbox {
fn enter(config_dir: &std::path::Path, cwd: &std::path::Path) -> Self {
let keys = [
"NEWT_CONFIG",
newt_core::config::NEWT_CONFIG_DIR_ENV,
"HOME",
];
let sandbox = Self {
vars: keys.iter().map(|k| (*k, std::env::var_os(k))).collect(),
cwd: std::env::current_dir().unwrap(),
};
unsafe {
std::env::remove_var("NEWT_CONFIG");
std::env::set_var(newt_core::config::NEWT_CONFIG_DIR_ENV, config_dir);
std::env::set_var("HOME", config_dir);
}
std::env::set_current_dir(cwd).unwrap();
sandbox
}
}
impl Drop for SkillRootSandbox {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.cwd);
for (key, value) in &self.vars {
unsafe {
match value {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
}
}
}
}
#[ignore = "real-resource: weekly/release tier; pins cwd, HOME and NEWT_CONFIG_DIR"]
#[serial_test::serial(real_fs)]
#[tokio::test]
async fn a_bundled_only_skill_is_indexed_and_loaded_by_use_skill() {
let root = tempfile::TempDir::new().unwrap();
let checkout = root.path().join("checkout");
write_skill(
&checkout.join(".newt").join("bundled-skills"),
"grounding-skill",
"Only in the bundled dir",
);
let nested = checkout.join("crate");
let config_dir = root.path().join("config");
fs::create_dir_all(&nested).unwrap();
fs::create_dir_all(&config_dir).unwrap();
let _sandbox = SkillRootSandbox::enter(&config_dir, &nested);
let ws = nested.to_str().unwrap();
let prompt = build_system_prompt_with_soul(ws, None, "plan.md");
assert!(
prompt.contains("grounding-skill: Only in the bundled dir"),
"the prompt index must list the bundled-only skill"
);
let body = newt_core::execute_tool(
"use_skill",
&serde_json::json!({ "name": "grounding-skill" }),
ws,
false,
200,
&newt_core::Caveats::top(),
&mut newt_core::NoMcp,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
)
.await;
assert!(
body.contains("Full body of grounding-skill."),
"use_skill must load the copy the index listed, got: {body}"
);
}