Skip to main content

eval_magic/adapters/
capabilities.rs

1//! Named code capabilities a harness descriptor references.
2//!
3//! Everything a descriptor cannot express as data — transcript stitching,
4//! slug sanitization, plugin-shadow scanning — lives behind one of these
5//! closed enums. A descriptor opts in by naming the capability
6//! (`parser = "codex-items"`); a harness whose stream is compatible with an
7//! existing capability gets the full feature from configuration alone. (The
8//! write guard needs no named capability: its install and verdict render from
9//! the descriptor's `[guard]` data via [`super::guard`].)
10//!
11//! The enums deserialize from the kebab-case capability names the
12//! `harness-descriptor` schema also enumerates, so an unknown name fails the
13//! schema gate with a listed-allowed-values message before ever reaching Rust.
14
15use std::io;
16use std::path::Path;
17
18use serde::{Deserialize, Serialize};
19
20use crate::core::ToolInvocation;
21
22use super::TranscriptSummary;
23use super::skill_shadow::PluginShadowReport;
24
25/// Transcript parsers: turn a captured CLI events file into tool invocations
26/// and a [`super::TranscriptSummary`].
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
28#[serde(rename_all = "kebab-case")]
29pub enum TranscriptParser {
30    /// `claude -p --output-format stream-json` events.
31    ClaudeStreamJson,
32    /// `codex exec --json` `item.completed` events.
33    CodexItems,
34}
35
36impl TranscriptParser {
37    /// Parse the captured events file into ordered tool invocations.
38    pub(crate) fn parse(self, path: &Path) -> io::Result<Vec<ToolInvocation>> {
39        match self {
40            TranscriptParser::ClaudeStreamJson => {
41                super::claude_code::stream_json::parse_claude_stream_json(path)
42            }
43            TranscriptParser::CodexItems => super::codex::transcript::parse_codex_events(path),
44        }
45    }
46
47    /// The full-summary counterpart of [`parse`](Self::parse).
48    pub(crate) fn parse_full(self, path: &Path) -> io::Result<TranscriptSummary> {
49        match self {
50            TranscriptParser::ClaudeStreamJson => {
51                super::claude_code::stream_json::parse_claude_stream_json_full(path)
52            }
53            TranscriptParser::CodexItems => super::codex::transcript::parse_codex_events_full(path),
54        }
55    }
56}
57
58/// Staged-slug generators, for harnesses whose naming rules need
59/// sanitization/truncation beyond a format string.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
61#[serde(rename_all = "kebab-case")]
62pub enum SlugCapability {
63    /// OpenCode's lowercase-alphanumeric-single-hyphen names, length-capped.
64    Opencode,
65}
66
67impl SlugCapability {
68    /// Generate the staged slug for one `(iteration, condition, skill)` cell.
69    pub(crate) fn staged_slug(
70        self,
71        prefix: &str,
72        iteration: u32,
73        condition: &str,
74        skill_name: &str,
75    ) -> String {
76        match self {
77            SlugCapability::Opencode => {
78                super::opencode::opencode_slug(prefix, iteration, condition, skill_name)
79            }
80        }
81    }
82}
83
84/// Shadow preflights: detect installed skills that shadow a staged slug.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
86#[serde(rename_all = "kebab-case")]
87pub enum ShadowPreflight {
88    /// Claude Code plugin/skill scan rooted at the user config dir.
89    ClaudePlugins,
90}
91
92impl ShadowPreflight {
93    /// Detect staged skill names that are also discoverable from the
94    /// operator's live environment. `None` when nothing is shadowed.
95    pub(crate) fn detect(
96        self,
97        scan_root: &Path,
98        staged_skill_names: &[&str],
99    ) -> Option<PluginShadowReport> {
100        match self {
101            ShadowPreflight::ClaudePlugins => super::claude_code::plugin_shadow::shadow_preflight(
102                &super::claude_code::plugin_shadow::config_dir_from_env(),
103                scan_root,
104                staged_skill_names,
105            ),
106        }
107    }
108}