use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde_json::Value;
use crate::schema::{claude_code::*, codex::*, raw_block_tag, ContentBlock};
use crate::session::{opencode_file_image_part, pi_content_has_unknown_image_shape};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Corpus {
ClaudeCode,
Codex,
Pi,
OpenCode,
Grok,
Gemini,
Goose,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Coverage {
Normalized,
Retained,
Dropped,
Unmodeled,
}
impl Coverage {
fn symbol(self) -> &'static str {
match self {
Coverage::Normalized => "✅ normalized",
Coverage::Retained => "◆ retained ",
Coverage::Dropped => "➖ dropped ",
Coverage::Unmodeled => "❌ UNMODELED ",
}
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Tally {
pub count: u64,
pub unmodeled_fields: BTreeMap<String, u64>,
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Report {
pub corpus: Option<&'static str>,
pub files: u64,
pub lines: u64,
pub parse_errors: u64,
pub records: BTreeMap<String, (Coverage, Tally)>,
pub blocks: BTreeMap<(String, Coverage), u64>,
pub tools: BTreeMap<String, u64>,
pub notes: BTreeMap<String, u64>,
}
impl Report {
fn bump(&mut self, key: String, cov: Coverage, extra: &crate::schema::ExtraFields) {
let entry = self.records.entry(key).or_insert((cov, Tally::default()));
entry.0 = cov;
entry.1.count += 1;
for k in extra.keys() {
*entry.1.unmodeled_fields.entry(k.clone()).or_insert(0) += 1;
}
}
fn bump_block(&mut self, block: &ContentBlock, raw: &Value) {
let (tag, cov) = match block.tag() {
Some(t) => (t.to_string(), block_coverage(block)),
None => (
raw_block_tag(raw).unwrap_or_else(|| "<no-type>".into()),
Coverage::Unmodeled,
),
};
*self.blocks.entry((tag, cov)).or_insert(0) += 1;
}
fn note(&mut self, key: &str) {
*self.notes.entry(key.to_string()).or_insert(0) += 1;
}
pub fn to_json(&self) -> serde_json::Value {
let cov = |c: Coverage| match c {
Coverage::Normalized => "normalized",
Coverage::Retained => "retained",
Coverage::Dropped => "dropped",
Coverage::Unmodeled => "unmodeled",
};
let records: serde_json::Map<String, serde_json::Value> = self
.records
.iter()
.map(|(k, (c, t))| {
(
k.clone(),
serde_json::json!({
"coverage": cov(*c),
"count": t.count,
"unmodeled_fields": t.unmodeled_fields.keys().collect::<Vec<_>>(),
}),
)
})
.collect();
let mut blocks_by_tag: BTreeMap<&str, Vec<serde_json::Value>> = BTreeMap::new();
for ((tag, c), n) in &self.blocks {
blocks_by_tag
.entry(tag.as_str())
.or_default()
.push(serde_json::json!({"coverage": cov(*c), "count": n}));
}
let blocks: serde_json::Map<String, serde_json::Value> = blocks_by_tag
.into_iter()
.map(|(k, v)| (k.to_string(), serde_json::Value::Array(v)))
.collect();
serde_json::json!({
"corpus": self.corpus,
"files": self.files,
"lines": self.lines,
"parse_errors": self.parse_errors,
"records": records,
"blocks": blocks,
"tools": self.tools,
"notes": self.notes,
})
}
pub fn print(&self) {
println!("# Coverage audit: {}", self.corpus.unwrap_or("?"));
println!(
"files={} lines={} parse_errors={}\n",
self.files, self.lines, self.parse_errors
);
println!("## Records (discriminant → coverage, count, unmodeled fields)");
for (key, (cov, tally)) in &self.records {
print!(" {} {:<40} {:>9}", cov.symbol(), key, tally.count);
if !tally.unmodeled_fields.is_empty() {
let mut fields: Vec<_> = tally.unmodeled_fields.keys().cloned().collect();
fields.sort();
print!(" unmodeled fields: {}", fields.join(", "));
}
println!();
}
if !self.blocks.is_empty() {
println!("\n## Content blocks");
for ((tag, cov), count) in &self.blocks {
println!(" {} {:<28} {:>9}", cov.symbol(), tag, count);
}
}
if !self.notes.is_empty() {
println!("\n## Structural notes");
for (k, v) in &self.notes {
println!(" {k}: {v}");
}
}
if !self.tools.is_empty() {
println!("\n## Tools observed (top 30 by frequency)");
let mut tools: Vec<_> = self.tools.iter().collect();
tools.sort_by(|a, b| b.1.cmp(a.1));
for (name, count) in tools.into_iter().take(30) {
println!(" {count:>9} {name}");
}
}
println!("\n## Summary of gaps (UNMODELED or dropped, non-UI)");
for (key, (cov, tally)) in &self.records {
if *cov == Coverage::Unmodeled {
println!(" ❌ {key} ({} occurrences) — not modeled", tally.count);
}
}
for ((tag, cov), count) in &self.blocks {
if *cov == Coverage::Unmodeled {
println!(" ❌ content block `{tag}` ({count}) — not modeled");
}
}
}
}
fn block_coverage(block: &ContentBlock) -> Coverage {
match block {
ContentBlock::Text { .. }
| ContentBlock::InputText { .. }
| ContentBlock::OutputText { .. }
| ContentBlock::ToolUse { .. }
| ContentBlock::ToolResult { .. } => Coverage::Normalized,
ContentBlock::Image { source } => image_source_coverage(source),
ContentBlock::InputImage { .. } | ContentBlock::Fallback { .. } => Coverage::Normalized,
ContentBlock::Thinking { .. } | ContentBlock::RedactedThinking { .. } => Coverage::Dropped,
ContentBlock::Unknown => Coverage::Unmodeled, }
}
fn image_source_coverage(source: &Value) -> Coverage {
match source.get("type").and_then(Value::as_str) {
Some("base64") => {
let mime = source
.get("media_type")
.and_then(Value::as_str)
.unwrap_or("");
let data = source.get("data").and_then(Value::as_str).unwrap_or("");
if mime.is_empty() || data.is_empty() {
Coverage::Dropped
} else {
Coverage::Normalized
}
}
Some("url") => {
let url = source.get("url").and_then(Value::as_str).unwrap_or("");
if url.is_empty() {
Coverage::Dropped
} else {
Coverage::Normalized
}
}
_ => Coverage::Dropped,
}
}
fn audit_nested_tool_result_images(content: &Value, report: &mut Report) {
let Some(items) = content.as_array() else {
return;
};
for item in items {
if item.get("type").and_then(Value::as_str) != Some("image") {
continue;
}
let cov = image_source_coverage(item.get("source").unwrap_or(&Value::Null));
*report
.blocks
.entry(("tool_result/image".to_string(), cov))
.or_insert(0) += 1;
}
}
pub fn audit_dir(dir: &Path, corpus: Corpus, limit: Option<usize>) -> Report {
let mut report = Report {
corpus: Some(match corpus {
Corpus::ClaudeCode => "claude-code",
Corpus::Codex => "codex",
Corpus::Pi => "pi",
Corpus::OpenCode => "opencode",
Corpus::Grok => "grok",
Corpus::Gemini => "gemini",
Corpus::Goose => "goose",
}),
..Default::default()
};
if corpus == Corpus::OpenCode {
if let Some((crate::session::OpenCodeStorageSurface::Sqlite, db_path)) =
crate::session::detect_opencode_storage_surface(dir)
{
return audit_opencode_sqlite(&db_path, limit, report);
}
}
if corpus == Corpus::Goose {
return audit_goose(dir, limit, report);
}
let mut files = jsonl_files(dir);
if corpus == Corpus::Grok {
files.retain(|path| {
path.file_name().and_then(|name| name.to_str()) == Some("chat_history.jsonl")
});
}
let files = match limit {
Some(n) => &files[..files.len().min(n)],
None => &files[..],
};
for path in files {
report.files += 1;
let Ok(text) = std::fs::read_to_string(path) else {
continue;
};
for line in text.lines().map(str::trim).filter(|l| !l.is_empty()) {
report.lines += 1;
match corpus {
Corpus::Codex => audit_codex_line(line, &mut report),
Corpus::ClaudeCode => audit_claude_line(line, &mut report),
Corpus::Pi => audit_pi_line(line, &mut report),
Corpus::OpenCode => audit_opencode_line(line, &mut report),
Corpus::Grok => audit_grok_line(line, &mut report),
Corpus::Gemini => audit_gemini_line(line, &mut report),
Corpus::Goose => unreachable!("Goose is audited through its SQLite store"),
}
}
}
report
}
fn audit_goose(root: &Path, limit: Option<usize>, mut report: Report) -> Report {
let catalog = crate::HarnessCatalog::new();
let discovery = catalog.discover(&crate::DiscoveryQuery {
harnesses: vec![crate::HarnessId::from(crate::HarnessId::GOOSE)],
homes: crate::HarnessHomes {
goose: root.to_path_buf(),
..crate::HarnessHomes::default()
},
limit,
..crate::DiscoveryQuery::default()
});
let descriptors = match discovery {
Ok(descriptors) => descriptors,
Err(error) => {
report.note(&format!("Goose discovery failed: {error}"));
return report;
}
};
let extra = EMPTY_EXTRA.get_or_init(Default::default);
for descriptor in descriptors {
let session = match catalog.load(&descriptor.locator) {
Ok(session) => session,
Err(error) => {
report.note(&format!(
"Goose session {} failed to load: {error}",
descriptor.locator.session_id
));
continue;
}
};
report.files += 1;
for message in session.messages {
report.lines += 1;
report.bump(
format!("message/{:?}", message.role).to_lowercase(),
Coverage::Normalized,
extra,
);
for call in message.tool_calls() {
*report.tools.entry(call.function.name.clone()).or_insert(0) += 1;
}
}
}
report
}
fn audit_opencode_sqlite(db_path: &Path, limit: Option<usize>, mut report: Report) -> Report {
match crate::session::opencode_sqlite_corpus_envelope_text(db_path, limit) {
Ok(text) => {
for line in text.lines().map(str::trim).filter(|l| !l.is_empty()) {
report.lines += 1;
if let Ok(v) = serde_json::from_str::<Value>(line) {
if v.get("key")
.and_then(Value::as_array)
.and_then(|k| k.first())
.and_then(Value::as_str)
== Some("session")
{
report.files += 1;
}
}
audit_opencode_line(line, &mut report);
}
}
Err(e) => {
report.note(&format!("opencode_sqlite_error: {e}"));
}
}
report
}
fn audit_codex_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let parsed: Result<CodexLine, _> = serde_json::from_str(line);
let Ok(parsed) = parsed else {
report.parse_errors += 1;
return;
};
match &parsed.record {
CodexRecord::Unknown => {
let tag = raw
.get("type")
.and_then(Value::as_str)
.unwrap_or("<no-type>");
report.bump(
format!("<line>/{tag}"),
Coverage::Unmodeled,
&Default::default(),
);
}
CodexRecord::ResponseItem { payload } => audit_codex_item(payload, &raw, report),
CodexRecord::EventMsg { payload } => {
let sub = payload.kind.clone().unwrap_or_else(|| "?".into());
report.bump(
format!("event_msg/{sub}"),
event_msg_coverage(&sub),
&payload.extra,
);
}
CodexRecord::SessionMeta { payload } => {
report.bump("session_meta".into(), Coverage::Retained, &payload.extra);
}
CodexRecord::TurnContext { payload } => {
report.bump("turn_context".into(), Coverage::Retained, &payload.extra);
}
CodexRecord::Compacted { .. } => {
report.bump(
"compacted".into(),
Coverage::Normalized,
&Default::default(),
);
}
}
}
fn event_msg_coverage(sub: &str) -> Coverage {
match sub {
"agent_message"
| "thread_rolled_back"
| "thread_goal_updated"
| "entered_review_mode"
| "exited_review_mode" => Coverage::Retained,
_ => Coverage::Dropped,
}
}
fn audit_codex_item(item: &ResponseItem, raw: &Value, report: &mut Report) {
let raw_payload = raw.get("payload").cloned().unwrap_or(Value::Null);
let cov = if item.is_normalized() {
Coverage::Normalized
} else if matches!(item, ResponseItem::Reasoning { .. }) {
Coverage::Retained
} else {
Coverage::Unmodeled
};
let tag = item.tag().map(str::to_string).unwrap_or_else(|| {
raw_payload
.get("type")
.and_then(Value::as_str)
.unwrap_or("<no-type>")
.to_string()
});
match item {
ResponseItem::Message {
content,
extra,
role,
} => {
report.bump(format!("response_item/message[{role}]"), cov, extra);
audit_blocks(content, &raw_payload, report);
}
ResponseItem::FunctionCall { name, extra, .. } => {
*report.tools.entry(name.clone()).or_insert(0) += 1;
report.bump("response_item/function_call".into(), cov, extra);
}
ResponseItem::FunctionCallOutput { extra, .. } => {
report.bump("response_item/function_call_output".into(), cov, extra);
}
ResponseItem::CustomToolCall { name, extra, .. } => {
if let Some(n) = name {
*report.tools.entry(n.clone()).or_insert(0) += 1;
}
report.bump("response_item/custom_tool_call".into(), cov, extra);
}
other => {
let extra = item_extra(other);
report.bump(format!("response_item/{tag}"), cov, extra);
}
}
}
fn item_extra(item: &ResponseItem) -> &crate::schema::ExtraFields {
match item {
ResponseItem::CustomToolCallOutput { extra, .. }
| ResponseItem::Reasoning { extra }
| ResponseItem::WebSearchCall { extra }
| ResponseItem::ToolSearchCall { extra }
| ResponseItem::ToolSearchOutput { extra }
| ResponseItem::ImageGenerationCall { extra } => extra,
_ => EMPTY_EXTRA.get_or_init(Default::default),
}
}
static EMPTY_EXTRA: std::sync::OnceLock<crate::schema::ExtraFields> = std::sync::OnceLock::new();
fn audit_claude_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let parsed: Result<ClaudeRecord, _> = serde_json::from_str(line);
let Ok(parsed) = parsed else {
report.parse_errors += 1;
return;
};
match &parsed {
ClaudeRecord::Unknown => {
let tag = raw
.get("type")
.and_then(Value::as_str)
.unwrap_or("<no-type>");
report.bump(
format!("<line>/{tag}"),
Coverage::Unmodeled,
&Default::default(),
);
}
ClaudeRecord::User { message, meta } | ClaudeRecord::Assistant { message, meta } => {
let role = match &parsed {
ClaudeRecord::Assistant { .. } => "assistant",
_ => "user",
};
report.bump(role.to_string(), Coverage::Normalized, &meta.extra);
if meta.is_sidechain {
report.note("sidechain (subagent) lines — flattened, not separated");
}
match &message.content {
MessageContent::Text(_) => {
report.bump_block(
&ContentBlock::Text {
text: String::new(),
},
&Value::Null,
);
}
MessageContent::Blocks(blocks) => {
let raw_blocks = raw
.get("message")
.and_then(|m| m.get("content"))
.cloned()
.unwrap_or(Value::Null);
audit_blocks(blocks, &Value::Null, report);
let _ = raw_blocks;
for b in blocks {
if let ContentBlock::ToolUse { name, .. } = b {
*report.tools.entry(name.clone()).or_insert(0) += 1;
}
}
}
}
}
ClaudeRecord::System { subtype, extra } => {
let sub = subtype.clone().unwrap_or_else(|| "?".into());
let cov = match sub.as_str() {
"scheduled_task_fire" | "local_command" | "away_summary" => Coverage::Normalized,
_ => Coverage::Dropped,
};
report.bump(format!("system/{sub}"), cov, extra);
}
other => {
let tag = other.tag().unwrap_or("?");
let cov = match tag {
"permission-mode" | "mode" | "last-prompt" | "queue-operation" | "ai-title"
| "pr-link" | "frame-link" | "agent-name" | "worktree-state" => Coverage::Dropped,
"attachment" => Coverage::Normalized,
"fork-context-ref" => Coverage::Normalized,
_ => Coverage::Unmodeled,
};
report.bump(tag.to_string(), cov, record_extra(other));
}
}
}
fn record_extra(rec: &ClaudeRecord) -> &crate::schema::ExtraFields {
match rec {
ClaudeRecord::Attachment { extra }
| ClaudeRecord::FileHistorySnapshot { extra }
| ClaudeRecord::FileHistoryDelta { extra }
| ClaudeRecord::AiTitle { extra }
| ClaudeRecord::PermissionMode { extra }
| ClaudeRecord::Mode { extra }
| ClaudeRecord::LastPrompt { extra }
| ClaudeRecord::QueueOperation { extra }
| ClaudeRecord::PrLink { extra }
| ClaudeRecord::FrameLink { extra }
| ClaudeRecord::AgentName { extra }
| ClaudeRecord::Started { extra }
| ClaudeRecord::Result { extra }
| ClaudeRecord::WorktreeState { extra }
| ClaudeRecord::ForkContextRef { extra } => extra,
_ => EMPTY_EXTRA.get_or_init(Default::default),
}
}
fn audit_blocks(blocks: &[ContentBlock], raw_payload: &Value, report: &mut Report) {
let raw_blocks = raw_payload.get("content").and_then(Value::as_array);
for (i, b) in blocks.iter().enumerate() {
let raw = raw_blocks
.and_then(|arr| arr.get(i))
.cloned()
.unwrap_or(Value::Null);
report.bump_block(b, &raw);
if let ContentBlock::ToolResult { content, .. } = b {
audit_nested_tool_result_images(content, report);
}
}
}
fn audit_pi_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let extra = EMPTY_EXTRA.get_or_init(Default::default);
let Some(ty) = raw.get("type").and_then(Value::as_str) else {
report.bump("<line>/<no-type>".to_string(), Coverage::Unmodeled, extra);
return;
};
match ty {
"session" => report.bump("session".to_string(), Coverage::Normalized, extra),
"message" => {
let message = raw.get("message");
let role = message.and_then(|m| m.get("role")).and_then(Value::as_str);
let content = message.and_then(|m| m.get("content"));
let unknown_image = matches!(role, Some("user") | Some("toolResult") | Some("custom"))
&& pi_content_has_unknown_image_shape(content);
match role {
_ if unknown_image => report.bump(
"message/UnknownImageShape".to_string(),
Coverage::Unmodeled,
extra,
),
Some("user") => {
report.bump("message/user".to_string(), Coverage::Normalized, extra)
}
Some("assistant") => {
report.bump("message/assistant".to_string(), Coverage::Normalized, extra)
}
Some("toolResult") => report.bump(
"message/toolResult".to_string(),
Coverage::Normalized,
extra,
),
Some("bashExecution") => report.bump(
"message/bashExecution".to_string(),
Coverage::Normalized,
extra,
),
Some("custom") => {
report.bump("message/custom".to_string(), Coverage::Normalized, extra)
}
Some(other) => report.bump(
format!("message/UnknownRole:{other}"),
Coverage::Unmodeled,
extra,
),
None => report.bump(
"message/UnknownRole:<none>".to_string(),
Coverage::Unmodeled,
extra,
),
}
}
"custom_message" => report.bump("custom_message".to_string(), Coverage::Normalized, extra),
"compaction" => report.bump("compaction".to_string(), Coverage::Normalized, extra),
"branch_summary" => report.bump("branch_summary".to_string(), Coverage::Normalized, extra),
"thinking_level_change" => report.bump(
"thinking_level_change".to_string(),
Coverage::Dropped,
extra,
),
"model_change" => report.bump("model_change".to_string(), Coverage::Normalized, extra),
"custom" => report.bump("custom".to_string(), Coverage::Dropped, extra),
"label" => report.bump("label".to_string(), Coverage::Dropped, extra),
"session_info" => report.bump("session_info".to_string(), Coverage::Normalized, extra),
other => report.bump(format!("<line>/{other}"), Coverage::Unmodeled, extra),
}
}
fn audit_gemini_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(value) => value,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let extra = EMPTY_EXTRA.get_or_init(Default::default);
let kind = raw.get("type").and_then(Value::as_str);
if kind.is_none() {
let key = if raw.get("sessionId").is_some_and(Value::is_string) {
"session_header"
} else if raw.get("$set").is_some() {
"metadata_update"
} else {
"<line>/<no-type>"
};
let coverage = if key == "<line>/<no-type>" {
Coverage::Unmodeled
} else {
Coverage::Retained
};
report.bump(key.to_string(), coverage, extra);
return;
}
match kind.unwrap_or_default() {
"user" | "gemini" => {
report.bump(
kind.unwrap_or_default().to_string(),
Coverage::Normalized,
extra,
);
if let Some(parts) = raw.get("content").and_then(Value::as_array) {
for part in parts {
if part.get("text").is_some() {
report.bump("content/text".into(), Coverage::Normalized, extra);
} else if part.get("inlineData").is_some() {
report.bump("content/inlineData".into(), Coverage::Normalized, extra);
} else if let Some(call) = part.get("functionCall") {
report.bump("content/functionCall".into(), Coverage::Normalized, extra);
if let Some(name) = call.get("name").and_then(Value::as_str) {
*report.tools.entry(name.to_string()).or_insert(0) += 1;
}
} else if let Some(response) = part.get("functionResponse") {
report.bump(
"content/functionResponse".into(),
Coverage::Normalized,
extra,
);
if let Some(name) = response.get("name").and_then(Value::as_str) {
*report.tools.entry(name.to_string()).or_insert(0) += 1;
}
} else {
report.bump("content/unknown".into(), Coverage::Unmodeled, extra);
}
}
} else if !raw
.get("content")
.is_some_and(|content| content.is_string() || content.is_null())
{
report.bump("content/nonstandard".into(), Coverage::Unmodeled, extra);
}
if raw.get("thoughts").is_some() {
report.bump("thoughts".into(), Coverage::Retained, extra);
}
}
"info" | "error" => report.bump(
kind.unwrap_or_default().to_string(),
Coverage::Retained,
extra,
),
other => report.bump(format!("<line>/{other}"), Coverage::Unmodeled, extra),
}
}
fn audit_grok_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(value) => value,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let extra = EMPTY_EXTRA.get_or_init(Default::default);
let Some(kind) = raw.get("type").and_then(Value::as_str) else {
report.bump("<line>/<no-type>".to_string(), Coverage::Unmodeled, extra);
return;
};
match kind {
"system" => {
let coverage = if raw.get("content").is_some_and(Value::is_string) {
Coverage::Retained
} else {
Coverage::Unmodeled
};
report.bump("system".to_string(), coverage, extra);
}
"user" => {
let (key, coverage) = classify_grok_user(&raw);
report.bump(key.clone(), coverage, extra);
audit_grok_content(raw.get("content"), &key, coverage, report);
}
"assistant" => {
let content_supported = raw
.get("content")
.is_none_or(|content| content.is_null() || content.is_string());
report.bump(
"assistant".to_string(),
if content_supported {
Coverage::Normalized
} else {
Coverage::Unmodeled
},
extra,
);
if let Some(calls) = raw.get("tool_calls").and_then(Value::as_array) {
for call in calls {
let modeled = call.get("id").is_some_and(Value::is_string)
&& call.get("name").is_some_and(Value::is_string);
report.bump(
if modeled {
"assistant/tool_call".to_string()
} else {
"assistant/tool_call:invalid".to_string()
},
if modeled {
Coverage::Normalized
} else {
Coverage::Unmodeled
},
extra,
);
if let Some(name) = call.get("name").and_then(Value::as_str) {
*report.tools.entry(name.to_string()).or_insert(0) += 1;
}
}
} else if raw.get("tool_calls").is_some() {
report.bump(
"assistant/tool_calls:non-array".to_string(),
Coverage::Unmodeled,
extra,
);
}
}
"tool_result" => {
let modeled = raw.get("tool_call_id").is_some_and(Value::is_string);
report.bump(
"tool_result".to_string(),
if modeled {
Coverage::Normalized
} else {
Coverage::Unmodeled
},
extra,
);
audit_grok_content(
raw.get("content"),
"tool_result",
Coverage::Normalized,
report,
);
}
"reasoning" | "backend_tool_call" => {
report.bump(kind.to_string(), Coverage::Dropped, extra)
}
other => report.bump(format!("<line>/{other}"), Coverage::Unmodeled, extra),
}
}
fn classify_grok_user(raw: &Value) -> (String, Coverage) {
if raw.get("synthetic_reason").and_then(Value::as_str) == Some("supercode_system_event") {
return ("user/system_event".to_string(), Coverage::Normalized);
}
let content = grok_audit_text(raw.get("content"));
let text = content.trim();
if text.starts_with("<user_info>") {
return (
"user/injected_context:user_info".to_string(),
Coverage::Dropped,
);
}
if text.starts_with("<system-reminder>") {
return (
"user/injected_context:system-reminder".to_string(),
Coverage::Dropped,
);
}
if text.is_empty() {
return ("user/empty".to_string(), Coverage::Dropped);
}
if text
.strip_prefix("<user_query>")
.and_then(|value| value.strip_suffix("</user_query>"))
.is_some_and(|value| value.trim().is_empty())
{
return ("user/empty_query".to_string(), Coverage::Dropped);
}
("user".to_string(), Coverage::Normalized)
}
fn grok_audit_text(content: Option<&Value>) -> String {
match content {
Some(Value::String(text)) => text.clone(),
Some(Value::Array(items)) => items
.iter()
.filter_map(|item| {
item.get("text")
.and_then(Value::as_str)
.or_else(|| item.as_str())
})
.collect::<Vec<_>>()
.join("\n"),
Some(other) => other.to_string(),
None => String::new(),
}
}
fn audit_grok_content(
content: Option<&Value>,
prefix: &str,
record_coverage: Coverage,
report: &mut Report,
) {
let extra = EMPTY_EXTRA.get_or_init(Default::default);
match content {
Some(Value::Array(items)) => {
for item in items {
let tag = item
.get("type")
.and_then(Value::as_str)
.unwrap_or("<no-type>");
let modeled = item.is_string() || item.get("text").is_some_and(Value::is_string);
let coverage = if record_coverage == Coverage::Dropped {
Coverage::Dropped
} else if modeled {
Coverage::Normalized
} else {
Coverage::Unmodeled
};
report.bump(format!("{prefix}/content/{tag}"), coverage, extra);
}
}
Some(_) => report.bump(format!("{prefix}/content"), record_coverage, extra),
None => report.bump(
format!("{prefix}/content:<missing>"),
if record_coverage == Coverage::Dropped {
Coverage::Dropped
} else {
Coverage::Unmodeled
},
extra,
),
}
}
const OPENCODE_KNOWN_PART_TYPES: &[&str] = &[
"text",
"reasoning",
"tool",
"file",
"step-start",
"step-finish",
"snapshot",
"patch",
"agent",
"subtask",
"retry",
"compaction",
];
const OPENCODE_KNOWN_TOOL_STATUSES: &[&str] = &["pending", "running", "completed", "error"];
fn audit_opencode_line(line: &str, report: &mut Report) {
let raw: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => {
report.parse_errors += 1;
return;
}
};
let extra = EMPTY_EXTRA.get_or_init(Default::default);
let Some(key) = raw.get("key").and_then(Value::as_array) else {
report.bump("<line>/<no-key>".to_string(), Coverage::Unmodeled, extra);
return;
};
let value = raw.get("value").cloned().unwrap_or(Value::Null);
let kind = key.first().and_then(Value::as_str).unwrap_or("<no-kind>");
match kind {
"session" => report.bump("session".to_string(), Coverage::Normalized, extra),
"message" => match value.get("role").and_then(Value::as_str) {
Some("user") => report.bump("message/user".to_string(), Coverage::Normalized, extra),
Some("assistant") => {
report.bump("message/assistant".to_string(), Coverage::Normalized, extra)
}
Some(other) => report.bump(
format!("message/UnknownRole:{other}"),
Coverage::Unmodeled,
extra,
),
None => report.bump(
"message/UnknownRole:<none>".to_string(),
Coverage::Unmodeled,
extra,
),
},
"part" => match value.get("type").and_then(Value::as_str) {
Some(t) if OPENCODE_KNOWN_PART_TYPES.contains(&t) => {
if t == "tool" {
if let Some(name) = value.get("tool").and_then(Value::as_str) {
*report.tools.entry(name.to_string()).or_insert(0) += 1;
}
match value
.get("state")
.and_then(|s| s.get("status"))
.and_then(Value::as_str)
{
Some(s) if OPENCODE_KNOWN_TOOL_STATUSES.contains(&s) => {
report.bump(format!("part/tool/{s}"), Coverage::Normalized, extra)
}
Some(other) => report.bump(
format!("part/tool/UnknownStatus:{other}"),
Coverage::Unmodeled,
extra,
),
None => report.bump(
"part/tool/UnknownStatus:<none>".to_string(),
Coverage::Unmodeled,
extra,
),
}
} else if t == "text" {
let ignored = value.get("ignored").and_then(Value::as_bool) == Some(true);
if ignored {
report.bump("part/text:ignored".to_string(), Coverage::Dropped, extra);
} else {
report.bump("part/text".to_string(), Coverage::Normalized, extra);
}
} else if t == "file" {
if opencode_file_image_part(&value).is_some() {
report.bump("part/file".to_string(), Coverage::Normalized, extra);
} else {
report.bump("part/file:residue".to_string(), Coverage::Dropped, extra);
}
} else {
let cov = match t {
"compaction" => Coverage::Normalized,
_ => Coverage::Dropped,
};
report.bump(format!("part/{t}"), cov, extra);
}
}
Some(other) => report.bump(
format!("part/UnknownType:{other}"),
Coverage::Unmodeled,
extra,
),
None => report.bump(
"part/UnknownType:<none>".to_string(),
Coverage::Unmodeled,
extra,
),
},
"session_diff" => report.bump("session_diff".to_string(), Coverage::Normalized, extra),
"todo" => report.bump("todo".to_string(), Coverage::Normalized, extra),
other => report.bump(format!("<line>/{other}"), Coverage::Unmodeled, extra),
}
}
fn jsonl_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let walker = ignore::WalkBuilder::new(dir)
.standard_filters(false)
.build();
for entry in walker.flatten() {
let p = entry.into_path();
if p.extension().and_then(|e| e.to_str()) == Some("jsonl") {
out.push(p);
}
}
out.sort();
out
}