返回 CodeWhale
tests.rs
根目录 / crates / tui / src / skills / system / tests.rs
1 use super::*;
2 use tempfile::TempDir;
3
4 fn skill_file(tmp: &TempDir, name: &str) -> std::path::PathBuf {
5 tmp.path().join(name).join("SKILL.md")
6 }
7
8 fn skill_dir(tmp: &TempDir, name: &str) -> std::path::PathBuf {
9 tmp.path().join(name)
10 }
11
12 fn marker_file(tmp: &TempDir) -> std::path::PathBuf {
13 tmp.path().join(".system-installed-version")
14 }
15
16 // ── fresh install ─────────────────────────────────────────────────────────
17
18 #[test]
19 fn fresh_install_creates_bundled_skills_and_marker() {
20 let tmp = TempDir::new().unwrap();
21 install_system_skills(tmp.path()).unwrap();
22
23 for skill in BUNDLED_SKILLS {
24 assert!(
25 skill_file(&tmp, skill.name).exists(),
26 "{} SKILL.md should be created",
27 skill.name
28 );
29 }
30 assert!(marker_file(&tmp).exists(), "marker should be created");
31
32 let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
33 assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
34 }
35
36 #[test]
37 fn bundled_integration_skills_use_current_codewhale_commands_and_paths() {
38 for (name, body) in [("mcp-builder", MCP_BUILDER_BODY), ("feishu", FEISHU_BODY)] {
39 assert!(
40 body.contains("codewhale mcp"),
41 "{name} must use the current CLI"
42 );
43 assert!(
44 !body.contains("deepseek mcp"),
45 "{name} must not recommend the retired CLI name"
46 );
47 }
48 assert!(SKILL_CREATOR_BODY.contains("<workspace>/.codewhale/skills"));
49 assert!(SKILL_CREATOR_BODY.contains("~/.codewhale/skills"));
50 assert!(SKILL_INSTALLER_BODY.contains("~/.codewhale/skills"));
51 // Bundled skills must name live tools. `read_file` is retired and cannot
52 // dispatch (crates/tui/src/tools/registry.rs:2067).
53 assert!(PDF_BODY.contains("built-in `File` tool (`action: \"read\"`)"));
54 for (name, body) in [
55 ("pdf", PDF_BODY),
56 ("help", HELP_BODY),
57 ("delegate", DELEGATE_BODY),
58 ("best-of-n", BEST_OF_N_BODY),
59 ] {
60 assert!(
61 !body.contains("read_file") && !body.contains("exec_shell"),
62 "{name} must not teach a retired tool name"
63 );
64 }
65 }
66
67 /// #4227 (requested by @JayBeest): the contributor sync/gate/digest skill
68 /// shipped in generations 8–11 and moved repo-local in generation 12. Its two
69 /// load-bearing refusals — never move a contributor's HEAD, never touch a
70 /// dirty tree — must survive any later edit to the retained body.
71 #[test]
72 fn contributor_onboarding_is_repo_local_and_keeps_its_refusals() {
73 assert!(
74 !is_bundled_skill_name("contributor-onboarding"),
75 "contributor-onboarding must not ship to every user anymore"
76 );
77
78 let body = contributor_onboarding_body();
79 assert!(body.contains("invocation: explicit-only"));
80 // Read-only by default: sync is proposed, never performed.
81 assert!(body.contains("Do not run `git fetch`, `git pull`, `git rebase`"));
82 assert!(body.contains("Do not stash, discard, reset, or commit a dirty tree"));
83 // The gate is quoted from CI rather than paraphrased, and the digest is
84 // built from files rather than generated.
85 assert!(body.contains("cargo clippy --workspace --all-features --locked"));
86 assert!(body.contains(".github/workflows/ci.yml"));
87 assert!(body.contains("Do not call a model provider"));
88 // Provider neutrality: the dogfood step sends nothing anywhere.
89 assert!(body.contains("./target/release/codewhale exec --help"));
90 assert!(body.contains("Never select a provider for them"));
91 // Contributor credit is part of the skill's own contract.
92 assert!(body.contains("@JayBeest"));
93 }
94
95 /// A generation-11 install carries a bundled `contributor-onboarding` copy.
96 /// Generation 12 stops shipping it but never deletes by name alone: an
97 /// installed copy — shipped or user-edited — survives the upgrade, while a
98 /// fresh generation-12 install never creates one.
99 #[test]
100 fn upgrade_to_generation_12_leaves_contributor_onboarding_in_place() {
101 let tmp = TempDir::new().unwrap();
102 fs::write(marker_file(&tmp), "11").unwrap();
103 let dir = skill_dir(&tmp, "contributor-onboarding");
104 fs::create_dir_all(&dir).unwrap();
105 fs::write(dir.join("SKILL.md"), contributor_onboarding_body()).unwrap();
106
107 install_system_skills(tmp.path()).unwrap();
108
109 assert_eq!(
110 fs::read_to_string(skill_file(&tmp, "contributor-onboarding")).unwrap(),
111 contributor_onboarding_body(),
112 "upgrade must leave the installed copy untouched"
113 );
114 assert_eq!(
115 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
116 BUNDLED_SKILL_VERSION
117 );
118
119 let fresh = TempDir::new().unwrap();
120 install_system_skills(fresh.path()).unwrap();
121 assert!(
122 !skill_file(&fresh, "contributor-onboarding").exists(),
123 "fresh installs must not receive the repo-local skill"
124 );
125 }
126
127 /// Generation 13 trims `social-media` and `health` from the bundle and moves
128 /// `feedback` repo-local. None of the three may ship to new installs; a
129 /// generation-12 `feedback` copy is left in place, never deleted by name.
130 #[test]
131 fn generation_13_trims_pack_and_feedback_goes_repo_local() {
132 for name in ["social-media", "health", "feedback"] {
133 assert!(
134 !is_bundled_skill_name(name),
135 "{name} must not ship in generation 13"
136 );
137 }
138
139 let tmp = TempDir::new().unwrap();
140 fs::write(marker_file(&tmp), "12").unwrap();
141 let dir = skill_dir(&tmp, "feedback");
142 fs::create_dir_all(&dir).unwrap();
143 fs::write(dir.join("SKILL.md"), feedback_body()).unwrap();
144
145 install_system_skills(tmp.path()).unwrap();
146
147 assert_eq!(
148 fs::read_to_string(skill_file(&tmp, "feedback")).unwrap(),
149 feedback_body(),
150 "upgrade must leave the installed feedback copy untouched"
151 );
152
153 let fresh = TempDir::new().unwrap();
154 install_system_skills(fresh.path()).unwrap();
155 for name in ["social-media", "health", "feedback"] {
156 assert!(
157 !skill_file(&fresh, name).exists(),
158 "fresh installs must not receive {name}"
159 );
160 }
161 }
162
163 #[test]
164 fn fresh_install_skills_parse_for_discovery() {
165 let tmp = TempDir::new().unwrap();
166 install_system_skills(tmp.path()).unwrap();
167
168 let registry = crate::skills::SkillRegistry::discover(tmp.path());
169 assert!(
170 registry.warnings().is_empty(),
171 "bundled skills should parse cleanly: {:?}",
172 registry.warnings()
173 );
174
175 for skill in BUNDLED_SKILLS {
176 let parsed = registry
177 .get(skill.name)
178 .unwrap_or_else(|| panic!("{} should be discoverable", skill.name));
179 assert!(
180 !parsed.description.is_empty(),
181 "{} should include model-visible description",
182 skill.name
183 );
184 }
185 }
186
187 #[test]
188 fn corrupt_marker_is_repaired_without_overwriting_user_skill_body() {
189 let tmp = TempDir::new().unwrap();
190 install_system_skills(tmp.path()).unwrap();
191
192 let user_body = "user-edited body";
193 fs::write(skill_file(&tmp, "delegate"), user_body).unwrap();
194 fs::remove_dir_all(skill_dir(&tmp, "skill-creator")).unwrap();
195 fs::write(marker_file(&tmp), "not-a-version").unwrap();
196
197 install_system_skills(tmp.path()).unwrap();
198
199 assert_eq!(
200 fs::read_to_string(skill_file(&tmp, "delegate")).unwrap(),
201 user_body
202 );
203 assert!(skill_file(&tmp, "skill-creator").exists());
204 assert_eq!(
205 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
206 BUNDLED_SKILL_VERSION
207 );
208 }
209
210 #[test]
211 fn directory_marker_is_replaced_and_user_skills_are_preserved() {
212 let tmp = TempDir::new().unwrap();
213 install_system_skills(tmp.path()).unwrap();
214
215 let user_body = "user-edited body";
216 fs::write(skill_file(&tmp, "delegate"), user_body).unwrap();
217 fs::remove_dir_all(skill_dir(&tmp, "skill-creator")).unwrap();
218 fs::remove_file(marker_file(&tmp)).unwrap();
219 fs::create_dir(marker_file(&tmp)).unwrap();
220 fs::write(marker_file(&tmp).join("stale-entry"), "stale").unwrap();
221
222 install_system_skills(tmp.path()).unwrap();
223
224 assert_eq!(
225 fs::read_to_string(skill_file(&tmp, "delegate")).unwrap(),
226 user_body
227 );
228 assert!(skill_file(&tmp, "skill-creator").exists());
229 assert!(marker_file(&tmp).is_file());
230 assert_eq!(
231 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
232 BUNDLED_SKILL_VERSION
233 );
234 }
235
236 #[test]
237 fn invalid_marker_is_repaired_even_when_no_skill_body_changes() {
238 let tmp = TempDir::new().unwrap();
239 install_system_skills(tmp.path()).unwrap();
240 fs::write(marker_file(&tmp), "").unwrap();
241
242 install_system_skills(tmp.path()).unwrap();
243
244 assert_eq!(
245 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
246 BUNDLED_SKILL_VERSION
247 );
248 }
249
250 #[test]
251 fn bundled_catalog_has_two_complete_truthful_tiers() {
252 for skill in BUNDLED_SKILLS {
253 assert!(
254 bundled_skill_tier(skill.name).is_some(),
255 "{} must have a picker tier",
256 skill.name
257 );
258 }
259 assert_eq!(
260 bundled_skill_tier("best-of-n"),
261 Some(BundledSkillTier::CoreAgentic)
262 );
263 assert_eq!(
264 bundled_skill_tier("pdf"),
265 Some(BundledSkillTier::FormatTooling)
266 );
267 assert_eq!(bundled_skill_tier("user-created"), None);
268 assert!(
269 !is_bundled_skill_name("imagine"),
270 "do not advertise image generation without an image-generation tool"
271 );
272 }
273
274 // ── idempotence ───────────────────────────────────────────────────────────
275
276 #[test]
277 fn calling_twice_is_idempotent() {
278 let tmp = TempDir::new().unwrap();
279 install_system_skills(tmp.path()).unwrap();
280
281 for skill in BUNDLED_SKILLS {
282 fs::write(
283 skill_file(&tmp, skill.name),
284 format!("{}-sentinel", skill.name),
285 )
286 .unwrap();
287 }
288
289 install_system_skills(tmp.path()).unwrap();
290
291 for skill in BUNDLED_SKILLS {
292 let body = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
293 assert_eq!(
294 body,
295 format!("{}-sentinel", skill.name),
296 "second install should not overwrite {}",
297 skill.name
298 );
299 }
300 }
301
302 // ── user deleted a directory ──────────────────────────────────────────────
303
304 #[test]
305 fn user_deleted_dir_is_not_recreated() {
306 let tmp = TempDir::new().unwrap();
307 install_system_skills(tmp.path()).unwrap();
308
309 // Simulate user deliberately removing one skill directory.
310 fs::remove_dir_all(skill_dir(&tmp, "delegate")).unwrap();
311
312 // Re-launch must NOT recreate the deleted directory.
313 install_system_skills(tmp.path()).unwrap();
314
315 assert!(
316 !skill_file(&tmp, "delegate").exists(),
317 "delegate must not be recreated after user deleted it"
318 );
319 assert!(
320 skill_file(&tmp, "skill-creator").exists(),
321 "skill-creator should still be present (not deleted by user)"
322 );
323 }
324
325 #[test]
326 fn user_deleted_all_dirs_are_not_recreated() {
327 let tmp = TempDir::new().unwrap();
328 install_system_skills(tmp.path()).unwrap();
329
330 for skill in BUNDLED_SKILLS {
331 fs::remove_dir_all(skill_dir(&tmp, skill.name)).unwrap();
332 }
333
334 install_system_skills(tmp.path()).unwrap();
335
336 for skill in BUNDLED_SKILLS {
337 assert!(
338 !skill_file(&tmp, skill.name).exists(),
339 "{} must not be recreated after user deletion",
340 skill.name
341 );
342 }
343 }
344
345 // ── version bump re-installs ──────────────────────────────────────────────
346
347 #[test]
348 fn outdated_marker_triggers_reinstall_of_existing_skills() {
349 let tmp = TempDir::new().unwrap();
350 // Exact shipped bodies present with old marker: refresh is allowed and
351 // newer skills are added. Non-matching user content is preserved
352 // elsewhere (see upgrade_preserves_user_modified_bundled_skill_body).
353 for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
354 fs::create_dir_all(skill_dir(&tmp, skill.name)).unwrap();
355 fs::write(skill_file(&tmp, skill.name), skill.body).unwrap();
356 }
357 fs::write(marker_file(&tmp), "0").unwrap();
358
359 install_system_skills(tmp.path()).unwrap();
360
361 for skill in BUNDLED_SKILLS {
362 assert!(
363 skill_file(&tmp, skill.name).exists(),
364 "{} should be installed after marker upgrade",
365 skill.name
366 );
367 let content = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
368 assert_eq!(
369 content, skill.body,
370 "{} body should match shipped",
371 skill.name
372 );
373 }
374 let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
375 assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
376 }
377
378 // ── partial previous install ─────────────────────────────────────────────
379
380 #[test]
381 fn version_bump_adds_skills_introduced_after_marker() {
382 let tmp = TempDir::new().unwrap();
383 // Pre-v5 install: only skills introduced through v4, with exact bodies.
384 for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
385 fs::create_dir_all(skill_dir(&tmp, skill.name)).unwrap();
386 fs::write(skill_file(&tmp, skill.name), skill.body).unwrap();
387 }
388 fs::write(marker_file(&tmp), "4").unwrap();
389
390 install_system_skills(tmp.path()).unwrap();
391
392 for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in == 5) {
393 assert!(
394 skill_file(&tmp, skill.name).exists(),
395 "v5 skill {} should be installed on upgrade",
396 skill.name
397 );
398 }
399 // Unchanged exact bodies remain current.
400 for skill in BUNDLED_SKILLS.iter().filter(|s| s.introduced_in <= 4) {
401 let content = fs::read_to_string(skill_file(&tmp, skill.name)).unwrap();
402 assert_eq!(content, skill.body);
403 }
404 let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
405 assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
406 }
407
408 #[test]
409 fn version_bump_from_v8_adds_handoff_without_recreating_deleted_skills() {
410 let tmp = TempDir::new().unwrap();
411 fs::write(marker_file(&tmp), "8").unwrap();
412
413 install_system_skills(tmp.path()).unwrap();
414
415 assert!(skill_file(&tmp, "handoff").is_file());
416 assert!(
417 !skill_file(&tmp, "delegate").exists(),
418 "an intentionally absent older skill must stay absent"
419 );
420 assert_eq!(
421 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
422 BUNDLED_SKILL_VERSION
423 );
424 }
425
426 #[test]
427 fn version_bump_from_v5_adds_best_of_n_without_recreating_deleted_skills() {
428 let tmp = TempDir::new().unwrap();
429 fs::write(marker_file(&tmp), "5").unwrap();
430
431 install_system_skills(tmp.path()).unwrap();
432
433 assert!(skill_file(&tmp, "best-of-n").is_file());
434 assert!(
435 !skill_file(&tmp, "delegate").exists(),
436 "an intentionally absent older skill must stay absent"
437 );
438 assert_eq!(
439 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
440 BUNDLED_SKILL_VERSION
441 );
442 }
443
444 #[test]
445 fn version_bump_respects_deleted_existing_skill_while_adding_new_skill() {
446 let tmp = TempDir::new().unwrap();
447
448 // Simulate v2 where older bundled skills had been deliberately removed
449 // before later versions introduced more system skills.
450 fs::write(marker_file(&tmp), "2").unwrap();
451
452 install_system_skills(tmp.path()).unwrap();
453
454 assert!(
455 !skill_file(&tmp, "skill-creator").exists(),
456 "version bump should not recreate deleted skill-creator"
457 );
458 assert!(
459 !skill_file(&tmp, "delegate").exists(),
460 "version bump should not recreate deleted delegate"
461 );
462 for skill in BUNDLED_SKILLS
463 .iter()
464 .filter(|skill| skill.introduced_in > 2)
465 {
466 assert!(
467 skill_file(&tmp, skill.name).exists(),
468 "version bump should install newly introduced {}",
469 skill.name
470 );
471 }
472 let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
473 assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
474 }
475
476 // ── upgrade ───────────────────────────────────────────────────────────────
477
478 #[test]
479 fn upgrade_from_v4_installs_pack_and_retires_unchanged_v4_best_practices() {
480 let tmp = TempDir::new().unwrap();
481 // Simulate a v4 install: marker + legacy skill bodies.
482 fs::create_dir_all(skill_dir(&tmp, "v4-best-practices")).unwrap();
483 fs::write(
484 skill_file(&tmp, "v4-best-practices"),
485 V4_BEST_PRACTICES_BODY,
486 )
487 .unwrap();
488 fs::write(marker_file(&tmp), "4").unwrap();
489
490 install_system_skills(tmp.path()).unwrap();
491
492 assert!(
493 !skill_dir(&tmp, "v4-best-practices").exists(),
494 "unchanged v4-best-practices must be retired"
495 );
496 assert!(skill_file(&tmp, "debug").exists());
497 assert!(skill_file(&tmp, "docx").exists());
498 assert!(skill_file(&tmp, "release").exists());
499 // Feishu is optional — not auto-installed by the default pack.
500 assert!(
501 !skill_dir(&tmp, "feishu").exists(),
502 "feishu must not be universally installed"
503 );
504 let ver = fs::read_to_string(marker_file(&tmp)).unwrap();
505 assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
506 }
507
508 #[test]
509 fn upgrade_preserves_modified_v4_best_practices() {
510 let tmp = TempDir::new().unwrap();
511 fs::create_dir_all(skill_dir(&tmp, "v4-best-practices")).unwrap();
512 fs::write(
513 skill_file(&tmp, "v4-best-practices"),
514 "---\nname: v4-best-practices\ndescription: user-owned\n---\n\n# mine\n",
515 )
516 .unwrap();
517 fs::write(marker_file(&tmp), "4").unwrap();
518
519 install_system_skills(tmp.path()).unwrap();
520
521 assert!(skill_dir(&tmp, "v4-best-practices").exists());
522 let body = fs::read_to_string(skill_file(&tmp, "v4-best-practices")).unwrap();
523 assert!(
524 body.contains("user-owned"),
525 "modified body must be preserved"
526 );
527 }
528
529 #[test]
530 fn upgrade_preserves_user_modified_bundled_skill_body() {
531 let tmp = TempDir::new().unwrap();
532 install_system_skills(tmp.path()).unwrap();
533 let path = skill_file(&tmp, "debug");
534 fs::write(
535 &path,
536 "---\nname: debug\ndescription: customized\n---\n\n# custom\n",
537 )
538 .unwrap();
539 // Force version bump attempt
540 fs::write(marker_file(&tmp), "4").unwrap();
541 install_system_skills(tmp.path()).unwrap();
542 let body = fs::read_to_string(path).unwrap();
543 assert!(
544 body.contains("customized"),
545 "user edit must not be overwritten by name alone"
546 );
547 }
548
549 /// A generation-10 install carries the Registry-first `mcp-discovery` body.
550 /// The generation-11 rewrite is only real for existing users if that untouched
551 /// copy is actually replaced — the digest allowance is what makes the upgrade
552 /// reach them instead of stopping at "body differs, must be the user's".
553 #[test]
554 fn upgrade_from_generation_10_refreshes_untouched_mcp_discovery() {
555 let tmp = TempDir::new().unwrap();
556 let old = MCP_DISCOVERY_GENERATION_10_BODY;
557 let path = skill_file(&tmp, "mcp-discovery");
558 fs::create_dir_all(skill_dir(&tmp, "mcp-discovery")).unwrap();
559 fs::write(&path, old).unwrap();
560 fs::write(marker_file(&tmp), "10").unwrap();
561
562 install_system_skills(tmp.path()).unwrap();
563
564 assert_eq!(
565 fs::read_to_string(&path).unwrap(),
566 MCP_DISCOVERY_BODY,
567 "an unmodified generation-10 body must upgrade to the shipped body"
568 );
569 assert_eq!(
570 fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
571 BUNDLED_SKILL_VERSION
572 );
573 }
574
575 #[test]
576 fn upgrade_from_generation_10_preserves_user_edited_mcp_discovery() {
577 let tmp = TempDir::new().unwrap();
578 let edited = format!("{MCP_DISCOVERY_GENERATION_10_BODY}\n- my own house rule\n");
579 let path = skill_file(&tmp, "mcp-discovery");
580 fs::create_dir_all(skill_dir(&tmp, "mcp-discovery")).unwrap();
581 fs::write(&path, &edited).unwrap();
582 fs::write(marker_file(&tmp), "10").unwrap();
583
584 install_system_skills(tmp.path()).unwrap();
585
586 assert_eq!(
587 fs::read_to_string(&path).unwrap(),
588 edited,
589 "an edited copy stays the user's, even one derived from a shipped body"
590 );
591 }
592
593 #[test]
594 fn upgrade_preserves_an_intentionally_empty_mcp_discovery() {
595 let tmp = TempDir::new().unwrap();
596 let path = skill_file(&tmp, "mcp-discovery");
597 fs::create_dir_all(skill_dir(&tmp, "mcp-discovery")).unwrap();
598 fs::write(&path, "").unwrap();
599 fs::write(marker_file(&tmp), "10").unwrap();
600 install_system_skills(tmp.path()).unwrap();
601 assert_eq!(fs::read_to_string(path).unwrap(), "");
602 }
603
604 #[test]
605 fn mcp_discovery_deleted_at_generation_10_stays_deleted() {
606 let tmp = TempDir::new().unwrap();
607 fs::write(marker_file(&tmp), "10").unwrap();
608
609 install_system_skills(tmp.path()).unwrap();
610
611 assert!(
612 !skill_file(&tmp, "mcp-discovery").exists(),
613 "the digest allowance must not resurrect a skill the user removed"
614 );
615 }
616
617 /// The retained body is evidence, not decoration: it must be the superseded
618 /// text (Registry-first, `registry_sync {}`, always-present tools), never a
619 /// stale copy of the current one, or the allowance would silently do nothing.
620 #[test]
621 fn retained_generation_10_body_is_the_superseded_one() {
622 let old = MCP_DISCOVERY_GENERATION_10_BODY;
623 assert_ne!(old, MCP_DISCOVERY_BODY);
624 assert!(is_superseded_shipped_body("mcp-discovery", old));
625 assert!(!is_superseded_shipped_body(
626 "mcp-discovery",
627 MCP_DISCOVERY_BODY
628 ));
629 assert!(!is_superseded_shipped_body("debug", old));
630 // The stale generation-10 facts the rewrite exists to remove.
631 assert!(old.contains("registry_sync {}"));
632 assert!(old.contains("available in the active tool"));
633 assert!(MCP_DISCOVERY_BODY.contains("registry_sync {query:"));
634 }
635
636 #[test]
637 fn end_user_pack_skills_parse_for_discovery() {
638 let tmp = TempDir::new().unwrap();
639 install_system_skills(tmp.path()).unwrap();
640 let registry = crate::skills::SkillRegistry::discover(tmp.path());
641 assert!(
642 registry.warnings().is_empty(),
643 "bundled skills should parse cleanly: {:?}",
644 registry.warnings()
645 );
646 for name in [
647 "debug", "test", "review", "document", "docx", "release", "plan", "verify",
648 ] {
649 assert!(registry.get(name).is_some(), "{name} must be discoverable");
650 }
651 }
652
653 #[test]
654 fn procedural_skill_homes_remain_bundled_and_lazy() {
655 for name in ["debug", "best-of-n", "simplify", "verify", "test", "review"] {
656 assert!(
657 is_bundled_skill_name(name),
658 "procedural skill home must remain available on demand: {name}"
659 );
660 }
661 }
662
663 #[test]
664 fn generation_14_refreshes_known_bodies_and_preserves_customizations_and_deletions() {
665 for (name, old) in SUPERSEDED_BODIES
666 .iter()
667 .filter(|(name, _)| *name != "mcp-discovery")
668 {
669 let skill = BUNDLED_SKILLS
670 .iter()
671 .find(|skill| skill.name == *name)
672 .unwrap();
673 assert_ne!(*old, skill.body);
674 for customized in [false, true] {
675 let tmp = TempDir::new().unwrap();
676 let file = skill_file(&tmp, name);
677 fs::create_dir_all(skill_dir(&tmp, name)).unwrap();
678 let body = if customized {
679 format!("{old}\nMy instructions.\n")
680 } else {
681 old.to_string()
682 };
683 fs::write(&file, &body).unwrap();
684 fs::write(marker_file(&tmp), "13").unwrap();
685 install_system_skills(tmp.path()).unwrap();
686 assert_eq!(
687 fs::read_to_string(file).unwrap(),
688 if customized {
689 body
690 } else {
691 skill.body.to_string()
692 },
693 "{name}"
694 );
695 }
696 let tmp = TempDir::new().unwrap();
697 fs::write(marker_file(&tmp), "13").unwrap();
698 install_system_skills(tmp.path()).unwrap();
699 assert!(!skill_file(&tmp, name).exists(), "{name} must stay deleted");
700 }
701 }
702
702 lines RUST