@fro.bot/systematic 1.4.0 → 1.6.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-ymsavt2y.js → index-95qwq9ph.js} +172 -24
- package/dist/index.js +50 -10
- 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/validation.d.ts +25 -0
- package/package.json +2 -2
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
|
|
|
@@ -186,6 +305,7 @@ import fs3 from "fs";
|
|
|
186
305
|
var cache = new Map;
|
|
187
306
|
var TOOL_MAPPINGS = [
|
|
188
307
|
[/\bTask\s+tool\b/gi, "delegate_task tool"],
|
|
308
|
+
[/\bTask\s+([\w-]+)\s*:/g, "delegate_task $1:"],
|
|
189
309
|
[/\bTask\s+([\w-]+)\s*\(/g, "delegate_task $1("],
|
|
190
310
|
[/\bTask\s*\(/g, "delegate_task("],
|
|
191
311
|
[/\bTask\b(?=\s+to\s+\w)/g, "delegate_task"],
|
|
@@ -199,7 +319,7 @@ var TOOL_MAPPINGS = [
|
|
|
199
319
|
[/\bGrep\b(?=\s+tool|\s+to\s+|\()/g, "grep"],
|
|
200
320
|
[/\bGlob\b(?=\s+tool|\s+to\s+|\()/g, "glob"],
|
|
201
321
|
[/\bWebFetch\b/g, "webfetch"],
|
|
202
|
-
[/\bSkill\b(?=\s+tool)/g, "skill"]
|
|
322
|
+
[/\bSkill\b(?=\s+tool|\s*\()/g, "skill"]
|
|
203
323
|
];
|
|
204
324
|
var PATH_REPLACEMENTS = [
|
|
205
325
|
[/\.claude\/skills\//g, ".opencode/skills/"],
|
|
@@ -238,14 +358,24 @@ function inferTemperature(name, description) {
|
|
|
238
358
|
}
|
|
239
359
|
return 0.3;
|
|
240
360
|
}
|
|
361
|
+
var CODE_BLOCK_PATTERN = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
241
362
|
function transformBody(body) {
|
|
242
|
-
|
|
363
|
+
const codeBlocks = [];
|
|
364
|
+
let placeholderIndex = 0;
|
|
365
|
+
const withPlaceholders = body.replace(CODE_BLOCK_PATTERN, (match) => {
|
|
366
|
+
codeBlocks.push(match);
|
|
367
|
+
return `__CODE_BLOCK_${placeholderIndex++}__`;
|
|
368
|
+
});
|
|
369
|
+
let result = withPlaceholders;
|
|
243
370
|
for (const [pattern, replacement] of TOOL_MAPPINGS) {
|
|
244
371
|
result = result.replace(pattern, replacement);
|
|
245
372
|
}
|
|
246
373
|
for (const [pattern, replacement] of PATH_REPLACEMENTS) {
|
|
247
374
|
result = result.replace(pattern, replacement);
|
|
248
375
|
}
|
|
376
|
+
for (let i = 0;i < codeBlocks.length; i++) {
|
|
377
|
+
result = result.replace(`__CODE_BLOCK_${i}__`, codeBlocks[i]);
|
|
378
|
+
}
|
|
249
379
|
return result;
|
|
250
380
|
}
|
|
251
381
|
function removeFields(data, fieldsToRemove) {
|
|
@@ -282,30 +412,48 @@ function normalizeModel(model) {
|
|
|
282
412
|
return `google/${model}`;
|
|
283
413
|
return `anthropic/${model}`;
|
|
284
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
|
+
}
|
|
285
430
|
function transformAgentFrontmatter(data, agentMode) {
|
|
286
431
|
const name = typeof data.name === "string" ? data.name : "";
|
|
287
432
|
const description = typeof data.description === "string" ? data.description : "";
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
440
|
if (typeof data.model === "string" && data.model !== "inherit") {
|
|
293
441
|
newData.model = normalizeModel(data.model);
|
|
294
442
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
} else {
|
|
298
|
-
newData.temperature = inferTemperature(name, description);
|
|
299
|
-
}
|
|
443
|
+
newData.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
|
|
444
|
+
addOptionalFields(newData, data);
|
|
300
445
|
return newData;
|
|
301
446
|
}
|
|
302
447
|
function convertContent(content, type, options = {}) {
|
|
303
448
|
if (content === "")
|
|
304
449
|
return "";
|
|
305
|
-
const { data, body, hadFrontmatter } = parseFrontmatter(content);
|
|
450
|
+
const { data, body, hadFrontmatter, parseError } = parseFrontmatter(content);
|
|
306
451
|
if (!hadFrontmatter) {
|
|
307
452
|
return options.skipBodyTransform ? content : transformBody(content);
|
|
308
453
|
}
|
|
454
|
+
if (parseError) {
|
|
455
|
+
return content;
|
|
456
|
+
}
|
|
309
457
|
const shouldTransformBody = !options.skipBodyTransform;
|
|
310
458
|
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
311
459
|
if (type === "agent") {
|
|
@@ -401,4 +549,4 @@ ${skillsXml}
|
|
|
401
549
|
</available_skills>`;
|
|
402
550
|
}
|
|
403
551
|
|
|
404
|
-
export {
|
|
552
|
+
export { parseFrontmatter, loadConfig, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, convertContent, convertFileWithCache, findSkillsInDir, formatSkillsXml };
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
findSkillsInDir,
|
|
9
9
|
formatSkillsXml,
|
|
10
10
|
loadConfig,
|
|
11
|
-
|
|
12
|
-
} from "./index-
|
|
11
|
+
parseFrontmatter
|
|
12
|
+
} from "./index-95qwq9ph.js";
|
|
13
13
|
|
|
14
14
|
// src/index.ts
|
|
15
15
|
import fs2 from "fs";
|
|
@@ -54,7 +54,8 @@ function getBootstrapContent(config, deps) {
|
|
|
54
54
|
if (!fs.existsSync(usingSystematicPath))
|
|
55
55
|
return null;
|
|
56
56
|
const fullContent = fs.readFileSync(usingSystematicPath, "utf8");
|
|
57
|
-
const
|
|
57
|
+
const { body } = parseFrontmatter(fullContent);
|
|
58
|
+
const content = body.trim();
|
|
58
59
|
const toolMapping = getToolMappingTemplate(bundledSkillsDir);
|
|
59
60
|
return `<SYSTEMATIC_WORKFLOWS>
|
|
60
61
|
You have access to structured engineering workflows via the systematic plugin.
|
|
@@ -106,7 +107,7 @@ function loadSkill(skillInfo) {
|
|
|
106
107
|
const converted = convertFileWithCache(skillInfo.skillFile, "skill", {
|
|
107
108
|
source: "bundled"
|
|
108
109
|
});
|
|
109
|
-
const body =
|
|
110
|
+
const { body } = parseFrontmatter(converted);
|
|
110
111
|
const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
|
|
111
112
|
return {
|
|
112
113
|
name: skillInfo.name,
|
|
@@ -128,11 +129,42 @@ function loadAgentAsConfig(agentInfo) {
|
|
|
128
129
|
source: "bundled",
|
|
129
130
|
agentMode: "subagent"
|
|
130
131
|
});
|
|
131
|
-
const {
|
|
132
|
-
|
|
132
|
+
const {
|
|
133
|
+
description,
|
|
134
|
+
prompt,
|
|
135
|
+
model,
|
|
136
|
+
temperature,
|
|
137
|
+
top_p,
|
|
138
|
+
tools,
|
|
139
|
+
disable,
|
|
140
|
+
mode,
|
|
141
|
+
color,
|
|
142
|
+
maxSteps,
|
|
143
|
+
permission
|
|
144
|
+
} = extractAgentFrontmatter(converted);
|
|
145
|
+
const config = {
|
|
133
146
|
description: description || `${agentInfo.name} agent`,
|
|
134
|
-
prompt
|
|
147
|
+
prompt
|
|
135
148
|
};
|
|
149
|
+
if (model !== undefined)
|
|
150
|
+
config.model = model;
|
|
151
|
+
if (temperature !== undefined)
|
|
152
|
+
config.temperature = temperature;
|
|
153
|
+
if (top_p !== undefined)
|
|
154
|
+
config.top_p = top_p;
|
|
155
|
+
if (tools !== undefined)
|
|
156
|
+
config.tools = tools;
|
|
157
|
+
if (disable !== undefined)
|
|
158
|
+
config.disable = disable;
|
|
159
|
+
if (mode !== undefined)
|
|
160
|
+
config.mode = mode;
|
|
161
|
+
if (color !== undefined)
|
|
162
|
+
config.color = color;
|
|
163
|
+
if (maxSteps !== undefined)
|
|
164
|
+
config.maxSteps = maxSteps;
|
|
165
|
+
if (permission !== undefined)
|
|
166
|
+
config.permission = permission;
|
|
167
|
+
return config;
|
|
136
168
|
} catch {
|
|
137
169
|
return null;
|
|
138
170
|
}
|
|
@@ -142,12 +174,20 @@ function loadCommandAsConfig(commandInfo) {
|
|
|
142
174
|
const converted = convertFileWithCache(commandInfo.file, "command", {
|
|
143
175
|
source: "bundled"
|
|
144
176
|
});
|
|
145
|
-
const { name, description } = extractCommandFrontmatter(converted);
|
|
177
|
+
const { name, description, agent, model, subtask } = extractCommandFrontmatter(converted);
|
|
178
|
+
const { body } = parseFrontmatter(converted);
|
|
146
179
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
147
|
-
|
|
148
|
-
template:
|
|
180
|
+
const config = {
|
|
181
|
+
template: body.trim(),
|
|
149
182
|
description: description || `${name || cleanName} command`
|
|
150
183
|
};
|
|
184
|
+
if (agent !== undefined)
|
|
185
|
+
config.agent = agent;
|
|
186
|
+
if (model !== undefined)
|
|
187
|
+
config.model = model;
|
|
188
|
+
if (subtask !== undefined)
|
|
189
|
+
config.subtask = subtask;
|
|
190
|
+
return config;
|
|
151
191
|
} catch {
|
|
152
192
|
return null;
|
|
153
193
|
}
|
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;
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fro.bot/systematic",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Structured engineering workflows for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@opencode-ai/plugin": "^1.1.30",
|
|
56
56
|
"@types/bun": "latest",
|
|
57
57
|
"@types/js-yaml": "^4.0.9",
|
|
58
|
-
"@types/node": "^
|
|
58
|
+
"@types/node": "^24.0.0",
|
|
59
59
|
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
60
60
|
"markdownlint-cli": "^0.47.0",
|
|
61
61
|
"rimraf": "^6.1.2",
|