Skip to content

feat(skills): Add wildcard skill dependencies (name = "*")#17

Merged
gricha merged 3 commits intomainfrom
feat/wildcard-skill-dependencies
Feb 18, 2026
Merged

feat(skills): Add wildcard skill dependencies (name = "*")#17
gricha merged 3 commits intomainfrom
feat/wildcard-skill-dependencies

Conversation

@gricha
Copy link
Member

@gricha gricha commented Feb 18, 2026

Add support for name = "*" in agents.toml to install all skills from a source with a single entry, replacing the need to enumerate each skill individually.

# Before: one entry per skill
[[skills]]
name = "find-bugs"
source = "getsentry/skills"

[[skills]]
name = "code-review"
source = "getsentry/skills"

# After: one wildcard entry
[[skills]]
name = "*"
source = "getsentry/skills"
exclude = ["deprecated-skill"]

The lockfile continues to enumerate each concrete skill for reproducibility. Wildcard expansion happens at install/update time — frozen installs expand from the existing lockfile with no network access.

Key behaviors

  • Explicit entries win — if both name = "code-review" and name = "*" resolve from the same repo, the explicit entry takes precedence
  • Exclude listexclude = [...] on a wildcard skips specific skills
  • Update detects new, changed, and removed upstream skills for wildcard sources
  • Remove detects wildcard-sourced skills and offers to add them to the exclude list instead
  • List annotates wildcard-sourced skills with (* source)
  • Add --alldotagents add getsentry/skills --all creates a wildcard entry
  • Frozen install — expands wildcards from lockfile entries (no network)
  • Conflict detection — two different wildcard sources producing the same skill name errors with an actionable message

Files changed

Area Files What
Config schema schema.ts, loader.ts, writer.ts, index.ts Discriminated union for wildcard/regular deps, addWildcardToConfig, addExcludeToWildcard, duplicate-source validation
Resolver resolver.ts, index.ts resolveWildcardSkills() discovers all skills from a source
Install install.ts expandSkills() helper for wildcard expansion with frozen/force support
Update update.ts updateWildcardSource() with new/changed/removed detection
Add add.ts --all flag for wildcard config creation
Remove remove.ts WildcardSkillRemoveError, interactive exclude prompt
List list.ts Wildcard annotation, lockfile-based expansion
Tests 7 test files 36 new tests (338 total, all passing)

Co-Authored-By: Claude noreply@anthropic.com

Agent transcript: https://claudescope.sentry.dev/share/uGvevEfl0wIbQi9loepf7YDoHBIZqWsB0a6T3Uwen3w

Add support for `name = "*"` in agents.toml to pull all skills from a
given source, with an optional exclude list. The lockfile still
enumerates each concrete skill for reproducibility.

Key behaviors:
- Explicit entries always win over wildcard-discovered skills
- Wildcard entries support `exclude = [...]` to skip specific skills
- Frozen install expands wildcards from the lockfile (no network)
- Update detects new/changed/removed upstream skills for wildcards
- Remove offers to add wildcard-sourced skills to the exclude list
- List annotates wildcard-sourced skills with their source
- Add supports `--all` flag for wildcard config creation

Co-Authored-By: Claude <noreply@anthropic.com>

Agent transcript: https://claudescope.sentry.dev/share/Qf-YG9fRYqU0XecGKLL_Wqjqq8U2vq-Dxvq0P8LGFFA
@gricha gricha marked this pull request as ready for review February 18, 2026 22:04
Copy link

@sentry-warden sentry-warden bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [EEC-XAP] Missing error handling for resolveSkill in wildcard re-resolution path (src/cli/commands/install.ts:176) (high confidence)

When a wildcard-expanded skill needs re-resolution with a locked commit (lines 176-179), the resolveSkill call lacks try-catch error handling. In contrast, the else branch (lines 184-192) wraps the same function call in try-catch and converts errors to InstallError. If resolution fails in the wildcard path, the raw error will bubble up instead of being wrapped in a descriptive InstallError.

🟠 [937-59A] Unvalidated skillName allows potential path traversal (src/cli/commands/remove.ts:98) (medium confidence)

The skillName from command-line args (line 98) is used directly in join(skillsDir, skillName) (lines 41, 118) without validation against skillNameSchema. A malicious input like ../../../etc could construct paths outside the intended skills directory, though rm would only succeed if the path exists and has appropriate permissions.

