ExternalConfigs.Loader (fnord v0.9.39)

View Source

Discovers 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

loaded()

@type loaded() :: %{
  cursor_rules: [ExternalConfigs.CursorRule.t()],
  cursor_skills: [ExternalConfigs.Skill.t()],
  claude_skills: [ExternalConfigs.Skill.t()],
  claude_agents: [ExternalConfigs.Agent.t()]
}

on_disk_scope()

@type on_disk_scope() :: :global | :project | :legacy

Functions

clear_cache()

@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.

dedup_cross_flavor(cursor_skills, claude_skills)

@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:

  1. 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/1 follows symlinks; cursor entries that cannot be stat'd are kept (conservative: don't silently drop on filesystem errors).

  2. 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.

has_any_on_disk?(project, source)

@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.

load(project)

@spec load(Store.Project.t()) :: loaded()

Load all enabled external configs for the given project, honoring the per-project external_configs settings toggles.

load_claude_agents(project)

@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.

load_claude_skills(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.

load_cursor_rules(project)

@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.

load_cursor_skills(project)

@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.

on_disk_scopes(project, atom)

@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.