@fro.bot/systematic 1.5.0 → 1.7.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 +20 -0
- package/README.md +258 -56
- package/dist/cli.js +1 -1
- package/dist/{index-kjhs9jeg.js → index-yxbcy3s7.js} +194 -45
- package/dist/index.js +129 -33
- package/dist/lib/agents.d.ts +22 -0
- package/dist/lib/commands.d.ts +6 -0
- package/dist/lib/converter.d.ts +1 -1
- package/dist/lib/frontmatter.d.ts +4 -0
- package/dist/lib/skill-loader.d.ts +6 -0
- package/dist/lib/skill-tool.d.ts +6 -0
- package/dist/lib/skills.d.ts +20 -1
- package/dist/lib/validation.d.ts +25 -0
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Marcus R. Brown <git@mrbro.dev>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,122 +1,324 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="./assets/banner.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="./assets/banner.svg">
|
|
6
|
+
<img alt="Systematic - Structured Engineering Workflows for OpenCode" src="./assets/banner.svg" width="100%">
|
|
7
|
+
</picture>
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
<br><br>
|
|
10
|
+
|
|
11
|
+
[](https://github.com/marcusrbrown/systematic/actions)
|
|
12
|
+
[](https://www.npmjs.com/package/@fro.bot/systematic)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
<br>
|
|
16
|
+
|
|
17
|
+
**[Overview](#overview)** · **[Quick Start](#quick-start)** · **[Skills](#skills)** · **[Agents](#agents)** · **[Commands](#commands)** · **[Development](#development)**
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
Systematic is an [OpenCode](https://opencode.ai/) plugin that transforms your AI assistant into a **disciplined engineering collaborator**. It provides battle-tested workflows adapted from the [Compound Engineering Plugin (CEP)](https://github.com/EveryInc/compound-engineering-plugin) for Claude Code.
|
|
26
|
+
|
|
27
|
+
### Why Systematic?
|
|
28
|
+
|
|
29
|
+
Most AI coding assistants respond to requests without structure or methodology. This leads to inconsistent outputs, missed requirements, and wasted iterations.
|
|
30
|
+
|
|
31
|
+
**Systematic solves this with structured workflows.** The plugin injects proven engineering processes directly into your AI's system prompt, enabling it to:
|
|
32
|
+
|
|
33
|
+
- **Brainstorm systematically** before jumping to implementation
|
|
34
|
+
- **Plan with rigor** using multi-phase workflows
|
|
35
|
+
- **Review code architecturally** with specialized agents
|
|
36
|
+
- **Follow consistent patterns** across your entire team
|
|
37
|
+
|
|
38
|
+
### Key Features
|
|
39
|
+
|
|
40
|
+
- **🧠 Structured Skills** — Pre-built workflows for brainstorming, planning, and code review
|
|
41
|
+
- **🤖 Specialized Agents** — Purpose-built subagents for architecture, security, and performance
|
|
42
|
+
- **⚡ Zero Configuration** — Works immediately after installation via config hooks
|
|
43
|
+
- **🔧 Extensible** — Add project-specific skills and commands alongside bundled ones
|
|
44
|
+
- **📦 Batteries Included** — Skills, agents, and commands ship with the npm package
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Prerequisites
|
|
49
|
+
|
|
50
|
+
- [OpenCode](https://opencode.ai/) installed and configured
|
|
51
|
+
- Node.js 18+ or Bun runtime
|
|
52
|
+
|
|
53
|
+
### Installation
|
|
54
|
+
|
|
55
|
+
Install the plugin via npm:
|
|
6
56
|
|
|
7
57
|
```bash
|
|
8
58
|
npm install @fro.bot/systematic
|
|
9
59
|
```
|
|
10
60
|
|
|
11
|
-
Add to your OpenCode
|
|
61
|
+
Add it to your OpenCode configuration (`~/.config/opencode/opencode.json`):
|
|
12
62
|
|
|
13
63
|
```json
|
|
14
64
|
{
|
|
15
|
-
"
|
|
65
|
+
"plugins": ["@fro.bot/systematic"]
|
|
16
66
|
}
|
|
17
67
|
```
|
|
18
68
|
|
|
19
|
-
|
|
69
|
+
That's it. Restart OpenCode and the plugin's skills, agents, and commands are available immediately.
|
|
70
|
+
|
|
71
|
+
> [!NOTE]
|
|
72
|
+
> Systematic uses OpenCode's `config` hook to automatically register all bundled content. No manual file copying required.
|
|
73
|
+
|
|
74
|
+
### Verify Installation
|
|
75
|
+
|
|
76
|
+
In any OpenCode conversation, type:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
/systematic:using-systematic
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If the skill loads and displays usage instructions, the plugin is working correctly.
|
|
20
83
|
|
|
21
|
-
|
|
84
|
+
## Skills
|
|
22
85
|
|
|
23
|
-
|
|
86
|
+
Skills are structured workflows that guide the AI through systematic engineering processes. They're loaded via the `systematic_skill` tool.
|
|
24
87
|
|
|
25
88
|
| Skill | Description |
|
|
26
89
|
|-------|-------------|
|
|
27
|
-
| `using-systematic` | Bootstrap skill
|
|
28
|
-
| `brainstorming` | Collaborative design workflow |
|
|
29
|
-
| `agent-browser` | Browser automation
|
|
30
|
-
| `agent-native-architecture` | Design systems
|
|
31
|
-
| `compound-docs` |
|
|
32
|
-
| `create-agent-skills` |
|
|
33
|
-
| `file-todos` |
|
|
34
|
-
| `git-worktree` |
|
|
90
|
+
| `using-systematic` | Bootstrap skill — teaches the AI how to discover and use other skills |
|
|
91
|
+
| `brainstorming` | Collaborative design workflow for exploring ideas before planning |
|
|
92
|
+
| `agent-browser` | Browser automation using Vercel's agent-browser CLI |
|
|
93
|
+
| `agent-native-architecture` | Design systems where AI agents are first-class citizens |
|
|
94
|
+
| `compound-docs` | Capture solved problems as categorized documentation |
|
|
95
|
+
| `create-agent-skills` | Expert guidance for writing and refining skills |
|
|
96
|
+
| `file-todos` | File-based todo tracking with status and dependency management |
|
|
97
|
+
| `git-worktree` | Manage git worktrees for isolated parallel development |
|
|
35
98
|
|
|
36
|
-
###
|
|
99
|
+
### How Skills Work
|
|
37
100
|
|
|
38
|
-
|
|
101
|
+
Skills are Markdown files with YAML frontmatter. When loaded, their content is injected into the conversation, guiding the AI's behavior:
|
|
39
102
|
|
|
40
|
-
|
|
103
|
+
```markdown
|
|
104
|
+
---
|
|
105
|
+
name: brainstorming
|
|
106
|
+
description: This skill should be used before implementing features...
|
|
107
|
+
---
|
|
41
108
|
|
|
42
|
-
|
|
43
|
-
- `/workflows:compound` - Build compound documentation
|
|
44
|
-
- `/workflows:plan` - Create implementation plans
|
|
45
|
-
- `/workflows:review` - Run code review with agents
|
|
46
|
-
- `/workflows:work` - Execute planned work
|
|
109
|
+
# Brainstorming
|
|
47
110
|
|
|
48
|
-
|
|
111
|
+
This skill provides detailed process knowledge for effective brainstorming...
|
|
112
|
+
```
|
|
49
113
|
|
|
50
|
-
|
|
51
|
-
- `/create-agent-skill` - Create a new skill
|
|
52
|
-
- `/deepen-plan` - Add detail to existing plans
|
|
53
|
-
- `/lfg` - Let's go - start working immediately
|
|
114
|
+
The AI is instructed to invoke skills **before** taking action — even with a 1% chance a skill might apply.
|
|
54
115
|
|
|
55
|
-
|
|
116
|
+
## Agents
|
|
56
117
|
|
|
57
|
-
|
|
118
|
+
Agents are specialized subagents with pre-configured prompts and expertise. They're registered automatically via the config hook.
|
|
58
119
|
|
|
59
|
-
|
|
120
|
+
### Review Agents
|
|
60
121
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
122
|
+
| Agent | Purpose |
|
|
123
|
+
|-------|---------|
|
|
124
|
+
| `architecture-strategist` | Analyze code changes from an architectural perspective |
|
|
125
|
+
| `security-sentinel` | Security audits, vulnerability assessment, OWASP compliance |
|
|
126
|
+
| `code-simplicity-reviewer` | Final review pass for simplicity and YAGNI principles |
|
|
127
|
+
| `pattern-recognition-specialist` | Detect design patterns, anti-patterns, and code smells |
|
|
128
|
+
| `performance-oracle` | Performance analysis, bottleneck identification, scalability |
|
|
66
129
|
|
|
67
|
-
|
|
130
|
+
### Research Agents
|
|
68
131
|
|
|
69
|
-
|
|
132
|
+
| Agent | Purpose |
|
|
133
|
+
|-------|---------|
|
|
134
|
+
| `framework-docs-researcher` | Gather framework documentation and best practices |
|
|
70
135
|
|
|
71
|
-
|
|
136
|
+
### Using Agents
|
|
72
137
|
|
|
73
|
-
|
|
138
|
+
Agents are invoked via OpenCode's `@mention` syntax or `delegate_task`:
|
|
74
139
|
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
140
|
+
```
|
|
141
|
+
@architecture-strategist Review the authentication refactoring in this PR
|
|
142
|
+
```
|
|
78
143
|
|
|
79
|
-
|
|
144
|
+
Or programmatically in skills/commands:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
delegate_task(subagent_type="architecture-strategist", prompt="Review...")
|
|
148
|
+
```
|
|
80
149
|
|
|
81
|
-
|
|
150
|
+
## Commands
|
|
82
151
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
152
|
+
Commands are slash-invokable shortcuts that trigger workflows or actions.
|
|
153
|
+
|
|
154
|
+
### Workflow Commands
|
|
86
155
|
|
|
87
|
-
|
|
156
|
+
| Command | Description |
|
|
157
|
+
|---------|-------------|
|
|
158
|
+
| `/workflows:brainstorm` | Explore requirements through collaborative dialogue |
|
|
159
|
+
| `/workflows:plan` | Create detailed implementation plans |
|
|
160
|
+
| `/workflows:review` | Run code review with specialized agents |
|
|
161
|
+
| `/workflows:work` | Execute planned work systematically |
|
|
162
|
+
| `/workflows:compound` | Build compound documentation |
|
|
163
|
+
|
|
164
|
+
### Utility Commands
|
|
165
|
+
|
|
166
|
+
| Command | Description |
|
|
167
|
+
|---------|-------------|
|
|
168
|
+
| `/lfg` | "Let's go" — start working immediately |
|
|
169
|
+
| `/create-agent-skill` | Create a new skill with guidance |
|
|
170
|
+
| `/deepen-plan` | Add detail to existing plans |
|
|
171
|
+
| `/agent-native-audit` | Audit code for agent-native patterns |
|
|
88
172
|
|
|
89
173
|
## Configuration
|
|
90
174
|
|
|
175
|
+
Systematic works out of the box, but you can customize it via configuration files.
|
|
176
|
+
|
|
177
|
+
### Plugin Configuration
|
|
178
|
+
|
|
91
179
|
Create `~/.config/opencode/systematic.json` or `.opencode/systematic.json` to disable specific bundled content:
|
|
92
180
|
|
|
93
181
|
```json
|
|
94
182
|
{
|
|
95
|
-
"disabled_skills": [],
|
|
183
|
+
"disabled_skills": ["git-worktree"],
|
|
96
184
|
"disabled_agents": [],
|
|
97
185
|
"disabled_commands": []
|
|
98
186
|
}
|
|
99
187
|
```
|
|
100
188
|
|
|
189
|
+
### Project-Specific Content
|
|
190
|
+
|
|
191
|
+
Add your own skills, agents, and commands alongside bundled ones:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
.opencode/
|
|
195
|
+
├── skills/
|
|
196
|
+
│ └── my-skill/
|
|
197
|
+
│ └── SKILL.md
|
|
198
|
+
├── agents/
|
|
199
|
+
│ └── my-agent.md
|
|
200
|
+
└── commands/
|
|
201
|
+
└── my-command.md
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Project-level content takes precedence over bundled content with the same name.
|
|
205
|
+
|
|
206
|
+
## Tools
|
|
207
|
+
|
|
208
|
+
The plugin exposes one tool to OpenCode:
|
|
209
|
+
|
|
210
|
+
| Tool | Description |
|
|
211
|
+
|------|-------------|
|
|
212
|
+
| `systematic_skill` | Load Systematic bundled skills by name |
|
|
213
|
+
|
|
214
|
+
For non-Systematic skills (project or user-level), use OpenCode's native `skill` tool.
|
|
215
|
+
|
|
216
|
+
## How It Works
|
|
217
|
+
|
|
218
|
+
Systematic uses three OpenCode plugin hooks:
|
|
219
|
+
|
|
220
|
+
```mermaid
|
|
221
|
+
flowchart TB
|
|
222
|
+
A[Plugin Loaded] --> B[config hook]
|
|
223
|
+
A --> C[tool hook]
|
|
224
|
+
A --> D[system.transform hook]
|
|
225
|
+
|
|
226
|
+
B --> E[Merge bundled agents/commands/skills into OpenCode config]
|
|
227
|
+
C --> F[Register systematic_skill tool]
|
|
228
|
+
D --> G[Inject bootstrap prompt into every conversation]
|
|
229
|
+
|
|
230
|
+
style A fill:#e1f5fe
|
|
231
|
+
style E fill:#f1f8e9
|
|
232
|
+
style F fill:#fff3e0
|
|
233
|
+
style G fill:#fce4ec
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
1. **`config` hook** — Merges bundled assets into your OpenCode configuration
|
|
237
|
+
2. **`tool` hook** — Registers the `systematic_skill` tool for loading skills
|
|
238
|
+
3. **`system.transform` hook** — Injects the "Using Systematic" guide into system prompts
|
|
239
|
+
|
|
240
|
+
This architecture ensures skills, agents, and commands are available immediately without manual setup.
|
|
241
|
+
|
|
101
242
|
## Development
|
|
102
243
|
|
|
244
|
+
### Prerequisites
|
|
245
|
+
|
|
246
|
+
- [Bun](https://bun.sh/) runtime
|
|
247
|
+
- Node.js 18+ (for compatibility)
|
|
248
|
+
|
|
249
|
+
### Setup
|
|
250
|
+
|
|
103
251
|
```bash
|
|
252
|
+
# Clone the repository
|
|
253
|
+
git clone https://github.com/marcusrbrown/systematic.git
|
|
254
|
+
cd systematic
|
|
255
|
+
|
|
104
256
|
# Install dependencies
|
|
105
257
|
bun install
|
|
106
258
|
|
|
107
|
-
# Build
|
|
259
|
+
# Build the plugin
|
|
108
260
|
bun run build
|
|
109
261
|
|
|
110
|
-
#
|
|
262
|
+
# Run type checking
|
|
111
263
|
bun run typecheck
|
|
112
264
|
|
|
113
|
-
#
|
|
265
|
+
# Run linter
|
|
114
266
|
bun run lint
|
|
115
267
|
|
|
116
|
-
# Run tests
|
|
268
|
+
# Run unit tests
|
|
117
269
|
bun test
|
|
118
270
|
```
|
|
119
271
|
|
|
272
|
+
### Project Structure
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
├── src/
|
|
276
|
+
│ ├── index.ts # Plugin entry point
|
|
277
|
+
│ ├── cli.ts # CLI entry point
|
|
278
|
+
│ └── lib/
|
|
279
|
+
│ ├── bootstrap.ts # System prompt injection
|
|
280
|
+
│ ├── config.ts # JSONC config loading
|
|
281
|
+
│ ├── config-handler.ts # OpenCode config hook
|
|
282
|
+
│ ├── skill-tool.ts # systematic_skill tool
|
|
283
|
+
│ ├── skills.ts # Skill discovery
|
|
284
|
+
│ ├── agents.ts # Agent discovery
|
|
285
|
+
│ └── commands.ts # Command discovery
|
|
286
|
+
├── skills/ # Bundled skills (SKILL.md files)
|
|
287
|
+
├── agents/ # Bundled agents (Markdown)
|
|
288
|
+
├── commands/ # Bundled commands (Markdown)
|
|
289
|
+
├── tests/
|
|
290
|
+
│ ├── unit/ # Unit tests
|
|
291
|
+
│ └── integration/ # Integration tests
|
|
292
|
+
└── dist/ # Build output
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Testing
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# Run all unit tests
|
|
299
|
+
bun test tests/unit
|
|
300
|
+
|
|
301
|
+
# Run a specific test file
|
|
302
|
+
bun test tests/unit/skills.test.ts
|
|
303
|
+
|
|
304
|
+
# Run integration tests
|
|
305
|
+
bun test tests/integration
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Contributing
|
|
309
|
+
|
|
310
|
+
See [`AGENTS.md`](./AGENTS.md) for detailed development guidelines, code style conventions, and architecture overview.
|
|
311
|
+
|
|
312
|
+
## Converting from Claude Code
|
|
313
|
+
|
|
314
|
+
Migrating skills, agents, or commands from Claude Code (CEP) to Systematic? See the [Conversion Guide](./docs/CONVERSION-GUIDE.md) for field mappings and examples.
|
|
315
|
+
|
|
316
|
+
## References
|
|
317
|
+
|
|
318
|
+
- [OpenCode Documentation](https://opencode.ai/docs/) — Official OpenCode platform docs
|
|
319
|
+
- [Compound Engineering Plugin](https://github.com/EveryInc/compound-engineering-plugin) — Original Claude Code workflows
|
|
320
|
+
- [Plugin Source Code](https://github.com/marcusrbrown/systematic) — View the implementation
|
|
321
|
+
|
|
120
322
|
## License
|
|
121
323
|
|
|
122
|
-
MIT
|
|
324
|
+
[MIT](LICENSE) © Marcus R. Brown
|
package/dist/cli.js
CHANGED
|
@@ -96,9 +96,103 @@ function formatFrontmatter(data) {
|
|
|
96
96
|
return ["---", yamlContent, "---"].join(`
|
|
97
97
|
`);
|
|
98
98
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
|
|
100
|
+
// src/lib/validation.ts
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
function isPermissionSetting(value) {
|
|
105
|
+
return value === "ask" || value === "allow" || value === "deny";
|
|
106
|
+
}
|
|
107
|
+
function isToolsMap(value) {
|
|
108
|
+
if (!isRecord(value))
|
|
109
|
+
return false;
|
|
110
|
+
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
111
|
+
}
|
|
112
|
+
function isAgentMode(value) {
|
|
113
|
+
return value === "subagent" || value === "primary" || value === "all";
|
|
114
|
+
}
|
|
115
|
+
function extractSimplePermission(data, key) {
|
|
116
|
+
if (!(key in data))
|
|
117
|
+
return;
|
|
118
|
+
const value = data[key];
|
|
119
|
+
return isPermissionSetting(value) ? value : null;
|
|
120
|
+
}
|
|
121
|
+
function extractBashPermission(data) {
|
|
122
|
+
if (!("bash" in data))
|
|
123
|
+
return;
|
|
124
|
+
const bash = data.bash;
|
|
125
|
+
if (isPermissionSetting(bash))
|
|
126
|
+
return bash;
|
|
127
|
+
if (isRecord(bash)) {
|
|
128
|
+
const entries = Object.entries(bash);
|
|
129
|
+
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
130
|
+
return Object.fromEntries(entries);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory) {
|
|
136
|
+
const permission = {};
|
|
137
|
+
if (edit)
|
|
138
|
+
permission.edit = edit;
|
|
139
|
+
if (bash)
|
|
140
|
+
permission.bash = bash;
|
|
141
|
+
if (webfetch)
|
|
142
|
+
permission.webfetch = webfetch;
|
|
143
|
+
if (doom_loop)
|
|
144
|
+
permission.doom_loop = doom_loop;
|
|
145
|
+
if (external_directory)
|
|
146
|
+
permission.external_directory = external_directory;
|
|
147
|
+
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
148
|
+
}
|
|
149
|
+
function normalizePermission(value) {
|
|
150
|
+
if (!isRecord(value))
|
|
151
|
+
return;
|
|
152
|
+
const bash = extractBashPermission(value);
|
|
153
|
+
if (bash === null)
|
|
154
|
+
return;
|
|
155
|
+
const edit = extractSimplePermission(value, "edit");
|
|
156
|
+
if (edit === null)
|
|
157
|
+
return;
|
|
158
|
+
const webfetch = extractSimplePermission(value, "webfetch");
|
|
159
|
+
if (webfetch === null)
|
|
160
|
+
return;
|
|
161
|
+
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
162
|
+
if (doom_loop === null)
|
|
163
|
+
return;
|
|
164
|
+
const external_directory = extractSimplePermission(value, "external_directory");
|
|
165
|
+
if (external_directory === null)
|
|
166
|
+
return;
|
|
167
|
+
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory);
|
|
168
|
+
}
|
|
169
|
+
function extractString(data, key, fallback = "") {
|
|
170
|
+
const value = data[key];
|
|
171
|
+
return typeof value === "string" ? value : fallback;
|
|
172
|
+
}
|
|
173
|
+
function extractNonEmptyString(data, key) {
|
|
174
|
+
const value = data[key];
|
|
175
|
+
if (typeof value !== "string")
|
|
176
|
+
return;
|
|
177
|
+
const trimmed = value.trim();
|
|
178
|
+
return trimmed !== "" ? trimmed : undefined;
|
|
179
|
+
}
|
|
180
|
+
function extractNumber(data, key) {
|
|
181
|
+
const value = data[key];
|
|
182
|
+
return typeof value === "number" ? value : undefined;
|
|
183
|
+
}
|
|
184
|
+
function extractBoolean(data, key) {
|
|
185
|
+
const value = data[key];
|
|
186
|
+
if (typeof value === "boolean")
|
|
187
|
+
return value;
|
|
188
|
+
if (typeof value === "string") {
|
|
189
|
+
const normalized = value.trim().toLowerCase();
|
|
190
|
+
if (normalized === "true")
|
|
191
|
+
return true;
|
|
192
|
+
if (normalized === "false")
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
102
196
|
}
|
|
103
197
|
|
|
104
198
|
// src/lib/walk-dir.ts
|
|
@@ -147,11 +241,23 @@ function findAgentsInDir(dir, maxDepth = 2) {
|
|
|
147
241
|
}));
|
|
148
242
|
}
|
|
149
243
|
function extractAgentFrontmatter(content) {
|
|
150
|
-
const { data, parseError } = parseFrontmatter(content);
|
|
244
|
+
const { data, parseError, body } = parseFrontmatter(content);
|
|
245
|
+
if (parseError) {
|
|
246
|
+
return { name: "", description: "", prompt: body.trim() };
|
|
247
|
+
}
|
|
151
248
|
return {
|
|
152
|
-
name:
|
|
153
|
-
description:
|
|
154
|
-
prompt:
|
|
249
|
+
name: extractString(data, "name"),
|
|
250
|
+
description: extractString(data, "description"),
|
|
251
|
+
prompt: body.trim(),
|
|
252
|
+
model: extractNonEmptyString(data, "model"),
|
|
253
|
+
temperature: extractNumber(data, "temperature"),
|
|
254
|
+
top_p: extractNumber(data, "top_p"),
|
|
255
|
+
tools: isToolsMap(data.tools) ? data.tools : undefined,
|
|
256
|
+
disable: extractBoolean(data, "disable"),
|
|
257
|
+
mode: isAgentMode(data.mode) ? data.mode : undefined,
|
|
258
|
+
color: extractNonEmptyString(data, "color"),
|
|
259
|
+
maxSteps: extractNumber(data, "maxSteps"),
|
|
260
|
+
permission: normalizePermission(data.permission)
|
|
155
261
|
};
|
|
156
262
|
}
|
|
157
263
|
|
|
@@ -173,11 +279,24 @@ function findCommandsInDir(dir, maxDepth = 2) {
|
|
|
173
279
|
}
|
|
174
280
|
function extractCommandFrontmatter(content) {
|
|
175
281
|
const { data, parseError } = parseFrontmatter(content);
|
|
176
|
-
|
|
282
|
+
if (parseError) {
|
|
283
|
+
return {
|
|
284
|
+
name: "",
|
|
285
|
+
description: "",
|
|
286
|
+
argumentHint: "",
|
|
287
|
+
agent: undefined,
|
|
288
|
+
model: undefined,
|
|
289
|
+
subtask: undefined
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const argumentHintRaw = extractString(data, "argument-hint");
|
|
177
293
|
return {
|
|
178
|
-
name:
|
|
179
|
-
description:
|
|
180
|
-
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, "")
|
|
294
|
+
name: extractString(data, "name"),
|
|
295
|
+
description: extractString(data, "description"),
|
|
296
|
+
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, ""),
|
|
297
|
+
agent: extractNonEmptyString(data, "agent"),
|
|
298
|
+
model: extractNonEmptyString(data, "model"),
|
|
299
|
+
subtask: extractBoolean(data, "subtask")
|
|
181
300
|
};
|
|
182
301
|
}
|
|
183
302
|
|
|
@@ -293,30 +412,48 @@ function normalizeModel(model) {
|
|
|
293
412
|
return `google/${model}`;
|
|
294
413
|
return `anthropic/${model}`;
|
|
295
414
|
}
|
|
415
|
+
function addOptionalFields(target, data) {
|
|
416
|
+
if (typeof data.top_p === "number")
|
|
417
|
+
target.top_p = data.top_p;
|
|
418
|
+
if (isToolsMap(data.tools))
|
|
419
|
+
target.tools = data.tools;
|
|
420
|
+
if (typeof data.disable === "boolean")
|
|
421
|
+
target.disable = data.disable;
|
|
422
|
+
if (typeof data.color === "string")
|
|
423
|
+
target.color = data.color;
|
|
424
|
+
if (typeof data.maxSteps === "number")
|
|
425
|
+
target.maxSteps = data.maxSteps;
|
|
426
|
+
const permission = normalizePermission(data.permission);
|
|
427
|
+
if (permission)
|
|
428
|
+
target.permission = permission;
|
|
429
|
+
}
|
|
296
430
|
function transformAgentFrontmatter(data, agentMode) {
|
|
297
431
|
const name = typeof data.name === "string" ? data.name : "";
|
|
298
432
|
const description = typeof data.description === "string" ? data.description : "";
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
433
|
+
const mode = isAgentMode(data.mode) ? data.mode : agentMode;
|
|
434
|
+
const newData = { mode };
|
|
435
|
+
if (description) {
|
|
436
|
+
newData.description = description;
|
|
437
|
+
} else if (name) {
|
|
438
|
+
newData.description = `${name} agent`;
|
|
439
|
+
}
|
|
303
440
|
if (typeof data.model === "string" && data.model !== "inherit") {
|
|
304
441
|
newData.model = normalizeModel(data.model);
|
|
305
442
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
} else {
|
|
309
|
-
newData.temperature = inferTemperature(name, description);
|
|
310
|
-
}
|
|
443
|
+
newData.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
|
|
444
|
+
addOptionalFields(newData, data);
|
|
311
445
|
return newData;
|
|
312
446
|
}
|
|
313
447
|
function convertContent(content, type, options = {}) {
|
|
314
448
|
if (content === "")
|
|
315
449
|
return "";
|
|
316
|
-
const { data, body, hadFrontmatter } = parseFrontmatter(content);
|
|
450
|
+
const { data, body, hadFrontmatter, parseError } = parseFrontmatter(content);
|
|
317
451
|
if (!hadFrontmatter) {
|
|
318
452
|
return options.skipBodyTransform ? content : transformBody(content);
|
|
319
453
|
}
|
|
454
|
+
if (parseError) {
|
|
455
|
+
return content;
|
|
456
|
+
}
|
|
320
457
|
const shouldTransformBody = !options.skipBodyTransform;
|
|
321
458
|
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
322
459
|
if (type === "agent") {
|
|
@@ -365,9 +502,29 @@ function extractFrontmatter(filePath) {
|
|
|
365
502
|
if (parseError) {
|
|
366
503
|
return { name: "", description: "" };
|
|
367
504
|
}
|
|
505
|
+
const metadataRaw = data.metadata;
|
|
506
|
+
let metadata;
|
|
507
|
+
if (isRecord(metadataRaw)) {
|
|
508
|
+
const entries = Object.entries(metadataRaw);
|
|
509
|
+
if (entries.every(([, v]) => typeof v === "string")) {
|
|
510
|
+
metadata = Object.fromEntries(entries);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
514
|
+
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
368
515
|
return {
|
|
369
|
-
name:
|
|
370
|
-
description:
|
|
516
|
+
name: extractString(data, "name"),
|
|
517
|
+
description: extractString(data, "description"),
|
|
518
|
+
license: extractNonEmptyString(data, "license"),
|
|
519
|
+
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
520
|
+
metadata,
|
|
521
|
+
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
522
|
+
userInvocable: extractBoolean(data, "user-invocable"),
|
|
523
|
+
subtask: data.context === "fork" ? true : undefined,
|
|
524
|
+
agent: extractNonEmptyString(data, "agent"),
|
|
525
|
+
model: extractNonEmptyString(data, "model"),
|
|
526
|
+
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
527
|
+
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
371
528
|
};
|
|
372
529
|
} catch {
|
|
373
530
|
return { name: "", description: "" };
|
|
@@ -382,34 +539,26 @@ function findSkillsInDir(dir, maxDepth = 3) {
|
|
|
382
539
|
for (const entry of entries) {
|
|
383
540
|
const skillFile = path3.join(entry.path, "SKILL.md");
|
|
384
541
|
if (fs4.existsSync(skillFile)) {
|
|
385
|
-
const
|
|
542
|
+
const frontmatter = extractFrontmatter(skillFile);
|
|
386
543
|
skills.push({
|
|
387
544
|
path: entry.path,
|
|
388
545
|
skillFile,
|
|
389
|
-
name: name || entry.name,
|
|
390
|
-
description: description || ""
|
|
546
|
+
name: frontmatter.name || entry.name,
|
|
547
|
+
description: frontmatter.description || "",
|
|
548
|
+
license: frontmatter.license,
|
|
549
|
+
compatibility: frontmatter.compatibility,
|
|
550
|
+
metadata: frontmatter.metadata,
|
|
551
|
+
disableModelInvocation: frontmatter.disableModelInvocation,
|
|
552
|
+
userInvocable: frontmatter.userInvocable,
|
|
553
|
+
subtask: frontmatter.subtask,
|
|
554
|
+
agent: frontmatter.agent,
|
|
555
|
+
model: frontmatter.model,
|
|
556
|
+
argumentHint: frontmatter.argumentHint,
|
|
557
|
+
allowedTools: frontmatter.allowedTools
|
|
391
558
|
});
|
|
392
559
|
}
|
|
393
560
|
}
|
|
394
561
|
return skills;
|
|
395
562
|
}
|
|
396
|
-
function formatSkillsXml(skills) {
|
|
397
|
-
if (skills.length === 0)
|
|
398
|
-
return "";
|
|
399
|
-
const skillsXml = skills.map((skill) => {
|
|
400
|
-
const lines = [
|
|
401
|
-
" <skill>",
|
|
402
|
-
` <name>systematic:${skill.name}</name>`,
|
|
403
|
-
` <description>${skill.description}</description>`
|
|
404
|
-
];
|
|
405
|
-
lines.push(" </skill>");
|
|
406
|
-
return lines.join(`
|
|
407
|
-
`);
|
|
408
|
-
}).join(`
|
|
409
|
-
`);
|
|
410
|
-
return `<available_skills>
|
|
411
|
-
${skillsXml}
|
|
412
|
-
</available_skills>`;
|
|
413
|
-
}
|
|
414
563
|
|
|
415
|
-
export {
|
|
564
|
+
export { parseFrontmatter, loadConfig, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, convertContent, convertFileWithCache, findSkillsInDir };
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
findAgentsInDir,
|
|
7
7
|
findCommandsInDir,
|
|
8
8
|
findSkillsInDir,
|
|
9
|
-
formatSkillsXml,
|
|
10
9
|
loadConfig,
|
|
11
|
-
|
|
12
|
-
} from "./index-
|
|
10
|
+
parseFrontmatter
|
|
11
|
+
} from "./index-yxbcy3s7.js";
|
|
13
12
|
|
|
14
13
|
// src/index.ts
|
|
15
14
|
import fs2 from "fs";
|
|
@@ -54,7 +53,8 @@ function getBootstrapContent(config, deps) {
|
|
|
54
53
|
if (!fs.existsSync(usingSystematicPath))
|
|
55
54
|
return null;
|
|
56
55
|
const fullContent = fs.readFileSync(usingSystematicPath, "utf8");
|
|
57
|
-
const
|
|
56
|
+
const { body } = parseFrontmatter(fullContent);
|
|
57
|
+
const content = body.trim();
|
|
58
58
|
const toolMapping = getToolMappingTemplate(bundledSkillsDir);
|
|
59
59
|
return `<SYSTEMATIC_WORKFLOWS>
|
|
60
60
|
You have access to structured engineering workflows via the systematic plugin.
|
|
@@ -106,7 +106,7 @@ function loadSkill(skillInfo) {
|
|
|
106
106
|
const converted = convertFileWithCache(skillInfo.skillFile, "skill", {
|
|
107
107
|
source: "bundled"
|
|
108
108
|
});
|
|
109
|
-
const body =
|
|
109
|
+
const { body } = parseFrontmatter(converted);
|
|
110
110
|
const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
|
|
111
111
|
return {
|
|
112
112
|
name: skillInfo.name,
|
|
@@ -114,7 +114,13 @@ function loadSkill(skillInfo) {
|
|
|
114
114
|
description: formatSkillDescription(skillInfo.description, skillInfo.name),
|
|
115
115
|
path: skillInfo.path,
|
|
116
116
|
skillFile: skillInfo.skillFile,
|
|
117
|
-
wrappedTemplate
|
|
117
|
+
wrappedTemplate,
|
|
118
|
+
disableModelInvocation: skillInfo.disableModelInvocation,
|
|
119
|
+
userInvocable: skillInfo.userInvocable,
|
|
120
|
+
subtask: skillInfo.subtask,
|
|
121
|
+
agent: skillInfo.agent,
|
|
122
|
+
model: skillInfo.model,
|
|
123
|
+
argumentHint: skillInfo.argumentHint
|
|
118
124
|
};
|
|
119
125
|
} catch {
|
|
120
126
|
return null;
|
|
@@ -128,11 +134,42 @@ function loadAgentAsConfig(agentInfo) {
|
|
|
128
134
|
source: "bundled",
|
|
129
135
|
agentMode: "subagent"
|
|
130
136
|
});
|
|
131
|
-
const {
|
|
132
|
-
|
|
137
|
+
const {
|
|
138
|
+
description,
|
|
139
|
+
prompt,
|
|
140
|
+
model,
|
|
141
|
+
temperature,
|
|
142
|
+
top_p,
|
|
143
|
+
tools,
|
|
144
|
+
disable,
|
|
145
|
+
mode,
|
|
146
|
+
color,
|
|
147
|
+
maxSteps,
|
|
148
|
+
permission
|
|
149
|
+
} = extractAgentFrontmatter(converted);
|
|
150
|
+
const config = {
|
|
133
151
|
description: description || `${agentInfo.name} agent`,
|
|
134
|
-
prompt
|
|
152
|
+
prompt
|
|
135
153
|
};
|
|
154
|
+
if (model !== undefined)
|
|
155
|
+
config.model = model;
|
|
156
|
+
if (temperature !== undefined)
|
|
157
|
+
config.temperature = temperature;
|
|
158
|
+
if (top_p !== undefined)
|
|
159
|
+
config.top_p = top_p;
|
|
160
|
+
if (tools !== undefined)
|
|
161
|
+
config.tools = tools;
|
|
162
|
+
if (disable !== undefined)
|
|
163
|
+
config.disable = disable;
|
|
164
|
+
if (mode !== undefined)
|
|
165
|
+
config.mode = mode;
|
|
166
|
+
if (color !== undefined)
|
|
167
|
+
config.color = color;
|
|
168
|
+
if (maxSteps !== undefined)
|
|
169
|
+
config.maxSteps = maxSteps;
|
|
170
|
+
if (permission !== undefined)
|
|
171
|
+
config.permission = permission;
|
|
172
|
+
return config;
|
|
136
173
|
} catch {
|
|
137
174
|
return null;
|
|
138
175
|
}
|
|
@@ -142,21 +179,36 @@ function loadCommandAsConfig(commandInfo) {
|
|
|
142
179
|
const converted = convertFileWithCache(commandInfo.file, "command", {
|
|
143
180
|
source: "bundled"
|
|
144
181
|
});
|
|
145
|
-
const { name, description } = extractCommandFrontmatter(converted);
|
|
182
|
+
const { name, description, agent, model, subtask } = extractCommandFrontmatter(converted);
|
|
183
|
+
const { body } = parseFrontmatter(converted);
|
|
146
184
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
147
|
-
|
|
148
|
-
template:
|
|
185
|
+
const config = {
|
|
186
|
+
template: body.trim(),
|
|
149
187
|
description: description || `${name || cleanName} command`
|
|
150
188
|
};
|
|
189
|
+
if (agent !== undefined)
|
|
190
|
+
config.agent = agent;
|
|
191
|
+
if (model !== undefined)
|
|
192
|
+
config.model = model;
|
|
193
|
+
if (subtask !== undefined)
|
|
194
|
+
config.subtask = subtask;
|
|
195
|
+
return config;
|
|
151
196
|
} catch {
|
|
152
197
|
return null;
|
|
153
198
|
}
|
|
154
199
|
}
|
|
155
200
|
function loadSkillAsCommand(loaded) {
|
|
156
|
-
|
|
201
|
+
const config = {
|
|
157
202
|
template: loaded.wrappedTemplate,
|
|
158
203
|
description: loaded.description
|
|
159
204
|
};
|
|
205
|
+
if (loaded.agent !== undefined)
|
|
206
|
+
config.agent = loaded.agent;
|
|
207
|
+
if (loaded.model !== undefined)
|
|
208
|
+
config.model = loaded.model;
|
|
209
|
+
if (loaded.subtask !== undefined)
|
|
210
|
+
config.subtask = loaded.subtask;
|
|
211
|
+
return config;
|
|
160
212
|
}
|
|
161
213
|
function collectAgents(dir, disabledAgents) {
|
|
162
214
|
const agents = {};
|
|
@@ -193,6 +245,8 @@ function collectSkillsAsCommands(dir, disabledSkills) {
|
|
|
193
245
|
continue;
|
|
194
246
|
const loaded = loadSkill(skillInfo);
|
|
195
247
|
if (loaded) {
|
|
248
|
+
if (loaded.userInvocable === false)
|
|
249
|
+
continue;
|
|
196
250
|
commands[loaded.prefixedName] = loadSkillAsCommand(loaded);
|
|
197
251
|
}
|
|
198
252
|
}
|
|
@@ -222,13 +276,27 @@ function createConfigHandler(deps) {
|
|
|
222
276
|
// src/lib/skill-tool.ts
|
|
223
277
|
import path3 from "path";
|
|
224
278
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
279
|
+
function formatSkillsXml(skills) {
|
|
280
|
+
if (skills.length === 0)
|
|
281
|
+
return "";
|
|
282
|
+
const skillLines = skills.flatMap((skill) => [
|
|
283
|
+
" <skill>",
|
|
284
|
+
` <name>systematic:${skill.name}</name>`,
|
|
285
|
+
` <description>${skill.description}</description>`,
|
|
286
|
+
" </skill>"
|
|
287
|
+
]);
|
|
288
|
+
return ["<available_skills>", ...skillLines, "</available_skills>"].join(" ");
|
|
289
|
+
}
|
|
225
290
|
function createSkillTool(options) {
|
|
226
291
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
227
292
|
const getSystematicSkills = () => {
|
|
228
|
-
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).map((skillInfo) => loadSkill(skillInfo)).filter((s) => s !== null).sort((a, b) => a.name.localeCompare(b.name));
|
|
293
|
+
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).map((skillInfo) => loadSkill(skillInfo)).filter((s) => s !== null).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name));
|
|
229
294
|
};
|
|
230
295
|
const buildDescription = () => {
|
|
231
296
|
const skills = getSystematicSkills();
|
|
297
|
+
if (skills.length === 0) {
|
|
298
|
+
return "Load a skill to get detailed instructions for a specific task. No skills are currently available.";
|
|
299
|
+
}
|
|
232
300
|
const skillInfos = skills.map((s) => ({
|
|
233
301
|
name: s.name,
|
|
234
302
|
description: s.description,
|
|
@@ -236,15 +304,22 @@ function createSkillTool(options) {
|
|
|
236
304
|
skillFile: s.skillFile
|
|
237
305
|
}));
|
|
238
306
|
const systematicXml = formatSkillsXml(skillInfos);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Skills provide specialized knowledge and step-by-step guidance.
|
|
242
|
-
Use this when a task matches an available skill's description
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
307
|
+
return [
|
|
308
|
+
"Load a skill to get detailed instructions for a specific task.",
|
|
309
|
+
"Skills provide specialized knowledge and step-by-step guidance.",
|
|
310
|
+
"Use this when a task matches an available skill's description.",
|
|
311
|
+
"Only the skills listed here are available:",
|
|
312
|
+
systematicXml
|
|
313
|
+
].join(" ");
|
|
314
|
+
};
|
|
315
|
+
const buildParameterHint = () => {
|
|
316
|
+
const skills = getSystematicSkills();
|
|
317
|
+
const examples = skills.slice(0, 3).map((s) => `'systematic:${s.name}'`).join(", ");
|
|
318
|
+
const hint = examples.length > 0 ? ` (e.g., ${examples}, ...)` : "";
|
|
319
|
+
return `The skill identifier from available_skills${hint}`;
|
|
246
320
|
};
|
|
247
321
|
let cachedDescription = null;
|
|
322
|
+
let cachedParameterHint = null;
|
|
248
323
|
return tool({
|
|
249
324
|
get description() {
|
|
250
325
|
if (cachedDescription == null) {
|
|
@@ -253,24 +328,45 @@ ${systematicXml}`;
|
|
|
253
328
|
return cachedDescription;
|
|
254
329
|
},
|
|
255
330
|
args: {
|
|
256
|
-
name: tool.schema.string().describe(
|
|
331
|
+
name: tool.schema.string().describe((() => {
|
|
332
|
+
if (cachedParameterHint == null) {
|
|
333
|
+
cachedParameterHint = buildParameterHint();
|
|
334
|
+
}
|
|
335
|
+
return cachedParameterHint;
|
|
336
|
+
})())
|
|
257
337
|
},
|
|
258
|
-
async execute(args) {
|
|
338
|
+
async execute(args, context) {
|
|
259
339
|
const requestedName = args.name;
|
|
260
340
|
const normalizedName = requestedName.startsWith("systematic:") ? requestedName.slice("systematic:".length) : requestedName;
|
|
261
341
|
const skills = getSystematicSkills();
|
|
262
342
|
const matchedSkill = skills.find((s) => s.name === normalizedName);
|
|
263
|
-
if (matchedSkill) {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
return `## Skill: ${matchedSkill.prefixedName}
|
|
267
|
-
|
|
268
|
-
**Base directory**: ${dir}
|
|
269
|
-
|
|
270
|
-
${body}`;
|
|
343
|
+
if (!matchedSkill) {
|
|
344
|
+
const availableSystematic = skills.map((s) => s.prefixedName);
|
|
345
|
+
throw new Error(`Skill "${requestedName}" not found. Available systematic skills: ${availableSystematic.join(", ")}`);
|
|
271
346
|
}
|
|
272
|
-
const
|
|
273
|
-
|
|
347
|
+
const body = extractSkillBody(matchedSkill.wrappedTemplate);
|
|
348
|
+
const dir = path3.dirname(matchedSkill.skillFile);
|
|
349
|
+
await context.ask({
|
|
350
|
+
permission: "skill",
|
|
351
|
+
patterns: [matchedSkill.prefixedName],
|
|
352
|
+
always: [matchedSkill.prefixedName],
|
|
353
|
+
metadata: {}
|
|
354
|
+
});
|
|
355
|
+
context.metadata({
|
|
356
|
+
title: `Loaded skill: ${matchedSkill.prefixedName}`,
|
|
357
|
+
metadata: {
|
|
358
|
+
name: matchedSkill.prefixedName,
|
|
359
|
+
dir
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
return [
|
|
363
|
+
`## Skill: ${matchedSkill.prefixedName}`,
|
|
364
|
+
"",
|
|
365
|
+
`**Base directory**: ${dir}`,
|
|
366
|
+
"",
|
|
367
|
+
body.trim()
|
|
368
|
+
].join(`
|
|
369
|
+
`);
|
|
274
370
|
}
|
|
275
371
|
});
|
|
276
372
|
}
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import { type PermissionConfig } from './validation.js';
|
|
1
2
|
export interface AgentFrontmatter {
|
|
3
|
+
/** Name of the agent */
|
|
2
4
|
name: string;
|
|
5
|
+
/** Description of the agent's purpose */
|
|
3
6
|
description: string;
|
|
7
|
+
/** The system prompt for the agent */
|
|
4
8
|
prompt: string;
|
|
9
|
+
/** Model to use (provider/model format) */
|
|
10
|
+
model?: string;
|
|
11
|
+
/** Temperature for generation */
|
|
12
|
+
temperature?: number;
|
|
13
|
+
/** Top-p sampling */
|
|
14
|
+
top_p?: number;
|
|
15
|
+
/** Tool whitelist/blacklist */
|
|
16
|
+
tools?: Record<string, boolean>;
|
|
17
|
+
/** Disable this agent */
|
|
18
|
+
disable?: boolean;
|
|
19
|
+
/** Agent mode */
|
|
20
|
+
mode?: 'subagent' | 'primary' | 'all';
|
|
21
|
+
/** Hex color code */
|
|
22
|
+
color?: string;
|
|
23
|
+
/** Max agentic iterations */
|
|
24
|
+
maxSteps?: number;
|
|
25
|
+
/** Permission settings */
|
|
26
|
+
permission?: PermissionConfig;
|
|
5
27
|
}
|
|
6
28
|
export interface AgentInfo {
|
|
7
29
|
name: string;
|
package/dist/lib/commands.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ export interface CommandFrontmatter {
|
|
|
2
2
|
name: string;
|
|
3
3
|
description: string;
|
|
4
4
|
argumentHint: string;
|
|
5
|
+
/** Agent ID to use for this command */
|
|
6
|
+
agent?: string;
|
|
7
|
+
/** Model override for this command */
|
|
8
|
+
model?: string;
|
|
9
|
+
/** Whether this command should run as a subtask */
|
|
10
|
+
subtask?: boolean;
|
|
5
11
|
}
|
|
6
12
|
export interface CommandInfo {
|
|
7
13
|
name: string;
|
package/dist/lib/converter.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type AgentMode } from './validation.js';
|
|
1
2
|
export type ContentType = 'skill' | 'agent' | 'command';
|
|
2
3
|
export type SourceType = 'bundled' | 'external';
|
|
3
|
-
export type AgentMode = 'primary' | 'subagent';
|
|
4
4
|
export interface ConvertOptions {
|
|
5
5
|
source?: SourceType;
|
|
6
6
|
agentMode?: AgentMode;
|
|
@@ -15,4 +15,8 @@ export interface FrontmatterResult<T = Record<string, unknown>> {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function parseFrontmatter<T = Record<string, unknown>>(content: string): FrontmatterResult<T>;
|
|
17
17
|
export declare function formatFrontmatter(data: Record<string, unknown>): string;
|
|
18
|
+
/**
|
|
19
|
+
* Removes YAML frontmatter from content and returns the body.
|
|
20
|
+
* Convenience wrapper around parseFrontmatter.
|
|
21
|
+
*/
|
|
18
22
|
export declare function stripFrontmatter(content: string): string;
|
|
@@ -6,6 +6,12 @@ export interface LoadedSkill {
|
|
|
6
6
|
path: string;
|
|
7
7
|
skillFile: string;
|
|
8
8
|
wrappedTemplate: string;
|
|
9
|
+
disableModelInvocation?: boolean;
|
|
10
|
+
userInvocable?: boolean;
|
|
11
|
+
subtask?: boolean;
|
|
12
|
+
agent?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
argumentHint?: string;
|
|
9
15
|
}
|
|
10
16
|
export declare function formatSkillCommandName(name: string): string;
|
|
11
17
|
export declare function formatSkillDescription(description: string, fallbackName: string): string;
|
package/dist/lib/skill-tool.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ToolDefinition } from '@opencode-ai/plugin';
|
|
2
|
+
import { type SkillInfo } from './skills.js';
|
|
2
3
|
export interface SkillToolOptions {
|
|
3
4
|
bundledSkillsDir: string;
|
|
4
5
|
disabledSkills: string[];
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Formats skills as XML for tool description.
|
|
9
|
+
* Uses indented format matching OpenCode's native skill tool.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatSkillsXml(skills: SkillInfo[]): string;
|
|
6
12
|
export declare function createSkillTool(options: SkillToolOptions): ToolDefinition;
|
package/dist/lib/skills.d.ts
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
export interface SkillFrontmatter {
|
|
2
2
|
name: string;
|
|
3
3
|
description: string;
|
|
4
|
+
license?: string;
|
|
5
|
+
compatibility?: string;
|
|
6
|
+
metadata?: Record<string, string>;
|
|
7
|
+
disableModelInvocation?: boolean;
|
|
8
|
+
userInvocable?: boolean;
|
|
9
|
+
subtask?: boolean;
|
|
10
|
+
agent?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
argumentHint?: string;
|
|
13
|
+
allowedTools?: string;
|
|
4
14
|
}
|
|
5
15
|
export interface SkillInfo {
|
|
6
16
|
path: string;
|
|
7
17
|
skillFile: string;
|
|
8
18
|
name: string;
|
|
9
19
|
description: string;
|
|
20
|
+
license?: string;
|
|
21
|
+
compatibility?: string;
|
|
22
|
+
metadata?: Record<string, string>;
|
|
23
|
+
disableModelInvocation?: boolean;
|
|
24
|
+
userInvocable?: boolean;
|
|
25
|
+
subtask?: boolean;
|
|
26
|
+
agent?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
argumentHint?: string;
|
|
29
|
+
allowedTools?: string;
|
|
10
30
|
}
|
|
11
31
|
export declare function extractFrontmatter(filePath: string): SkillFrontmatter;
|
|
12
32
|
export declare function findSkillsInDir(dir: string, maxDepth?: number): SkillInfo[];
|
|
13
|
-
export declare function formatSkillsXml(skills: SkillInfo[]): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type guards and validation utilities for agent/skill/command frontmatter.
|
|
3
|
+
*/
|
|
4
|
+
export type AgentMode = 'subagent' | 'primary' | 'all';
|
|
5
|
+
export type PermissionSetting = 'ask' | 'allow' | 'deny';
|
|
6
|
+
export interface PermissionConfig {
|
|
7
|
+
edit?: PermissionSetting;
|
|
8
|
+
bash?: PermissionSetting | Record<string, PermissionSetting>;
|
|
9
|
+
webfetch?: PermissionSetting;
|
|
10
|
+
doom_loop?: PermissionSetting;
|
|
11
|
+
external_directory?: PermissionSetting;
|
|
12
|
+
}
|
|
13
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
14
|
+
export declare function isPermissionSetting(value: unknown): value is PermissionSetting;
|
|
15
|
+
export declare function isToolsMap(value: unknown): value is Record<string, boolean>;
|
|
16
|
+
export declare function isAgentMode(value: unknown): value is AgentMode;
|
|
17
|
+
export declare function normalizePermission(value: unknown): PermissionConfig | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Shared frontmatter extraction helpers.
|
|
20
|
+
* Centralized to ensure consistent behavior across agents, commands, and skills.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractString(data: Record<string, unknown>, key: string, fallback?: string): string;
|
|
23
|
+
export declare function extractNonEmptyString(data: Record<string, unknown>, key: string): string | undefined;
|
|
24
|
+
export declare function extractNumber(data: Record<string, unknown>, key: string): number | undefined;
|
|
25
|
+
export declare function extractBoolean(data: Record<string, unknown>, key: string): boolean | undefined;
|