Skip to content

v0.0.6: discover skills in any repository layout, adopt outside skills, select removals by repository, one-document --json - #1

Merged
huija merged 9 commits into
mainfrom
feature/v0.0.6
Sep 29, 2026
Merged

huija merged 9 commits into
mainfrom
feature/v0.0.6

Conversation

@huija

@huija huija commented Sep 29, 2026

Copy link
Copy Markdown
Owner

This branch collects four behavior changes and their fixes, each with tests and documentation.

Discover skills wherever a repository puts them

get only looked at the repository root and below skills/, so a repository
that publishes its skills directly in the root, or under an agent directory
such as .agents/skills or .claude/skills, was reported as having no SKILL.md
at all. Discovery now scans the repository root, the conventional skills/
collection at any depth, every hidden <name>/skills collection, and the
root-level directories, in a fixed precedence order, collapsing duplicates.
An explicit subdirectory still wins over a name.

Complete a bare owner/repo address with github.com

A bare path like openai/skills used to become https://openai/skills, which
only resolves when the first segment happens to be a host. A two-segment path
whose first segment carries no dot is now completed with github.com, so
skillmod get openai/skills//gh-fix-ci works; a dotted first segment stays a
host, ports and Windows drive letters are left alone, and filesystem paths are
untouched. A fetch that fails names the URL it was resolved to.

Remove by repository

remove only accepted skill names and aliases, so clearing everything that
came from one repository meant naming each skill. A positional argument that
contains a / — which a skill name can never do — is now a repository
selector: one entry needs no choice, several are offered interactively exactly
like get offers a repository's skills, a non-interactive run lists them for
a rerun, and --all takes every entry the repository declares. The --agent
unlink path resolves the same selectors to names.

One document under --json

--json used to print the human summary on stderr alongside the document on
stdout, and a command that failed early printed no document at all. Every
human-readable channel is now closed in that mode — no summary, no progress —
and exactly one document is written whatever happens, with an error field
naming the failure and the exit code unchanged. entries always marshals as
an array. Only a cobra usage error keeps plain text on stderr, because no
command ran to produce a report.

Adopt skills added outside skillmod with sync --adopt

Skills that appear in the installation directory after a manual copy, an npx
install, or a hand edit used to be invisible to sync: no declaration, no
baseline. sync --adopt declares each one as a local entry with its current
content as the lock baseline, the same thing init does for a machine's
existing skills. No file is rewritten, so a later edit shows up as local drift
instead of being reset. A plain sync only names them and points at the flag,
--dry-run plans without asking, and --check refuses the combination
instead of silently ignoring it.

Other fixes

  • init on a machine with no existing skills claimed an empty manifest it had
    not written, because the non-interactive confirmation gate sat before the
    write phase. The gate now applies only when there is something to confirm.
  • Missing-manifest advice is split by scope: a project without one usually
    means the wrong directory, so the advice offers --global before suggesting
    init.
  • list answers from the cached reference snapshot younger than fifteen
    minutes, which keeps list --global fast; update remains the command that
    deliberately refreshes.
  • The README pair and the agent skill are rewritten in plain language, and the
    references cover the address grammar, the discovery layouts, the freshness
    window, the repository selector, sync --adopt, and the one-document --json
    contract.

make check, go test -race, govulncheck, the cross-platform test matrix,
and all six release cross-builds pass on this branch.

