Liking cljdoc? Tell your friends :D

toolnexus.skill


apply-skills-filterclj/s

(apply-skills-filter by-name filter-map)

SPEC §3 S2 — the per-agent allowlist, semantics IDENTICAL to the MCP per-server tools filter: nil or empty ⇒ every skill; at least one true ⇒ an allowlist of exactly the true-mapped names; only false values ⇒ a drop-list over the all-on baseline. Unknown names are ignored.

Returns [filtered-by-name unmatched-names]. The unmatched list is RETURNED rather than only logged so the requirement is testable without capturing stdout — the warn line is emitted from load-skills.

SPEC §3 S2 — the per-agent allowlist, semantics IDENTICAL to the MCP
per-server tools filter: nil or empty ⇒ every skill; at least one `true` ⇒ an
allowlist of exactly the true-mapped names; only `false` values ⇒ a drop-list
over the all-on baseline. Unknown names are ignored.

Returns `[filtered-by-name unmatched-names]`. The unmatched list is RETURNED
rather than only logged so the requirement is testable without capturing
stdout — the warn line is emitted from `load-skills`.
sourceraw docstring

candidatesclj/s

(candidates root)

Every SKILL.md under root, in DISCOVERY ORDER, as a parsed skill or a typed skip.

DISCOVERY ORDER is pinned, not inherited: discovery-compare over the path RELATIVE to this root's logical base — depth, then code point (A15/A1c). A symlink sorts at the path it was DISCOVERED at, not at its target, because that is the only one of the two a caller can see.

It is sorted HERE rather than relied upon from koine.fs/find-files even though find-files sorts: first-name-wins is order-dependent, a rule that decides which duplicate survives may not rest on another library's promise, and find-files' own order is plain lexicographic — which is precisely the order A15 corrects.

Note find-files takes a SUFFIX, not a glob, so the suffix is "/SKILL.md" — a bare "SKILL.md" would also match a file named MYSKILL.md.

Every SKILL.md under `root`, in DISCOVERY ORDER, as a parsed skill or a typed
skip.

DISCOVERY ORDER is pinned, not inherited: `discovery-compare` over the path
RELATIVE to this root's logical base — depth, then code point (A15/A1c). A
symlink sorts at the path it was DISCOVERED at, not at its target, because
that is the only one of the two a caller can see.

It is sorted HERE rather than relied upon from `koine.fs/find-files` even
though find-files sorts: first-name-wins is order-dependent, a rule that
decides which duplicate survives may not rest on another library's promise,
and find-files' own order is plain lexicographic — which is precisely the
order A15 corrects.

Note find-files takes a SUFFIX, not a glob, so the suffix is "/SKILL.md" —
a bare "SKILL.md" would also match a file named MYSKILL.md.
sourceraw docstring

default-sample-limitclj/s

source

discovery-compareclj/s

(discovery-compare a b)

The §3 discovery order over two paths RELATIVE to a root's logical base: DEPTH ascending, then Unicode CODE POINT (addendum A15, correcting A1a).

WHY DEPTH FIRST, and why A1a alone was wrong. §3 and the OpenSpec scenario both say a SHALLOWER path beats a nested copy of the same name. A pure code-point sort does not deliver that — it delivers it only for names that happen to sort before the nested directory's first segment:

docx -> docx/            (d < s, top-level wins)
xlsx -> synced/…/xlsx    (x > s, the NESTED copy wins)

So the winner depended on the skill's first letter relative to a sibling directory's name, which is indefensible and is not what anyone intended. The external consumer found it on a real corpus. Sorting by depth first makes the rule that was already written down actually true, for every name.

A1a/A1c are unchanged and remain the TIE-BREAK WITHIN a depth: code POINT, via tool/compare-strings, never a locale collator, never case folding, never compare (which is UTF-16 code-UNIT order on the JVM host and disagrees for anything above U+FFFF).

The §3 discovery order over two paths RELATIVE to a root's logical base:
DEPTH ascending, then Unicode CODE POINT (addendum A15, correcting A1a).

WHY DEPTH FIRST, and why A1a alone was wrong. §3 and the OpenSpec scenario
both say a SHALLOWER path beats a nested copy of the same name. A pure
code-point sort does not deliver that — it delivers it only for names that
happen to sort before the nested directory's first segment:

    docx -> docx/            (d < s, top-level wins)
    xlsx -> synced/…/xlsx    (x > s, the NESTED copy wins)

So the winner depended on the skill's first letter relative to a sibling
directory's name, which is indefensible and is not what anyone intended. The
external consumer found it on a real corpus. Sorting by depth first makes the
rule that was already written down actually true, for every name.

