storage

package
v1.2.3 Latest Latest
Warning

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

Go to latest
Published: May 3, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SubkindFrontmatterMissing   = "missing"
	SubkindFrontmatterMalformed = "malformed"
)

Invalid-frontmatter subkinds. Kept as constants so callers can pattern-match on stable strings.

Variables

View Source
var (
	ErrFrontmatterMissing   = errors.New("storage: frontmatter missing")
	ErrFrontmatterMalformed = errors.New("storage: frontmatter malformed")
)

Typed errors returned by ExtractFrontmatter. Callers use errors.Is to distinguish between a truly-absent frontmatter block and one that is present but unparseable.

Functions

func AtomicWrite

func AtomicWrite(path string, data []byte) error

AtomicWrite writes data to path via a temp file + rename for crash safety. Sets explicit 0644 permissions on the resulting file. Overwrites if exists.

func AtomicWriteExclusive

func AtomicWriteExclusive(path string, data []byte) error

AtomicWriteExclusive writes data to path via a temp file + hard link. Unlike AtomicWrite, it fails if path already exists (returns an error where os.IsExist reports true). This avoids the TOCTOU race inherent in Stat-then-Rename patterns.

func BuildPlainMeta added in v1.1.0

func BuildPlainMeta(filename string) domain.DocMeta

BuildPlainMeta creates synthetic metadata from a filename for standalone mode. Exported for use in cmd/ when a single file fails front matter parsing.

func DeleteDoc

func DeleteDoc(docsDir, filename string) error

DeleteDoc removes a document from docsDir and regenerates the README index. Returns an error if the file does not exist or cannot be removed. Index regeneration errors are returned separately (wrapped with context).

func ExtractFrontmatter added in v1.2.3

func ExtractFrontmatter(data []byte) (fmBytes []byte, bodyBytes []byte, err error)

ExtractFrontmatter captures the frontmatter bytes verbatim and returns them alongside the body bytes. Unlike Unmarshal, it does not parse the YAML into a DocMeta struct — it returns the raw byte regions.

This is the foundation of invariant I24 (polish FM byte-identity): a caller that subsequently writes fmBytes to disk gets back the exact bytes it received, without YAML re-serialization (which would lose key ordering, comment lines, and quote styles).

CRLF normalization matches Unmarshal: if the input contains "\r\n", it is converted to "\n" before delimiter detection, and the returned byte slices reflect the normalized form. The codebase is LF-first (enforced via .gitattributes) so the normalized form is the canonical representation.

Postcondition on success: fmBytes + bodyBytes == normalized_input.

Error semantics:

  • ErrFrontmatterMissing — no "---\n" opening delimiter.
  • ErrFrontmatterMalformed — opening delimiter present but the YAML block is unclosed, empty, or unparseable. The underlying yaml.Unmarshal error is wrapped via fmt.Errorf("%w: %v", ...) so callers can surface the parse detail while still matching on the sentinel via errors.Is.

func ExtractSlug

func ExtractSlug(filename string) string

ExtractSlug extracts the slug from a filename with pattern {type}-{slug}-{date}.md. Example: "decision-auth-strategy-2026-03-07.md" → "auth-strategy"

func ExtractTitle added in v1.0.0

func ExtractTitle(body string, filename string) string

