Skip to content

Releases: kaisers-io/refs

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 05:03

Upgrading

Run refs sync after updating. Two things are worth expecting on that first run.

Packages may be reported as missing that are still on disk. Negated workspace patterns are
applied now, so a package a repository excludes (!examples/vue/2*) is no longer a workspace
member — and an entry registered for one before this release no longer verifies. Nothing is removed
automatically; the finding names the entry and leaves the decision alone.

Packages that arrived upstream since the last sync are reported. Only those: a package the
configuration never had and that did not arrive in the fetched range stays unmentioned, however
long it has been there.

Added

  • refs sync now reports a package that arrived upstream, and refs edit --create registers
    it.
    The drift probe checked the packages the configuration already had, so a package added
    upstream after refs add stayed invisible — and there was no command to register one either:
    refs add refuses an already-tracked ref, and every refs edit mode needs an entry to edit. The
    only instruction anyone could give was "hand-edit config.toml".

    refs sync answers "did upstream gain a package?" from the range it just fetched, not by
    comparing a scan against the configuration. That distinction is the whole design: a scan cannot
    tell a package that just arrived from one the ref's owner deliberately never tracked, because
    there is no inventory of what was there before — the fetch range is that inventory. A ref whose
    owner tracks 3 packages out of 140 hears about the other 137 exactly never. refs doctor lists
    every unregistered member instead, because it was asked to.

    The question it asks of that range is about package NAMES, not manifest paths. A package
    renamed in place modifies its manifest rather than adding one, and a package merely moved to
    another directory adds one without being new — so a path-based reading is wrong in both
    directions. Only the manifests the range actually changed have to be read out of history: an
    untouched manifest is byte-identical at both ends, so the name it carries now is the name it
    carried before.

    The repair is a command now rather than a config fragment, with the ref key filled in so it
    runs as printed:

    refs edit 'github.com/acme/alpha' --package '@acme/new' --create --path 'packages/new' \
      --description "<what it is>"
    

    It is a distinct mode, not an upsert — an ordinary field edit naming an unregistered package
    still fails with not_found, so a typo in --package can never become a new entry. The
    finding carries name and path, both verified against the checkout and both shell-quoted
    (being verified makes a value true, not shell-safe: zPackagePath permits $() and a manifest
    name is checked only for being non-empty), and deliberately no description: a manifest description is untrusted third-party content, and copying it moves it
    into a file refs later reads as its own configuration. The skill instructs agents to propose the
    registration and wait for the user to agree, rather than run it on their own initiative.

