Add rewrites to mappings
diff --git a/ast/compare.go b/ast/compare.go
new file mode 100644
index 0000000..fec22b1
--- /dev/null
+++ b/ast/compare.go
@@ -0,0 +1,56 @@
+package ast
+
+import (
+	"reflect"
+)
+
+// NodesEqual compares two AST nodes for equality
+func NodesEqual(a, b Node) bool {
+	if a == nil || b == nil {
+		return a == b
+	}
+
+	if a.Type() != b.Type() {
+		return false
+	}
+
+	switch n1 := a.(type) {
+	case *Term:
+		if n2, ok := b.(*Term); ok {
+			return n1.Foundry == n2.Foundry &&
+				n1.Key == n2.Key &&
+				n1.Layer == n2.Layer &&
+				n1.Match == n2.Match &&
+				n1.Value == n2.Value
+		}
+	case *TermGroup:
+		if n2, ok := b.(*TermGroup); ok {
+			if n1.Relation != n2.Relation || len(n1.Operands) != len(n2.Operands) {
+				return false
+			}
+			for i := range n1.Operands {
+				if !NodesEqual(n1.Operands[i], n2.Operands[i]) {
+					return false
+				}
+			}
+			return true
+		}
+	case *Token:
+		if n2, ok := b.(*Token); ok {
+			return NodesEqual(n1.Wrap, n2.Wrap)
+		}
+	case *CatchallNode:
+		if n2, ok := b.(*CatchallNode); ok {
+			return n1.NodeType == n2.NodeType &&
+				reflect.DeepEqual(n1.RawContent, n2.RawContent) &&
+				NodesEqual(n1.Wrap, n2.Wrap)
+		}
+	}
+	return false
+}
+
+// IsTermNode checks if a node is a Term node
+func IsTermNode(node Node) bool {
+	_, ok := node.(*Term)
+	return ok
+}
diff --git a/mapper/mapper.go b/mapper/mapper.go
index fa75bd6..7d7b3db 100644
--- a/mapper/mapper.go
+++ b/mapper/mapper.go
@@ -74,11 +74,12 @@
 
 // MappingOptions contains the options for applying mappings
 type MappingOptions struct {
-	FoundryA  string
-	LayerA    string
-	FoundryB  string
-	LayerB    string
-	Direction Direction
+	FoundryA    string
+	LayerA      string
+	FoundryB    string
+	LayerB      string
+	Direction   Direction
+	AddRewrites bool
 }
 
 // ApplyQueryMappings applies the specified mapping rules to a JSON object
@@ -144,6 +145,19 @@
 		node = tokenWrap
 	}
 
+	// Store original node for rewrite if needed
+	var originalNode ast.Node
+	if opts.AddRewrites {
+		originalBytes, err := parser.SerializeToJSON(node)
+		if err != nil {
+			return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
+		}
+		originalNode, err = parser.ParseJSON(originalBytes)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse original node for rewrite: %w", err)
+		}
+	}
+
 	// Apply each rule to the AST
 	for _, rule := range rules {
 		// Create pattern and replacement based on direction
@@ -201,6 +215,98 @@
 		return nil, fmt.Errorf("failed to parse result JSON: %w", err)
 	}
 