extractTitle returns the first top-level markdown heading (# ...) from body. Falls back to the slug portion of filename (e.g. "auth-strategy" from "decision-auth-strategy-2026-03-07.md") when the body has no heading. Only matches # (h1), not ## or deeper. ExtractTitle returns the first # heading from body, or a slug from filename.

func FindReferencingDocs

func FindReferencingDocs(docsDir, filename string) ([]string, error)

FindReferencingDocs returns filenames of documents whose `related:` field references the given filename (compared without .md extension).

func GenerateReadmeBridge added in v1.0.0

func GenerateReadmeBridge(loreDir string) error

GenerateReadmeBridge creates .lore/README.md as a discovery bridge. Skips silently if the file already exists (user may have edited it).

func GenerateReleaseNotes added in v1.0.0

func GenerateReleaseNotes(version string, date string, docs []ReleaseDoc, docsDir string) (string, error)

GenerateReleaseNotes produces a release notes Markdown document with front matter.

func Marshal

func Marshal(meta domain.DocMeta, body string) ([]byte, error)

Marshal produces "---\n{yaml}\n---\n{body}" from DocMeta and body.

func NormalizeAfter

func NormalizeAfter(after string) string

NormalizeAfter converts a YYYY-MM date to YYYY-MM-01 for lexicographic comparison. YYYY-MM-DD values pass through unchanged.

func QuickHealthCheck

func QuickHealthCheck(docsDir string) (int, error)

QuickHealthCheck performs a fast health check on the docs directory. Returns the number of issues found:

  • orphan .tmp files (interrupted writes)
  • missing or empty README.md index

This is NOT the full diagnostic — see lore doctor for full checks.

func ReadDocContent

func ReadDocContent(path string) (string, error)

ReadDocContent returns the full file content (front matter + body) for display. The path argument is expected to come from SearchResult.Path (produced by SearchDocs), which constructs paths via filepath.Join(dir, entry.Name()) — never from user input.

func RegenerateIndex

func RegenerateIndex(docsDir string) error

RegenerateIndex creates or updates the README.md index in docsDir. It scans all .md files (except README.md), parses their front matter, and generates a sorted table. The result is written atomically.

func Unmarshal

func Unmarshal(data []byte) (domain.DocMeta, string, error)

Unmarshal parses front matter + body from a document.

func UnmarshalPermissive added in v1.1.0

func UnmarshalPermissive(data []byte) (domain.DocMeta, string, error)

UnmarshalPermissive parses front matter + body from a document WITHOUT running ValidateMeta. Used by PlainCorpusStore (standalone mode) so external docs sites can have partial front matter (e.g. just `type` and `date`, or arbitrary types like "blog-post") without being rejected and silently downgraded to a synthetic "note" meta.

Strict validation (ValidateMeta) is only appropriate for lore-managed corpora where the commit-capture workflow is guaranteed to fill every required field.

func UpdateChangelog added in v1.0.0

func UpdateChangelog(projectDir string, version string, date string, docs []ReleaseDoc) (bool, error)

UpdateChangelog updates or creates CHANGELOG.md with a new release section. Returns (headerMissing, error). headerMissing is true when an existing CHANGELOG lacks the "# Changelog" header — callers should warn on stderr.

func UpdateReleasesJSON added in v1.0.0

func UpdateReleasesJSON(loreDir string, version string, date string, docFilenames []string) error

UpdateReleasesJSON adds a new release entry to .lore/releases.json.

func ValidateFilename

func ValidateFilename(filename string) error

ValidateFilename rejects path traversal attempts in user-supplied filenames.

func ValidateMeta

func ValidateMeta(meta domain.DocMeta) error

ValidateMeta checks that required fields (type, date, status) are present, that type is a recognized DocType constant, and that date is in YYYY-MM-DD format.

Types

type CorpusStore

type CorpusStore struct {
	Dir string // path to .lore/docs/
}

CorpusStore implements domain.CorpusReader for the local filesystem.

func (*CorpusStore) ListDocs

func (s *CorpusStore) ListDocs(filter domain.DocFilter) ([]domain.DocMeta, error)

ListDocs scans the directory for .md files and returns their metadata. Skips README.md (auto-generated index). Returns partial results alongside a combined error if any files could not be read or parsed.

func (*CorpusStore) ReadDoc

func (s *CorpusStore) ReadDoc(id string) (string, error)

ReadDoc reads a single document by ID (filename without extension).

type DiagnosticReport

type DiagnosticReport struct {
	Issues   []Issue
	DocCount int // number of valid .md documents scanned
	Checked  int // total number of checks performed
}

DiagnosticReport holds the results of a full corpus health check.

func Diagnose

func Diagnose(docsDir string) (*DiagnosticReport, error)

Diagnose performs a comprehensive health check on the docs directory. It checks for orphan .tmp files, broken references, stale index, stale cache (metadata.json), and invalid front matter.

type FixReport

type FixReport struct {
	Fixed     int      // number of issues successfully fixed
	Remaining int      // number of issues that still need manual attention
	Errors    int      // number of errors during fix (permission, etc.)
	Details   []string // descriptions of actions taken
}

FixReport holds the results of automatic repairs.

func Fix

func Fix(docsDir string, report *DiagnosticReport) (*FixReport, error)

Fix applies automatic repairs based on the diagnostic report. Only issues with AutoFix=true are attempted. Individual fix failures are logged but do not stop other repairs.

type Issue

type Issue struct {
	Category string // "orphan-tmp", "broken-ref", "stale-index", "stale-cache", "invalid-frontmatter"
	// Subkind narrows an issue to a precise sub-category. Currently
	// only populated for Category == "invalid-frontmatter":
	//   - "missing"   — no "---\n" delimiter; a synthesized FM is safe
	//   - "malformed" — "---" present but the YAML between delimiters
	//                   is unparseable; auto-fix would destroy
	//                   potentially recoverable authentic content
	// Story 8-22 / invariant I31: `doctor --fix` never rewrites an
	// FM when the source contains a "---" delimiter — the malformed
	// path surfaces a restore hint rather than synthesizing.
	Subkind string
	File    string // file concerned
	Detail  string // human-readable description
	AutoFix bool   // repairable automatically?
}

Issue represents a single diagnostic finding.

type PlainCorpusStore added in v1.1.0

type PlainCorpusStore struct {
	Dir string // path to the markdown directory
}

PlainCorpusStore implements domain.CorpusReader for any directory of Markdown files. Unlike CorpusStore, it gracefully handles files without YAML front matter, making it suitable for standalone Angela usage on non-lore projects.

func (*PlainCorpusStore) ListDocs added in v1.1.0

func (s *PlainCorpusStore) ListDocs(filter domain.DocFilter) ([]domain.DocMeta, error)

ListDocs scans the directory recursively for .md files and returns their metadata. Files with valid YAML front matter use the parsed metadata. Files without front matter get synthetic metadata derived from the filename and file modification time.

func (*PlainCorpusStore) ReadDoc added in v1.1.0

func (s *PlainCorpusStore) ReadDoc(id string) (string, error)

ReadDoc reads a single document by relative path (e.g., "commands/angela-polish.md"). Unlike CorpusStore.ReadDoc which only accepts flat filenames, PlainCorpusStore supports subdirectory paths for recursive standalone mode.

type ReleaseDoc added in v1.0.0

type ReleaseDoc struct {
	domain.DocMeta
	Title string // first # heading from body, fallback: slug from filename
}

ReleaseDoc enriches DocMeta with the document title for release notes display.

func CollectReleaseDocuments added in v1.0.0

func CollectReleaseDocuments(docsDir string, commits []string) ([]ReleaseDoc, error, error)

CollectReleaseDocuments scans docsDir for documents whose front matter commit field matches one of the given commit hashes. Documents are grouped by type and sorted by date within each group. Returns matched docs alongside any non-fatal parse errors (callers should surface these as warnings).

type ReleaseEntry added in v1.0.0

type ReleaseEntry struct {
	Version   string   `json:"version"`
	Date      string   `json:"date"`
	Documents []string `json:"documents"`
}

ReleaseEntry represents one release in releases.json.

type SearchResult

type SearchResult struct {
	Filename string
	Path     string
	Meta     domain.DocMeta
	Title    string // first # heading from body, fallback: slug from filename
}

SearchResult represents a single search hit from SearchDocs.

func FindDocByCommit

func FindDocByCommit(dir string, commitHash string) (*SearchResult, error)

FindDocByCommit searches for a document whose front matter commit field matches commitHash (strict equality — caller resolves short hashes before calling). Returns nil if no match is found.

func SearchDocs

func SearchDocs(dir string, keyword string, filter domain.DocFilter) ([]SearchResult, error)

SearchDocs searches documents matching keyword (case-insensitive) in filename, tags, and body, then applies type and date filters from DocFilter. Returns results sorted by date descending.

type WriteResult

type WriteResult struct {
	Filename string // e.g. "decision-auth-strategy-2026-03-07.md"
	Path     string // e.g. "/path/to/.lore/docs/decision-auth-strategy-2026-03-07.md"
	// Deprecated: always nil — callers should call RegenerateIndex explicitly.
	IndexErr error
}

WriteResult contains the outcome of a WriteDoc operation.

func WriteDoc

func WriteDoc(dir string, meta domain.DocMeta, subject string, body string) (WriteResult, error)

WriteDoc creates a document in the given directory via AtomicWrite. subject is used for the filename slug (e.g., "add JWT middleware" → "add-jwt-middleware"). After writing, it regenerates the README index. Index errors are surfaced in WriteResult.IndexErr but do not cause WriteDoc itself to fail.

Jump to

Keyboard shortcuts

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