Fixed

  • Workspace patterns are matched by minimatch, the matcher npm itself uses. Hand-written
    matching disagreed with the real resolvers in five distinct ways, each found only after the last
    was fixed: extglob (@(a|b)) read as a literal directory name, trailing slashes treated
    symmetrically where minimatch is asymmetric, repeated separators silently matching nothing, and
    two more. Delegating removes that class of defect rather than the current instance of it.

    Measured before choosing: picomatch disagrees with minimatch on ten of 154 comparisons over
    the shapes this scanner supports, exactly on trailing slashes and repeated separators — so it is
    not a drop-in. pnpm matches through picomatch but normalizes first, and was measured to agree
    with minimatch on every one of those shapes, so one matcher covers both ecosystems.

    Walking stays here: containment guards, the diagnostics that say why a scan came up short, and
    the deliberate one-level depth policy are unchanged. minimatch answers only whether a path
    matches a pattern. Nothing new is installed by refs users — the CLI publishes a bundle with no
    dependencies — and that bundle grows by 24 KB.

    One behaviour improves as a consequence: a negation in a shape this scanner cannot WALK
    (!packages/{a,b}) is now applied, because applying an exclusion needs matching and never
    walking. It used to be reported as unsupported and silently ignored.

  • Negated workspace patterns are applied instead of ignored. !packages/fixtures was dropped
    as an unsupported shape (a v1 simplification), so refs add registered packages the repository
    had explicitly excluded, and every finding about a repository declaring one was silenced —
    TanStack Query declares two, and all hundred of its packages came back unverifiable. Negations
    are now expanded exactly like inclusive patterns and subtracted from the result, which makes the
    scan an accurate statement of membership rather than an approximation of one.

    A wildcard inside the last segment (examples/vue/2*) is supported too, since that is the shape
    real repositories exclude by, and a negation nobody can expand costs every finding about the
    repository rather than just the paths it names. Glob syntax that is still unimplemented —
    {a,b}, ?, […] — now reports unsupported_pattern rather than reading as a literal
    directory name and silently matching nothing.

  • A monorepo can now be resolved by the name in its own root manifest. Workspace detection
    expands the globs a repository declares, and a workspace root is not one of its own targets — so a
    root that names itself was registered nowhere, and refs resolve @acme/toolkit came back empty
    for a repository that was tracked all along. refs add now registers a named root at path: "."
    alongside the workspace members.

    Both pnpm and Yarn address a workspace root by that name (pnpm --filter <root-name>,
    yarn workspace <root-name>); npm and Turborepo use a positional handle instead. Of eighteen
    well-known monorepos surveyed, eight carry a name someone would plausibly use for the repository
    and ten carry a throwaway like root or monorepo-root — which is what settles it: registering
    the name costs nothing where it is a throwaway, since nobody resolves "root", and answers the
    question where it is not.

    Two things this deliberately does not do. A repository that declares no workspaces is untouched:
    refs add's npm fallback owns that shape, and probing the root there would displace a locator it
    did not choose — as it also would where a workspace declaration selects nothing, so the package
    named in an npm:<pkg> source survives there too. And where a workspace member already claims the
    root's name — @remix-run/react-router is a real example, in a repository that also publishes
    react-router — the member wins and the root is simply not registered, which costs that
    repository nothing it had before. That rule lives in detection itself rather than in refs add,
    so relocation agrees with registration: a member that moves is still found uniquely, instead of
    becoming ambiguous against a same-named root and leaving resolve with no path for a package
    that is plainly there. And a root is never reported as a package's new location: its name is an
    alias for the repository, so a member that upstream deletes is reported as gone rather than as
    having moved to the repository root — which would have sent a caller to the wrong directory and
    described a move that never happened.

    Refs tracked before this change keep the package map they were given, and no command adds one
    entry to an existing ref — refs add refuses a tracked ref, refs edit --package needs an entry
    to edit. So refs sync and refs doctor's config-drift check now report a root the
    configuration does not register, with the entry to add — including the path registration would
    actually use, which is the member's rather than the root's where a workspace member declares the
    same name. That costs one manifest read per ref, and a workspace scan only where there is
    something to report; it is asked only of refs that already register packages, since a plain
    reference repository registers none on purpose and is left alone.

    The root package takes the ref's own description when its manifest carries none, which is the
    ordinary case for a private workspace root. That is not the per-package fallback refs add
    otherwise refuses: the root is not a package beside the repository, it is that repository.

  • A failed lookup no longer reads as an absent repository. refs resolve exits 4 when a query
    matches nothing, and the message ended "run refs list, or add it: refs add <url>". That second
    half is a guess: a query can miss every route while the repository is tracked perfectly well under
    another identifier — a monorepo root whose own package name was never registered, for instance.
    An agent read the suggestion as confirmation and told someone a repository they had tracked was
    not tracked, then stopped.

    The message now states what was searched and points at evidence rather than prescribing a fix, and
    --json carries a reason on resolve's routing misses: unmatched_query (nothing matched, by
    any route), package_not_registered (the ref is tracked and registers no such package), or
    ref_not_registered (a canonical git url named an absent ref — the one case where adding it is
    the right answer, since only a canonical url establishes which ref was meant). There is deliberately no reason meaning "this repository does not exist", because nothing
    refs can observe establishes that; and reason is absent on every other not_found, where its
    absence mea...

Read more

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 11:33
0115dfe

