| name | skill-publish |
| sheetId | 6.10 |
| description | Land a freshly-built SKILL.md into the Zynkr skills pipeline — the closing step of the canonical chain (/skill-sourcer → /skill-triager → /skill-creator → /skill-qa → /skill-publish). Attaches the artifact to the existing pipeline issue, fires the publish-skill workflow to open a PR, and hands off to /skill-triager confirm-ship for closure. Trigger on phrases like 'publish this skill', 'land this skill', 'I finished building, push it', 'register this skill', 'add this built skill', 'check category and dedup before upload', or '/skill-publish'. Use whenever the user has a finished SKILL.md and wants it landed in zynkr-skill-builder — whether it came from /skill-creator (happy path) or from elsewhere (fresh intake fallback). |
| category | engineer |
| project | skill-publish |
| platform | claude |
| status | WIP |
| author | Peter Tu |
| input | A path to the finished SKILL.md (folder or file), a GitHub URL to a SKILL.md, or pasted content. Optionally an existing pipeline issue number — if not given, the skill tries to match by slug + category against the GitHub Project. |
| output | Either (a) continuation mode: existing Project item updated (Build Status=ready-to-ship, Built Skill URL=PR URL, Artifact=skill-md-only), publish-skill.yml dispatched, PR opened, comment posted on the existing issue, or (b) fresh-intake mode: new GitHub issue + Project item created (Intake Source=skill-publish), then same dispatch + PR + comment. |
| synergy | ["skill-sourcer","skill-triager","skill-creator"] |
| disable-model-invocation | true |
| security_audits | {"gen_agent_trust_hub":"pending","socket":"pending","snyk":"pending"} |
Skill Publish
npx skills add https://github.com/peter-tu-zynkr/zynkr-skill-builder --skill skill-publish
The closing step of the Zynkr skill pipeline. /skill-publish lands a finished SKILL.md into zynkr-skill-builder, attaches it to the existing pipeline issue, and hands off the rest to automation + /skill-triager.
Canonical chain (the happy path you'll hit 80% of the time):
/skill-sourcer → /skill-triager → /skill-creator → /skill-qa → /skill-publish
(find idea) (approve) (build content) (QA gate) (land + ship)
By the time /skill-publish runs, a GitHub issue already exists in peter-tu-zynkr/zynkr-skill-idea with Pipeline Status=approved, a slug + category, and an open skill/<slug> branch carrying the stub SKILL.md. /skill-creator produced the real content. /skill-publish just attaches the artifact to that existing pipeline state, fires publish-skill.yml to open the PR, and lets /skill-triager close the loop after merge.
Step 0 — QA gate (mandatory, runs in both modes)
Before anything else, the SKILL.md must clear QA. Invoke /skill-qa <path> on the artifact and read its verdict:
- Recent-PASS short-circuit: if
/zynkr-skills already ran /skill-qa on this same artifact this turn and it PASSed, record "QA: PASS (from front-door)" and proceed — don't re-run.
- ERROR-tier fail → HARD STOP. Do not detect mode, do not dispatch (Step 5's dispatch is the only state-changing step). Print the QA report's ERRORs with their fixes and tell the user to fix and re-run
/skill-publish.
- WARN-only → advise, then ask: "QA passed with N warnings (listed). Publish anyway, or fix first?" Default to publishing with the warnings recorded. WARN never blocks.
Print a one-line verdict at the top of your output: QA: PASS (0 errors, N warnings) or QA: FAIL (M errors) — not publishing.
This is the interactive half of the tollgate; CI (publish-skill.yml / qa.yml) re-runs the same engine as a hard backstop, so a skipped or overridden Step 0 still cannot land an ERROR-tier skill on main.
Step 1 — Locate the artifact and detect mode
Accept input in any of these forms:
- Folder path → expect a
SKILL.md at the root
- Direct
SKILL.md path → read the file
- Pasted SKILL.md content → use as-is
- GitHub URL to a
SKILL.md (raw or blob) → fetch it
If the user didn't provide an input, ask:
"Where's the finished SKILL.md? Drop a folder path, a SKILL.md path, a GitHub link, or paste the content."
Then detect the mode — this changes everything downstream
Ask the user (or auto-detect from context):
"Was this skill opened via /skill-sourcer already? If yes, what's the issue number? If not, I'll do a fresh intake."
Mode A — Continuation (happy path): The user gives an issue number, or you can match the SKILL.md's frontmatter name/description against open issues in peter-tu-zynkr/zynkr-skill-idea with label triage-ready or building. Confirm the match with the user:
"Found issue #N — [Skill Proposal] X — <title> (Pipeline Status: approved, Build Status: context-prep). Attaching this SKILL.md to it. ✓"
Skip the dedup/classify chain entirely — the issue already carries those decisions.
Mode B — Fresh intake (fallback): No prior pipeline item exists. Run the full classify + dedup + propose chain (Steps 2b–4b below) before landing the artifact.
Step 2 — Continuation flow (Mode A — happy path)
The issue already exists; the SKILL.md is the body that fills it. Almost no skill-side work — most of the action is in the workflow.
-
Read the SKILL.md — parse frontmatter (name, description, category, status). Display it to the user as a sanity check, but don't re-classify — the issue's category is authoritative.
-
Verify the category matches — if the SKILL.md's category: differs from what's on the existing issue/Project item, surface the mismatch and ask the user which to trust. Update the issue/Project to match the SKILL.md, or fix the SKILL.md, before continuing.
-
Resolve the target path — skills/<N-cat>/<slug>/SKILL.md from the existing Project item's Build Target Path. (If empty, derive slug from the SKILL.md's name or the issue title.)
-
Fire the publish-skill dispatch (see Step 5 — same payload for both modes).
-
Update the existing Project item — Build Status=ready-to-ship, Artifact=skill-md-only, Built Skill URL=pending PR (will backfill once the workflow runs).
-
Post a comment on the existing issue so the audit trail is one continuous thread:
gh issue comment <num> --repo peter-tu-zynkr/zynkr-skill-idea --body "Skill artifact attached via \`/skill-publish\`. Dispatched \`publish-skill.yml\` — PR will follow shortly."
-
Skip to Step 6 (sync READMEs after PR merges).
Step 2b — Fresh-intake flow (Mode B — fallback)
When no prior pipeline item exists for this SKILL.md. Same shape as /skill-sourcer but without the extractor (the SKILL.md is the extract). Run these steps before reaching Step 5.
2b.1 Parse the SKILL.md as the extract
Read frontmatter and body intro. Build a structured extract:
name: <frontmatter.name>
title: <H1 or humanised name>
description: <frontmatter.description>
input: <frontmatter.input>
output: <frontmatter.output>
who it helps: <inferred from description — ask if unclear>
source URL: <upstream URL or "Peter-authored, no upstream">
prior category: <frontmatter.category or null>
Confirm with the user: "This is what I read from the SKILL.md. Anything to correct before classifying?"
2b.2 Classify (subagent)
Spawn the skill-classifier subagent at ../skill-sourcer/agents/classifier.md. Pass the SKILL.md's category: as the prior — the classifier confirms or overrides.
2b.3 Dedup (subagent)
Spawn the skill-deduplicator subagent at ../skill-sourcer/agents/deduplicator.md.
new → proceed.
partial_overlap → ask the user whether to proceed anyway.
exact_duplicate / near_duplicate → you should be in continuation mode, not fresh intake. Switch back to Mode A — the existing item is what you want to attach to. (If the user really wants a duplicate proposal, fall through to "replace existing" — rare.)
2b.4 Propose (subagent)
Spawn the skill-proposer subagent at ../skill-sourcer/agents/proposer.md. Creates the issue + Project item with:
Intake Source=skill-publish
Artifact=skill-md-only
Build Status=ready-to-ship
- Issue body includes
**Built via**: skill-publish and **Built Skill URL**: pending PR
Ask: "Set Keep=yes to approve now, or leave as ? for later review." On approval, set Pipeline Status=approved, add triage-ready label.
Then fall through to Step 5.
Step 5 — Fire publish-skill.yml dispatch (both modes)
Pre-flight: do not reach this step unless Step 0 returned QA PASS (or WARN-only with explicit user override). The dispatch is the one state-changing action — never fire it for an ERROR-tier skill.
Identical for continuation and fresh intake. The dispatch event is what makes the SKILL.md actually land in the repo.
Pick one of the three payload shapes — skill_md_url, skill_md_b64, or bundle_b64. The workflow rejects payloads with more than one or none.
Decision tree:
- Skill is a single SKILL.md, content already on GitHub (other repo, gist, raw URL) →
skill_md_url.
- Skill is a single SKILL.md, only local →
skill_md_b64.
- Skill has additional files (
references/, scripts/, assets/) — the common case for Peter-authored skills → bundle_b64.
Shape 1 — skill_md_url (single file, remote):
gh api repos/peter-tu-zynkr/zynkr-skill-builder/dispatches \
-X POST \
-f event_type=skill-publish-request \
-F "client_payload[issue_number]=<num>" \
-F "client_payload[issue_repo]=peter-tu-zynkr/zynkr-skill-idea" \
-F "client_payload[slug]=<slug>" \
-F "client_payload[category]=<category-number-or-slug>" \
-F "client_payload[skill_md_url]=<raw or blob URL to the SKILL.md>"
Shape 2 — skill_md_b64 (single file, local):
gh api repos/peter-tu-zynkr/zynkr-skill-builder/dispatches \
-X POST \
-f event_type=skill-publish-request \
-F "client_payload[issue_number]=<num>" \
-F "client_payload[issue_repo]=peter-tu-zynkr/zynkr-skill-idea" \
-F "client_payload[slug]=<slug>" \
-F "client_payload[category]=<category-number-or-slug>" \
-F "client_payload[skill_md_b64]=$(base64 -w0 < /path/to/SKILL.md)"
Shape 3 — bundle_b64 (multi-file, local):
SLUG=<slug>
BUNDLE_B64=$(tar -czf - -C /path/to/parent "$SLUG" | base64 | tr -d '\n')
gh api repos/peter-tu-zynkr/zynkr-skill-builder/dispatches \
-X POST \
-f event_type=skill-publish-request \
-F "client_payload[issue_number]=<num>" \
-F "client_payload[issue_repo]=peter-tu-zynkr/zynkr-skill-idea" \
-F "client_payload[slug]=$SLUG" \
-F "client_payload[category]=<category-number-or-slug>" \
-F "client_payload[bundle_b64]=$BUNDLE_B64"
The workflow extracts the bundle to skills/<N-cat>/<slug>/ — all files inside (SKILL.md, references/, scripts/, assets/) land in the same PR.
Size limit. GitHub caps client_payload at ~64 KB serialized. Gzipped markdown is usually 25-35% of raw, so most multi-file skills fit comfortably (product-flow-design with 7 files = ~40 KB b64). If your bundle is too large, use the pre-push fallback:
- Create branch
skill/<slug> from main locally.
- Add the whole folder, commit, push to that branch.
- Open the PR manually with
gh pr create --base main --head skill/<slug> --title "publish(<slug>): from <repo>#<num>" --body ....
- Comment on the source issue with the PR URL.
- Skip the dispatch entirely — you've already done what it would have.
This fallback should be rare; most skills fit in bundle_b64.
Verify the workflow landed:
gh run list --repo peter-tu-zynkr/zynkr-skill-builder \
--workflow publish-skill.yml --limit 3
The workflow:
- Writes
skills/<N-cat>/<slug>/SKILL.md (idempotent — refuses to overwrite)
- Runs the QA gate (
validate-skill.ts — frontmatter + body + path-leak ERROR checks); blocks the PR on any ERROR
- Creates branch
skill/<slug>, opens a PR titled publish(<slug>): from peter-tu-zynkr/zynkr-skill-idea#<num>
- Comments back on the source issue with the PR URL
Surface the run URL and (once the workflow finishes) the PR URL to the user.
After the PR merges, ingest-skills.yml runs and publishes the skill to the marketplace within ~30 s.
Step 6 — Sync READMEs (after PR merges)
Same logic as /skill-sourcer's Step 6.5, plus a taxonomy-tree rebuild.
A — Local taxonomy tree
The auto-generated section of skills/README.md updates itself when ingest-skills.yml runs in CI. No manual rebuild needed unless you're working off a local branch.
B — SOURCED.md row (optional, for the audit trail)
Append a row in peter-tu-zynkr/zynkr-skill-idea/SOURCED.md with Intake Source: skill-publish so the two intake paths stay distinguishable in the index.
C — Build handoff tracking
Same Pipeline Status / Build Status two-track model as /skill-sourcer. After the PR merges and ingest fires, the artifact is live but the Project still says Build Status=ready-to-ship. /skill-triager Option D confirm-ship is the closer — see Step 7.
Step 7 — Hand off to /skill-triager (the closer)
/skill-publish does not flip the Project to shipped itself. That's /skill-triager's job, and on purpose:
- The PR may take time to merge (review, conflicts).
ingest-skills.yml may not have run yet by the time /skill-publish returns.
- A separate human gate (
/skill-triager confirm-ship) gives one final verification beat before the audit trail closes.
When you're done with /skill-publish, tell the user:
"Published via dispatch. PR opened (URL: <pr_url>). After the PR merges, run /skill-triager and pick Option D — confirm-ship on this issue to close the loop. The triager will run three read-only verifications (gh api contents, skills-index.json, /api/skills) before flipping Pipeline Status=shipped, then offer to install it into your local ~/.claude/skills/ via npx skills add … --skill <slug> — publishing to the marketplace alone does not make the skill invocable in your own Claude Code session."
Completion checklist (mode-dependent):
Continuation mode (happy path):
Fresh intake mode (fallback):
Ask: "Want to publish another skill?"
Where this skill can decouple from the others
The canonical chain is opinionated, but each handoff has a clean break-point you can use:
QA is the one step you cannot decouple from. Even hand-committed skills (the pre-push fallback, a manual PR, a direct commit) must clear the CI QA backstop (qa.yml required check + the ingest-skills.yml pre-step). Skipping Step 0 locally only defers the gate to CI; it does not remove it.
Decouple from /skill-sourcer
- When: Someone hands you a finished SKILL.md without going through sourcer (downloaded from another repo, third-party authored, scraped from a talk).
- How: Use Step 2b (fresh intake) —
/skill-publish runs classify + dedup + propose itself. No /skill-sourcer invocation needed.
- Tradeoff: You lose the source-link traceability that sourcer captures. Manually note the origin in the issue body if it matters.
Decouple from /skill-triager's scaffold step
- When: You used
/skill-creator standalone (not on a skill/<slug> branch opened by pickup-approved-issue.yml). The stub SKILL.md from the scaffolder is irrelevant or absent.
- How: Run
/skill-publish directly against the existing approved issue. publish-skill.yml opens a new PR with your real SKILL.md — the stub PR (if any) can be closed without merging.
- Tradeoff: Two PRs for the same slug if both flows ran. Clean up the unused scaffold PR.
Decouple from /skill-creator
- When: You hand-authored the SKILL.md, or it came from outside the Anthropic skill-creator workflow entirely.
- How: The provenance doesn't matter to
/skill-publish — it cares about the SKILL.md being well-formed (parseable frontmatter, valid category, unique slug). No creator handshake required.
- Tradeoff: No evals workspace, no benchmark history. Add tests separately if you want quantitative quality data.
Decouple from /skill-triager closure
- When: You want the skill live and don't care about the audit Project flipping to
shipped.
- How: Just merge the PR.
ingest-skills.yml makes it live regardless of triager status. The Project item stays at Build Status=ready-to-ship forever — harmless but messy.
- Tradeoff: Audit trail goes stale. Recommended only for low-stakes / personal skills.
Decouple from publish-skill.yml (the dispatch path)
- When: You're committing the SKILL.md by hand (e.g., a manual PR on a fork, working without
gh auth).
- How: Skip Step 5. Commit
skills/<N-cat>/<slug>/SKILL.md to a branch yourself, open a PR. /skill-publish still does the classify + dedup + Project bookkeeping; you handle the file write.
- Tradeoff: You lose the automated PR + back-comment. Update
Built Skill URL manually once the PR is up.
Error handling
- SKILL.md missing required frontmatter: ask the user to fill it before continuing —
validate-skill.ts will block the workflow anyway.
- QA ERROR-tier fail (Step 0): treat like missing frontmatter — stop and re-prompt with the QA report; CI's
qa.yml would block the PR anyway.
- Artifact path doesn't exist: stop and re-prompt.
- Continuation mode but the issue isn't
approved: flag it — the user may have meant a different issue, or the triager step was skipped. Confirm before attaching.
- Existing target file in the repo (idempotency block):
publish-skill.ts exits 3. The slug is already taken — pick a different slug, or update the existing skill via a normal PR instead of re-dispatching.
- Dispatch returns non-2xx: report the error and don't update the Project. The dispatch is the only state-changing step; everything before it is reversible.
- Workflow's "Comment back on the source issue" step shows a warning (yellow): this is expected when
SKILL_IDEA_PAT isn't set as a repo secret — the default GITHUB_TOKEN is scoped to zynkr-skill-builder only and can't comment cross-repo. The workflow continues past it (continue-on-error: true); the PR is open and the file landed. Post the back-comment yourself via gh issue comment <num> --repo peter-tu-zynkr/zynkr-skill-idea --body "Published... PR: <url>" and surface this to the user. Long-term fix: create a fine-grained PAT with cross-repo issues:write, save it as the SKILL_IDEA_PAT secret on zynkr-skill-builder.
- Bundle extracts but SKILL.md is missing inside: the tarball's top-level directory didn't match the slug. Rebuild with
tar -czf - -C <parent> <slug>/ — the path argument must be the slug folder name, not its contents or an absolute path.