SIGN IN SIGN UP

feat(skill-loader): honor user-invocable spec field + OmO hide_nested_by_default flag (#54)

* feat(skill-loader): read user-invocable and disable-model-invocation from SKILL.md

Extends the skill loader to parse the Claude Code spec fields
user-invocable and disable-model-invocation from SKILL.md frontmatter.
Both are optional booleans; absent values retain the spec defaults
(user-invocable: true, disable-model-invocation: false).

Adds depth and userInvocable to LoadedSkill so downstream registration
can honor the spec field and apply the OmO nesting policy on top.
flatName stays paired with depth > 0 for the flat slash-name shortcut.

No behavior change yet: the registration layer still treats every
skill the same. The next commit wires the filter.

* feat(skill-loader): honor user-invocable in slash picker with OmO nesting-inversion flag

Teaches the slash-command registration layer to respect the Claude
Code user-invocable frontmatter field. Skills with user-invocable:
false are omitted from the slash-command picker; skills with
user-invocable: true (or unset) remain visible per spec default.

Adds a per-workspace override: skills.hide_nested_by_default. When
true, nested skills whose frontmatter does not set user-invocable are
treated as if they had set user-invocable: false. Top-level skills
are never affected by the override. Explicit frontmatter values
always win over the inversion.

When a nested skill is visible, it registers under its flat short
name (/child) unless that name is already taken by a top-level skill
or a previously-registered nested sibling, in which case it falls
back to the path-prefixed unique name (/parent/child) and a warning
is logged.

Skills tool, UI Skills picker, and load_skills delegation remain
unaffected by visibility; hidden skills stay fully reachable by
agents.

* test(skill-loader): cover user-invocable parsing, inversion flag, and collisions

Ten new cases across two files:

skill-directory-loader.test.ts
- top-level and nested depth/flatName tagging
- user-invocable frontmatter parsing for true, false, and unset

skill-definition-record.test.ts
- spec-default behavior (nested visible when flag off, field unset)
- user-invocable: false hides a skill at any depth
- OmO hide_nested_by_default inversion flag hides nested skills
- opt-in via user-invocable: true survives the inversion flag
- top-level skills unaffected by the inversion flag
- nested-vs-nested flat-name collision falls back to prefixed path
- top-level wins over nested on flat-name collision regardless of
  iteration order

* fix(skill-loader): only index SKILL.md directories as skills, per Anthropic spec (#55)

* fix(skill-loader): only index SKILL.md directories as skills, per Anthropic spec

Stops indexing bare .md files inside skills directories as skills. Per
the Anthropic Agent Skills spec (https://agentskills.io/specification),
a skill is a directory containing SKILL.md. Sibling .md files within a
skill's directory are supporting material and must be referenced via
@path from the parent SKILL.md, not indexed as independent skills.

Affects both discovery paths:
- skill-directory-loader.ts (serial async, used by every loadX wrapper
  in loader.ts)
- async-loader.ts (concurrency-16 async, used by the blocking worker)

The {dirName}.md fallback — where a directory without SKILL.md can
still declare a skill via a file named after the directory — is
preserved. It's an OmO extension the spec does not cover, and removing
it would break existing skills that use this entrypoint pattern.

User-configured single-file skill sources via skills.sources in
opencode.json are unaffected: config-source-discovery.ts still honors
absolute .md paths because those are explicit, not directory walks.

* test(skill-loader): update fixtures to use SKILL.md directories

Six tests previously encoded the pre-spec behavior where plain .md
files inside a skills directory were indexed as skills. They now
either flip to assert the spec-correct behavior (discovery returns
zero skills for bare .md) or restructure fixtures to use proper
dir/SKILL.md layouts.

async-loader.test.ts:
- 'discovers direct .md files' now asserts direct .md files are
  ignored per spec
- 'preserves nested skill path names for nested direct markdown
  discovery' now asserts sibling .md files inside a nested directory
  are ignored when there is no SKILL.md entrypoint

blocking.test.ts:
- Four tests restructured from flat dir/skill-N.md fixtures to
  dir/skill-N/SKILL.md fixtures. Same assertions, spec-compliant
  inputs.
V
Vacbo committed
4b4ccaea469dad70c4105cdbddec26a1b1f89853
Parent: bc3e389
Committed by GitHub <noreply@github.com> on 4/21/2026, 10:33:49 PM