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.
Files changed (37) hide show
  1. skill_graft-0.4.0/LICENSE +18 -0
  2. skill_graft-0.4.0/PKG-INFO +360 -0
  3. skill_graft-0.4.0/README.md +348 -0
  4. skill_graft-0.4.0/pyproject.toml +99 -0
  5. skill_graft-0.4.0/pyproject.toml.orig +90 -0
  6. skill_graft-0.4.0/src/skill_graft/__init__.py +0 -0
  7. skill_graft-0.4.0/src/skill_graft/cli.py +514 -0
  8. skill_graft-0.4.0/src/skill_graft/commands/__init__.py +1 -0
  9. skill_graft-0.4.0/src/skill_graft/commands/deploy.py +125 -0
  10. skill_graft-0.4.0/src/skill_graft/commands/edit.py +270 -0
  11. skill_graft-0.4.0/src/skill_graft/commands/explain.py +432 -0
  12. skill_graft-0.4.0/src/skill_graft/commands/housekeeping.py +10 -0
  13. skill_graft-0.4.0/src/skill_graft/commands/init_package.py +100 -0
  14. skill_graft-0.4.0/src/skill_graft/commands/resolve.py +33 -0
  15. skill_graft-0.4.0/src/skill_graft/commands/review.py +372 -0
  16. skill_graft-0.4.0/src/skill_graft/commands/scope.py +35 -0
  17. skill_graft-0.4.0/src/skill_graft/commands/transaction.py +162 -0
  18. skill_graft-0.4.0/src/skill_graft/commands/update.py +341 -0
  19. skill_graft-0.4.0/src/skill_graft/infra/__init__.py +1 -0
  20. skill_graft-0.4.0/src/skill_graft/infra/apm.py +79 -0
  21. skill_graft-0.4.0/src/skill_graft/infra/cache.py +584 -0
  22. skill_graft-0.4.0/src/skill_graft/infra/errors.py +67 -0
  23. skill_graft-0.4.0/src/skill_graft/infra/files.py +87 -0
  24. skill_graft-0.4.0/src/skill_graft/infra/git.py +118 -0
  25. skill_graft-0.4.0/src/skill_graft/infra/paths.py +127 -0
  26. skill_graft-0.4.0/src/skill_graft/pipeline/__init__.py +2 -0
  27. skill_graft-0.4.0/src/skill_graft/pipeline/compute.py +181 -0
  28. skill_graft-0.4.0/src/skill_graft/pipeline/config.py +397 -0
  29. skill_graft-0.4.0/src/skill_graft/pipeline/deployed.py +401 -0
  30. skill_graft-0.4.0/src/skill_graft/pipeline/discovery.py +213 -0
  31. skill_graft-0.4.0/src/skill_graft/pipeline/hidden.py +179 -0
  32. skill_graft-0.4.0/src/skill_graft/pipeline/hull.py +306 -0
  33. skill_graft-0.4.0/src/skill_graft/pipeline/lock.py +278 -0
  34. skill_graft-0.4.0/src/skill_graft/pipeline/patch.py +171 -0
  35. skill_graft-0.4.0/src/skill_graft/pipeline/references.py +205 -0
  36. skill_graft-0.4.0/src/skill_graft/pipeline/rename.py +181 -0
  37. 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
+ [![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fremigius42%2Fskill-graft%2Fmain%2Fpyproject.toml&query=%24.project.version&label=version)](pyproject.toml)
16
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
17
+ [![CI](https://github.com/remigius42/skill-graft/actions/workflows/ci.yml/badge.svg)](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).