feat(server): surface portable .agents/skills across provider snapshots - #6382
feat(server): surface portable .agents/skills across provider snapshots#6382marcushohlbein wants to merge 2 commits into
Conversation
Providers without a native skill inventory (Cursor, Grok, OpenCode) did not surface cross-agent portable skills in the `$` picker, so a skill placed under `~/.agents/skills` or `<cwd>/.agents/skills` was invisible to them. Extract the filesystem scanner (frontmatter parse + best-effort root scan) out of ClaudeSkills.ts into a shared scanFilesystemSkillRoots/discoverAgentSkills module; Claude reuses it and layers its `.claude` roots, while the three skill-less providers augment their snapshot draft via a new augmentProviderSnapshotWithAgentSkills helper. Resolution stays most-specific-wins, extended to two axes: project beats user, and within a scope the Claude-native `.claude/skills` root beats a portable `.agents` namesake. Codex is intentionally not augmented: it reports skills natively via its app-server. Generated with Claude Code (anthropic) inside the T3 Code harness.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| const textGeneration = yield* makeCursorTextGeneration(effectiveConfig, processEnv); | ||
|
|
||
| const checkProvider = checkCursorProviderStatus(effectiveConfig, processEnv).pipe( | ||
| Effect.flatMap((draft) => augmentProviderSnapshotWithAgentSkills(draft, cwd)), |
There was a problem hiding this comment.
🟠 High Drivers/CursorDriver.ts:138
Cursor projects opened outside the server startup directory receive the wrong skills inventory: checkProvider advertises skills from ServerConfig.cwd and omits the project's <project>/.agents/skills, while exposing startup-directory skills to every project. Project-scoped discovery needs to use the workspace cwd associated with the project/thread rather than the process startup cwd.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Drivers/CursorDriver.ts around line 138:
Cursor projects opened outside the server startup directory receive the wrong skills inventory: `checkProvider` advertises skills from `ServerConfig.cwd` and omits the project's `<project>/.agents/skills`, while exposing startup-directory skills to every project. Project-scoped discovery needs to use the workspace cwd associated with the project/thread rather than the process startup cwd.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces new agent skills discovery capability across multiple providers, including a new RPC endpoint and service. New feature additions of this scope warrant human review. Additionally, a High-severity finding identifies that CursorDriver uses the server startup cwd rather than the project workspace cwd, causing incorrect skill discovery for projects opened elsewhere. You can customize Macroscope's approvability policy. Learn more. |
The provider snapshot is environment-level, so project-scoped skill discovery keyed on ServerConfig.cwd (the launch dir) leaks the startup project's skills to every project and hides each project's own `<root>/.agents/skills`. This adds the server foundation for per-project resolution (web wiring + snapshot switch follow in a later change): - Split discovery into user-only / project-only primitives (`discoverUserAgentSkills`, `discoverProjectAgentSkills`, `discoverClaudeProjectSkills`) alongside the existing combined scanner. - New `WorkspaceAgentSkills` service resolves project skills for a workspace root + provider (Claude adds `<root>/.claude/skills`; others portable only), reusing `WorkspacePaths.normalizeWorkspaceRoot`. - New `projects.listAgentSkills` RPC (contract + ws handler + auth scope) so the client can fetch skills for the active thread's workspace root. - No behavior change yet: the snapshot still carries skills as before; this RPC is additive until the client merges its results and the snapshot drops project roots. Generated with Claude Code (anthropic) inside the T3 Code harness.
There was a problem hiding this comment.
Reviewed the new Effect service (WorkspaceAgentSkills), the skill-discovery helpers, the driver call sites, and the new RPC contract error.
One convention violation and one suggestion:
ProjectListAgentSkillsErrortakes a pre-formattedmessagefrom the RPC handler instead of deriving it from its structural attributes, and it drops the normalized failure context that every sibling project error carries.WorkspaceAgentSkillsleaksFileSystem | Pathinto its service interface requirement channel instead of acquiring them inmake.
Everything else looks consistent with the conventions: namespace subpath imports, canonical Context.Service -> make -> layer ordering in WorkspaceAgentSkills.ts, no service-instance injection, no ManagedRuntime/runPromise in service code, and the shared scanFilesystemSkillRoots extraction preserves the original comments and invariants.
Posted via Macroscope — Effect Service Conventions
| readonly list: ( | ||
| input: ProjectListAgentSkillsInput, | ||
| ) => Effect.Effect< | ||
| ProjectListAgentSkillsResult, | ||
| WorkspacePaths.WorkspacePathsError, | ||
| FileSystem.FileSystem | Path.Path | ||
| >; |
There was a problem hiding this comment.
The service interface pushes FileSystem.FileSystem | Path.Path onto every consumer's requirement channel; make acquires only WorkspacePaths. Other services in this directory (WorkspaceFileSystem, WorkspaceEntries) acquire their platform dependencies in make so their methods are Effect<A, E> and the layer owns the requirements. Suggest doing the same here: yield* FileSystem.FileSystem / yield* Path.Path in make, provide them to the discovery helpers with Effect.provideService, and drop the R parameter from the interface.
Posted via Macroscope — Effect Service Conventions
| new ProjectListAgentSkillsError({ | ||
| cwd: input.cwd, | ||
| message: `Failed to list project agent skills in '${input.cwd}'.`, | ||
| cause, | ||
| }), |
There was a problem hiding this comment.
Constructing the caller-visible message at the wrapping site duplicates cwd into an unstructured field. Once ProjectListAgentSkillsError derives its message from its attributes (see the contracts comment), drop message here and pass the structural context instead — cwd, provider, and a normalized failure category from cause — matching the neighbouring projectsReadFile / projectsListEntries handlers that spread projectFileFailureContext(cause) / projectEntriesFailureContext(cause).
Posted via Macroscope — Effect Service Conventions
| export class ProjectListAgentSkillsError extends Schema.TaggedErrorClass<ProjectListAgentSkillsError>()( | ||
| "ProjectListAgentSkillsError", | ||
| { | ||
| cwd: Schema.optional(TrimmedNonEmptyString), | ||
| message: TrimmedNonEmptyString, | ||
| cause: Schema.optional(Schema.Defect()), | ||
| }, | ||
| ) {} |
There was a problem hiding this comment.
message is a required field here but is not derived from the error's own attributes — the RPC handler in ws.ts hand-formats the string and passes it in. Every sibling error in this file (ProjectListEntriesError, ProjectReadFileError, ProjectWriteFileError) derives the message inside the class from cwd and keeps a normalized failure category so callers get structured context rather than a prose blob. Consider mirroring that pattern (and, since the wrapped WorkspacePathsError union distinguishes not-exists / create-failed / stat-failed / not-directory, capturing that category plus the requested provider):
-) {}
+) {
+ // @effect-diagnostics-next-line overriddenSchemaConstructor:off
+ constructor(props: { readonly cwd: string; readonly cause?: unknown }) {
+ super({
+ ...props,
+ message:
+ decodedProjectErrorMessage(props) ??
+ `Failed to list project agent skills in '${props.cwd}'.`,
+ } as any);
+ }
+}Posted via Macroscope — Effect Service Conventions
Problem
Providers without a native skill inventory (Cursor, Grok, OpenCode) never surfaced cross-agent portable skills in the
$picker. A skill placed under~/.agents/skillsor<cwd>/.agents/skills— the portable, cross-agent convention — was invisible to them, even though Claude and Codex already expose skills.Fix
ClaudeSkills.tsinto a sharedscanFilesystemSkillRoots/discoverAgentSkillsmodule.augmentProviderSnapshotWithAgentSkillshelper that callsdiscoverAgentSkills.Resolution rule
Most-specific-wins, extended to two axes:
.claude/skillsroot beats a portable.agents/skillsroot of the same name — so a Claude-specific skill keeps running even when a portable namesake exists. (Portable-first would silently hide existing.claudeskills the moment a same-named portable one appears.)Tests
AgentSkills.test.tsandaugmentProviderSnapshotWithAgentSkills.test.tscover the shared scanner and the augment seam (both branches: no-op passthrough returns the same draft reference; populated roots attach skills with project winning on collision).ClaudeSkills.test.tsgains a characterization test pinning the within-scope.claude-over-.agentsprecedence.vp run --filter t3 typecheckclean for the touched files.Generated with Claude Code (anthropic) inside the T3 Code harness.
Note
Low Risk
Read-only filesystem scans and snapshot augmentation; new RPC is orchestration read-scoped. Collision rules are explicit and covered by tests; no auth or data mutation paths.
Overview
Adds shared filesystem discovery for portable cross-agent skills under
~/.agents/skillsand<cwd>/.agents/skills, and wires them into provider snapshots and a new per-project RPC.Provider snapshots: Cursor, Grok, and OpenCode now augment their
checkProviderdraft withaugmentProviderSnapshotWithAgentSkills(Codex unchanged—native inventory). Claude refactors onto the sharedscanFilesystemSkillRootsscanner and also scans.agents/skillsalongside Claude-native roots, with project over user and.claude/skillsover.agents/skillswithin a scope.Per-project listing: New
projects.listAgentSkillsRPC (read scope),WorkspaceAgentSkillsservice, and contracts types list project-scoped skills for a givencwdand provider—Claude includes.claude/skills; other drivers use portable roots only.Reviewed by Cursor Bugbot for commit 0513515. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Surface portable
.agents/skillsacross provider snapshots via a newprojects.listAgentSkillsRPC~/.agents/skills(user scope) and<workspace>/.agents/skills(project scope), with project entries overriding user entries on name collisions..claude/skillsdirectories; Claude-native skills override portable ones within the same scope.augmentProviderSnapshotWithAgentSkillsto attach discovered skills to provider snapshots for Cursor, Grok, and OpenCode drivers; returns the original draft unchanged when no skills are found.WorkspaceAgentSkillsservice and aprojects.listAgentSkillsWebSocket RPC (requiring orchestration read scope) that returns project-scoped skills for a given provider and working directory.name/descriptionwhen present, falling back to the directory name; malformed or unreadable entries are silently skipped.Macroscope summarized 0513515.