Changed

  • The skill's version-question flow moved into its own file. Every question about a
    dependency's source loads SKILL.md and INVESTIGATE.md in full, and 30 % of
    INVESTIGATE.md was a block on resolving versions to tags and diffing between them — read
    on every plain source question, used on almost none of them. It is now VERSIONS.md, with a
    route of its own in SKILL.md §5 and a one-line pointer at the end of INVESTIGATE.md, so
    a mis-route costs one extra read rather than a wrong answer.

    Alongside it, a compression pass over what remains: the worker output contract was stated
    twice and is now stated once, the five clickable-link rules became one normalization, and a
    handful of sentences that repeated something said a few lines earlier are gone. Nothing
    behavioural was removed — the capability gate, the trust boundary, the five hard rules and
    the worker prompt's own safety rules are untouched, and the measured partial-clone cost model
    (which git commands fetch blobs, and that git blame fetches one per visited revision) is
    refs' own measurement rather than something the term "partial clone" implies, so it stays
    verbatim.

    A plain source question now loads 20 % less. A version question loads about what it did
    before, plus one extra file read.

Added

  • refs sync and refs doctor now report configuration that has fallen behind its upstream.
    A configured package path is only a locator, and upstream can delete or move what sits at it.
    Until now only refs resolve noticed, for the one package an agent happened to route to, and it
    persisted nothing — so a package deleted upstream could sit wrong in the configuration
    indefinitely while every other package in the same checkout went uninspected.

    Each successful refs sync result now carries a nested structure: {status, packages}, probed
    inside the lock the sync already holds, right after the checkout was updated. Nothing is stored:
    the answer is reported and thrown away, so there is no drift state that can itself go stale. A
    removal and a relocation are reported as different findings, because they need opposite repairs —
    telling an agent to "fix the path" of a package upstream deleted sends it looking for something
    that is not there. Human output gains indented lines under the affected ref and stays silent when
    everything resolves; the summary counts and exit code are untouched, since a drifted ref synced
    perfectly well.

    Only refs that actually sync are probed, which keeps --stale-only a genuine no-op — and is why
    refs doctor gains a config-drift check as the deliberate "check everything now" counterpart.
    It takes each ref's lock with a short timeout and reports the ref as busy rather than waiting,
    writes nothing, and reports warn rather than fail: the configuration has fallen behind,
    nothing is broken. refs list deliberately stays blind — without stored state it would
    turn a cheap inventory command into a locking filesystem sweep.

  • refs resolve answers in one call what used to take three. The skill's investigation flow
    began resolvesyncresolve again, and the third call was not ceremony: package
    verification had described the checkout as it was before the sync, so reusing that answer meant
    reporting a path that no longer necessarily held what it claimed. --sync-if-stale fetches (or
    clones) only when the ref is stale or its checkout absent, and everything it reports describes the
    checkout afterwards. The rule has left the skill and become code.

    It refuses, rather than syncing, when the checkout is unmanaged or unverifiable. sync
    hard-resets and cleans; running it against a directory whose identity was never established is
    how a stray clone loses its history. A failing sync fails the command rather than returning a
    success envelope containing a stale path.

  • refs resolve --project <dir> reports the version a project has installed. The skill used to
    tell the agent to read the project's lockfile by hand, and nothing in refs touched one — so the
    deterministic half of every "what changed between my version and a newer one" question was done
    by the least deterministic component available, against pnpm's peer-qualified keys, aliases,
    overrides and three vendor-specific formats.

    The answer is read from node_modules, walking up in Node's own lookup order, and stops at the
    first installation slot that exists rather than the first readable manifest — falling through to
    an ancestor would report a shadowed install Node would not have loaded. There is deliberately no
    lockfile fallback: a lockfile says what should be installed, node_modules says what is, and
    the second is the question. installed.status is found, not_materialized,
    unsupported_layout (Yarn PnP, detected but never loaded — .pnp.cjs is project code) or
    unverifiable.

  • refs resolve --ref <ref> scopes a query to one ref's packages. A package name registered by
    several refs used to be answered with "use the full ref key" — advice the command could not
    honour, because a full-key query routes by ref and comes back with package: null. The error
    now names a remedy that exists.

  • refs doctor gained a locks check. A held lock used to be invisible: acquisition failed with a
    message that named no owner, and doctor had no lock check at all — so the one command meant to
    answer "is something stuck?" could not see the thing that was stuck. The check lists every entry
    in the locks directory with its recorded owner, how long it has been held, and against which
    window.

    A held lock is not a warning by itself: that is what a concurrent refs sync looks like, so
    it reports ok with the holder listed. warn is reserved for something that will not resolve on
    its own — a recorded process that is gone, a lock past its window and still there, metadata that
    cannot be read, or something that is not a lock at all occupying a lock name. Like every other
    doctor warning, it does not change the exit code.