A1a/A1c are unchanged and remain the TIE-BREAK WITHIN a depth: code POINT,
via `tool/compare-strings`, never a locale collator, never case folding,
never `compare` (which is UTF-16 code-UNIT order on the JVM host and
disagrees for anything above U+FFFF).
sourceraw docstring

execute-skillclj/s

(execute-skill loaded skill-name)
(execute-skill loaded skill-name limit)

The skill tool's execute, as a plain fn over a loaded map.

An unknown name is NOT a throw — SPEC §3 step 1 makes it ToolResult{isError:true}, so the model sees it and can retry.

DIVERGENCE, following the ports: SPEC §3 prose says "Available: ", while golang/skill.go, js, python, java, csharp and elixir all emit "Available skills: " (and the literal none when there are no skills). We match the ports. SPEC §3 should be corrected.

The `skill` tool's execute, as a plain fn over a loaded map.

An unknown name is NOT a throw — SPEC §3 step 1 makes it
`ToolResult{isError:true}`, so the model sees it and can retry.

DIVERGENCE, following the ports: SPEC §3 prose says `"Available: "`, while
golang/skill.go, js, python, java, csharp and elixir all emit
`"Available skills: "` (and the literal `none` when there are no skills).
We match the ports. SPEC §3 should be corrected.
sourceraw docstring

file-nameclj/s

(file-name path)

The last segment of path. NOT named base-name/basename — nothing in clojure.core collides, but the name is kept explicit for readability.

The last segment of `path`. NOT named `base-name`/`basename` — nothing in
clojure.core collides, but the name is kept explicit for readability.
sourceraw docstring

list-skillsclj/s

(list-skills input)

SPEC §3 S3 — list/validate inventory: {:skills … :skipped …}, no toolkit wired, nothing left open.

DELIBERATELY UNFILTERED, following js/src/skill.ts listSkills, which ignores opts.filter entirely: the inventory is what you AUTHOR an allowlist from, so filtering it would be circular.

SPEC §3 S3 — list/validate inventory: `{:skills … :skipped …}`, no toolkit
wired, nothing left open.

DELIBERATELY UNFILTERED, following js/src/skill.ts `listSkills`, which ignores
`opts.filter` entirely: the inventory is what you AUTHOR an allowlist from, so
filtering it would be circular.
sourceraw docstring

load-optsclj/s

(load-opts input)

load-skills accepts every shape js/src/skill.ts' loadSkills accepts, plus the two data shapes: a root string, a seq of roots, a seq of skill defs, one skill def, or the full options map.

`load-skills` accepts every shape js/src/skill.ts' `loadSkills` accepts, plus
the two data shapes: a root string, a seq of roots, a seq of skill defs, one
skill def, or the full options map.
sourceraw docstring

load-skillsclj/s

(load-skills input)

Discover skills from every source §3 offers and shape them into one loaded map.

input is a root string, a seq of roots, a seq of skill DEFS, or an options map {:dirs … :skills … :filter … :sample-limit …} — the same union js/src/skill.ts' loadSkills takes. Directory roots are collected first, in the order given, then the data defs, so the existing first-name-wins rule resolves collisions ACROSS sources without needing a second rule.

Returns {:skills [info…] :by-name {} :skipped [] :filter-unmatched [] :sample-limit n}. A caller passing only roots gets byte-identical behaviour to before this option existed.

Discover skills from every source §3 offers and shape them into one loaded
map.

`input` is a root string, a seq of roots, a seq of skill DEFS, or an options
map `{:dirs … :skills … :filter … :sample-limit …}` — the same union
js/src/skill.ts' `loadSkills` takes. Directory roots are collected first, in
the order given, then the data defs, so the existing first-name-wins rule
resolves collisions ACROSS sources without needing a second rule.

Returns `{:skills [info…] :by-name {} :skipped [] :filter-unmatched []
:sample-limit n}`. A caller passing only roots gets byte-identical behaviour
to before this option existed.
sourceraw docstring

merge-candidatesclj/s

(merge-candidates cands)

Dedupe by name, FIRST WINS; later duplicates become typed skips. Returns {:skills [info …] :by-name {name info} :skipped [skip …]}.

Dedupe by name, FIRST WINS; later duplicates become typed skips.
Returns `{:skills [info …] :by-name {name info} :skipped [skip …]}`.
sourceraw docstring

no-skills-messageclj/s

source

parent-dirclj/s

(parent-dir path)

The directory part of path, or "." when there is no separator.

The directory part of `path`, or "." when there is no separator.
sourceraw docstring

sample-sibling-filesclj/s

(sample-sibling-files dir limit)

Up to limit files under the skill's directory, EXCLUDING every SKILL.md.

