@fro.bot/systematic 1.2.0 → 1.2.2
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 +18 -26
- package/dist/{index-33zyxync.js → index-hvkf19rd.js} +217 -133
- package/dist/index.d.ts +3 -0
- package/dist/index.js +71 -176
- 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 +10 -0
- package/dist/lib/frontmatter.d.ts +9 -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/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
2
3
|
import {
|
|
3
4
|
convertContent,
|
|
4
5
|
findAgentsInDir,
|
|
5
6
|
findCommandsInDir,
|
|
6
|
-
findSkillsInDir
|
|
7
|
-
|
|
7
|
+
findSkillsInDir,
|
|
8
|
+
getConfigPaths
|
|
9
|
+
} from "./index-hvkf19rd.js";
|
|
8
10
|
|
|
9
11
|
// src/cli.ts
|
|
10
|
-
import fs from "
|
|
11
|
-
import path from "
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
12
14
|
var VERSION = "0.1.0";
|
|
13
15
|
var HELP = `
|
|
14
16
|
systematic - OpenCode plugin for systematic engineering workflows
|
|
@@ -36,12 +38,6 @@ Examples:
|
|
|
36
38
|
systematic convert skill ./skills/my-skill/SKILL.md
|
|
37
39
|
systematic config show
|
|
38
40
|
`;
|
|
39
|
-
function getUserConfigDir() {
|
|
40
|
-
return path.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
|
|
41
|
-
}
|
|
42
|
-
function getProjectConfigDir() {
|
|
43
|
-
return path.join(process.cwd(), ".opencode");
|
|
44
|
-
}
|
|
45
41
|
function listItems(type) {
|
|
46
42
|
const packageRoot = path.resolve(import.meta.dirname, "..");
|
|
47
43
|
const bundledDir = packageRoot;
|
|
@@ -64,7 +60,7 @@ function listItems(type) {
|
|
|
64
60
|
console.error(`Unknown type: ${type}. Use: skills, agents, commands`);
|
|
65
61
|
process.exit(1);
|
|
66
62
|
}
|
|
67
|
-
const items = finder(path.join(bundledDir, subdir)
|
|
63
|
+
const items = finder(path.join(bundledDir, subdir));
|
|
68
64
|
if (items.length === 0) {
|
|
69
65
|
console.log(`No ${type} found.`);
|
|
70
66
|
return;
|
|
@@ -72,7 +68,7 @@ function listItems(type) {
|
|
|
72
68
|
console.log(`Available ${type}:
|
|
73
69
|
`);
|
|
74
70
|
for (const item of items.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
75
|
-
console.log(` ${item.name}
|
|
71
|
+
console.log(` ${item.name}`);
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
function runConvert(type, filePath, modeArg) {
|
|
@@ -101,31 +97,27 @@ function runConvert(type, filePath, modeArg) {
|
|
|
101
97
|
console.log(converted);
|
|
102
98
|
}
|
|
103
99
|
function configShow() {
|
|
104
|
-
const
|
|
105
|
-
const projectDir = getProjectConfigDir();
|
|
100
|
+
const paths = getConfigPaths(process.cwd());
|
|
106
101
|
console.log(`Configuration locations:
|
|
107
102
|
`);
|
|
108
|
-
console.log(` User config: ${
|
|
109
|
-
console.log(` Project config: ${
|
|
110
|
-
|
|
111
|
-
if (fs.existsSync(projectConfig)) {
|
|
103
|
+
console.log(` User config: ${paths.userConfig}`);
|
|
104
|
+
console.log(` Project config: ${paths.projectConfig}`);
|
|
105
|
+
if (fs.existsSync(paths.projectConfig)) {
|
|
112
106
|
console.log(`
|
|
113
107
|
Project configuration:`);
|
|
114
|
-
console.log(fs.readFileSync(projectConfig, "utf-8"));
|
|
108
|
+
console.log(fs.readFileSync(paths.projectConfig, "utf-8"));
|
|
115
109
|
}
|
|
116
|
-
|
|
117
|
-
if (fs.existsSync(userConfig)) {
|
|
110
|
+
if (fs.existsSync(paths.userConfig)) {
|
|
118
111
|
console.log(`
|
|
119
112
|
User configuration:`);
|
|
120
|
-
console.log(fs.readFileSync(userConfig, "utf-8"));
|
|
113
|
+
console.log(fs.readFileSync(paths.userConfig, "utf-8"));
|
|
121
114
|
}
|
|
122
115
|
}
|
|
123
116
|
function configPath() {
|
|
124
|
-
const
|
|
125
|
-
const projectDir = getProjectConfigDir();
|
|
117
|
+
const paths = getConfigPaths(process.cwd());
|
|
126
118
|
console.log("Config file paths:");
|
|
127
|
-
console.log(` User: ${
|
|
128
|
-
console.log(` Project: ${
|
|
119
|
+
console.log(` User: ${paths.userConfig}`);
|
|
120
|
+
console.log(` Project: ${paths.projectConfig}`);
|
|
129
121
|
}
|
|
130
122
|
var args = process.argv.slice(2);
|
|
131
123
|
var command = args[0];
|
|
@@ -1,6 +1,67 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/lib/config.ts
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
7
|
+
var DEFAULT_CONFIG = {
|
|
8
|
+
disabled_skills: [],
|
|
9
|
+
disabled_agents: [],
|
|
10
|
+
disabled_commands: [],
|
|
11
|
+
bootstrap: {
|
|
12
|
+
enabled: true
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function loadJsoncFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(filePath))
|
|
18
|
+
return null;
|
|
19
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
20
|
+
return parseJsonc(content);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mergeArraysUnique(arr1, arr2) {
|
|
26
|
+
const set = new Set;
|
|
27
|
+
if (arr1)
|
|
28
|
+
arr1.forEach((item) => set.add(item));
|
|
29
|
+
if (arr2)
|
|
30
|
+
arr2.forEach((item) => set.add(item));
|
|
31
|
+
return Array.from(set);
|
|
32
|
+
}
|
|
33
|
+
function loadConfig(projectDir) {
|
|
34
|
+
const homeDir = os.homedir();
|
|
35
|
+
const userConfigPath = path.join(homeDir, ".config/opencode/systematic.json");
|
|
36
|
+
const projectConfigPath = path.join(projectDir, ".opencode/systematic.json");
|
|
37
|
+
const userConfig = loadJsoncFile(userConfigPath);
|
|
38
|
+
const projectConfig = loadJsoncFile(projectConfigPath);
|
|
39
|
+
const result = {
|
|
40
|
+
disabled_skills: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills),
|
|
41
|
+
disabled_agents: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents),
|
|
42
|
+
disabled_commands: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands),
|
|
43
|
+
bootstrap: {
|
|
44
|
+
...DEFAULT_CONFIG.bootstrap,
|
|
45
|
+
...userConfig?.bootstrap,
|
|
46
|
+
...projectConfig?.bootstrap
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function getConfigPaths(projectDir) {
|
|
52
|
+
const homeDir = os.homedir();
|
|
53
|
+
return {
|
|
54
|
+
userConfig: path.join(homeDir, ".config/opencode/systematic.json"),
|
|
55
|
+
projectConfig: path.join(projectDir, ".opencode/systematic.json"),
|
|
56
|
+
userDir: path.join(homeDir, ".config/opencode/systematic"),
|
|
57
|
+
projectDir: path.join(projectDir, ".opencode/systematic")
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
1
61
|
// src/lib/converter.ts
|
|
2
|
-
import
|
|
3
|
-
|
|
62
|
+
import fs2 from "fs";
|
|
63
|
+
|
|
64
|
+
// src/lib/frontmatter.ts
|
|
4
65
|
function parseFrontmatter(content) {
|
|
5
66
|
const lines = content.split(/\r?\n/);
|
|
6
67
|
if (lines.length === 0 || lines[0].trim() !== "---") {
|
|
@@ -47,6 +108,31 @@ function formatFrontmatter(data) {
|
|
|
47
108
|
return lines.join(`
|
|
48
109
|
`);
|
|
49
110
|
}
|
|
111
|
+
function stripFrontmatter(content) {
|
|
112
|
+
const lines = content.split(`
|
|
113
|
+
`);
|
|
114
|
+
let inFrontmatter = false;
|
|
115
|
+
let frontmatterEnded = false;
|
|
116
|
+
const contentLines = [];
|
|
117
|
+
for (const line of lines) {
|
|
118
|
+
if (line.trim() === "---") {
|
|
119
|
+
if (inFrontmatter) {
|
|
120
|
+
frontmatterEnded = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
inFrontmatter = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (frontmatterEnded || !inFrontmatter) {
|
|
127
|
+
contentLines.push(line);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return contentLines.join(`
|
|
131
|
+
`).trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/lib/converter.ts
|
|
135
|
+
var cache = new Map;
|
|
50
136
|
function inferTemperature(name, description) {
|
|
51
137
|
const sample = `${name} ${description ?? ""}`.toLowerCase();
|
|
52
138
|
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
@@ -110,159 +196,67 @@ ${body}`;
|
|
|
110
196
|
return content;
|
|
111
197
|
}
|
|
112
198
|
function convertFileWithCache(filePath, type, options = {}) {
|
|
113
|
-
const fd =
|
|
199
|
+
const fd = fs2.openSync(filePath, "r");
|
|
114
200
|
try {
|
|
115
|
-
const stats =
|
|
201
|
+
const stats = fs2.fstatSync(fd);
|
|
116
202
|
const cacheKey = `${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}`;
|
|
117
203
|
const cached = cache.get(cacheKey);
|
|
118
204
|
if (cached != null && cached.mtimeMs === stats.mtimeMs) {
|
|
119
205
|
return cached.converted;
|
|
120
206
|
}
|
|
121
|
-
const content =
|
|
207
|
+
const content = fs2.readFileSync(fd, "utf8");
|
|
122
208
|
const converted = convertContent(content, type, options);
|
|
123
209
|
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
124
210
|
return converted;
|
|
125
211
|
} finally {
|
|
126
|
-
|
|
212
|
+
fs2.closeSync(fd);
|
|
127
213
|
}
|
|
128
214
|
}
|
|
129
215
|
|
|
130
|
-
// src/lib/
|
|
131
|
-
import
|
|
132
|
-
import
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
let inFrontmatter = false;
|
|
139
|
-
let name = "";
|
|
140
|
-
let description = "";
|
|
141
|
-
for (const line of lines) {
|
|
142
|
-
if (line.trim() === "---") {
|
|
143
|
-
if (inFrontmatter)
|
|
144
|
-
break;
|
|
145
|
-
inFrontmatter = true;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (inFrontmatter) {
|
|
149
|
-
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
150
|
-
if (match) {
|
|
151
|
-
const [, key, value] = match;
|
|
152
|
-
if (key === "name")
|
|
153
|
-
name = value.trim();
|
|
154
|
-
if (key === "description")
|
|
155
|
-
description = value.trim();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return { name, description };
|
|
160
|
-
} catch {
|
|
161
|
-
return { name: "", description: "" };
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function stripFrontmatter(content) {
|
|
165
|
-
const lines = content.split(`
|
|
166
|
-
`);
|
|
167
|
-
let inFrontmatter = false;
|
|
168
|
-
let frontmatterEnded = false;
|
|
169
|
-
const contentLines = [];
|
|
170
|
-
for (const line of lines) {
|
|
171
|
-
if (line.trim() === "---") {
|
|
172
|
-
if (inFrontmatter) {
|
|
173
|
-
frontmatterEnded = true;
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
inFrontmatter = true;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (frontmatterEnded || !inFrontmatter) {
|
|
180
|
-
contentLines.push(line);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return contentLines.join(`
|
|
184
|
-
`).trim();
|
|
185
|
-
}
|
|
186
|
-
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
|
187
|
-
const skills = [];
|
|
188
|
-
if (!fs2.existsSync(dir))
|
|
189
|
-
return skills;
|
|
190
|
-
function recurse(currentDir, depth) {
|
|
191
|
-
if (depth > maxDepth)
|
|
192
|
-
return;
|
|
193
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
194
|
-
for (const entry of entries) {
|
|
195
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
196
|
-
if (entry.isDirectory()) {
|
|
197
|
-
const skillFile = path.join(fullPath, "SKILL.md");
|
|
198
|
-
if (fs2.existsSync(skillFile)) {
|
|
199
|
-
const { name, description } = extractFrontmatter(skillFile);
|
|
200
|
-
skills.push({
|
|
201
|
-
path: fullPath,
|
|
202
|
-
skillFile,
|
|
203
|
-
name: name || entry.name,
|
|
204
|
-
description: description || "",
|
|
205
|
-
sourceType
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
recurse(fullPath, depth + 1);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
recurse(dir, 0);
|
|
213
|
-
return skills;
|
|
214
|
-
}
|
|
215
|
-
function findAgentsInDir(dir, sourceType, maxDepth = 2) {
|
|
216
|
-
const agents = [];
|
|
217
|
-
if (!fs2.existsSync(dir))
|
|
218
|
-
return agents;
|
|
216
|
+
// src/lib/walk-dir.ts
|
|
217
|
+
import fs3 from "fs";
|
|
218
|
+
import path2 from "path";
|
|
219
|
+
function walkDir(rootDir, options = {}) {
|
|
220
|
+
const { maxDepth = 3, filter } = options;
|
|
221
|
+
const results = [];
|
|
222
|
+
if (!fs3.existsSync(rootDir))
|
|
223
|
+
return results;
|
|
219
224
|
function recurse(currentDir, depth, category) {
|
|
220
225
|
if (depth > maxDepth)
|
|
221
226
|
return;
|
|
222
|
-
const entries =
|
|
227
|
+
const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
|
|
223
228
|
for (const entry of entries) {
|
|
224
|
-
const fullPath =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
230
|
+
const walkEntry = {
|
|
231
|
+
path: fullPath,
|
|
232
|
+
name: entry.name,
|
|
233
|
+
isDirectory: entry.isDirectory(),
|
|
234
|
+
depth,
|
|
235
|
+
category
|
|
236
|
+
};
|
|
237
|
+
if (!filter || filter(walkEntry)) {
|
|
238
|
+
results.push(walkEntry);
|
|
234
239
|
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
recurse(dir, 0);
|
|
238
|
-
return agents;
|
|
239
|
-
}
|
|
240
|
-
function findCommandsInDir(dir, sourceType, maxDepth = 2) {
|
|
241
|
-
const commands = [];
|
|
242
|
-
if (!fs2.existsSync(dir))
|
|
243
|
-
return commands;
|
|
244
|
-
function recurse(currentDir, depth, category) {
|
|
245
|
-
if (depth > maxDepth)
|
|
246
|
-
return;
|
|
247
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
248
|
-
for (const entry of entries) {
|
|
249
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
250
240
|
if (entry.isDirectory()) {
|
|
251
241
|
recurse(fullPath, depth + 1, entry.name);
|
|
252
|
-
} else if (entry.name.endsWith(".md")) {
|
|
253
|
-
const baseName = entry.name.replace(/\.md$/, "");
|
|
254
|
-
const commandName = category ? `/${category}:${baseName}` : `/${baseName}`;
|
|
255
|
-
commands.push({
|
|
256
|
-
name: commandName,
|
|
257
|
-
file: fullPath,
|
|
258
|
-
sourceType,
|
|
259
|
-
category
|
|
260
|
-
});
|
|
261
242
|
}
|
|
262
243
|
}
|
|
263
244
|
}
|
|
264
|
-
recurse(
|
|
265
|
-
return
|
|
245
|
+
recurse(rootDir, 0);
|
|
246
|
+
return results;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/lib/agents.ts
|
|
250
|
+
function findAgentsInDir(dir, maxDepth = 2) {
|
|
251
|
+
const entries = walkDir(dir, {
|
|
252
|
+
maxDepth,
|
|
253
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
254
|
+
});
|
|
255
|
+
return entries.map((entry) => ({
|
|
256
|
+
name: entry.name.replace(/\.md$/, ""),
|
|
257
|
+
file: entry.path,
|
|
258
|
+
category: entry.category
|
|
259
|
+
}));
|
|
266
260
|
}
|
|
267
261
|
function extractAgentFrontmatter(content) {
|
|
268
262
|
const lines = content.split(`
|
|
@@ -290,6 +284,23 @@ function extractAgentFrontmatter(content) {
|
|
|
290
284
|
}
|
|
291
285
|
return { name, description, prompt: stripFrontmatter(content) };
|
|
292
286
|
}
|
|
287
|
+
|
|
288
|
+
// src/lib/commands.ts
|
|
289
|
+
function findCommandsInDir(dir, maxDepth = 2) {
|
|
290
|
+
const entries = walkDir(dir, {
|
|
291
|
+
maxDepth,
|
|
292
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
293
|
+
});
|
|
294
|
+
return entries.map((entry) => {
|
|
295
|
+
const baseName = entry.name.replace(/\.md$/, "");
|
|
296
|
+
const commandName = entry.category ? `/${entry.category}:${baseName}` : `/${baseName}`;
|
|
297
|
+
return {
|
|
298
|
+
name: commandName,
|
|
299
|
+
file: entry.path,
|
|
300
|
+
category: entry.category
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
}
|
|
293
304
|
function extractCommandFrontmatter(content) {
|
|
294
305
|
const lines = content.split(`
|
|
295
306
|
`);
|
|
@@ -320,4 +331,77 @@ function extractCommandFrontmatter(content) {
|
|
|
320
331
|
return { name, description, argumentHint };
|
|
321
332
|
}
|
|
322
333
|
|
|
323
|
-
|
|
334
|
+
// src/lib/skills.ts
|
|
335
|
+
import fs4 from "fs";
|
|
336
|
+
import path3 from "path";
|
|
337
|
+
function extractFrontmatter(filePath) {
|
|
338
|
+
try {
|
|
339
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
340
|
+
const lines = content.split(`
|
|
341
|
+
`);
|
|
342
|
+
let inFrontmatter = false;
|
|
343
|
+
let name = "";
|
|
344
|
+
let description = "";
|
|
345
|
+
for (const line of lines) {
|
|
346
|
+
if (line.trim() === "---") {
|
|
347
|
+
if (inFrontmatter)
|
|
348
|
+
break;
|
|
349
|
+
inFrontmatter = true;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (inFrontmatter) {
|
|
353
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
354
|
+
if (match) {
|
|
355
|
+
const [, key, value] = match;
|
|
356
|
+
if (key === "name")
|
|
357
|
+
name = value.trim();
|
|
358
|
+
if (key === "description")
|
|
359
|
+
description = value.trim();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { name, description };
|
|
364
|
+
} catch {
|
|
365
|
+
return { name: "", description: "" };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function findSkillsInDir(dir, maxDepth = 3) {
|
|
369
|
+
const skills = [];
|
|
370
|
+
const entries = walkDir(dir, {
|
|
371
|
+
maxDepth,
|
|
372
|
+
filter: (e) => e.isDirectory
|
|
373
|
+
});
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
const skillFile = path3.join(entry.path, "SKILL.md");
|
|
376
|
+
if (fs4.existsSync(skillFile)) {
|
|
377
|
+
const { name, description } = extractFrontmatter(skillFile);
|
|
378
|
+
skills.push({
|
|
379
|
+
path: entry.path,
|
|
380
|
+
skillFile,
|
|
381
|
+
name: name || entry.name,
|
|
382
|
+
description: description || ""
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return skills;
|
|
387
|
+
}
|
|
388
|
+
function formatSkillsXml(skills) {
|
|
389
|
+
if (skills.length === 0)
|
|
390
|
+
return "";
|
|
391
|
+
const skillsXml = skills.map((skill) => {
|
|
392
|
+
const lines = [
|
|
393
|
+
" <skill>",
|
|
394
|
+
` <name>systematic:${skill.name}</name>`,
|
|
395
|
+
` <description>${skill.description}</description>`
|
|
396
|
+
];
|
|
397
|
+
lines.push(" </skill>");
|
|
398
|
+
return lines.join(`
|
|
399
|
+
`);
|
|
400
|
+
}).join(`
|
|
401
|
+
`);
|
|
402
|
+
return `<available_skills>
|
|
403
|
+
${skillsXml}
|
|
404
|
+
</available_skills>`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export { stripFrontmatter, loadConfig, getConfigPaths, convertContent, convertFileWithCache, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, findSkillsInDir, formatSkillsXml };
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @bun
|
|
1
2
|
import {
|
|
2
3
|
convertContent,
|
|
3
4
|
convertFileWithCache,
|
|
@@ -6,63 +7,65 @@ import {
|
|
|
6
7
|
findAgentsInDir,
|
|
7
8
|
findCommandsInDir,
|
|
8
9
|
findSkillsInDir,
|
|
10
|
+
formatSkillsXml,
|
|
11
|
+
loadConfig,
|
|
9
12
|
stripFrontmatter
|
|
10
|
-
} from "./index-
|
|
13
|
+
} from "./index-hvkf19rd.js";
|
|
11
14
|
|
|
12
15
|
// src/index.ts
|
|
13
|
-
import fs3 from "
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
+
import fs3 from "fs";
|
|
17
|
+
import path3 from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
17
19
|
|
|
18
|
-
// src/lib/
|
|
19
|
-
import fs from "
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
function mergeArraysUnique(arr1, arr2) {
|
|
42
|
-
const set = new Set;
|
|
43
|
-
if (arr1)
|
|
44
|
-
arr1.forEach((item) => set.add(item));
|
|
45
|
-
if (arr2)
|
|
46
|
-
arr2.forEach((item) => set.add(item));
|
|
47
|
-
return Array.from(set);
|
|
20
|
+
// src/lib/bootstrap.ts
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import os from "os";
|
|
23
|
+
import path from "path";
|
|
24
|
+
function getToolMappingTemplate(bundledSkillsDir) {
|
|
25
|
+
return `**Tool Mapping for OpenCode:**
|
|
26
|
+
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
27
|
+
- \`TodoWrite\` \u2192 \`update_plan\`
|
|
28
|
+
- \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
|
|
29
|
+
- \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
|
|
30
|
+
- \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
|
|
31
|
+
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` \u2192 Your native tools
|
|
32
|
+
|
|
33
|
+
**Skills naming:**
|
|
34
|
+
- Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
|
|
35
|
+
- Skills can also be invoked without prefix if unambiguous
|
|
36
|
+
|
|
37
|
+
**Skills usage:**
|
|
38
|
+
- Use \`systematic_skill\` to load Systematic bundled skills
|
|
39
|
+
- Use the native \`skill\` tool for non-Systematic skills
|
|
40
|
+
|
|
41
|
+
**Skills location:**
|
|
42
|
+
Bundled skills are in \`${bundledSkillsDir}/\``;
|
|
48
43
|
}
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
disabled_agents: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents),
|
|
58
|
-
disabled_commands: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands),
|
|
59
|
-
bootstrap: {
|
|
60
|
-
...DEFAULT_CONFIG.bootstrap,
|
|
61
|
-
...userConfig?.bootstrap,
|
|
62
|
-
...projectConfig?.bootstrap
|
|
44
|
+
function getBootstrapContent(config, deps) {
|
|
45
|
+
const { bundledSkillsDir } = deps;
|
|
46
|
+
if (!config.bootstrap.enabled)
|
|
47
|
+
return null;
|
|
48
|
+
if (config.bootstrap.file) {
|
|
49
|
+
const customPath = config.bootstrap.file.startsWith("~/") ? path.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
|
|
50
|
+
if (fs.existsSync(customPath)) {
|
|
51
|
+
return fs.readFileSync(customPath, "utf8");
|
|
63
52
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
53
|
+
}
|
|
54
|
+
const usingSystematicPath = path.join(bundledSkillsDir, "using-systematic/SKILL.md");
|
|
55
|
+
if (!fs.existsSync(usingSystematicPath))
|
|
56
|
+
return null;
|
|
57
|
+
const fullContent = fs.readFileSync(usingSystematicPath, "utf8");
|
|
58
|
+
const content = stripFrontmatter(fullContent);
|
|
59
|
+
const toolMapping = getToolMappingTemplate(bundledSkillsDir);
|
|
60
|
+
return `<SYSTEMATIC_WORKFLOWS>
|
|
61
|
+
You have access to structured engineering workflows via the systematic plugin.
|
|
62
|
+
|
|
63
|
+
**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.**
|
|
64
|
+
|
|
65
|
+
${content}
|
|
66
|
+
|
|
67
|
+
${toolMapping}
|
|
68
|
+
</SYSTEMATIC_WORKFLOWS>`;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
// src/lib/config-handler.ts
|
|
@@ -105,9 +108,9 @@ function loadSkillAsCommand(skillInfo) {
|
|
|
105
108
|
return null;
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
|
-
function collectAgents(dir,
|
|
111
|
+
function collectAgents(dir, disabledAgents) {
|
|
109
112
|
const agents = {};
|
|
110
|
-
const agentList = findAgentsInDir(dir
|
|
113
|
+
const agentList = findAgentsInDir(dir);
|
|
111
114
|
for (const agentInfo of agentList) {
|
|
112
115
|
if (disabledAgents.includes(agentInfo.name))
|
|
113
116
|
continue;
|
|
@@ -118,9 +121,9 @@ function collectAgents(dir, sourceType, disabledAgents) {
|
|
|
118
121
|
}
|
|
119
122
|
return agents;
|
|
120
123
|
}
|
|
121
|
-
function collectCommands(dir,
|
|
124
|
+
function collectCommands(dir, disabledCommands) {
|
|
122
125
|
const commands = {};
|
|
123
|
-
const commandList = findCommandsInDir(dir
|
|
126
|
+
const commandList = findCommandsInDir(dir);
|
|
124
127
|
for (const commandInfo of commandList) {
|
|
125
128
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
126
129
|
if (disabledCommands.includes(cleanName))
|
|
@@ -132,9 +135,9 @@ function collectCommands(dir, sourceType, disabledCommands) {
|
|
|
132
135
|
}
|
|
133
136
|
return commands;
|
|
134
137
|
}
|
|
135
|
-
function collectSkillsAsCommands(dir,
|
|
138
|
+
function collectSkillsAsCommands(dir, disabledSkills) {
|
|
136
139
|
const commands = {};
|
|
137
|
-
const skillList = findSkillsInDir(dir
|
|
140
|
+
const skillList = findSkillsInDir(dir);
|
|
138
141
|
for (const skillInfo of skillList) {
|
|
139
142
|
if (disabledSkills.includes(skillInfo.name))
|
|
140
143
|
continue;
|
|
@@ -149,9 +152,9 @@ function createConfigHandler(deps) {
|
|
|
149
152
|
const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
|
|
150
153
|
return async (config) => {
|
|
151
154
|
const systematicConfig = loadConfig(directory);
|
|
152
|
-
const bundledAgents = collectAgents(bundledAgentsDir,
|
|
153
|
-
const bundledCommands = collectCommands(bundledCommandsDir,
|
|
154
|
-
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir,
|
|
155
|
+
const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents);
|
|
156
|
+
const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
|
|
157
|
+
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
155
158
|
const existingAgents = config.agent ?? {};
|
|
156
159
|
config.agent = {
|
|
157
160
|
...bundledAgents,
|
|
@@ -167,64 +170,9 @@ function createConfigHandler(deps) {
|
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
// src/lib/skill-tool.ts
|
|
170
|
-
import fs2 from "
|
|
171
|
-
import path2 from "
|
|
173
|
+
import fs2 from "fs";
|
|
174
|
+
import path2 from "path";
|
|
172
175
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
173
|
-
var HOOK_KEY = "systematic_skill_tool_hooked";
|
|
174
|
-
var SYSTEMATIC_MARKER = "__systematic_skill_tool__";
|
|
175
|
-
var globalStore = globalThis;
|
|
176
|
-
function getHookState() {
|
|
177
|
-
let state = globalStore[HOOK_KEY];
|
|
178
|
-
if (state == null) {
|
|
179
|
-
state = {
|
|
180
|
-
hookedTool: null,
|
|
181
|
-
hookedDescription: null,
|
|
182
|
-
initialized: false
|
|
183
|
-
};
|
|
184
|
-
globalStore[HOOK_KEY] = state;
|
|
185
|
-
}
|
|
186
|
-
return state;
|
|
187
|
-
}
|
|
188
|
-
function getHookedTool() {
|
|
189
|
-
return getHookState().hookedTool;
|
|
190
|
-
}
|
|
191
|
-
function formatSkillsXml(skills) {
|
|
192
|
-
if (skills.length === 0)
|
|
193
|
-
return "";
|
|
194
|
-
const skillsXml = skills.map((skill) => {
|
|
195
|
-
const lines = [
|
|
196
|
-
" <skill>",
|
|
197
|
-
` <name>systematic:${skill.name}</name>`,
|
|
198
|
-
` <description>${skill.description}</description>`
|
|
199
|
-
];
|
|
200
|
-
lines.push(" </skill>");
|
|
201
|
-
return lines.join(`
|
|
202
|
-
`);
|
|
203
|
-
}).join(`
|
|
204
|
-
`);
|
|
205
|
-
return `<available_skills>
|
|
206
|
-
${skillsXml}
|
|
207
|
-
</available_skills>`;
|
|
208
|
-
}
|
|
209
|
-
function mergeDescriptions(baseDescription, hookedDescription, systematicSkillsXml) {
|
|
210
|
-
if (hookedDescription == null || hookedDescription.trim() === "") {
|
|
211
|
-
return `${baseDescription}
|
|
212
|
-
|
|
213
|
-
${systematicSkillsXml}`;
|
|
214
|
-
}
|
|
215
|
-
const availableSkillsMatch = hookedDescription.match(/<available_skills>([\s\S]*?)<\/available_skills>/);
|
|
216
|
-
if (availableSkillsMatch) {
|
|
217
|
-
const existingSkillsContent = availableSkillsMatch[1];
|
|
218
|
-
const systematicSkillsContent = systematicSkillsXml.replace("<available_skills>", "").replace("</available_skills>", "").trim();
|
|
219
|
-
const mergedContent = `<available_skills>
|
|
220
|
-
${systematicSkillsContent}
|
|
221
|
-
${existingSkillsContent}</available_skills>`;
|
|
222
|
-
return hookedDescription.replace(/<available_skills>[\s\S]*?<\/available_skills>/, mergedContent);
|
|
223
|
-
}
|
|
224
|
-
return `${hookedDescription}
|
|
225
|
-
|
|
226
|
-
${systematicSkillsXml}`;
|
|
227
|
-
}
|
|
228
176
|
function wrapSkillContent(skillPath, content) {
|
|
229
177
|
const skillDir = path2.dirname(skillPath);
|
|
230
178
|
const converted = convertContent(content, "skill", { source: "bundled" });
|
|
@@ -239,7 +187,7 @@ ${body.trim()}
|
|
|
239
187
|
function createSkillTool(options) {
|
|
240
188
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
241
189
|
const getSystematicSkills = () => {
|
|
242
|
-
return findSkillsInDir(bundledSkillsDir
|
|
190
|
+
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
243
191
|
};
|
|
244
192
|
const buildDescription = () => {
|
|
245
193
|
const skills = getSystematicSkills();
|
|
@@ -248,11 +196,12 @@ function createSkillTool(options) {
|
|
|
248
196
|
|
|
249
197
|
Skills provide specialized knowledge and step-by-step guidance.
|
|
250
198
|
Use this when a task matches an available skill's description.`;
|
|
251
|
-
|
|
252
|
-
|
|
199
|
+
return `${baseDescription}
|
|
200
|
+
|
|
201
|
+
${systematicXml}`;
|
|
253
202
|
};
|
|
254
203
|
let cachedDescription = null;
|
|
255
|
-
|
|
204
|
+
return tool({
|
|
256
205
|
get description() {
|
|
257
206
|
if (cachedDescription == null) {
|
|
258
207
|
cachedDescription = buildDescription();
|
|
@@ -281,22 +230,10 @@ ${wrapped}`;
|
|
|
281
230
|
throw new Error(`Failed to load skill "${requestedName}": ${errorMessage}`);
|
|
282
231
|
}
|
|
283
232
|
}
|
|
284
|
-
const hookedTool = getHookedTool();
|
|
285
|
-
if (hookedTool != null && typeof hookedTool.execute === "function") {
|
|
286
|
-
try {
|
|
287
|
-
return await hookedTool.execute(args);
|
|
288
|
-
} catch {}
|
|
289
|
-
}
|
|
290
233
|
const availableSystematic = skills.map((s) => `systematic:${s.name}`);
|
|
291
234
|
throw new Error(`Skill "${requestedName}" not found. Available systematic skills: ${availableSystematic.join(", ")}`);
|
|
292
235
|
}
|
|
293
236
|
});
|
|
294
|
-
Object.defineProperty(toolDef, SYSTEMATIC_MARKER, {
|
|
295
|
-
value: true,
|
|
296
|
-
enumerable: false,
|
|
297
|
-
writable: false
|
|
298
|
-
});
|
|
299
|
-
return toolDef;
|
|
300
237
|
}
|
|
301
238
|
|
|
302
239
|
// src/index.ts
|
|
@@ -318,48 +255,6 @@ var getPackageVersion = () => {
|
|
|
318
255
|
return "unknown";
|
|
319
256
|
}
|
|
320
257
|
};
|
|
321
|
-
var getBootstrapContent = (config) => {
|
|
322
|
-
if (!config.bootstrap.enabled)
|
|
323
|
-
return null;
|
|
324
|
-
if (config.bootstrap.file) {
|
|
325
|
-
const customPath = config.bootstrap.file.startsWith("~/") ? path3.join(os2.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
|
|
326
|
-
if (fs3.existsSync(customPath)) {
|
|
327
|
-
return fs3.readFileSync(customPath, "utf8");
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
const usingSystematicPath = path3.join(bundledSkillsDir, "using-systematic/SKILL.md");
|
|
331
|
-
if (!fs3.existsSync(usingSystematicPath))
|
|
332
|
-
return null;
|
|
333
|
-
const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
|
|
334
|
-
const content = stripFrontmatter(fullContent);
|
|
335
|
-
const toolMapping = `**Tool Mapping for OpenCode:**
|
|
336
|
-
When skills reference tools you don't have, substitute OpenCode equivalents:
|
|
337
|
-
- \`TodoWrite\` → \`update_plan\`
|
|
338
|
-
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
|
339
|
-
- \`Skill\` tool → OpenCode's native \`skill\` tool
|
|
340
|
-
- \`SystematicSkill\` tool → \`systematic_skill\` (Systematic plugin skills)
|
|
341
|
-
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
|
342
|
-
|
|
343
|
-
**Skills naming:**
|
|
344
|
-
- Bundled skills use the \`systematic:\` prefix (e.g., \`systematic:brainstorming\`)
|
|
345
|
-
- Skills can also be invoked without prefix if unambiguous
|
|
346
|
-
|
|
347
|
-
**Skills usage:**
|
|
348
|
-
- Use \`systematic_skill\` to load Systematic bundled skills
|
|
349
|
-
- Use the native \`skill\` tool for non-Systematic skills
|
|
350
|
-
|
|
351
|
-
**Skills location:**
|
|
352
|
-
Bundled skills are in \`${bundledSkillsDir}/\``;
|
|
353
|
-
return `<SYSTEMATIC_WORKFLOWS>
|
|
354
|
-
You have access to structured engineering workflows via the systematic plugin.
|
|
355
|
-
|
|
356
|
-
**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.**
|
|
357
|
-
|
|
358
|
-
${content}
|
|
359
|
-
|
|
360
|
-
${toolMapping}
|
|
361
|
-
</SYSTEMATIC_WORKFLOWS>`;
|
|
362
|
-
};
|
|
363
258
|
var SystematicPlugin = async ({ client, directory }) => {
|
|
364
259
|
const config = loadConfig(directory);
|
|
365
260
|
const configHandler = createConfigHandler({
|
|
@@ -402,7 +297,7 @@ var SystematicPlugin = async ({ client, directory }) => {
|
|
|
402
297
|
if (existingSystem.includes("title generator") || existingSystem.includes("generate a title")) {
|
|
403
298
|
return;
|
|
404
299
|
}
|
|
405
|
-
const content = getBootstrapContent(config);
|
|
300
|
+
const content = getBootstrapContent(config, { bundledSkillsDir });
|
|
406
301
|
if (content) {
|
|
407
302
|
if (!output.system) {
|
|
408
303
|
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,10 @@
|
|
|
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
|
+
}
|
|
8
|
+
export declare function convertContent(content: string, type: ContentType, options?: ConvertOptions): string;
|
|
9
|
+
export declare function convertFileWithCache(filePath: string, type: ContentType, options?: ConvertOptions): string;
|
|
10
|
+
export declare function clearConverterCache(): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ParsedFrontmatter {
|
|
2
|
+
data: Record<string, string | number | boolean>;
|
|
3
|
+
body: string;
|
|
4
|
+
raw: string;
|
|
5
|
+
}
|
|
6
|
+
export type { ParsedFrontmatter };
|
|
7
|
+
export declare function parseFrontmatter(content: string): ParsedFrontmatter;
|
|
8
|
+
export declare function formatFrontmatter(data: Record<string, string | number | boolean>): string;
|
|
9
|
+
export declare function stripFrontmatter(content: string): string;
|
|
@@ -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[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fro.bot/systematic",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Structured engineering workflows for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"commands"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"build": "bun run clean && bun build src/index.ts src/cli.ts --outdir dist --target bun --splitting --packages external && tsc --emitDeclarationOnly",
|
|
24
25
|
"dev": "bun --watch src/index.ts",
|
|
25
26
|
"test": "bun test tests/unit",
|
|
26
27
|
"test:integration": "bun test tests/integration",
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
"typecheck": "tsc --noEmit",
|
|
29
30
|
"lint": "biome check .",
|
|
30
31
|
"fix": "bun run lint --fix",
|
|
31
|
-
"prepublishOnly": "bun run build
|
|
32
|
+
"prepublishOnly": "bun run build"
|
|
32
33
|
},
|
|
33
34
|
"keywords": [
|
|
34
35
|
"opencode",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"@types/node": "^22.0.0",
|
|
57
58
|
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
58
59
|
"markdownlint-cli": "^0.47.0",
|
|
60
|
+
"rimraf": "^6.1.2",
|
|
59
61
|
"semantic-release": "^25.0.0",
|
|
60
62
|
"semantic-release-export-data": "^1.2.0",
|
|
61
63
|
"typescript": "^5.7.0"
|