@zenobius/opencode-skillful 1.0.0 → 1.1.0-next.4

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 +809 -48
  2. package/dist/index.js +7162 -549
  3. package/package.json +7 -3
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,116 @@ 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
+ - **Pluggable prompt rendering** with model-aware format selection (XML, JSON, Markdown)
78
+
79
+ ## Prompt Renderer Configuration
80
+
81
+ The plugin supports **multiple formats for prompt injection**, allowing you to optimize results for different LLM models and use cases.
82
+
83
+ > See the [Configuration](#configuration) section for complete configuration details, including bunfig setup and global/project-level overrides.
84
+
85
+ ### Supported Formats
86
+
87
+ Choose the format that works best for your LLM:
88
+
89
+ | Format | Best For | Characteristics |
90
+ | ----------------- | ---------------------- | ---------------------------------------------------------------------- |
91
+ | **XML** (default) | Claude models | Human-readable, structured, XML-optimized for Claude |
92
+ | **JSON** | GPT and strict parsers | Machine-readable, strict JSON structure, strong parsing support |
93
+ | **Markdown** | All models | Readable prose, heading-based structure, easy to read in conversations |
94
+
95
+ ### Configuration Syntax
96
+
97
+ Set your preferences in `.opencode-skillful.json`:
98
+
99
+ ```json
100
+ {
101
+ "promptRenderer": "xml",
102
+ "modelRenderers": {
103
+ "claude-3-5-sonnet": "xml",
104
+ "gpt-4": "json",
105
+ "gpt-4-turbo": "json"
106
+ }
107
+ }
108
+ ```
109
+
110
+ **How It Works:**
111
+
112
+ 1. Every tool execution checks the current active LLM model
113
+ 2. If `modelRenderers[modelID]` is configured, that format is used
114
+ 3. Otherwise, the global `promptRenderer` default is used
115
+ 4. Results are rendered in the selected format and injected into the prompt
116
+
117
+ ### Format Output Examples
118
+
119
+ #### XML Format (Claude Optimized)
120
+
121
+ ```xml
122
+ <Skill>
123
+ <name>git-commits</name>
124
+ <description>Guidelines for writing effective git commit messages</description>
125
+ <toolName>writing_git_commits</toolName>
126
+ </Skill>
127
+ ```
128
+
129
+ **Advantages:**
130
+
131
+ - Matches Claude's native instruction format
132
+ - Clear tag-based structure
133
+ - Excellent readability for complex nested data
134
+
135
+ #### JSON Format (GPT Optimized)
136
+
137
+ ```json
138
+ {
139
+ "name": "git-commits",
140
+ "description": "Guidelines for writing effective git commit messages",
141
+ "toolName": "writing_git_commits"
142
+ }
143
+ ```
144
+
145
+ **Advantages:**
146
+
147
+ - Strong parsing support across LLMs
148
+ - Strict, validated structure
149
+ - Familiar format for language models trained on JSON data
150
+
151
+ #### Markdown Format (Human Readable)
152
+
153
+ ```markdown
154
+ # Skill
155
+
156
+ ### name
157
+
158
+ - **name**: _git-commits_
159
+
160
+ ### description
161
+
162
+ - **description**: _Guidelines for writing effective git commit messages_
163
+
164
+ ### toolName
165
+
166
+ - **toolName**: _writing_git_commits_
167
+ ```
168
+
169
+ **Advantages:**
170
+
171
+ - Most readable in conversations
172
+ - Natural language-friendly
173
+ - Works well for exploratory workflows
77
174
 
78
175
  ## Skill Discovery Paths
79
176
 
80
177
  Skills are discovered from these locations (in priority order, last wins on duplicates):
81
178
 
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)
179
+ 1. `~/.config/opencode/skills/` - Standard XDG config location or (`%APPDATA%/.opencode/skills` for windows users)
180
+ 2. `.opencode/skills/` - Project-local skills (highest priority)
85
181
 
86
182
  ## Usage in OpenCode
87
183
 
