Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,44 @@
use nomifun_cron::prompt::{
SKILL_SUGGEST_FILENAME, build_existing_conversation_prompt,
build_new_conversation_prompt_with_skill_suggest, build_new_conversation_with_skill_prompt,
build_skill_suggest_prompt,
};
#[test]
fn build_new_conversation_prompt_with_skill_suggest_includes_follow_up_block() {
let prompt = build_new_conversation_prompt_with_skill_suggest("Daily Report", "Every day at 9am", "Summarize it.");
assert!(prompt.contains(&format!("create a file named \"{SKILL_SUGGEST_FILENAME}\"")));
assert!(prompt.contains("short kebab-case name"));
assert!(prompt.contains("If you think the task is too simple or one-off to benefit from a skill file"));
}
#[test]
fn build_new_conversation_with_skill_prompt_matches_frontend_copy() {
let prompt = build_new_conversation_with_skill_prompt("Daily Report", "Summarize it.");
assert_eq!(
prompt,
"[Scheduled Task Context]\nTask: Daily Report\n\nThis is a scheduled task execution. A skill file with detailed instructions has been loaded\ninto your workspace. You MUST read and follow the skill instructions precisely.\n\nRules:\n1. Execute the task directly — do NOT ask clarifying questions.\n2. Follow the output format, tone, sources, and steps defined in the skill.\n3. If the task requires external data (news, weather, etc.), search for the latest information.\n[/Scheduled Task Context]\n\nSummarize it."
);
}
#[test]
fn build_existing_conversation_prompt_matches_frontend_copy() {
let prompt = build_existing_conversation_prompt("Daily Report", "Every day at 9am", "Summarize it.");
assert_eq!(
prompt,
"[Scheduled Task Execution]\nTask: Daily Report\nSchedule: Every day at 9am\n\nThis message is NOT a conversation from the user — it is a scheduled task triggered automatically.\nThe text below is a TASK INSTRUCTION that you must execute, not something the user is saying to you.\n\nRules:\n1. Treat the instruction as a command to perform, not as a chat message to respond to.\n2. Execute it directly — do NOT ask clarifying questions.\n3. If the task requires external data (news, weather, etc.), search for the latest information.\n\nTask instruction:\nSummarize it."
);
}
#[test]
fn build_skill_suggest_prompt_matches_frontend_copy() {
let prompt = build_skill_suggest_prompt("Daily Report");
assert!(prompt.starts_with("The task \"Daily Report\" is a recurring scheduled task. Based on what you just did,"));
assert!(prompt.contains("```markdown"));
assert!(prompt.contains("Use concrete details from this execution, not placeholders."));
assert!(
prompt.ends_with(
"If you think the task is too simple or one-off to benefit from a skill file, you can skip this."
)
);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,116 @@
use std::time::{SystemTime, UNIX_EPOCH};
use nomifun_cron::skill_file::{
ParsedSkillContent, build_skill_content, content_hash, cron_skill_dir, cron_skill_file_path, parse_skill_content,
read_skill_content, validate_skill_content, write_raw_skill_file, write_skill_file,
};
fn unique_temp_dir(label: &str) -> std::path::PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
std::env::temp_dir().join(format!("nomifun-cron-{label}-{}-{nanos}", std::process::id()))
}
#[test]
fn build_skill_content_matches_frontend_shape() {
let content = build_skill_content(
"Daily Report",
"Line 1\nLine 2\r\nLine 3",
"Run report",
Some("Every day at 9am"),
);
assert!(content.contains("name: Daily Report"));
assert!(content.contains("description: Line 1 Line 2 Line 3"));
assert!(content.contains("This is a scheduled task: **Daily Report**"));
assert!(content.contains("Schedule: Every day at 9am"));
assert!(content.contains("## Instructions"));
assert!(content.ends_with("Run report"));
}
#[test]
fn parse_skill_content_roundtrips_built_files() {
let built = build_skill_content("My Job", "My Description", "First\n\nSecond", None);
let parsed = parse_skill_content(&built).unwrap();
assert_eq!(
parsed,
ParsedSkillContent {
name: "My Job".into(),
description: "My Description".into(),
body: "First\n\nSecond".into(),
}
);
}
#[test]
fn parse_skill_content_skips_blank_lines_after_frontmatter() {
let parsed = parse_skill_content("---\nname: Test\ndescription: Desc\n---\n\n\nPrompt").unwrap();
assert_eq!(parsed.body, "Prompt");
}
#[test]
fn parse_skill_content_handles_empty_body() {
let parsed = parse_skill_content("---\nname: Test\ndescription: Desc\n---\n\n").unwrap();
assert_eq!(parsed.body, "");
}
#[test]
fn validate_skill_content_rejects_placeholders() {
let err =
validate_skill_content("---\nname: skill-name\ndescription: Real description\n---\n\nReal body").unwrap_err();
assert!(err.to_string().contains("template placeholder"));
}
#[test]
fn content_hash_normalizes_line_endings_and_edges() {
let a = content_hash("---\nname: Test\ndescription: Desc\n---\n\nBody\n");
let b = content_hash("---\r\nname: Test\r\ndescription: Desc\r\n---\r\n\r\nBody");
let c = content_hash(" ---\nname: Test\ndescription: Desc\n---\n\nBody ");
assert_eq!(a, b);
assert_eq!(a, c);
}
#[tokio::test]
async fn write_read_and_resolve_skill_file_paths() {
let base = unique_temp_dir("write-read");
std::fs::create_dir_all(&base).unwrap();
let file_path = write_skill_file(
&base,
"job-123",
"Daily Report",
"Generate daily report",
"Run report",
Some("Every day at 9am"),
)
.await
.unwrap();
assert_eq!(
cron_skill_dir(&base, "job-123").unwrap(),
base.join("cron").join("skills").join("cron-job-123")
);
assert_eq!(file_path, cron_skill_file_path(&base, "job-123").unwrap());
let raw = read_skill_content(&base, "job-123").await.unwrap().unwrap();
let parsed = parse_skill_content(&raw).unwrap();
assert_eq!(parsed.name, "Daily Report");
assert_eq!(parsed.description, "Generate daily report");
assert_eq!(parsed.body, "Run report");
std::fs::remove_dir_all(&base).unwrap();
}
#[tokio::test]
async fn write_raw_skill_file_validates_before_writing() {
let base = unique_temp_dir("write-raw");
std::fs::create_dir_all(&base).unwrap();
let err = write_raw_skill_file(&base, "job-456", "not valid").await.unwrap_err();
assert!(err.to_string().contains("skill file must start with YAML frontmatter"));
assert!(read_skill_content(&base, "job-456").await.unwrap().is_none());
std::fs::remove_dir_all(&base).unwrap();
}