aicx 0.9.2

Operator CLI + MCP server: canonical corpus first, optional semantic index second (Claude Code, Codex, Gemini)
Documentation
use super::support::{
    parse_artifact_frontmatter, prompt_workflow_slug, stem_workflow_slug, title_workflow_slug,
};
use super::*;
use chrono::{NaiveDate, Utc};
use std::fs;
use std::path::{Path, PathBuf};

fn tmp_dir(name: &str) -> PathBuf {
    std::env::temp_dir().join(format!(
        "aicx-reports-extractor-{name}-{}-{}",
        std::process::id(),
        Utc::now().timestamp_nanos_opt().unwrap_or_default()
    ))
}

fn write_file(path: &Path, content: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).expect("create parent dirs");
    }
    fs::write(path, content).expect("write file");
}

#[test]
fn parses_artifact_frontmatter_and_status() {
    let input = "---\nagent: codex\nrun_id: wf-001\nprompt_id: prompt-001\nstatus: completed\ncreated: 2026-04-12T20:11:06+02:00\nmode: implement\nskill_code: vc-workflow\n---\n# Report\nBody";
    let (frontmatter, body) = parse_artifact_frontmatter(input);
    let frontmatter = frontmatter.expect("frontmatter");

    assert_eq!(frontmatter.status.as_deref(), Some("completed"));
    assert_eq!(
        frontmatter.created.as_deref(),
        Some("2026-04-12T20:11:06+02:00")
    );
    assert_eq!(
        frontmatter.report.telemetry.run_id.as_deref(),
        Some("wf-001")
    );
    assert_eq!(
        frontmatter.report.steering.skill_code.as_deref(),
        Some("vc-workflow")
    );
    assert_eq!(body, "# Report\nBody");
}

#[test]
fn build_reports_explorer_merges_markdown_and_meta_and_keeps_meta_only_runs() {
    let root = tmp_dir("merge-meta");
    let repo_root = root.join("VetCoders").join("ai-contexters");
    let report_path = repo_root
        .join("2026_0412")
        .join("reports")
        .join("20260412_feature_codex.md");
    let meta_path = repo_root
        .join("2026_0412")
        .join("reports")
        .join("20260412_feature_codex.meta.json");
    let launching_meta = repo_root
        .join("2026_0411")
        .join("marbles")
        .join("reports")
        .join("20260411_1316_marbles-ancestor_L1_codex.meta.json");
    let transcript = repo_root
        .join("2026_0411")
        .join("marbles")
        .join("reports")
        .join("20260411_1316_marbles-ancestor_L1_codex.transcript.log");

    write_file(
        &report_path,
        "---\nagent: codex\nrun_id: wf-20260412-001\nprompt_id: report-artifacts\nstatus: completed\ncreated: 2026-04-12T20:11:06+02:00\nskill_code: vc-workflow\n---\n# Report Artifacts Dashboard\n## Findings\n- build static HTML\n",
    );
    write_file(
        &meta_path,
        r#"{
  "status": "completed",
  "agent": "codex",
  "run_id": "wf-20260412-001",
  "prompt_id": "report-artifacts",
  "skill_code": "impl",
  "duration_s": 12.5
}"#,
    );
    write_file(
        &launching_meta,
        &r#"{
  "status": "launching",
  "agent": "codex",
  "run_id": "marb-131611-001",
  "prompt_id": "marbles-ancestor_L1_20260411",
  "transcript": "__TRANSCRIPT__"
}"#
        .replace("__TRANSCRIPT__", &transcript.display().to_string()),
    );
    write_file(&transcript, "[13:16:11] assistant: booting artifact scan\n");

    let config = ReportsExtractorConfig {
        artifacts_root: root.clone(),
        org: "VetCoders".to_string(),
        repo: "ai-contexters".to_string(),
        date_from: Some(NaiveDate::from_ymd_opt(2026, 4, 11).expect("date")),
        date_to: Some(NaiveDate::from_ymd_opt(2026, 4, 12).expect("date")),
        workflow: None,
        title: "AICX Reports Explorer".to_string(),
        preview_chars: 120,
        deterministic: false,
    };

    let artifact = build_reports_explorer(&config).expect("build reports explorer");
    let payload: ReportsExplorerPayload =
        serde_json::from_str(&artifact.bundle_json).expect("parse bundle");

    assert_eq!(payload.records.len(), 2);
    assert!(
        payload
            .records
            .iter()
            .any(|record| record.has_markdown && record.has_meta)
    );
    assert!(
        payload
            .records
            .iter()
            .any(|record| !record.has_markdown && record.has_meta)
    );
    assert!(
        payload
            .records
            .iter()
            .any(|record| record.workflow == "report-artifacts")
    );
    assert!(
        payload
            .records
            .iter()
            .all(|record| record.workflow != "day-root")
    );
    assert!(artifact.html.contains("Workflow Report Explorer"));
    assert!(artifact.html.contains("Import JSON Bundle"));

    let _ = fs::remove_dir_all(&root);
}

#[test]
fn build_reports_explorer_does_not_read_transcripts_outside_repo_root() {
    let root = tmp_dir("outside-transcript");
    let repo_root = root.join("VetCoders").join("ai-contexters");
    let meta_path = repo_root
        .join("2026_0411")
        .join("reports")
        .join("20260411_external_transcript.meta.json");
    let outside = tmp_dir("outside-transcript-secret").join("secret.log");

    write_file(&outside, "outside secret that must not be imported\n");
    write_file(
        &meta_path,
        &r#"{
  "status": "launching",
  "agent": "codex",
  "run_id": "marb-outside-transcript",
  "prompt_id": "outside-transcript",
  "transcript": "__TRANSCRIPT__"
}"#
        .replace("__TRANSCRIPT__", &outside.display().to_string()),
    );

    let config = ReportsExtractorConfig {
        artifacts_root: root.clone(),
        org: "VetCoders".to_string(),
        repo: "ai-contexters".to_string(),
        date_from: None,
        date_to: None,
        workflow: None,
        title: "AICX Reports Explorer".to_string(),
        preview_chars: 120,
        deterministic: false,
    };

    let artifact = build_reports_explorer(&config).expect("build reports explorer");
    let payload: ReportsExplorerPayload =
        serde_json::from_str(&artifact.bundle_json).expect("parse bundle");
    let record = payload.records.first().expect("record");

    assert!(!record.has_transcript);
    assert!(record.transcript_path.is_none());
    assert!(!record.detail_text.contains("outside secret"));

    let _ = fs::remove_dir_all(&root);
    let _ = fs::remove_dir_all(outside.parent().expect("outside parent"));
}

#[test]
fn infers_day_root_workflow_from_prompt_ids_and_file_stems() {
    assert_eq!(
        prompt_workflow_slug(Some("report-artifacts-dashboard_20260412")).as_deref(),
        Some("report-artifacts-dashboard")
    );
    assert_eq!(
        stem_workflow_slug(Path::new(
            "/tmp/20260412_2031_report-artifacts-dashboard_codex.md"
        ))
        .as_deref(),
        Some("report-artifacts-dashboard")
    );
    assert_eq!(
        title_workflow_slug("Examination: report artifacts dashboard").as_deref(),
        Some("examination-report-artifacts-dashboard")
    );
}