+	// Add rewrites if enabled and node was changed
+	if opts.AddRewrites && !ast.NodesEqual(node, originalNode) {
+		// Create rewrite object
+		rewrite := map[string]any{
+			"@type":  "koral:rewrite",
+			"editor": "termMapper",
+		}
+
+		// Check if all terms in a group have their foundry changed
+		if term, ok := originalNode.(*ast.Term); ok {
+			if termGroup, ok := node.(*ast.TermGroup); ok {
+				// Check if all terms in the group have a different foundry
+				allFoundryChanged := true
+				for _, op := range termGroup.Operands {
+					if t, ok := op.(*ast.Term); ok {
+						if t.Foundry == term.Foundry {
+							allFoundryChanged = false
+							break
+						}
+					}
+				}
+				if allFoundryChanged {
+					rewrite["scope"] = "foundry"
+					rewrite["src"] = term.Foundry
+				} else {
+					// Full node replacement
+					originalBytes, err := parser.SerializeToJSON(originalNode)
+					if err != nil {
+						return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
+					}
+					var originalJSON any
+					if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
+						return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
+					}
+					rewrite["src"] = originalJSON
+				}
+			} else if newTerm, ok := node.(*ast.Term); ok {
+				// Single term changes
+				if term.Foundry != newTerm.Foundry {
+					rewrite["scope"] = "foundry"
+					rewrite["src"] = term.Foundry
+				} else if term.Layer != newTerm.Layer {
+					rewrite["scope"] = "layer"
+					rewrite["src"] = term.Layer
+				} else if term.Key != newTerm.Key {
+					rewrite["scope"] = "key"
+					rewrite["src"] = term.Key
+				} else if term.Value != newTerm.Value {
+					rewrite["scope"] = "value"
+					rewrite["src"] = term.Value
+				} else {
+					// No specific attribute changed, use full node replacement
+					originalBytes, err := parser.SerializeToJSON(originalNode)
+					if err != nil {
+						return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
+					}
+					var originalJSON any
+					if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
+						return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
+					}
+					rewrite["src"] = originalJSON
+				}
+			}
+		} else {
+			// Full node replacement
+			originalBytes, err := parser.SerializeToJSON(originalNode)
+			if err != nil {
+				return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
+			}
+			var originalJSON any
+			if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
+				return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
+			}
+			rewrite["src"] = originalJSON
+		}
+
+		// Add rewrite to the node
+		if resultMap, ok := resultData.(map[string]any); ok {
+			if wrapMap, ok := resultMap["wrap"].(map[string]any); ok {
+				rewrites, exists := wrapMap["rewrites"]
+				if !exists {
+					rewrites = []any{}
+				}
+				if rewritesList, ok := rewrites.([]any); ok {
+					wrapMap["rewrites"] = append(rewritesList, rewrite)
+				} else {
+					wrapMap["rewrites"] = []any{rewrite}
+				}
+			}
+		}
+	}
+
 	// Restore rewrites if they existed
 	if oldRewrites != nil {
 		if resultMap, ok := resultData.(map[string]any); ok {
diff --git a/mapper/mapper_test.go b/mapper/mapper_test.go
index 39389c9..c41f2b9 100644
--- a/mapper/mapper_test.go
+++ b/mapper/mapper_test.go
@@ -79,10 +79,11 @@
 			}`,
 		},
 		{
-			name:      "B to A direction",
+			name:      "Simple A to B mapping with rewrites",
 			mappingID: "test-mapper",
 			opts: MappingOptions{
-				Direction: BtoA,
+				Direction:   AtoB,
+				AddRewrites: true,
 			},
 			input: `{
 				"@type": "koral:token",
@@ -97,21 +98,48 @@
 			expected: `{
 				"@type": "koral:token",
 				"wrap": {
-					"@type": "koral:term",
-					"foundry": "opennlp",
-					"key": "PIDAT",
-					"layer": "p",
-					"match": "match:eq"
+					"@type": "koral:termGroup",
+					"operands": [
+						{
+							"@type": "koral:term",
+							"foundry": "opennlp",
+							"key": "PIDAT",
+							"layer": "p",
+							"match": "match:eq"
+						},
+						{
+							"@type": "koral:term",
+							"foundry": "opennlp",
+							"key": "AdjType",
+							"layer": "p",
+							"match": "match:eq",
+							"value": "Pdt"
+						}
+					],
+					"relation": "relation:and",
+					"rewrites": [
+						{
+							"@type": "koral:rewrite",
+							"editor": "termMapper",
+							"src": {
+								"@type": "koral:term",
+								"foundry": "opennlp",
+								"key": "PIDAT",
+								"layer": "p",
+								"match": "match:eq"
+							}
+						}
+					]
 				}
 			}`,
-			expectError: false,
 		},
 		{
-			name:      "Mapping with foundry override",
+			name:      "Mapping with foundry override and rewrites",
 			mappingID: "test-mapper",
 			opts: MappingOptions{
-				Direction: AtoB,
-				FoundryB:  "custom",
+				Direction:   AtoB,
+				FoundryB:    "custom",
+				AddRewrites: true,
 			},
 			input: `{
 				"@type": "koral:token",
@@ -144,11 +172,47 @@
 							"value": "Pdt"
 						}
 					],
-					"relation": "relation:and"
+					"relation": "relation:and",
+					"rewrites": [
+						{
+							"@type": "koral:rewrite",
+							"editor": "termMapper",
+							"scope": "foundry",
+							"src": "opennlp"
+						}
+					]
 				}
 			}`,
 		},
 		{
+			name:      "B to A direction",
+			mappingID: "test-mapper",
+			opts: MappingOptions{
+				Direction: BtoA,
+			},
+			input: `{
+				"@type": "koral:token",
+				"wrap": {
+					"@type": "koral:term",
+					"foundry": "opennlp",
+					"key": "PIDAT",
+					"layer": "p",
+					"match": "match:eq"
+				}
+			}`,
+			expected: `{
+				"@type": "koral:token",
+				"wrap": {
+					"@type": "koral:term",
+					"foundry": "opennlp",
+					"key": "PIDAT",
+					"layer": "p",
+					"match": "match:eq"
+				}
+			}`,
+			expectError: false,
+		},
+		{
 			name:      "Invalid mapping ID",
 			mappingID: "nonexistent",
 			opts: MappingOptions{