note

package
v0.22.0 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package note implements nt's markdown notes with light YAML frontmatter (SPEC §5). Notes are one file each under notes/, so they need no shared lock: creation and edits are atomic single-file writes.

Index

Constants

View Source
const (
	// TierSmallStore is the note count at or below which the index shows
	// everything untired (today's behavior).
	TierSmallStore = 30
	// TierRecentDays is the recency window for the middle tier.
	TierRecentDays = 14
	// TierRecentCap bounds the recent tier even during a busy fortnight.
	TierRecentCap = 50
	// TierPinnedWarn is the pinned-tier size doctor warns at — "always shown"
	// invites dumping, so the cost is made legible rather than forbidden.
	TierPinnedWarn = 15
)

The index's tiering knobs. The catalog used to print one stub per note forever (~60 tokens each), so a session-start `nt index` grew linearly with store HISTORY; tiering bounds it by store CONVENTIONS (pinned) + recent ACTIVITY instead. Small stores are never tiered — completeness is cheap there.

View Source
const TaskNoteFolder = "__tasks__"

TaskNoteFolder is the subfolder under notes/ where a task's "body" notes live (auto-split paragraph captures and explicit task detail). The double-underscore name is deliberately "reserved-looking" so it won't collide with a plain "tasks" folder a user might keep for their own hand-curated notes; grouping these machine-created notes here keeps them out of a human's folders — like the "journal" folder does for daily notes.

Variables

View Source
var Kinds = map[string]Kind{
	"lesson":   {Folder: "lessons", Tag: "lesson"},
	"decision": {Folder: "decisions", Tag: "decision"},
	"ref":      {Folder: "ref", Tag: "ref"},
	"rule":     {Folder: "rules", Tag: "rule"},
	"memory":   {Folder: "memory", Tag: "memory-core"},
}

Kinds maps a note class (CLI --kind / MCP kind:) to its canonical folder + tag, so multi-agent stores converge on one taxonomy instead of inventing folders. Shared by the CLI and MCP surfaces — keep them identical. "memory" is the always-loaded core-memory layer (the OpenCode plugin injects it): files under memory/ carrying the memory-core tag, NOT a bare "memory" tag.

Functions

func Slug

func Slug(title string) string

Slug derives a filesystem-safe slug from a title, falling back to a timestamp when the title yields nothing usable (à la nb).

func TitleOverlap added in v0.20.0

func TitleOverlap(a, b string) float64

TitleOverlap is the word-set Jaccard (0..1) of two titles, ignoring short and stopword tokens — the similarity heuristic behind duplicate detection, exported so task-side dedup can reuse the exact same notion nt uses for notes.

Types

type Cache added in v0.4.0

type Cache struct {
	// contains filtered or unexported fields
}

Cache is an mtime-keyed parse cache for note files. List walks notes/ but only re-reads and re-parses files whose size or mtime changed since last time — turning a snapshot rebuild from "read+parse every note" into "stat every note, read+parse the few that changed". This is what lets the in-memory read-model scale to thousands of notes (a single edit no longer re-reads the whole store).

Returned *Note values are shared with the cache; the read-model treats them as read-only, so this is safe. Cache is safe for concurrent use.

func NewCache added in v0.4.0

func NewCache() *Cache

NewCache returns an empty note cache.

func (*Cache) ByID added in v0.20.0

func (c *Cache) ByID(id string) *Note

ByID returns the note with the given id as of the last List (nil if none). It does not walk the dir, so callers should List first to refresh — the id index is rebuilt on every List. This turns a get-by-id from an O(notes) resolve scan into an O(1) map lookup.

func (*Cache) List added in v0.4.0

func (c *Cache) List(s *store.Store) ([]*Note, error)

List returns all notes under notes/, reusing unchanged files from the cache and re-parsing only those that were added or modified. Deleted files are evicted. Output ordering matches note.List (by Rel).

type Kind added in v0.22.0

type Kind struct {
	Folder string // canonical folder under notes/
	Tag    string // canonical tag stamped on the note
}

Kind is the canonical folder + tag pair for one note class (see Kinds).

type Note

type Note struct {
	Path     string
	Rel      string // path relative to notes/ (slash-separated), set by List
	ID       string
	Title    string
	Tags     []string
	Aliases  []string
	Source   string
	Created  string
	Updated  string // stamped when nt rewrites the note (retag, --field)
	Archived bool   // frontmatter archived: true — retired from active views, still on disk
	Favorite bool   // frontmatter favorite: true — starred/pinned for quick access
	// SupersededBy is the id of the note that replaces this one (frontmatter
	// superseded_by:). A superseded note is dropped from active views like an
	// archived one — so a resume sees the single canonical decision, not both
	// forks — while the pointer preserves the trail.
	SupersededBy string
	// ModTime is the note file's last-modified time, set by List/Load/cache. It
	// captures every change — including edits made outside nt (Obsidian, git) that
	// never touch the `updated:` frontmatter — so "changed since T" is reliable.
	ModTime time.Time
	Body    string
	Extra   []string // raw frontmatter lines for keys nt doesn't model (preserved verbatim)
}