The exclusion is the detail that moves bytes and that spike s15 got wrong: golang/skill.go:217 and js/src/skill.ts:198 both guard the sampler with entry.Name() != "SKILL.md". Leaving SKILL.md in adds one <file> line and the shared hello-world output stops matching the other ports.

limit: 0 ⇒ default 10 · n>0 ⇒ cap at n · -1 ⇒ nil, i.e. sampling disabled (SPEC §3 S5).

ORDER — deliberate divergence. SPEC §3 does not pin the sample's order, and no shipped port sorts: js/go/python all walk a DFS stack over raw readdir order and slice the first limit entries. Go's os.ReadDir sorts, Python's os.scandir does not, so with >1 sibling the shipped ports are not guaranteed to agree with each other. WE SORT, because a port whose whole claim is byte-identical output on two hosts cannot depend on readdir order. Pending a SPEC fix that pins the sampler's order for everyone; recorded here rather than smuggled in.

Up to `limit` files under the skill's directory, EXCLUDING every SKILL.md.

The exclusion is the detail that moves bytes and that spike s15 got wrong:
`golang/skill.go:217` and `js/src/skill.ts:198` both guard the sampler with
`entry.Name() != "SKILL.md"`. Leaving SKILL.md in adds one `<file>` line and
the shared hello-world output stops matching the other ports.

`limit`: 0 ⇒ default 10 · n>0 ⇒ cap at n · -1 ⇒ nil, i.e. sampling disabled
(SPEC §3 S5).

ORDER — deliberate divergence. SPEC §3 does not pin the sample's order, and no
shipped port sorts: js/go/python all walk a DFS stack over raw readdir order
and slice the first `limit` entries. Go's os.ReadDir sorts, Python's
os.scandir does not, so with >1 sibling the shipped ports are not guaranteed
to agree with each other. WE SORT, because a port whose whole claim is
byte-identical output on two hosts cannot depend on readdir order. Pending a
SPEC fix that pins the sampler's order for everyone; recorded here rather than
smuggled in.
sourceraw docstring

skill-filesclj/s

(skill-files info limit)

The <skill_files> list for one skill, or nil when there is no block.

Two sources, one rule, following js/src/skill.ts' execute:

  • an on-disk skill samples its siblings and ALWAYS emits the block (even empty) unless sampling is off — that is the shipped byte-exact behaviour;
  • a data/provider skill lists its SUPPLIED logical resources, in the order supplied, and omits the block entirely when it has none (an instruction-only skill has nothing to disclose).
The `<skill_files>` list for one skill, or nil when there is no block.

Two sources, one rule, following js/src/skill.ts' execute:
  * an on-disk skill samples its siblings and ALWAYS emits the block (even
    empty) unless sampling is off — that is the shipped byte-exact behaviour;
  * a data/provider skill lists its SUPPLIED logical resources, in the order
    supplied, and omits the block entirely when it has none (an
    instruction-only skill has nothing to disclose).
sourceraw docstring

skill-outputclj/s

(skill-output skill files)

SPEC §0.6. Built by explicit concatenation, never a template: every newline here is part of the contract, and there is no trailing newline (the ports join lines). files = nil disables sampling (S5 -1): the <skill_files> block AND its "Note: file list is sampled." line are both omitted.

SPEC §0.6. Built by explicit concatenation, never a template: every newline
here is part of the contract, and there is no trailing newline (the ports join
lines). `files` = nil disables sampling (S5 `-1`): the `<skill_files>` block
AND its "Note: file list is sampled." line are both omitted.
sourceraw docstring

skill-toolclj/s

(skill-tool loaded)
(skill-tool loaded {:keys [sample-limit]})

The single skill tool (SPEC §3), shipped by default alongside the skills.

The single `skill` tool (SPEC §3), shipped by default alongside the skills.
sourceraw docstring

skill-tool-descriptionclj/s

SPEC §3 skill.txt — verbatim from opencode.

SPEC §3 skill.txt — verbatim from opencode.
sourceraw docstring

skill-tool-input-schemaclj/s

source

skills-promptclj/s

(skills-prompt loaded)

preamble + "\n\n" + "## Available Skills" + one line per DESCRIBED skill, sorted by name. No described skill at all ⇒ the no-skills message, with NO preamble (SPEC §3).

preamble + "\n\n" + "## Available Skills" + one line per DESCRIBED skill,
sorted by name. No described skill at all ⇒ the no-skills message, with NO
preamble (SPEC §3).
sourceraw docstring

skills-prompt-preambleclj/s

SPEC §3 — the fixed instruction preamble, copied verbatim from golang/skill.go's SkillsPromptPreamble. Do not reword.

SPEC §3 — the fixed instruction preamble, copied verbatim from
`golang/skill.go`'s `SkillsPromptPreamble`. Do not reword.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close