skill-graft 0.4.0__tar.gz
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.
- skill_graft-0.4.0/LICENSE +18 -0
- skill_graft-0.4.0/PKG-INFO +360 -0
- skill_graft-0.4.0/README.md +348 -0
- skill_graft-0.4.0/pyproject.toml +99 -0
- skill_graft-0.4.0/pyproject.toml.orig +90 -0
- skill_graft-0.4.0/src/skill_graft/__init__.py +0 -0
- skill_graft-0.4.0/src/skill_graft/cli.py +514 -0
- skill_graft-0.4.0/src/skill_graft/commands/__init__.py +1 -0
- skill_graft-0.4.0/src/skill_graft/commands/deploy.py +125 -0
- skill_graft-0.4.0/src/skill_graft/commands/edit.py +270 -0
- skill_graft-0.4.0/src/skill_graft/commands/explain.py +432 -0
- skill_graft-0.4.0/src/skill_graft/commands/housekeeping.py +10 -0
- skill_graft-0.4.0/src/skill_graft/commands/init_package.py +100 -0
- skill_graft-0.4.0/src/skill_graft/commands/resolve.py +33 -0
- skill_graft-0.4.0/src/skill_graft/commands/review.py +372 -0
- skill_graft-0.4.0/src/skill_graft/commands/scope.py +35 -0
- skill_graft-0.4.0/src/skill_graft/commands/transaction.py +162 -0
- skill_graft-0.4.0/src/skill_graft/commands/update.py +341 -0
- skill_graft-0.4.0/src/skill_graft/infra/__init__.py +1 -0
- skill_graft-0.4.0/src/skill_graft/infra/apm.py +79 -0
- skill_graft-0.4.0/src/skill_graft/infra/cache.py +584 -0
- skill_graft-0.4.0/src/skill_graft/infra/errors.py +67 -0
- skill_graft-0.4.0/src/skill_graft/infra/files.py +87 -0
- skill_graft-0.4.0/src/skill_graft/infra/git.py +118 -0
- skill_graft-0.4.0/src/skill_graft/infra/paths.py +127 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/__init__.py +2 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/compute.py +181 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/config.py +397 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/deployed.py +401 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/discovery.py +213 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/hidden.py +179 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/hull.py +306 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/lock.py +278 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/patch.py +171 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/references.py +205 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/rename.py +181 -0
- skill_graft-0.4.0/src/skill_graft/pipeline/vendor.py +120 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright 2026 Andreas Remigius Schmidt
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
+
subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skill-graft
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Graft agent skills from other people's repositories onto your own setup
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Operating System :: MacOS
|
|
8
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
9
|
+
Requires-Dist: tomlkit>=0.15.2
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# [skill-graft](https://github.com/remigius42/skill-graft)
|
|
14
|
+
|
|
15
|
+
[](pyproject.toml)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://github.com/remigius42/skill-graft/actions/workflows/ci.yml)
|
|
18
|
+
|
|
19
|
+
Copyright 2026 [Andreas Remigius Schmidt](https://github.com/remigius42)
|
|
20
|
+
|
|
21
|
+
Graft agent skills from other people's repositories onto your own setup.
|
|
22
|
+
|
|
23
|
+
You name the skills you want. skill-graft follows their references to other skills and files, vendors the whole graph,
|
|
24
|
+
applies your renames and patches, and hands the result to [APM](https://github.com/microsoft/apm) to deploy into Claude
|
|
25
|
+
Code, opencode, and the other harnesses APM supports.
|
|
26
|
+
|
|
27
|
+
Skills call each other (`/grilling`, "call the Skill tool with `domain-modeling`"), so taking one skill means taking
|
|
28
|
+
every skill it reaches. Installing a whole collection is fine for a large model, but on a smaller one the full skill set
|
|
29
|
+
crowds the context, and taking only a subset by hand means tracing those calls yourself, skill by skill. skill-graft
|
|
30
|
+
computes that hull, the skills you ask for plus every skill they call, and deploys only those skills. Renames come with
|
|
31
|
+
it: a skill named like a harness built-in (`code-review`) shadows it or is shadowed by it, and renaming one skill by
|
|
32
|
+
hand misses the other skills that call it by name.
|
|
33
|
+
|
|
34
|
+
If you already know every skill you need, APM alone can install just those: `apm install <owner>/<repo> --skill <name>`
|
|
35
|
+
([docs](https://microsoft.github.io/apm/reference/cli/install/#skill-subset)). skill-graft also finds the skills they
|
|
36
|
+
reference, lets you rename skills whose names clash, shows who calls what (`tree`, `why`), and applies your patches,
|
|
37
|
+
checking on `update` that they still apply.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
skill-graft runs on Linux and macOS, and on Windows through [WSL](https://learn.microsoft.com/windows/wsl/). Native
|
|
42
|
+
Windows is unsupported: there every command prints `skill-graft: native Windows is unsupported: run skill-graft in WSL`
|
|
43
|
+
and exits 1.
|
|
44
|
+
|
|
45
|
+
skill-graft needs Python 3.11 or later. It is not on PyPI; install a release from the repository with any of:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
uv tool install git+https://github.com/remigius42/skill-graft@v0.4.0
|
|
49
|
+
pipx install --python python3.11 git+https://github.com/remigius42/skill-graft@v0.4.0
|
|
50
|
+
pip install git+https://github.com/remigius42/skill-graft@v0.4.0 # into the active environment
|
|
51
|
+
pipenv install "skill-graft @ git+https://github.com/remigius42/skill-graft@v0.4.0"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
uv picks a suitable Python itself; pipx and pip use theirs, so point them at 3.11 or later. pipenv installs into that
|
|
55
|
+
project's environment: run it as `pipenv run skill-graft`.
|
|
56
|
+
|
|
57
|
+
skill-graft also runs `git`. With git 2.44 or later, looking up what the [cache](#cache) holds never contacts the
|
|
58
|
+
remote; the git macOS ships is older, Homebrew's is not. A remote that refuses files asked by name needs git 2.36 or
|
|
59
|
+
later.
|
|
60
|
+
|
|
61
|
+
These install the latest release; the others are listed under
|
|
62
|
+
[releases](https://github.com/remigius42/skill-graft/releases), and each tag's README describes its release. To install
|
|
63
|
+
the unreleased `main` instead, drop `@v0.4.0` from the URL. `skill-graft --version` names what is installed: the
|
|
64
|
+
version, the tag or branch the URL names and the commit, `skill-graft <version> (v<version> @ <commit>)`, or only the
|
|
65
|
+
commit for a URL naming neither.
|
|
66
|
+
|
|
67
|
+
The release tags after `v0.3.0` are signed with this SSH key, fingerprint
|
|
68
|
+
`SHA256:U5FNzbtnPR2kPX/Edlg1+p7rJmlBIx88HBKkzBVmOWE`:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
remigius42-github.dcds7@aleeas.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHKYDmxyNPjrRUXm2mr6s8UiL/yT89vmPC0gPB1pZc2L
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
GitHub shows such a tag as **Verified**. To check one yourself, save that line as `~/allowed_signers`, then in a clone
|
|
75
|
+
run `git -c gpg.ssh.allowedSignersFile="$HOME/allowed_signers" verify-tag v<version>`; git reads a relative path from
|
|
76
|
+
the clone's top folder. A tag can be moved, a commit cannot: to install exactly what you checked, put the commit
|
|
77
|
+
`git rev-parse "v<version>^{commit}"` prints in the URL in place of the tag.
|
|
78
|
+
|
|
79
|
+
skill-graft runs APM as a command, so `apm` must be on `PATH` as well, in version 0.35.0: the folders APM deploys to
|
|
80
|
+
were measured on it, and another version may deploy elsewhere. Install it the same way, e.g.
|
|
81
|
+
`uv tool install apm-cli==0.35.0`, `pipx install apm-cli==0.35.0` or `pip install apm-cli==0.35.0`.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
Besides skill-graft and APM, you need a harness folder for APM to deploy into, such as `~/.claude/` for Claude Code. To
|
|
86
|
+
deploy the `tdd` skill of [mattpocock/skills](https://github.com/mattpocock/skills), and every skill it calls, create
|
|
87
|
+
your package:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
skill-graft init
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`init` writes the package's config, `~/.apm/skill-graft/skill-graft.toml`, all commented out. Add the repository to it
|
|
94
|
+
as a source, a new table after the comments (the commented `[sources.mp]` there is only an example):
|
|
95
|
+
|
|
96
|
+
```toml
|
|
97
|
+
[sources.mp]
|
|
98
|
+
git = "https://github.com/mattpocock/skills.git"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Then:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
skill-graft update mp # set the source's ref to its branch tip
|
|
105
|
+
skill-graft add mp/tdd --dry-run --diff-file review.diff # write the text it would vendor, nothing else
|
|
106
|
+
skill-graft add mp/tdd # ask for the skill; vendors the hull and writes the lock
|
|
107
|
+
skill-graft tree # see the hull: mp/tdd and every skill it calls
|
|
108
|
+
skill-graft deploy # deploy what skill-graft.lock records through APM
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The dry run writes `mp/tdd` and every skill it calls, as they would deploy, to `review.diff`, and writes nothing else.
|
|
112
|
+
Read it in an editor, never by printing it to a terminal: it is untrusted text. Then run the `add` itself.
|
|
113
|
+
|
|
114
|
+
After editing `skill-graft.toml` or a patch by hand, run `skill-graft resolve` before `deploy`, previewing it first with
|
|
115
|
+
`skill-graft resolve --dry-run --diff-file review.diff`: `deploy` deploys only what the lock records, and names
|
|
116
|
+
`resolve` when the config or a patch differs from it. An edit to a vendored copy in `.apm/skills/` is refused too; move
|
|
117
|
+
it into a patch, or delete the folder to vendor it again.
|
|
118
|
+
|
|
119
|
+
`tdd` and the skills it calls are now in `~/.claude/skills/`. To share skills through a project instead, see
|
|
120
|
+
[A package for one project](#a-package-for-one-project); to deploy them under other names, `[rename]` and `prefix` in
|
|
121
|
+
[Config](#config); to change what a skill says, [Writing a patch](#writing-a-patch).
|
|
122
|
+
|
|
123
|
+
Later, to move `mp` to its new tip, review what would change first:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
skill-graft update mp --dry-run --diff-file review.diff # write each changed skill's text as deployed
|
|
127
|
+
skill-graft update mp --expect <hull-digest> # the `apply:` line the dry run printed
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Read `review.diff` in an editor, as before. The `apply:` line updates to exactly the hull you read; a push, a path
|
|
131
|
+
source's edit, a patch or a rename since then makes it refuse.
|
|
132
|
+
|
|
133
|
+
## Commands
|
|
134
|
+
|
|
135
|
+
`resolve`, `update` and the editing commands resolve and stage: they write the lock and `.apm/skills/` together, and
|
|
136
|
+
never deploy. `deploy` checks the package against the lock and deploys it through APM.
|
|
137
|
+
|
|
138
|
+
Options are taken only spelled in full: `--dry` is a usage error, not `--dry-run`.
|
|
139
|
+
|
|
140
|
+
### Setup
|
|
141
|
+
|
|
142
|
+
| Command | What it does |
|
|
143
|
+
| ------------------ | ----------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `init [--project]` | Create the package, `~/.apm/skill-graft/` (or `./.apm/skill-graft/`), and register it with APM. |
|
|
145
|
+
|
|
146
|
+
### Staging
|
|
147
|
+
|
|
148
|
+
| Command | What it does |
|
|
149
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
150
|
+
| `resolve` | Compute the hull from the config as it stands, vendor it patched and renamed, print the tree and the skills prose mentions outside the hull. |
|
|
151
|
+
| `add <skill-id>…` | Append skills to `entries`, keeping the file's comments, once the result resolves. |
|
|
152
|
+
| `remove <skill-id>…` | Delete skills from `entries`; one another entry reaches stays in the hull. |
|
|
153
|
+
| `rename <skill-id> <name>` | Set a skill's deployed name; the name it would deploy as without a key undoes it. |
|
|
154
|
+
| `bind <alias>/<name> <skill-id>\|false` | Resolve `<name>` from that source to the skill, or to no reference. |
|
|
155
|
+
| `unbind <alias>/<name>` | Remove that bind, so `<name>` resolves by default again. |
|
|
156
|
+
| `update [source…] [--expect <hull-digest>]` | Move each git source's `ref` to its branch tip, if the hull still resolves and every patch still applies there. |
|
|
157
|
+
|
|
158
|
+
Each staging command takes `--dry-run` and `--diff-file <file>`. `--dry-run` resolves and prints the hull diff, each
|
|
159
|
+
added, removed or changed skill with its changed files but never their text, and writes nothing but `--diff-file`'s
|
|
160
|
+
file. `--diff-file <file>` writes that text as deployed, the Deployed diff, to a new `*.diff` file, or one it wrote
|
|
161
|
+
before; read it in an editor. `update` compares against the hull at the current refs, the other commands against the
|
|
162
|
+
vendored copies. `update`'s `--expect <hull-digest>` updates only to the hull a dry run showed.
|
|
163
|
+
|
|
164
|
+
### Deploying
|
|
165
|
+
|
|
166
|
+
| Command | What it does |
|
|
167
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
168
|
+
| `deploy [--no-apm-install \| --into <dir>]` | Deploy what the lock records through APM, refusing a hull other than the lock's or an edited vendored copy. `--no-apm-install` stops before running APM. `--into <dir>` writes the skills and the lock into an empty `<dir>` instead, never touching the package or APM. |
|
|
169
|
+
|
|
170
|
+
### Inspecting
|
|
171
|
+
|
|
172
|
+
| Command | What it does |
|
|
173
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `tree [--format text\|mermaid [--link]]` | Print the hull, writing nothing; with `--format mermaid`, `--link` gives a mermaid.live URL: see the warning below. |
|
|
175
|
+
| `why <skill>` | Print the paths from the entries that reach a skill, and its upstream folder. |
|
|
176
|
+
| `help [command]`, or `--help` on any command | Explain it. |
|
|
177
|
+
|
|
178
|
+
Warning: `tree --format mermaid --link` hands the diagram to mermaid.live, a third-party site whose page code can read
|
|
179
|
+
it: it then sees which upstream repositories and refs you use, a target list for a supply-chain attack on them.
|
|
180
|
+
|
|
181
|
+
## Config
|
|
182
|
+
|
|
183
|
+
`skill-graft.toml`, in the package:
|
|
184
|
+
|
|
185
|
+
```toml
|
|
186
|
+
entries = ["mp/tdd", "mp/implement", "own/chisel"]
|
|
187
|
+
reserved = ["review"]
|
|
188
|
+
|
|
189
|
+
[sources.mp]
|
|
190
|
+
git = "https://github.com/mattpocock/skills.git"
|
|
191
|
+
branch = "main" # optional; defaults to the remote's default branch
|
|
192
|
+
ref = "<sha>"
|
|
193
|
+
prefix = "mp-" # optional; every skill of `mp` deploys as mp-<name>
|
|
194
|
+
|
|
195
|
+
[sources.cp]
|
|
196
|
+
git = "https://github.com/cursor/plugins.git"
|
|
197
|
+
ref = "<sha>"
|
|
198
|
+
subdirectory = "pstack/skills" # optional; read only this folder of the repository
|
|
199
|
+
|
|
200
|
+
[sources.own]
|
|
201
|
+
path = "~/skills"
|
|
202
|
+
|
|
203
|
+
[rename]
|
|
204
|
+
"own/chisel" = "own-chisel"
|
|
205
|
+
"mp/tdd" = "tdd" # wins over the prefix
|
|
206
|
+
|
|
207
|
+
[bind]
|
|
208
|
+
"mp/grilling" = "own/grilling"
|
|
209
|
+
"mp/tmp" = false # `/tmp` from `mp` is not a reference
|
|
210
|
+
|
|
211
|
+
[patches]
|
|
212
|
+
"mp/implement" = ["patches/implement-no-pr.patch"]
|
|
213
|
+
|
|
214
|
+
[check] # optional; graft-check's limits, defaults shown
|
|
215
|
+
reader-turns = 30
|
|
216
|
+
reader-budget-usd = 1.0
|
|
217
|
+
patcher-turns = 10
|
|
218
|
+
patcher-budget-usd = 0.5
|
|
219
|
+
readers-at-once = 4
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- `entries`: the skills you ask for. Slash references and Skill tool calls (`Skill(x)`, or "call the Skill tool with
|
|
223
|
+
`x`") pull in the rest of the hull; prose mentions only become suggestions.
|
|
224
|
+
- `sources`: local folders (`path`) and git repositories (`git`), read at the commit `ref` names.
|
|
225
|
+
- `subdirectory`: a git source is read only in this folder, as if it were the repository's root, for a repository
|
|
226
|
+
holding several plugins whose skills share names. It must be a real folder at the commit read, or the command fails;
|
|
227
|
+
`update` then leaves `ref` where it is. The lock still records each skill's `upstream` relative to the repository's
|
|
228
|
+
root.
|
|
229
|
+
- `prefix` and `[rename]`: a skill deploys as its `[rename]` value, else its source's prefix before its name, else its
|
|
230
|
+
name. Carried into its folder, `name:` and every reference.
|
|
231
|
+
- `[bind]`: settles which skill a reference means when several sources have one of that name, or with `false` makes it
|
|
232
|
+
no reference at all.
|
|
233
|
+
- `[patches]`: unified diffs, applied before renames, so a patch can change what a skill calls (see
|
|
234
|
+
[Writing a patch](#writing-a-patch)).
|
|
235
|
+
- `reserved`: deployed names to refuse, on top of the harness built-ins skill-graft warns about.
|
|
236
|
+
- `[check]`: how far graft-check's readers and patcher may go: turns and spend in USD each, and how many readers run at
|
|
237
|
+
once. Each value is written in plain decimal digits; [docs/security.md](docs/security.md#limits) says what reaching a
|
|
238
|
+
limit does.
|
|
239
|
+
|
|
240
|
+
## A package for one project
|
|
241
|
+
|
|
242
|
+
`skill-graft init` creates your package, `~/.apm/skill-graft/`, deployed for you in every project. For skills a team
|
|
243
|
+
shares through one repository, run `skill-graft init --project` in its root (which needs a harness folder such as
|
|
244
|
+
`.claude/`, or APM refuses): it creates `.apm/skill-graft/` there and registers it in the project's `apm.yml`. Every
|
|
245
|
+
command then acts on the nearest `.apm/skill-graft/` above the working folder, and on `~/.apm/skill-graft/` when there
|
|
246
|
+
is none.
|
|
247
|
+
|
|
248
|
+
Commit the whole `.apm/skill-graft/`, vendored skills and its `.gitattributes` included, with `apm.yml` and
|
|
249
|
+
`apm.lock.yaml`; `.gitattributes` keeps git from changing the vendored skills' line endings, which `deploy` would refuse
|
|
250
|
+
as edits. Teammates then need only APM: `apm install` deploys the skills, and only whoever changes them runs
|
|
251
|
+
skill-graft. Keep path sources inside the project (`path = "../../skills"`) and the rest git sources: the lock then
|
|
252
|
+
records them relative to the package and reads the same on every machine; a path outside the project is recorded
|
|
253
|
+
absolute.
|
|
254
|
+
|
|
255
|
+
## Checking a package
|
|
256
|
+
|
|
257
|
+
skill-graft finds references by their form alone, so it misses "run the grilling skill", takes `/tmp` for a call, and
|
|
258
|
+
can rewrite a word a prefix happens to match. The `graft-check` skill, in this repository's `skills/`, has an agent read
|
|
259
|
+
the vendored skills for those mistakes. Install it like any other skill, with the lines `init` writes commented out into
|
|
260
|
+
`skill-graft.toml`:
|
|
261
|
+
|
|
262
|
+
```toml
|
|
263
|
+
[sources.skill-graft]
|
|
264
|
+
git = "https://github.com/remigius42/skill-graft.git"
|
|
265
|
+
subdirectory = "skills"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
skill-graft update skill-graft
|
|
270
|
+
skill-graft add skill-graft/graft-check
|
|
271
|
+
skill-graft deploy
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Until that `update` sets the source's `ref`, every `deploy` fails, not only graft-check's.
|
|
275
|
+
|
|
276
|
+
Run it in your harness as `/graft-check`, or `/graft-check mp/tdd` to check only some skills; it runs only when you ask.
|
|
277
|
+
It deploys into a temporary folder with `deploy --into`, and has one reader per skill compare its upstream text with its
|
|
278
|
+
vendored copy. The readers report missed and false references, references resolved to the wrong skill, wrong or missed
|
|
279
|
+
rewrites, and relative links vendoring broke. For each finding it proposes `add`, `bind`, `unbind` or a patch, and
|
|
280
|
+
applies it only once you accept; a patch is written by a confined patcher, and you read its diff before it goes into the
|
|
281
|
+
package. It never runs APM: when the package would change, it tells you to run `skill-graft deploy`. It reads lock
|
|
282
|
+
`version` 1 only; for a newer lock, update the source you installed it from.
|
|
283
|
+
|
|
284
|
+
## Cache
|
|
285
|
+
|
|
286
|
+
Git **Sources** are cached under `$XDG_CACHE_HOME/skill-graft/`, else `~/.cache/skill-graft/`: one repository per URL,
|
|
287
|
+
holding commits and trees and only the files read, and one export per commit and `subdirectory`.
|
|
288
|
+
|
|
289
|
+
- Any command but `init` and a `--dry-run` removes, as it ends, each export no command has used for 30 days. One removed
|
|
290
|
+
is exported again from its repository when next needed, without the remote.
|
|
291
|
+
- No command removes a repository. Every **Ref** read stays in its repository, with its files, even after it vanished
|
|
292
|
+
from every branch upstream.
|
|
293
|
+
- Deleting a repository, or the whole folder, by hand is safe unless a **Ref** you use has since vanished upstream: that
|
|
294
|
+
one cannot be fetched again.
|
|
295
|
+
|
|
296
|
+
## Security
|
|
297
|
+
|
|
298
|
+
Skill text comes from other people's repositories and may carry prompt injection. [docs/security.md](docs/security.md)
|
|
299
|
+
has the details.
|
|
300
|
+
|
|
301
|
+
- skill-graft fails resolution on a skill holding a hidden character (tag characters, bidi controls, supplementary
|
|
302
|
+
variation selectors), as APM's install does, so none reaches any agent through the hull.
|
|
303
|
+
- Wherever a working Claude Code is installed, graft-check reads skill text only through `claude -p --restricted`
|
|
304
|
+
readers limited to `Read`, `Grep` and `Glob`, without MCP servers, confined to that skill's folders, and patches
|
|
305
|
+
through a patcher confined to a temporary folder. A probe reader must answer first; outside Claude Code it asks before
|
|
306
|
+
using your Claude Code account. The probe and keeping skill text from the main agent are steps of the skill: they hold
|
|
307
|
+
only as far as your harness's model follows them. Otherwise it says it is unhardened and why, and reads only on your
|
|
308
|
+
yes; an unhardened check is likely not secure.
|
|
309
|
+
- In a hardened check, each reader and the patcher stop at a cap on their turns and their spend, and at most 4 readers
|
|
310
|
+
run at once, all settable in `[check]` ([Limits](docs/security.md#limits)). The patcher's reply never reaches the main
|
|
311
|
+
agent, and its diff reaches it only as counts.
|
|
312
|
+
- A reader's reply reaches the main agent only as a report filter admits it, held to the report shape and bound in code,
|
|
313
|
+
or, when the reader fails, as one bounded error line.
|
|
314
|
+
- Run it outside auto mode; in Claude Code's sandbox, allow the API's domain. Fix arguments and how many readers run at
|
|
315
|
+
once are checked at the prompt level only.
|
|
316
|
+
|
|
317
|
+
Report a vulnerability privately, as [SECURITY.md](SECURITY.md) says, not in a public issue.
|
|
318
|
+
|
|
319
|
+
## Writing a patch
|
|
320
|
+
|
|
321
|
+
A patch is a unified diff against the skill's upstream folder, which `skill-graft why <skill>` prints on its last line.
|
|
322
|
+
No command writes one; in a shell (a project package keeps its patches in its own `.apm/skill-graft/`):
|
|
323
|
+
|
|
324
|
+
```sh
|
|
325
|
+
upstream="$(skill-graft why mp/implement | sed -n 's/^upstream: //p')"
|
|
326
|
+
cd "$(mktemp -d)"
|
|
327
|
+
cp -rL "$upstream" base && rm -rf base/.git
|
|
328
|
+
# Only for a skill that already has patches: apply them first, in order.
|
|
329
|
+
(cd base && git apply -p1 ~/.apm/skill-graft/patches/first.patch)
|
|
330
|
+
cp -r base edited
|
|
331
|
+
"$EDITOR" edited/SKILL.md
|
|
332
|
+
LC_ALL=C diff -ruN base/ edited/ > ~/.apm/skill-graft/patches/second.patch
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Then list it, after the skill's earlier patches:
|
|
336
|
+
|
|
337
|
+
```toml
|
|
338
|
+
[patches]
|
|
339
|
+
"mp/implement" = ["patches/first.patch", "patches/second.patch"]
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`skill-graft resolve --dry-run --diff-file review.diff` writes the patched skill as it would deploy to `review.diff`;
|
|
343
|
+
read it in an editor, then run `skill-graft resolve`.
|
|
344
|
+
|
|
345
|
+
`diff` needs `-N`, or added and deleted files are lost; skill-graft refuses such a patch, and binary changes, by the
|
|
346
|
+
lines `diff` writes for them in English: hence `LC_ALL=C`. A file such a patch leaves empty is deleted, with any folder
|
|
347
|
+
that leaves empty, as `-N` writes a deleted file as one emptied; so any `diff`, GNU or the BSD one of macOS, deletes
|
|
348
|
+
alike. A `git diff` patch applies as git applies it. A patch must not change the skill's `name:`: use `[rename]`. When
|
|
349
|
+
`update` finds that a patch no longer applies at a new commit, it keeps the old `ref` and names the upstream folder at
|
|
350
|
+
the new commit: redo the patch against it, then run `update` again.
|
|
351
|
+
|
|
352
|
+
## Contributing
|
|
353
|
+
|
|
354
|
+
Report bugs and suggest ideas in [GitHub issues](https://github.com/remigius42/skill-graft/issues), and vulnerabilities
|
|
355
|
+
as [SECURITY.md](SECURITY.md) says. To change the code, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
356
|
+
|
|
357
|
+
This is a personal exploration project with best-effort maintenance. Responses to issues or pull requests may be slow,
|
|
358
|
+
and changes that don't align with the project's direction are unlikely to merge.
|
|
359
|
+
|
|
360
|
+
If skill-graft saves you time, you can [buy me a coffee](https://buymeacoffee.com/remigius).
|