Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .agents/skills/skillrig/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ name: skillrig
description: >-
Point a repository at your org's agent-skills library and manage vendored skills with the
`skillrig` CLI — bind/choose the origin (`init`), search/discover skills in it (`search`),
vendor/add a skill (`add`, local or remote, with an optional immutable `--pin`),
verify/check that committed skills are exactly what was approved (`verify`), and generate
the origin's catalog (`index`). Use whenever the user wants to find, search, discover,
install, add, vendor, pull in, lock, or pin an agent skill from a skills library; filter
view one skill's full details (`show`/`info`), vendor/add a skill (`add`, local or remote,
with an optional immutable `--pin`), verify/check that committed skills are exactly what was
approved (`verify`), and generate the origin's catalog (`index`). Use whenever the user wants
to find, search, discover, install, add, vendor, pull in, lock, or pin an agent skill from a
skills library; read or see a skill's full/complete description or details (`show`/`info`)
when the search table truncated it; filter
skills by topic; set/configure where skills come from or fix a "no origin configured" error;
point a repo at a skills repo (OWNER/REPO[@branch]) or use SKILLRIG_ORIGIN; fetch from a
private/remote origin or debug auth / unreachable / not-found / no-such-version fetch errors;
Expand Down Expand Up @@ -46,6 +48,7 @@ unmodified (a CI gate), including debugging command output:
|---|---|---|
| Choose where skills come from (bind the origin) | `skillrig init` | [references/init.md](references/init.md) |
| Discover skills in the origin (search/filter by topic) | `skillrig search [QUERY...]` | [references/search.md](references/search.md) |
| Read one skill's full details (untruncated description) | `skillrig show <skill>` (alias `info`) | [references/show.md](references/show.md) |
| Vendor a skill into the repo (local or remote; `--pin` a version) | `skillrig add <skill>` | [references/add.md](references/add.md) |
| Prove vendored skills match what was approved | `skillrig verify` | [references/verify.md](references/verify.md) |
| **Origin-side:** generate the origin's catalog (`index.json`) | `skillrig index` | [references/index.md](references/index.md) |
Expand Down Expand Up @@ -97,5 +100,5 @@ from our library" is `skillrig`; "what skills exist out there for X?" is `find-s

