agentsmd

package
v0.17.3 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package agentsmd loads project-context markdown files (AGENTS.md plus CLAUDE.md for compatibility) and composes them into a system-prompt addition.

Difference from skills

Skills (internal/skill) are model-invoked: the model sees an index in the system prompt and pulls in a skill's body on demand via the `skill` tool. Project-context files are the opposite: their contents are auto-appended to the system prompt unconditionally so the model always has the project's house rules in context.

Discovery (v0.2 surface)

Load() returns AT MOST the following blocks, in precedence order (lowest first; all that exist are concatenated, none overrides any other):

User layers:
  1. ~/.agents/AGENTS.md                  (SourceUserAgents)
  2. ~/.config/hygge/AGENTS.md            (SourceUserHygge)
  3. ~/.claude/CLAUDE.md                  (SourceUserClaude)

Project layers, gated on a project root found via walk-up from Pwd:
  4. <project-root>/.agents/AGENTS.md     (SourceProjectAgents)
  5. <project-root>/AGENTS.md             (SourceProjectRoot)
  6. <project-root>/AGENTS.local.md       (SourceProjectRoot)
  7. <project-root>/CLAUDE.md             (SourceProjectRoot)
  8. <project-root>/CLAUDE.local.md       (SourceProjectRoot)

Subdirectory AGENTS.md / CLAUDE.md files are NOT loaded at startup. They are loaded lazily, on-demand, by the per-tool-call loader described in STATUS.md (walks up from each tool-touched path to the project root, injecting any newly-seen context block as a transient system note in the next provider turn).

The project-root walk stops at the first directory containing AGENTS.md, CLAUDE.md, .git, .agents/, or .hygge/. The walk also halts when it reaches $HOME so user-level files are not re-discovered as project-level blocks.

Index

Constants

View Source
const MaxLazyContextBytes = 256 * 1024

MaxLazyContextBytes bounds the total byte size of lazy-loaded subdirectory context blocks. Files that would push the running total over this cap are expected to be skipped with a slog.Warn.

Reserved for the lazy loader; not consulted by Load() in v0.2.

View Source
const MaxLazyContextFiles = 50

MaxLazyContextFiles bounds how many subdirectory context files the future lazy per-tool-call loader (see STATUS.md) may inject across a single session. Beyond this cap the loader is expected to log a slog.Warn and stop loading new files.

Reserved for the lazy loader; not consulted by Load() in v0.2.

Variables

View Source
var LazyExcludeDirs = map[string]struct{}{
	".git":         {},
	".agents":      {},
	".hygge":       {},
	"node_modules": {},
	"vendor":       {},
	".venv":        {},
	"__pycache__":  {},
	"dist":         {},
	"target":       {},
	"bin":          {},
	"build":        {},
}

LazyExcludeDirs is the set of directory names the future lazy loader should skip when walking up from a tool-touched path looking for AGENTS.md / CLAUDE.md. Their top-level files are already handled by dedicated layers, and dependency / build directories should never contribute project context.

Reserved for the lazy loader; not consulted by Load() in v0.2.

Functions

func BuildLazyAddition

func BuildLazyAddition(blocks []Block) string

BuildLazyAddition formats lazy-loaded subdir context blocks for injection into a single turn's system prompt. Returns "" when blocks is empty.

The output is shaped like BuildSystemPromptAdditions but headered "## Additional project context (loaded for this turn)" so it is obvious to the model that this material was just brought in by a tool call rather than being part of the startup prompt.

func BuildSystemPromptAdditions

func BuildSystemPromptAdditions(blocks []Block) string

BuildSystemPromptAdditions returns the project-context content as one concatenated block ready to APPEND to the base system prompt. Returns an empty string when blocks is empty.

The output follows this stable shape:

## Project context

<!-- source: <source-token>: <path-or-relpath> -->
<content>

---

<!-- source: <source-token>: <path-or-relpath> -->
<content>

For project-layer blocks the comment uses the relative path (e.g. `project/root: CLAUDE.local.md`) so the model can locate the file inside the working tree.

func FindProjectRootFrom

func FindProjectRootFrom(start string) string

FindProjectRootFrom walks parents of start looking for the first directory that contains one of the project-root markers (AGENTS.md, CLAUDE.md, .git, .agents/, .hygge/). Returns the absolute matching directory, or "" if no marker was found before the filesystem root.

FindProjectRootFrom does not stop at $HOME; use findProjectRoot when you need the home-stop boundary (Load / agentsmd internal use). The CLI and other callers that just want "the nearest project root from here" use this function.

func WalkUp

func WalkUp(start string, opt WalkOption, visit func(dir string) WalkAction) string

WalkUp walks from start toward the filesystem root, calling visit for each directory. The walk halts when:

  • visit returns WalkStop, OR
  • the current directory equals opt.HomeStop (exclusive — HomeStop itself is never visited), OR
  • the next parent equals the current directory (filesystem root), OR
  • a directory's basename appears in opt.ExcludeDirs (visit is not called for that directory or any of its descendants between start and the root, but they are all still "walked through" so callers can mark them seen).

ExcludeDir short-circuit: if the excluded ancestor is detected before the walk reaches it, visit is never called for any directory at or below the excluded ancestor. The walk continues past the excluded ancestor toward the root without skipping those parent directories.