⚠️ [P8A-TBR] Trust validation bypassed in updateRegularSkill (src/cli/commands/update.ts:121) (high confidence)

validateTrustedSource is called without the config.trust parameter, causing it to return immediately and skip all trust checks. Compare to install.ts:167 which correctly passes config.trust.

⚠️ [GVL-S7E] Path traversal via untrusted skill name from SKILL.md (src/skills/resolver.ts:164) (high confidence)

The resolveWildcardSkills function uses d.meta.name from SKILL.md files without validating against skillNameSchema. A malicious repo could contain name: "../../../malicious" in SKILL.md, causing join(skillsDir, name) in install.ts to write files outside the skills directory.

Identified by Warden via find-bugs

- Pass config.trust to update helper functions (was silently allowing
  all sources by omitting the trust parameter)
- Use lockfile for concrete skill names in remove gitignore update
  (literal "*" from wildcard entries was matching all skills in gitignore)
- Track wildcard removals separately so lockfile is persisted even when
  the only changes are upstream skill deletions

Co-Authored-By: Claude <noreply@anthropic.com>

Agent transcript: https://claudescope.sentry.dev/share/XlKtRbKngflaSdn-1aoADpuokeN_YlukhiVCnE4G9vQ
- Validate discovered skill names against safe regex in
  resolveWildcardSkills to prevent path traversal via malicious
  SKILL.md frontmatter
- Trim TOML lines before regex matching in addExcludeToWildcard
  to handle valid indented TOML
- Return removed skills from runUpdate so the CLI reports upstream
  removals instead of silently saying "up to date"
- Make sync command wildcard-aware: expand wildcards from lockfile
  for declared names and gitignore, skip "*" in missing skills check

Co-Authored-By: Claude <noreply@anthropic.com>

Agent transcript: https://claudescope.sentry.dev/share/tL6yIUATV96GpNKi9FF4unQ2yGyU2Kta5qyrSx08c0A
Comment on lines +230 to 234
if (!discoveredNames.has(name) && !excludeSet.has(name)) {
delete newLock.skills[name];
await rm(join(skillsDir, name), { recursive: true, force: true });
removed.push(name);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The skill removal logic in update.ts incorrectly checks !excludeSet.has(name), preventing cleanup of skills that are manually added to the exclude list.
Severity: MEDIUM

Suggested Fix

Remove the !excludeSet.has(name) check from the if condition in src/cli/commands/update.ts. The condition should simply be if (!discoveredNames.has(name)) to ensure that any skill no longer discovered (either removed upstream or excluded) is properly cleaned up.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/cli/commands/update.ts#L230-L234

Potential issue: When a user manually adds a skill to the `exclude` list in
`agents.toml` for a wildcard source and then runs `dotagents update`, the skill is not
removed from the filesystem or the lockfile. The condition `if
(!discoveredNames.has(name) && !excludeSet.has(name))` in `update.ts` is incorrect. The
`!excludeSet.has(name)` check prevents the removal of a skill that is explicitly
excluded, leaving orphaned files and stale lockfile entries.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

if (wildcardSources.has(locked.source)) {
declaredNames.add(name);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync omits wildcard exclude list filtering from lockfile

Medium Severity

When building declaredNames from the lockfile for wildcard sources, sync.ts doesn't filter by the wildcard's exclude list. Both install.ts (frozen mode, line 93) and list.ts (line 52) correctly build an excludeSet from wDep.exclude and skip matching names, but sync.ts adds every lockfile entry whose source matches a wildcard source without any exclude check. This causes excluded skills still present in the lockfile to be incorrectly treated as "declared," leading to spurious "missing skill" warnings.

Fix in Cursor Fix in Web

resolved = await resolveSkill(name, dep, {
projectRoot: scope.root,
lockedCommit,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing try-catch on wildcard skill re-resolve path

Low Severity

When a wildcard-expanded skill needs re-resolution with a locked commit (because the fresh commit differs from the lockfile), the resolveSkill call at line 176 lacks a try-catch, unlike the equivalent call for regular skills at line 184. If this resolution fails, a raw ResolveError propagates instead of a user-friendly InstallError, which the CLI handler doesn't catch — resulting in an unhandled error with a stack trace rather than a clean error message.

Fix in Cursor Fix in Web

@gricha gricha merged commit d2f7d9c into main Feb 18, 2026
13 checks passed
@gricha gricha deleted the feat/wildcard-skill-dependencies branch February 18, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments