Skip to content
Open
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 @@ -97,6 +97,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
5 changes: 3 additions & 2 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 @@ -315,7 +316,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 @@ -329,7 +330,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
72 changes: 72 additions & 0 deletions internal/cli/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,78 @@ func renderSearchResult(w io.Writer, origin string, matches []skillcore.CatalogE
return err
}

// showResultJSON is the complete, untruncated --json view of a show: the
// resolved origin and the single matched skill with every field add needs. It
// reuses the skillcore.CatalogEntry JSON tags, so the record is byte-for-byte the
// same shape search emits per entry — one entry, named `skill`.
type showResultJSON struct {
Origin string `json:"origin"`
Skill skillcore.CatalogEntry `json:"skill"`
}

// showFooterPrefix is the next-step footer for a human show — the skill name is
// appended so the hint is a runnable command (cli.md Principle 3).
const showFooterPrefix = "→ vendor it: skillrig add "

// renderShowResult writes a single skill's full record to w. With jsonOut it
// emits one complete JSON object (origin + the whole catalog entry, all fields
// present); otherwise a human-friendly labelled block whose defining feature is
// the COMPLETE, untruncated description — the gap issue #17 closes, since search
// clips it to ~80 chars. The block is a fixed handful of header/field lines plus
// the description body and a footer hint. Data goes to stdout (the caller passes
// cmd.OutOrStdout()).
func renderShowResult(w io.Writer, origin string, e skillcore.CatalogEntry, jsonOut bool) error {
if jsonOut {
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)

return enc.Encode(showResultJSON{Origin: origin, Skill: e})
}

var b strings.Builder

fmt.Fprintf(&b, "%s %s (%s)\n", e.Name, e.Version, e.Namespace)
fmt.Fprintf(&b, "path: %s\n", e.Path)

if len(e.Topics) > 0 {
fmt.Fprintf(&b, "topics: %s\n", strings.Join(e.Topics, ", "))
}

if len(e.Requires) > 0 {
fmt.Fprintf(&b, "requires: %s\n", joinRequires(e.Requires))
}

// The whole point of show: the complete description, untruncated, set off by a
// blank line so it reads as the body of the record.
if desc := strings.TrimSpace(e.Description); desc != "" {
fmt.Fprintf(&b, "\n%s\n", desc)
}
Comment on lines +211 to +215
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. rendershowresult prints full description 📘 Rule violation ⚙ Maintainability

The new human output path for show prints the full multi-line description without
truncation/newline collapsing. This conflicts with the compliance requirement that human output
descriptions be truncated to 80 characters and have newlines replaced with spaces.
Agent Prompt
## Issue description
Human output in `renderShowResult` prints `e.Description` verbatim (including newlines and unbounded length), but the checklist requires human descriptions to be truncated to 80 chars and newlines replaced with spaces.

## Issue Context
`renderShowResult` currently writes `desc := strings.TrimSpace(e.Description)` and then `fmt.Fprintf(&b, "\n%s\n", desc)`.

## Fix Focus Areas
- internal/cli/output.go[211-215]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


fmt.Fprintf(&b, "\n%s%s\n", showFooterPrefix, e.Name)

Comment on lines +179 to +218
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Footer hint breaks for names 🐞 Bug ≡ Correctness

renderShowResult prints a supposedly runnable skillrig add <name> footer using the raw catalog
skill name, but valid skill names can start with - or include spaces, making the suggested command
fail (flag parsing / shell splitting). This violates the stated contract that the footer is a
runnable next-step command.
Agent Prompt
### Issue description
`renderShowResult` claims its footer is a runnable command, but it appends the raw skill name directly after `skillrig add `. Names are only validated as “single path segment” and may legally begin with `-` or contain spaces; in those cases the printed command is not runnable (cobra interprets `-foo` as a flag; shells split `foo bar`).

### Issue Context
- `renderShowResult` builds the footer via `showFooterPrefix + e.Name`.
- `validateSkillName` in skillcore does **not** reject leading `-` or spaces.

### Fix Focus Areas
- internal/cli/output.go[179-218]

### Implementation notes
- At minimum, make the command robust for leading-dash names by emitting `skillrig add -- <name>`.
- For full “runnable” behavior, add simple shell-quoting for names containing whitespace or other special chars (or tighten/align skill-name validation rules across the system if that’s the intended invariant).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify agenskills.io specification on allowed characters in skill frontmatter field "name" - gh cli parsing logic of this metadata and if this is a real concern

_, err := io.WriteString(w, b.String())

return err
}

// joinRequires renders a skill's backing-tool requirements as a compact human
// summary — "tool (version)" joined by commas, or a bare tool when no version
// constraint is recorded — mirroring search's requires summary.
func joinRequires(reqs []skillcore.Require) string {
parts := make([]string, 0, len(reqs))

for _, r := range reqs {
part := r.Tool
if r.Version != "" {
part += " (" + r.Version + ")"
}

parts = append(parts, part)
}

return strings.Join(parts, ", ")
}

// searchEmptyFooter is the next-step hint for an empty search result (still exit 0).
const searchEmptyFooter = "→ broaden the query, or run skillrig search with no filter to list all"

Expand Down
1 change: 1 addition & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func Execute() int {
func registerSubcommands(root *cobra.Command, opts *globalOpts) {
root.AddCommand(newInitCmd(opts))
root.AddCommand(newSearchCmd(opts))
root.AddCommand(newShowCmd(opts))
root.AddCommand(newAddCmd(opts))
root.AddCommand(newVerifyCmd(opts))
root.AddCommand(newIndexCmd(opts))
Expand Down
Loading