@@ -89,69 +185,705 @@ Skills are discovered from these locations (in priority order, last wins on dupl
89
185
 
90
186
  ```
91
187
  # List all available skills
92
- skill_find
93
- query="*"
188
+ skill_find query="*"
94
189
 
95
190
  # Search by keyword
96
- skill_find
97
- query="git commit"
98
-
99
- # Path prefix matching
100
- skill_find
101
- query="experts/data-ai"
191
+ skill_find query="git commit"
102
192
 
103
193
  # Exclude terms
104
- skill_find
105
- query="testing -performance"
194
+ skill_find query="testing -performance"
106
195
  ```
107
196
 
108
197
  ### Loading Skills
109
198
 
199
+ Skills must be loaded by their fully-qualified identifier (generated from the directory path).
200
+
201
+ The skill identifier is created by converting the directory path to a slug: directory separators and hyphens become underscores.
202
+
110
203
  ```
111
- # Load a single skill
112
- skill_use
113
- skill_names=["writing-git-commits"]
204
+ skills/
205
+ experts/
206
+ ai/
207
+ agentic-engineer/ # Identifier: experts_ai_agentic_engineer
208
+ superpowers/
209
+ writing/
210
+ code-review/ # Identifier: superpowers_writing_code_review
211
+ ```
212
+
213
+ When loading skills, use the full identifier:
214
+
215
+ ```
216
+ # Load a skill by its identifier
217
+ skill_use skill_names=["experts_ai_agentic_engineer"]
114
218
 
115
219
  # Load multiple skills
116
- skill_use
117
- skill_names=["writing-git-commits", "code-review"]
220
+ skill_use skill_names=["experts_ai_agentic_engineer", "superpowers_writing_code_review"]
118
221
  ```
119
222
 
120
223
  ### Reading Skill Resources
121
224
 
122
225
  ```
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"
226
+
227
+ # Read a reference document
228
+
229
+ skill_resource skill_name="writing-git-commits" relative_path="references/style-guide.md"
230
+
231
+ # Read a template
232
+
233
+ skill_resource skill_name="brand-guidelines" relative_path="assets/logo-usage.html"
234
+
235
+ ```
236
+
237
+ Normally you won't need to use `skill_resource` directly, since the llm will do it for you.
238
+
239
+ When skill_use is called, it will be wrapped with instructions to the llm on
240
+ how to load references, assets, and scripts from the skill.
241
+
242
+ But when writing your skills, there's nothing stopping you from using `skill_resource` to be explicit.
243
+
244
+ (Just be aware that exotic file types might need special tooling to handle them properly.)
245
+
246
+ ### Executing Skill Scripts
247
+
248
+ ```
249
+
250
+ # Execute a script without arguments
251
+
252
+ skill_exec skill_name="build-utils" relative_path="scripts/generate-changelog.sh"
253
+
254
+ # Execute a script with arguments
255
+
256
+ skill_exec skill_name="code-generator" relative_path="scripts/gen.py" args=["--format", "json", "--output", "schema.json"]
257
+
127
258
  ```
128
259
 
129
260
  ## Plugin Tools
130
261
 
131
- ### `skill_find`
262
+ The plugin provides four core tools implemented in `src/tools/`:
132
263
 
133
- Search for skills using natural query syntax.
264
+ ### `skill_find` (SkillFinder.ts)
265
+
266
+ Search for skills using natural query syntax with intelligent ranking by relevance.
267
+
268
+ **Parameters:**
134
269
 
135
270
  - `query`: Search string supporting:
136
271
  - `*` or empty: List all skills
137
- - Path prefixes: `experts`, `superpowers/writing`
138
- - Keywords: `git commit`
139
- - Negation: `-term`
140
- - Quoted phrases: `"exact match"`
272
+ - Path prefixes: `experts`, `superpowers/writing` (prefix matching)
273
+ - Keywords: `git commit` (multiple terms use AND logic)
274
+ - Negation: `-term` (exclude results)
275
+ - Quoted phrases: `"exact match"` (phrase matching)
276
+
277
+ **Returns:**
278
+
279
+ - List of matching skills with:
280
+ - `skill_name`: Skill identifier (e.g., `experts_writing_git_commits`)
281
+ - `skill_shortname`: Directory name of the skill (e.g., `writing-git-commits`)
282
+ - `description`: Human-readable description
283
+ - If config.debug is enabled:
284
+ - Debug information: discovered count, parsed results, rejections, duplicates, errors
285
+
286
+ **Example Response:**
287
+
288
+ ```xml
289
+ <SkillSearchResults query="git commit">
290
+ <Skills>
291
+ <Skill skill_name="experts_writing_git_commits" skill_shortname="writing-git-commits">
292
+ Guidelines for writing effective git commit messages
293
+ </Skill>
294
+ </Skills>
295
+ <Summary>
296
+ <Total>42</Total>
297
+ <Matches>1</Matches>
298
+ <Feedback>Found 1 skill matching your query</Feedback>
299
+ </Summary>
300
+ </SkillSearchResults>
301
+ ```
302
+
303
+ ### `skill_use` (SkillUser.ts)
141
304
 
142
- ### `skill_use`
305
+ Load one or more skills into the chat context with full resource metadata.
143
306
 
144
- Load one or more skills into the chat context.
307
+ **Parameters:**
145
308
 
146
- - `skill_names`: Array of skill names to load (by toolName or short name)
309
+ - `skill_names`: Array of skill identifiers to load (e.g., `experts_ai_agentic_engineer`)
147
310
 
148
- ### `skill_resource`
311
+ **Features:**
149
312
 
150
- Read a resource file from a skill's directory.
313
+ - Injects skill metadata and content including:
314
+ - Skill name and description
315
+ - Resource inventory:
316
+ - `references`: Documentation and reference files
317
+ - `assets`: Binary files, templates, images
318
+ - `scripts`: Executable scripts with paths and MIME types
319
+ - Full skill content formatted as Markdown for easy reading
320
+ - Base directory context for relative path resolution
151
321
 
152
- - `skill_name`: The skill containing the resource
322
+ **Behavior:** Silently injects skills as user messages (persists in conversation history)
323
+
324
+ **Example Response:**
325
+
326
+ ```json
327
+ { "loaded": ["experts/writing-git-commits"], "notFound": [] }
328
+ ```
329
+
330
+ ### `skill_resource` (SkillResourceReader.ts)
331
+
332
+ Read a specific resource file from a skill's directory and inject silently into the chat.
333
+
334
+ **When to Use:**
335
+
336
+ - Load specific reference documents or templates from a skill
337
+ - Access supporting files without loading the entire skill
338
+ - Retrieve examples, guides, or configuration templates
339
+ - Most commonly used after loading a skill with `skill_use` to access its resources
340
+
341
+ **Parameters:**
342
+
343
+ - `skill_name`: The skill containing the resource (by toolName/FQDN or short name)
153
344
  - `relative_path`: Path to the resource relative to the skill directory
154
345
 
346
+ **Returns:**
347
+
348
+ - MIME type of the resource
349
+ - Success confirmation with skill name, resource path, and type
350
+
351
+ **Behavior:**
352
+
353
+ - Silently injects resource content without triggering AI response (noReply pattern)
354
+ - Resolves relative paths within skill directory (e.g., `references/guide.md`, `assets/template.html`, `scripts/setup.sh`)
355
+ - Supports any text or binary file type
356
+
357
+ **Example Response:**
358
+
359
+ ```
360
+ Load Skill Resource
361
+
362
+ skill: experts/writing-git-commits
363
+ resource: templates/commit-template.md
364
+ type: text/markdown
365
+ ```
366
+
367
+ ### `skill_exec` (SkillScriptExec.ts)
368
+
369
+ Execute scripts from skill resources with optional arguments.
370
+
371
+ **Parameters:**
372
+
373
+ - `skill_name`: The skill containing the script (by toolName/FQDN or short name)
374
+ - `relative_path`: Path to the script file relative to the skill directory
375
+ - `args`: Optional array of string arguments to pass to the script
376
+
377
+ **Returns:**
378
+
379
+ - Exit code of the script execution
380
+ - Standard output (stdout)
381
+ - Standard error (stderr)
382
+ - Formatted text representation
383
+
384
+ **Behavior:**
385
+
386
+ - Locates and executes scripts within skill directories
387
+ - Passes arguments to the script
388
+ - Silently injects execution results
389
+ - Includes proper error handling and reporting
390
+
391
+ **Example Response:**
392
+
393
+ ```
394
+ Executed script from skill "build-utils": scripts/generate-changelog.sh
395
+
396
+ Exit Code: 0
397
+ STDOUT: Changelog generated successfully
398
+ STDERR: (none)
399
+ ```
400
+
401
+ ## Error Handling
402
+
403
+ ### skill_find Errors
404
+
405
+ When a search query doesn't match any skills, the response includes feedback:
406
+
407
+ ```xml
408
+ <SkillSearchResults query="nonexistent">
409
+ <Skills></Skills>
410
+ <Summary>
411
+ <Total>42</Total>
412
+ <Matches>0</Matches>
413
+ <Feedback>No skills found matching your query</Feedback>
414
+ </Summary>
415
+ </SkillSearchResults>
416
+ ```
417
+
418
+ ### skill_use Errors
419
+
420
+ When loading skills, if a skill name is not found, it's returned in the `notFound` array:
421
+
422
+ ```json
423
+ { "loaded": ["writing-git-commits"], "notFound": ["nonexistent-skill"] }
424
+ ```
425
+
426
+ The loaded skills are still injected into the conversation, allowing you to use available skills even if some are not found.
427
+
428
+ ### skill_resource Errors
429
+
430
+ Resource loading failures occur when:
431
+
432
+ - The skill doesn't exist
433
+ - The resource path doesn't exist within the skill
434
+ - The file cannot be read due to permissions
435
+
436
+ The tool returns an error message indicating which skill or resource path was problematic.
437
+
438
+ ### skill_exec Errors
439
+
440
+ Script execution includes exit codes and error output:
441
+
442
+ ```
443
+ Executed script from skill "build-utils": scripts/generate-changelog.sh
444
+
445
+ Exit Code: 1
446
+ STDOUT: (partial output)
447
+ STDERR: Permission denied: scripts/generate-changelog.sh
448
+ ```
449
+
450
+ Non-zero exit codes indicate script failures. Always check STDERR and the exit code when troubleshooting.
451
+
452
+ ## Configuration
453
+
454
+ The plugin loads configuration from **bunfig**, supporting both project-local and global configuration files:
455
+
456
+ ### Configuration Files
457
+
458
+ Configuration is loaded in this priority order (highest priority last):
459
+
460
+ 1. **Global config** (standard platform locations):
461
+ - Linux/macOS: `~/.config/opencode-skillful/config.json`
462
+ - Windows: `%APPDATA%/opencode-skillful/config.json`
463
+
464
+ 2. **Project config** (in your project root):
465
+ - `.opencode-skillful.json`
466
+
467
+ Later configuration files override earlier ones. Use project-local `.opencode-skillful.json` to override global settings for specific projects.
468
+
469
+ ### Configuration Options
470
+
471
+ #### Plugin Installation
472
+
473
+ First, register the plugin in your OpenCode config (`~/.config/opencode/config.json`):
474
+
475
+ ```json
476
+ {
477
+ "plugins": ["@zenobius/opencode-skillful"]
478
+ }
479
+ ```
480
+
481
+ #### Skill Discovery Configuration
482
+
483
+ Create `.opencode-skillful.json` in your project root or global config directory:
484
+
485
+ ```json
486
+ {
487
+ "debug": false,
488
+ "basePaths": ["~/.config/opencode/skills", ".opencode/skills"],
489
+ "promptRenderer": "xml",
490
+ "modelRenderers": {}
491
+ }
492
+ ```
493
+
494
+ **Configuration Fields:**
495
+
496
+ - **debug** (boolean, default: `false`): Enable debug output showing skill discovery stats
497
+ - When enabled, `skill_find` responses include discovered, parsed, rejected, and error counts
498
+ - Useful for diagnosing skill loading and parsing issues
499
+
500
+ - **basePaths** (array, default: standard locations): Custom skill search directories
501
+ - Paths are searched in priority order; later paths override earlier ones for duplicate skill names
502
+ - Default: `[~/.config/opencode/skills, .opencode/skills]`
503
+ - Use project-local `.opencode/skills/` for project-specific skills
504
+ - Platform-aware paths: automatically resolves to XDG, macOS, or Windows standard locations
505
+
506
+ - **promptRenderer** (string, default: `'xml'`): Default format for prompt injection
507
+ - Options: `'xml'` | `'json'` | `'md'`
508
+ - XML (default): Claude-optimized, human-readable structured format
509
+ - JSON: GPT-optimized, strict JSON formatting for strong parsing models
510
+ - Markdown: Human-readable format with headings and nested lists
511
+ - Used when no model-specific renderer is configured
512
+
513
+ - **modelRenderers** (object, default: `{}`): Per-model format overrides
514
+ - Maps model IDs to preferred formats
515
+ - Overrides global `promptRenderer` for specific models
516
+ - Example: `{ "gpt-4": "json", "claude-3-5-sonnet": "xml" }`
517
+
518
+ ### How Renderer Selection Works
519
+
520
+ When any tool executes (`skill_find`, `skill_use`, `skill_resource`):
521
+
522
+ 1. The plugin queries the OpenCode session to determine the active LLM model
523
+ 2. Builds a list of model candidates to check, from most to least specific:
524
+ - Full model ID (e.g., `"anthropic-claude-3-5-sonnet"`)
525
+ - Generic model pattern (e.g., `"claude-3-5-sonnet"`)
526
+ 3. Checks if any candidate exists in `modelRenderers` configuration
527
+ - First match wins (most specific takes precedence)
528
+ - If found, uses that format
529
+ 4. If no match in `modelRenderers`, falls back to `promptRenderer` default
530
+ 5. Renders the results in the selected format and injects into the prompt
531
+
532
+ **Example**: If your config has `"claude-3-5-sonnet": "xml"` and the active model is `"anthropic-claude-3-5-sonnet"`, the plugin will:
533
+
534
+ - Try matching `"anthropic-claude-3-5-sonnet"` (no match)
535
+ - Try matching `"claude-3-5-sonnet"` (match found! Use XML)
536
+ - Return `"xml"` format
537
+
538
+ This allows different models to receive results in their preferred format without needing to specify every model variant. Configure the generic model name once and it works for all provider-prefixed variations.
539
+
540
+ ### Example Configurations
541
+
542
+ #### Global Configuration for Multi-Model Setup
543
+
544
+ `~/.config/opencode-skillful/config.json`:
545
+
546
+ ```json
547
+ {
548
+ "debug": false,
549
+ "promptRenderer": "xml",
550
+ "modelRenderers": {
551
+ "claude-3-5-sonnet": "xml",
552
+ "claude-3-opus": "xml",
553
+ "gpt-4": "json",
554
+ "gpt-4-turbo": "json",
555
+ "llama-2-70b": "md"
556
+ }
557
+ }
558
+ ```
559
+
560
+ #### Project-Specific Override
561
+
562
+ `.opencode-skillful.json` (project root):
563
+
564
+ ```json
565
+ {
566
+ "debug": true,
567
+ "basePaths": ["~/.config/opencode/skills", ".opencode/skills", "./vendor/skills"],
568
+ "promptRenderer": "xml",
569
+ "modelRenderers": {
570
+ "gpt-4": "json"
571
+ }
572
+ }
573
+ ```
574
+
575
+ This project-local config:
576
+
577
+ - Enables debug output for troubleshooting
578
+ - Adds a custom vendor skills directory
579
+ - Uses JSON format specifically for GPT-4 when it's the active model
580
+ - Falls back to XML for all other models
581
+
582
+ ## Architecture
583
+
584
+ ### System Design Overview
585
+
586
+ The plugin uses a **layered, modular architecture** with clear separation of concerns and two-phase initialization.
587
+
588
+ #### Design Principles
589
+
590
+ - **Async Coordination**: ReadyStateMachine ensures tools don't execute before registry initialization completes
591
+ - **Security-First Resource Access**: All resources are pre-indexed at parse time; no path traversal possible
592
+ - **Factory Pattern**: Centralized API creation for easy testing and configuration management
593
+ - **Lazy Loading**: Skills only injected when explicitly requested, minimal memory overhead
594
+
595
+ ### System Layers
596
+
597
+ ```
598
+ ┌──────────────────────────────────────────────────────┐
599
+ │ Plugin Entry Point (index.ts) │
600
+ │ - Defines 3 core tools: skill_find, skill_use, │
601
+ │ skill_resource │
602
+ │ - Initializes API factory and config │
603
+ │ - Manages message injection (XML serialization) │
604
+ └──────────────────────────────────────────────────────┘
605
+
606
+ ┌──────────────────────────────────────────────────────┐
607
+ │ API Factory Layer (api.ts, config.ts) │
608
+ │ - createApi(): initializes logger, registry, tools │
609
+ │ - getPluginConfig(): resolves base paths with proper │
610
+ │ precedence (project-local overrides global) │
611
+ └──────────────────────────────────────────────────────┘
612
+
613
+ ┌──────────────────────────────────────────────────────┐
614
+ │ Services Layer (src/services/) │
615
+ │ - SkillRegistry: discovery, parsing, resource │
616
+ │ mapping with ReadyStateMachine coordination │
617
+ │ - SkillSearcher: query parsing + intelligent ranking│
618
+ │ - SkillResourceResolver: safe path-based retrieval │
619
+ │ - SkillFs (via lib): filesystem abstraction (mockable)
620
+ └──────────────────────────────────────────────────────┘
621
+
622
+ ┌──────────────────────────────────────────────────────┐
623
+ │ Core Libraries (src/lib/) │
624
+ │ - ReadyStateMachine: async initialization sequencing │
625
+ │ - SkillFs: abstract filesystem operations │
626
+ │ - Identifiers: path ↔ tool name conversions │
627
+ │ - OpenCodeChat: message injection abstraction │
628
+ │ - xml.ts: JSON → XML serialization │
629
+ └──────────────────────────────────────────────────────┘
630
+ ```
631
+
632
+ ### Initialization Flow (Why This Matters)
633
+
634
+ The plugin uses a **two-phase initialization pattern** to coordinate async discovery with tool execution:
635
+
636
+ ```
637
+ PHASE 1: SYNCHRONOUS CREATION
638
+ ├─ Plugin loads configuration
639
+ ├─ createApi() called:
640
+ │ ├─ Creates logger
641
+ │ ├─ Creates SkillRegistry (factory, NOT initialized yet)
642
+ │ └─ Returns registry + tool creators
643
+ └─ index.ts registers tools immediately
644
+
645
+ PHASE 2: ASYNCHRONOUS DISCOVERY (Background)
646
+ ├─ registry.initialise() called
647
+ │ ├─ Sets ready state to "loading"
648
+ │ ├─ Scans all base paths for SKILL.md files
649
+ │ ├─ Parses each file (YAML frontmatter + resources)
650
+ │ ├─ Pre-indexes all resources (scripts/assets/references)
651
+ │ └─ Sets ready state to "ready"
652
+
653
+ └─ WHY PRE-INDEXING: Prevents path traversal attacks.
654
+ Tools can only retrieve pre-registered resources,
655
+ never arbitrary filesystem paths.
656
+
657
+ PHASE 3: TOOL EXECUTION (User Requested)
658
+ ├─ User calls: skill_find("git commits")
659
+ ├─ Tool executes:
660
+ │ ├─ await registry.controller.ready.whenReady()
661
+ │ │ (blocks until Phase 2 completes)
662
+ │ ├─ Search registry
663
+ │ └─ Return results
664
+ └─ WHY THIS PATTERN: Multiple tools can call whenReady()
665
+ at different times without race conditions.
666
+ Simple Promise would resolve once; this allows
667
+ concurrent waiters.
668
+ ```
669
+
670
+ ### Data Flow Diagram: skill_find Query
671
+
672
+ ```
673
+ ┌─────────────────────────────────────────┐
674
+ │ User Query: skill_find("git commit") │
675
+ └────────────┬────────────────────────────┘
676
+
677
+ ┌─────────────────────────────────────────┐
678
+ │ SkillFinder Tool (tools/SkillFinder.ts) │
679
+ │ - Validates query string │
680
+ │ - Awaits: controller.ready.whenReady() │
681
+ └────────────┬────────────────────────────┘
682
+ ↓ (continues when registry ready)
683
+ ┌─────────────────────────────────────────┐
684
+ │ SkillSearcher.search() │
685
+ │ - Parse query via search-string library │
686
+ │ ("git commit" → include: [git,commit])│
687
+ │ - Filter ALL include terms must match │
688
+ │ (name, description, or toolName) │
689
+ │ - Exclude results matching exclude │
690
+ │ terms │
691
+ │ - Rank results: │
692
+ │ * nameMatches × 3 (strong signal) │
693
+ │ * descMatches × 1 (weak signal) │
694
+ │ * exact match bonus +10 │
695
+ └────────────┬────────────────────────────┘
696
+
697
+ ┌─────────────────────────────────────────┐
698
+ │ Results with Feedback │
699
+ │ - Matched skills array │
700
+ │ - Sorted by relevance score │
701
+ │ - User-friendly feedback message │
702
+ │ "Searching for: **git commit** | │
703
+ │ Found 3 matches" │
704
+ └────────────┬────────────────────────────┘
705
+
706
+ ┌─────────────────────────────────────────┐
707
+ │ Format & Return (index.ts) │
708
+ │ - Convert to XML via jsonToXml() │
709
+ │ - Inject into message context │
710
+ │ - Return to user │
711
+ └─────────────────────────────────────────┘
712
+ ```
713
+
714
+ ### Data Flow Diagram: skill_use Loading
715
+
716
+ ```
717
+ ┌──────────────────────────────────┐
718
+ │ User: skill_use("git-commits") │
719
+ └────────────┬─────────────────────┘
720
+
721
+ ┌──────────────────────────────────┐
722
+ │ SkillUser Tool (tools/SkillUser) │
723
+ │ - Await ready state │
724
+ │ - Validate skill names │
725
+ └────────────┬─────────────────────┘
726
+
727
+ ┌──────────────────────────────────┐
728
+ │ Registry.controller.get(key) │
729
+ │ - Look up skill in Map │
730
+ │ - Return Skill object with: │
731
+ │ * Name, description │
732
+ │ * Content (markdown) │
733
+ │ * Resource maps (indexed) │
734
+ └────────────┬─────────────────────┘
735
+
736
+ ┌──────────────────────────────────┐
737
+ │ Format Skill for Injection │
738
+ │ - Skill metadata │
739
+ │ - Resource inventory (with MIME) │
740
+ │ - Full content as markdown │
741
+ │ - Base directory context │
742
+ └────────────┬─────────────────────┘
743
+
744
+ ┌──────────────────────────────────┐
745
+ │ Silent Injection Pattern │
746
+ │ - Inject as user message │
747
+ │ - Persists in conversation │
748
+ │ - Returns success summary │
749
+ │ { loaded: [...], notFound: []}│
750
+ └──────────────────────────────────┘
751
+ ```
752
+
753
+ ### Data Flow Diagram: skill_resource Access
754
+
755
+ ```
756
+ ┌──────────────────────────────────────────────┐
757
+ │ User: skill_resource("git-commits", │
758
+ │ "scripts/commit.sh") │
759
+ └──────────────┬───────────────────────────────┘
760
+
761
+ ┌──────────────────────────────────────────────┐
762
+ │ SkillResourceReader Tool │
763
+ │ (tools/SkillResourceReader.ts) │
764
+ │ - Validate skill_name │
765
+ │ - Parse path: "scripts/commit.sh" │
766
+ │ ├─ type = "scripts" │
767
+ │ └─ relative_path = "commit.sh" │
768
+ │ - Assert type is valid (scripts|assets|refs)│
769
+ └──────────────┬───────────────────────────────┘
770
+
771
+ ┌──────────────────────────────────────────────┐
772
+ │ SkillResourceResolver │
773
+ │ - Fetch skill object from registry │
774
+ │ - Look up in skill.scripts Map │
775
+ │ (pre-indexed at parse time) │
776
+ │ - Retrieve: { absolutePath, mimeType } │
777
+ │ │
778
+ │ ★ SECURITY: Only pre-indexed paths exist │
779
+ │ No way to request ../../../etc/passwd │
780
+ └──────────────┬───────────────────────────────┘
781
+
782
+ ┌──────────────────────────────────────────────┐
783
+ │ File I/O (SkillFs abstraction) │
784
+ │ - Read file content from absolutePath │
785
+ │ - Detect MIME type (e.g., text/plain) │
786
+ └──────────────┬───────────────────────────────┘
787
+
788
+ ┌──────────────────────────────────────────────┐
789
+ │ Return Injection Object │
790
+ │ { │
791
+ │ skill_name, │
792
+ │ resource_path, │
793
+ │ resource_mimetype, │
794
+ │ content │
795
+ │ } │
796
+ └──────────────┬───────────────────────────────┘
797
+
798
+ ┌──────────────────────────────────────────────┐
799
+ │ Silent Injection │
800
+ │ - Inject content into message │
801
+ │ - User sees resource inline │
802
+ └──────────────────────────────────────────────┘
803
+ ```
804
+
805
+ ### Key Design Decisions and Their Rationale
806
+
807
+ | Decision | Why | Impact |
808
+ | ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------ |
809
+ | **ReadyStateMachine** | Ensures tools don't race with async registry setup | Tools are guaranteed registry is ready before execution |
810
+ | **Pre-indexed Resources** | Prevents path traversal attacks | Security: only pre-registered paths retrievable |
811
+ | **Factory Pattern (api.ts)** | Centralizes initialization | Easy to test, swap implementations, mock components |
812
+ | **Removed SkillProvider** | Eliminated unnecessary abstraction | Simpler code, direct registry access, easier debugging |
813
+ | **XML Serialization** | Human-readable message injection | Results display nicely formatted in chat context |
814
+ | **Two-phase Init** | Async discovery doesn't block tool registration | Tools available immediately, discovery happens in background |
815
+
816
+ ### Services Layer Breakdown
817
+
818
+ #### SkillRegistry (`src/services/SkillRegistry.ts`)
819
+
820
+ - **Role**: Central skill catalog and discovery engine
821
+ - **Responsibilities**:
822
+ 1. Scan multiple base paths for SKILL.md files (recursive)
823
+ 2. Parse each file's YAML frontmatter (validates schema)
824
+ 3. Index all resources (scripts/assets/references) for safe retrieval
825
+ 4. Register skills in controller.skills Map by toolName
826
+ 5. Coordinate initialization via ReadyStateMachine
827
+ - **Key Methods**:
828
+ - `initialise()`: Async discovery and parsing pipeline
829
+ - `register()`: Parse and store skill files
830
+ - `parseSkill()`: Extract metadata and resource paths
831
+ - **Error Handling**: Malformed skills logged but don't halt discovery
832
+
833
+ #### SkillSearcher (`src/services/SkillSearcher.ts`)
834
+
835
+ - **Role**: Natural language query interpretation and ranking
836
+ - **Responsibilities**:
837
+ 1. Parse queries using search-string library (Gmail syntax)
838
+ 2. Filter: ALL include terms must match (AND logic)
839
+ 3. Exclude: Remove results matching exclude terms
840
+ 4. Rank by relevance (name matches 3×, description 1×, exact +10)
841
+ 5. Generate user-friendly feedback
842
+ - **Scoring Formula**: `(nameMatches × 3) + (descMatches × 1) + exactBonus`
843
+ - **Edge Cases**: Empty queries or "\*" list all skills
844
+
845
+ ### Tools Layer Overview
846
+
847
+ #### SkillFinder (`src/tools/SkillFinder.ts`)
848
+
849
+ - Wraps SkillSearcher with ready-state synchronization
850
+ - Transforms results to SkillFinder schema
851
+ - Returns matched skills + summary metadata
852
+
853
+ #### SkillUser (`src/tools/SkillUser.ts`)
854
+
855
+ - Loads skills into chat context
856
+ - Injects as user message (persists in conversation)
857
+ - Returns { loaded: [...], notFound: [...] }
858
+
859
+ #### SkillResourceReader (`src/tools/SkillResourceReader.ts`)
860
+
861
+ - Safe resource path parsing and validation
862
+ - Pre-indexed path lookup (prevents traversal)
863
+ - Returns injection object with MIME type and content
864
+
865
+ ### Configuration and Path Resolution
866
+
867
+ ```
868
+ Configuration Priority (Last Wins):
869
+ 1. Global: ~/.opencode/skills/ (lowest)
870
+ 2. Project: ./.opencode/skills/ (highest)
871
+
872
+ Why This Order:
873
+ - Users install global skills once
874
+ - Projects override with local versions
875
+ - Same skill name in both → project version wins
876
+ ```
877
+
878
+ ## Contributing
879
+
880
+ Contributions are welcome! When adding features, follow these principles:
881
+
882
+ - **Explain the WHY, not the WHAT**: Code comments should document design decisions, not mechanics
883
+ - **Keep modules focused**: Each file has one primary responsibility
884
+ - **Test async behavior**: Ready state coordination is critical
885
+ - **Document algorithms**: Ranking, parsing, and search logic should have detailed comments
886
+
155
887
  ## Creating Skills
156
888
 
157
889
  Skills follow the [Anthropic Agent Skills Specification](https://github.com/anthropics/skills). Each skill is a directory containing a `SKILL.md` file:
@@ -164,18 +896,28 @@ skills/
164
896
  template.md
165
897
  ```
166
898
 
899
+ ### Skill Structure
900
+
901
+ ```
902
+ my-skill/
903
+ SKILL.md # Required: Skill metadata and instructions
904
+ references/ # Optional: Documentation and guides
905
+ guide.md
906
+ examples.md
907
+ assets/ # Optional: Binary files and templates
908
+ template.html
909
+ logo.png
910
+ scripts/ # Optional: Executable scripts
911
+ setup.sh
912
+ generate.py
913
+ ```
914
+
167
915
  ### SKILL.md Format
168
916
 
169
917
  ```yaml
170
918
  ---
171
919
  name: my-skill
172
920
  description: A brief description of what this skill does (min 20 chars)
173
- license: MIT
174
- allowed-tools:
175
- - bash
176
- - read
177
- metadata:
178
- author: Your Name
179
921
  ---
180
922
  # My Skill
181
923
 
@@ -185,9 +927,28 @@ Instructions for the AI agent when this skill is loaded...
185
927
  **Requirements:**
186
928
 
187
929
  - `name` must match the directory name (lowercase, alphanumeric, hyphens only)
188
- - `description` must be at least 20 characters
930
+ - `description` must be at least 20 characters and should be concise (under 500 characters recommended)
931
+ - Focus on when to use the skill, not what it does
932
+ - Include specific triggering conditions and symptoms
933
+ - Use third person for system prompt injection
934
+ - Example: "Use when designing REST APIs to establish consistent patterns and improve developer experience"
189
935
  - Directory name and frontmatter `name` must match exactly
190
936
 
937
+ ### Resource Types
938
+
939
+ Skill resources are automatically discovered and categorized:
940
+
941
+ - **References** (`references/` directory): Documentation, guides, and reference materials
942
+ - Typically Markdown, plaintext, or HTML files
943
+ - Accessed via `skill_resource` for reading documentation
944
+ - **Assets** (`assets/` directory): Templates, images, and binary files
945
+ - Can include HTML templates, images, configuration files
946
+ - Useful for providing templates and examples to the AI
947
+ - **Scripts** (`scripts/` directory): Executable scripts that perform actions
948
+ - Shell scripts (.sh), Python scripts (.py), or other executables
949
+ - Executed via `skill_exec` with optional arguments
950
+ - Useful for automation, code generation, or complex operations
951
+
191
952
  ## Considerations
192
953
 
193
954
  - Skills are discovered at plugin initialization (requires restart to reload)