Changed

  • refs resolve establishes that the path it hands back is really this ref's checkout. It used
    to report presence from a .git entry alone, while add and sync both ran a stronger guard
    before mutating — so the one command whose result is read as "the source is here" was the one
    that did not check what was there. A manual clone at the derived path, a half-finished remove,
    a restored backup or a symlinked second home all produced a confident answer about the wrong
    repository, with no error and no warning.

    Every reply now carries checkout: {status, reason?}managed, missing, unmanaged or
    unverifiable — read straight out of .git/config without spawning git, so the hot path stays
    subprocess-free. The origin URL is never echoed back in reason; it can carry credentials.

    managed requires the core.hooksPath marker to be this home's hooks directory, not merely
    present — the comparison add already makes — so a manual clone that sets it for its own purposes
    does not pass. A config git itself would reject (an unterminated quote, an undefined escape, a
    line that is neither a section nor an assignment) is unverifiable rather than partially read: a
    file git would not accept is not evidence of identity.

    Package verification is gated on it. A manifest read inside an unrelated checkout can answer
    verified for a package that has nothing to do with the query, so anything other than managed
    or missing now yields package.status: "unverifiable" instead of a confident location.

    missing is unchanged and still means checkout.status === "missing". Callers should branch on
    checkout.status, which answers the question missing was often assumed to.

  • The "lock is held" error now says who holds it and for how long. It used to read lock <name> is held — another refs process is running, which left no way to tell a running sync from
    something that crashed 90 seconds ago. It now names the recorded pid, whether that pid is still
    present, how long the lock has been held, and when it becomes reclaimable.

    It deliberately says "recorded pid … is present (identity not verified)" rather than "held by pid
    …": only ESRCH establishes that a process is gone, so a pid that answers may equally be an
    unrelated process that reused the number. And it says "reclaimable", never "released
    automatically" — nothing removes a lock in the background; the phrase means the next acquisition
    attempt is entitled to take it.

  • An operation that ran without the lock it asked for now fails instead of reporting success.
    A lock can still be lost while its holder works — a stolen lock is detected by the next renewal,
    or by release finding a foreign token. Previously the callback's result was returned as if
    nothing had happened. It is now reported as a conflict (exit code 5), because the work ran
    without the mutual exclusion it requested and its result is not trustworthy. An operation that
    failed on its own keeps precedence: its own error is what the caller sees.

Fixed

  • The rm -rf commands refs doctor and refs add suggest are now quoted. Ref keys derive from
    user-supplied urls and permit spaces, $(), backticks, semicolons and quotes, and the refs home
    itself routinely sits under a path containing a space — so pasting an unquoted suggestion could
    delete several wrong paths and leave the intended one, or execute a command substitution embedded
    in a repository name. The form is now rm -rf -- '<path>'; -- additionally stops a path
    beginning with - from parsing as options.

  • A pid in a lock's metadata is now required to be a positive integer within the range
    process.kill accepts. 0 and negative values are
    process-group selectors for process.kill, so metadat...

Read more

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 16:10
a64d22c

Added

  • refs resolve now verifies that the package it routes to is actually where the config says
    it is. A configured path is only a locator; the package name is its identity, and upstream
    repos restructure on their own schedule. Previously a package that had moved — or a different
    package that had taken over its directory — was handed back regardless, so an agent read the
    wrong source and answered confidently. That failure produced no error and no warning.

    package.status now reports what was established: verified, relocated (found at exactly
    one new path, which is returned in place of the stale one), unmaterialized (no checkout yet),
    unverifiable (verification could not complete — reason says why), ambiguous (the name
    exists at several paths, listed in candidates), or missing. All six exit 0; see
    docs/commands.md for the full contract.

    relocated corrects the answer for that call only and never writes to config.toml. Persist
    it with refs edit <ref> --package <name> path <new-path>.

