|
1 | 1 | """GCF delta encoding: only added/removed symbols for incremental delivery.""" |
2 | 2 |
|
| 3 | +from __future__ import annotations |
| 4 | + |
3 | 5 | from .constants import KIND_ABBREV |
4 | 6 | from .types import DeltaPayload |
5 | 7 |
|
@@ -52,3 +54,83 @@ def encode_delta(d: DeltaPayload) -> str: |
52 | 54 | parts.append(f"{e.source} -> {e.target} {e.edge_type}") |
53 | 55 |
|
54 | 56 | return "\n".join(parts) + "\n" |
| 57 | + |
| 58 | + |
| 59 | +def encode_delta_with_session( |
| 60 | + d: DeltaPayload, |
| 61 | + sess: "Session | None" = None, |
| 62 | +) -> str: |
| 63 | + """Encode a DeltaPayload with session dedup for added symbols. |
| 64 | +
|
| 65 | + Stacks delta and session dedup: symbols that are "added" relative to the |
| 66 | + previous payload but were transmitted in an earlier call become bare |
| 67 | + references instead of full declarations. After encoding, new symbols |
| 68 | + are recorded in the session. |
| 69 | +
|
| 70 | + Args: |
| 71 | + d: The delta payload to encode. |
| 72 | + sess: Optional session tracker. If None, falls back to encode_delta. |
| 73 | +
|
| 74 | + Returns: |
| 75 | + GCF delta-formatted text string. |
| 76 | + """ |
| 77 | + if sess is None: |
| 78 | + return encode_delta(d) |
| 79 | + |
| 80 | + from .session import Session |
| 81 | + |
| 82 | + parts: list[str] = [] |
| 83 | + |
| 84 | + savings = 0.0 |
| 85 | + if d.full_tokens > 0: |
| 86 | + savings = 100.0 * (1.0 - d.delta_tokens / d.full_tokens) |
| 87 | + |
| 88 | + parts.append( |
| 89 | + f"GCF profile=graph tool={d.tool} delta=true base_root={d.base_root} " |
| 90 | + f"new_root={d.new_root} tokens={d.delta_tokens} savings={savings:.0f}% session=true" |
| 91 | + ) |
| 92 | + |
| 93 | + # Removed symbols. |
| 94 | + if d.removed: |
| 95 | + parts.append("## removed") |
| 96 | + for s in d.removed: |
| 97 | + kind = KIND_ABBREV.get(s.kind, s.kind) |
| 98 | + parts.append(f"{kind} {s.qualified_name}") |
| 99 | + |
| 100 | + # Added symbols: session-aware. Previously transmitted become bare refs. |
| 101 | + new_symbols = [] |
| 102 | + bare_refs = [] |
| 103 | + for s in d.added: |
| 104 | + if sess.transmitted(s.qualified_name): |
| 105 | + bare_refs.append(s) |
| 106 | + else: |
| 107 | + new_symbols.append(s) |
| 108 | + |
| 109 | + if bare_refs: |
| 110 | + parts.append(f"## ref [{len(bare_refs)}]") |
| 111 | + for s in bare_refs: |
| 112 | + sid = sess.get_id(s.qualified_name) |
| 113 | + parts.append(f"@{sid} # previously transmitted") |
| 114 | + |
| 115 | + if new_symbols: |
| 116 | + parts.append("## added") |
| 117 | + for i, s in enumerate(new_symbols): |
| 118 | + kind = KIND_ABBREV.get(s.kind, s.kind) |
| 119 | + parts.append(f"@{i} {kind} {s.qualified_name} {s.score:.2f} {s.provenance}") |
| 120 | + |
| 121 | + # Removed edges. |
| 122 | + if d.removed_edges: |
| 123 | + parts.append("## edges_removed") |
| 124 | + for e in d.removed_edges: |
| 125 | + parts.append(f"{e.source} -> {e.target} {e.edge_type}") |
| 126 | + |
| 127 | + # Added edges. |
| 128 | + if d.added_edges: |
| 129 | + parts.append("## edges_added") |
| 130 | + for e in d.added_edges: |
| 131 | + parts.append(f"{e.source} -> {e.target} {e.edge_type}") |
| 132 | + |
| 133 | + # Record new symbols in session. |
| 134 | + sess.record(new_symbols) |
| 135 | + |
| 136 | + return "\n".join(parts) + "\n" |
0 commit comments