gem-skill 0.1.3 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/CLAUDE.md +63 -0
- data/README.md +68 -15
- data/docs/cache.md +82 -5
- data/docs/commands/bundle-skill.md +12 -3
- data/docs/commands/gem-skill.md +91 -6
- data/docs/configuration.md +61 -0
- data/docs/how-it-works.md +58 -9
- data/docs/index.md +15 -11
- data/docs/skill-files.md +73 -10
- data/lib/gem/skill/cache.rb +24 -0
- data/lib/gem/skill/cli/bundle_command.rb +57 -22
- data/lib/gem/skill/cli/gem_command.rb +171 -9
- data/lib/gem/skill/fetcher.rb +52 -0
- data/lib/gem/skill/frontmatter.rb +64 -0
- data/lib/gem/skill/generator.rb +46 -13
- data/lib/gem/skill/linker.rb +17 -3
- data/lib/gem/skill/runner.rb +75 -9
- data/lib/gem/skill/verifier.rb +139 -0
- data/lib/gem/skill/version.rb +1 -1
- data/lib/gem/skill.rb +36 -1
- data/ruby-gem-skills/SKILL.md +53 -0
- metadata +37 -5
data/docs/how-it-works.md
CHANGED
|
@@ -16,6 +16,9 @@ Gemfile.lock / gem name
|
|
|
16
16
|
↓
|
|
17
17
|
Cache write to ~/.gem/skills/<gem>/<version>/
|
|
18
18
|
↓
|
|
19
|
+
Verifier (optional, --verify) check the skill's code against the
|
|
20
|
+
gem's actual source; correct mismatches in place
|
|
21
|
+
↓
|
|
19
22
|
Linker symlink .claude/skills/<gem> → cache dir
|
|
20
23
|
```
|
|
21
24
|
|
|
@@ -78,27 +81,71 @@ Manages the global skill cache. Structure:
|
|
|
78
81
|
└── <gem_name>/
|
|
79
82
|
└── <version>/
|
|
80
83
|
├── SKILL.md
|
|
81
|
-
└── metadata.json (gem, version, model, generated_at, sources
|
|
84
|
+
└── metadata.json (gem, version, model, generated_at, sources,
|
|
85
|
+
and after --verify: a "verification" block with
|
|
86
|
+
source provenance + structured changes)
|
|
82
87
|
```
|
|
83
88
|
|
|
84
89
|
`Cache::ROOT` is set once at load time from `GEMSKILL_DIR` (default: `~/.gem/skills`).
|
|
85
90
|
|
|
91
|
+
`read_metadata` / `write_skill` / `merge_metadata` let the verifier rewrite a
|
|
92
|
+
cached skill and annotate its metadata without clobbering the original
|
|
93
|
+
`generated_at`, `model`, or `sources`.
|
|
94
|
+
|
|
95
|
+
### Verifier
|
|
96
|
+
|
|
97
|
+
`lib/gem/skill/verifier.rb`
|
|
98
|
+
|
|
99
|
+
Optional second pass, enabled by `--verify`. Generation synthesizes prose
|
|
100
|
+
sources (README, changelog, examples) which are frequently stale or wrong about
|
|
101
|
+
exact signatures. The verifier re-checks the generated skill against the gem's
|
|
102
|
+
**actual source code** — the only source of truth — and corrects mismatched
|
|
103
|
+
method signatures, default argument values, visibility, return values, and
|
|
104
|
+
behavioral claims.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
Verifier.new(gem_name, version, model:).verify(skill_content)
|
|
108
|
+
# => Result(content:, changes:, changed:, verifiable:, source:, model:)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Ground truth comes from `Fetcher#source_code` (the gem's `lib/**/*.rb`), and
|
|
112
|
+
`Fetcher#source_manifest` records which files were examined. Whether the skill
|
|
113
|
+
actually changed is decided by a **deterministic diff** of the content before and
|
|
114
|
+
after — not by trusting the model's self-report — so the exit code is reliable.
|
|
115
|
+
If no installed source is available, `verifiable` is false and the skill is left
|
|
116
|
+
untouched.
|
|
117
|
+
|
|
118
|
+
Each correction in `changes` is a structured, issue-ready Hash
|
|
119
|
+
(`category`, `symbol`, `skill_section`, `source_location`, `was`, `now`,
|
|
120
|
+
`detail`, `source_evidence`) — detailed enough to file a documentation bug
|
|
121
|
+
against the gem. The Runner writes these, plus source provenance, into the
|
|
122
|
+
`verification` block of `metadata.json`.
|
|
123
|
+
|
|
86
124
|
### Linker
|
|
87
125
|
|
|
88
126
|
`lib/gem/skill/linker.rb`
|
|
89
127
|
|
|
90
|
-
Creates and manages directory symlinks in
|
|
128
|
+
Creates and manages directory symlinks in the project's skill directory
|
|
129
|
+
(default `.claude/skills/`, Claude Code's convention):
|
|
91
130
|
|
|
92
131
|
```
|
|
93
|
-
|
|
132
|
+
<project_dir>/<gem_name> → ~/.gem/skills/<gem_name>/<version>/
|
|
94
133
|
```
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
The directory is `Linker.project_dir`, read from `GEMSKILL_PROJECT_DIR` each call
|
|
136
|
+
(default `.claude/skills`). Codex users set it to `.agents` or `.codex` so
|
|
137
|
+
`bundle skill` links into a Codex root instead. Symlinks point to the **version
|
|
138
|
+
directory**, not directly to `SKILL.md`; the assistant discovers `SKILL.md` by
|
|
139
|
+
reading inside the linked directory.
|
|
98
140
|
|
|
99
141
|
`Linker.prune_dead_links` removes any symlink whose target no longer exists
|
|
100
142
|
in the cache (e.g. after `gem skill purge`).
|
|
101
143
|
|
|
144
|
+
The cache itself is assistant-neutral. `SKILL.md` is a shared format; other
|
|
145
|
+
assistants read it from their own roots. Note that linking only makes a skill
|
|
146
|
+
*available* — some assistants (e.g. Codex) require it to be in the available-skills
|
|
147
|
+
list or referenced explicitly before it's *active*.
|
|
148
|
+
|
|
102
149
|
### Runner
|
|
103
150
|
|
|
104
151
|
`lib/gem/skill/runner.rb`
|
|
@@ -107,12 +154,14 @@ Shared core used by both CLI commands. Drives one gem through the
|
|
|
107
154
|
cache-check → generate → link sequence:
|
|
108
155
|
|
|
109
156
|
```ruby
|
|
110
|
-
Runner.install_skill(gem_name, version, spinner, force:, model:)
|
|
111
|
-
#
|
|
157
|
+
Runner.install_skill(gem_name, version, spinner, force:, model:, verify:)
|
|
158
|
+
# => Runner::Result(error:, verify_fixed:, change_count:)
|
|
112
159
|
```
|
|
113
160
|
|
|
114
|
-
|
|
115
|
-
fiber) can record
|
|
161
|
+
Captures errors into the result rather than raising, so the caller (the
|
|
162
|
+
concurrent fiber) can record them without killing other in-flight fibers. When
|
|
163
|
+
`verify:` is set, the result's `verify_fixed` lets the CLI aggregate across all
|
|
164
|
+
gems and exit `2` (`EXIT_VERIFY_FIXED`) if any skill was corrected.
|
|
116
165
|
|
|
117
166
|
---
|
|
118
167
|
|
data/docs/index.md
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
<tr>
|
|
4
4
|
<td width="40%"><img src="assets/images/gem-skill.jpg" alt="gem-skill logo" style="width:100%;display:block;"></td>
|
|
5
5
|
<td width="60%">
|
|
6
|
-
Generate
|
|
7
|
-
|
|
8
|
-
can share the
|
|
6
|
+
Generate <code>SKILL.md</code> files for AI coding assistants
|
|
7
|
+
(Claude Code, OpenAI Codex, and others) from Ruby gem documentation,
|
|
8
|
+
and cache them globally so every project that uses a gem can share the
|
|
9
|
+
same pre-built knowledge.<br><br>
|
|
9
10
|
<strong><a href="https://madbomber.github.io/gem-skill">Full documentation →</a></strong>
|
|
10
11
|
</td>
|
|
11
12
|
</tr>
|
|
@@ -14,14 +15,15 @@
|
|
|
14
15
|
|
|
15
16
|
## The problem it solves
|
|
16
17
|
|
|
17
|
-
Every time
|
|
18
|
-
it re-reads the README, scans examples, and figures out the API. That
|
|
19
|
-
tokens and time — and the result evaporates when the conversation ends.
|
|
18
|
+
Every time an AI coding assistant encounters a gem it hasn't seen in the current
|
|
19
|
+
context, it re-reads the README, scans examples, and figures out the API. That
|
|
20
|
+
costs tokens and time — and the result evaporates when the conversation ends.
|
|
20
21
|
|
|
21
22
|
`gem-skill` runs that pipeline once, offline, and stores the output as a
|
|
22
|
-
`SKILL.md` in `~/.gem/skills`. Projects symlink to the cached version, so
|
|
23
|
-
|
|
24
|
-
repeating the ingestion work.
|
|
23
|
+
`SKILL.md` in `~/.gem/skills`. Projects symlink to the cached version, so your
|
|
24
|
+
assistant has accurate, version-specific knowledge about each gem without
|
|
25
|
+
repeating the ingestion work. `SKILL.md` is a shared format — Claude Code,
|
|
26
|
+
OpenAI Codex, and other assistants all read it.
|
|
25
27
|
|
|
26
28
|
## Quick start
|
|
27
29
|
|
|
@@ -53,7 +55,8 @@ gem README / changelog / RubyGems API
|
|
|
53
55
|
↓
|
|
54
56
|
Linker creates .claude/skills/<gem> → cache dir
|
|
55
57
|
↓
|
|
56
|
-
|
|
58
|
+
The assistant reads SKILL.md automatically
|
|
59
|
+
(Claude Code from .claude/skills/; other assistants from their own roots)
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
All concurrent work is handled by async fibers — multiple gems are processed
|
|
@@ -66,4 +69,5 @@ simultaneously with live TTY spinner progress.
|
|
|
66
69
|
- **Concurrent** — all LLM calls run concurrently via async fibers
|
|
67
70
|
- **Two interfaces** — `gem skill` for global cache management, `bundle skill` for project-aware linking
|
|
68
71
|
- **Auto-install** — `gem install --with-skill` generates skills during normal gem installation
|
|
69
|
-
- **Configurable** — `GEMSKILL_DIR` and `GEMSKILL_MODEL` environment variables
|
|
72
|
+
- **Configurable** — `GEMSKILL_DIR`, `GEMSKILL_PROJECT_DIR`, and `GEMSKILL_MODEL` environment variables
|
|
73
|
+
- **Multi-assistant** — `SKILL.md` works with Claude Code, OpenAI Codex, and others; `GEMSKILL_PROJECT_DIR` points project links at the right directory
|
data/docs/skill-files.md
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
# Skill Files
|
|
2
2
|
|
|
3
|
-
A `SKILL.md` is a structured Markdown document that gives
|
|
4
|
-
practical knowledge about a Ruby gem.
|
|
5
|
-
present in
|
|
3
|
+
A `SKILL.md` is a structured Markdown document that gives an AI coding assistant
|
|
4
|
+
deep, practical knowledge about a Ruby gem. It's a shared format — assistants
|
|
5
|
+
such as Claude Code and OpenAI Codex read it automatically when it is present in
|
|
6
|
+
the skills directory they look in (Claude Code uses `.claude/skills/`; see
|
|
7
|
+
[Using the cache with other assistants](#using-with-other-assistants)).
|
|
6
8
|
|
|
7
9
|
## Format
|
|
8
10
|
|
|
9
|
-
Every generated skill
|
|
10
|
-
|
|
11
|
+
Every generated skill begins with **YAML frontmatter** — the `name` and
|
|
12
|
+
`description` that make it discoverable as an Agent Skill — followed by a
|
|
13
|
+
top-level heading and seven sections:
|
|
11
14
|
|
|
12
15
|
```markdown
|
|
16
|
+
---
|
|
17
|
+
name: faraday
|
|
18
|
+
description: "HTTP client library for Ruby with pluggable adapters and middleware; use when making HTTP requests... (faraday v2.14.3)"
|
|
19
|
+
---
|
|
20
|
+
|
|
13
21
|
# faraday v2.14.3
|
|
14
22
|
|
|
15
23
|
## Overview
|
|
@@ -34,12 +42,27 @@ Initializer patterns, environment variables, defaults worth knowing.
|
|
|
34
42
|
How to test code that uses this gem: mocks, fakes, fixtures, VCR patterns.
|
|
35
43
|
```
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
### Frontmatter
|
|
46
|
+
|
|
47
|
+
The frontmatter is what registers the file as a skill — both Claude Code and
|
|
48
|
+
OpenAI Codex require it, and the `description` is the text loaded into the
|
|
49
|
+
assistant's context to decide *when* the skill applies. gem-skill generates it
|
|
50
|
+
deterministically:
|
|
51
|
+
|
|
52
|
+
- **`name`** — the gem name normalized to hyphen-case (lowercase letters,
|
|
53
|
+
digits, hyphens). For example `ruby_llm` becomes `ruby-llm`, since underscores
|
|
54
|
+
aren't allowed in skill names.
|
|
55
|
+
- **`description`** — a one-line, trigger-oriented summary derived from the
|
|
56
|
+
Overview, with the version appended, sanitized to satisfy both assistants
|
|
57
|
+
(single line, no angle brackets).
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
`SKILL.md` it finds. This means:
|
|
59
|
+
## What an assistant does with it
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
When an assistant opens a project whose skills directory contains `SKILL.md`
|
|
62
|
+
files, it reads every one it finds (Claude Code, for instance, reads everything
|
|
63
|
+
in `.claude/skills/`). This means:
|
|
64
|
+
|
|
65
|
+
- The assistant knows the correct API for the exact version you're using
|
|
43
66
|
- No token cost re-deriving usage from READMEs mid-conversation
|
|
44
67
|
- The knowledge persists across conversation turns
|
|
45
68
|
- Multiple gems can be in scope simultaneously
|
|
@@ -75,4 +98,44 @@ gem skill install my_gem --force
|
|
|
75
98
|
|
|
76
99
|
Skills are cached per version. `faraday 2.12.0` and `faraday 2.14.3` each get
|
|
77
100
|
their own `SKILL.md`. Symlinks in `.claude/skills/` point to the version
|
|
78
|
-
matching your `Gemfile.lock`, so
|
|
101
|
+
matching your `Gemfile.lock`, so the assistant always has the right version
|
|
102
|
+
context.
|
|
103
|
+
|
|
104
|
+
## Using with other assistants
|
|
105
|
+
|
|
106
|
+
`SKILL.md` is not specific to one assistant. The `~/.gem/skills` cache is
|
|
107
|
+
assistant-neutral; `bundle skill` links skills into `.claude/skills/`, which
|
|
108
|
+
Claude Code reads automatically. Other assistants discover skills in their own
|
|
109
|
+
roots:
|
|
110
|
+
|
|
111
|
+
| Assistant | Global roots | Project-local roots |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| Claude Code | `~/.claude/skills/` | `.claude/skills/` |
|
|
114
|
+
| OpenAI Codex | `~/.codex/skills`, `~/.agents/skills` | `.agents/`, `.codex/` |
|
|
115
|
+
|
|
116
|
+
**Project-local (recommended):** point `bundle skill` at the right directory with
|
|
117
|
+
the `GEMSKILL_PROJECT_DIR` environment variable (default `.claude/skills`):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
export GEMSKILL_PROJECT_DIR=".agents" # or ".codex"
|
|
121
|
+
bundle skill install # symlinks now land in .agents/
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See [`GEMSKILL_PROJECT_DIR`](configuration.md#gemskill_project_dir) for the full
|
|
125
|
+
table of suggested values.
|
|
126
|
+
|
|
127
|
+
**Global:** to share cached skills across all projects for an assistant, symlink
|
|
128
|
+
a cached version directory into its global root:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
ln -s ~/.gem/skills/faraday/2.14.3 ~/.agents/skills/faraday
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
!!! note "Availability is not the same as activation"
|
|
135
|
+
Assistants differ in how a present `SKILL.md` becomes active. **Claude Code**
|
|
136
|
+
treats every `SKILL.md` under `.claude/skills/` as active automatically.
|
|
137
|
+
**OpenAI Codex** does *not* auto-activate a skill just because the file
|
|
138
|
+
exists — it must appear in the session's available-skills list, or you must
|
|
139
|
+
explicitly point Codex at it. So linking a skill into a Codex root makes it
|
|
140
|
+
*available* but may not make it *active* on its own; check your assistant's
|
|
141
|
+
skill-discovery rules.
|
data/lib/gem/skill/cache.rb
CHANGED
|
@@ -42,6 +42,30 @@ module Gem::Skill
|
|
|
42
42
|
File.read(path)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Read metadata.json back as a Hash with string keys. Returns {} if absent
|
|
46
|
+
# or unparseable.
|
|
47
|
+
def self.read_metadata(gem_name, version)
|
|
48
|
+
path = metadata_path(gem_name, version)
|
|
49
|
+
return {} unless File.exist?(path)
|
|
50
|
+
|
|
51
|
+
JSON.parse(File.read(path))
|
|
52
|
+
rescue JSON::ParserError
|
|
53
|
+
{}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Overwrite just the SKILL.md content, leaving metadata untouched.
|
|
57
|
+
def self.write_skill(gem_name, version, skill_content)
|
|
58
|
+
File.write(skill_path(gem_name, version), skill_content)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Merge additional keys into the existing metadata.json, preserving
|
|
62
|
+
# generated_at, model, sources, etc. Keys are normalized to strings so a
|
|
63
|
+
# symbol key never collides with its string twin.
|
|
64
|
+
def self.merge_metadata(gem_name, version, extra)
|
|
65
|
+
data = read_metadata(gem_name, version).merge(extra.transform_keys(&:to_s))
|
|
66
|
+
File.write(metadata_path(gem_name, version), JSON.generate(data))
|
|
67
|
+
end
|
|
68
|
+
|
|
45
69
|
def self.versions(gem_name)
|
|
46
70
|
dir = File.join(ROOT, gem_name)
|
|
47
71
|
return [] unless Dir.exist?(dir)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# gem/skill first — see gem_command.rb: activation must resolve json < 3
|
|
4
|
+
# before a bare `require "json"` can activate a newer version.
|
|
5
|
+
require "gem/skill"
|
|
3
6
|
require "async"
|
|
4
7
|
require "fileutils"
|
|
5
8
|
require "json"
|
|
6
9
|
require "tty-spinner"
|
|
7
|
-
require "gem/skill"
|
|
8
10
|
|
|
9
11
|
module Gem::Skill
|
|
10
12
|
# Handles `bundle skill SUBCOMMAND` via Bundler's plugin API (plugins.rb).
|
|
11
|
-
# Project-aware: reads Gemfile.lock and manages
|
|
13
|
+
# Project-aware: reads Gemfile.lock and manages project skill symlinks
|
|
14
|
+
# (directory set by GEMSKILL_PROJECT_DIR, default .claude/skills/).
|
|
12
15
|
module BundlerCommand
|
|
13
16
|
SUBCOMMANDS = %w[install refresh list].freeze
|
|
14
17
|
|
|
@@ -42,9 +45,12 @@ module Gem::Skill
|
|
|
42
45
|
return
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
force
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
force = opts[:force]
|
|
49
|
+
verify = opts[:verify]
|
|
50
|
+
model = opts[:model] || Generator::DEFAULT_MODEL
|
|
51
|
+
max_tokens = opts[:max_tokens] || Generator::MAX_TOKENS
|
|
52
|
+
errors = []
|
|
53
|
+
results = []
|
|
48
54
|
|
|
49
55
|
multi = TTY::Spinner::Multi.new(
|
|
50
56
|
"[:spinner] Installing skills (#{model})",
|
|
@@ -58,8 +64,9 @@ module Gem::Skill
|
|
|
58
64
|
sp = multi.register(" [:spinner] :title")
|
|
59
65
|
sp.update(title: "#{gem_name} #{version}")
|
|
60
66
|
barrier.async do
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
result = install_one(gem_name, version, sp, force: force, model: model, verify: verify, max_tokens: max_tokens)
|
|
68
|
+
results << result
|
|
69
|
+
errors << "#{gem_name} #{version}: #{result.error}" if result.error
|
|
63
70
|
end
|
|
64
71
|
end
|
|
65
72
|
barrier.wait
|
|
@@ -69,14 +76,18 @@ module Gem::Skill
|
|
|
69
76
|
|
|
70
77
|
Linker.prune_dead_links
|
|
71
78
|
report_errors(errors)
|
|
79
|
+
report_verify(results, verify)
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def self.refresh(opts = {})
|
|
75
83
|
gems = Lockfile.gems
|
|
76
|
-
linked
|
|
77
|
-
force
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
linked = Linker.linked_gems.to_h { |e| [e[:gem_name], e[:version]] }
|
|
85
|
+
force = opts[:force]
|
|
86
|
+
verify = opts[:verify]
|
|
87
|
+
model = opts[:model] || Generator::DEFAULT_MODEL
|
|
88
|
+
max_tokens = opts[:max_tokens] || Generator::MAX_TOKENS
|
|
89
|
+
errors = []
|
|
90
|
+
results = []
|
|
80
91
|
|
|
81
92
|
multi = TTY::Spinner::Multi.new(
|
|
82
93
|
"[:spinner] Refreshing skills (#{model})",
|
|
@@ -90,14 +101,15 @@ module Gem::Skill
|
|
|
90
101
|
sp = multi.register(" [:spinner] :title")
|
|
91
102
|
sp.update(title: "#{gem_name} #{version}")
|
|
92
103
|
barrier.async do
|
|
93
|
-
|
|
104
|
+
result = if !force && linked[gem_name] == version
|
|
94
105
|
sp.auto_spin
|
|
95
106
|
sp.success("up to date")
|
|
96
|
-
|
|
107
|
+
Runner::Result.success
|
|
97
108
|
else
|
|
98
|
-
install_one(gem_name, version, sp, force: force, model: model)
|
|
109
|
+
install_one(gem_name, version, sp, force: force, model: model, verify: verify, max_tokens: max_tokens)
|
|
99
110
|
end
|
|
100
|
-
|
|
111
|
+
results << result
|
|
112
|
+
errors << "#{gem_name} #{version}: #{result.error}" if result.error
|
|
101
113
|
end
|
|
102
114
|
end
|
|
103
115
|
barrier.wait
|
|
@@ -107,6 +119,7 @@ module Gem::Skill
|
|
|
107
119
|
|
|
108
120
|
Linker.prune_dead_links
|
|
109
121
|
report_errors(errors)
|
|
122
|
+
report_verify(results, verify)
|
|
110
123
|
end
|
|
111
124
|
|
|
112
125
|
def self.list
|
|
@@ -120,7 +133,7 @@ module Gem::Skill
|
|
|
120
133
|
ok = entries.count { |e| e[:valid] }
|
|
121
134
|
broken = entries.size - ok
|
|
122
135
|
|
|
123
|
-
puts "Skills linked in .
|
|
136
|
+
puts "Skills linked in #{Linker.project_dir}/ (#{ok} ok#{broken > 0 ? ", #{broken} broken" : ""}):"
|
|
124
137
|
puts ""
|
|
125
138
|
entries.each do |e|
|
|
126
139
|
status = e[:valid] ? "ok " : "BROKEN"
|
|
@@ -130,9 +143,9 @@ module Gem::Skill
|
|
|
130
143
|
|
|
131
144
|
# --- private ---
|
|
132
145
|
|
|
133
|
-
def self.install_one(gem_name, version, spinner, force:, model:)
|
|
146
|
+
def self.install_one(gem_name, version, spinner, force:, model:, verify: false, max_tokens: Generator::MAX_TOKENS)
|
|
134
147
|
spinner.auto_spin
|
|
135
|
-
Runner.install_skill(gem_name, version, spinner, force: force, model: model)
|
|
148
|
+
Runner.install_skill(gem_name, version, spinner, force: force, model: model, verify: verify, max_tokens: max_tokens)
|
|
136
149
|
end
|
|
137
150
|
private_class_method :install_one
|
|
138
151
|
|
|
@@ -144,6 +157,20 @@ module Gem::Skill
|
|
|
144
157
|
end
|
|
145
158
|
private_class_method :report_errors
|
|
146
159
|
|
|
160
|
+
# When --verify applied fixes, report and exit non-zero so callers/CI can
|
|
161
|
+
# detect that the README-derived skill disagreed with the source.
|
|
162
|
+
def self.report_verify(results, verify)
|
|
163
|
+
return unless verify
|
|
164
|
+
|
|
165
|
+
fixed = results.count(&:verify_fixed)
|
|
166
|
+
return if fixed.zero?
|
|
167
|
+
|
|
168
|
+
warn ""
|
|
169
|
+
warn "Verify corrected #{fixed} skill(s) against gem source."
|
|
170
|
+
exit Gem::Skill::EXIT_VERIFY_FIXED
|
|
171
|
+
end
|
|
172
|
+
private_class_method :report_verify
|
|
173
|
+
|
|
147
174
|
def self.parse_options(args)
|
|
148
175
|
opts = {}
|
|
149
176
|
remaining = []
|
|
@@ -151,9 +178,12 @@ module Gem::Skill
|
|
|
151
178
|
args.each do |arg|
|
|
152
179
|
case arg
|
|
153
180
|
when "--force" then opts[:force] = true
|
|
181
|
+
when "--verify" then opts[:verify] = true
|
|
154
182
|
when "--version", "-v" then opts[:version] = true
|
|
155
183
|
when /\A--model(?:=(.+))?\z/
|
|
156
184
|
opts[:model] = $1 || args[args.index(arg) + 1]
|
|
185
|
+
when /\A--max-tokens(?:=(.+))?\z/
|
|
186
|
+
opts[:max_tokens] = ($1 || args[args.index(arg) + 1]).to_i
|
|
157
187
|
else
|
|
158
188
|
remaining << arg unless opts[:model].nil? && arg !~ /\A--/
|
|
159
189
|
remaining << arg if arg !~ /\A--/
|
|
@@ -170,13 +200,18 @@ module Gem::Skill
|
|
|
170
200
|
|
|
171
201
|
Subcommands:
|
|
172
202
|
install Generate and link skills for all gems in Gemfile.lock
|
|
173
|
-
refresh Re-sync
|
|
203
|
+
refresh Re-sync the project skill directory after bundle update
|
|
174
204
|
list Show skills linked in this project
|
|
175
205
|
|
|
176
206
|
Options:
|
|
177
|
-
--force
|
|
178
|
-
--
|
|
179
|
-
--
|
|
207
|
+
--force Regenerate even if already cached
|
|
208
|
+
--verify Verify generated skills against gem source and fix mismatches
|
|
209
|
+
--model MODEL LLM model to use (default: #{Generator::DEFAULT_MODEL})
|
|
210
|
+
--max-tokens TOKENS Max output tokens (overrides GEMSKIL_MAX_TOKENS; default: #{Generator::MAX_TOKENS})
|
|
211
|
+
--version, -v Print gem-skill version and exit
|
|
212
|
+
|
|
213
|
+
Env:
|
|
214
|
+
GEMSKILL_PROJECT_DIR Project dir for symlinks (default: .claude/skills)
|
|
180
215
|
USAGE
|
|
181
216
|
end
|
|
182
217
|
private_class_method :usage
|