opencode-dynamic-skills 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/README.zh-CN.md +180 -0
- package/dist/api.d.ts +96 -0
- package/dist/commands/SlashCommand.d.ts +26 -0
- package/dist/commands/SlashCommand.test.d.ts +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +23805 -0
- package/dist/lib/Identifiers.d.ts +17 -0
- package/dist/lib/Indentifiers.test.d.ts +1 -0
- package/dist/lib/OpenCodeChat.d.ts +5 -0
- package/dist/lib/OpenCodeChat.test.d.ts +1 -0
- package/dist/lib/ReadyStateMachine.d.ts +36 -0
- package/dist/lib/SkillFs.d.ts +30 -0
- package/dist/lib/createPromptRenderer.d.ts +52 -0
- package/dist/lib/createPromptRenderer.test.d.ts +9 -0
- package/dist/lib/getModelFormat.d.ts +35 -0
- package/dist/lib/renderers/JsonPromptRenderer.d.ts +8 -0
- package/dist/lib/renderers/JsonPromptRenderer.test.d.ts +10 -0
- package/dist/lib/renderers/MdPromptRenderer.d.ts +18 -0
- package/dist/lib/renderers/MdPromptRenderer.test.d.ts +11 -0
- package/dist/lib/renderers/XmlPromptRenderer.d.ts +9 -0
- package/dist/lib/renderers/XmlPromptRenderer.test.d.ts +11 -0
- package/dist/lib/renderers/resourceMapToArray.d.ts +2 -0
- package/dist/lib/xml.d.ts +1 -0
- package/dist/mocks.d.ts +32 -0
- package/dist/mocks.skillfs.d.ts +1 -0
- package/dist/services/MessageModelIdAccountant.d.ts +22 -0
- package/dist/services/SkillRegistry.d.ts +9 -0
- package/dist/services/SkillRegistry.test.d.ts +1 -0
- package/dist/services/SkillResourceResolver.d.ts +20 -0
- package/dist/services/SkillResourceResolver.test.d.ts +1 -0
- package/dist/services/SkillSearcher.d.ts +77 -0
- package/dist/services/SkillSearcher.functions.test.d.ts +1 -0
- package/dist/services/SkillSearcher.test.d.ts +1 -0
- package/dist/services/logger.d.ts +2 -0
- package/dist/tools/SkillFinder.d.ts +46 -0
- package/dist/tools/SkillRecommender.d.ts +25 -0
- package/dist/tools/SkillRecommender.test.d.ts +1 -0
- package/dist/tools/SkillResourceReader.d.ts +43 -0
- package/dist/tools/SkillUser.d.ts +5 -0
- package/dist/types.d.ts +211 -0
- package/package.json +56 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a query string to match against toolName paths
|
|
3
|
+
* Converts slashes and hyphens to underscores for prefix matching
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizePathQuery(query: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Strip the "skills_" prefix from toolName for user-facing matching
|
|
8
|
+
*/
|
|
9
|
+
export declare function stripSkillsPrefix(toolName: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generate tool name from skill path
|
|
12
|
+
* Examples:
|
|
13
|
+
* skills/brand-guidelines/SKILL.md → skills_brand_guidelines
|
|
14
|
+
* skills/document-skills/docx/SKILL.md → skills_document_skills_docx
|
|
15
|
+
* skills/image-processing/SKILL.md → skills_image_processing
|
|
16
|
+
*/
|
|
17
|
+
export declare function toolName(skillPath: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReadyStateMachine - Async Initialization State Coordinator
|
|
3
|
+
*
|
|
4
|
+
* WHY: Tools need to wait for the skill registry to finish async discovery and
|
|
5
|
+
* parsing before they can execute. This state machine coordinates that initialization
|
|
6
|
+
* without requiring callers to manage Promise chains manually.
|
|
7
|
+
*
|
|
8
|
+
* DESIGN: Implements a simple state machine with watchers pattern for notification.
|
|
9
|
+
* State transitions: idle → loading → (ready|error)
|
|
10
|
+
*
|
|
11
|
+
* CONTRACT:
|
|
12
|
+
* - setStatus(): update internal state and notify all watchers
|
|
13
|
+
* - watchReady(callback): subscribe to state changes, returns unsubscribe function
|
|
14
|
+
* - whenReady(): blocking async method that resolves when state is ready, rejects if error
|
|
15
|
+
*
|
|
16
|
+
* WHY NOT A SIMPLE PROMISE:
|
|
17
|
+
* - Promise resolves once, but multiple tools may call whenReady() at different times
|
|
18
|
+
* - This pattern allows multiple concurrent waiters without race conditions
|
|
19
|
+
* - Supports reversible state (could transition back to loading if needed)
|
|
20
|
+
*
|
|
21
|
+
* EXAMPLE:
|
|
22
|
+
* const ready = createReadyStateMachine();
|
|
23
|
+
* ready.setStatus('loading');
|
|
24
|
+
* // ... do async work ...
|
|
25
|
+
* ready.setStatus('ready');
|
|
26
|
+
*
|
|
27
|
+
* // Tool execution:
|
|
28
|
+
* await ready.whenReady(); // blocks until ready
|
|
29
|
+
* // ... now safe to execute ...
|
|
30
|
+
*/
|
|
31
|
+
export type ReadyStateMachine = ReturnType<typeof createReadyStateMachine>;
|
|
32
|
+
export declare function createReadyStateMachine(): {
|
|
33
|
+
setStatus: (newStatus: "idle" | "loading" | "ready" | "error") => void;
|
|
34
|
+
watchReady: (callback: (status: "idle" | "loading" | "ready" | "error") => void) => () => void;
|
|
35
|
+
whenReady: () => Promise<void>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillFs - Abstract filesystem access for skills
|
|
3
|
+
*
|
|
4
|
+
* This module encapsulates all filesystem operations related to skill discovery and loading.
|
|
5
|
+
* It provides a mockable interface that works across different Node.js implementations,
|
|
6
|
+
* enabling unit tests to stub filesystem operations without complex mocking libraries.
|
|
7
|
+
*
|
|
8
|
+
* Each function is designed as a pure export to be easily replaced in test environments
|
|
9
|
+
* (e.g., via mocking FS access in test suites).
|
|
10
|
+
*/
|
|
11
|
+
export declare const readSkillFile: (path: string) => Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* List all files in a skill subdirectory (e.g., scripts/, resources/)
|
|
14
|
+
* Returns a flat array of absolute file paths
|
|
15
|
+
*
|
|
16
|
+
* @param skillPath - Base path to the skill directory
|
|
17
|
+
* @param subdirectory - Subdirectory to scan (e.g., 'scripts', 'resources')
|
|
18
|
+
* @returns Array of absolute file paths
|
|
19
|
+
*/
|
|
20
|
+
export declare const listSkillFiles: (skillPath: string, subdirectory: string) => string[];
|
|
21
|
+
export declare const findSkillPaths: (basePath: string) => Promise<string[]>;
|
|
22
|
+
export declare const doesPathExist: (path: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Detect MIME type from file extension
|
|
25
|
+
* Used for skill resources to identify content type
|
|
26
|
+
*
|
|
27
|
+
* @param filePath - Path to the file
|
|
28
|
+
* @returns MIME type string
|
|
29
|
+
*/
|
|
30
|
+
export declare const detectMimeType: (filePath: string) => string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Renderer Factory
|
|
3
|
+
*
|
|
4
|
+
* WHY: Factory pattern centralizes renderer instantiation and makes it easy to:
|
|
5
|
+
* - Add new renderer types in the future
|
|
6
|
+
* - Test with different renderers
|
|
7
|
+
* - Handle invalid formats gracefully
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Create a prompt renderer for the specified format
|
|
11
|
+
*
|
|
12
|
+
* @param format The desired format: 'json' | 'xml' | 'md'
|
|
13
|
+
* @returns A PromptRenderer instance for the specified format
|
|
14
|
+
* @throws Error if format is not recognized
|
|
15
|
+
*/
|
|
16
|
+
export declare function createPromptRenderer(): {
|
|
17
|
+
getFormatter: (format: "json" | "xml" | "md") => (args: {
|
|
18
|
+
data: import("../types").Skill;
|
|
19
|
+
type: "Skill";
|
|
20
|
+
} | {
|
|
21
|
+
data: {
|
|
22
|
+
skill_name: string;
|
|
23
|
+
resource_path: string;
|
|
24
|
+
resource_mimetype: string;
|
|
25
|
+
content: string;
|
|
26
|
+
};
|
|
27
|
+
type: "SkillResource";
|
|
28
|
+
} | {
|
|
29
|
+
data: {
|
|
30
|
+
mode?: "search" | "recommend";
|
|
31
|
+
query: string | string[];
|
|
32
|
+
skills: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
}>;
|
|
36
|
+
summary: {
|
|
37
|
+
total: number;
|
|
38
|
+
matches: number;
|
|
39
|
+
feedback: string;
|
|
40
|
+
};
|
|
41
|
+
recommendations?: Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
score: number;
|
|
45
|
+
reason: string;
|
|
46
|
+
}>;
|
|
47
|
+
guidance?: string;
|
|
48
|
+
debug?: import("../types").SkillRegistryDebugInfo;
|
|
49
|
+
};
|
|
50
|
+
type: "SkillSearchResults";
|
|
51
|
+
}) => string;
|
|
52
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Format Resolver - Select renderer based on active LLM model
|
|
3
|
+
*
|
|
4
|
+
* WHY: Different LLM models have different preferences and strengths:
|
|
5
|
+
* - Claude models: optimized for XML, prefer structured XML injection
|
|
6
|
+
* - GPT models: strong JSON parsing, prefer JSON-formatted data
|
|
7
|
+
* - Other models: may benefit from markdown readability
|
|
8
|
+
*
|
|
9
|
+
* This function detects the active model and selects the configured
|
|
10
|
+
* format preference for that model, falling back to progressively more
|
|
11
|
+
* generic model patterns.
|
|
12
|
+
*
|
|
13
|
+
* HOW IT WORKS:
|
|
14
|
+
* 1. Query the current session via client.session.message()
|
|
15
|
+
* 2. Extract the modelID from the response (e.g., "anthropic-claude-3-5-sonnet")
|
|
16
|
+
* 3. Try matching in order:
|
|
17
|
+
* - Full model ID (e.g., "anthropic-claude-3-5-sonnet")
|
|
18
|
+
* - Generic model pattern (e.g., "claude-3-5-sonnet")
|
|
19
|
+
* 4. If no match, fall back to promptRenderer default
|
|
20
|
+
*/
|
|
21
|
+
import type { PluginConfig } from '../types';
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the appropriate prompt format for the current model
|
|
24
|
+
*
|
|
25
|
+
* @param args An object containing:
|
|
26
|
+
* - modelId?: The identifier of the active model (e.g., "claude-3-5-sonnet")
|
|
27
|
+
* - providerId?: The identifier of the model provider (e.g., "anthropic")
|
|
28
|
+
* - config: The plugin configuration (has promptRenderer and modelRenderers)
|
|
29
|
+
* @returns The format to use: 'json' | 'xml' | 'md'
|
|
30
|
+
*/
|
|
31
|
+
export declare function getModelFormat(args: {
|
|
32
|
+
modelId?: string;
|
|
33
|
+
providerId?: string;
|
|
34
|
+
config: PluginConfig;
|
|
35
|
+
}): 'json' | 'xml' | 'md';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JsonPromptRenderer - Format objects as JSON
|
|
3
|
+
*
|
|
4
|
+
* WHY: Some LLM models (especially GPT family) have strong JSON parsing
|
|
5
|
+
* and prefer structured JSON data over XML for reliability and clarity.
|
|
6
|
+
*/
|
|
7
|
+
import type { PromptRenderer } from '../../types';
|
|
8
|
+
export declare const createJsonPromptRenderer: () => PromptRenderer;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JsonPromptRenderer Tests
|
|
3
|
+
*
|
|
4
|
+
* Test coverage for real use cases:
|
|
5
|
+
* - Rendering Skill objects for prompt injection
|
|
6
|
+
* - Rendering SkillResource objects (file content)
|
|
7
|
+
* - Rendering SkillSearchResults objects (search results)
|
|
8
|
+
* - Proper JSON formatting with indentation
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MdPromptRenderer - Format objects as human-readable Markdown
|
|
3
|
+
*
|
|
4
|
+
* WHY: Markdown provides human-readable formatting that works well for:
|
|
5
|
+
* - Models that benefit from visual structure and readability
|
|
6
|
+
* - Debugging and development (easier to read in logs)
|
|
7
|
+
* - Accessibility and presentation
|
|
8
|
+
*
|
|
9
|
+
* STRUCTURE:
|
|
10
|
+
* - Top-level keys → H3 headings (### key)
|
|
11
|
+
* - Nested objects → H4 headings (#### key) with increased indentation
|
|
12
|
+
* - Leaf nodes → nested bullet list items with emphasis: - **key**: *value*
|
|
13
|
+
* - Arrays → nested bullets under parent key
|
|
14
|
+
* - Special characters → HTML-escaped (<, >, &)
|
|
15
|
+
* - Skill content → appended after --- separator with ### Content heading
|
|
16
|
+
*/
|
|
17
|
+
import type { PromptRenderer } from '../../types';
|
|
18
|
+
export declare const createMdPromptRenderer: () => PromptRenderer;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MdPromptRenderer Tests
|
|
3
|
+
*
|
|
4
|
+
* Test coverage for real use cases:
|
|
5
|
+
* - Rendering Skill objects with metadata, references, scripts, assets
|
|
6
|
+
* - Rendering SkillResource objects (file content)
|
|
7
|
+
* - Rendering SkillSearchResults objects
|
|
8
|
+
* - HTML character escaping for security
|
|
9
|
+
* - Special values handling (null, undefined)
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XmlPromptRenderer - Format objects as XML (current default)
|
|
3
|
+
*
|
|
4
|
+
* WHY: Claude models are trained extensively on XML and prefer structured
|
|
5
|
+
* XML injection for skill metadata and search results. This maintains the
|
|
6
|
+
* current behavior as the default and recommended format for Claude models.
|
|
7
|
+
*/
|
|
8
|
+
import type { PromptRenderer } from '../../types';
|
|
9
|
+
export declare const createXmlPromptRenderer: () => PromptRenderer;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XmlPromptRenderer Tests
|
|
3
|
+
*
|
|
4
|
+
* Test coverage for real use cases:
|
|
5
|
+
* - Rendering Skill objects as XML with resource maps converted to arrays
|
|
6
|
+
* - Rendering SkillResource objects (file content)
|
|
7
|
+
* - Rendering SkillSearchResults objects
|
|
8
|
+
* - XML character escaping for security
|
|
9
|
+
* - Resource map serialization
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function jsonToXml(json: object, rootElement?: string): string;
|
package/dist/mocks.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Skill, SkillRegistry, SkillRegistryController, SkillSearchResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock Skill object with sensible defaults
|
|
4
|
+
* @param overrides Partial skill properties to override defaults
|
|
5
|
+
* @returns A complete Skill object
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMockSkill(overrides?: Partial<Skill>): Skill;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a mock SkillRegistryController
|
|
10
|
+
* @param skills Array of skills to include in the registry
|
|
11
|
+
* @returns A mock registry controller
|
|
12
|
+
*/
|
|
13
|
+
export declare function createMockRegistryController(skills?: Skill[]): SkillRegistryController;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a mock SkillProvider with a registry controller
|
|
16
|
+
* @param skills Array of skills to include in the provider
|
|
17
|
+
* @returns A mock skill provider
|
|
18
|
+
*/
|
|
19
|
+
export declare function createMockRegistry(skills?: Skill[]): Promise<SkillRegistry>;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a mock SkillSearchResult
|
|
22
|
+
* @param overrides Partial search result properties to override defaults
|
|
23
|
+
* @returns A mock search result
|
|
24
|
+
*/
|
|
25
|
+
export declare function mockSearchResult(overrides?: Partial<SkillSearchResult>): SkillSearchResult;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a fully configured mock search result with skill matches and metadata
|
|
28
|
+
* @param skills Array of skill matches to include in the result
|
|
29
|
+
* @param overrides Additional overrides for search result properties
|
|
30
|
+
* @returns A complete search result with proper structure
|
|
31
|
+
*/
|
|
32
|
+
export declare function mockFullSearchResult(skills?: Skill[], overrides?: Partial<SkillSearchResult>): SkillSearchResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare function createMessageModelIdAccountant(): {
|
|
2
|
+
reset: () => void;
|
|
3
|
+
track: (info: {
|
|
4
|
+
sessionID: string;
|
|
5
|
+
messageID: string;
|
|
6
|
+
modelID: string;
|
|
7
|
+
providerID: string;
|
|
8
|
+
}) => void;
|
|
9
|
+
untrackMessage: (args: {
|
|
10
|
+
messageID: string;
|
|
11
|
+
sessionID: string;
|
|
12
|
+
}) => void;
|
|
13
|
+
untrackSession: (sessionID: string) => void;
|
|
14
|
+
getModelInfo: (args: {
|
|
15
|
+
messageID: string;
|
|
16
|
+
sessionID: string;
|
|
17
|
+
}) => {
|
|
18
|
+
modelID: string;
|
|
19
|
+
providerID: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
};
|
|
22
|
+
export { createMessageModelIdAccountant };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PluginConfig, PluginLogger, SkillRegistry, SkillRegistryController, SkillResourceMap } from '../types';
|
|
2
|
+
export declare const stripTrailingPathSeparators: (path: string) => string;
|
|
3
|
+
export declare const suggestSkillsDirectoryPath: (path: string) => string | null;
|
|
4
|
+
export declare function createSkillRegistryController(): SkillRegistryController;
|
|
5
|
+
/**
|
|
6
|
+
* SkillRegistry manages skill discovery and parsing
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSkillRegistry(config: PluginConfig, logger: PluginLogger): Promise<SkillRegistry>;
|
|
9
|
+
export declare function createSkillResourceMap(skillPath: string, filePaths: string[]): SkillResourceMap;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SkillRegistry } from '../types';
|
|
2
|
+
export declare function normalizeSkillResourcePath(inputPath: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Skill resources are mapped on startup as a dictionary of relative paths to resource metadata.
|
|
5
|
+
*
|
|
6
|
+
* This resolver uses that mapping to solve several things:
|
|
7
|
+
*
|
|
8
|
+
* - locate and read the actual resource files.
|
|
9
|
+
* - ensure the requested path isn't outside the skill directory (security).
|
|
10
|
+
* - return the content and mime type of the resource.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSkillResourceResolver(provider: SkillRegistry): (args: {
|
|
13
|
+
skill_name: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
relative_path: string;
|
|
16
|
+
}) => Promise<{
|
|
17
|
+
absolute_path: string;
|
|
18
|
+
content: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillSearcher - Natural Language Query Parser and Skill Ranking Engine
|
|
3
|
+
*
|
|
4
|
+
* WHY: Users expect Gmail-style search syntax (quoted phrases, negation, multiple terms).
|
|
5
|
+
* This module abstracts the search-string library and implements skill-specific ranking.
|
|
6
|
+
*
|
|
7
|
+
* SEARCH STRATEGY:
|
|
8
|
+
* 1. Parse user query into include/exclude terms (using search-string for Gmail syntax)
|
|
9
|
+
* 2. Filter: ALL include terms must appear in skill (name, description, or toolName)
|
|
10
|
+
* 3. Exclude: Remove matches that contain any exclude term
|
|
11
|
+
* 4. Rank: Score by match location (name matches weighted 3×, description 1×)
|
|
12
|
+
* 5. Sort: By total score (desc) → name matches (desc) → alphabetically
|
|
13
|
+
*
|
|
14
|
+
* QUERY INTERPRETATION:
|
|
15
|
+
* - "git commit" → include: [git, commit]
|
|
16
|
+
* - "git -rebase" → include: [git], exclude: [rebase]
|
|
17
|
+
* - '"git rebase"' → include: [git rebase] (phrase, no split)
|
|
18
|
+
* - "*" or empty → list all skills
|
|
19
|
+
*
|
|
20
|
+
* RANKING ALGORITHM:
|
|
21
|
+
* - Name match: +3 per term found in skill name
|
|
22
|
+
* - Description match: +1 per term found in description
|
|
23
|
+
* - Exact name match: +10 bonus
|
|
24
|
+
* - Final sort: highest score first, tie-break by name matches, then alphabetical
|
|
25
|
+
*
|
|
26
|
+
* WHY SEARCH-STRING: Handles complex syntax edge cases (quotes, escaped chars)
|
|
27
|
+
* without rolling our own parser. Proven in production systems (Gmail, etc).
|
|
28
|
+
*
|
|
29
|
+
* WHY NO PARTIAL MATCHING: "python" won't match "python-docstring" because we check
|
|
30
|
+
* term-by-term inclusion, not word-level matching. This matches user expectations
|
|
31
|
+
* (they searched for "python", not "python-" or "pythonish").
|
|
32
|
+
*/
|
|
33
|
+
import type { Skill, ParsedSkillQuery, SkillRank, SkillRegistryController, SkillSearcher } from '../types';
|
|
34
|
+
/**
|
|
35
|
+
* Parse a user query into structured search terms
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseQuery(queryString: string | string[]): ParsedSkillQuery;
|
|
38
|
+
/**
|
|
39
|
+
* Calculate ranking score for a skill against query terms
|
|
40
|
+
*
|
|
41
|
+
* SCORING FORMULA:
|
|
42
|
+
* - nameMatches: number of include terms found in skill name (0 or more)
|
|
43
|
+
* - descMatches: number of include terms found in description (0 or more)
|
|
44
|
+
* - totalScore = (nameMatches × 3) + (descMatches × 1) + exactBonus
|
|
45
|
+
* - exactBonus: +10 if query is single term and equals skill name exactly
|
|
46
|
+
*
|
|
47
|
+
* WHY THIS WEIGHTING:
|
|
48
|
+
* - Skill name is a strong signal (3× weight) because it's the identifier
|
|
49
|
+
* - Description matches are weaker (1× weight) because skill names vary
|
|
50
|
+
* - Exact match bonus breaks ties and promotes direct matches to top
|
|
51
|
+
*
|
|
52
|
+
* EXAMPLE:
|
|
53
|
+
* - Query: "git" | Skill name: "writing-git-commits" | Score: 3 (name match)
|
|
54
|
+
* - Query: "git" | Skill name: "git" | Score: 13 (name match + exact bonus)
|
|
55
|
+
* - Query: "revert changes" | Skill name: "git-revert" | Description: "Revert..."
|
|
56
|
+
* | Score: 4 (1 name match + 1 desc match + 2 bonus doesn't apply)
|
|
57
|
+
*/
|
|
58
|
+
export declare function rankSkill(skill: Skill, includeTerms: string[]): SkillRank;
|
|
59
|
+
/**
|
|
60
|
+
* Filter out skills matching exclusion terms
|
|
61
|
+
*/
|
|
62
|
+
export declare function shouldIncludeSkill(skill: Skill, excludeTerms: string[]): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Generate user-friendly feedback about query interpretation
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateFeedback(query: ParsedSkillQuery, matchCount: number): string;
|
|
67
|
+
/**
|
|
68
|
+
* SkillSearcher - Natural Language Query Parser for Skills
|
|
69
|
+
*
|
|
70
|
+
* Provides clean abstraction over search-string library for skill discovery.
|
|
71
|
+
* Handles query parsing, ranking, and result formatting with support for:
|
|
72
|
+
* - Natural syntax (Gmail-style)
|
|
73
|
+
* - Negation (-term)
|
|
74
|
+
* - Quoted phrases
|
|
75
|
+
* - Multiple search terms (AND logic)
|
|
76
|
+
*/
|
|
77
|
+
export declare function createSkillSearcher(registry: SkillRegistryController): SkillSearcher;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillFinder Tool - Natural Language Skill Discovery
|
|
3
|
+
*
|
|
4
|
+
* WHY: This tool wraps SkillSearcher with ready-state synchronization.
|
|
5
|
+
* Users call skill_find("git commits") to search for relevant skills.
|
|
6
|
+
* The tool must block until the registry is fully initialized before searching.
|
|
7
|
+
*
|
|
8
|
+
* CRITICAL PATTERN: `await provider.controller.ready.whenReady()`
|
|
9
|
+
* This line ensures that skill discovery has completed before we attempt to
|
|
10
|
+
* search. Without this, early calls would search an empty registry.
|
|
11
|
+
*
|
|
12
|
+
* RETURN VALUE: Object with:
|
|
13
|
+
* - query: original query for user reference
|
|
14
|
+
* - skills: matched skills with toolName and description
|
|
15
|
+
* - summary: metadata (total skills, matches found, feedback message)
|
|
16
|
+
* - debug: registry debug info (only if enabled in config)
|
|
17
|
+
*
|
|
18
|
+
* TOOL REGISTRATION: This factory is called in api.ts like:
|
|
19
|
+
* findSkills: createSkillFinder(registry)
|
|
20
|
+
* Which returns an async function that OpenCode registers as a tool.
|
|
21
|
+
*
|
|
22
|
+
* FEEDBACK: The feedback message explains the query interpretation to the user:
|
|
23
|
+
* "Searching for: **git commit** | Found 3 matches"
|
|
24
|
+
*
|
|
25
|
+
* @param provider SkillRegistry instance (must be initialized first)
|
|
26
|
+
* @returns Async function callable by OpenCode as skill_find tool
|
|
27
|
+
*/
|
|
28
|
+
import type { SkillRegistry } from '../types';
|
|
29
|
+
/**
|
|
30
|
+
* Creates a tool function that searches for skills
|
|
31
|
+
*/
|
|
32
|
+
export declare function createSkillFinder(provider: SkillRegistry): (args: {
|
|
33
|
+
query: string | string[];
|
|
34
|
+
}) => Promise<{
|
|
35
|
+
query: string | string[];
|
|
36
|
+
skills: {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}[];
|
|
40
|
+
summary: {
|
|
41
|
+
total: number;
|
|
42
|
+
matches: number;
|
|
43
|
+
feedback: string;
|
|
44
|
+
};
|
|
45
|
+
debug: import("../types").SkillRegistryDebugInfo | undefined;
|
|
46
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SkillRegistry } from '../types';
|
|
2
|
+
export declare function createSkillRecommender(provider: SkillRegistry): (args: {
|
|
3
|
+
task: string;
|
|
4
|
+
limit?: number;
|
|
5
|
+
}) => Promise<{
|
|
6
|
+
mode: "recommend";
|
|
7
|
+
query: string;
|
|
8
|
+
skills: {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}[];
|
|
12
|
+
recommendations: {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
score: number;
|
|
16
|
+
reason: string;
|
|
17
|
+
}[];
|
|
18
|
+
guidance: string;
|
|
19
|
+
summary: {
|
|
20
|
+
total: number;
|
|
21
|
+
matches: number;
|
|
22
|
+
feedback: string;
|
|
23
|
+
};
|
|
24
|
+
debug: import("../types").SkillRegistryDebugInfo | undefined;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillResourceReader Tool - Safe Skill Resource Access
|
|
3
|
+
*
|
|
4
|
+
* WHY: Skills contain resources (scripts, assets, references) that need to be
|
|
5
|
+
* retrieved safely. This tool:
|
|
6
|
+
* 1. Validates the resource type (scripts|assets|references)
|
|
7
|
+
* 2. Resolves the path safely (prevents path traversal attacks)
|
|
8
|
+
* 3. Reads the file content and returns it for injection
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL SECURITY: Resources are pre-indexed at parse time. This tool can only
|
|
11
|
+
* retrieve resources that were explicitly registered in the skill's resource maps.
|
|
12
|
+
* It cannot request arbitrary paths like "../../etc/passwd".
|
|
13
|
+
*
|
|
14
|
+
* PATH PARSING: Input path format is "type/relative/path"
|
|
15
|
+
* - type: one of 'scripts', 'assets', 'references' (validated)
|
|
16
|
+
* - relative/path: path within that type's directory
|
|
17
|
+
*
|
|
18
|
+
* EXAMPLE:
|
|
19
|
+
* - Args: { skill_name: 'git-commit', relative_path: 'scripts/commit.sh' }
|
|
20
|
+
* - Type: 'scripts', path: 'commit.sh'
|
|
21
|
+
* - Resolves: skill.scripts.get('commit.sh') → reads file content
|
|
22
|
+
*
|
|
23
|
+
* RETURN VALUE: Object with:
|
|
24
|
+
* - injection: contains skill_name, resource_path, MIME type, and content
|
|
25
|
+
* The caller (index.ts) injects this content into the message body
|
|
26
|
+
*
|
|
27
|
+
* READY STATE: Must wait for registry initialization before accessing skills
|
|
28
|
+
*
|
|
29
|
+
* @param provider SkillRegistry instance (must be initialized first)
|
|
30
|
+
* @returns Async function callable by OpenCode as skill_resource tool
|
|
31
|
+
*/
|
|
32
|
+
import { SkillRegistry } from '../types';
|
|
33
|
+
export declare function createSkillResourceReader(provider: SkillRegistry): (args: {
|
|
34
|
+
skill_name: string;
|
|
35
|
+
relative_path: string;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
injection: {
|
|
38
|
+
skill_name: string;
|
|
39
|
+
resource_path: string;
|
|
40
|
+
resource_mimetype: string;
|
|
41
|
+
content: string;
|
|
42
|
+
};
|
|
43
|
+
}>;
|