@fro.bot/systematic 1.2.1 → 1.3.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -24
- package/dist/index-ymsavt2y.js +404 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +149 -216
- package/dist/lib/agents.d.ts +12 -0
- package/dist/lib/bootstrap.d.ts +5 -0
- package/dist/lib/commands.d.ts +12 -0
- package/dist/lib/config-handler.d.ts +17 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/converter.d.ts +12 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/skill-loader.d.ts +14 -0
- package/dist/lib/skill-tool.d.ts +6 -0
- package/dist/lib/skills.d.ts +13 -0
- package/dist/lib/walk-dir.d.ts +12 -0
- package/package.json +5 -3
- package/dist/index-hkk4125w.js +0 -324
package/dist/index.js
CHANGED
|
@@ -1,69 +1,124 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
convertContent,
|
|
4
3
|
convertFileWithCache,
|
|
5
4
|
extractAgentFrontmatter,
|
|
6
5
|
extractCommandFrontmatter,
|
|
7
6
|
findAgentsInDir,
|
|
8
7
|
findCommandsInDir,
|
|
9
8
|
findSkillsInDir,
|
|
9
|
+
formatSkillsXml,
|
|
10
|
+
loadConfig,
|
|
10
11
|
stripFrontmatter
|
|
11
|
-
} from "./index-
|
|
12
|
+
} from "./index-ymsavt2y.js";
|
|
12
13
|
|
|
13
14
|
// src/index.ts
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import path3 from "path";
|
|
15
|
+
import fs2 from "fs";
|
|
16
|
+
import path4 from "path";
|
|
17
17
|
import { fileURLToPath } from "url";
|
|
18
18
|
|
|
19
|
-
// src/lib/
|
|
19
|
+
// src/lib/bootstrap.ts
|
|
20
20
|
import fs from "fs";
|
|
21
|
-
import path from "path";
|
|
22
21
|
import os from "os";
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
import path from "path";
|
|
23
|
+
function getToolMappingTemplate(bundledSkillsDir) {
|
|
24
|
+
return `**Tool Mapping for OpenCode:**
|
|
25
|
+
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
26
|
+
- \`TodoWrite\` \u2192 \`update_plan\`
|
|
27
|
+
- \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
|
|
28
|
+
- \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
|
|
29
|
+
- \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
|
|
30
|
+
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
|
|
31
|
+
|
|
32
|
+
**Skills naming:**
|
|
33
|
+
- Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
|
|
34
|
+
- Skills can also be invoked without prefix if unambiguous
|
|
35
|
+
|
|
36
|
+
**Skills usage:**
|
|
37
|
+
- Use \`systematic_skill\` to load Systematic bundled skills
|
|
38
|
+
- Use the native \`skill\` tool for non-Systematic skills
|
|
39
|
+
|
|
40
|
+
**Skills location:**
|
|
41
|
+
Bundled skills are in \`${bundledSkillsDir}/\``;
|
|
42
|
+
}
|
|
43
|
+
function getBootstrapContent(config, deps) {
|
|
44
|
+
const { bundledSkillsDir } = deps;
|
|
45
|
+
if (!config.bootstrap.enabled)
|
|
46
|
+
return null;
|
|
47
|
+
if (config.bootstrap.file) {
|
|
48
|
+
const customPath = config.bootstrap.file.startsWith("~/") ? path.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
|
|
49
|
+
if (fs.existsSync(customPath)) {
|
|
50
|
+
return fs.readFileSync(customPath, "utf8");
|
|
51
|
+
}
|
|
30
52
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
if (!fs.existsSync(filePath))
|
|
35
|
-
return null;
|
|
36
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
37
|
-
return parseJsonc(content);
|
|
38
|
-
} catch {
|
|
53
|
+
const usingSystematicPath = path.join(bundledSkillsDir, "using-systematic/SKILL.md");
|
|
54
|
+
if (!fs.existsSync(usingSystematicPath))
|
|
39
55
|
return null;
|
|
56
|
+
const fullContent = fs.readFileSync(usingSystematicPath, "utf8");
|
|
57
|
+
const content = stripFrontmatter(fullContent);
|
|
58
|
+
const toolMapping = getToolMappingTemplate(bundledSkillsDir);
|
|
59
|
+
return `<SYSTEMATIC_WORKFLOWS>
|
|
60
|
+
You have access to structured engineering workflows via the systematic plugin.
|
|
61
|
+
|
|
62
|
+
**IMPORTANT: The using-systematic skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the systematic_skill tool to load "using-systematic" again - that would be redundant.**
|
|
63
|
+
|
|
64
|
+
${content}
|
|
65
|
+
|
|
66
|
+
${toolMapping}
|
|
67
|
+
</SYSTEMATIC_WORKFLOWS>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/skill-loader.ts
|
|
71
|
+
import path2 from "path";
|
|
72
|
+
var SKILL_PREFIX = "systematic:";
|
|
73
|
+
var SKILL_DESCRIPTION_PREFIX = "(systematic - Skill) ";
|
|
74
|
+
function formatSkillCommandName(name) {
|
|
75
|
+
if (name.startsWith(SKILL_PREFIX)) {
|
|
76
|
+
return name;
|
|
40
77
|
}
|
|
78
|
+
return `${SKILL_PREFIX}${name}`;
|
|
41
79
|
}
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return Array.from(set);
|
|
80
|
+
function formatSkillDescription(description, fallbackName) {
|
|
81
|
+
const desc = description || `${fallbackName} skill`;
|
|
82
|
+
if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
|
|
83
|
+
return desc;
|
|
84
|
+
}
|
|
85
|
+
return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
|
|
49
86
|
}
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
function wrapSkillTemplate(skillPath, body) {
|
|
88
|
+
const skillDir = path2.dirname(skillPath);
|
|
89
|
+
return `<skill-instruction>
|
|
90
|
+
Base directory for this skill: ${skillDir}/
|
|
91
|
+
File references (@path) in this skill are relative to this directory.
|
|
92
|
+
|
|
93
|
+
${body.trim()}
|
|
94
|
+
</skill-instruction>
|
|
95
|
+
|
|
96
|
+
<user-request>
|
|
97
|
+
$ARGUMENTS
|
|
98
|
+
</user-request>`;
|
|
99
|
+
}
|
|
100
|
+
function extractSkillBody(wrappedTemplate) {
|
|
101
|
+
const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
102
|
+
return match ? match[1].trim() : wrappedTemplate;
|
|
103
|
+
}
|
|
104
|
+
function loadSkill(skillInfo) {
|
|
105
|
+
try {
|
|
106
|
+
const converted = convertFileWithCache(skillInfo.skillFile, "skill", {
|
|
107
|
+
source: "bundled"
|
|
108
|
+
});
|
|
109
|
+
const body = stripFrontmatter(converted);
|
|
110
|
+
const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
|
|
111
|
+
return {
|
|
112
|
+
name: skillInfo.name,
|
|
113
|
+
prefixedName: formatSkillCommandName(skillInfo.name),
|
|
114
|
+
description: formatSkillDescription(skillInfo.description, skillInfo.name),
|
|
115
|
+
path: skillInfo.path,
|
|
116
|
+
skillFile: skillInfo.skillFile,
|
|
117
|
+
wrappedTemplate
|
|
118
|
+
};
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
67
122
|
}
|
|
68
123
|
|
|
69
124
|
// src/lib/config-handler.ts
|
|
@@ -84,7 +139,9 @@ function loadAgentAsConfig(agentInfo) {
|
|
|
84
139
|
}
|
|
85
140
|
function loadCommandAsConfig(commandInfo) {
|
|
86
141
|
try {
|
|
87
|
-
const converted = convertFileWithCache(commandInfo.file, "command", {
|
|
142
|
+
const converted = convertFileWithCache(commandInfo.file, "command", {
|
|
143
|
+
source: "bundled"
|
|
144
|
+
});
|
|
88
145
|
const { name, description } = extractCommandFrontmatter(converted);
|
|
89
146
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
90
147
|
return {
|
|
@@ -95,20 +152,15 @@ function loadCommandAsConfig(commandInfo) {
|
|
|
95
152
|
return null;
|
|
96
153
|
}
|
|
97
154
|
}
|
|
98
|
-
function loadSkillAsCommand(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
description: skillInfo.description || `${skillInfo.name} skill`
|
|
104
|
-
};
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
155
|
+
function loadSkillAsCommand(loaded) {
|
|
156
|
+
return {
|
|
157
|
+
template: loaded.wrappedTemplate,
|
|
158
|
+
description: loaded.description
|
|
159
|
+
};
|
|
108
160
|
}
|
|
109
|
-
function collectAgents(dir,
|
|
161
|
+
function collectAgents(dir, disabledAgents) {
|
|
110
162
|
const agents = {};
|
|
111
|
-
const agentList = findAgentsInDir(dir
|
|
163
|
+
const agentList = findAgentsInDir(dir);
|
|
112
164
|
for (const agentInfo of agentList) {
|
|
113
165
|
if (disabledAgents.includes(agentInfo.name))
|
|
114
166
|
continue;
|
|
@@ -119,9 +171,9 @@ function collectAgents(dir, sourceType, disabledAgents) {
|
|
|
119
171
|
}
|
|
120
172
|
return agents;
|
|
121
173
|
}
|
|
122
|
-
function collectCommands(dir,
|
|
174
|
+
function collectCommands(dir, disabledCommands) {
|
|
123
175
|
const commands = {};
|
|
124
|
-
const commandList = findCommandsInDir(dir
|
|
176
|
+
const commandList = findCommandsInDir(dir);
|
|
125
177
|
for (const commandInfo of commandList) {
|
|
126
178
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
127
179
|
if (disabledCommands.includes(cleanName))
|
|
@@ -133,15 +185,15 @@ function collectCommands(dir, sourceType, disabledCommands) {
|
|
|
133
185
|
}
|
|
134
186
|
return commands;
|
|
135
187
|
}
|
|
136
|
-
function collectSkillsAsCommands(dir,
|
|
188
|
+
function collectSkillsAsCommands(dir, disabledSkills) {
|
|
137
189
|
const commands = {};
|
|
138
|
-
const skillList = findSkillsInDir(dir
|
|
190
|
+
const skillList = findSkillsInDir(dir);
|
|
139
191
|
for (const skillInfo of skillList) {
|
|
140
192
|
if (disabledSkills.includes(skillInfo.name))
|
|
141
193
|
continue;
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
commands[
|
|
194
|
+
const loaded = loadSkill(skillInfo);
|
|
195
|
+
if (loaded) {
|
|
196
|
+
commands[loaded.prefixedName] = loadSkillAsCommand(loaded);
|
|
145
197
|
}
|
|
146
198
|
}
|
|
147
199
|
return commands;
|
|
@@ -150,9 +202,9 @@ function createConfigHandler(deps) {
|
|
|
150
202
|
const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
|
|
151
203
|
return async (config) => {
|
|
152
204
|
const systematicConfig = loadConfig(directory);
|
|
153
|
-
const bundledAgents = collectAgents(bundledAgentsDir,
|
|
154
|
-
const bundledCommands = collectCommands(bundledCommandsDir,
|
|
155
|
-
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir,
|
|
205
|
+
const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents);
|
|
206
|
+
const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
|
|
207
|
+
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
156
208
|
const existingAgents = config.agent ?? {};
|
|
157
209
|
config.agent = {
|
|
158
210
|
...bundledAgents,
|
|
@@ -168,92 +220,32 @@ function createConfigHandler(deps) {
|
|
|
168
220
|
}
|
|
169
221
|
|
|
170
222
|
// src/lib/skill-tool.ts
|
|
171
|
-
import
|
|
172
|
-
import path2 from "path";
|
|
223
|
+
import path3 from "path";
|
|
173
224
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
174
|
-
var HOOK_KEY = "systematic_skill_tool_hooked";
|
|
175
|
-
var SYSTEMATIC_MARKER = "__systematic_skill_tool__";
|
|
176
|
-
var globalStore = globalThis;
|
|
177
|
-
function getHookState() {
|
|
178
|
-
let state = globalStore[HOOK_KEY];
|
|
179
|
-
if (state == null) {
|
|
180
|
-
state = {
|
|
181
|
-
hookedTool: null,
|
|
182
|
-
hookedDescription: null,
|
|
183
|
-
initialized: false
|
|
184
|
-
};
|
|
185
|
-
globalStore[HOOK_KEY] = state;
|
|
186
|
-
}
|
|
187
|
-
return state;
|
|
188
|
-
}
|
|
189
|
-
function getHookedTool() {
|
|
190
|
-
return getHookState().hookedTool;
|
|
191
|
-
}
|
|
192
|
-
function formatSkillsXml(skills) {
|
|
193
|
-
if (skills.length === 0)
|
|
194
|
-
return "";
|
|
195
|
-
const skillsXml = skills.map((skill) => {
|
|
196
|
-
const lines = [
|
|
197
|
-
" <skill>",
|
|
198
|
-
` <name>systematic:${skill.name}</name>`,
|
|
199
|
-
` <description>${skill.description}</description>`
|
|
200
|
-
];
|
|
201
|
-
lines.push(" </skill>");
|
|
202
|
-
return lines.join(`
|
|
203
|
-
`);
|
|
204
|
-
}).join(`
|
|
205
|
-
`);
|
|
206
|
-
return `<available_skills>
|
|
207
|
-
${skillsXml}
|
|
208
|
-
</available_skills>`;
|
|
209
|
-
}
|
|
210
|
-
function mergeDescriptions(baseDescription, hookedDescription, systematicSkillsXml) {
|
|
211
|
-
if (hookedDescription == null || hookedDescription.trim() === "") {
|
|
212
|
-
return `${baseDescription}
|
|
213
|
-
|
|
214
|
-
${systematicSkillsXml}`;
|
|
215
|
-
}
|
|
216
|
-
const availableSkillsMatch = hookedDescription.match(/<available_skills>([\s\S]*?)<\/available_skills>/);
|
|
217
|
-
if (availableSkillsMatch) {
|
|
218
|
-
const existingSkillsContent = availableSkillsMatch[1];
|
|
219
|
-
const systematicSkillsContent = systematicSkillsXml.replace("<available_skills>", "").replace("</available_skills>", "").trim();
|
|
220
|
-
const mergedContent = `<available_skills>
|
|
221
|
-
${systematicSkillsContent}
|
|
222
|
-
${existingSkillsContent}</available_skills>`;
|
|
223
|
-
return hookedDescription.replace(/<available_skills>[\s\S]*?<\/available_skills>/, mergedContent);
|
|
224
|
-
}
|
|
225
|
-
return `${hookedDescription}
|
|
226
|
-
|
|
227
|
-
${systematicSkillsXml}`;
|
|
228
|
-
}
|
|
229
|
-
function wrapSkillContent(skillPath, content) {
|
|
230
|
-
const skillDir = path2.dirname(skillPath);
|
|
231
|
-
const converted = convertContent(content, "skill", { source: "bundled" });
|
|
232
|
-
const body = stripFrontmatter(converted);
|
|
233
|
-
return `<skill-instruction>
|
|
234
|
-
Base directory for this skill: ${skillDir}/
|
|
235
|
-
File references (@path) in this skill are relative to this directory.
|
|
236
|
-
|
|
237
|
-
${body.trim()}
|
|
238
|
-
</skill-instruction>`;
|
|
239
|
-
}
|
|
240
225
|
function createSkillTool(options) {
|
|
241
226
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
242
227
|
const getSystematicSkills = () => {
|
|
243
|
-
return findSkillsInDir(bundledSkillsDir
|
|
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));
|
|
244
229
|
};
|
|
245
230
|
const buildDescription = () => {
|
|
246
231
|
const skills = getSystematicSkills();
|
|
247
|
-
const
|
|
232
|
+
const skillInfos = skills.map((s) => ({
|
|
233
|
+
name: s.name,
|
|
234
|
+
description: s.description,
|
|
235
|
+
path: s.path,
|
|
236
|
+
skillFile: s.skillFile
|
|
237
|
+
}));
|
|
238
|
+
const systematicXml = formatSkillsXml(skillInfos);
|
|
248
239
|
const baseDescription = `Load a skill to get detailed instructions for a specific task.
|
|
249
240
|
|
|
250
241
|
Skills provide specialized knowledge and step-by-step guidance.
|
|
251
242
|
Use this when a task matches an available skill's description.`;
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
return `${baseDescription}
|
|
244
|
+
|
|
245
|
+
${systematicXml}`;
|
|
254
246
|
};
|
|
255
247
|
let cachedDescription = null;
|
|
256
|
-
|
|
248
|
+
return tool({
|
|
257
249
|
get description() {
|
|
258
250
|
if (cachedDescription == null) {
|
|
259
251
|
cachedDescription = buildDescription();
|
|
@@ -269,98 +261,39 @@ Use this when a task matches an available skill's description.`;
|
|
|
269
261
|
const skills = getSystematicSkills();
|
|
270
262
|
const matchedSkill = skills.find((s) => s.name === normalizedName);
|
|
271
263
|
if (matchedSkill) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return `## Skill: systematic:${matchedSkill.name}
|
|
264
|
+
const body = extractSkillBody(matchedSkill.wrappedTemplate);
|
|
265
|
+
const dir = path3.dirname(matchedSkill.skillFile);
|
|
266
|
+
return `## Skill: ${matchedSkill.prefixedName}
|
|
276
267
|
|
|
277
|
-
**Base directory**: ${
|
|
268
|
+
**Base directory**: ${dir}
|
|
278
269
|
|
|
279
|
-
${
|
|
280
|
-
} catch (error) {
|
|
281
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
-
throw new Error(`Failed to load skill "${requestedName}": ${errorMessage}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const hookedTool = getHookedTool();
|
|
286
|
-
if (hookedTool != null && typeof hookedTool.execute === "function") {
|
|
287
|
-
try {
|
|
288
|
-
return await hookedTool.execute(args);
|
|
289
|
-
} catch {}
|
|
270
|
+
${body}`;
|
|
290
271
|
}
|
|
291
|
-
const availableSystematic = skills.map((s) =>
|
|
272
|
+
const availableSystematic = skills.map((s) => s.prefixedName);
|
|
292
273
|
throw new Error(`Skill "${requestedName}" not found. Available systematic skills: ${availableSystematic.join(", ")}`);
|
|
293
274
|
}
|
|
294
275
|
});
|
|
295
|
-
Object.defineProperty(toolDef, SYSTEMATIC_MARKER, {
|
|
296
|
-
value: true,
|
|
297
|
-
enumerable: false,
|
|
298
|
-
writable: false
|
|
299
|
-
});
|
|
300
|
-
return toolDef;
|
|
301
276
|
}
|
|
302
277
|
|
|
303
278
|
// src/index.ts
|
|
304
|
-
var __dirname2 =
|
|
305
|
-
var packageRoot =
|
|
306
|
-
var bundledSkillsDir =
|
|
307
|
-
var bundledAgentsDir =
|
|
308
|
-
var bundledCommandsDir =
|
|
309
|
-
var packageJsonPath =
|
|
279
|
+
var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
|
|
280
|
+
var packageRoot = path4.resolve(__dirname2, "..");
|
|
281
|
+
var bundledSkillsDir = path4.join(packageRoot, "skills");
|
|
282
|
+
var bundledAgentsDir = path4.join(packageRoot, "agents");
|
|
283
|
+
var bundledCommandsDir = path4.join(packageRoot, "commands");
|
|
284
|
+
var packageJsonPath = path4.join(packageRoot, "package.json");
|
|
310
285
|
var hasLoggedInit = false;
|
|
311
286
|
var getPackageVersion = () => {
|
|
312
287
|
try {
|
|
313
|
-
if (!
|
|
288
|
+
if (!fs2.existsSync(packageJsonPath))
|
|
314
289
|
return "unknown";
|
|
315
|
-
const content =
|
|
290
|
+
const content = fs2.readFileSync(packageJsonPath, "utf8");
|
|
316
291
|
const parsed = JSON.parse(content);
|
|
317
292
|
return parsed.version ?? "unknown";
|
|
318
293
|
} catch {
|
|
319
294
|
return "unknown";
|
|
320
295
|
}
|
|
321
296
|
};
|
|
322
|
-
var getBootstrapContent = (config) => {
|
|
323
|
-
if (!config.bootstrap.enabled)
|
|
324
|
-
return null;
|
|
325
|
-
if (config.bootstrap.file) {
|
|
326
|
-
const customPath = config.bootstrap.file.startsWith("~/") ? path3.join(os2.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
|
|
327
|
-
if (fs3.existsSync(customPath)) {
|
|
328
|
-
return fs3.readFileSync(customPath, "utf8");
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const usingSystematicPath = path3.join(bundledSkillsDir, "using-systematic/SKILL.md");
|
|
332
|
-
if (!fs3.existsSync(usingSystematicPath))
|
|
333
|
-
return null;
|
|
334
|
-
const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
|
|
335
|
-
const content = stripFrontmatter(fullContent);
|
|
336
|
-
const toolMapping = `**Tool Mapping for OpenCode:**
|
|
337
|
-
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
338
|
-
- \`TodoWrite\` \u2192 \`update_plan\`
|
|
339
|
-
- \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
|
|
340
|
-
- \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
|
|
341
|
-
- \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
|
|
342
|
-
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
|
|
343
|
-
|
|
344
|
-
**Skills naming:**
|
|
345
|
-
- Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
|
|
346
|
-
- Skills can also be invoked without prefix if unambiguous
|
|
347
|
-
|
|
348
|
-
**Skills usage:**
|
|
349
|
-
- Use \`systematic_skill\` to load Systematic bundled skills
|
|
350
|
-
- Use the native \`skill\` tool for non-Systematic skills
|
|
351
|
-
|
|
352
|
-
**Skills location:**
|
|
353
|
-
Bundled skills are in \`${bundledSkillsDir}/\``;
|
|
354
|
-
return `<SYSTEMATIC_WORKFLOWS>
|
|
355
|
-
You have access to structured engineering workflows via the systematic plugin.
|
|
356
|
-
|
|
357
|
-
**IMPORTANT: The using-systematic skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the systematic_skill tool to load "using-systematic" again - that would be redundant.**
|
|
358
|
-
|
|
359
|
-
${content}
|
|
360
|
-
|
|
361
|
-
${toolMapping}
|
|
362
|
-
</SYSTEMATIC_WORKFLOWS>`;
|
|
363
|
-
};
|
|
364
297
|
var SystematicPlugin = async ({ client, directory }) => {
|
|
365
298
|
const config = loadConfig(directory);
|
|
366
299
|
const configHandler = createConfigHandler({
|
|
@@ -403,7 +336,7 @@ var SystematicPlugin = async ({ client, directory }) => {
|
|
|
403
336
|
if (existingSystem.includes("title generator") || existingSystem.includes("generate a title")) {
|
|
404
337
|
return;
|
|
405
338
|
}
|
|
406
|
-
const content = getBootstrapContent(config);
|
|
339
|
+
const content = getBootstrapContent(config, { bundledSkillsDir });
|
|
407
340
|
if (content) {
|
|
408
341
|
if (!output.system) {
|
|
409
342
|
output.system = [];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AgentFrontmatter {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
}
|
|
6
|
+
export interface AgentInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
file: string;
|
|
9
|
+
category?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function findAgentsInDir(dir: string, maxDepth?: number): AgentInfo[];
|
|
12
|
+
export declare function extractAgentFrontmatter(content: string): AgentFrontmatter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CommandFrontmatter {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
argumentHint: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CommandInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
file: string;
|
|
9
|
+
category?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function findCommandsInDir(dir: string, maxDepth?: number): CommandInfo[];
|
|
12
|
+
export declare function extractCommandFrontmatter(content: string): CommandFrontmatter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Config } from '@opencode-ai/sdk';
|
|
2
|
+
export interface ConfigHandlerDeps {
|
|
3
|
+
directory: string;
|
|
4
|
+
bundledSkillsDir: string;
|
|
5
|
+
bundledAgentsDir: string;
|
|
6
|
+
bundledCommandsDir: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create the config hook handler for the Systematic plugin.
|
|
10
|
+
*
|
|
11
|
+
* This follows the pattern used by oh-my-opencode to inject bundled agents,
|
|
12
|
+
* skills (as commands), and commands into OpenCode's configuration.
|
|
13
|
+
*
|
|
14
|
+
* Only bundled content is loaded. User/project overrides are not supported.
|
|
15
|
+
* Existing OpenCode config is preserved and takes precedence.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createConfigHandler(deps: ConfigHandlerDeps): (config: Config) => Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BootstrapConfig {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
file?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SystematicConfig {
|
|
6
|
+
disabled_skills: string[];
|
|
7
|
+
disabled_agents: string[];
|
|
8
|
+
disabled_commands: string[];
|
|
9
|
+
bootstrap: BootstrapConfig;
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_CONFIG: SystematicConfig;
|
|
12
|
+
export declare function loadConfig(projectDir: string): SystematicConfig;
|
|
13
|
+
export declare function getConfigPaths(projectDir: string): {
|
|
14
|
+
userConfig: string;
|
|
15
|
+
projectConfig: string;
|
|
16
|
+
userDir: string;
|
|
17
|
+
projectDir: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ContentType = 'skill' | 'agent' | 'command';
|
|
2
|
+
export type SourceType = 'bundled' | 'external';
|
|
3
|
+
export type AgentMode = 'primary' | 'subagent';
|
|
4
|
+
export interface ConvertOptions {
|
|
5
|
+
source?: SourceType;
|
|
6
|
+
agentMode?: AgentMode;
|
|
7
|
+
/** Skip body content transformations (tool names, paths, etc.) */
|
|
8
|
+
skipBodyTransform?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function convertContent(content: string, type: ContentType, options?: ConvertOptions): string;
|
|
11
|
+
export declare function convertFileWithCache(filePath: string, type: ContentType, options?: ConvertOptions): string;
|
|
12
|
+
export declare function clearConverterCache(): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface FrontmatterResult<T = Record<string, unknown>> {
|
|
2
|
+
data: T;
|
|
3
|
+
body: string;
|
|
4
|
+
hadFrontmatter: boolean;
|
|
5
|
+
parseError: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parses YAML frontmatter from Markdown content.
|
|
9
|
+
*
|
|
10
|
+
* Uses js-yaml with JSON_SCHEMA for security (prevents code execution via YAML tags).
|
|
11
|
+
* Supports all standard YAML keys including hyphenated ones (e.g., 'argument-hint').
|
|
12
|
+
*
|
|
13
|
+
* @param content - Markdown content with optional frontmatter
|
|
14
|
+
* @returns Parsed frontmatter data, body content, and parsing status
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseFrontmatter<T = Record<string, unknown>>(content: string): FrontmatterResult<T>;
|
|
17
|
+
export declare function formatFrontmatter(data: Record<string, unknown>): string;
|
|
18
|
+
export declare function stripFrontmatter(content: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SkillInfo } from './skills.js';
|
|
2
|
+
export interface LoadedSkill {
|
|
3
|
+
name: string;
|
|
4
|
+
prefixedName: string;
|
|
5
|
+
description: string;
|
|
6
|
+
path: string;
|
|
7
|
+
skillFile: string;
|
|
8
|
+
wrappedTemplate: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function formatSkillCommandName(name: string): string;
|
|
11
|
+
export declare function formatSkillDescription(description: string, fallbackName: string): string;
|
|
12
|
+
export declare function wrapSkillTemplate(skillPath: string, body: string): string;
|
|
13
|
+
export declare function extractSkillBody(wrappedTemplate: string): string;
|
|
14
|
+
export declare function loadSkill(skillInfo: SkillInfo): LoadedSkill | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SkillFrontmatter {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SkillInfo {
|
|
6
|
+
path: string;
|
|
7
|
+
skillFile: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function extractFrontmatter(filePath: string): SkillFrontmatter;
|
|
12
|
+
export declare function findSkillsInDir(dir: string, maxDepth?: number): SkillInfo[];
|
|
13
|
+
export declare function formatSkillsXml(skills: SkillInfo[]): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WalkEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
name: string;
|
|
4
|
+
isDirectory: boolean;
|
|
5
|
+
depth: number;
|
|
6
|
+
category?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WalkOptions {
|
|
9
|
+
maxDepth?: number;
|
|
10
|
+
filter?: (entry: WalkEntry) => boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function walkDir(rootDir: string, options?: WalkOptions): WalkEntry[];
|