@fro.bot/systematic 1.2.1 → 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 +15 -24
- package/dist/{index-hkk4125w.js → index-hvkf19rd.js} +216 -133
- package/dist/index.d.ts +3 -0
- package/dist/index.js +63 -169
- 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 +3 -3
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
convertContent,
|
|
5
5
|
findAgentsInDir,
|
|
6
6
|
findCommandsInDir,
|
|
7
|
-
findSkillsInDir
|
|
8
|
-
|
|
7
|
+
findSkillsInDir,
|
|
8
|
+
getConfigPaths
|
|
9
|
+
} from "./index-hvkf19rd.js";
|
|
9
10
|
|
|
10
11
|
// src/cli.ts
|
|
11
12
|
import fs from "fs";
|
|
@@ -37,12 +38,6 @@ Examples:
|
|
|
37
38
|
systematic convert skill ./skills/my-skill/SKILL.md
|
|
38
39
|
systematic config show
|
|
39
40
|
`;
|
|
40
|
-
function getUserConfigDir() {
|
|
41
|
-
return path.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
|
|
42
|
-
}
|
|
43
|
-
function getProjectConfigDir() {
|
|
44
|
-
return path.join(process.cwd(), ".opencode");
|
|
45
|
-
}
|
|
46
41
|
function listItems(type) {
|
|
47
42
|
const packageRoot = path.resolve(import.meta.dirname, "..");
|
|
48
43
|
const bundledDir = packageRoot;
|
|
@@ -65,7 +60,7 @@ function listItems(type) {
|
|
|
65
60
|
console.error(`Unknown type: ${type}. Use: skills, agents, commands`);
|
|
66
61
|
process.exit(1);
|
|
67
62
|
}
|
|
68
|
-
const items = finder(path.join(bundledDir, subdir)
|
|
63
|
+
const items = finder(path.join(bundledDir, subdir));
|
|
69
64
|
if (items.length === 0) {
|
|
70
65
|
console.log(`No ${type} found.`);
|
|
71
66
|
return;
|
|
@@ -73,7 +68,7 @@ function listItems(type) {
|
|
|
73
68
|
console.log(`Available ${type}:
|
|
74
69
|
`);
|
|
75
70
|
for (const item of items.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
76
|
-
console.log(` ${item.name}
|
|
71
|
+
console.log(` ${item.name}`);
|
|
77
72
|
}
|
|
78
73
|
}
|
|
79
74
|
function runConvert(type, filePath, modeArg) {
|
|
@@ -102,31 +97,27 @@ function runConvert(type, filePath, modeArg) {
|
|
|
102
97
|
console.log(converted);
|
|
103
98
|
}
|
|
104
99
|
function configShow() {
|
|
105
|
-
const
|
|
106
|
-
const projectDir = getProjectConfigDir();
|
|
100
|
+
const paths = getConfigPaths(process.cwd());
|
|
107
101
|
console.log(`Configuration locations:
|
|
108
102
|
`);
|
|
109
|
-
console.log(` User config: ${
|
|
110
|
-
console.log(` Project config: ${
|
|
111
|
-
|
|
112
|
-
if (fs.existsSync(projectConfig)) {
|
|
103
|
+
console.log(` User config: ${paths.userConfig}`);
|
|
104
|
+
console.log(` Project config: ${paths.projectConfig}`);
|
|
105
|
+
if (fs.existsSync(paths.projectConfig)) {
|
|
113
106
|
console.log(`
|
|
114
107
|
Project configuration:`);
|
|
115
|
-
console.log(fs.readFileSync(projectConfig, "utf-8"));
|
|
108
|
+
console.log(fs.readFileSync(paths.projectConfig, "utf-8"));
|
|
116
109
|
}
|
|
117
|
-
|
|
118
|
-
if (fs.existsSync(userConfig)) {
|
|
110
|
+
if (fs.existsSync(paths.userConfig)) {
|
|
119
111
|
console.log(`
|
|
120
112
|
User configuration:`);
|
|
121
|
-
console.log(fs.readFileSync(userConfig, "utf-8"));
|
|
113
|
+
console.log(fs.readFileSync(paths.userConfig, "utf-8"));
|
|
122
114
|
}
|
|
123
115
|
}
|
|
124
116
|
function configPath() {
|
|
125
|
-
const
|
|
126
|
-
const projectDir = getProjectConfigDir();
|
|
117
|
+
const paths = getConfigPaths(process.cwd());
|
|
127
118
|
console.log("Config file paths:");
|
|
128
|
-
console.log(` User: ${
|
|
129
|
-
console.log(` Project: ${
|
|
119
|
+
console.log(` User: ${paths.userConfig}`);
|
|
120
|
+
console.log(` Project: ${paths.projectConfig}`);
|
|
130
121
|
}
|
|
131
122
|
var args = process.argv.slice(2);
|
|
132
123
|
var command = args[0];
|
|
@@ -1,7 +1,67 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/lib/
|
|
2
|
+
// src/lib/config.ts
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
|
|
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
|
+
|
|
61
|
+
// src/lib/converter.ts
|
|
62
|
+
import fs2 from "fs";
|
|
63
|
+
|
|
64
|
+
// src/lib/frontmatter.ts
|
|
5
65
|
function parseFrontmatter(content) {
|
|
6
66
|
const lines = content.split(/\r?\n/);
|
|
7
67
|
if (lines.length === 0 || lines[0].trim() !== "---") {
|
|
@@ -48,6 +108,31 @@ function formatFrontmatter(data) {
|
|
|
48
108
|
return lines.join(`
|
|
49
109
|
`);
|
|
50
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;
|
|
51
136
|
function inferTemperature(name, description) {
|
|
52
137
|
const sample = `${name} ${description ?? ""}`.toLowerCase();
|
|
53
138
|
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
@@ -111,159 +196,67 @@ ${body}`;
|
|
|
111
196
|
return content;
|
|
112
197
|
}
|
|
113
198
|
function convertFileWithCache(filePath, type, options = {}) {
|
|
114
|
-
const fd =
|
|
199
|
+
const fd = fs2.openSync(filePath, "r");
|
|
115
200
|
try {
|
|
116
|
-
const stats =
|
|
201
|
+
const stats = fs2.fstatSync(fd);
|
|
117
202
|
const cacheKey = `${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}`;
|
|
118
203
|
const cached = cache.get(cacheKey);
|
|
119
204
|
if (cached != null && cached.mtimeMs === stats.mtimeMs) {
|
|
120
205
|
return cached.converted;
|
|
121
206
|
}
|
|
122
|
-
const content =
|
|
207
|
+
const content = fs2.readFileSync(fd, "utf8");
|
|
123
208
|
const converted = convertContent(content, type, options);
|
|
124
209
|
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
125
210
|
return converted;
|
|
126
211
|
} finally {
|
|
127
|
-
|
|
212
|
+
fs2.closeSync(fd);
|
|
128
213
|
}
|
|
129
214
|
}
|
|
130
215
|
|
|
131
|
-
// src/lib/
|
|
132
|
-
import
|
|
133
|
-
import
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
let inFrontmatter = false;
|
|
140
|
-
let name = "";
|
|
141
|
-
let description = "";
|
|
142
|
-
for (const line of lines) {
|
|
143
|
-
if (line.trim() === "---") {
|
|
144
|
-
if (inFrontmatter)
|
|
145
|
-
break;
|
|
146
|
-
inFrontmatter = true;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (inFrontmatter) {
|
|
150
|
-
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
151
|
-
if (match) {
|
|
152
|
-
const [, key, value] = match;
|
|
153
|
-
if (key === "name")
|
|
154
|
-
name = value.trim();
|
|
155
|
-
if (key === "description")
|
|
156
|
-
description = value.trim();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return { name, description };
|
|
161
|
-
} catch {
|
|
162
|
-
return { name: "", description: "" };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function stripFrontmatter(content) {
|
|
166
|
-
const lines = content.split(`
|
|
167
|
-
`);
|
|
168
|
-
let inFrontmatter = false;
|
|
169
|
-
let frontmatterEnded = false;
|
|
170
|
-
const contentLines = [];
|
|
171
|
-
for (const line of lines) {
|
|
172
|
-
if (line.trim() === "---") {
|
|
173
|
-
if (inFrontmatter) {
|
|
174
|
-
frontmatterEnded = true;
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
inFrontmatter = true;
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (frontmatterEnded || !inFrontmatter) {
|
|
181
|
-
contentLines.push(line);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return contentLines.join(`
|
|
185
|
-
`).trim();
|
|
186
|
-
}
|
|
187
|
-
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
|
188
|
-
const skills = [];
|
|
189
|
-
if (!fs2.existsSync(dir))
|
|
190
|
-
return skills;
|
|
191
|
-
function recurse(currentDir, depth) {
|
|
192
|
-
if (depth > maxDepth)
|
|
193
|
-
return;
|
|
194
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
195
|
-
for (const entry of entries) {
|
|
196
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
197
|
-
if (entry.isDirectory()) {
|
|
198
|
-
const skillFile = path.join(fullPath, "SKILL.md");
|
|
199
|
-
if (fs2.existsSync(skillFile)) {
|
|
200
|
-
const { name, description } = extractFrontmatter(skillFile);
|
|
201
|
-
skills.push({
|
|
202
|
-
path: fullPath,
|
|
203
|
-
skillFile,
|
|
204
|
-
name: name || entry.name,
|
|
205
|
-
description: description || "",
|
|
206
|
-
sourceType
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
recurse(fullPath, depth + 1);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
recurse(dir, 0);
|
|
214
|
-
return skills;
|
|
215
|
-
}
|
|
216
|
-
function findAgentsInDir(dir, sourceType, maxDepth = 2) {
|
|
217
|
-
const agents = [];
|
|
218
|
-
if (!fs2.existsSync(dir))
|
|
219
|
-
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;
|
|
220
224
|
function recurse(currentDir, depth, category) {
|
|
221
225
|
if (depth > maxDepth)
|
|
222
226
|
return;
|
|
223
|
-
const entries =
|
|
227
|
+
const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
|
|
224
228
|
for (const entry of entries) {
|
|
225
|
-
const fullPath =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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);
|
|
235
239
|
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
recurse(dir, 0);
|
|
239
|
-
return agents;
|
|
240
|
-
}
|
|
241
|
-
function findCommandsInDir(dir, sourceType, maxDepth = 2) {
|
|
242
|
-
const commands = [];
|
|
243
|
-
if (!fs2.existsSync(dir))
|
|
244
|
-
return commands;
|
|
245
|
-
function recurse(currentDir, depth, category) {
|
|
246
|
-
if (depth > maxDepth)
|
|
247
|
-
return;
|
|
248
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
249
|
-
for (const entry of entries) {
|
|
250
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
251
240
|
if (entry.isDirectory()) {
|
|
252
241
|
recurse(fullPath, depth + 1, entry.name);
|
|
253
|
-
} else if (entry.name.endsWith(".md")) {
|
|
254
|
-
const baseName = entry.name.replace(/\.md$/, "");
|
|
255
|
-
const commandName = category ? `/${category}:${baseName}` : `/${baseName}`;
|
|
256
|
-
commands.push({
|
|
257
|
-
name: commandName,
|
|
258
|
-
file: fullPath,
|
|
259
|
-
sourceType,
|
|
260
|
-
category
|
|
261
|
-
});
|
|
262
242
|
}
|
|
263
243
|
}
|
|
264
244
|
}
|
|
265
|
-
recurse(
|
|
266
|
-
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
|
+
}));
|
|
267
260
|
}
|
|
268
261
|
function extractAgentFrontmatter(content) {
|
|
269
262
|
const lines = content.split(`
|
|
@@ -291,6 +284,23 @@ function extractAgentFrontmatter(content) {
|
|
|
291
284
|
}
|
|
292
285
|
return { name, description, prompt: stripFrontmatter(content) };
|
|
293
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
|
+
}
|
|
294
304
|
function extractCommandFrontmatter(content) {
|
|
295
305
|
const lines = content.split(`
|
|
296
306
|
`);
|
|
@@ -321,4 +331,77 @@ function extractCommandFrontmatter(content) {
|
|
|
321
331
|
return { name, description, argumentHint };
|
|
322
332
|
}
|
|
323
333
|
|
|
324
|
-
|
|
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
|
@@ -7,63 +7,65 @@ import {
|
|
|
7
7
|
findAgentsInDir,
|
|
8
8
|
findCommandsInDir,
|
|
9
9
|
findSkillsInDir,
|
|
10
|
+
formatSkillsXml,
|
|
11
|
+
loadConfig,
|
|
10
12
|
stripFrontmatter
|
|
11
|
-
} from "./index-
|
|
13
|
+
} from "./index-hvkf19rd.js";
|
|
12
14
|
|
|
13
15
|
// src/index.ts
|
|
14
16
|
import fs3 from "fs";
|
|
15
|
-
import os2 from "os";
|
|
16
17
|
import path3 from "path";
|
|
17
18
|
import { fileURLToPath } from "url";
|
|
18
19
|
|
|
19
|
-
// src/lib/
|
|
20
|
+
// src/lib/bootstrap.ts
|
|
20
21
|
import fs from "fs";
|
|
21
|
-
import path from "path";
|
|
22
22
|
import os from "os";
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const set = new Set;
|
|
44
|
-
if (arr1)
|
|
45
|
-
arr1.forEach((item) => set.add(item));
|
|
46
|
-
if (arr2)
|
|
47
|
-
arr2.forEach((item) => set.add(item));
|
|
48
|
-
return Array.from(set);
|
|
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}/\``;
|
|
49
43
|
}
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
disabled_agents: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents),
|
|
59
|
-
disabled_commands: mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands),
|
|
60
|
-
bootstrap: {
|
|
61
|
-
...DEFAULT_CONFIG.bootstrap,
|
|
62
|
-
...userConfig?.bootstrap,
|
|
63
|
-
...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");
|
|
64
52
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
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>`;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
// src/lib/config-handler.ts
|
|
@@ -106,9 +108,9 @@ function loadSkillAsCommand(skillInfo) {
|
|
|
106
108
|
return null;
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
|
-
function collectAgents(dir,
|
|
111
|
+
function collectAgents(dir, disabledAgents) {
|
|
110
112
|
const agents = {};
|
|
111
|
-
const agentList = findAgentsInDir(dir
|
|
113
|
+
const agentList = findAgentsInDir(dir);
|
|
112
114
|
for (const agentInfo of agentList) {
|
|
113
115
|
if (disabledAgents.includes(agentInfo.name))
|
|
114
116
|
continue;
|
|
@@ -119,9 +121,9 @@ function collectAgents(dir, sourceType, disabledAgents) {
|
|
|
119
121
|
}
|
|
120
122
|
return agents;
|
|
121
123
|
}
|
|
122
|
-
function collectCommands(dir,
|
|
124
|
+
function collectCommands(dir, disabledCommands) {
|
|
123
125
|
const commands = {};
|
|
124
|
-
const commandList = findCommandsInDir(dir
|
|
126
|
+
const commandList = findCommandsInDir(dir);
|
|
125
127
|
for (const commandInfo of commandList) {
|
|
126
128
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
127
129
|
if (disabledCommands.includes(cleanName))
|
|
@@ -133,9 +135,9 @@ function collectCommands(dir, sourceType, disabledCommands) {
|
|
|
133
135
|
}
|
|
134
136
|
return commands;
|
|
135
137
|
}
|
|
136
|
-
function collectSkillsAsCommands(dir,
|
|
138
|
+
function collectSkillsAsCommands(dir, disabledSkills) {
|
|
137
139
|
const commands = {};
|
|
138
|
-
const skillList = findSkillsInDir(dir
|
|
140
|
+
const skillList = findSkillsInDir(dir);
|
|
139
141
|
for (const skillInfo of skillList) {
|
|
140
142
|
if (disabledSkills.includes(skillInfo.name))
|
|
141
143
|
continue;
|
|
@@ -150,9 +152,9 @@ function createConfigHandler(deps) {
|
|
|
150
152
|
const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
|
|
151
153
|
return async (config) => {
|
|
152
154
|
const systematicConfig = loadConfig(directory);
|
|
153
|
-
const bundledAgents = collectAgents(bundledAgentsDir,
|
|
154
|
-
const bundledCommands = collectCommands(bundledCommandsDir,
|
|
155
|
-
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);
|
|
156
158
|
const existingAgents = config.agent ?? {};
|
|
157
159
|
config.agent = {
|
|
158
160
|
...bundledAgents,
|
|
@@ -171,61 +173,6 @@ function createConfigHandler(deps) {
|
|
|
171
173
|
import fs2 from "fs";
|
|
172
174
|
import path2 from "path";
|
|
173
175
|
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
176
|
function wrapSkillContent(skillPath, content) {
|
|
230
177
|
const skillDir = path2.dirname(skillPath);
|
|
231
178
|
const converted = convertContent(content, "skill", { source: "bundled" });
|
|
@@ -240,7 +187,7 @@ ${body.trim()}
|
|
|
240
187
|
function createSkillTool(options) {
|
|
241
188
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
242
189
|
const getSystematicSkills = () => {
|
|
243
|
-
return findSkillsInDir(bundledSkillsDir
|
|
190
|
+
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
244
191
|
};
|
|
245
192
|
const buildDescription = () => {
|
|
246
193
|
const skills = getSystematicSkills();
|
|
@@ -249,11 +196,12 @@ function createSkillTool(options) {
|
|
|
249
196
|
|
|
250
197
|
Skills provide specialized knowledge and step-by-step guidance.
|
|
251
198
|
Use this when a task matches an available skill's description.`;
|
|
252
|
-
|
|
253
|
-
|
|
199
|
+
return `${baseDescription}
|
|
200
|
+
|
|
201
|
+
${systematicXml}`;
|
|
254
202
|
};
|
|
255
203
|
let cachedDescription = null;
|
|
256
|
-
|
|
204
|
+
return tool({
|
|
257
205
|
get description() {
|
|
258
206
|
if (cachedDescription == null) {
|
|
259
207
|
cachedDescription = buildDescription();
|
|
@@ -282,22 +230,10 @@ ${wrapped}`;
|
|
|
282
230
|
throw new Error(`Failed to load skill "${requestedName}": ${errorMessage}`);
|
|
283
231
|
}
|
|
284
232
|
}
|
|
285
|
-
const hookedTool = getHookedTool();
|
|
286
|
-
if (hookedTool != null && typeof hookedTool.execute === "function") {
|
|
287
|
-
try {
|
|
288
|
-
return await hookedTool.execute(args);
|
|
289
|
-
} catch {}
|
|
290
|
-
}
|
|
291
233
|
const availableSystematic = skills.map((s) => `systematic:${s.name}`);
|
|
292
234
|
throw new Error(`Skill "${requestedName}" not found. Available systematic skills: ${availableSystematic.join(", ")}`);
|
|
293
235
|
}
|
|
294
236
|
});
|
|
295
|
-
Object.defineProperty(toolDef, SYSTEMATIC_MARKER, {
|
|
296
|
-
value: true,
|
|
297
|
-
enumerable: false,
|
|
298
|
-
writable: false
|
|
299
|
-
});
|
|
300
|
-
return toolDef;
|
|
301
237
|
}
|
|
302
238
|
|
|
303
239
|
// src/index.ts
|
|
@@ -319,48 +255,6 @@ var getPackageVersion = () => {
|
|
|
319
255
|
return "unknown";
|
|
320
256
|
}
|
|
321
257
|
};
|
|
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
258
|
var SystematicPlugin = async ({ client, directory }) => {
|
|
365
259
|
const config = loadConfig(directory);
|
|
366
260
|
const configHandler = createConfigHandler({
|
|
@@ -403,7 +297,7 @@ var SystematicPlugin = async ({ client, directory }) => {
|
|
|
403
297
|
if (existingSystem.includes("title generator") || existingSystem.includes("generate a title")) {
|
|
404
298
|
return;
|
|
405
299
|
}
|
|
406
|
-
const content = getBootstrapContent(config);
|
|
300
|
+
const content = getBootstrapContent(config, { bundledSkillsDir });
|
|
407
301
|
if (content) {
|
|
408
302
|
if (!output.system) {
|
|
409
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
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",
|
|
24
|
+
"build": "bun run clean && bun build src/index.ts src/cli.ts --outdir dist --target bun --splitting --packages external && tsc --emitDeclarationOnly",
|
|
25
25
|
"dev": "bun --watch src/index.ts",
|
|
26
26
|
"test": "bun test tests/unit",
|
|
27
27
|
"test:integration": "bun test tests/integration",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
|
30
30
|
"lint": "biome check .",
|
|
31
31
|
"fix": "bun run lint --fix",
|
|
32
|
-
"prepublishOnly": "bun run build
|
|
32
|
+
"prepublishOnly": "bun run build"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"opencode",
|