Unify corpus and annotation rewrite entries

Change-Id: Ie2e91e76bb46a2b8b7db7b15b00b09a0188f7a59
diff --git a/ast/ast.go b/ast/ast.go
index 918a8cd..b944824 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -101,8 +101,10 @@
 	}
 }
 
-// MarshalJSON implements custom JSON marshaling to ensure clean output
-func (r *Rewrite) MarshalJSON() ([]byte, error) {
+// MarshalJSON implements custom JSON marshaling to ensure clean output.
+// Uses a value receiver so both json.Marshal(rw) and json.Marshal(&rw)
+// produce identical output including the @type field.
+func (r Rewrite) MarshalJSON() ([]byte, error) {
 	// Create a map with only the modern field names
 	result := make(map[string]any)
 
@@ -131,6 +133,34 @@
 	return json.Marshal(result)
 }
 
+// ToMap converts the Rewrite to a map[string]any suitable for direct
+// injection into map-based JSON structures. The output is identical to
+// what MarshalJSON produces.
+func (r *Rewrite) ToMap() map[string]any {
+	result := map[string]any{
+		"@type": "koral:rewrite",
+	}
+	if r.Editor != "" {
+		result["editor"] = r.Editor
+	}
+	if r.Operation != "" {
+		result["operation"] = r.Operation
+	}
+	if r.Scope != "" {
+		result["scope"] = r.Scope
+	}
+	if r.Src != "" {
+		result["src"] = r.Src
+	}
+	if r.Comment != "" {
+		result["_comment"] = r.Comment
+	}
+	if r.Original != nil {
+		result["original"] = r.Original
+	}
+	return result
+}
+
 // Token represents a koral:token
 type Token struct {
 	Wrap     Node      `json:"wrap"`