Returns the directory where visit returned WalkStop, or "" if the walk terminated without visit ever returning WalkStop.

Types

type Block

type Block struct {
	// Path is the absolute path the file was read from.
	Path string
	// RelPath is the project-relative path for project-layer blocks
	// (empty for user-layer blocks).  Used by `hygge context list`
	// and the system-prompt comment header so the model can locate
	// the source within the project.
	RelPath string
	// Source identifies which layer this block came from.
	Source Source
	// Content is the file's contents, trimmed of leading and trailing
	// whitespace.  Empty when the file was empty — the block is still
	// returned so callers know the user created the file deliberately.
	Content string
}

Block is a single project-context file loaded from a specific location.

func Load

func Load(opts LoadOptions) ([]Block, error)

Load returns every project-context file found, in precedence order (lowest first). Missing files are silently skipped; an empty slice is returned when nothing exists anywhere.

Load only considers the project-root files and the user layers — it does NOT walk subdirectories. Subdirectory context is loaded lazily by the per-tool-call loader described in STATUS.md.

type LazyTracker

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

LazyTracker tracks lazy per-tool-call context loading state for a single agent session. Not safe for concurrent use — callers must serialise access (the agent loop already serialises per session).

Construct via NewLazyTracker. The zero value is not usable.

func NewLazyTracker

func NewLazyTracker(homeDir, projectRoot string, seenDirs []string) *LazyTracker

NewLazyTracker constructs a tracker bounded by projectRoot (the first directory containing AGENTS.md / CLAUDE.md / .git / .agents / .hygge, as discovered by Load) and seeded with seenDirs — every directory whose AGENTS.md / CLAUDE.md was already loaded at bootstrap, so those files are never re-injected.

projectRoot == "" disables the tracker: Touch will always return nil. homeDir is reserved for future use (currently unused — the walk already terminates at projectRoot).

func (*LazyTracker) Touch

func (t *LazyTracker) Touch(pwd string, paths []string) []Block

Touch reports the directories the agent's most recent tool calls referenced. pwd is the agent's working directory (used to resolve relative paths). Each entry in paths is either a file or directory path (absolute or relative to pwd).

For each touched path, the tracker walks upward toward projectRoot looking for AGENTS.md, or CLAUDE.md / CLAUDE.local.md when no AGENTS.md exists there, in directories not yet seen. Directories named in LazyExcludeDirs are skipped (the walk treats them as if they didn't exist). Every visited directory is marked seen whether or not it contained a context file.

Returns any newly-discovered blocks with Source = SourceProjectSubdir. When MaxLazyContextFiles or MaxLazyContextBytes would be exceeded, the tracker logs slog.Warn once and returns nil from then on.

Returns nil when projectRoot is empty, when no new files were found, or when the cap has been hit.

type LoadOptions

type LoadOptions struct {
	HomeDir       string
	XDGConfigHome string
	Pwd           string
}

LoadOptions mirrors skill.LoadOptions: HomeDir / XDGConfigHome let tests redirect the user layers into a tempdir; Pwd seeds the project walk-up.

type Source

type Source int

Source identifies which discovery location a Block came from.

const (
	// SourceUserAgents is ~/.agents/AGENTS.md.
	SourceUserAgents Source = iota
	// SourceUserHygge is ~/.config/hygge/AGENTS.md.
	SourceUserHygge
	// SourceUserClaude is ~/.claude/CLAUDE.md (CLAUDE-format compat
	// at the user level).
	SourceUserClaude
	// SourceProjectAgents is <project-root>/.agents/AGENTS.md.
	SourceProjectAgents
	// SourceProjectRoot is the conventional file (AGENTS.md /
	// AGENTS.local.md / CLAUDE.md / CLAUDE.local.md) at the project
	// root itself.
	SourceProjectRoot
	// SourceProjectSubdir is an AGENTS.md or CLAUDE.md found in a
	// subdirectory of the project root.
	//
	// Not produced by Load() in v0.2; reserved for the lazy
	// per-tool-call loader described in STATUS.md, which surfaces
	// subdir context only when the agent actually touches the
	// directory via a tool call.
	SourceProjectSubdir
)

Source values, ordered by precedence (lower-priority first).

func (Source) String

func (s Source) String() string

String returns a short diagnostic token for the source.

type WalkAction

type WalkAction int

WalkAction is the outcome of one Visit call.

const (
	// WalkContinue moves the walk to the parent directory.
	WalkContinue WalkAction = iota
	// WalkStop ends the walk immediately after this step.
	WalkStop
)

type WalkOption

type WalkOption struct {
	// HomeStop, if non-empty, halts the walk when the current directory
	// equals this path.  Used to prevent project-root detection from
	// walking into the user's home directory and mis-attributing
	// user-layer files as project roots.
	HomeStop string
	// ExcludeDirs is a set of directory basenames whose presence on the
	// path from start to the filesystem root short-circuits the Visit
	// callback for that directory and all directories below it.  The
	// walk still marks excluded directories seen (so LazyTracker does
	// not revisit them) but never calls Visit for them.
	// Empty means no exclusions.
	ExcludeDirs map[string]struct{}
}

WalkOption configures WalkUp. The zero value is a no-op walk.

Jump to

Keyboard shortcuts

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