Documentation
¶
Overview ¶
Package bage is the public, curated facade for Båge — the bidirectional code-graph round-trip file editor. It is the only import surface consumers (Hylla's cross-store coordinator, IDEs, standalone tools) should depend on; the internal/* packages are implementation detail and are deliberately not re-exported beyond the types and behavior gathered here.
Files are the source of truth ¶
Båge treats the files on disk as authoritative. A graph (when present) is a projection of the files, never the reverse. Every edit is addressed by a Region — a (byte range, line/col range, region_hash) locator that is trusted only while the live file still contains the anchored content; on drift Båge rejects or re-grounds rather than silently misapplying an edit.
Region-anchored editing ¶
An Edit targets a Region by its region_hash — the xxHash %016x of the region's RAW bytes (HYLLA_NODE_CONTRACT.md §1, §2; SPEC §8.1). The hash is the content anchor and gives omp-parity with Hylla's per-node locator bundle (the same region_hash is the only seam, so file-mode and graph-mode resolve identically) and is the basis of concurrency safety. Resolution (ADR-0003) is layered:
- region_hash VERIFIES: the bytes at the region's own offset are the block the edit targets when their hash matches (an Exact, in-place resolve).
- the CST RELOCATES: when the in-place hash no longer matches, Båge reparses the live file and matches the region_hash against every node. Exactly one match is a benign concurrent shift, re-resolved to the new offset (Shifted).
- identity DISAMBIGUATES — and Båge declines: zero matches is a conflict and more than one match is ambiguous; both are hard rejects. Båge over-rejects on purpose: corruption is never acceptable, a rejected edit is.
A FileAnchor (per file: RawHash gates byte-offset validity, NormHash classifies whitespace-only drift) accompanies the edits as the file-level drift gate.
The FILE-LEG two-phase contract ¶
Båge owns only the FILE leg of an edit. The two-phase protocol is:
Prepare(ctx, edits, anchors) -> *Plan // optimistic: resolve + stage + WAL Commit(plan) -> []EditResult // atomic: resolve-under-lock + write Rollback(plan) // discard staged edits
Prepare is OPTIMISTIC and holds no lock: it reads each live file, resolves every edit against those bytes (rejecting a Conflict/Ambiguous with a *ConflictError, matchable via errors.Is(err, ErrConflict)), preview-splices, runs the optional Formatter/Linter, reparses to confirm the result still parses, and durably records a write-ahead-log intent. Prepare never mutates a source file — its sole on-disk effect is the WAL record.
Commit is the ATOMIC, lossless point. Per file, UNDER A PER-FILE LOCK, it RE-READS the live bytes and RE-RESOLVES every edit (resolve-under-lock, so a concurrent commit that benignly shifted a region is picked up and the edit lands at the current offset, never the stale one; a same-region conflict is rejected), atomic-writes, and computes one EditResult per edit — the write-back contract (changed byte range, recomputed region/file hashes, new line range) Hylla reads to re-ingest only the changed region (SPEC §8.2). Same-file commits serialize on one lock; cross-file commits take different locks and run in parallel.
Standalone vs integrated use ¶
In standalone mode Båge is a pure file/LSP edit engine with no graph: callers typically use Apply (Prepare-then-Commit) for a one-shot edit. In integrated mode Hylla's cross-store coordinator drives Prepare/Commit/Rollback on the FILE leg interleaved with its own graph leg, so a single agent-facing edit lands in both the graph and the files as an all-or-nothing operation with no drift between them. The coordinator — not Båge — sequences the two legs; Båge exposes only the FILE-leg verbs.
Recover is the crash path ¶
Recover replays any WAL intent left behind by a crash between Prepare and Commit, restoring the affected files to their pre-Prepare state so the files converge back to a consistent, committed state. A clean Commit leaves nothing to replay, so Recover is then a no-op.
Index ¶
- Constants
- Variables
- func NormHash(h Hasher, raw []byte) string
- func Normalize(b []byte) []byte
- func RawHash(h Hasher, raw []byte) string
- func RegionHash(src []byte, start, end int) string
- func ResolveRange(src []byte, line int, lines string, start, end int) (region.Region, error)
- type BatchResult
- type Block
- type CmdFormatter
- type CmdLinter
- type Config
- type ConflictError
- type DeleteResult
- type Edit
- type EditResult
- type Editor
- func (e *Editor) Apply(ctx context.Context, edits []Edit, anchors []FileAnchor) ([]EditResult, error)
- func (e *Editor) ApplyBatch(ctx context.Context, ops []Op) ([]BatchResult, error)
- func (e *Editor) Close() error
- func (e *Editor) Commit(plan *Plan) ([]EditResult, error)
- func (e *Editor) Create(ctx context.Context, op Op) (EditResult, error)
- func (e *Editor) Delete(ctx context.Context, op Op) (DeleteResult, error)
- func (e *Editor) Move(ctx context.Context, op Op) (MoveResult, error)
- func (e *Editor) Parser() ParserPort
- func (e *Editor) Prepare(ctx context.Context, edits []Edit, anchors []FileAnchor) (*Plan, error)
- func (e *Editor) Read(ctx context.Context, path string, opts ReadOptions) (ReadResult, error)
- func (e *Editor) Recover(ctx context.Context) error
- func (e *Editor) Rename(ctx context.Context, file string, line, col uint32, newName string) (*Plan, error)
- func (e *Editor) Rollback(plan *Plan) error
- type ErrorEnvelope
- type FileAnchor
- type Formatter
- type Hasher
- type Kind
- type Lang
- type Linter
- type MoveResult
- type Node
- type Op
- type OpKind
- type OpenedFile
- type ParseDefect
- type ParserPort
- type Plan
- type ReadOptions
- type ReadResult
- type Region
- type Symbol
- type Tree
- type XXHasher
Examples ¶
Constants ¶
const ( // OpCreate is a create-from-non-existence op: bring a new file into being, // rejecting if the path already exists. OpCreate = session.OpCreate // OpDelete is a delete op: unlink an existing file gated by its expected // raw_hash, capturing the prior bytes for restore. OpDelete = session.OpDelete // OpMove is a relocate op: anchored delete of the source plus anchored create // of the destination as one atomic-on-recovery unit. OpMove = session.OpMove // OpEdit is a region-anchored edit op carried as an Op so a heterogeneous // ApplyBatch can mix edits with create/delete/move. OpEdit = session.OpEdit )
Re-exported file-lifecycle op kinds. Each tags an Op for Create/Delete/Move/ ApplyBatch; see session for the canonical definitions.
const ( LangUnknown = parser.LangUnknown LangGo = parser.LangGo LangTypeScript = parser.LangTypeScript LangTSX = parser.LangTSX LangJavaScript = parser.LangJavaScript LangPython = parser.LangPython LangRust = parser.LangRust LangJava = parser.LangJava LangC = parser.LangC LangCPP = parser.LangCPP LangCSharp = parser.LangCSharp LangRuby = parser.LangRuby LangJSON = parser.LangJSON LangHTML = parser.LangHTML LangCSS = parser.LangCSS LangYAML = parser.LangYAML LangTOML = parser.LangTOML LangXML = parser.LangXML LangMakefile = parser.LangMakefile LangBash = parser.LangBash LangMarkdown = parser.LangMarkdown // LangText is the grammar-free fallback: any file type with no registered // grammar opens and round-trips losslessly under it. LangText = parser.LangText )
Re-exported language constants. Each names a tree-sitter grammar the parser adapter can select; see parser for the canonical definitions.
const ( // KindConflict marks a region-anchored edit that could not be resolved against // the live file (concurrent change or ambiguous twins). KindConflict = session.KindConflict // KindDrift marks a raw_hash drift reject: the live bytes no longer match the // expected anchor the caller saw. KindDrift = session.KindDrift // KindExists marks a create rejected because the target path already exists. KindExists = session.KindExists // KindNotFound marks an op rejected because the target path does not exist. KindNotFound = session.KindNotFound // KindUsage marks a caller/usage error (bad arguments or invalid request). KindUsage = session.KindUsage // KindIO marks the default I/O or otherwise unclassified failure. KindIO = session.KindIO )
Re-exported error-classification kinds. Each names a stable Kind a host can switch on; see session for the canonical definitions.
Variables ¶
var ErrConflict = session.ErrConflict
ErrConflict is the sentinel wrapped by every ConflictError, matchable with errors.Is without inspecting the path. See session.ErrConflict.
var ErrExists = session.ErrExists
ErrExists is the sentinel returned when Create (or a Move destination) targets a path that already exists: Båge never clobbers, so callers match the reject with errors.Is(err, ErrExists). See session.ErrExists.
var ErrNotFound = session.ErrNotFound
ErrNotFound is the sentinel returned when Delete or Move targets a path that does not exist: there is nothing to delete/move, distinct from a drift reject. Callers match it with errors.Is(err, ErrNotFound). See session.ErrNotFound.
Functions ¶
func NormHash ¶
NormHash returns the digest of Normalize(raw) — the content anchor used for file_norm_hash, encoded as 16-char zero-padded lowercase hex by h. Pass XXHasher{} to match the contract.
func Normalize ¶
Normalize applies Båge's canonical content-normalization rule and returns a new slice: drop all carriage returns, strip trailing horizontal whitespace per line, then strip ALL leading BOMs LAST (the idempotency fixpoint). Hylla MUST use this exact rule so the normalized hashes agree cross-system — it is the single source of truth for the contract (HYLLA_NODE_CONTRACT §4).
func RawHash ¶
RawHash returns the digest of the RAW bytes (gates byte-offset validity), encoded as 16-char zero-padded lowercase hex by h. Pass XXHasher{} to match the contract.
func RegionHash ¶
RegionHash returns the region_hash for src[start:end]: the normalized-bytes digest (XXHasher %016x) that anchors a region by content, byte-identical to what Hylla stores per node (HYLLA_NODE_CONTRACT §4). This is the cross-system identity/conflict anchor; Hylla computes the same string for the same bytes.
func ResolveRange ¶ added in v0.3.0
ResolveRange builds a region-anchored target over src from one addressing mode. Exactly one mode must be supplied: a single line (line >= 1), a 1-based inclusive line range (lines = "L1-L2"), or a raw byte range (start and end both >= 0). The unset sentinel for line, start, and end is -1, matching the cmd/bage --line/--start/--end flag defaults.
Line addressing is resolved to a concrete byte range against src via a region.LineIndex. A resolved line range spans THROUGH the final line's trailing newline; that newline is excluded so a replacement preserves line structure (a final line with no trailing newline is left as-is). Supplying more than one mode, or no mode at all, returns an error.
Types ¶
type BatchResult ¶ added in v0.2.0
type BatchResult = session.BatchResult
BatchResult is the per-op outcome a host reads after a successful ApplyBatch, in input order, so it can map each op to its graph effect. Exactly one inner result field is populated, selected by Kind. See session.BatchResult.
type Block ¶ added in v0.3.0
type Block struct {
// Kind is the grammar node kind (e.g. "function_declaration"), "line" for the
// text fallback, or "range" for a line/byte-addressed read.
Kind string `json:"kind" toon:"kind"`
// Name is the declared identifier, best-effort; "" when none was found.
Name string `json:"name" toon:"name"`
// StartLine is the 1-based start line of the block.
StartLine int `json:"start_line" toon:"start_line"`
// EndLine is the 1-based end line of the block.
EndLine int `json:"end_line" toon:"end_line"`
// StartByte is the inclusive start byte offset of the block.
StartByte int `json:"start_byte" toon:"start_byte"`
// EndByte is the exclusive end byte offset of the block.
EndByte int `json:"end_byte" toon:"end_byte"`
// RegionHash anchors the block by content; see region.HashRegion.
RegionHash string `json:"region_hash" toon:"region_hash"`
// Content is the raw source for the block's byte range; "" unless requested.
Content string `json:"content,omitempty" toon:"content,omitempty"`
}
Block is one Outline Symbol enriched with its content anchor and, optionally, its raw bytes. It is a FLAT struct (deliberately not an embed) so both JSON and TOON render it cleanly: JSON keys stay snake_case with no leaked Go field names or nested object, and a slice of Blocks is a uniform array that TOON emits in its compact tabular form (the token win). RegionHash is the region_hash that anchors the block by content (byte-identical to what Hylla stores per node); Content is the source slice for the block's byte range, populated only when ReadBlocks is asked to include it.
func ReadBlocks ¶ added in v0.3.0
func ReadBlocks(opened *OpenedFile, includeContent bool) []Block
ReadBlocks returns one Block per Outline Symbol of opened, in source order: every named declaration for a grammar-backed tree, or one line block for the grammar-free text fallback. Each Block carries the region_hash for its byte range (region.HashRegion over the opened source) so a host can anchor edits by content. When includeContent is true each Block.Content is set to the raw source bytes for its range (bounds-guarded as in directName); when false Content stays empty so callers can list structure cheaply.
type CmdFormatter ¶
type CmdFormatter = format.CmdFormatter
CmdFormatter is an exec-backed Formatter that shells out to a configured command. See format.CmdFormatter.
type CmdLinter ¶
CmdLinter is an exec-backed Linter that shells out to a configured command. See format.CmdLinter.
type Config ¶
type Config struct {
// Lang is the source language the parser uses; required.
Lang Lang
// Hasher computes region/file digests; defaults to XXHasher{} when nil.
Hasher Hasher
// Formatter, when non-nil, rewrites staged bytes before linting/parsing.
Formatter Formatter
// Linter, when non-nil, blocks the edit on a lint failure.
Linter Linter
// WALDir is the directory holding the write-ahead log; required.
WALDir string
// LSPCommand is the language-server command (argv) used by Rename; optional.
LSPCommand []string
}
Config configures an Editor. WALDir and Lang are required; Hasher defaults to XXHasher{} when nil. Formatter and Linter are optional pipeline steps run over the staged bytes. LSPCommand names the language-server command (argv) used by Rename; it may be empty when rename is not needed.
type ConflictError ¶
type ConflictError = session.ConflictError
ConflictError reports that a region-anchored edit could not be resolved against the live file — the target's region_hash matches no live node (a concurrent edit changed the same region) or matches more than one (ambiguous twins). Båge rejects rather than guesses (SPEC §8.3, §8.4, ADR-0003). See session.ConflictError.
type DeleteResult ¶ added in v0.2.0
type DeleteResult = session.DeleteResult
DeleteResult is the success signal a host reads after a Delete to close that file's node versions: the deleted path and the confirmed raw_hash the live bytes matched at unlink time. See session.DeleteResult.
type Edit ¶
Edit is a region-anchored edit: replace the bytes of a Region with NewText (SPEC §8.1). The model echoes a shown region_hash; it never resends old text. See region.Edit.
type EditResult ¶
type EditResult = region.EditResult
EditResult is the write-back contract returned to Hylla after a commit: the changed byte range plus the recomputed region/file hashes and new line range, so Hylla re-ingests only the changed region (SPEC §8.2). See region.EditResult.
type Editor ¶
type Editor struct {
// contains filtered or unexported fields
}
Editor is the configured FILE-LEG edit engine: the public handle wrapping a region-anchored session, a shared parser, and (lazily, per Rename) a language-server client. It is the behavior facade consumers drive; data types are re-exported as aliases above.
func Open ¶
Open validates cfg and wires an Editor: a tree-sitter parser as the ParserPort and a session.Session over the configured WALDir, Hasher, Lang, Formatter, and Linter. WALDir is required; a nil Hasher defaults to XXHasher{}. Lang is OPTIONAL: when LangUnknown (the zero value) each file's language is auto-detected from its path via LangForPath, so an agent IDE can open a mixed-language tree; when set it forces that language for every file.
func (*Editor) Apply ¶
func (e *Editor) Apply(ctx context.Context, edits []Edit, anchors []FileAnchor) ([]EditResult, error)
Apply is the standalone convenience for a one-shot edit: it Prepares the edits and, on success, immediately Commits the resulting Plan, returning the EditResults. Integrated callers that interleave the FILE leg with a graph leg use Prepare/Commit directly so the coordinator controls the commit point.
Example ¶
ExampleEditor_Apply opens an Editor over a temp WALDir with the default XXHasher and no formatter, then applies a single region-anchored edit to a temp Go file. The edit targets the byte range covering "hi" and carries the matching region_hash so Resolve verifies the content before splicing. It is fully hermetic: no language server and no container are involved.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/hylla-io/bage/internal/hashing"
"github.com/hylla-io/bage/internal/region"
"github.com/hylla-io/bage/pkg/bage"
)
func main() {
dir, err := os.MkdirTemp("", "bage-example-*")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)
src := "package main\n\nvar Greeting = \"hi\"\n"
file := filepath.Join(dir, "greeting.go")
if err := os.WriteFile(file, []byte(src), 0o644); err != nil {
panic(err)
}
ed, err := bage.Open(bage.Config{
Lang: bage.LangGo,
WALDir: dir,
})
if err != nil {
panic(err)
}
defer ed.Close()
// Anchor the edit to the byte range covering "hi" with its region_hash, so
// the resolver verifies the targeted content before applying "hello".
start := len("package main\n\nvar Greeting = \"")
end := start + len("hi")
live := []byte(src)
hasher := hashing.XXHasher{}
edit := bage.Edit{
Region: bage.Region{
Path: file,
StartByte: start,
EndByte: end,
RegionHash: region.HashRegion(live, start, end),
},
NewText: "hello",
}
anchor := bage.FileAnchor{
Path: file,
RawHash: hashing.RawHash(hasher, live),
NormHash: hashing.NormHash(hasher, live),
}
results, err := ed.Apply(context.Background(), []bage.Edit{edit}, []bage.FileAnchor{anchor})
if err != nil {
panic(err)
}
out, err := os.ReadFile(file)
if err != nil {
panic(err)
}
fmt.Printf("changed [%d:%d] new lines %d-%d\n", results[0].ChangedStart, results[0].ChangedEnd, results[0].NewStartLine, results[0].NewEndLine)
fmt.Print(string(out))
}
Output: changed [30:35] new lines 3-3 package main var Greeting = "hello"
func (*Editor) ApplyBatch ¶ added in v0.2.0
ApplyBatch applies a heterogeneous []Op (Edit + Create + Delete + Move) as ONE all-or-nothing change a host maps to ONE graph mutation, returning one BatchResult per op in input order. If ANY op fails its anchor (region_hash/ raw_hash drift, clobber, parse/lint floor) or fails to apply, the ENTIRE batch is rejected and the filesystem is left EXACTLY as before — no op half-applied; a crash mid-commit converges via Recover to fully-before or fully-after. An empty batch is a hard reject. It delegates to the session batch leg.
func (*Editor) Close ¶
Close releases the Editor's resources. The parser and session hold no long-lived handles between edits (the LSP client is per-Rename), so Close is currently a no-op kept for forward-compatible lifecycle management.
func (*Editor) Commit ¶
func (e *Editor) Commit(plan *Plan) ([]EditResult, error)
Commit is the atomic, lossless point: per file, under that file's lock, it re-reads the live bytes and re-resolves every edit (resolve-under-lock, so a benign concurrent shift lands at the current offset and a same-region conflict is rejected), atomic-writes, and returns one EditResult per edit. A *ConflictError aborts the commit, leaving the source untouched and the WAL intact for Recover; full success clears the WAL.
func (*Editor) Create ¶ added in v0.2.0
Create brings a new file into being from op (op.Kind must be OpCreate), returning a whole-file EditResult a host can ingest. The path's anchor is NON-EXISTENCE: an existing path HARD-REJECTS with ErrExists (matchable via errors.Is) and is never clobbered. The staged op.Content clears the same format/lint/parse floor edits clear, and the create is WAL-logged so a crash unlinks the half-created file. It delegates to the session create leg.
func (*Editor) Delete ¶ added in v0.2.0
Delete unlinks the file named by op (op.Kind must be OpDelete), returning a DeleteResult a host can ingest to close that file's node versions. Its anchor is the expected raw_hash drift gate: the live bytes must still hash to op.ExpectedRawHash or the delete HARD-REJECTS with a *ConflictError (matchable via errors.Is(err, ErrConflict)) and never discards bytes the caller did not see; a missing path rejects with ErrNotFound. The full prior bytes are WAL-captured before the unlink so a crash can restore them. It delegates to the session delete leg.
func (*Editor) Move ¶ added in v0.2.0
Move relocates op.Path to op.To (op.Kind must be OpMove), preserving the source bytes unchanged, and returns a MoveResult a host can ingest. It composes the delete and create anchors: the SOURCE is gated by op.ExpectedRawHash (drift HARD-REJECTS with a *ConflictError; a missing source rejects with ErrNotFound) and the DESTINATION by non-existence (an existing op.To HARD-REJECTS with ErrExists, no clobber). It is RELOCATE-ONLY (no LSP import-fixup) and delegates to the session move leg.
func (*Editor) Parser ¶
func (e *Editor) Parser() ParserPort
Parser returns the Editor's shared ParserPort so Hylla can reuse the exact parser Båge edits with, keeping graph ingest and file edits structurally consistent.
func (*Editor) Prepare ¶
Prepare optimistically stages every region-anchored edit against the live files, drift-checks via the per-region region_hash (rejecting a Conflict or Ambiguous as a *ConflictError, matchable via errors.Is(err, ErrConflict)), runs the optional Formatter/Linter, reparses to prove the result is valid, and durably records a WAL intent. It returns a Plan whose staged bytes are not yet on disk — Prepare's sole on-disk effect is the WAL record. anchors carries the per-file drift gate (SPEC §8.1) for each file the edits touch.
func (*Editor) Read ¶ added in v0.3.0
func (e *Editor) Read(ctx context.Context, path string, opts ReadOptions) (ReadResult, error)
Read opens path with the shared parser, lists its Blocks, and returns a ReadResult carrying the path, detected language, and the whole-file raw and normalized hashes computed with the Editor's hasher.
Addressing mode is chosen from opts, and the three modes are mutually exclusive: line mode (opts.Line >= 1, optionally bounded by EndLine > Line), byte mode (opts.EndByte > opts.StartByte), and whole-file/symbol mode when neither line nor byte addressing is active. Because lines are 1-based, Line 0 means "unset"; a byte range is active only when EndByte > StartByte, so the zero-value ReadOptions{} stays whole-file. Setting opts.Symbol together with a line or byte range is rejected with an error.
In line or byte mode Read returns exactly one Block: a synthetic Kind:"range" Symbol over the range ResolveRange resolves, anchored by region.HashRegion over that range; its Content is the range's raw bytes when opts.IncludeContent is set (bounds-guarded). Otherwise, when opts.IncludeContent is set each Block's Content is populated; when opts.Symbol is non-empty the Blocks are filtered to those whose Name matches exactly. It reuses OpenFile, ReadBlocks, ResolveRange, and the Editor's hasher; the opened file is closed before Read returns.
func (*Editor) Recover ¶
Recover is the crash path: it replays any WAL intent left in the Editor's WALDir, restoring affected files to their pre-Prepare state, then clears the WAL. A clean Commit leaves nothing to replay, so Recover is then a no-op.
func (*Editor) Rename ¶
func (e *Editor) Rename(ctx context.Context, file string, line, col uint32, newName string) (*Plan, error)
Rename performs an LSP-driven rename of the symbol at the zero-based (line, col) UTF-16 position in file, then stages the resulting cross-file edits as region-anchored edits. It requires Config.LSPCommand: it spawns the language server, requests the rename, converts the server's WorkspaceEdit into byte-range edits, grounds each as a Region with a content region_hash, builds one FileAnchor per file, and Prepares them. It returns the Plan; the caller Commits (or Rollbacks) it. The server is shut down before Rename returns.
type ErrorEnvelope ¶ added in v0.3.0
type ErrorEnvelope = session.ErrorEnvelope
ErrorEnvelope is the machine- and human-renderable projection of a Båge error: its stable Kind, the offending path (when known), and the underlying message. Hosts marshal it to JSON or render it as one diagnostic line. See session.ErrorEnvelope.
func Envelope ¶ added in v0.3.0
func Envelope(err error) ErrorEnvelope
Envelope projects err into an ErrorEnvelope: it classifies err via KindOf, records err.Error() as the message, and lifts the Path from a wrapped ConflictError when present. It thin-wraps session.Envelope.
type FileAnchor ¶
type FileAnchor = region.FileAnchor
FileAnchor is the per-file drift gate: a file's RawHash (byte-offset validity) and NormHash (whitespace-only drift classifier). See region.FileAnchor.
type Kind ¶ added in v0.3.0
Kind is the stable, machine-readable classification of a Båge error, re-exported so an external host (Hylla, an MCP server) can react to a failure without importing internal/session or inspecting wrapped chains. See session.Kind.
type Lang ¶
Lang enumerates the source languages a parser adapter can parse. See parser.Lang.
func LangForPath ¶
LangForPath selects the language for a file path by extension/basename, falling back to the grammar-free text mode (never LangUnknown) so any file can be opened and losslessly round-tripped. See parser.LangForPath.
type Linter ¶
Linter validates staged source content, blocking the edit on failure. See format.Linter.
type MoveResult ¶ added in v0.2.0
type MoveResult = session.MoveResult
MoveResult is the signal a host reads after a Move so it can re-identify the moved file's nodes: the removed source path plus the destination's whole-file EditResult over the relocated bytes. See session.MoveResult.
type Node ¶
Node is a single concrete-syntax-tree node addressed by byte range and point. See parser.Node.
type Op ¶ added in v0.2.0
Op is a tagged file-lifecycle operation a host drives through the facade: Create (from non-existence), Delete (raw_hash-gated), Move (anchored delete+create), or Edit (region_hash-gated). It is the public surface of the session engine's lifecycle spine (ADR-0004 §10) so a host like Hylla can drive create/delete/move/batch WITHOUT importing internal/*. See session.Op.
type OpKind ¶ added in v0.2.0
OpKind tags which file-lifecycle operation an Op is. See session.OpKind.
type OpenedFile ¶
type OpenedFile struct {
// Path is the file path that was opened (as supplied by the caller).
Path string
// Lang is the language selected for Path via LangForPath; never LangUnknown.
Lang Lang
// Tree is the parsed CST together with the source bytes it was parsed from.
Tree *Tree
}
OpenedFile is a freshly parsed file handle: the path, the selected language, and the concrete syntax tree. It is the read-only convenience an agent IDE uses to inspect a file without opening a full Editor. The caller MUST Close it when done so the adapter can free the native tree.
func OpenFile ¶
func OpenFile(ctx context.Context, path string) (*OpenedFile, error)
OpenFile reads path, selects a language with LangForPath (falling back to the grammar-free text mode for any type without a registered grammar, so ANY file opens), and parses it with the same tree-sitter adapter Båge edits with. The returned OpenedFile must be Closed by the caller.
func (*OpenedFile) Close ¶
func (o *OpenedFile) Close()
Close releases the native resources held by the opened tree. It is nil-safe and idempotent (parser.Tree.Close is idempotent), so calling it twice or on a zero OpenedFile is a no-op.
type ParseDefect ¶ added in v0.2.0
type ParseDefect struct {
// Kind is "ERROR" for an error-kind node or "MISSING" for an inserted
// recovery node.
Kind string
// Line is the 1-based line of StartByte.
Line int
// Col is the 1-based column (byte offset within the line, +1) of StartByte.
Col int
// StartByte is the inclusive start byte offset of the offending node.
StartByte int
// EndByte is the exclusive end byte offset of the offending node.
EndByte int
}
ParseDefect is one syntax problem surfaced by parse-health: an ERROR-kind node (a span the grammar could not incorporate) or a MISSING node (a zero-width node the parser inserted to recover, e.g. an absent closing brace). It is the cheap, LSP-free tier of `bage diagnose` (SPEC §10.5) and uses the SAME ERROR/MISSING signal the edit parse-floor relies on. Line/Col are 1-based; the byte range is the half-open [StartByte, EndByte) span of the offending node.
func ParseHealth ¶ added in v0.2.0
func ParseHealth(opened *OpenedFile) []ParseDefect
ParseHealth walks a parsed file and reports every ERROR-kind or MISSING node as a ParseDefect with 1-based line/col and byte range. A clean parse reports none.
The grammar-free text fallback (LangText / nil native tree) ALWAYS parses losslessly — every byte lands in a line node — so it can never produce a defect and ParseHealth returns an empty slice for it without walking. This mirrors the edit parse-floor: the same ERROR/MISSING signal gates an edit and is what diagnose surfaces (SPEC §10.5). It never returns nil for a non-nil tree with no defects; callers can rely on a non-nil (possibly empty) result.
type ParserPort ¶
type ParserPort = parser.ParserPort
ParserPort is the engine-agnostic parsing contract Hylla consumes for shared ingest parsing. See parser.ParserPort.
func NewParser ¶
func NewParser() ParserPort
NewParser returns a fresh ParserPort backed by the official CGO tree-sitter adapter. It lets Hylla use Båge's shared parser for ingest without opening a full Editor, so the graph and the files can never disagree on structure.
type Plan ¶
Plan is the result of a successful Prepare: the durably-logged WAL intent plus the region edits and per-file anchors Commit re-validates under lock. See session.Plan.
type ReadOptions ¶ added in v0.3.0
type ReadOptions struct {
// IncludeContent populates each returned Block's Content with its raw bytes.
IncludeContent bool
// Symbol, when non-empty, keeps only Blocks whose Name equals it.
Symbol string
// Line is the 1-based start line of a sub-range read (0 = unset; lines are
// 1-based). When EndLine > Line the read spans the inclusive [Line, EndLine].
Line int
// EndLine is the 1-based end line of a sub-range read (0 = unset).
EndLine int
// StartByte is the inclusive start byte of a sub-range read.
StartByte int
// EndByte is the exclusive end byte of a sub-range read (range active only
// when EndByte > StartByte).
EndByte int
}
ReadOptions selects what an Editor.Read returns. The zero value reads the whole file's structure with no raw content. IncludeContent populates each Block's Content with its source slice; Symbol, when non-empty, filters the returned Blocks to those whose Name matches exactly. Line/EndLine and StartByte/EndByte address a sub-range (see Read for the addressing rule).
type ReadResult ¶ added in v0.3.0
type ReadResult struct {
// Path is the file path that was read (as supplied by the caller).
Path string `json:"path" toon:"path"`
// Lang is the detected source language's string name.
Lang string `json:"lang" toon:"lang"`
// RawHash is the whole-file raw-bytes digest (byte-offset validity gate).
RawHash string `json:"raw_hash" toon:"raw_hash"`
// NormHash is the whole-file normalized-bytes digest (content anchor).
NormHash string `json:"norm_hash" toon:"norm_hash"`
// Blocks are the file's Outline blocks, optionally filtered by ReadOptions.
Blocks []Block `json:"blocks" toon:"blocks"`
}
ReadResult is the structured outcome of an Editor.Read: the read path, the detected language, the raw and normalized whole-file hashes (the drift gate), and the file's Blocks. RawHash gates byte-offset validity; NormHash is the whitespace-insensitive content anchor. Blocks carry per-block region_hashes and, when requested, raw content.
func (ReadResult) RenderText ¶ added in v0.3.0
func (r ReadResult) RenderText(w io.Writer) error
RenderText writes the human-readable read view of r to w: a header line "<path> lang=<lang> raw=<raw> norm=<norm> blocks=<N>" followed by one line per Block — " <kind> <name> lines [<sl>:<el>] bytes [<sb>:<eb>] region=<H>" — where an empty Block.Name renders as "-". This is byte-identical to the text output cmd/bage show emits, so the CLI and any RenderText-aware host share one format. Implementing RenderText makes ReadResult text-renderable without importing pkg/render.
type Region ¶
Region is a content-anchored locator into a file: a byte range, the matching line/col range, and the region_hash that anchors the region by content (SPEC §8.1). See region.Region.
type Symbol ¶
type Symbol struct {
// Kind is the grammar node kind (e.g. "function_declaration"), or "line" for
// the text fallback.
Kind string
// Name is the declared identifier, best-effort; "" when none was found.
Name string
// StartByte is the inclusive start byte offset of the node.
StartByte int
// EndByte is the exclusive end byte offset of the node.
EndByte int
// StartLine is the 1-based start line of the node.
StartLine int
// EndLine is the 1-based end line of the node.
EndLine int
}
Symbol is one entry in a file's Outline: a named declaration node (or, for the grammar-free text fallback, a single line). Bytes are the half-open CST range; StartLine and EndLine are 1-based to match EditResult line numbering. Name is best-effort and may be empty when no identifier child is found.
func Outline ¶
Outline returns a documentSymbol-like listing of a parsed tree: every named declaration node, in source order, with its byte and line ranges. It is grammar-agnostic — it selects declaration nodes by named-node kind, so it works for any tree-sitter grammar. For the grammar-free text fallback it returns one Symbol per source line instead.
The text fallback is identified by a nil native tree (treesitter.textTree sets Tree.Native == nil), NOT by child count: the text document now carries line children, and some real grammars (e.g. HTML) also use a "document" root — so the engine-free handle is the unambiguous discriminator.