klasp-agents-claude 0.4.0

Claude Code agent surface for klasp — installs the PreToolUse hook that gates AI commits.
Documentation
//! The bash shim klasp drops into `.claude/hooks/klasp-gate.sh`.
//!
//! Per [docs/design.md §7], the script is intentionally three lines of work
//! around a marker comment: the schema-version export is the wire-protocol
//! handshake, and `exec klasp gate "$@"` hands off to the Rust binary. The
//! marker substring [`MANAGED_MARKER`] is the idempotency anchor — install
//! re-greps for it to decide whether the file is klasp's to touch.

/// The literal substring `install` looks for to recognise klasp's managed
/// hook script. Stable across schema bumps; the version digit follows.
pub const MANAGED_MARKER: &str = "# klasp:managed";

/// Render the hook script body for the given wire-protocol schema version.
///
/// Pure: no filesystem, no env. Used by both `install` (to write the file)
/// and `--dry-run` (to preview it).
pub fn render(schema_version: u32) -> String {
    format!(
        "#!/usr/bin/env bash\n\
         {marker} v{ver} — generated by `klasp install`. Do not edit; re-run install instead.\n\
         export KLASP_GATE_SCHEMA={ver}\n\
         exec klasp gate \"$@\"\n",
        marker = MANAGED_MARKER,
        ver = schema_version,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn render_v2_contains_marker_export_and_exec() {
        let s = render(2);
        assert!(s.starts_with("#!/usr/bin/env bash\n"));
        assert!(s.contains(MANAGED_MARKER));
        assert!(s.contains("export KLASP_GATE_SCHEMA=2\n"));
        assert!(s.contains("exec klasp gate \"$@\"\n"));
    }

    #[test]
    fn render_parameterises_schema_version() {
        let s = render(7);
        assert!(s.contains("# klasp:managed v7"));
        assert!(s.contains("export KLASP_GATE_SCHEMA=7"));
    }

    #[test]
    fn render_ends_with_newline() {
        assert!(render(1).ends_with('\n'));
    }
}