Designed but **not implemented** (don't assume they exist): multi-client symlink views, a
prerequisite/health `doctor` (the reserved exit `3`), `bump --pr` upgrades, and `global`
scope. The shipped surface is `init` + `search` + `add` (local **or** remote, with `--pin`) +
`verify`, plus the origin-side `index` generator.
scope. The shipped surface is `init` + `search` + `show` (alias `info`) + `add` (local **or**
remote, with `--pin`) + `verify`, plus the origin-side `index` generator.
66 changes: 66 additions & 0 deletions .agents/skills/skillrig/references/show.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# `skillrig show <skill>` — read one skill's full details (Query)

> The **human** counterpart to [search](search.md): drill into ONE skill and print its whole
> record — most importantly the **complete, untruncated description** that the `search` table
> clips to ~80 chars. `info` is an alias. Needs an origin (run [init](init.md) first).

`show` resolves the active origin and reads its catalog (`index.json`) through the **same**
path `search` uses, then prints a single named skill's full record: name, version, namespace,
the full description, topics, path, and backing-tool requirements. Reach for it when `search`
showed a truncated one-liner and a human wants to read the whole thing (an agent can instead
pipe `search <name> --json` to `jq`).

```
skillrig show terraform-plan-review # full human-readable record
skillrig info terraform-plan-review # identical (alias)
skillrig show terraform-plan-review --json # complete record for an agent / jq
```

## Lookup is exact (a point lookup, not a filter)

The skill name is matched **exactly** — the same canonical name `add` vendors by — not the
fuzzy, case-insensitive substring match `search` uses. So `show` is for a name you already
know (typically one you saw in `search`). A name the origin does not publish is an **error**
(exit 1), pointing you back at `search` — deliberately unlike `search`, where an empty result
is a clean exit 0.

## Freshness & origin

Like `search`, `show` fetches the origin's `index.json` **per call** (no local cache), resolves
the active origin through the shared resolver (`SKILLRIG_ORIGIN` > project `.skillrig/config.toml`
> global), and checks the origin's convention version before reading. A **remote** origin is
fetched over `git` (a private one uses the auto-resolved read-only token); a **local-path**
origin is read with no network.

## Output

- **Human (default)** — a labelled block: a `name version (namespace)` header, the `path`,
`topics`, and `requires` lines, then the **full description** as the body, and a footer hint
pointing at `add`.
- **`--json`** — `{ "origin": ..., "skill": { ...full catalog entry... } }`, untruncated and
pipeable: `skillrig show terraform-plan-review --json | jq '.skill.requires'`.

| Flag | Purpose |
|------|---------|
| `--json` | Emit the complete record (origin + the whole catalog entry) on stdout |
| `--verbose` | Show the raw underlying cause behind a summary or error |

## Exit codes

| Code | When |
|------|------|
| `0` | Success — the named skill was found and printed |
| `1` | Usage/config: no origin configured, the named skill is **not in the origin**, unreachable/auth/incompatible-convention fetching the catalog, bad args |

`show` never emits `2`/`3` (those are reserved for verification/prerequisite gates).

## Error handling

| Symptom (stderr) | Cause | Fix |
|------------------|-------|-----|
| `skill "<name>" not found in origin ...` | no skill by that exact name in the catalog | run `skillrig search` to list the real names, then `show` one |
| `no origin configured` | no resolvable origin | `skillrig init --origin OWNER/REPO`, or set `SKILLRIG_ORIGIN` |
| `... is unreachable` / `authentication ... failed` / `... not found` / `convention version N` | catalog fetch/gate failures (shared with `search`) | see [search.md](search.md) — same typed errors and fixes |

All failures state what/why/fix and exit `1`; `--verbose` shows the raw cause. Errors to
stderr, data to stdout (so `skillrig show <name> --json 2>/dev/null | jq .` stays clean).
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ skillrig search terraform plan
skillrig search --topic aws --topic terraform
```

### `show`

Print **one** skill's full record from the configured origin — its complete,
**untruncated** description (the part `search` clips to a one-line preview), plus
its version, namespace, topics, path, and backing-tool requirements. `show` is the
human counterpart to `search`: where `search` lists many skills compactly, `show`
drills into one (an agent gets the same data from `search ... --json | jq`).
`info` is an alias. Read-only; needs a resolvable origin but no git working tree;
the skill name is matched exactly. A name the origin does not publish is exit 1
(run `skillrig search` to list the real names).

```sh
# Show a skill's full details (alias: skillrig info <skill>).
skillrig show terraform-plan-review

# The complete record as JSON, for an agent or jq.
skillrig show terraform-plan-review --json
```

### `add`

Vendor a named skill from the configured origin into the canonical
Expand Down
9 changes: 6 additions & 3 deletions docs/design/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ skillrig — rig up your agents with skills (git-native skill distribution)

Commands:
search Query the origin's index.json for skills [implemented]
show Print one skill's full record (untruncated description) [implemented]
add Vendor a skill into this repo + write the lock entry [implemented]
verify Offline integrity check — label-honesty (exit code; CI gate) [implemented]
index Generate the origin's index.json from skill frontmatter [implemented, origin-side]
Expand Down Expand Up @@ -222,11 +223,13 @@ terraform-cost | v0.9.0 | my-org | Estimate the cost delta of a plan.

`search` is **query-first**: `search [QUERY...]` is a case-insensitive token-AND substring match over `name` + `description` + `topics`, with `--topic` as a separate exact-string filter (repeatable). The result order is deterministic (N6) — a fixed relevance bucket (exact-name > name > topic > description match) then lexicographic by name. There is **no** fuzzy / semantic / TF-IDF ranking. An empty result is a **clean exit 0** (not an error) with a footer hint.

**Truncation rules** (human output only):
**Truncation rules** (compact *list* human output — `search`, the `verify` failure list):
- Description: first 80 chars, append `...` if truncated
- Newlines replaced with spaces
- `requires` shown as a comma-joined tool list, not the full constraint set

These rules keep a **list** scannable, where each row must stay one line so N items render in ~N lines. They do **not** apply to a **single-item detail view**: `skillrig show <skill>` is the deliberate drill-down that prints ONE skill's *complete, untruncated* description (multi-line preserved) — that is its entire reason to exist (issue #17). The list→detail split is the same scan→inspect workflow `--json` serves; `show` is the human inspect step. A detail view still strips terminal control bytes from the (untrusted, fetched) catalog text and bounds the surrounding structure (a fixed header block + footer), but the description body itself is intentionally full. Control bytes / ANSI escapes are stripped from all human output (list and detail) since catalog content is fetched and not trusted for terminal rendering; `--json` is never sanitized.

**Footer hints**: Every compact output MUST end with a hint line suggesting the drill-down command. This is the agent's navigation cue.

### JSON Output (`--json`): Complete and Pipeable
Expand Down Expand Up @@ -319,7 +322,7 @@ Every `skillrig` subcommand MUST identify which pattern(s) it follows. This clas

| Pattern | Purpose | Examples | Constraints |
|---------|---------|----------|-------------|
| **Query** | Deterministic read of the discovery artifact | `search` *(implemented)* | Reads the origin's `index.json` (fetched per call — no offline cache this slice; an unreachable origin is the `UnreachableError`). Query-first: deterministic token-AND substring over `name`+`description`+`topics` + exact `--topic` filter; fixed relevance-bucket then lexicographic order — **no inference / no fuzzy ranking** (N6). Empty result = clean exit 0. Gates the origin's `skillrigConvention` before reading. |
| **Query** | Deterministic read of the discovery artifact | `search`, `show` *(implemented)* | Reads the origin's `index.json` (fetched per call — no offline cache this slice; an unreachable origin is the `UnreachableError`). `search` is query-first: deterministic token-AND substring over `name`+`description`+`topics` + exact `--topic` filter; fixed relevance-bucket then lexicographic order — **no inference / no fuzzy ranking** (N6); an empty result = clean exit 0. `show <skill>` (alias `info`) is the **point-lookup** sibling: an EXACT-name match into the same catalog that prints ONE skill's full record — the complete, untruncated description `search` clips to ~80 chars (issue #17) — so an unknown NAMED skill is a usage error (exit 1), not the empty-set success. Both gate the origin's `skillrigConvention` before reading. |
| **Vendor Mutation** | Write skill tree + lock entry | `add` *(implemented — local + remote)*, `bump --pr` | Writes lock via `skillcore` only. Serves a **local-path** origin (read a checkout) and a **remote** `OWNER/REPO` origin (fetch the subtree over `git`, token via `os.exec` of `gh`/`git`, never a write credential) — the two origin forms are classified, never "both-present". `--pin` vendors an immutable version. Supports `--dry-run`; refuses to clobber content that diverges from the locked `treeSha` without `--force`. `bump` *proposes* (opens a PR), never force-adopts (R13). MUST never silently discard local edits (R32). Vendors byte-identical + mode-preserving; the skill name MUST be a single path segment (no traversal); **path-traversal + symlink guards apply to remotely-fetched content too**. **Symlinks in a skill subtree are rejected this slice** — following them would break byte-identical / git-canonical vendoring (git records a symlink as a link, not its target); preserving symlinks faithfully is a future relaxation. |
| **Verification Gate** | Offline integrity / prereq / conformance | `verify` *(implemented — integrity-only)*, `lint` | MUST be offline + deterministic. Exit-code driven. **No live/online signal in this path** (R11/N1). `verify` = consumer CI gate; `lint` = author CI gate on the origin. As implemented, `verify` is **integrity-only** (label-honesty + orphan detection, exit 2); prerequisite/eligibility checks (a missing `requires` tool → exit 3) belong to the future `doctor`, so `verify` does not emit exit 3 today. |
| **Environment** | Health, auth, config, bootstrap | `doctor`, `init` | MUST be idempotent. `doctor` checks prerequisite auth (R18); works without a fully-configured project. `init` is **consumer-side only** — binds to an *existing* origin, never bootstraps one (architecture §2d). |
Expand All @@ -333,7 +336,7 @@ Each pattern has a distinct failure mode expectation:

| Pattern | Failure Mode |
|---------|-------------|
| **Query** | MUST fail with clear error + suggested fix (no origin → run `init`; unreachable/auth/incompatible-convention fetching the catalog → the matching typed error). An **empty match set is success (exit 0)**, not a failure — it prints a footer hint, not an error. |
| **Query** | MUST fail with clear error + suggested fix (no origin → run `init`; unreachable/auth/incompatible-convention fetching the catalog → the matching typed error). For `search`, an **empty match set is success (exit 0)**, not a failure — it prints a footer hint, not an error. For `show`, a **named skill the origin does not publish is exit 1** (a point lookup of a named thing that doesn't exist), with a what/why/fix pointing at `search`. |
| **Vendor Mutation** | MUST validate origin + auth before fetching. Three-way-merge conflict → non-zero exit, write git-style conflict markers, instruct resolve-and-rerun (architecture §5b). Never discard local edits. |
| **Verification Gate** | MUST be deterministic pass/fail by exit code. Label-honesty mismatch = fail (exit 2); orphan = fail (exit 2); unresolved conflict markers = fail. Prereq miss (exit 3) is reserved for the future `doctor` — the implemented `verify` is integrity-only and does not emit it. |
| **Environment** | MUST be idempotent and safe to retry. MUST distinguish "tool missing" from "tool exists but unauthenticated" (R18). |
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func newAddCmd(opts *globalOpts) *cobra.Command {
func (ac *addCmd) run(cmd *cobra.Command) error {
cwd, err := ac.getwd()
if err != nil {
return &UsageError{Msg: "cannot determine working directory\nwhy: " + err.Error(), Cause: err}
return usageCannotGetwd(err)
}

res, err := config.ResolveOrigin(cwd, ac.env)
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func newIndexCmd(opts *globalOpts) *cobra.Command {
func (ic *indexCmd) run(cmd *cobra.Command) error {
cwd, err := ic.getwd()
if err != nil {
return &UsageError{Msg: "cannot determine working directory\nwhy: " + err.Error(), Cause: err}
return usageCannotGetwd(err)
}

originRoot, err := gitToplevel(cmd.Context(), cwd)
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (ic *initCmd) writeTarget(cmd *cobra.Command) (path, scope string, err erro

cwd, err := ic.getwd()
if err != nil {
return "", "", &UsageError{Msg: "cannot determine working directory\nwhy: " + err.Error(), Cause: err}
return "", "", usageCannotGetwd(err)
}

path, err = config.ProjectWriteTarget(cmd.Context(), cwd)
Expand Down
Loading