Changed

  • resolve's package.local_path can now be null. It is null for missing and
    ambiguous, where no safe location is known. A caller that treated a zero exit as "here is a
    usable path" must check package.status first; previously the field was always a string.

  • Workspace detection now reports why it found nothing. An unreadable or malformed workspace
    declaration, an unreadable manifest, a candidate resolving outside the repo, an unsupported
    pattern, a package directory reachable only through a symlink — each used to collapse into the
    same empty result, leaving a transient read error indistinguishable from "every package was
    removed". Each is now reported, and a scan carrying any of them is treated as possibly
    incomplete: it can neither conclude that a package is gone nor that a single sighting of one is
    unique. refs add is unaffected — it consumes the same best-effort list it always has.

    A manifest that reads fine but declares no usable name is reported too, but does not make
    a scan incomplete: there is demonstrably no resolvable package at that path. Nameless manifests
    are common enough (zod's own repository root has none) that treating them as failures would
    permanently suppress detection for those repos.

    One limit is deliberate and worth knowing: a scan only covers what the repo's workspace
    declaration points at. A package registered by refs add's npm fallback — at path: ".", or
    the packument's directory — lives outside that coverage, so if it moves, resolve reports
    unverifiable rather than guessing. It never reports missing from a scan that had nowhere
    to look.

v0.9.0

Choose a tag to compare

@boriskaiser boriskaiser released this 13 Aug 12:48
f3208c6

Added

  • refs tells you when a newer version is published. refs sync and refs doctor ask npm at most
    once a day and cache the answer; refs sync mentions a newer release in its warnings, and
    refs doctor reports it as a cli-update check. refs --version is untouched — it stays exactly
    one version line on stdout, because the skill's capability gate and any script parse it.

    Both switches live in [updates] in config.toml and default to on: check governs the registry
    request everywhere, notify the routine path only. notify = false with check = true is
    "don't interrupt me, but answer when I ask" — refs sync neither asks nor mentions, refs doctor
    still does both. REFS_UPDATE_CHECK overrides check (0 off, 1 on), and the check is off in
    CI. The table is absent from a config that wants the defaults, and refs never writes one.

    Nothing about it is load-bearing: an unreachable registry, a malformed answer or an unwritable
    cache all mean "we don't know" and are never reported as a fault. The registry host is hardcoded
    rather than read from npm configuration, only a plain x.y.z is accepted from the response, and
    the update command is printed for you to run — refs does not install itself.

Fixed

  • A ref can be recorded without a tag_format. Finalizing an add used to reject a proposal whose
    tag_format_candidate was null, which left one option for a repository that publishes no tags:
    invent a convention. A real user hit this and was asked to confirm v{version} for two
    repositories that have no tags at all — a claim nobody had verified, written into config.toml
    where later agents read it as fact. The candidate now survives finalize as an absent field.

    refs tag is the only command that reads it, and it exits 3 (validation) when there is none,
    naming the ref — or the package, with the --package form of the fix. The distinction from 4
    carries information: 3 means this ref cannot resolve any version, 4 means this particular
    version was never tagged. The skill's add flow gained an explicit branch for the null case, so
    an agent reports the absence instead of proposing something to fill the gap.

    A format already recorded can only be removed by editing config.toml directly; refs edit can
    set one but has no way to unset it.

v0.8.3

Choose a tag to compare

@boriskaiser boriskaiser released this 13 Aug 12:48
95a6c82

Added

  • The skill states a trust boundary. Everything inside a managed checkout is untrusted third-party
    content — README, comments, commit messages, and any AGENTS.md or CLAUDE.md a tracked repo
    ships — so it is evidence to read and never instruction to follow. Documentation stays evidence,
    including a repo's own contributor and agent docs; what goes to the user as a finding is content
    targeting the agent that reads it. Both worker flows carry the rule into their prompts. This
    narrows the blast radius of indirect prompt injection; it is not a sandbox, and SECURITY.md says
    so.

Changed

  • The skill installs nothing. Its capability gate used to ask permission and then run
    npm i -g @kaisers-io/refs itself; it now prints the command — pinned to the version the skill
    was written against, not @latest — and stops until the user has run it. A skill that installs
    the executable giving it its capabilities is a bootstrap trust boundary worth keeping explicit,
    and Anthropic's skill documentation discourages global installs from a skill.

  • The --json examples in the skill's command reference use placeholder repositories
    (example-org/…) instead of real third-party ones, and say up front that they are illustrative
    output rather than repositories the skill fetches.

v0.8.2

Choose a tag to compare

@boriskaiser boriskaiser released this 10 Aug 20:41
336e703

Fixed

  • Documentation that described flows which did not work. The README's quickstart failed in both
    of its branches, the skill's onboarding handed the user a prompt naming a zod version that was
    never tagged, and docs/commands.md showed a stored url and a package count the CLI does not
    produce. Every documented command was re-run and corrected against its real output, and the
    skill check's Windows note was still describing the gap 0.8.1 closed.

  • git_transport was documented as overridable per ref. It can be written there, since the
    override schema is derived from the settings schema, but nothing reads it: only refs add
    consults the setting, and add refuses a key that is already configured. Documented as inert.

Changed

  • The README and the package page are rewritten around the agent workflow, which is how refs is
    meant to be used, with the manual CLI route kept as the side note it is.

  • The package description now matches the repository's.

v0.8.1

Choose a tag to compare

@boriskaiser boriskaiser released this 10 Aug 17:32
219f11a

Fixed

  • Locked commands could hang instead of timing out. If a lock looked abandoned but could not
    actually be reclaimed — another process holding the steal claim, or Windows refusing to remove
    the directory while a handle was still open inside it — the acquire loop retried without ever
    consulting its deadline, so the ten-second acquisition budget never applied and the command spun
    until interrupted. Both unbounded paths now honour the deadline and fail with the conflict error
    (exit code 5) as documented.

  • refs doctor reported a correctly installed skill as missing on native Windows. Its three
    global search locations were derived from $HOME, which Windows typically leaves unset, while
    the installer resolves os.homedir() — so all three silently dropped out of the search and the
    check reported warn, "not found in the locations this check knows about". It now reads the
    same home directory the installer writes to. macOS and Linux were unaffected, the two agreeing
    there.

Security

  • Canonicalizing a git url no longer takes quadratic time. Trailing slashes were trimmed with a
    pattern anchored at the end of the string, which backtracks through a run of slashes from every
    position; a url carrying a long run in the middle of its path took 14 seconds to be rejected.
    Such a url is reachable — refs add npm:<package> reads repository.url straight out of the
    registry's packument, and nothing bounds its length — so a published package could stall the
    command that adds it. Trimming is now linear.

  • Every git invocation that receives a url now ends option parsing with -- first. Without it,
    git honours a url shaped like --upload-pack=<command> and executes it. Urls accepted through
    refs add were already refused by canonicalization, but refs sync re-reads them from the
    config file, where they are only checked for being non-empty — the guarantee therefore held one
    step away from the call that depended on it. It now holds at the call.

Changed

  • Published packages carry a provenance attestation.
    npm produces these automatically for public repositories, and 0.8.0 shipped without one
    because the repository was private at the time and the check for that fails silently. The
    release workflow now states --provenance and refuses to publish if the repository is not
    public.

  • refs init's skill-install hint now presents the second form as installing from a local
    clone, rather than as a workaround for the repository's development phase. Both commands
    are unchanged; only the wording differs.

v0.8.0

Choose a tag to compare

@boriskaiser boriskaiser released this 10 Aug 13:23

Changed

  • Lowered the supported Node.js floor from >=24.12 to >=24.2. The real requirement was
    always import.meta.main (used by the CLI's entry-point check), which Node added in 24.2.0 —
    the higher number had no other reason behind it. Users on Node 24.2 through 24.11 were
    previously blocked for no reason and can now run refs as-is.

Fixed

  • The agent skill's citation contract now binds every source reference, not only
    worker-relayed ones. An inline investigation (no worker dispatched, per the step 3
    dosing rule) previously fell outside the contract and could emit bare absolute paths
    instead of clickable relative-text links.

Removed

  • The Claude Code and Codex plugin packaging (.claude-plugin/, .codex-plugin/, and the
    .agents/plugins/marketplace.json mirror). Neither manifest carried a payload beyond the
    refs skill, so installing one produced a second copy that could drift from the one
    skills add installs — observed in practice: two refs entries in the Codex skill picker,
    and an icon that reached only one of them. refs now ships exactly two ways: the CLI from
    npm (@kaisers-io/refs) and the skill from git (skills add).

    If you installed the plugin, uninstall it (Claude Code: /plugin; Codex CLI: /plugins)
    and install the skill instead — see the install section in the README, which covers the
    repository still being private. The @refs plugin-mention gap in Codex (codex-cli 0.146.0,
    openai/codex#22078) no longer applies: there
    is no plugin to mention. Invoke the skill with /refs in Claude Code or $refs in Codex.


Published retroactively on 2026-08-10, the day this repository became public. The text above is this version's CHANGELOG.md entry, unedited — including anything it said while the repository was still private.

v0.7.0

Choose a tag to compare

@boriskaiser boriskaiser released this 10 Aug 13:23

Changed

  • refs list, show, resolve and init print one key: value per line, with a blank line
    between list entries. The key description header line is gone, and local_path: is now
    path: (package path: for the package inside a ref).
  • [stale]/[missing] markers are replaced by synced: <when>, plus status: stale and
    missing: … lines when they apply. Human output only; --json keeps stale and missing.
  • refs init says config: unchanged where it used to print (noop).
  • The npm-facing packages/cli/README.md now documents the skill-check search locations the
    root README has described since 0.6.1.

Added

  • last_fetched_at on refs list items and refs resolve output, missing and stale on
    refs show — all --json, all additive.
  • Source citations from the agent skill are now clickable markdown links.

Published retroactively on 2026-08-10, the day this repository became public. The text above is this version's CHANGELOG.md entry, unedited — including anything it said while the repository was still private.

v0.6.1

Choose a tag to compare

@boriskaiser boriskaiser released this 10 Aug 13:23

Fixed

  • refs doctor's skill check no longer reports a skill that is installed and working as
    missing. It only ever looked in ~/.claude/skills/refs and ~/.codex/skills/refs, but
    npx skills add kaisers-io/refs — the documented installer — writes neither: it keeps one
    real copy in a shared .agents/skills/refs directory and symlinks each agent's own
    directory at it. A Claude Code user was rescued by that symlink; anyone without one — a
    Codex-only user, or anyone whose install went to the current project rather than $HOME
    was told to install a skill they already had, and never saw the version comparison that
    0.6.0 made the point of this check. Five locations are now checked, in this order:
    ~/.agents/skills/refs/SKILL.md (shared ~/.agents), $CLAUDE_CONFIG_DIR or ~/.claude
    (Claude Code), $CODEX_HOME or ~/.codex (Codex), <cwd>/.agents/skills/refs
    (project ./.agents), and <cwd>/.claude/skills/refs (project ./.claude). The last two
    are there because skills add installs into the current project unless -g is passed, and
    implies -y when it runs inside an agent, so an agent-driven install never touches $HOME
    at all — and because naming a single agent (skills add … -a claude-code) switches the
    installer to copy mode, which skips the shared .agents directory entirely and writes only
    that agent's own. Unlike its global counterpart, the project path takes no env override:
    the installer hardcodes a relative .claude/skills there. Codex needs no counterpart at
    all, being a universal agent whose project install lands in ./.agents in every mode. The
    locations are deduplicated by resolved real path, so the usual symlinked install is
    reported once rather than once per agent, while two genuinely independent copies are both
    compared and a problem in either wins.
  • refs doctor's "skill not found" message no longer claims the skill is not installed, and
    no longer names directories it did not search. That list of locations is best-effort and
    cannot be otherwise: the paths are the skills installer's implementation detail rather
    than a documented contract, the canonical directory has moved before, and 74 agents carry a
    global skills directory of their own. A skill installed for some other agent still works
    and is simply invisible here, so the detail now names the locations it searched and keeps
    the install hint for the case where the skill really is missing. Those names are derived
    from the paths actually resolved, so with $CLAUDE_CONFIG_DIR or $CODEX_HOME set the
    message names the override rather than the ~/.claude/~/.codex it replaced — an override
    moves the search, it does not widen it, and pointing anyone at the directory the check just
    skipped would be worse than saying nothing. It stays a warn, never a fail — the skill's
    own capability gate compares refs --version against the pin in the file the agent already
    loaded and depends on none of this.

Published retroactively on 2026-08-10, the day this repository became public. The text above is this version's CHANGELOG.md entry, unedited — including anything it said while the repository was still private.