Documentation
¶
Overview ¶
Package textpatch applies anchored edits to a text document and reports what changed, without knowing what kind of document it is. Prompts, assets, and (later) knowledge pages all hand it a body and get a body back, so one edit grammar serves every content kind.
The grammar is "find this exact text, put that text in its place": an anchor must resolve to exactly one span unless the caller opts into an occurrence, and an anchor that matches nothing or matches ambiguously aborts the whole call rather than applying a guess. Line numbers are read output only and are never accepted as an edit anchor, because a line number is stale the moment an earlier edit lands.
Index ¶
- Constants
- func AddProperties(props map[string]any)
- func ContentFields(body string, req ContentRequest) (map[string]any, error)
- func CountLines(body string) int
- func LineRange(body string, start, end int) (string, error)
- func LocateFields(body string, q LocateQuery, opts Options) (map[string]any, error)
- func OutlineFields(body string, syntax Syntax) map[string]any
- func PatchFields(res Result) map[string]any
- func PropertiesMap() map[string]any
- func StatsFields(body string) map[string]any
- func UnifiedDiff(oldBody, newBody string, context int) string
- func UnifiedDiffLabeled(oldBody, newBody, oldLabel, newLabel string, context int) string
- type ContentRequest
- type Edit
- type EditResult
- type Error
- type Landmark
- type LocateQuery
- type LocateResult
- type Match
- type Options
- type Result
- type Section
- type Stats
- type Syntax
Constants ¶
const ( // DefaultContextBytes is how much surrounding text a match reports. DefaultContextBytes = 160 // DefaultLocateLimit caps reported matches. DefaultLocateLimit = 20 )
Locate defaults.
const ( FieldSizeBytes = "size_bytes" FieldLines = "lines" FieldSections = "sections" FieldLandmarks = "landmarks" FieldHash = "hash" FieldContent = "content" FieldSection = "section" FieldCount = "count" FieldMatches = "matches" FieldTruncated = "truncated" FieldEdits = "edits" FieldDiff = "diff" )
Response field names. The verbs answer identically on every tool that adopts the grammar, so the wire keys live here beside the argument schema rather than being re-declared per toolkit, where they could drift apart.
const ( // OpReplace swaps the span matched by Find (literal) or Pattern (regex) // for Replace. An empty Replace deletes the matched span. OpReplace = "replace" // OpInsertBefore places Text immediately before the matched anchor, // leaving the anchor in place. OpInsertBefore = "insert_before" // OpInsertAfter places Text immediately after the matched anchor, // leaving the anchor in place. OpInsertAfter = "insert_after" // OpReplaceSection replaces a whole markdown section (its heading // through the next heading of the same or higher level) with Text. OpReplaceSection = "replace_section" // OpMoveSection relocates a whole section before or after another // heading, or to the start or end of the document. OpMoveSection = "move_section" // OpAppend adds Text at the end of the body; no anchor needed. OpAppend = "append" // OpPrepend adds Text at the start of the body; no anchor needed. OpPrepend = "prepend" )
Edit operations. An edit with no Op is a Replace.
const ( OccurrenceFirst = "first" OccurrenceLast = "last" OccurrenceAll = "all" )
Occurrence selectors accepted by Edit.Occurrence in addition to a 1-based decimal index. Absent means "the anchor must be unique".
const ( PositionStart = "start" PositionEnd = "end" )
Move destinations accepted by Edit.Position for OpMoveSection.
const ( CodeNoMatch = "PATCH_NO_MATCH" CodeAmbiguous = "PATCH_AMBIGUOUS" CodeStaleBase = "PATCH_STALE_BASE" CodeNotText = "PATCH_NOT_TEXT" CodeTooLarge = "PATCH_TOO_LARGE" CodeSectionNotFound = "PATCH_SECTION_NOT_FOUND" CodeBadPattern = "PATCH_BAD_PATTERN" CodeBadEdit = "PATCH_BAD_EDIT" // CodeBadSelector reports a CSS selector that does not parse. CodeBadSelector = "PATCH_BAD_SELECTOR" // CodeNoStructure reports a document whose content type has no addressable // structure, so section- and selector-scoped verbs do not apply to it. CodeNoStructure = "PATCH_NO_STRUCTURE" // CodeUnresolvedMarkup reports markup that could not be resolved into a // reliable element tree, so no element span was trusted. CodeUnresolvedMarkup = "PATCH_UNRESOLVED_MARKUP" )
Error codes. Every failure an agent can correct carries one of these, so the caller can branch without parsing prose.
const ( // DefaultMaxEdits caps how many edits one call may carry. DefaultMaxEdits = 100 // DefaultMaxPatternLen caps the length of a regex source string. RE2 // match time is linear, so this bounds compilation cost rather than // guarding against catastrophic backtracking. DefaultMaxPatternLen = 512 // DefaultMaxMatches caps how many spans a single occurrence:"all" edit // may touch. DefaultMaxMatches = 1000 )
Limits bounding a single Apply call. Zero means the package default.
const PropertiesJSON = `` /* 5206-byte string literal not displayed */
PropertiesJSON is the JSON Schema fragment for the patch, search, and navigation arguments. Every tool that adopts the grammar splices these exact properties into its own input schema, so the grammar an agent reads on manage_asset is literally the grammar it reads on manage_prompt.
const VerbsDescription = "To change part of existing content, use 'patch' with anchored edits; do not read the whole " +
"body back and resend it. To find the part to change, use 'locate' (literal or regex search reporting line " +
"numbers, enclosing section, and a copyable context window) or 'outline' (heading tree with sizes). " +
"'get_content' reads the whole body, one section, or a line range; 'stats' reports size, line count, version, " +
"and content hash without any body; 'diff' compares two versions. Anchors are text, never line numbers: an " +
"anchor that matches nothing or matches ambiguously refuses the whole call and writes nothing. The 'content' " +
"argument remains the way to do a genuine full rewrite."
VerbsDescription is the shared steering text every tool that adopts the grammar appends to its own description, so the rule reads identically wherever an agent meets it.
Variables ¶
This section is empty.
Functions ¶
func AddProperties ¶
AddProperties splices the shared grammar into a tool's schema properties.
A name collision panics rather than resolving silently: the whole point of the shared fragment is that the grammar reads identically on every tool, so a tool redefining one of these names is an authoring error that must surface at build time, not a divergence to be tolerated at runtime.
func ContentFields ¶
func ContentFields(body string, req ContentRequest) (map[string]any, error)
ContentFields renders a requested span of the document alongside the whole document's size and line count, so a caller reading one section still learns how much it did not read. The resolved heading is reported only for a section read.
func CountLines ¶
CountLines returns the number of lines in body. An empty body has zero lines; a body not ending in a newline still counts its final partial line.
func LineRange ¶
LineRange returns lines [start, end] of body, 1-based and inclusive. end may exceed the line count, in which case the rest of the body is returned.
It resolves the two byte offsets directly rather than materializing the document's lines, so reading ten lines out of a large asset stays cheap.
func LocateFields ¶
LocateFields renders every match of an anchor with the total count, which is the number that tells an agent whether the anchor is safe to patch with.
func OutlineFields ¶
OutlineFields renders the heading tree plus the document's size and line count. The section list is never nil, so it serializes as [] not null. On HTML syntax it also reports the addressable landmarks (elements carrying an id or a data-* marker), which is the whole answer for a JSX dashboard with no headings.
func PatchFields ¶
PatchFields renders the outcome of an applied or dry-run patch: the per-edit report and a unified diff of the changed hunks, never the new body.
func PropertiesMap ¶
PropertiesMap returns PropertiesJSON decoded, for tools whose input schema is built as a Go map rather than raw JSON. It panics on a malformed constant, which is a build-time authoring error rather than a runtime condition.
func StatsFields ¶
StatsFields renders size, line count, and content hash, with none of the body.
func UnifiedDiff ¶
UnifiedDiff renders the changed hunks between two bodies in unified diff format, with context unchanged lines around each hunk (0 uses the default). It returns "" when the bodies are identical.
Unified diff is the platform's output format for a change and never an input format: as output the server generates it from two known strings, which makes it the most compact accurate description of what changed and the one a model reads most fluently.
func UnifiedDiffLabeled ¶
UnifiedDiffLabeled is UnifiedDiff with the conventional ---/+++ file header, used when comparing two named versions.
Types ¶
type ContentRequest ¶
type ContentRequest struct {
Syntax Syntax
Section string
Selector string
Occurrence string
LineStart int
LineEnd int
}
ContentRequest selects which span of a document to read: the whole body when nothing is set, one named section or selector-addressed element, or an inclusive 1-based line range. Syntax names the region grammar for the section and selector forms.
type Edit ¶
type Edit struct {
Op string `json:"op,omitempty"`
Find string `json:"find,omitempty"`
Pattern string `json:"pattern,omitempty"`
Replace string `json:"replace,omitempty"`
Text string `json:"text,omitempty"`
Section string `json:"section,omitempty"`
Selector string `json:"selector,omitempty"`
Occurrence string `json:"occurrence,omitempty"`
Before string `json:"before,omitempty"`
After string `json:"after,omitempty"`
Position string `json:"position,omitempty"`
}
Edit is one anchored change. Field use depends on Op:
replace Find or Pattern -> Replace (empty Replace deletes) insert_before Find or Pattern, Text placed before the anchor insert_after Find or Pattern, Text placed after the anchor replace_section Section -> Text move_section Section, plus Before, After, or Position append/prepend Text only
Section additionally scopes the anchor search on replace, insert_before, and insert_after, which is how a phrase repeated across a document becomes unambiguous without quoting a long anchor. Selector does the same on an HTML, JSX or SVG document, naming an element by CSS selector; it is the precise form (a balanced element span) where section is the convenient one (a heading).
type EditResult ¶
type EditResult struct {
// Index is the edit's position in the request list.
Index int `json:"index"`
// Op is the effective operation that ran.
Op string `json:"op"`
// Matches is how many spans the edit changed (1 unless occurrence:"all").
Matches int `json:"matches"`
// Normalized is true when the exact anchor failed and the edit resolved
// only after CRLF and trailing-whitespace normalization.
Normalized bool `json:"normalized,omitempty"`
// Line is the 1-based line in the pre-edit body where the change landed.
Line int `json:"line"`
}
EditResult reports the outcome of one applied edit. It deliberately carries no content: the point of patching is that the body never crosses the wire.
type Error ¶
type Error struct {
Code string
// EditIndex is the 0-based index of the failing edit, or -1 when the
// failure is not attributable to one edit.
EditIndex int
Message string
Hint string
}
Error is a corrective, self-describing patch failure. Code is stable and machine-readable; Hint tells the agent how to recover.
func NotTextError ¶
NotTextError refuses a verb that only makes sense on text against binary content, rather than corrupting it or dumping it as garbage.
func StaleBaseError ¶
StaleBaseError refuses a patch whose base_version no longer matches the stored content, so a concurrent edit is never silently overwritten.
type Landmark ¶ added in v1.113.0
type Landmark struct {
// Tag is the element's tag name as written.
Tag string `json:"tag"`
// Selector is a selector that resolves to this element, ready to paste into
// a patch.
Selector string `json:"selector"`
// ID is the element's id attribute when it has one.
ID string `json:"id,omitempty"`
// Line is the 1-based line of the element's start tag.
Line int `json:"line"`
// SizeBytes is the byte size of the element's whole outer span.
SizeBytes int `json:"size_bytes"`
}
Landmark is an addressable element an agent would target on an HTML, JSX or SVG document: one carrying an id or a data-* region marker. It reports a copyable selector, so outline answers "where can I patch" for a dashboard that has no headings at all.
type LocateQuery ¶
type LocateQuery struct {
// Find is a literal anchor; Pattern is a regex. Exactly one is required.
Find string
Pattern string
// Section scopes the search to one section when set; Selector scopes it to
// one selector-addressed element on an HTML document. Occurrence
// disambiguates a selector that matches several elements.
Section string
Selector string
Occurrence string
// ContextBytes is how much text to include around each hit; 0 uses
// DefaultContextBytes.
ContextBytes int
// Limit caps the reported matches; 0 uses DefaultLocateLimit. Count
// always reports the true total up to the match cap.
Limit int
}
LocateQuery selects what to search for and how much of each hit to report.
type LocateResult ¶
type LocateResult struct {
// Count is the number of matches found, up to the match cap. Truncated
// reports whether the scan or the reported list stopped short.
Count int `json:"count"`
Matches []Match `json:"matches"`
// Truncated is true when more hits exist than the reported list carries,
// either because Limit cut the list or the match cap stopped the scan.
Truncated bool `json:"truncated,omitempty"`
}
LocateResult reports every hit for a search, and how many there were in total, which is the number that tells an agent whether an anchor is safe.
func Locate ¶
func Locate(body string, q LocateQuery, opts Options) (LocateResult, error)
Locate finds every occurrence of a literal or regex anchor and reports where each one sits, what section encloses it, and a context window wide enough to copy verbatim into a patch anchor.
The count is the point: an agent that locates first never hits PATCH_AMBIGUOUS, and an agent that wanted to change every occurrence learns how many there are before deciding.
type Match ¶
type Match struct {
// Line is the 1-based line the match starts on.
Line int `json:"line"`
// Offset is the byte offset of the match in the document.
Offset int `json:"offset"`
// Section is the heading of the innermost section containing the match,
// empty when the match sits above the first heading.
Section string `json:"section,omitempty"`
// Context is the surrounding text, wide enough to paste into a "find"
// anchor when the match alone would be ambiguous.
Context string `json:"context"`
}
Match is one hit reported by Locate: enough to copy a verbatim anchor out of the context window without reading the document.
type Options ¶
type Options struct {
// Syntax is the region grammar for section- and selector-scoped edits. The
// zero value is markdown, so an Options that sets no syntax keeps the
// markdown behavior #1033 shipped.
Syntax Syntax
// MaxEdits caps the number of edits; 0 uses DefaultMaxEdits.
MaxEdits int
// MaxPatternLen caps regex source length; 0 uses DefaultMaxPatternLen.
MaxPatternLen int
// MaxMatches caps spans touched by one occurrence:"all" edit; 0 uses
// DefaultMaxMatches.
MaxMatches int
// MaxResultBytes rejects a result body larger than this; 0 means no cap.
MaxResultBytes int
// DiffContext is the number of unchanged context lines around each diff
// hunk; 0 uses defaultDiffContext.
DiffContext int
}
Options bound one Apply call. A zero Options uses the package defaults and imposes no result-size limit.
type Result ¶
type Result struct {
// Body is the patched document.
Body string `json:"-"`
// Edits reports each edit in request order.
Edits []EditResult `json:"edits"`
// Diff is a unified diff of the changed hunks only.
Diff string `json:"diff"`
// SizeBytes is the length of the patched body.
SizeBytes int `json:"size_bytes"`
// Lines is the line count of the patched body.
Lines int `json:"lines"`
}
Result is the outcome of an Apply call.
func Apply ¶
Apply runs an ordered list of edits against body and returns the patched document with a per-edit report and a unified diff of the changed hunks.
Every edit resolves against an in-memory copy and the first failure aborts the whole call, so a partially applied patch is never written. Edits apply in order against the evolving body, which means a later edit may anchor on text an earlier edit introduced.
type Section ¶
type Section struct {
// Heading is the full heading line as written ("## Methodology").
Heading string `json:"heading"`
// Title is the heading text with its hashes and spacing removed.
Title string `json:"title"`
// Path is the ancestor chain ("Report > Methodology"), which
// disambiguates a title that repeats under different parents.
Path string `json:"path"`
// Level is 1 for "#" through 6 for "######".
Level int `json:"level"`
// Line is the 1-based line of the heading.
Line int `json:"line"`
// SizeBytes is the byte size of the whole section, heading included.
SizeBytes int `json:"size_bytes"`
// contains filtered or unexported fields
}
Section is one markdown heading and the span it owns: the heading line through the line before the next heading of the same or higher level.
func Content ¶
func Content(body string, req ContentRequest) (string, Section, error)
Content returns the requested span of body. The returned Section is populated only for a region read, so a caller can report which heading or element it resolved to.
func FindSection ¶
FindSection resolves a caller-supplied markdown section name against body's outline. The name may be the heading line ("## Methodology"), the bare title ("Methodology"), or an ancestor path ("Report > Methodology"); matching is case-insensitive. A name that matches nothing or more than one section is an error carrying the outline or the disambiguating advice.
type Stats ¶
type Stats struct {
SizeBytes int `json:"size_bytes"`
Lines int `json:"lines"`
Hash string `json:"hash"`
}
Stats is the cheap metadata of a document: enough to decide whether to read it at all, without reading any of it.
type Syntax ¶ added in v1.113.0
type Syntax int
Syntax is how a document's regions are named. It is derived from the content type, not guessed from the bytes, so the same body addressed as markdown and as HTML answers to different region grammars deliberately.
const ( // SyntaxMarkdown names regions by ATX heading. It is the zero value, so a // caller that sets no syntax keeps the markdown behavior #1033 shipped. SyntaxMarkdown Syntax = iota // SyntaxHTML names regions by HTML heading or by CSS selector, over an // element tree with balanced spans. It serves HTML, JSX, SVG and XML. SyntaxHTML // SyntaxNone has no addressable structure; only anchored edits apply. SyntaxNone )
func SyntaxForContentType ¶ added in v1.113.0
SyntaxForContentType selects the region grammar for a media type, routing through pkg/contenttype so the platform's single detection seam decides. HTML, JSX, SVG and XML get the element grammar; markdown and plain text keep ATX headings; every other textual type has no structure.