@zenobius/opencode-skillful 1.0.0-next.6 → 1.1.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +588 -48
  2. package/dist/index.js +2473 -1213
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  An interpretation of the [Anthropic Agent Skills Specification](https://github.com/anthropics/skills) for OpenCode, providing lazy-loaded skill discovery and injection.
4
4
 
5
- Differenator is :
5
+ Differentiators include:
6
6
 
7
7
  - Conversationally the agent uses `skill_find words, words words` to discover skills
8
8
  - The agent uses `skill_use fully_resolved_skill_name` and,
@@ -68,20 +68,19 @@ skill_find "testing -performance"
68
68
 
69
69
  ## Features
70
70
 
71
- - :mag: Discover SKILL.md files from multiple locations
72
- - :zap: Lazy loading - skills only inject when explicitly requested
73
- - :file_folder: Path prefix matching for organized skill browsing
74
- - :abc: Natural query syntax with negation and quoted phrases
75
- - :label: Skill ranking by relevance (name matches weighted higher)
76
- - :recycle: Silent message insertion (noReply pattern)
71
+ - Discover SKILL.md files from multiple locations
72
+ - Lazy loading - skills only inject when explicitly requested
73
+ - Path prefix matching for organized skill browsing
74
+ - Natural query syntax with negation and quoted phrases
75
+ - Skill ranking by relevance (name matches weighted higher)
76
+ - Silent message insertion (noReply pattern)
77
77
 
78
78
  ## Skill Discovery Paths
79
79
 
80
80
  Skills are discovered from these locations (in priority order, last wins on duplicates):
81
81
 
82
- 1. `~/.opencode/skills/` - User global skills (lowest priority)
83
- 2. `~/.config/opencode/skills/` - Standard XDG config location
84
- 3. `.opencode/skills/` - Project-local skills (highest priority)
82
+ 1. `~/.config/opencode/skills/` - Standard XDG config location or (`%APPDATA%/.opencode/skills` for windows users)
83
+ 2. `.opencode/skills/` - Project-local skills (highest priority)
85
84
 
86
85
  ## Usage in OpenCode
87
86
 
@@ -89,48 +88,85 @@ Skills are discovered from these locations (in priority order, last wins on dupl
89
88
 
90
89
  ```
91
90
  # List all available skills
92
- skill_find
93
- query="*"
91
+ skill_find query="*"
94
92
 
95
93
  # Search by keyword
96
- skill_find
97
- query="git commit"
98
-
99
- # Path prefix matching
100
- skill_find
101
- query="experts/data-ai"
94
+ skill_find query="git commit"
102
95
 
103
96
  # Exclude terms
104
- skill_find
105
- query="testing -performance"
97
+ skill_find query="testing -performance"
106
98
  ```
107
99
 
108
100
  ### Loading Skills
109
101
 
102
+ Skills must be loaded by their fully-qualified identifier (generated from the directory path).
103
+
104
+ The skill identifier is created by converting the directory path to a slug: directory separators and hyphens become underscores.
105
+
106
+ ```
107
+ skills/
108
+ experts/
109
+ ai/
110
+ agentic-engineer/ # Identifier: experts_ai_agentic_engineer
111
+ superpowers/
112
+ writing/
113
+ code-review/ # Identifier: superpowers_writing_code_review
114
+ ```
115
+
116
+ When loading skills, use the full identifier:
117
+
110
118
  ```
111
- # Load a single skill
112
- skill_use
113
- skill_names=["writing-git-commits"]
119
+ # Load a skill by its identifier
120
+ skill_use skill_names=["experts_ai_agentic_engineer"]
114
121
 
115
122
  # Load multiple skills
116
- skill_use
117
- skill_names=["writing-git-commits", "code-review"]
123
+ skill_use skill_names=["experts_ai_agentic_engineer", "superpowers_writing_code_review"]
118
124
  ```
119
125
 
120
126
  ### Reading Skill Resources
121
127
 
122
128
  ```
123
- # Read a resource file from a skill's directory
124
- skill_resource
125
- skill_name="brand-guidelines"
126
- relative_path="templates/logo-usage.md"
129
+
130
+ # Read a reference document
131
+
132
+ skill_resource skill_name="writing-git-commits" relative_path="references/style-guide.md"
133
+
134
+ # Read a template
135
+
136
+ skill_resource skill_name="brand-guidelines" relative_path="assets/logo-usage.html"
137
+
138
+ ```
139
+
140
+ Normally you won't need to use `skill_resource` directly, since the llm will do it for you.
141
+
142
+ When skill_use is called, it will be wrapped with instructions to the llm on
143
+ how to load references, assets, and scripts from the skill.
144
+
145
+ But when writing your skills, there's nothing stopping you from using `skill_resource` to be explicit.
146
+
147
+ (Just be aware that exotic file types might need special tooling to handle them properly.)
148
+
149
+ ### Executing Skill Scripts
150
+
151
+ ```
152
+
153
+ # Execute a script without arguments
154
+
155
+ skill_exec skill_name="build-utils" relative_path="scripts/generate-changelog.sh"
156
+
157
+ # Execute a script with arguments
158
+
159
+ skill_exec skill_name="code-generator" relative_path="scripts/gen.py" args=["--format", "json", "--output", "schema.json"]
160
+
127
161
  ```
128
162
 
129
163
  ## Plugin Tools
130
164
 
131
- ### `skill_find`
165
+ The plugin provides four core tools implemented in `src/tools/`:
132
166
 
133
- Search for skills using natural query syntax.
167
+ ### `skill_find` (SkillFinder.ts)
168
+
169
+ Search for skills using natural query syntax with intelligent ranking by relevance.
134
170
 
135
171
  **Parameters:**
136
172
 
@@ -141,33 +177,508 @@ Search for skills using natural query syntax.
141
177
  - Negation: `-term` (exclude results)
142
178
  - Quoted phrases: `"exact match"` (phrase matching)
143
179
 
144
- **Returns:** Matching skills with name and fully-qualified toolName (FQDN).
180
+ **Returns:**
181
+
182
+ - List of matching skills with:
183
+ - `skill_name`: Skill identifier (e.g., `experts_writing_git_commits`)
184
+ - `skill_shortname`: Directory name of the skill (e.g., `writing-git-commits`)
185
+ - `description`: Human-readable description
186
+ - If config.debug is enabled:
187
+ - Debug information: discovered count, parsed results, rejections, duplicates, errors
188
+
189
+ **Example Response:**
190
+
191
+ ```xml
192
+ <SkillSearchResults query="git commit">
193
+ <Skills>
194
+ <Skill skill_name="experts_writing_git_commits" skill_shortname="writing-git-commits">
195
+ Guidelines for writing effective git commit messages
196
+ </Skill>
197
+ </Skills>
198
+ <Summary>
199
+ <Total>42</Total>
200
+ <Matches>1</Matches>
201
+ <Feedback>Found 1 skill matching your query</Feedback>
202
+ </Summary>
203
+ </SkillSearchResults>
204
+ ```
145
205
 
146
- ### `skill_use`
206
+ ### `skill_use` (SkillUser.ts)
147
207
 
148
- Load one or more skills into the chat context.
208
+ Load one or more skills into the chat context with full resource metadata.
149
209
 
150
210
  **Parameters:**
151
211
 
152
- - `skill_names`: Array of skill names to load (by toolName/FQDN or short name)
212
+ - `skill_names`: Array of skill identifiers to load (e.g., `experts_ai_agentic_engineer`)
153
213
 
154
214
  **Features:**
155
215
 
156
- - Injects skill metadata and content with:
157
- - Skill resources available via `skill_resource(skillname, resourcename)`
158
- - Base directory context for relative path resolution
216
+ - Injects skill metadata and content including:
217
+ - Skill name and description
218
+ - Resource inventory:
219
+ - `references`: Documentation and reference files
220
+ - `assets`: Binary files, templates, images
221
+ - `scripts`: Executable scripts with paths and MIME types
159
222
  - Full skill content formatted as Markdown for easy reading
223
+ - Base directory context for relative path resolution
224
+
225
+ **Behavior:** Silently injects skills as user messages (persists in conversation history)
226
+
227
+ **Example Response:**
228
+
229
+ ```json
230
+ { "loaded": ["experts/writing-git-commits"], "notFound": [] }
231
+ ```
232
+
233
+ ### `skill_resource` (SkillResourceReader.ts)
234
+
235
+ Read a specific resource file from a skill's directory and inject silently into the chat.
160
236
 
161
- ### `skill_resource`
237
+ **When to Use:**
162
238
 
163
- Read a resource file from a skill's directory and inject silently into the chat.
239
+ - Load specific reference documents or templates from a skill
240
+ - Access supporting files without loading the entire skill
241
+ - Retrieve examples, guides, or configuration templates
242
+ - Most commonly used after loading a skill with `skill_use` to access its resources
164
243
 
165
244
  **Parameters:**
166
245
 
167
246
  - `skill_name`: The skill containing the resource (by toolName/FQDN or short name)
168
247
  - `relative_path`: Path to the resource relative to the skill directory
169
248
 
170
- **Behavior:** Silently injects resource content without triggering AI response (noReply pattern)
249
+ **Returns:**
250
+
251
+ - MIME type of the resource
252
+ - Success confirmation with skill name, resource path, and type
253
+
254
+ **Behavior:**
255
+
256
+ - Silently injects resource content without triggering AI response (noReply pattern)
257
+ - Resolves relative paths within skill directory (e.g., `references/guide.md`, `assets/template.html`, `scripts/setup.sh`)
258
+ - Supports any text or binary file type
259
+
260
+ **Example Response:**
261
+
262
+ ```
263
+ Load Skill Resource
264
+
265
+ skill: experts/writing-git-commits
266
+ resource: templates/commit-template.md
267
+ type: text/markdown
268
+ ```
269
+
270
+ ### `skill_exec` (SkillScriptExec.ts)
271
+
272
+ Execute scripts from skill resources with optional arguments.
273
+
274
+ **Parameters:**
275
+
276
+ - `skill_name`: The skill containing the script (by toolName/FQDN or short name)
277
+ - `relative_path`: Path to the script file relative to the skill directory
278
+ - `args`: Optional array of string arguments to pass to the script
279
+
280
+ **Returns:**
281
+
282
+ - Exit code of the script execution
283
+ - Standard output (stdout)
284
+ - Standard error (stderr)
285
+ - Formatted text representation
286
+
287
+ **Behavior:**
288
+
289
+ - Locates and executes scripts within skill directories
290
+ - Passes arguments to the script
291
+ - Silently injects execution results
292
+ - Includes proper error handling and reporting
293
+
294
+ **Example Response:**
295
+
296
+ ```
297
+ Executed script from skill "build-utils": scripts/generate-changelog.sh
298
+
299
+ Exit Code: 0
300
+ STDOUT: Changelog generated successfully
301
+ STDERR: (none)
302
+ ```
303
+
304
+ ## Error Handling
305
+
306
+ ### skill_find Errors
307
+
308
+ When a search query doesn't match any skills, the response includes feedback:
309
+
310
+ ```xml
311
+ <SkillSearchResults query="nonexistent">
312
+ <Skills></Skills>
313
+ <Summary>
314
+ <Total>42</Total>
315
+ <Matches>0</Matches>
316
+ <Feedback>No skills found matching your query</Feedback>
317
+ </Summary>
318
+ </SkillSearchResults>
319
+ ```
320
+
321
+ ### skill_use Errors
322
+
323
+ When loading skills, if a skill name is not found, it's returned in the `notFound` array:
324
+
325
+ ```json
326
+ { "loaded": ["writing-git-commits"], "notFound": ["nonexistent-skill"] }
327
+ ```
328
+
329
+ The loaded skills are still injected into the conversation, allowing you to use available skills even if some are not found.
330
+
331
+ ### skill_resource Errors
332
+
333
+ Resource loading failures occur when:
334
+
335
+ - The skill doesn't exist
336
+ - The resource path doesn't exist within the skill
337
+ - The file cannot be read due to permissions
338
+
339
+ The tool returns an error message indicating which skill or resource path was problematic.
340
+
341
+ ### skill_exec Errors
342
+
343
+ Script execution includes exit codes and error output:
344
+
345
+ ```
346
+ Executed script from skill "build-utils": scripts/generate-changelog.sh
347
+
348
+ Exit Code: 1
349
+ STDOUT: (partial output)
350
+ STDERR: Permission denied: scripts/generate-changelog.sh
351
+ ```
352
+
353
+ Non-zero exit codes indicate script failures. Always check STDERR and the exit code when troubleshooting.
354
+
355
+ ## Configuration
356
+
357
+ The plugin reads configuration from the OpenCode config file (`~/.config/opencode/config.json`):
358
+
359
+ ```json
360
+ {
361
+ "plugins": ["@zenobius/opencode-skillful"],
362
+ "skillful": {
363
+ "debug": false,
364
+ "basePaths": ["~/.config/opencode/skills", ".opencode/skills"]
365
+ }
366
+ }
367
+ ```
368
+
369
+ ### Configuration Options
370
+
371
+ - **debug** (boolean, default: `false`): Enable debug output showing skill discovery stats
372
+ - When enabled, `skill_find` includes discovered, parsed, rejected, and duplicate counts
373
+ - Useful for diagnosing skill loading issues
374
+ - **basePaths** (array, default: standard locations): Custom skill search directories
375
+ - Paths are searched in order; later paths override earlier ones for duplicate skill names
376
+ - Use project-local `.opencode/skills/` for project-specific skills
377
+
378
+ ## Architecture
379
+
380
+ ### System Design Overview
381
+
382
+ The plugin uses a **layered, modular architecture** with clear separation of concerns and two-phase initialization.
383
+
384
+ #### Design Principles
385
+
386
+ - **Async Coordination**: ReadyStateMachine ensures tools don't execute before registry initialization completes
387
+ - **Security-First Resource Access**: All resources are pre-indexed at parse time; no path traversal possible
388
+ - **Factory Pattern**: Centralized API creation for easy testing and configuration management
389
+ - **Lazy Loading**: Skills only injected when explicitly requested, minimal memory overhead
390
+
391
+ ### System Layers
392
+
393
+ ```
394
+ ┌──────────────────────────────────────────────────────┐
395
+ │ Plugin Entry Point (index.ts) │
396
+ │ - Defines 3 core tools: skill_find, skill_use, │
397
+ │ skill_resource │
398
+ │ - Initializes API factory and config │
399
+ │ - Manages message injection (XML serialization) │
400
+ └──────────────────────────────────────────────────────┘
401
+
402
+ ┌──────────────────────────────────────────────────────┐
403
+ │ API Factory Layer (api.ts, config.ts) │
404
+ │ - createApi(): initializes logger, registry, tools │
405
+ │ - getPluginConfig(): resolves base paths with proper │
406
+ │ precedence (project-local overrides global) │
407
+ └──────────────────────────────────────────────────────┘
408
+
409
+ ┌──────────────────────────────────────────────────────┐
410
+ │ Services Layer (src/services/) │
411
+ │ - SkillRegistry: discovery, parsing, resource │
412
+ │ mapping with ReadyStateMachine coordination │
413
+ │ - SkillSearcher: query parsing + intelligent ranking│
414
+ │ - SkillResourceResolver: safe path-based retrieval │
415
+ │ - SkillFs (via lib): filesystem abstraction (mockable)
416
+ └──────────────────────────────────────────────────────┘
417
+
418
+ ┌──────────────────────────────────────────────────────┐
419
+ │ Core Libraries (src/lib/) │
420
+ │ - ReadyStateMachine: async initialization sequencing │
421
+ │ - SkillFs: abstract filesystem operations │
422
+ │ - Identifiers: path ↔ tool name conversions │
423
+ │ - OpenCodeChat: message injection abstraction │
424
+ │ - xml.ts: JSON → XML serialization │
425
+ └──────────────────────────────────────────────────────┘
426
+ ```
427
+
428
+ ### Initialization Flow (Why This Matters)
429
+
430
+ The plugin uses a **two-phase initialization pattern** to coordinate async discovery with tool execution:
431
+
432
+ ```
433
+ PHASE 1: SYNCHRONOUS CREATION
434
+ ├─ Plugin loads configuration
435
+ ├─ createApi() called:
436
+ │ ├─ Creates logger
437
+ │ ├─ Creates SkillRegistry (factory, NOT initialized yet)
438
+ │ └─ Returns registry + tool creators
439
+ └─ index.ts registers tools immediately
440
+
441
+ PHASE 2: ASYNCHRONOUS DISCOVERY (Background)
442
+ ├─ registry.initialise() called
443
+ │ ├─ Sets ready state to "loading"
444
+ │ ├─ Scans all base paths for SKILL.md files
445
+ │ ├─ Parses each file (YAML frontmatter + resources)
446
+ │ ├─ Pre-indexes all resources (scripts/assets/references)
447
+ │ └─ Sets ready state to "ready"
448
+
449
+ └─ WHY PRE-INDEXING: Prevents path traversal attacks.
450
+ Tools can only retrieve pre-registered resources,
451
+ never arbitrary filesystem paths.
452
+
453
+ PHASE 3: TOOL EXECUTION (User Requested)
454
+ ├─ User calls: skill_find("git commits")
455
+ ├─ Tool executes:
456
+ │ ├─ await registry.controller.ready.whenReady()
457
+ │ │ (blocks until Phase 2 completes)
458
+ │ ├─ Search registry
459
+ │ └─ Return results
460
+ └─ WHY THIS PATTERN: Multiple tools can call whenReady()
461
+ at different times without race conditions.
462
+ Simple Promise would resolve once; this allows
463
+ concurrent waiters.
464
+ ```
465
+
466
+ ### Data Flow Diagram: skill_find Query
467
+
468
+ ```
469
+ ┌─────────────────────────────────────────┐
470
+ │ User Query: skill_find("git commit") │
471
+ └────────────┬────────────────────────────┘
472
+
473
+ ┌─────────────────────────────────────────┐
474
+ │ SkillFinder Tool (tools/SkillFinder.ts) │
475
+ │ - Validates query string │
476
+ │ - Awaits: controller.ready.whenReady() │
477
+ └────────────┬────────────────────────────┘
478
+ ↓ (continues when registry ready)
479
+ ┌─────────────────────────────────────────┐
480
+ │ SkillSearcher.search() │
481
+ │ - Parse query via search-string library │
482
+ │ ("git commit" → include: [git,commit])│
483
+ │ - Filter ALL include terms must match │
484
+ │ (name, description, or toolName) │
485
+ │ - Exclude results matching exclude │
486
+ │ terms │
487
+ │ - Rank results: │
488
+ │ * nameMatches × 3 (strong signal) │
489
+ │ * descMatches × 1 (weak signal) │
490
+ │ * exact match bonus +10 │
491
+ └────────────┬────────────────────────────┘
492
+
493
+ ┌─────────────────────────────────────────┐
494
+ │ Results with Feedback │
495
+ │ - Matched skills array │
496
+ │ - Sorted by relevance score │
497
+ │ - User-friendly feedback message │
498
+ │ "Searching for: **git commit** | │
499
+ │ Found 3 matches" │
500
+ └────────────┬────────────────────────────┘
501
+
502
+ ┌─────────────────────────────────────────┐
503
+ │ Format & Return (index.ts) │
504
+ │ - Convert to XML via jsonToXml() │
505
+ │ - Inject into message context │
506
+ │ - Return to user │
507
+ └─────────────────────────────────────────┘
508
+ ```
509
+
510
+ ### Data Flow Diagram: skill_use Loading
511
+
512
+ ```
513
+ ┌──────────────────────────────────┐
514
+ │ User: skill_use("git-commits") │
515
+ └────────────┬─────────────────────┘
516
+
517
+ ┌──────────────────────────────────┐
518
+ │ SkillUser Tool (tools/SkillUser) │
519
+ │ - Await ready state │
520
+ │ - Validate skill names │
521
+ └────────────┬─────────────────────┘
522
+
523
+ ┌──────────────────────────────────┐
524
+ │ Registry.controller.get(key) │
525
+ │ - Look up skill in Map │
526
+ │ - Return Skill object with: │
527
+ │ * Name, description │
528
+ │ * Content (markdown) │
529
+ │ * Resource maps (indexed) │
530
+ └────────────┬─────────────────────┘
531
+
532
+ ┌──────────────────────────────────┐
533
+ │ Format Skill for Injection │
534
+ │ - Skill metadata │
535
+ │ - Resource inventory (with MIME) │
536
+ │ - Full content as markdown │
537
+ │ - Base directory context │
538
+ └────────────┬─────────────────────┘
539
+
540
+ ┌──────────────────────────────────┐
541
+ │ Silent Injection Pattern │
542
+ │ - Inject as user message │
543
+ │ - Persists in conversation │
544
+ │ - Returns success summary │
545
+ │ { loaded: [...], notFound: []}│
546
+ └──────────────────────────────────┘
547
+ ```
548
+
549
+ ### Data Flow Diagram: skill_resource Access
550
+
551
+ ```
552
+ ┌──────────────────────────────────────────────┐
553
+ │ User: skill_resource("git-commits", │
554
+ │ "scripts/commit.sh") │
555
+ └──────────────┬───────────────────────────────┘
556
+
557
+ ┌──────────────────────────────────────────────┐
558
+ │ SkillResourceReader Tool │
559
+ │ (tools/SkillResourceReader.ts) │
560
+ │ - Validate skill_name │
561
+ │ - Parse path: "scripts/commit.sh" │
562
+ │ ├─ type = "scripts" │
563
+ │ └─ relative_path = "commit.sh" │
564
+ │ - Assert type is valid (scripts|assets|refs)│
565
+ └──────────────┬───────────────────────────────┘
566
+
567
+ ┌──────────────────────────────────────────────┐
568
+ │ SkillResourceResolver │
569
+ │ - Fetch skill object from registry │
570
+ │ - Look up in skill.scripts Map │
571
+ │ (pre-indexed at parse time) │
572
+ │ - Retrieve: { absolutePath, mimeType } │
573
+ │ │
574
+ │ ★ SECURITY: Only pre-indexed paths exist │
575
+ │ No way to request ../../../etc/passwd │
576
+ └──────────────┬───────────────────────────────┘
577
+
578
+ ┌──────────────────────────────────────────────┐
579
+ │ File I/O (SkillFs abstraction) │
580
+ │ - Read file content from absolutePath │
581
+ │ - Detect MIME type (e.g., text/plain) │
582
+ └──────────────┬───────────────────────────────┘
583
+
584
+ ┌──────────────────────────────────────────────┐
585
+ │ Return Injection Object │
586
+ │ { │
587
+ │ skill_name, │
588
+ │ resource_path, │
589
+ │ resource_mimetype, │
590
+ │ content │
591
+ │ } │
592
+ └──────────────┬───────────────────────────────┘
593
+
594
+ ┌──────────────────────────────────────────────┐
595
+ │ Silent Injection │
596
+ │ - Inject content into message │
597
+ │ - User sees resource inline │
598
+ └──────────────────────────────────────────────┘
599
+ ```
600
+
601
+ ### Key Design Decisions and Their Rationale
602
+
603
+ | Decision | Why | Impact |
604
+ | ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------ |
605
+ | **ReadyStateMachine** | Ensures tools don't race with async registry setup | Tools are guaranteed registry is ready before execution |
606
+ | **Pre-indexed Resources** | Prevents path traversal attacks | Security: only pre-registered paths retrievable |
607
+ | **Factory Pattern (api.ts)** | Centralizes initialization | Easy to test, swap implementations, mock components |
608
+ | **Removed SkillProvider** | Eliminated unnecessary abstraction | Simpler code, direct registry access, easier debugging |
609
+ | **XML Serialization** | Human-readable message injection | Results display nicely formatted in chat context |
610
+ | **Two-phase Init** | Async discovery doesn't block tool registration | Tools available immediately, discovery happens in background |
611
+
612
+ ### Services Layer Breakdown
613
+
614
+ #### SkillRegistry (`src/services/SkillRegistry.ts`)
615
+
616
+ - **Role**: Central skill catalog and discovery engine
617
+ - **Responsibilities**:
618
+ 1. Scan multiple base paths for SKILL.md files (recursive)
619
+ 2. Parse each file's YAML frontmatter (validates schema)
620
+ 3. Index all resources (scripts/assets/references) for safe retrieval
621
+ 4. Register skills in controller.skills Map by toolName
622
+ 5. Coordinate initialization via ReadyStateMachine
623
+ - **Key Methods**:
624
+ - `initialise()`: Async discovery and parsing pipeline
625
+ - `register()`: Parse and store skill files
626
+ - `parseSkill()`: Extract metadata and resource paths
627
+ - **Error Handling**: Malformed skills logged but don't halt discovery
628
+
629
+ #### SkillSearcher (`src/services/SkillSearcher.ts`)
630
+
631
+ - **Role**: Natural language query interpretation and ranking
632
+ - **Responsibilities**:
633
+ 1. Parse queries using search-string library (Gmail syntax)
634
+ 2. Filter: ALL include terms must match (AND logic)
635
+ 3. Exclude: Remove results matching exclude terms
636
+ 4. Rank by relevance (name matches 3×, description 1×, exact +10)
637
+ 5. Generate user-friendly feedback
638
+ - **Scoring Formula**: `(nameMatches × 3) + (descMatches × 1) + exactBonus`
639
+ - **Edge Cases**: Empty queries or "\*" list all skills
640
+
641
+ ### Tools Layer Overview
642
+
643
+ #### SkillFinder (`src/tools/SkillFinder.ts`)
644
+
645
+ - Wraps SkillSearcher with ready-state synchronization
646
+ - Transforms results to SkillFinder schema
647
+ - Returns matched skills + summary metadata
648
+
649
+ #### SkillUser (`src/tools/SkillUser.ts`)
650
+
651
+ - Loads skills into chat context
652
+ - Injects as user message (persists in conversation)
653
+ - Returns { loaded: [...], notFound: [...] }
654
+
655
+ #### SkillResourceReader (`src/tools/SkillResourceReader.ts`)
656
+
657
+ - Safe resource path parsing and validation
658
+ - Pre-indexed path lookup (prevents traversal)
659
+ - Returns injection object with MIME type and content
660
+
661
+ ### Configuration and Path Resolution
662
+
663
+ ```
664
+ Configuration Priority (Last Wins):
665
+ 1. Global: ~/.opencode/skills/ (lowest)
666
+ 2. Project: ./.opencode/skills/ (highest)
667
+
668
+ Why This Order:
669
+ - Users install global skills once
670
+ - Projects override with local versions
671
+ - Same skill name in both → project version wins
672
+ ```
673
+
674
+ ## Contributing
675
+
676
+ Contributions are welcome! When adding features, follow these principles:
677
+
678
+ - **Explain the WHY, not the WHAT**: Code comments should document design decisions, not mechanics
679
+ - **Keep modules focused**: Each file has one primary responsibility
680
+ - **Test async behavior**: Ready state coordination is critical
681
+ - **Document algorithms**: Ranking, parsing, and search logic should have detailed comments
171
682
 
172
683
  ## Creating Skills
173
684
 
@@ -181,18 +692,28 @@ skills/
181
692
  template.md
182
693
  ```
183
694
 
695
+ ### Skill Structure
696
+
697
+ ```
698
+ my-skill/
699
+ SKILL.md # Required: Skill metadata and instructions
700
+ references/ # Optional: Documentation and guides
701
+ guide.md
702
+ examples.md
703
+ assets/ # Optional: Binary files and templates
704
+ template.html
705
+ logo.png
706
+ scripts/ # Optional: Executable scripts
707
+ setup.sh
708
+ generate.py
709
+ ```
710
+
184
711
  ### SKILL.md Format
185
712
 
186
713
  ```yaml
187
714
  ---
188
715
  name: my-skill
189
716
  description: A brief description of what this skill does (min 20 chars)
190
- license: MIT
191
- allowed-tools:
192
- - bash
193
- - read
194
- metadata:
195
- author: Your Name
196
717
  ---
197
718
  # My Skill
198
719
 
@@ -202,9 +723,28 @@ Instructions for the AI agent when this skill is loaded...
202
723
  **Requirements:**
203
724
 
204
725
  - `name` must match the directory name (lowercase, alphanumeric, hyphens only)
205
- - `description` must be at least 20 characters
726
+ - `description` must be at least 20 characters and should be concise (under 500 characters recommended)
727
+ - Focus on when to use the skill, not what it does
728
+ - Include specific triggering conditions and symptoms
729
+ - Use third person for system prompt injection
730
+ - Example: "Use when designing REST APIs to establish consistent patterns and improve developer experience"
206
731
  - Directory name and frontmatter `name` must match exactly
207
732
 
733
+ ### Resource Types
734
+
735
+ Skill resources are automatically discovered and categorized:
736
+
737
+ - **References** (`references/` directory): Documentation, guides, and reference materials
738
+ - Typically Markdown, plaintext, or HTML files
739
+ - Accessed via `skill_resource` for reading documentation
740
+ - **Assets** (`assets/` directory): Templates, images, and binary files
741
+ - Can include HTML templates, images, configuration files
742
+ - Useful for providing templates and examples to the AI
743
+ - **Scripts** (`scripts/` directory): Executable scripts that perform actions
744
+ - Shell scripts (.sh), Python scripts (.py), or other executables
745
+ - Executed via `skill_exec` with optional arguments
746
+ - Useful for automation, code generation, or complex operations
747
+
208
748
  ## Considerations
209
749
 
210
750
  - Skills are discovered at plugin initialization (requires restart to reload)