Note is a parsed markdown note.

func Active added in v0.12.0

func Active(ns []*Note) []*Note

Active drops notes retired from the working set: archived notes and superseded ones (a superseded note has a newer canonical version, so views show only the current decision, not both forks).

func Create

func Create(s *store.Store, title, body string, tags []string, source, folder string) (*Note, error)

Create builds and writes a new note, returning it. The body is prefixed with an H1 title when it doesn't already start with one. Create writes a new note. folder, when non-empty, is a slash-separated subfolder under notes/ (e.g. "work" or "work/auth"); it is created as needed. The filename is slugged from the title; the body and frontmatter are written by Save.

func FindSimilar added in v0.20.0

func FindSimilar(notes []*Note, title string, tags []string) []*Note

FindSimilar returns active, non-reserved notes that look like near-duplicates of a note with the given title and tags — a guard against concurrent forks (two agents independently recording the same decision). A candidate matches when it has the identical slug, OR it shares a tag AND its title word-set overlaps heavily (Jaccard ≥ 0.5) — UNLESS the pair is a "parallel sibling": each note carries a distinguishing tag the other lacks that also appears in its own title ("taskly repo map" @taskly vs "ratelim repo map" @ratelim). Multi-project stores legitimately hold same-shaped notes per project; the project tag in the title is how the pair self-identifies as distinct. This is a cheap heuristic, not semantic dedup.

func List

func List(s *store.Store) ([]*Note, error)

func Load

func Load(path string) (*Note, error)

Load parses a note file (frontmatter + body). Unknown frontmatter keys are ignored, not an error.

func (*Note) ChangedDate added in v0.22.0

func (n *Note) ChangedDate() string

ChangedDate is the note's effective change date (YYYY-MM-DD): the later of its file mtime (catches external edits) and its frontmatter updated/created.

func (*Note) Description added in v0.20.0

func (n *Note) Description(max int) string

List loads all notes in the store's notes directory, recursing into subfolders so an Obsidian-style nested vault works. Hidden dirs (.obsidian/, .trash/, .git/) and non-.md files are skipped. Each note's Rel (path relative to notes/, slash-separated) is set for link resolution; results are sorted by Rel for deterministic ordering. Active drops archived notes — the working set, for views/search that should hide retired notes. List itself returns everything (archived included) so link-rewriting and the archived view still see them. Description returns the note's one-line summary for index/stub views: its `description:` frontmatter if set (kept in Extra, since nt doesn't model the key), else the first non-heading body line. Clamped to a single line ≤max chars. This is the "one-sentence summary" granularity of progressive disclosure — what an agent reads to decide whether to open the full note.

func (*Note) Pinned added in v0.22.0

func (n *Note) Pinned() bool

Pinned reports whether a note belongs to the always-shown index tier: standing knowledge whose relevance does not decay with file age.

func (*Note) Project added in v0.22.0

func (n *Note) Project() string

Project returns the note's `project:` frontmatter value ("" when unset). The key isn't modeled (it rides in Extra, preserved verbatim); this accessor is how recall's same-project boost reads a note's declared project membership — alongside its tags and folder path.

func (*Note) Reserved added in v0.20.0

func (n *Note) Reserved() bool

Reserved reports whether a note lives in a machine-managed folder that isn't part of the human/agent knowledge base — currently notes/__tasks__/, where nt files the detail bodies of split tasks. These are reachable by id/link but are kept out of the KB catalog (nt index) and search so they don't pollute it.

func (*Note) Save

func (n *Note) Save() error

Save writes the note atomically with frontmatter.

type Tiers added in v0.22.0

type Tiers struct {
	Pinned        []*Note
	Recent        []*Note
	OlderByFolder map[string]int
	OlderTotal    int
	Tiered        bool // false ⇒ small store, everything is in Recent
}

Tiers is the index default view of a large store: standing notes always, recent activity in full, and the long tail as per-folder counts (each with the expansion path printed by the caller — nothing hides silently).

func TierIndex added in v0.22.0

func TierIndex(notes []*Note, now time.Time) Tiers

TierIndex splits an already-filtered index note set into tiers as of `now`. Order within tiers: Pinned by folder/title (stable shape), Recent newest first (what changed since last session reads top-down).

func (*Tiers) LimitRecent added in v0.22.0

func (t *Tiers) LimitRecent(n int)

LimitRecent truncates the recent tier to n stubs, moving the overflow into the rollup counts so pinned + recent + older still equals the total — a --limit must shrink the listing, never make notes vanish from the math.

Jump to

Keyboard shortcuts

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