weavatrix-refactor-plan 0.1.1

Evidence metadata, validation profiles, and canonical fingerprints for Weavatrix refactor plans
Documentation
mod common;

use blazingly_json::Value;
use weavatrix_refactor_plan::{
    Completeness, EditPlan, GraphRevision, PlanAnnotations, PlanErrorCode, RefactorOperation,
    RefactorPlan, attach_annotations, extract_annotations,
};

#[test]
fn legacy_text_only_roundtrip_is_lossless() {
    let mut legacy = EditPlan::new("rename", vec![common::file_edit("src/a.rs")]);
    legacy.completeness = Some(Completeness::new(Completeness::PARTIAL));
    let annotations = PlanAnnotations {
        created_at: Some("2026-08-02T12:00:00Z".to_owned()),
        graph_revision: GraphRevision::Null,
        uncertain_references: Some(Vec::new()),
        not_modified: Some(Vec::new()),
        warnings: Some(Vec::new()),
        extensions: [("vendor".to_owned(), Value::String("kept".to_owned()))]
            .into_iter()
            .collect(),
        ..PlanAnnotations::default()
    };
    legacy = attach_annotations(legacy, &annotations).unwrap();

    let refactor = RefactorPlan::from_text_edit_plan(legacy.clone()).unwrap();
    assert_eq!(extract_annotations(&legacy).unwrap(), refactor.evidence);
    let restored = refactor.try_into_text_edit_plan().unwrap();
    assert_eq!(restored, legacy);
}

#[test]
fn non_modify_operations_cannot_be_mislabeled_as_legacy_text_plans() {
    let plan = RefactorPlan::new(
        "create",
        vec![RefactorOperation::Create(
            weavatrix_refactor_plan::CreateFile::new("a.rs", "x"),
        )],
    );
    let error = plan.try_into_text_edit_plan().unwrap_err();
    assert_eq!(error.code(), PlanErrorCode::NotTextOnly);
    assert_eq!(error.operation_index(), Some(0));
}

#[test]
fn annotation_attachment_rejects_nested_reserved_collisions() {
    let mut annotations = common::strict_plan(Completeness::COMPLETE).evidence;
    annotations
        .completeness_proof
        .as_mut()
        .unwrap()
        .scope
        .extensions
        .insert("kind".to_owned(), Value::String("shadow".to_owned()));
    let error = attach_annotations(
        EditPlan::new("rename", vec![common::file_edit("src/a.rs")]),
        &annotations,
    )
    .unwrap_err();
    assert_eq!(error.code(), PlanErrorCode::ExtensionConflict);
}