Migrate agent skills into the Runner repository - #106
Merged
Conversation
Research independent tasks with parallel read-only subagents, compile the findings into decision-closed handoff prompts, and fire them off as parallel agent sessions in fresh git worktrees.
Four skills, all using only the portable `name` + `description` frontmatter keys that OpenCode recognises. - ascii-diagrams: conventions for three diagram types (annotated file tree with NEW/MOD status column, data flow with inline failure-mode annotations, UI mockup with change markers). Plus EXAMPLES.md with three worked examples. - deep-research-subagents: escalating ONE large question to parallel read-only subagents, and synthesising the reports. - background-subagent: firing the current prompt at a background subagent and returning immediately. - docs-and-diagram-tooling: which visual medium to pick, and the render and preview tooling actually installed on this machine. The subagent skills are written against verified behaviour of opencode 1.18.19: the task tool's parameter set, the enforced read-only permission list on the `explore` subagent, the default `subagent_depth` of 1, and the OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS gate on `background: true`. Every diagram is character-count aligned and under 100 columns.
Adds a standalone site at the repo root that renders every skills/*/SKILL.md automatically — there is no hand-maintained list anywhere, so a new skill directory is the only step needed to get a card on the site. The app lives at the root precisely so the glob can reach skills/ without moving it: the vercel-labs/skills CLI treats that directory as a priority search path. - src/lib/frontmatter.ts is based on the parser on leoncheng.dev with four bugs fixed: folded/literal block scalars (a `description: >-` used to become the literal string ">-"), values that merely start and end with a quote, CRLF documents (previously read as having no frontmatter at all), and the spec's nested `metadata:` map. - Install commands are templated from one place; the codeload tarball's `<repo>-<branch>` root is derived from DEFAULT_BRANCH rather than hardcoded, because renaming the default branch would otherwise break the curl command silently. - Design mirrors leoncheng.dev: cream paper with the dot grid, navy ink, hard offset shadows, and the terminal-window chrome from its guide cards. - Deploys via the current Pages flow (upload-pages-artifact + deploy-pages), serving ./docs at https://leoncheng.dev/agent-skills/. 66 unit tests cover the parser, skill derivation, and the install templates.
The smallest useful skill in the repo, built as a controlled experiment against a plugin that does the same job deterministically. Findings recorded in the skill's Reliability section: - The undocumented `experimental.text.complete` plugin hook DOES fire. Verified end-to-end, not inferred from types: the 1.18.19 binary carries the call site in SessionProcessor's `text-end` case, and a probe plugin in an isolated project made a model's one-word reply come back wrapped in emoji it never generated. `output.text` is mutable and persisted. - It fires once per text PART, not per response, so a one-pair-per-response guarantee is not achievable from this hook alone. - A plugin cannot enforce "never mention the ducks" — the model saw them in its own prior turns and volunteered an explanation for them. - The skill approach did NOT decay within 6 tool-heavy turns, which is better than expected. Post-compaction behaviour remains unverified. Lesson: skills are for on-demand procedures; always-on behaviour belongs in a plugin, a hook, or AGENTS.md. The companion plugin is user configuration, not a repo artifact. It lives at ~/.config/opencode/plugins/duck-mode.ts and is off unless DUCK_MODE=1.
pages.yml runs `npm run build` before upload-pages-artifact, so the committed output is never deployed. Keeping it only guarantees a stale artifact and a diff on every skill added.
duck-mode: skill vs plugin, and which one actually works
Site: catalog for agent-skills, deployed to GitHub Pages
feat: ASCII diagramming and subagent delegation skills
New skills: - skill-smith: authoring/reviewing/installing skills. Gates on the no-op test, triages between AGENTS.md / hook / skill, emits portable frontmatter only, and covers the relative-symlink + restart-OpenCode install. - grill-me: rounds-and-frontier interview model, adapted from upstream `grilling`. Replaces the stale one-question-at-a-time version. - red-team-this: adversarial review of the agent's own output. Six fixed objection classes, mandatory file:line evidence, expected-cost ranking, and a proceed / proceed-with-change / stop verdict. - worktree-up: the git worktree setup procedure (the rule itself lives in AGENTS.md). Sibling <repo>.worktrees/<topic>, branch off origin's default, deps, and fixed-port collision checks. Vendored from mattpocock/skills (MIT) @ 0ab1b63: - tdd, diagnosing-bugs, code-review as flat skills - writing-for-agents (+ mechanics) as reference docs under skill-smith Frontmatter on every vendored skill normalised to name + description, names matched to their new flat directories, and cross-references to unvendored skills resolved by inlining (tdd -> codebase-design glossary) or rewriting (code-review -> self-contained gh/glab issue fetching). Attribution, upstream commit SHAs, per-file change notes and the MIT licence text in CREDITS.md, referenced from the README.
These four were not carrying their weight. Three of them (code-review, diagnosing-bugs, tdd) plus skill-smith's two writing-for-agents reference docs were the repository's only vendored third-party content, so nothing vendored remains after this. Nine skills are left. CREDITS.md is trimmed rather than deleted: skills/grill-me/SKILL.md is adapted from mattpocock/skills and still requires attribution. The "What was vendored" table and the "Updating vendored content" section go (both described only files that no longer exist); the "Adapted, not vendored" section and the MIT licence text stay. Surrounding prose in CREDITS.md and README.md no longer claims vendored content exists. Closes leoncheng57/leoncheng57.github.io#249
Replace the single unstyled footer line with the shared design-system footer (leoncheng.dev #198): an optional extra row above a main row of `← leoncheng.dev` / feedback trigger / copyright. SiteFooter, FeedbackTrigger and both CSS modules are ported from leoncheng57/leoncheng57.github.io. The CSS is byte-identical apart from its header comment so future re-syncs stay a clean diff. Two adaptations the port forces: - The home link is a plain `<a href="https://leoncheng.dev">`, not the upstream react-router `<Link to="/">`. This site is deployed at leoncheng.dev/agent-skills/ and owns `/` for its own catalogue, so a relative link would land on the catalogue rather than the personal site. - The MIT/source line moves into the footer's extraRow slot, so nothing is lost. Colours are wired up in tokens.css by pointing the `--sf-*` / `--fb-*` custom properties at this repo's palette. They are var() references, not literals, so the existing dark-mode block flips the footer too. Two mappings are not one-to-one: `--sf-line`/`--fb-line` use --hr-color rather than --text-primary (same value in light, a muted slate instead of near-white for the 3px rule in dark), and `--fb-focus`/`--fb-accent` use --link-color because upstream's --blue-emphasis is not defined here (it is the same #087da8). FeedbackTrigger reads the page path through a new `currentPagePath()` helper instead of `window.location.pathname` inline, so the module is importable without a DOM. The path keeps the /agent-skills/ base prefix, which is what distinguishes this site from leoncheng.dev in the shared form's triage. Tests are adapted to this repo's `node` Vitest environment rather than adding jsdom and a DOM testing library: SiteFooter is asserted through react-dom/server, and feedbackConfig keeps its pure unit tests. Dialog interaction is covered by manual browser checks instead.
WebKit renders input[type="search"] with native searchfield chrome — a light rounded pill with a magnifier glyph that collides with the `filter` label — and nothing in src/ ever reset the appearance. Current WebKit does not paint that pill here, because it drops the native look once an author sets both `border` and `background`, which .filterInput does. That degradation is an undocumented heuristic rather than a guarantee, so the flat rendering was incidental; `appearance: none` makes it explicit. The part that does reproduce on current WebKit is the shadow-tree cancel button, which appears as a grey circle-X as soon as the field is non-empty and is not covered by resetting the input's own appearance. It is reset too, and replaced with a styled clear button: Escape does not clear this field (measured both with and without the native button), so dropping it outright would have cost the only one-click clear. The clear button forces the wrapper from <label> to <div> with an explicit <label for> — a label may not contain interactive content other than its own control. It stays mounted while empty (visibility: hidden, aria-hidden, tabIndex -1) so the container does not widen on the first keystroke. .filter:focus-within is untouched. Refs leoncheng57/leoncheng57.github.io#250
No SKILL.md declared a `metadata:` block, so `skill.tags` was `[]`
everywhere and the tag rows on both the card and the detail page were
dead code. Add `metadata.tags` to all nine skills and turn the card's
chips into buttons that drive the existing filter.
The vocabulary is deliberately seven tags shared across nine skills,
rather than a bespoke tag per skill: a tag carried by exactly one skill
groups nothing and filters nothing.
critique grill-me, red-team-this
diagrams ascii-diagrams, docs-and-diagram-tooling
docs ascii-diagrams, docs-and-diagram-tooling
output-style ascii-diagrams, duck-mode
research deep-research-subagents, parallel-research-handoff
subagents background-subagent, deep-research-subagents,
parallel-research-handoff
worktrees parallel-research-handoff, worktree-up
`metadata` is a free-form string->string map in the Agent Skills spec
and OpenCode ignores it, so portability is unaffected.
Clicking a chip sets the catalogue query, because `filterSkills` already
matches tag text — no second filtering path. The chips are real
`<button>`s so they are keyboard-reachable, and focus moves to the
filter input afterwards so the query that caused the grid to change is
visible (and scrolled into view) rather than implied.
`skills.test.ts` now asserts against the real skill set: 1-3 tags each,
all drawn from the vocabulary, and every vocabulary tag used by at least
two skills. That last assertion is what stops the vocabulary rotting one
bespoke tag at a time.
Closes leoncheng57/leoncheng57.github.io#251
…t-local install method (#9) * feat(install): add a project-local install method All four existing methods install globally into ~/.agents/skills/<skill>. Add a fifth that copies the skill into .agents/skills/<skill> relative to a repository root, so it travels with the codebase and loads only there — .agents/ is the highest-reach project path too (OpenCode, Cursor, Codex, Copilot, Gemini CLI, Amp, Roo, Zed), with .claude/skills/ the Claude Code equivalent. InstallMethod grows a `scope` field so the two groups can be told apart in the UI and asserted in tests, and INSTALL_SCOPES gains the project-scoped .agents/skills row that the new command actually writes to. The tests pin the exact command, the global-first ordering the lede depends on, and the one thing that would silently break it: a `~/` creeping into the project destination, or a relative path creeping into a global one. Closes leoncheng57/leoncheng57.github.io#253 * feat(skill-page): put Instructions above Install, collapse both The four install blocks filled a screen and pushed the skill's actual content below the fold. Instructions now comes first and both sections are native <details>, collapsed on load: keyboard- and screen-reader-operable with no JS, and working without it. The <h2> moves inside the <summary> rather than being replaced by one, so heading navigation still reaches it while collapsed and the existing aria-labelledby="instructions" keeps pointing at a real element; the install disclosure is labelled the same way. Summaries carry the reading time and the method count so a shut section still says something. rehype-slug gives every body heading an id, so a deep link like #hard-rules used to target a node the browser will not scroll to once its <details> is shut — SkillRoute now opens the ancestors first and scrolls on the next frame, on load and on hash change alike. Install gets --surface-raised plus the site's border and offset shadow so it reads as a utility panel rather than document body. That framing exposed a pre-existing overflow (present on main too): .method is a grid item, so its min-width: auto let the longest git clone command size the track and push the card out of its container instead of scrolling inside .command. The disclosure marker is a rotated glyph, not a replacement for the focus ring: :focus-visible still resolves to the global outline on both summaries. Closes leoncheng57/leoncheng57.github.io#252
upload-pages-artifact@v3 bundles upload-artifact@v4, which still targets the deprecated Node 20 action runtime. v5 bundles upload-artifact v7 and keeps the existing path input compatible.\n\nCloses leoncheng57/leoncheng57.github.io#266
Adds an optional `skills/<name>/SIMULATION.md` — one short, content-authored transcript of the skill firing — rendered on the skill page as a collapsed "Simulation Example" section above Full Instructions. The transcript lives beside SKILL.md rather than inside it because SKILL.md is agent context: every line is injected into the model when the skill loads. A worked example there is pure token cost, and models imitate an example's literal content instead of following the procedure. Same reasoning that already produced skills/ascii-diagrams/EXAMPLES.md. Discovery is a second `import.meta.glob`, joined to skills by directory name, so dropping the file in is the only step — no registry, no per-skill React. One generic component walks whatever the parser produced. - Turns are `## user` / `## assistant` / `## tool` / `## note`, optionally labelled after an em dash. The splitter is fence-aware: transcripts are mostly code fences and one will eventually contain a line starting `## user`. - `title`, `trigger` and `caveat` are all required. `trigger` is checked against both the first user turn and the skill description, so renaming a trigger fails the build rather than leaving a stale example behind. `caveat` names what the transcript compresses and renders in the panel. - Malformed files parse to null and are skipped: the glob is eager, so throwing would take the site down over one typo. CI is strict instead. - Skills without one render nothing. A reviewed WITHOUT_SIMULATION list in the tests keeps that a decision rather than drift, mirroring TAG_VOCABULARY. - SkillMarkdown gains a className passthrough so the panel can re-point prose at the terminal palette and strip the nested per-<pre> chrome, keeping one react-markdown pipeline. skills/grill-me ships the reference implementation; README documents the convention and the authoring rules.
Wave 1 of the SIMULATION.md rollout, chosen to prove the convention at both extremes before the remaining skills are written against it. duck-mode is the smallest possible case: two-line replies, no reasoning to show. Its transcript carries the two clauses that are easy to miss — the file written to disk contains no ducks, and the assistant never mentions them — and its note says plainly what a static transcript cannot show, that nothing enforces any of it. worktree-up is the opposite: four consecutive tool turns and almost no prose until the report. It ends on the port collision rather than the happy path, because `lsof` naming a PID and `ps -p` proving whose it is are the two commands the skill exists to make you run. Neither needed a role outside user/assistant/tool/note, so the four-word vocabulary holds. Both are removed from WITHOUT_SIMULATION.
Wave 2 of the SIMULATION.md rollout: red-team-this, ascii-diagrams, background-subagent and deep-research-subagents. Each transcript reaches the guard the skill exists to enforce rather than stopping at the happy path: - red-team-this opens on the side-switch, grounds every ranked objection in a file:line or a command that ran, keeps the one ungrounded objection in its own bucket, and reports two objection classes as empty rather than skipping them silently. - background-subagent splices both branches of the precondition check — flag unset and refused, then set and launched — and ends the turn at the report, because not continuing is the entire value of the mechanism. - deep-research-subagents fans out four concurrent read-only agents with explicit do-not-read boundaries, then synthesises: two reports disagreed and the conflict is resolved by reading the cited lines, not by picking a side. - ascii-diagrams produces one annotated tree with two invisible-in-the-diff traps marked at the line where each bites, then runs the width check. Two things this wave changed beyond content. red-team-this emits a literal `## Speculative` section. A heading inside a turn body mints a rehype-slug id that collides with the instruction body rendered further down the same page, so the transcript uses bold — the no-headings rule holds and the test caught it. Ranked tables and file trees were being wrapped into unreadable fragments by the 74ch prose measure. The panel now caps only paragraphs, lists and quotes, letting tables, trees and command output use the full panel width.
) Wave 3, the two skills whose real runs are far too long to show. Both transcripts therefore stop somewhere deliberate and say so in the caveat. parallel-research-handoff ends at the second user checkpoint rather than at the launch. The skill mandates showing the prompts before firing and asking plan-mode and PR-versus-commit; running straight through them would advertise an autonomy the skill withholds. The handoff prompt shown carries the negative finding and the SHARED-RESOURCE rule, which are the parts agents most often get wrong. build-waves shows exactly one wave boundary out of six: the next wave's read-only research overlapping the current wave's writes, the plan file and todowrite updated together, and the run continuing without asking permission. Its note states plainly what one boundary cannot demonstrate — that the queue survives compaction and a model change, which is the whole point of putting it on disk. Also fixes a real defect in the trigger-drift test. Trigger phrases are matched against hard-wrapped markdown, where a phrase routinely straddles a line break, so "do not stop until it is done" failed to match itself. Whitespace is now flattened before comparing; line wrapping is not semantic.
Completes the SIMULATION.md rollout. Every shipped skill now has one and WITHOUT_SIMULATION is empty. This skill was pencilled in as the likely permanent exception — it is mostly a machine inventory and a decision table, which is not obviously transcript shaped. That turned out to be wrong. Its named failure is precisely a transcript: an agent hand-writes a Mermaid block into a file and declares it done, and nobody discovers it does not parse until a reader opens the page. The example therefore turns on the render call, with the medium chosen from where the reader opens the doc and the house style read from an existing file before anything is written. Also fixes the absence test, which could no longer be reached through real data once coverage was complete. It now layers one synthetic skill over the real catalog, so the "renders nothing" branch stays covered rather than depending on a gap in the catalog staying open.
Adds OpenCode custom commands alongside the skills, on their own /commands route, with six commands chosen to demonstrate the three legitimate shapes and every capability a command has that a skill does not. Why both exist, which is the thing the catalogue has to teach: a skill is model-invoked and its description is resident in the agent's context on every turn — twelve skills currently cost ~5,700 characters, permanently, growing linearly. A command is human-invoked and costs nothing until typed. That makes a large command catalogue free where a large skill catalogue is not, and makes a command the right tool for re-asserting exact instructions at turn 40, after the skill body injected at turn 1 has been compacted away. The six: /worktree-up thin trigger over a skill, $ARGUMENTS /grill-me thin trigger, agent: plan so it cannot start implementing /red-team thin trigger over red-team-this /verify !`npm test` output injected before the model sees the prompt /handoff subtask: true, compiles off-context, composite of two skills /standup no skill behind it, deliberately House rule, enforced by a test: a command carries the happy path and its skill carries the failure modes. No command restates a failure-mode table, because two copies drift. Three decisions worth recording. Relations are DERIVED from the template body, not declared in frontmatter. Name equality would not work — /red-team defers to red-team-this and /verify to human-verification-steps, because nobody types a skill's full name as a slash command — and this repo should not add speculative keys to a frontmatter namespace OpenCode owns. Reading back the deferral cannot drift from the truth. Command worked examples live in a separate command-simulations/ directory, the one place this diverges from skills. OpenCode registers every .md in commands/ as invocable, so a sibling verify.SIMULATION.md would put a bogus /verify.SIMULATION in the user's autocomplete. The format is identical and the skill parser is reused verbatim. Commands ship OpenCode-only. Claude Code reads .claude/commands/ with a different dialect (argument-hint, allowed-tools), so unlike SKILL.md these are not portable. The install table says so rather than implying otherwise. SimulationPanel gains optional sourcePath/sourceUrl props so a command page can point the terminal chrome at its own transcript; skillName still works unchanged.
Owner
Author
Post-merge archive follow-ups
Current hosting topology: the catalog is deployed by the old repository at https://leoncheng.dev/agent-skills/. This repository has Pages configured at https://leoncheng.dev/custom-dca-opencode/, but its root currently returns 404 and |
This was referenced Aug 23, 2026
Owner
Author
This was referenced Aug 23, 2026
…ills # Conflicts: # package-lock.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
leoncheng57/agent-skillsas a non-squashedagent-skills/subtree, preserving its source historyVerification
npm ci --ignore-scriptsnpm run typechecknpm test(449 Runner tests + 405 catalog tests)npm run buildnpm run test:e2e(250 passed, 1 expected screenshot-runner skip)npx --yes skills add ./agent-skills --list(13 skills discovered)Closes #104.
Source-repository companion: leoncheng57/agent-skills#37