markdown

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package markdown implements the byte-preserving Markdown engine that the agent-memory project uses to read and modify .agent-memory/ files without reformatting unchanged regions.

The pattern: parse the AST only to locate byte offsets, never use it to render output. Splices are byte-level substring replacements on the original source. See docs/patterns/byte-preserving-engine.md.

This package is the production version of spike S1. The spike code lives at spikes/s1-byte-preserving-markdown/.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssignMissingIDs

func AssignMissingIDs(src []byte) ([]byte, []string, error)

AssignMissingIDs scans src for headings without an <!-- @id: ... --> anchor and injects one on the line immediately after each such heading. Returns the new bytes plus a slice of section IDs in document order (one entry per section; the value is the FINAL ID of that section, whether pre-existing or newly assigned).

Slug rules:

  • lowercase
  • alphanumeric only (a-z, 0-9); any other rune becomes a dash
  • runs of dashes collapse to one
  • leading and trailing dashes stripped
  • truncated to 64 characters (then re-trimmed if the cut left a trailing dash)
  • empty result falls back to "section" (then made unique)

Collisions are resolved by appending "-2", "-3", etc. Pre-existing anchors take precedence and reserve their ID even when a different heading would have produced the same natural slug.

AssignMissingIDs is idempotent: running it on its own output produces byte-identical bytes (the second pass sees every section already anchored and generates no splice ops).

Unchanged regions outside the inserted anchors are byte-identical to the input; the function reuses the byte-preserving Splice primitive.

func CountSections

func CountSections(src []byte) (int, error)

CountSections is a small convenience for callers that want to assert "this splice didn't accidentally drop a section" without re-doing a full ParseSections round trip themselves. Returns -1 on parse error.

func Merge3Way

func Merge3Way(base, ours, theirs []byte) (result []byte, conflicted bool, warnings []string, err error)

Merge3Way performs a section-aware three-way merge of a memory Markdown file: base is the common ancestor, ours/theirs the two sides. It exists so a git merge driver can union two branches' edits to a shared `.agent-memory/` file instead of producing whole-file conflict markers.

The file is treated as a tree of sections keyed by their `@id` anchor (or, when un-anchored, by heading text + level + occurrence). The merge is recursive and byte-preserving — each kept section's bytes are copied verbatim from the side that owns them, so nothing is reflowed:

  • present on both sides, unchanged or changed on only one side → take it;
  • added on one side only → keep it (this is the common "both branches appended a different decision/pitfall" case → clean union);
  • changed on BOTH sides differently → a `<!-- @merge-conflict -->` block wrapping both versions, and conflicted=true so the driver leaves the file unmerged for a human;
  • deleted on one side → the surviving version is kept and a warning is recorded (memory is never silently lost; both-sides-deleted is honoured).

conflicted reports whether any section needs human resolution; warnings lists non-fatal notes (kept-despite-delete). A parse failure on any input returns an error so the caller can fall back to git's default merge.

func ReplaceSection

func ReplaceSection(src []byte, sec Section, newContent []byte) ([]byte, error)

ReplaceSection is a convenience wrapper that applies one Splice op covering the section's full range. Equivalent to:

Splice(src, []SpliceOp{{ByteStart: sec.ByteStart, ByteEnd: sec.ByteEnd, Replacement: newContent}})

func Splice

func Splice(src []byte, ops []SpliceOp) ([]byte, error)

Splice applies one or more SpliceOps to src and returns the new bytes.

All ByteStart/ByteEnd values refer to the ORIGINAL src; the function internally sorts ops by ByteStart and stitches the result piece by piece, so callers don't have to worry about offsets shifting between ops.

Returns an error if:

  • any op has invalid bounds (ByteStart < 0, ByteEnd > len(src), ByteStart > ByteEnd);
  • any two ops overlap (a previous op's ByteEnd > the next op's ByteStart after sorting).

An empty ops slice returns a copy of src and no error.

func ValidateMarkdown

func ValidateMarkdown(src []byte) error

ValidateMarkdown is a thin post-splice sanity check. It parses src through goldmark and returns an error if the parser fails or returns a nil document. Goldmark accepts almost any input as best-effort Markdown, so this primarily catches programming errors (e.g., a splice that produced non-UTF-8 bytes by accident) rather than user-style malformed Markdown.

Heavier structural checks (per-category schema validation, required sections, etc.) live in internal/schema/ from T1.9 onward.

Types

type Section

type Section struct {
	HeadingText  string
	HeadingLevel int
	AnchorID     string // empty if no <!-- @id: ... --> anchor follows the heading
	Occurrence   int    // 1-based among sections with the same (HeadingText, HeadingLevel)
	ByteStart    int    // inclusive; start of the heading line
	ByteEnd      int    // exclusive; start of next heading at same/higher level, or len(src)
	ContentHash  string // "sha256:<hex>" of bytes [ByteStart, ByteEnd)
}

Section represents a Markdown section located in source bytes.

A "section" runs from a heading line (inclusive) up to the start of the next heading at the same or higher level — or to the end of the source if no such heading follows. Nested headings produce nested sections; a level-2 section therefore subsumes any level-3+ sections beneath it.

func FindByHeading

func FindByHeading(sections []Section, heading string, level, occurrence int) (*Section, bool)

FindByHeading returns a pointer to the section matching (text, level, occurrence), or (nil, false) if not found. Occurrence is 1-based; pass 1 for "first match".

func FindByID

func FindByID(sections []Section, id string) (*Section, bool)

FindByID returns a pointer to the first section with the given anchor ID, or (nil, false) if not found.

func ParseSections

func ParseSections(src []byte) ([]Section, error)

ParseSections walks the goldmark AST and returns sections with byte offsets in document order. Headings inside fenced code blocks are not returned — goldmark recognises them as code and does not emit them as headings.

Every Section's ContentHash is computed against the current src, so a caller comparing hashes across writes detects byte-level drift.

type SpliceOp

type SpliceOp struct {
	ByteStart   int
	ByteEnd     int
	Replacement []byte
}

SpliceOp describes a single byte-range substitution: replace src[ByteStart:ByteEnd] with Replacement. ByteEnd is exclusive. Replacement may be empty (delete) or longer than the original range (insert + replace).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL