Repository navigation
Releases
Releases · remigius42/skill-graft
skill-graft 0.4.0
Added
- graft-check checks the lock before it reads it, and ends the Check on a line skill-graft would not write, naming
no value and advising to update skill-graft add,remove,rename,bind,unbindandresolvetake--dry-runand--diff-file <file>, to see what they
would vendor before they write it.--dry-runprints the Hull diff of the Vendored copies against what the
command would vendor, each changed file named, and writes nothing;--diff-file <file>writes that text as deployed,
the Deployed diff, to read in an editor. Before the firstaddevery Skill shows as added
Changed
- The README installs the latest release instead of
main - BREAKING:
buildis nowdeploy, its--no-installis--no-apm-install, andupdate --diff <file>is
update --diff-file <file>; the old names exit 2.deploy --into <dir>is whatbuild --into <dir>was. Options are
no longer accepted abbreviated:--dryfor--dry-runexits 2. Upgrade skill-graft first, then update its Source
(skill-graft update skill-graftunderinit's alias) and runskill-graft deploy: a graft-check vendored before
this release runsbuild --intoand ends with an error, and the new one runsdeploy --into, which an older
skill-graft refuses - A Deployed diff's first line names no command; a
review.diff0.3.0 wrote is still overwritten - Each Deployed diff,
update --diff-file's too, opens after its header with the Hull astreeprints it, each
Skill marked+added or~changed, then a# - <skill id>line per removed Skill - In a hardened
/graft-check, a reader's reply reaches the main agent only as a report filter shipped with the skill
admits it: a report in the report shape, at most 100 findings and 200 uses, each value one line of printable ASCII of
at most 200 characters, a character outside it shown as?. Any other reply runs its reader once more; a second one
leaves its Skill with no report,not in the report shape, and none of either is shown - A reader that fails shows one line of its error, in printable ASCII, at most 200 characters, as a failed patcher does
- A Skill under a folder named with anything but
A-Z a-z 0-9 . _ -, or with only dots, is unusable: an Entry,
a[bind]target, or a slash orSkill(<name>)Reference that needs it fails, naming the folder with every other
byte written in octal (my\040skills/tdd). Elsewhere in its Source it blocks nothing. A path Source renames
the folder; a git one needs a fork - A git Source's
subdirectorytakes those characters only:subdirectory = "my skills"is a config error - The error for a Skill whose name is no path segment writes the name and its path with the same octal escapes
deploydeploys only whatskill-graft.lockrecords. It refuses, writing nothing and namingskill-graft resolve, a
git Source's Skill or a patched one whose hash differs from the lock's (the config or a Patch edited by
hand, an edited cache export), any change to the Hull's shape, and a missing lock or one of anotherversion. The
refusal names what differs: each Skill, edge (own/tdd → own/notes (prose) added), suggestion, deployed name or
Source. A path Source's Skill without Patches whose text changed is still deployed, and named:
<skill> changed in its path source since skill-graft.lock: deployed as is. After editingskill-graft.tomlor a
Patch by hand, runskill-graft resolve, thendeployresolve,updateandadd,remove,rename,bind,unbindnow write the Vendored copies in
.apm/skills/too, together with the lock: on any failure both keep their bytes. Each also writes
.apm/skill-graft/.gitattributeswhen the Package has none, so git keeps the committed copies' and Patches'
line endings.update --dry-runstill writes nothing- No command overwrites an edited Vendored copy: each that vendors first checks
.apm/skills/against the lock, and
refuses, writing nothing, a copy whose hash differs (a stray file such as.DS_Storeincluded), a folder the lock
lists no Skill under, and a file directly in.apm/skills/, saying to move the edit into a Patch or delete
the folder. A missing copy is vendored again. Without a lock it can read, the copies cannot be checked: the error says
to delete.apm/skills/, then runskill-graft resolve graft-checkrunsskill-graft resolveafter adding a Patch fix, and again after undoing one- When APM fails after
init --projectregistered the Package,initsays to runskill-graft resolve, then
skill-graft deploy - skill-graft requires APM 0.35.0: install it with
uv tool install apm-cli==0.35.0. A path Source inside or
containing~/agent-data/workflows, or in project scopeagent-data/workflowsunder the project root, where APM
0.35.0'sgrok-bottarget deploys skills, now gets the warning that it overlaps a folder where APM deploys skills - Upgrading: a Package whose lock a
resolveor an edit wrote after its lastbuildholds copies older than its
lock, which the first command refuses as edited. Check them, then delete.apm/skills/and runskill-graft resolve.
A clone whose copies git already converted, as withcore.autocrlf, is refused too: once.gitattributesis
committed, delete them and check them out again (git checkout -- .apm/skill-graft/.apm/skills), or delete them and
runresolve. A skill-graft upgrade that renders the lock or rewrites differently makesdeployrefuse until
skill-graft resolve
Fixed
- The error line of a failed patcher no longer runs into the first line of its output when its error output ends without
a line break - Finding a skill's references no longer slows to minutes on a Markdown file with a long line holding many code spans
each followed by/, such as`x`/arepeated: a long line takes no longer than the same text split into many - With
PYTHONUNBUFFEREDset,help,--helpand a command's--helpinto a pipe whose reader has closed it report
the broken pipe and exit 1, as without it, instead of exiting 0 /graft-checknames the Package's folder from the throwaway deploy's first line, so its closing advice compares
the Vendored copies there, not a project folder above it, and no longer advisesskill-graft deployfor nothing/graft-checkwrites a Patch's diff after changing into its folder in a call of its own, which Claude Code no
longer blocks as it blocked both in one command- A hardened
/graft-checkstarts its probe, readers and patcher through a script shipped with the skill, so Claude
Code no longer blocks the command for changing folder and writing its output to files in one go. The script sets their
confining flags itself and runs only in a temporary folder, so a permission rule allowing it allows no otherclaude
run than a reader of the folders it is given or a patcher in a temporary folder
Security
- The release tags are signed from this release on, with the SSH key the README names, and GitHub shows them as
Verified. The README says how to check a tag and install the commit it names
skill-graft 0.3.0
Added
- The supported platforms are stated: Linux and macOS, and Windows through WSL. The README says so, and the package
declares them as classifiers - The git cache prunes itself: every command but
initandupdate --dry-runremoves, as it ends, each export no
command has used for 30 days, and each leftover of a killed export or clone. No command removes a repository, and
every Ref read stays in its repository, even after it vanished upstream. The README's new "Cache" section says
where the cache is and when deleting it by hand loses a Ref - A new clone of a git Source holds commits and trees only; an export fetches the files it writes, in one request,
and a Source withsubdirectoryexports only that folder. A remote without partial clones gives a whole clone, as
before; one refusing files asked by name has the repository fetched whole, once (git 2.36 or later). With git 2.44 or
later, looking up what the cache holds never contacts the remote
Changed
- The first export of a commit and
subdirectoryneeds the remote, even when the commit is in the cache; an export
removed as unused is exported again without it update's Hull diff fetches the files of a cached currentrefit lacks, so a lock written on another machine
still gets its diff; when that fails, it says there is no previous Hull to compare, naming the fetchupdate --dry-runon a remote refusing files asked by name fails, naming the Source and saying to runupdate
without--dry-run
Fixed
- The error for an object missing from the cache no longer says to delete the repository, the one thing that can lose a
Ref vanished upstream - On native Windows, every command,
help,--helpand--versiontoo, prints
skill-graft: native Windows is unsupported: run skill-graft in WSLand exits 1, in place of a traceback ending in
No module named 'fcntl' - A Patch written with
diff -ruNon macOS that deletes a file deletes it, as on Linux, with any folder that leaves
empty; it left the file behind, empty, and one deletingSKILL.mdwas not refused. Any file a plaindiffPatch
leaves empty is deleted; agit diffPatch applies as git applies it
skill-graft 0.2.3
Added
tree --format mermaidgroups Skills by Source and draws Entries amber. A Source from GitHub or
GitLab, and each of its Skills, link to the upstream tree at its Ref; the link is rebuilt from the
repository's host and path, so nothing else from its URL, such as a token, reaches the diagram. A shared--link
names those repositories
Changed
- Every command prints its warnings last: after its output, its notices and APM's, with only its error line after them,
and also when it fails.build's no longer scroll away above APM's output, norupdate's above its Hull diff,
and warnings aboutskill-graft.tomlno longer come first update --dry-runwith nothing to apply, no Ref moving and the lock it would write recording what
skill-graft.lockrecords, saysnothing to applyin place of itsapply:line. Moving no Ref, its prose
suggestion line says to runresolve, not to update first--expectnames its value<hull-digest>, after the Hull digest it takes:update --helpshows
--expect hull-digest, and a mismatch saysits hull digest is …renameandbindlook up the Skill they are given in its own Source only: a missing Skill is reported
before another Source's missing Ref or failed fetch, and arenameorbindthat changes nothing no longer
needs any other Source. Abindtarget that is no Skill ID is refused asbind: '<target>' is not …, without
naming the key
Fixed
- When writing
skill-graft.lockfails afterupdatemoved a Ref, or Ctrl-C interrupts it,skill-graft.tomlis
restored; so is it when Ctrl-C interrupts an edit's lock write. A restore that fails too is named, and the original
error is the one reported - Without APM,
initcreates nothing and says to install APM and runinitagain, instead of leaving a Package
that a secondinitrefuses with no way forward - Refusing an existing Package that APM does not list,
initgives theapm installcommand that registers it - The
apm installcommandsinitsuggests, and the one named when APM fails, are shell-quoted - Into a pipe its reader has already closed, such as
skill-graft tree | head -1, a command prints its warnings, then
its error line, and exits 1, with noException ignored … BrokenPipeErrorfrom Python and no exit 120; so dohelp,
--versionand--help. A command started with standard output closed no longer fails with a traceback
skill-graft 0.2.2
Added
update --diff <file>writes the Deployed diff: each changed or added Skill's text as deployed, Patches
and Renames applied, as a unified diff to read in an editor, never printed. Every control, format and separator
character, and every Hidden character, is escaped, so no line hides or passes for another. It writes only a new
*.difffile or one it wrote before, never inside an.apmfolder or a Skill's folderupdate --expect <digest>updates only to the Hull with that Hull digest: a dry run with--diffends with
theapply:line to run, and a push, a path Source's edit, a Patch or a Rename since then makes it
refuse, writing nothing
Changed
updatenames each changed file under each changed or added Skill, with its sizes and executable bit, never
its text; a dry run without--diffends with thereview:command to runupdatenames each Skill edited sinceskill-graft.lockwas written, whose earlier text no diff can show, and
then prints noapply:line- An old Ref missing from the cache is named as such, with its likely causes, instead of "not cached"
Fixed
- With several git Sources without a
ref, every command names them all and suggests oneskill-graft update
that works:update asuggestsskill-graft update a b, keeping--dry-runand--diff(and turning--expect
into--dry-run), instead of hints sending the user fromatoband back
skill-graft 0.2.1
Added
skill-graft --versionprints the installed version; installed from git, also the commit, and the branch or tag
if one was named:skill-graft 0.2.0 (main @ e294a0e); without a readable installation,skill-graft unknown- An OWASP Top 10 section in
docs/security.md: how skill-graft meets each risk of the LLM and agentic
lists, what is out of scope (judging whether a Skill is malicious), and future work - A
[check]table inskill-graft.tomlfor the limits of a hardened/graft-check:reader-turns(30),
reader-budget-usd(1.0),patcher-turns(10),patcher-budget-usd(0.5) andreaders-at-once(4), each
written in plain decimal digits; any other form fails like any bad key
Changed
/graft-checkis hardened in any harness with a working Claude Code, not only in Claude Code: before reading any
Skill text it runs one probe reader, and outside Claude Code it first asks to use your Claude Code account. A
failed probe makes the Check unhardened, naming the probe's error and, when not logged in, advising
claude auth login- In a hardened
/graft-check, each reader and the patcher stop at a cap on their turns and their spend, and at most
readers-at-oncereaders run at once, in batches. A reader that stops leaves its Skill with no report; a
patcher that stops ends that fix - The patcher's reply no longer reaches the main agent. A failed patcher shows one line of its error, in printable
ASCII, at most 200 characters - The summary of a proposed Patch gives counts only (
files: 1, lines: +2 -1, binary: 0), never the patcher's
file names. A binary file, or a file name holding a control character, ends that fix
skill-graft 0.2.0
Added
- On Claude Code,
/graft-checkreads Skill text only through headless readers,claude -p --restrictedlimited
toRead,GrepandGlob, without MCP servers and confined to that Skill's folders; its own session sees
only CLI output and the readers' reports. A Patch it proposes is written by a patcher confined to a temporary
folder, and you read its diff before it enters the Package. Elsewhere, or with aclaudelacking those flags,
it says the Check is unhardened and why, and reads only on your yes - A warning for invisible characters common as copy-paste debris (zero-width, soft hyphen, bidi marks), once per file
with its first line and count docs/security.mdand a Security section in the README: what APM and skill-graft block, what the Check
enforces on Claude Code, running it in Claude Code's sandbox, and the residual risks
Changed
- BREAKING: resolution fails when a Hull Skill holds a Hidden character, in its Upstream text, a
file or folder name, or the lines its Patches add, naming each file, line and character class; one run reports
them all.resolve,build(--intotoo),updateand the editing commands write nothing;whyandtreeprint
the Hull, then fail. Such a Hull'sbuildalready failed in APM; nowresolvefails too - BREAKING: a Markdown file of a Hull Skill that is not UTF-8, Upstream or as patched, fails resolution
instead of being skipped with a warning; so does any file of it that cannot be read
Security
- A
SKILL.mdthat a git Source links outside its Skill, or that is not a regular file (a FIFO or device, or a
link to one), is no longer read, so a Source can neither make skill-graft read a local file nor hang it. Such a
Skill's name is unreadable: an Entry or Reference naming its folder fails, naming theSKILL.md, never
what it links to; other Entries never see it
skill-graft 0.1.0
Added
skill-graft initcreates the package,~/.apm/skill-graft/, with a commentedskill-graft.toml, and
registers it with APM.init --projectcreates one in the project instead,.apm/skill-graft/, registered in
the project'sapm.yml; every command then acts on the nearest package above the working folder, and on the
user scope's when there is noneskill-graft buildvendors the hull into the package's.apm/skills/and deploys it through APM into Claude
Code, opencode and the other harnesses APM supports;--no-installstops before APM, and--into <dir>writes
the skills and the lock into an empty folder instead, leaving the package and APM untouched- Sources: local folders (
path) and git repositories (git, with an optionalbranch), each git source read at
the commit itsrefnames and cached outside the package.subdirectoryreads only one folder of a
repository, as if it were its root skill-graft update [source…] [--dry-run]moves each git source'srefto its branch tip, only if the hull
still resolves and every patch still applies there, and prints which skills that adds, removes or changes- The hull: the entries plus every skill they reach through slash references (
/grilling) and Skill tool
references (Skill(grilling), or a sentence telling the reader to call the Skill tool withgrilling), found
in every Markdown file of a skill. A reference resolves in its own source first, else in exactly one other;
one matching no skill is a warning. Prose references to a skill outside the hull become suggestions toadd skill-graft resolvecomputes the hull, writesskill-graft.lockand prints the hull as a tree, followed by
the suggestions[bind], andskill-graft bindandunbind: fix which skill a name from one source resolves to, or make it no
reference at all withfalse[rename]and a source'sprefixset a skill's deployed name, carried into its folder, itsname:and every
reference to it, prose included;skill-graft renamesets or undoes a rename. Deployed names must be unique
across the hull; a name a harness already uses, such ascode-review, is a warning, and one in your own
reservedlist an error[patches]: unified diffs applied to a skill's upstream text before its references are read, so a patch can
change what a skill calls. A patch that fails names each failing hunk, and nothing is writtenskill-graft tree [--format text|mermaid] [--link]andskill-graft why <skill>explain the hull without
writing anything: the whole tree, as text, Mermaid or a mermaid.live link, or the paths from the entries to one
skill and its upstream folderskill-graft addandremovechangeentries, keeping the file's comments, once the result resolvesskill-graft help [command], and--helpon every command- The
graft-checkskill, in this repository'sskills/: run as/graft-check, it has an agent read a built
package for missed, false or misdirected references, wrong rewrites and links vendoring broke, and proposes
add,bind,unbindor a patch for each, applied only once you accept