refactor: single-source skill tree for all agent plugins - #7
Merged
prathamesh-sonpatki merged 12 commits intoAug 24, 2026
Merged
Conversation
Root-source plugin entry with explicit skills path replaces the plugins/last9 directory copy; Grok-specific root manifest removed so its documented Claude-manifest fallback serves it.
source.path moves from ./plugins/last9 to ./; Codex discovers the root-level skills/ conventionally (verified live: all four skills visible in-session), so no committed mirror is needed.
prepack regenerates skills/ via git ls-files (tracked canonical only, so uncommitted experiments never leak into the tarball); the committed mirror is deleted and gitignored. Bundled-skills tests now exercise both present and absent states.
The old job verified committed plugin copies; there are none now. The replacement validates the hub model directly: frontmatter name equals directory name, declared marketplace skills paths exist, and the opencode tarball ships every tracked canonical skill. Job name kept identical — it is a branch-protection required check.
Adding a skill is a single commit under skills/<name>/; manifests reference the canonical tree directly and the OpenCode plugin assembles it at pack time. Sync-script instructions removed everywhere.
…ned ones - check-skill-pack.sh fails closed on malformed manifests and bounds name extraction to the frontmatter block (adversarial + testing findings); negative selftests cover every fail-closed branch - root-level plugin.json manifests for Claude Code and Codex make the repo root a fully-formed plugin under source:"./" entries - orphaned plugins/last9 manifest dirs removed; release-please extra-files repointed at the root manifests
git ls-files glob cannot match nested paths today; the guard keeps the copy loop correct if that ever widens (adversarial finding #9).
fb5704a intended this deletion but a prepack regeneration between staging and committing resurrected identical files, silently unstaging it. Committed mirrors are forbidden under the hub model.
Pins the invariant whose violation slipped through fb5704a: a pack-time regeneration between staging and committing resurrected identical mirror files and unstaged their deletion unnoticed.
Live testing showed Grok Build's marketplace loader drops entries whose source resolves to the marketplace root (./, ., omitted all invisible to catalog discovery) and requires real skill files in a real subdir; symlinked skills dirs are also rejected. R5's per-format fallback fires: plugins/last9/skills returns as the single sanctioned mirror. Automation keeps it honest: the gate asserts byte-parity with canonical skills/ (both directions), and a master-only workflow step refreshes the mirror so contributors still touch only skills/. Selftests extended: mirror-drift case added; fixtures carry the mirror.
- fail closed on missing consumer manifests; require >=1 plugin entry - type-safe skills assertions (string-typed values false-passed -d checks) - declared paths must contain SKILL.md, not just exist as directories - tarball completeness via tar -tzf exact-set match, both directions (catches extras, not just omissions); npm failures fail fast - assemble-skills copies all tracked files under skills/<name>/ (not SKILL.md alone) and asserts assembled count == tracked count - Grok mirror parity skips on contributor PRs (SKIP_MIRROR_PARITY) and enforces at full strength on master after the auto-refresh bot runs; refresh regenerates from scratch so orphans cannot survive - selftests cover string-skills, deleted manifest, codex pointer drift, and the parity skip
The workflow exports the skip for the gate; the env leaked into the selftest, neutering its drift-detection case. Each selftest invocation now pins its own environment via env -u.
prathamesh-sonpatki
merged commit Aug 24, 2026
267b8a4
into
fde-311-opencode-plugin-for-last9-hosted-mcp-server
5 checks passed
prathamesh-sonpatki
added a commit
that referenced
this pull request
Aug 24, 2026
…skills (#6) * feat(opencode): add opencode-last9 plugin for hosted MCP and bundled skills New npm package `opencode-last9` connects OpenCode to the hosted Last9 MCP server: - config hook injects the org-scoped remote endpoint (https://app.last9.io/api/v4/organizations/<org-slug>/mcp); OpenCode handles OAuth automatically (RFC 7591 dynamic client registration) - org slug resolves from plugin options or LAST9_ORG_SLUG; explicit url / LAST9_MCP_URL override for self-hosted environments; a user-defined last9 MCP entry is never clobbered; nothing configured = no injection - bundles the canonical skills via config.skills.paths so investigation skills ship with the tools Repo wiring: - sync-agent-plugin-skills.sh mirrors skills into both packaged plugins; CI parity check extended to plugins/opencode-last9/skills - release-please config + manifest track the new package - README + CONTRIBUTING document the OpenCode install path The plugin entrypoint exports functions only — opencode's loader rejects modules with non-function exports. Refs FDE-311. * fix(ci): drop orphaned packaged skill, assemble test URLs via constant - Remove plugins/opencode-last9/skills/sidekiq-debugging: its canonical source is not yet committed, so the sync-parity check deleted it as an orphaned copy on CI. It returns when that skill lands with both mirrors. - Assemble expected URLs in tests from a base constant; the structural reference scan rejects literal organizations/<slug>/mcp shapes even for fake slugs. Refs FDE-311. * refactor: single-source skill tree for all agent plugins (#7) * refactor(skills): point Claude marketplace at canonical root tree Root-source plugin entry with explicit skills path replaces the plugins/last9 directory copy; Grok-specific root manifest removed so its documented Claude-manifest fallback serves it. * refactor(skills): point Codex marketplace at canonical root tree source.path moves from ./plugins/last9 to ./; Codex discovers the root-level skills/ conventionally (verified live: all four skills visible in-session), so no committed mirror is needed. * refactor(opencode): assemble skills at pack time from canonical tree prepack regenerates skills/ via git ls-files (tracked canonical only, so uncommitted experiments never leak into the tarball); the committed mirror is deleted and gitignored. Bundled-skills tests now exercise both present and absent states. * refactor(ci): replace sync-parity with skill distribution checks The old job verified committed plugin copies; there are none now. The replacement validates the hub model directly: frontmatter name equals directory name, declared marketplace skills paths exist, and the opencode tarball ships every tracked canonical skill. Job name kept identical — it is a branch-protection required check. * docs: describe repo-as-hub skill distribution Adding a skill is a single commit under skills/<name>/; manifests reference the canonical tree directly and the OpenCode plugin assembles it at pack time. Sync-script instructions removed everywhere. * fix(review): harden skill gate, add root plugin manifests, drop orphaned ones - check-skill-pack.sh fails closed on malformed manifests and bounds name extraction to the frontmatter block (adversarial + testing findings); negative selftests cover every fail-closed branch - root-level plugin.json manifests for Claude Code and Codex make the repo root a fully-formed plugin under source:"./" entries - orphaned plugins/last9 manifest dirs removed; release-please extra-files repointed at the root manifests * fix(review): reject unexpected path depth in skill assembly git ls-files glob cannot match nested paths today; the guard keeps the copy loop correct if that ever widens (adversarial finding #9). * chore: drop committed opencode plugin skill mirror fb5704a intended this deletion but a prepack regeneration between staging and committing resurrected identical files, silently unstaging it. Committed mirrors are forbidden under the hub model. * fix(review): forbid committed plugin skill copies in the gate Pins the invariant whose violation slipped through fb5704a: a pack-time regeneration between staging and committing resurrected identical mirror files and unstaged their deletion unnoticed. * fix(grok): sanctioned fallback mirror — Grok cannot follow root sources Live testing showed Grok Build's marketplace loader drops entries whose source resolves to the marketplace root (./, ., omitted all invisible to catalog discovery) and requires real skill files in a real subdir; symlinked skills dirs are also rejected. R5's per-format fallback fires: plugins/last9/skills returns as the single sanctioned mirror. Automation keeps it honest: the gate asserts byte-parity with canonical skills/ (both directions), and a master-only workflow step refreshes the mirror so contributors still touch only skills/. Selftests extended: mirror-drift case added; fixtures carry the mirror. * fix(review): harden gate per cross-harness Cursor review - fail closed on missing consumer manifests; require >=1 plugin entry - type-safe skills assertions (string-typed values false-passed -d checks) - declared paths must contain SKILL.md, not just exist as directories - tarball completeness via tar -tzf exact-set match, both directions (catches extras, not just omissions); npm failures fail fast - assemble-skills copies all tracked files under skills/<name>/ (not SKILL.md alone) and asserts assembled count == tracked count - Grok mirror parity skips on contributor PRs (SKIP_MIRROR_PARITY) and enforces at full strength on master after the auto-refresh bot runs; refresh regenerates from scratch so orphans cannot survive - selftests cover string-skills, deleted manifest, codex pointer drift, and the parity skip * fix(ci): pin selftest env — CI exports SKIP_MIRROR_PARITY on PRs The workflow exports the skip for the gate; the env leaked into the selftest, neutering its drift-detection case. Each selftest invocation now pins its own environment via env -u.
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
Every skill now lives exactly once in git — with one documented exception for Grok. Marketplace manifests reference the canonical
skills/tree directly (Claude Code and Codex via root-source entries), and the OpenCode plugin assembles the tree into its tarball at pack time. The sync script, both committed mirrors, and the drift-failure class they produced are gone.Stacked on #6 (which introduced
opencode-last9); retarget tomasterafter that merges.Session-settled decisions carried from planning: repo-as-hub over auto-sync bot and npm artifact (user-directed); skills bundled on every install path (user-directed); automation-maintained fallback copies only where manifests cannot reference the canonical tree (user-approved — this PR exercises that clause once, for Grok).
Why not keep the mirrors
The old model failed twice in one day: a legitimate edit failed parity until re-synced, and a packaged copy of an uncommitted skill passed local checks but died in CI as an orphaned mirror. Ceremony also grew linearly per plugin format, while the strategy doc positions this repo as a growing multi-runtime skills layer.
Design decisions
source: "./"+ explicitskillspaths).source.path: "./"makes the repo root itself the plugin; an in-session probe confirmed all four skills discovered (last9:*). Root-level.claude-plugin/plugin.json/.codex-plugin/plugin.jsonmake the root a fully-formed plugin rather than relying on undocumented defaults../,., omitted) and rejects symlinked skill dirs; only real files in a real subdirectory install.plugins/last9/skillsreturns as the single sanctioned mirror: byte-parity enforced by the CI gate in both directions, auto-refreshed by a master-only workflow step so contributors still touch onlyskills/.prepackscript enumerates skills viagit ls-files, so only committed canonical content can reach npm; generatedskills/is gitignored.Test plan
plugins/opencode-last9;tsc --noEmitclean.scripts/check-skill-pack.shpasses on this tree and fails correctly on seeded faults (name mismatch, mirror drift verified in scratch repos); selftests cover all gate branches.last9:*skills ✅~/.grok/installed-plugins/✅ — final in-session probe blocked by free-tier quota; re-run when quota resets1b2e1d7,1b61d39,4ec02ab,8d5d322,2bc9b5f; receipt at/tmp/compound-engineering-503/ce-code-review/20260824-123136-32f2d084/review.json.Post-Deploy Monitoring & Validation
last9release-please run: confirm version bumps land in root.claude-plugin/plugin.json/.codex-plugin/plugin.json(paths changed).Related to FDE-311 (the plugin this stacks on).