A project command without SKILL.mod usually means the user is standing
in the wrong directory — most often $HOME, whose installation
directories are the global scope's — so the project advice now offers
--global before suggesting init, and the global scope gets its own
advice spelling skillmod --global init/get instead of reusing the
project wording. Pins updated per the wording contract; adoption share
behavior is unchanged.
A bare path like openai/skills used to become https://openai/skills, which
only resolves when the first segment happens to be a host. Complete a
two-segment path without a dotted first segment with github.com instead,
because that is where skill repositories overwhelmingly live, so
skillmod get openai/skills//gh-fix-ci now works. A first segment that
carries a dot stays a host, a port or Windows drive letter is left alone,
and filesystem paths are untouched. The manifest still records the short
form, so the expansion is invisible in SKILL.mod.
Discovery used to look at the repository root and below skills/ only, so a
repository that publishes its skills directly in the root, or under an agent
directory such as .agents/skills or .claude/skills, was reported as having
no SKILL.md at all. Scan the conventional collection roots plus every
hidden <name>/skills directory and the root-level directories as well, in a
fixed order, with duplicates collapsed. An explicit subdirectory still wins
over a name, and the name shorthand now resolves anywhere a skill was
found, so a repository only has to look conventional to people who never
type a path.
remove only accepted skill names and aliases, so clearing everything that
came from one repository meant naming each skill. A positional argument that
contains a slash — which a skill name can never do — is now a repository
selector: it matches the entries declared from that repository and, when
more than one is declared, offers them interactively exactly like get
offers a repository's skills. A single-entry repository needs no choice, a
run with no channel to ask through lists the candidates instead, and a
repository nothing comes from is reported by name. The --agent unlink path
resolves the same selectors to names before delegating to share.
--json used to print the human summary on stderr alongside the document on
stdout, so a terminal showed the same run twice in two formats, and a
command that failed early — a refused selection, a missing manifest, a bad
address — printed no document at all, which made the flag useless for
automation. In JSON mode every human-readable channel is now closed: no
summary, no progress, and one document on stdout whatever happens, with an
error field naming the failure and the exit code unchanged. entries always
marshals as an array so a consumer never special-cases null. A cobra usage
error still exits 1 with plain text on stderr, because no command ran to
produce a report.
list refreshed every declared repository with ls-remote on each run, which
is what made list --global slow with many skills. Cache each snapshot with
the time it was taken and reuse one younger than fifteen minutes, so a
repeated list is local-only; update stays the command that deliberately
contacts the remote, and an absent, unreadable, or aged-out snapshot is
refreshed exactly as before. The fetch time is rewritten on every
ls-remote, even when the tags are unchanged, because it is what tells a
reader how old its cache is.
Skills that appear in the installation directory after a manual copy, an
npx install, or a hand edit were invisible to sync: no declaration, no
baseline, nothing keeping them from drifting. sync --adopt now declares
each one as a local entry with its current content as the lock baseline,
which is what init does for a machine's existing skills. No file is
rewritten, so a later edit shows up as local drift instead of being reset;
a directory a stale lock record occupies is left for prune, and two names
differing only in case are refused rather than written into a declaration
no other machine can materialize. A run without the flag only names the
undeclared skills and points at it, so the feature is discoverable from
the command that would otherwise ignore them. The summary states a run's
preserved local edits instead of printing the same stable no-change line
as an idle one; drift stays informational, and verify is the command that
judges it.
…vior

The README and the agent skill read like a spec: long sentences, abstract
nouns, and two sections that mostly repeated each other. Rewrite both in
plain language, drop "Why this exists" and "What you get" into one short
paragraph, and let the five-minute tour use the github.com default. The
Chinese README keeps the same outline, as the structure test requires.

The references are updated to match the behavior that changed with them:
the address grammar and the discovery layouts in manifests.md, the
reference-snapshot freshness window in storage.md, the repository selector
and sync --adopt in use-cases.md, and the one-document --json contract
with its error field in automation.md. CONTRIBUTING.md picks up the branch
protection note that was sitting in the stash: changes reach main only
through pull requests, so the tag landing procedure waits on the PR CI
run rather than a direct push.
A scan that found no skill reported that it had already generated an empty
manifest and then returned from the shared non-interactive confirmation
gate, which sits before the write phase. A script or CI run therefore
claimed a write that never happened, and the prompt it stopped at is one a
non-interactive environment cannot answer.

An empty scan has nothing to accept, so the gate now applies only when the
run has an entry to confirm; it still closes for a scan that found
something, since silently accepting a candidate would declare a skill the
user never approved. The empty-scan note picks its tense from the run it
belongs to: the write path reports the manifest as generated, and a dry run
reports the one it would generate, matching the "no files were written"
note that follows it in the same report.

TestInit_EmptyRunKeepsTheManifestItReports covers the empty write, the
dry-run tense, and the still-closed gate, a path no test had reached.
@huija
huija merged commit b2c4442 into main Sep 29, 2026
10 checks passed
@huija
huija deleted the feature/v0.0.6 branch September 29, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant