Skip to main content

ghidra_mcp/
skill_asset.rs

1//! Embed the driver skill (`skill/SKILL.md`) in the binary so an agent plugin can regenerate its
2//! committed copy from the shipped binary (single source of truth — no skill drift). The ship-guard
3//! test below fails the build if the embedded content is stubbed, emptied, or truncated rather than the
4//! authored driver skill.
5//!
6//! The skill lives INSIDE this crate rather than at a repo root, so `include_str!` needs no `..`
7//! traversal and the crate stays relocatable: `shared/ghidra-mcp` can be vendored or moved without
8//! silently embedding whatever happens to sit at the old relative path.
9
10/// The driver skill, embedded at compile time (version-locked to this binary).
11pub const DRIVER_SKILL: &str = include_str!("../skill/SKILL.md");
12
13/// The bytes a plugin's `emit-skill` subcommand writes: the canonical with its leading HTML comment header removed,
14/// so the output IS the shipped plugin copy rather than something a caller must post-process.
15///
16/// The header is maintainer-facing ("edit ONLY the canonical, this copy is generated") and is meaningless
17/// once emitted into the generated copy. Stripping happens HERE, at emit time, and never in `DRIVER_SKILL`
18/// itself - `embedded_skill_matches_disk_source` below asserts the const equals the canonical byte for
19/// byte, so stripping in the const would red that test.
20pub fn emit_bytes() -> &'static str {
21    // Find the header's closing delimiter, then resume after whatever line ending follows it.
22    // Matching the line ending explicitly (e.g. `find("\n-->\n")`) would silently fail on a CRLF
23    // checkout: the file would read `-->\r\n`, the pattern would not match, and the fallback below
24    // would emit the ENTIRE canonical including the header - a red oracle with a confusing message
25    // on a machine that differs from the author's only in `core.autocrlf`.
26    // The header is an HTML comment at offset 0, so REQUIRE that before searching for its close.
27    // Without this the `None` fallback below is illusory: on a canonical with no header whose BODY
28    // happens to contain `-->` (an HTML comment close in an example, say), `find` would hit the body
29    // occurrence and silently strip everything before it, which is the opposite of "emit as-is".
30    if !DRIVER_SKILL.starts_with("<!--") {
31        return DRIVER_SKILL;
32    }
33    match DRIVER_SKILL.find("-->") {
34        Some(i) => match DRIVER_SKILL[i..].find('\n') {
35            Some(j) => &DRIVER_SKILL[i + j + 1..],
36            // A closing delimiter with nothing after it: emit nothing rather than the header.
37            None => "",
38        },
39        // No header at all (a canonical that never had one): emit as-is rather than truncating.
40        None => DRIVER_SKILL,
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    /// Positive ship-guard: there is no placeholder file to test against, so assert the AUTHORED driver
49    /// skill is present. Fails if `skill/SKILL.md` is stubbed, emptied, or truncated (roadmap M3
50    /// ship-guard: "the binary embeds the AUTHORED skill, NOT the placeholder").
51    #[test]
52    fn embedded_skill_is_the_authored_driver_skill() {
53        assert!(
54            DRIVER_SKILL.contains("name: ghidra-re-driver"),
55            "skill frontmatter `name: ghidra-re-driver` missing"
56        );
57        assert!(
58            DRIVER_SKILL.contains("## The write loop"),
59            "`## The write loop` section missing"
60        );
61        assert!(
62            DRIVER_SKILL.contains("Judge the decompile before you trust it"),
63            "decompile-judging section missing"
64        );
65        for tool in [
66            "rename",
67            "comment",
68            "set_datatype",
69            "set_prototype",
70            "set_local",
71        ] {
72            assert!(
73                DRIVER_SKILL.contains(tool),
74                "write tool `{tool}` missing from the embedded skill"
75            );
76        }
77        assert!(
78            DRIVER_SKILL.len() > 8000,
79            "embedded skill unexpectedly small ({} bytes) — likely a stub, not the authored skill",
80            DRIVER_SKILL.len()
81        );
82    }
83
84    /// Shell-free byte-identity oracle for the EMBEDDING step: the const must equal the on-disk canonical.
85    /// It does NOT prove the emit round-trips into the plugin copy - `--emit` strips the canonical's HTML
86    /// comment header via `emit_bytes()`, so the two differ by exactly that header. The emit contract is
87    /// pinned separately by each plugin's tests/skill_emit.rs, which compares the built binary's stdout to
88    /// that plugin's committed copy. Reads the
89    /// same absolute path `include_str!` embedded (via `CARGO_MANIFEST_DIR`), so line endings (CRLF/LF)
90    /// are consistent on both sides — no shell pipe, no `diff`, no cross-platform trap.
91    #[test]
92    fn embedded_skill_matches_disk_source() {
93        let disk = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/skill/SKILL.md"))
94            .expect("read skill/SKILL.md");
95        assert_eq!(
96            DRIVER_SKILL, disk,
97            "embedded skill drifted from skill/SKILL.md"
98        );
99    }
100}