bage

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 19 Imported by: 0

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

Examples

Constants

View Source
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.

View Source
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.

View Source
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

View Source
var ErrConflict = session.ErrConflict

ErrConflict is the sentinel wrapped by every ConflictError, matchable with errors.Is without inspecting the path. See session.ErrConflict.

View Source
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.

View Source
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

func NormHash(h Hasher, raw []byte) string

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

func Normalize(b []byte) []byte

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

func RawHash(h Hasher, raw []byte) string

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

func RegionHash(src []byte, start, end int) string

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

func ResolveRange(src []byte, line int, lines string, start, end int) (region.Region, error)

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

type CmdLinter = format.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

type Edit = region.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

func Open(cfg Config) (*Editor, error)

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

func (e *Editor) ApplyBatch(ctx context.Context, ops []Op) ([]BatchResult, error)

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

func (e *Editor) Close() error

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

func (e *Editor) Create(ctx context.Context, op Op) (EditResult, error)

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

func (e *Editor) Delete(ctx context.Context, op Op) (DeleteResult, error)

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

func (e *Editor) Move(ctx context.Context, op Op) (MoveResult, error)

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

func (e *Editor) Prepare(ctx context.Context, edits []Edit, anchors []FileAnchor) (*Plan, error)

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

func (e *Editor) Recover(ctx context.Context) error

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.

func (*Editor) Rollback

func (e *Editor) Rollback(plan *Plan) error

Rollback abandons a prepared Plan, discarding the staged edits and clearing the WAL; the source files are left untouched.

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 Formatter

type Formatter = format.Formatter

Formatter rewrites staged source content before commit. See format.Formatter.

type Hasher

type Hasher = hashing.Hasher

Hasher computes a stable hex digest of a byte slice. See hashing.Hasher.

type Kind added in v0.3.0

type Kind = session.Kind

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.

func KindOf added in v0.3.0

func KindOf(err error) Kind

KindOf classifies err into a Kind: the empty Kind for nil, a self-classifying error's own Kind, the matched sentinel for ErrConflict/ErrExists/ErrNotFound, and otherwise KindIO. It thin-wraps session.KindOf.

type Lang

type Lang = parser.Lang

Lang enumerates the source languages a parser adapter can parse. See parser.Lang.

func LangForPath

func LangForPath(path string) Lang

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

type Linter = format.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

type Node = parser.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

type Op = session.Op

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

type OpKind = session.OpKind

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

type Plan = session.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

type Region = region.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

func Outline(tree *Tree) []Symbol

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.

type Tree

type Tree = parser.Tree

Tree is a parsed concrete syntax tree together with its source bytes. See parser.Tree.

type XXHasher

type XXHasher = hashing.XXHasher

XXHasher is the canonical xxHash64 Hasher shared with Hylla. See hashing.XXHasher.

Jump to

Keyboard shortcuts

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