Repository navigation
feat(opencode): add skills.exclude for hidden and trash copies - #159
Merged
Merged
Conversation
Skill discovery scans ~/.claude/skills and ~/.agents/skills with dot: true, so gstack's per-host copies (gstack/.openclaw, .factory, ...) and skills/.trash load next to the canonical skills: about 60 of 86 listed skills, roughly 53k chars of every first request (#59). Only whole-root env flags exist today, and skills.paths only adds roots. Plan: - core: Glob.Options gains an optional ignore list passed to glob's ignore option. It prunes excluded directories during the walk, so a .trash tree is not read at all. - core v1 config: ConfigSkillsV1 gains skills.exclude (string array), described in the schema. The v2 array form of skills lowers to {paths, urls} and cannot carry it, so the object form is the key. - opencode skill: ScanState carries the configured excludes, so every scan honors them: global ~/.claude and ~/.agents, project ancestors, config directories, skills.paths and skills.urls. Patterns match each SKILL.md path relative to the root it was found under, so one list reads the same in every root (for example **/.trash/**). - An excluded match is dropped before it reaches dirs or add(), so it cannot shadow the canonical skill or raise a duplicate-name warning. - Without the setting the default list is empty and nothing changes. - Test (skill.test.ts): a home root with a canonical review skill, a gstack/.openclaw copy and a .trash copy, all named review. With the setting, dirs() holds only the canonical directory and all() only its location. Without it, dirs() holds all three directories. Red proof: the new cases run against the unchanged tree before implementing. - Docs: a short skills.exclude section in packages/web skills.mdx. - Not changed: the v2 skill plugin in packages/core never scans ~/.claude or ~/.agents, so it has no copies to exclude. - Deferred to publication: CHANGESET.md needs this PR's number under "Fork changes", and CI reads it from the PR head, so that entry is added once the PR exists. Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
A project root holds a canonical review skill, a gstack/.openclaw copy and a .trash copy, all named review. With skills.exclude set to **/.trash/** and **/gstack/.*/**, dirs() must hold only the canonical directory and all() only its location. Without the setting, dirs() must hold all three directories. Red on the base before the implementation commit: the excluded case fails because dirs() still holds the .trash and gstack/.openclaw directories, and the unset case passes as a preservation check. Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
skills.exclude takes glob patterns for SKILL.md files. Each pattern is matched against the path relative to the root a scan started from, and every scan honors the list: global ~/.claude and ~/.agents, project ancestors, config directories, skills.paths and skills.urls. The list goes to glob's ignore option, so excluded directories are pruned during the walk, and an excluded match never reaches dirs or add(). A copy under gstack/.openclaw or .trash therefore cannot shadow the canonical skill or raise a duplicate-name warning. The default is an empty list, so nothing changes without the setting. The regression fixture lives in a project .claude/skills root rather than a home root that the plan named: the global Global.home is captured when the test layer builds, so withHome cannot redirect it (filed as #157). Refs #59 Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
Only the skills.exclude hunks are taken from the generator output, in packages/sdk/openapi.json and packages/sdk/js/src/v2/gen/types.gen.ts. packages/sdk/openapi.json was already behind the source for activeAssistantMessageID (packages/opencode/src/server/routes/instance/ httpapi/public.ts) before this change. That drift is not regenerated here so this commit stays scoped to skills.exclude. Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
Author
|
Session stamp for the PR that this session opened:
|
Adds the Features entry that the changeset check requires for this PR. The PR number is only known once the PR exists, so the entry follows the PR creation. Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
Brings in the #155 revert (#163) before this PR merges. The reviewed head 394ec55 stays on the first parent. Claude-Session: https://claude.ai/code/session_015hTsruVJZ4rc4nVuPj1KgU
brentyates-swx
added a commit
that referenced
this pull request
Oct 11, 2026
Brings in 8d536e3 (#159) so the branch is current with the default branch. It does not touch the SQLite driver, the queue, or the lock tests. Refs #162 Claude-Session: https://claude.ai/code/session_01LrS7BTRTnc6eYrSxBFoWAo
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
Adds
skills.exclude: glob patterns forSKILL.mdfiles that skip copies in every skill root. Hidden per-host copies, such as gstack's.openclaw,.factory, and.hermesinstalls under~/.claude/skills/gstack, and.trashcopies are no longer loaded, so they stop filling the skill listing sent on every first request.SKILL.mdpath relative to the root its scan started from, so**/.trash/**works the same in every root: global~/.claudeand~/.agents, project ancestors, config directories,skills.paths, andskills.urls.ignoreoption, so excluded directories are pruned during the walk. Excluded matches never reachdirsoradd(), so a copy cannot shadow the canonical skill or raise a duplicate-name warning.packages/web/src/content/docs/skills.mdx. The schema description is inpackages/core/src/v1/config/skills.ts.Closes #59
Testing
packages/opencode/test/skill/skill.test.tspasses. Its two new cases use a project root holding a canonicalreviewskill, a hidden per-host copy, and a.trashcopy.ignorewiring, or ignoring the configured list, each fails the excluded case.Notes
.claude/skillsroot.withHomecannot redirect the global root, becauseGlobal.homeis captured when the test layer builds, so the existing "discovers global skills" test covers only the project root. Tracked in tests: withHome does not redirect global ~/.claude skill discovery #157.packages/sdk/openapi.jsonwas already behind the source foractiveAssistantMessageIDbefore this change. This PR adds only theskills.excludehunks and does not regenerate that drift. Tracked in sdk: packages/sdk/openapi.json is behind the source for activeAssistantMessageID #158.skillslowers to{paths, urls}and cannot carryexclude; only the object form does.Stock compatibility. Stock opencode's V1 config schema is strict (
additionalProperties: false), so a sharedopencode.jsonthat setsskills.excludefails validation on a stock build until this lands upstream. Do not add the key to a shared config until a build with this change ships.🤖 Generated with Claude Code