ExternalConfigs.Loader (fnord v0.9.35)
View SourceDiscovers and loads Cursor rules, Cursor skills, and Claude Code skills from the user's home directory and the current project's source root.
Project-scoped entries override user/global entries of the same name (CSS-style layering).
Summary
Functions
Clears all external-configs loader caches. Call from tests that mutate the on-disk rules/skills mid-test; production code should never need this since the escript VM is short-lived.
Remove cursor skills that duplicate a claude skill, preferring claude.
Cheap on-disk presence check, used by the coordinator's startup nudge to detect "feature is off, but the files are sitting right there." Skips parsing; just asks whether any candidate file exists under the global or project search paths for the given source.
Load all enabled external configs for the given project, honoring the
per-project external_configs settings toggles.
Load all Claude Code subagents (global + project), project overriding global by name. Cached per session and per project.
Load all Claude Code skills (global + project), project overriding global. Cached per session and per project.
Load only cursor rules (global + project) regardless of settings. Cached per session and per project. The hot path is the Injector, which reruns this on every file read/write while the rule set is stable for the life of the invocation.
Load all cursor skills (global + project), project overriding global. Cached per session and per project.
Reports which scopes (:global, :project, :legacy) actually have
candidate files on disk for the given source. Empty list means nothing
is present. Like has_any_on_disk?/2 this skips parsing and only probes
for file existence, but it preserves where the hits are so the startup
nudge can tell the user whether the detected files are global (in their
home directory, shared across all projects) or local to this repo.
Types
@type loaded() :: %{ cursor_rules: [ExternalConfigs.CursorRule.t()], cursor_skills: [ExternalConfigs.Skill.t()], claude_skills: [ExternalConfigs.Skill.t()], claude_agents: [ExternalConfigs.Agent.t()] }
@type on_disk_scope() :: :global | :project | :legacy
Functions
@spec clear_cache() :: :ok
Clears all external-configs loader caches. Call from tests that mutate the on-disk rules/skills mid-test; production code should never need this since the escript VM is short-lived.
@spec dedup_cross_flavor([ExternalConfigs.Skill.t()], [ExternalConfigs.Skill.t()]) :: [ ExternalConfigs.Skill.t() ]
Remove cursor skills that duplicate a claude skill, preferring claude.
Two dedup passes run in order:
Inode identity — cursor skills whose on-disk directory resolves to the same inode as a claude skill are dropped. Handles individual directory symlinks and whole-tree symlinks at any depth.
File.stat/1follows symlinks; cursor entries that cannot be stat'd are kept (conservative: don't silently drop on filesystem errors).Name identity — cursor skills whose name matches a claude skill are dropped even when stored at a distinct path. A same-name clash across flavors almost always means one is a copy or re-export of the other.
@spec has_any_on_disk?(Store.Project.t(), Settings.ExternalConfigs.source()) :: boolean()
Cheap on-disk presence check, used by the coordinator's startup nudge to detect "feature is off, but the files are sitting right there." Skips parsing; just asks whether any candidate file exists under the global or project search paths for the given source.
@spec load(Store.Project.t()) :: loaded()
Load all enabled external configs for the given project, honoring the
per-project external_configs settings toggles.
@spec load_claude_agents(Store.Project.t()) :: [ExternalConfigs.Agent.t()]
Load all Claude Code subagents (global + project), project overriding global by name. Cached per session and per project.
@spec load_claude_skills(Store.Project.t()) :: [ExternalConfigs.Skill.t()]
Load all Claude Code skills (global + project), project overriding global. Cached per session and per project.
Skills flagged for self-delegation (see ExternalConfigs.Skill -
fnord_skip) are dropped here, before any consumer sees them. The drop
is logged once per cache miss with the offending path.
@spec load_cursor_rules(Store.Project.t()) :: [ExternalConfigs.CursorRule.t()]
Load only cursor rules (global + project) regardless of settings. Cached per session and per project. The hot path is the Injector, which reruns this on every file read/write while the rule set is stable for the life of the invocation.
@spec load_cursor_skills(Store.Project.t()) :: [ExternalConfigs.Skill.t()]
Load all cursor skills (global + project), project overriding global. Cached per session and per project.
Skills flagged for self-delegation (see ExternalConfigs.Skill -
fnord_skip) are dropped here, before any consumer sees them. The drop
is logged once per cache miss with the offending path.
@spec on_disk_scopes(Store.Project.t(), Settings.ExternalConfigs.source()) :: [ on_disk_scope() ]
Reports which scopes (:global, :project, :legacy) actually have
candidate files on disk for the given source. Empty list means nothing
is present. Like has_any_on_disk?/2 this skips parsing and only probes
for file existence, but it preserves where the hits are so the startup
nudge can tell the user whether the detected files are global (in their
home directory, shared across all projects) or local to this repo.