edit-mirrored-skill ยท diff
git:20261009.c0b0faf to git:20261009.8abc51e
1 added, 1 removed. Audit A to A.
---
name: edit-mirrored-skill
description: Edit, sync or start mirroring a `gke-*` skill in `agents/platform/skills/` that is copied from google/skills. Use when changing one of those skills, updating it to upstream, or resolving a skill sync conflict.
---
# Task
Change a mirrored skill so the change survives upstream syncs and `make skills-check` passes.
This skill is the procedure; [`docs/designs/upstream-skill-overlays.md`](../../../docs/designs/upstream-skill-overlays.md)
is the design and its rationale. `scripts/skill_overlay.py` is the tool behind the
`make skills-*` targets.
A skill is mirrored when `agents/platform/skill-overlays/<skill>/upstream.lock` exists. It has
three layers:
| Layer | Path | Edit by hand |
| ------------- | ----------------------------------------- | ------------------------------------------- |
| Upstream copy | `third_party/google-skills/<skill>/` | never; only the tool writes it and the lock |
| Overlay | `agents/platform/skill-overlays/<skill>/` | patch headers, `append.md`, deleting a file |
| Shipped skill | `agents/platform/skills/<skill>/` | yes, then record it with `skills-refresh` |
The shipped skill is the copy with the patches applied in filename order, then `append.md`. It
is committed, and it is what the agent reads. `skills-refresh` writes patch bodies and
`append.md` from your edits; `skills-sync` and `skills-continue` rewrite the copy, lock, patches
and shipped skill; `skills-generate` rebuilds the shipped skill.
A `gke-*` skill without a lock is still overwritten by `scripts/sync-upstream-skills.py`; follow
`AGENTS.md`, "Skills Guidelines", for it instead.
# Rules
- Never edit `third_party/google-skills/` or `upstream.lock` by hand. CI compares both with
`google/skills`. If a sync reports that upstream renamed or removed the skill, stop and ask a
maintainer.
- Keep one patch per reason. Fold a follow-up edit into the patch with the same reason.
- Put an upstream sync in its own pull request, with no other edit.
- Never delete a patch to get past a conflict. Drop one only when upstream's new text covers its
`Why:`, and say so in the pull request.
- Keep `MSG=` to plain words: the Makefile passes it through the shell, so backticks and double
quotes break. Edit the `Subject:` line in the patch afterwards if it needs code formatting.
- Run `make skills-check` before committing; commit the shipped skill and its overlay together.
- Run `make docs-generate` when a skill is added or a frontmatter `description` changes, and
commit the regenerated skill catalogue. Run `make docs-check` before pushing.
- Expect the `Docker Build` check (the platform image build), not `make skills-check`, to run every
shell block of the shipped skills through `deploy/docker/check_skill_commands.py`. If an edit or sync changes a block listed in its
`KNOWN_FINDINGS`, update the entry in the same pull request; rewrite a command Tirith refuses in
a patch.
- Follow the eval loop ([`.agents/rules/eval_driven_development.md`](../../rules/eval_driven_development.md))
for any change to what the agent reads: adding, changing or removing a patch or `append.md`, or a
sync. A change that leaves every shipped skill byte-identical states that exemption in one line
under **Live validation**.
- Leave stopping mirroring (deleting a skill's copy, lock and overlay) to a maintainer.
# Workflow
## Make a change
1. Edit files under `agents/platform/skills/<skill>/`.
2. If the edit serves the same reason as an existing patch, run
`make skills-refresh SKILL=<skill> PATCH=<nnnn>`. If it succeeds, stop here. If it fails with
`conflicts with a later patch`, a later patch depends on those lines: go to step 3, keep the
new patch even if refresh warns, and skip step 4.
3. Otherwise run `make skills-refresh SKILL=<skill> MSG="<subject>"`. It writes the next
`NNNN-<slug>.patch`.
4. If refresh warns that the edit changes lines an earlier patch introduced, delete the new patch
and go to step 2 with that patch's number.
5. Fill in the new patch's header; refresh writes `TODO` in the first three and `none` in
`Upstream-Issue:`:
- `Why:`: why this repository needs the change. Whoever resolves a future conflict reads only
this line, so state the intent, not the edit. Name a related earlier patch if there is one.
- `Local-Issue:`: the issue that asked for it, not the pull request.
- `Retire-When:`: the upstream change that makes the patch unnecessary, or `never; <reason>`.
- `Upstream-Issue:`: the `google/skills` issue filed for a general fix, or
`none (specific to this repository)`.
## Add or edit the appended section
- Add one by ending the shipped `SKILL.md` with a section whose first line starts with
`<!-- kube-agents: local addition`, then run `make skills-refresh SKILL=<skill>`; refresh writes
it to `append.md`.
- Edit an existing one in the shipped skill and refresh the same way. Keep the marker line.
## Remove a change
1. Delete the patch, or edit `append.md`.
2. Run `make skills-generate SKILL=<skill>`.
3. If it reports that a later patch no longer applies, that patch builds on the one you deleted,
and refresh cannot fix it. Delete that patch too and run `make skills-generate` again. If its
`Why:` still holds, redo its edit in the shipped skill, run
`make skills-refresh SKILL=<skill> MSG="<subject>"`, and copy the old patch's header into the
new one.
## Sync to upstream
Sync a skill only when you are about to change it and upstream changed the same text, when an
eval or a report shows the skill misleading the agent, or when upstream fixes a correctness or
security bug in it. Being behind upstream is not a reason: each sync needs the eval loop. To change
a skill otherwise, patch it at its current pin ("Make a change").
1. Run `make skills-status` to see which mirrored skills upstream has moved past.
2. Run `make skills-sync SKILL=<skill>`, or add `REF=<commit>` for a commit on upstream's `main`.
3. If the output starts with `CONFLICT:`, for each stopped patch:
- Open the listed files under `.skill-sync/<skill>/` and read the patch's `Why:`.
- Merge so both upstream's change and the patch's intent hold. Keep text upstream added beside
the patch's lines.
- To drop the patch because upstream now covers its `Why:`, resolve to upstream's text.
- Remove every conflict marker, then run `make skills-continue SKILL=<skill>`.
4. Any other failure is an error, not a conflict: read the message.
5. Copy the sync's final report into the pull request: retired patches with their `Why:`, dropped
patches and the reason, any `append.md` note, and the patch count, share of lines changed and
conflicts resolved. Question a retired patch whose `Retire-When:` says never.
6. To abandon a sync, delete `.skill-sync/<skill>/`. Nothing outside it has changed.
## Start mirroring
- Adopt an upstream `gke-*` skill this repository does not ship with
`make skills-sync SKILL=<skill>`, then add it to `SKILL_GROUPS` in `scripts/generate_docs.py`
and run `make docs-generate`.
- Move a skill this repository ships without a lock onto the overlay:
1. Choose `REF`. Nothing records the commit the skill was copied from: take the last commit on
upstream's `main` before the newest `sync-upstream-skills.py` commit in
`git log -- agents/platform/skills/<skill>`.
2. Run `make skills-import SKILL=<skill> REF=<commit>`.
3. Diff `third_party/google-skills/<skill>/` against the shipped skill. Every difference must
- trace to a `SKILL_SUBSTITUTIONS` or `SKILL_FOOTERS` entry in
+ trace to a `SKILL_SUBSTITUTIONS`, `SKILL_FILE_SUBSTITUTIONS` or `SKILL_FOOTERS` entry in
`scripts/sync-upstream-skills.py`, or to a known local edit. If any other hunk remains, `REF`
is wrong: delete the copy and lock and import again with another commit.
4. Run `make skills-generate SKILL=<skill>`. This resets the shipped skill to the copy; the
local text stays in `HEAD`.
5. Each refresh writes one patch. For each reason, re-apply only that reason's hunks from
`git diff HEAD -- agents/platform/skills/<skill>/` and run
`make skills-refresh SKILL=<skill> MSG="<reason>"`. Restore a footer as the appended section.
Repeat until that diff is empty, so the shipped skill is byte-identical again.
6. Fill in each patch's header as in "Make a change", step 5; copy each registry entry's comment
into its patch's `Why:`.
7. Delete the skill's registry entries, the snippet constants only they use, and the tests in
`scripts/test_sync_upstream_skills.py` that assert them. That script rejects an entry for a
locked skill.
## Fix a failing check
| `make skills-check` reports | Cause | Fix |
| ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------- |
| the committed skill differs from copy + overlay | the skill was edited and no patch records it | `make skills-refresh SKILL=<skill>` |
| the committed skill differs from copy + overlay | a patch or `append.md` was edited | `make skills-generate SKILL=<skill>` |
| the copy does not match its lock | the upstream copy was edited by hand | revert the copy; change it only through a sync |
| a patch no longer applies | a patch it builds on was deleted or edited | "Remove a change", step 3 |