@graftsync/skill 0.0.0-stage → 0.1.0

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/SKILL.md +261 -0
  3. package/package.json +28 -3
  4. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brandon Roberts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/SKILL.md ADDED
@@ -0,0 +1,261 @@
1
+ ---
2
+ name: graft-sync
3
+ description: Keep local modifications ("grafts") to upstream npm packages working when upstream releases a new version. Use when a repo has a `.grafts/` directory and an upstream dependency was bumped, `graft sync` reported `needs-agent`, a graft's anchors miss or its oracle fails, or you are asked to sync, repair, add, or retire a graft.
4
+ ---
5
+
6
+ # Graft sync
7
+
8
+ A **graft** is one local modification to an upstream package, described by
9
+ `.grafts/<id>/graft.md`. The repo's grafts are the contract between the people who wrote
10
+ them and you. Your job when upstream moves is to make each graft hold again — or to say
11
+ clearly that it can't.
12
+
13
+ Full format and tooling contract: the Graft spec in the Graft repository,
14
+ https://github.com/brandonroberts/graft/blob/main/docs/spec.md
15
+
16
+ ## What a graft is made of
17
+
18
+ `graft.md` frontmatter:
19
+
20
+ - `rootstock.package` / `rootstock.verified` — upstream package and the last version the
21
+ oracle passed against.
22
+ - `mechanism` — how it is applied (e.g. `patch`: a pnpm `patchedDependencies` patch file).
23
+ - `variants` — one implementation per upstream version range (`range` + `module` path).
24
+ Ranges are disjoint; exactly one variant applies to any version.
25
+ - `oracle` — the command that proves the invariants hold, run once per `mode`
26
+ (with `GRAFT_MODE` and `GRAFT_UPSTREAM_VERSION` set).
27
+ - `status` — `applied` | `conflicted` | `upstreamed` | `obsolete`.
28
+
29
+ Body sections, all required — read them before touching anything:
30
+
31
+ - **Why** — what the modification enables and why upstream doesn't do it.
32
+ - **Invariants** — observable behaviors that must hold. **This is the contract.**
33
+ - **Where to look** — the upstream area, described by behavior so you can re-find it after
34
+ a refactor. Trust this over file paths.
35
+ - **Anchors** — what each variant matches on and why it was chosen.
36
+ - **History** — what changed upstream each time and how the graft adapted.
37
+
38
+ ## Tools
39
+
40
+ Everything goes through the `graft` CLI (`npx graft …`, `pnpm exec graft …`, or however the
41
+ project runs it). Every command takes `--json` for machine-readable output and `--root` for
42
+ the project root. Exit codes: `0` ok, `1` something needs attention, `2` tooling error.
43
+
44
+ | Step | Command |
45
+ |---|---|
46
+ | Overview | `graft status` |
47
+ | Deterministic first pass | `graft sync [--version X] [--install]` |
48
+ | Read intent + variants (and which variant the installed version selects) | `graft show <id>` |
49
+ | Anchor report | `graft check <id> [--version X]` |
50
+ | What moved upstream | `graft diff <id> [from] [to]` |
51
+ | Apply a variant to pristine upstream, for inspection | `graft preview <id> [--version X] [--out dir]` |
52
+ | Register a new variant | `graft variant add <id> --range R --module ./variants/… --verify-version X --note "…"` |
53
+ | Wire the package manager to the selected variants + reinstall | `graft apply` |
54
+ | Prove it | `graft verify <id>` |
55
+ | Status, verified version, History | `graft mark <id> [--status S] [--verified X] [--history "…"]` |
56
+ | Where a variant fits across published versions | `graft replay <id> [--range R]` |
57
+
58
+ Reports from `sync` land in `.grafts/.report/` (`sync.md`, `sync.json`, and per graft
59
+ `diff.patch` and `applied/`).
60
+
61
+ ## The loop
62
+
63
+ 1. **Run the deterministic first pass first.** `graft sync --version X --install`.
64
+ Never re-derive what already passes. Green grafts are done — `sync` already bumped
65
+ `verified` and wrote History. Only grafts marked `needs-agent` are yours.
66
+ 2. **Gather context for each `needs-agent` graft**, in this order:
67
+ `graft show <id>` → the reasons in the sync report → `graft check <id> --version <target>` →
68
+ `graft diff <id>` (from `verified` to the target) → the failing oracle output, if the oracle ran.
69
+ 3. **Locate the new equivalent code by behavior.** Use *Where to look* and *Anchors*, then
70
+ the scoped diff. If a target file was removed or renamed, search the new version's
71
+ published files for the behavior (the pristine copy is under `.grafts/.cache/upstream/`). Upstream
72
+ renames and moves are normal; the behavior is what you are re-finding.
73
+ 4. **Write a new variant for the new range** with your own editing tools, inside the graft's
74
+ directory (e.g. `variants/v5.patch`). See *Mechanisms* below.
75
+ 5. **Iterate until the patch applies cleanly** to the pristine target, which `sync`/`check`
76
+ cached under `.grafts/.cache/upstream/<pkg>@<target>/`. For example, run
77
+ `git apply --check <variant>` inside that directory. Registration (next step) is the final
78
+ anchor check.
79
+ 6. **Register it:** `graft variant add <id> --range '>=<target>' --module ./variants/<file>
80
+ --verify-version <target> --note "<what moved upstream and how the variant differs>"`. It
81
+ refuses (and rolls back) if the anchors don't match at the target. Older open-ended ranges
82
+ are narrowed automatically; existing variant files are never touched. Then
83
+ `graft check <id> --version <target>` should be clean.
84
+ 7. **Prove it:** re-run `graft sync --version <target> --install`. With anchors now matching,
85
+ it wires the package manager to the new variant, installs the target, and runs the oracle
86
+ in every mode; on green it sets `verified` and status `applied`. (Or install yourself, run
87
+ `graft verify <id>`, then `graft mark <id> --status applied --verified <target>`.)
88
+ 8. **Finish:** open a PR (see below).
89
+
90
+ ## Adding a new graft
91
+
92
+ When asked to capture a modification as a graft (new, or migrating an existing patch):
93
+
94
+ 1. **Scaffold:** `graft init --agent` (once; it also installs this skill), then `graft add <id> --package <pkg> --target <file>`.
95
+ This creates `graft.md`, an empty variant stub (which fails loudly until written), and an
96
+ oracle stub that exits 1.
97
+ 2. **Write the intent first,** with the person who owns the modification if you can: *Why*,
98
+ the *Invariants* (observable behaviors, one per bullet), *Where to look* (by behavior), and
99
+ *Anchors* (what the variant matches on and why).
100
+ 3. **Write the variant.** For `patch`: run `pnpm patch <pkg>@<installed>`, make the change in the
101
+ printed directory, then run `pnpm patch-commit <dir> --patches-dir <tmp>`. Move the generated
102
+ file onto the scaffolded `variants/v<major>.patch`. `pnpm patch-commit` may also write a
103
+ version-pinned entry to `package.json`; the next step replaces it.
104
+ 4. **Apply it:** `graft apply`. This points the package manager at the
105
+ variant, reinstalls, and re-checks anchors.
106
+ 5. **Write the oracle** so it fails before the modification and passes after. Check that it
107
+ fails without the graft. An oracle that can't fail proves nothing. Test the Invariants,
108
+ plus at least one pre-existing behavior the graft must not break.
109
+ 6. **Prove it:** `graft verify <id>`, then `graft sync` (green).
110
+ 7. **Find the variant's real range:** `graft replay <id>` checks every published version.
111
+ Record what you learn in *History*.
112
+
113
+ ## Rules
114
+
115
+ - **Never edit an existing variant.** Older version ranges keep their variants and tests.
116
+ Write a new file for the new range. `graft variant add` refuses to re-register a file.
117
+ - **The Invariants are the contract and the oracle is the only proof.** Anchors matching or a
118
+ clean apply is not success; a passing oracle is.
119
+ - **Never weaken the oracle** to get green: don't skip, loosen, or delete checks, and don't
120
+ change Invariants to match what you built. If an invariant is genuinely obsolete, say so
121
+ and ask a human.
122
+ - **Never set `verified`** to a version the oracle has not passed against.
123
+ - **Fail loudly.** A graft that silently applies nothing is the worst outcome. Anchors must
124
+ match exactly once; don't add fuzz, catch-alls, or "skip if not found" logic.
125
+ - **Anchor by behavior.** Prefer stable exported names, public API lists, and user-facing
126
+ strings over incidental code shape. Never anchor on formatting.
127
+ - Keep each change to what the failure needs. Don't refactor other grafts or upstream code.
128
+
129
+ ## Version ranges
130
+
131
+ - Start the new variant at the target version. For a prerelease target, use the `-0` floor so
132
+ prereleases select it: `>=5.0.0-0`.
133
+ - Leave it open-ended (`>=5.0.0-0`) unless you know where it stops applying.
134
+ - Older open-ended ranges get capped below the new floor by `graft variant add`
135
+ (`>=4.0.0` → `>=4.0.0 <5.0.0-0`).
136
+
137
+ ## Mechanisms
138
+
139
+ ### `patch` (any package manager)
140
+
141
+ - A variant is a git-style patch (`diff --git a/<file> b/<file>`, paths relative to the
142
+ package root). Every hunk is an anchor: its context and removed lines must appear exactly
143
+ in the target version (position may shift; no fuzz). Files the patch creates must not
144
+ already exist upstream. The same variant works with every package manager.
145
+ - Make one with the project's package manager: `pnpm patch <pkg>@<version>` then
146
+ `pnpm patch-commit <dir> --patches-dir <tmp>`, `npm patch <pkg>` then `npm patch commit <dir>`
147
+ (npm 12+), or `bun patch <pkg>` then `bun patch --commit node_modules/<pkg>`. Move the generated file into the graft's
148
+ `variants/`. For npm, diff a pristine copy (under `.grafts/.cache/upstream/`) against your
149
+ modified copy, using package-relative paths.
150
+ - Don't hand-edit the package manager's patch configuration. `graft apply`
151
+ wires the selected variant: pnpm `patchedDependencies`, npm 12+ `patchedDependencies` (name-only
152
+ key), bun's version-pinned key, or (npm < 12, yarn and others) Graft's built-in applier,
153
+ `graft apply --installed` in `postinstall`. The
154
+ built-in applier is offline, idempotent, and never edits store-shared files in place.
155
+ With upm (or the `vendor` applier forced in `.grafts/config.json`), Graft builds a patched
156
+ tarball under `.grafts/.vendor/` and points overrides and the root dependency at it. Commit
157
+ that tarball; never edit it by hand. `graft check` rebuilds it and reports a stale one.
158
+ `graft sync --install` rewires it for the new version before installing. At the installed
159
+ version, `graft check` reports any configuration that would apply something else or fail
160
+ silently, and any patched file in `node_modules` that doesn't match pristine upstream + the
161
+ variant.
162
+ - Keep hunk context away from lines that change every release (`"version"` in package.json).
163
+ - If the graft's variants are generated, give it a `generator: { command }` in its
164
+ frontmatter (`{version}` and `{out}` are substituted; e.g.
165
+ `node tools/cjs-to-esm/make-variant.mjs semver {version} {out}`). `graft sync` then
166
+ regenerates and registers a variant itself when the old one misses, and keeps it only if
167
+ the oracle passes. If sync still hands the graft to you, its reasons say what the generator
168
+ did; the rejected output is under `.grafts/.report/<id>/regenerated/`. Fix the generator
169
+ or review that output rather than hand-patching the old variant.
170
+
171
+ ### `fork` (commits on a fork of the upstream repo)
172
+
173
+ - The graft's `fork:` block names the fork (`repo`), the upstream source repo (`upstream`), the
174
+ release tag pattern (`tag`, e.g. `v{version}`), and optionally `dir` and `build`.
175
+ - A variant is a JSON file: `{ "ref": "<fork branch, tag or commit>", "base": "<upstream
176
+ version>" }`. The change is the fork's diff from `tag(base)` to `ref`. Every hunk is an
177
+ anchor, located in the upstream source at the target version's tag (fuzz 0).
178
+ - Graft installs a version by rebasing that diff onto the target tag, running `build`, and
179
+ `npm pack`. The tarball goes in `.grafts/.vendor/`, installed through overrides. Don't edit
180
+ the tarball or the overrides by hand; `graft apply` / `graft sync --install` write them.
181
+ - When `sync` says the change no longer rebases onto a new tag:
182
+ 1. Work in the fork repository, not in `node_modules`. Create a new branch from the new
183
+ upstream tag, e.g. `git switch -c graft/<id>-<target> <tag>`. Re-apply the change by
184
+ behavior, using *Where to look*, and commit.
185
+ 2. Push that branch to the fork, if you're allowed to. Otherwise tell the user it needs
186
+ pushing; Graft reads the fork from `fork.repo`.
187
+ 3. Write a new variant file, e.g. `variants/v<target>.json`, as
188
+ `{ "ref": "graft/<id>-<target>", "base": "<target>" }`. Register it with
189
+ `graft variant add <id> --range '>=<target>' --module ./variants/v<target>.json
190
+ --verify-version <target>`.
191
+ 4. Prove it with `graft sync --version <target> --install`.
192
+ - **Repo mode** (`fork.install: none`): the fork is the product (e.g. a translated docs site),
193
+ nothing is installed, and the oracle runs in the rebased checkout. The diff in
194
+ `.grafts/.report/<id>/diff.patch` is what upstream changed since the fork's base in the files
195
+ the fork touches. A clean rebase is not enough: upstream text that lands next to the change
196
+ (e.g. a new English paragraph in a translated page) only fails the oracle, so read its
197
+ output. Same fix steps as above, with `graft sync --version <target>` (no `--install`). A
198
+ green sync leaves the result as branch `graft/<id>/<target>` in `.grafts/.cache/fork/<id>`;
199
+ fetch it from there. Never push it unless the user asks.
200
+ - A target can be an upstream commit between releases: `--version 22.3.0-next.3+<sha>` (the
201
+ version part is matched against variant ranges; use upstream's version at that commit).
202
+ - **In place** (variant `{ "ref": "@worktree", "base": "@verified" }`): never write a new
203
+ variant. A green sync puts the new `verified` only in the result branch; adopt it with
204
+ `git fetch <repo> <ref> && git read-tree -u --reset FETCH_HEAD`. On the `-agent` branch,
205
+ graft.md already says the target version (`status: conflicted`): resolve, then
206
+ `graft sync --version <target>` verifies the working tree as it is.
207
+ - **Start from the prepared branch.** On `needs-agent`, the report's "Start from" line names
208
+ `graft/<id>/<target>-agent` in the cache clone: git's merge of the fork's change onto the
209
+ target, with conflict markers left only in the files it lists. Fetch it into the fork, resolve
210
+ those files by behavior (keep upstream's new content, re-apply the change to it, e.g.
211
+ translate the new paragraph), remove every marker, commit, and register the new variant
212
+ (`base` = the target). Don't redo the files that merged cleanly.
213
+
214
+ ## When to stop: `conflicted`
215
+
216
+ Stop and mark the graft `conflicted` (`graft mark <id> --status conflicted --history "…"`) when:
217
+
218
+ - the behavior the graft modifies no longer exists upstream, or upstream changed it in a way
219
+ that makes an invariant impossible or contradictory;
220
+ - you cannot make the oracle pass in every mode without weakening it;
221
+ - upstream now does what the graft did — say so; a human decides whether to mark it
222
+ `upstreamed`. Graft detects the common case for you: when the variant no longer applies
223
+ but upstream already contains its change (for patches, the reverse patch applies cleanly),
224
+ sync reports the graft as **likely upstreamed** (`likelyUpstreamed: true`, and
225
+ `upstreamed.evidence` from `graft check --json`). Then don't write a new variant:
226
+ confirm the invariants hold on pristine upstream, and recommend (or, if asked, apply with
227
+ `graft mark <id> --status upstreamed --history "…"`) the `upstreamed` status with a History entry citing the version.
228
+
229
+ Name the failing invariant, what moved upstream, and what you tried. Open a **draft** PR with
230
+ the same information. Don't leave a half-working variant registered: if you recorded one that
231
+ doesn't pass, say so explicitly in History and the PR.
232
+
233
+ ## Running inside the Graft GitHub Action
234
+
235
+ When the action invokes you, `GRAFT_PROMPT_FILE` holds your task, and the environment points
236
+ at everything else: `GRAFT_SKILL_PATH` (this file), `GRAFT_COMMAND` (how to run the CLI),
237
+ `GRAFT_REPORT_MD` / `GRAFT_REPORT_JSON` (the sync report), `GRAFT_PACKAGE`,
238
+ `GRAFT_TARGET_VERSION`, and `GRAFT_BRANCH` (already checked out).
239
+
240
+ - Do **not** commit, push, or open a pull request. The action re-runs `graft sync` on your
241
+ work and opens the PR itself: a normal PR only if the oracle passes, otherwise a draft.
242
+ - Write the PR description (format below) to `GRAFT_PR_BODY_FILE`.
243
+
244
+ ## PR description
245
+
246
+ ```markdown
247
+ ## Graft sync: <package> <verified> → <target>
248
+
249
+ | Graft | Outcome |
250
+ |---|---|
251
+ | <id> | ✅ deterministic first pass / 🛠️ new variant / ❌ conflicted |
252
+
253
+ ### <id>
254
+ **What changed upstream:** <the behavior-level change, citing the scoped diff>
255
+ **Anchors that moved:** <anchor names, and where they went>
256
+ **New variant:** `variants/<file>` for `<range>` — <how it differs from the previous variant>
257
+ **Proof:** oracle passed in <modes> against <package>@<target>
258
+ ```
259
+
260
+ For a conflicted graft, replace *New variant* and *Proof* with **Failing invariant**,
261
+ **What was tried**, and **What a human needs to decide**.
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@graftsync/skill",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Agent skill (SKILL.md) teaching coding agents the graft sync loop.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/brandonroberts/graft.git",
9
+ "directory": "packages/skill"
10
+ },
11
+ "homepage": "https://github.com/brandonroberts/graft#readme",
12
+ "bugs": "https://github.com/brandonroberts/graft/issues",
13
+ "keywords": [
14
+ "graft",
15
+ "skill",
16
+ "agent",
17
+ "claude",
18
+ "patch",
19
+ "npm"
20
+ ],
21
+ "files": [
22
+ "SKILL.md"
23
+ ],
24
+ "exports": {
25
+ "./SKILL.md": "./SKILL.md",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
6
31
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.