Summary
Every agent that uses DocxSession ends up writing two near-identical pieces of code:
- A verification pass that re-projects the document and asserts no placeholders remain, no commentary text survives, expected fill values appear in plausible positions. This is a regex zoo every time — the rules don't change between agents, only the values.
- A "show me what I changed" output that diffs before-and-after projections so the human (or another agent) can review the work. Today this requires the caller to project twice (once at session start, once at end) and run a shell diff.
Both are common enough that a first-class API would let agents write declarative verification and declarative review output.
Surfaced from the NVCA work — see ~/Code/Docxodus-Agents/AGENT_WORKFLOW.md Phase 5 (verification) and bootstrap/Verify.cs.skeleton.
Proposed Solution
1. session.GetEditSummary()
public EditSummary GetEditSummary();
public sealed record EditSummary
{
public int TotalAnchors { get; init; }
public IReadOnlyList<TemplatePlaceholder> RemainingPlaceholders { get; init; } = Array.Empty<TemplatePlaceholder>();
public IReadOnlyList<TextMatch> BareUnderscoreRuns { get; init; } = Array.Empty<TextMatch>();
public IReadOnlyList<TextMatch> UnbalancedBrackets { get; init; } = Array.Empty<TextMatch>();
public int FootnoteCount { get; init; } // excluding Word-reserved separators
public int InlineFootnoteRefCount { get; init; }
public int CommentCount { get; init; }
}
Lets verification be one call instead of multiple regex passes:
var summary = session.GetEditSummary();
Assert.Empty(summary.RemainingPlaceholders);
Assert.Empty(summary.BareUnderscoreRuns);
Assert.Equal(0, summary.FootnoteCount);
2. session.GetDiff() — markdown diff vs. initial state
public string GetDiff(DiffFormat format = DiffFormat.Unified);
public enum DiffFormat
{
Unified, // standard unified diff (git-style)
SideBySide, // two-column for human review
Json, // structured: [{ op: "delete"|"insert", anchorId, before?, after? }]
}
Compares the projection at construction time against the current projection. Stored cheaply — we already hold the initial bytes in the session's memory stream; project once at session construction time and cache.
The Json format is the agentic-friendly variant: an LLM can read the structured list and decide whether changes look right, without parsing diff syntax.
3. Convenience: session.RemainingPlaceholders()
Pure delegation to FindPlaceholders but a shorter, more discoverable name for the "am I done yet?" use case:
public IReadOnlyList<TemplatePlaceholder> RemainingPlaceholders(PlaceholderKinds kinds = PlaceholderKinds.All)
=> FindPlaceholders(kinds);
Mainly a discoverability win — agents grep for "Remaining" / "Done" / "Pending" more naturally than "FindPlaceholders."
Implementation Approach
GetEditSummary is mostly composition: it calls existing FindPlaceholders + Grep (for underscore runs and unbalanced brackets) and combines the results. The FootnoteCount / CommentCount reads come from the AnchorIndex.
GetDiff is the heavier piece. Cache the initial-state markdown projection once at DocxSession construction (cheap — ~200ms for a 100-page DOCX). On GetDiff() re-project and run a line-based diff (existing .NET libraries: DiffMatchPatch, or hand-rolled — diff against ~200KB markdown is fast).
For DiffFormat.Json, group hunks by anchor id (extract from the {#kind:scope:unid} markers that lead each block). Output:
[
{ "op": "delete", "anchorId": "p:body:abc…", "before": "Drafting Note..." },
{ "op": "modify", "anchorId": "p:body:def…", "before": "[___]", "after": "ACME, INC." },
{ "op": "insert", "anchorId": "p:body:ghi…", "after": "New paragraph text" }
]
Acceptance Criteria
Summary
Every agent that uses
DocxSessionends up writing two near-identical pieces of code:Both are common enough that a first-class API would let agents write declarative verification and declarative review output.
Surfaced from the NVCA work — see
~/Code/Docxodus-Agents/AGENT_WORKFLOW.mdPhase 5 (verification) and bootstrap/Verify.cs.skeleton.Proposed Solution
1.
session.GetEditSummary()Lets verification be one call instead of multiple regex passes:
2.
session.GetDiff()— markdown diff vs. initial stateCompares the projection at construction time against the current projection. Stored cheaply — we already hold the initial bytes in the session's memory stream; project once at session construction time and cache.
The
Jsonformat is the agentic-friendly variant: an LLM can read the structured list and decide whether changes look right, without parsing diff syntax.3. Convenience:
session.RemainingPlaceholders()Pure delegation to
FindPlaceholdersbut a shorter, more discoverable name for the "am I done yet?" use case:Mainly a discoverability win — agents grep for "Remaining" / "Done" / "Pending" more naturally than "FindPlaceholders."
Implementation Approach
GetEditSummaryis mostly composition: it calls existingFindPlaceholders+Grep(for underscore runs and unbalanced brackets) and combines the results. TheFootnoteCount/CommentCountreads come from theAnchorIndex.GetDiffis the heavier piece. Cache the initial-state markdown projection once atDocxSessionconstruction (cheap — ~200ms for a 100-page DOCX). OnGetDiff()re-project and run a line-based diff (existing .NET libraries:DiffMatchPatch, or hand-rolled — diff against ~200KB markdown is fast).For
DiffFormat.Json, group hunks by anchor id (extract from the{#kind:scope:unid}markers that lead each block). Output:[ { "op": "delete", "anchorId": "p:body:abc…", "before": "Drafting Note..." }, { "op": "modify", "anchorId": "p:body:def…", "before": "[___]", "after": "ACME, INC." }, { "op": "insert", "anchorId": "p:body:ghi…", "after": "New paragraph text" } ]Acceptance Criteria
GetEditSummaryreturns the same counts on an unedited DOCX as on a freshly-opened session of the same bytes.GetEditSummary().RemainingPlaceholders.Count == 0and all other counts are zero.GetDiff(Unified)produces a parseable unified diff thatpatch(1)can apply against the original projection.GetDiff(Json)produces one entry per modified anchor, ordered by document position.docx_mutation_api.mdadds a verification recipe;Docxodus-Agents/bootstrap/Verify.cs.skeletonshrinks toAssert.Empty(session.GetEditSummary().RemainingPlaceholders).