@fro.bot/systematic 1.1.0 → 1.2.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/README.md CHANGED
@@ -52,9 +52,9 @@ Quick shortcuts to invoke workflows:
52
52
  - `/deepen-plan` - Add detail to existing plans
53
53
  - `/lfg` - Let's go - start working immediately
54
54
 
55
- ### Review Agents
55
+ ### Agents
56
56
 
57
- Specialized code review agents organized by category:
57
+ Specialized agents organized by category:
58
58
 
59
59
  **Review:**
60
60
 
@@ -98,42 +98,6 @@ Create `~/.config/opencode/systematic.json` or `.opencode/systematic.json` to di
98
98
  }
99
99
  ```
100
100
 
101
- ## Converting CEP Content
102
-
103
- The CLI includes a converter for adapting Claude Code agents, skills, and commands from Compound Engineering Plugin (CEP) to OpenCode.
104
-
105
- ### Convert a Skill
106
-
107
- Skills are directories containing `SKILL.md` and supporting files:
108
-
109
- ```bash
110
- npx @fro.bot/systematic convert skill /path/to/cep/skills/my-skill -o ./skills/my-skill
111
- ```
112
-
113
- ### Convert an Agent
114
-
115
- Agents are markdown files that get OpenCode-compatible YAML frontmatter:
116
-
117
- ```bash
118
- npx @fro.bot/systematic convert agent /path/to/cep/agents/review/my-agent.md -o ./agents/review/my-agent.md
119
- ```
120
-
121
- ### Convert a Command
122
-
123
- Commands are markdown templates:
124
-
125
- ```bash
126
- npx @fro.bot/systematic convert command /path/to/cep/commands/my-command.md -o ./commands/my-command.md
127
- ```
128
-
129
- ### Dry Run
130
-
131
- Preview conversion without writing files:
132
-
133
- ```bash
134
- npx @fro.bot/systematic convert skill /path/to/skill --dry-run
135
- ```
136
-
137
101
  ## Development
138
102
 
139
103
  ```bash
package/dist/cli.js CHANGED
@@ -1,191 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ convertContent,
3
4
  findAgentsInDir,
4
5
  findCommandsInDir,
5
6
  findSkillsInDir
6
- } from "./index-v8dhd5s2.js";
7
+ } from "./index-33zyxync.js";
7
8
 
8
9
  // src/cli.ts
9
- import fs2 from "node:fs";
10
- import path2 from "node:path";
11
-
12
- // src/lib/converter.ts
13
10
  import fs from "node:fs";
14
11
  import path from "node:path";
15
- function parseFrontmatter(raw) {
16
- const lines = raw.split(/\r?\n/);
17
- if (lines.length === 0 || lines[0].trim() !== "---") {
18
- return { data: {}, body: raw };
19
- }
20
- let endIndex = -1;
21
- for (let i = 1;i < lines.length; i++) {
22
- if (lines[i].trim() === "---") {
23
- endIndex = i;
24
- break;
25
- }
26
- }
27
- if (endIndex === -1) {
28
- return { data: {}, body: raw };
29
- }
30
- const yamlLines = lines.slice(1, endIndex);
31
- const body = lines.slice(endIndex + 1).join(`
32
- `);
33
- const data = {};
34
- for (const line of yamlLines) {
35
- const match = line.match(/^(\w+):\s*(.*)$/);
36
- if (match) {
37
- const [, key, value] = match;
38
- if (value === "true")
39
- data[key] = true;
40
- else if (value === "false")
41
- data[key] = false;
42
- else if (/^\d+(\.\d+)?$/.test(value))
43
- data[key] = parseFloat(value);
44
- else
45
- data[key] = value;
46
- }
47
- }
48
- return { data, body };
49
- }
50
- function formatFrontmatter(data, body) {
51
- const lines = [];
52
- if (data.description)
53
- lines.push(`description: ${data.description}`);
54
- if (data.mode)
55
- lines.push(`mode: ${data.mode}`);
56
- if (data.model)
57
- lines.push(`model: ${data.model}`);
58
- if (data.temperature !== undefined)
59
- lines.push(`temperature: ${data.temperature}`);
60
- if (lines.length === 0)
61
- return body;
62
- return ["---", ...lines, "---", "", body].join(`
63
- `);
64
- }
65
- function inferTemperature(name, description) {
66
- const sample = `${name} ${description || ""}`.toLowerCase();
67
- if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
68
- return 0.1;
69
- }
70
- if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
71
- return 0.2;
72
- }
73
- if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
74
- return 0.3;
75
- }
76
- if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
77
- return 0.6;
78
- }
79
- return 0.3;
80
- }
81
- function normalizeModel(model) {
82
- if (model.includes("/"))
83
- return model;
84
- if (/^claude-/.test(model))
85
- return `anthropic/${model}`;
86
- if (/^(gpt-|o1-|o3-)/.test(model))
87
- return `openai/${model}`;
88
- if (/^gemini-/.test(model))
89
- return `google/${model}`;
90
- return `anthropic/${model}`;
91
- }
92
- function convertAgent(sourcePath, options = {}) {
93
- const content = fs.readFileSync(sourcePath, "utf-8");
94
- const name = path.basename(sourcePath, ".md");
95
- const { data, body } = parseFrontmatter(content);
96
- const existingDescription = data.description;
97
- const existingModel = data.model;
98
- let description = existingDescription;
99
- if (!description) {
100
- const firstLine = body.split(`
101
- `).find((l) => l.trim() && !l.startsWith("#"));
102
- description = firstLine?.slice(0, 100) || `${name} agent`;
103
- }
104
- const frontmatter = {
105
- description,
106
- mode: "subagent",
107
- temperature: inferTemperature(name, description)
108
- };
109
- if (existingModel && existingModel !== "inherit") {
110
- frontmatter.model = normalizeModel(existingModel);
111
- }
112
- const converted = formatFrontmatter(frontmatter, body.trim());
113
- const outputPath = options.output || sourcePath;
114
- if (!options.dryRun) {
115
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
116
- fs.writeFileSync(outputPath, converted);
117
- }
118
- return {
119
- type: "agent",
120
- sourcePath,
121
- outputPath,
122
- converted: true,
123
- files: [path.basename(outputPath)]
124
- };
125
- }
126
- function convertCommand(sourcePath, options = {}) {
127
- const content = fs.readFileSync(sourcePath, "utf-8");
128
- const outputPath = options.output || sourcePath;
129
- if (!options.dryRun) {
130
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
131
- fs.writeFileSync(outputPath, content);
132
- }
133
- return {
134
- type: "command",
135
- sourcePath,
136
- outputPath,
137
- converted: true,
138
- files: [path.basename(outputPath)]
139
- };
140
- }
141
- function convertSkill(sourcePath, options = {}) {
142
- const stats = fs.statSync(sourcePath);
143
- if (!stats.isDirectory()) {
144
- throw new Error(`Skill source must be a directory: ${sourcePath}`);
145
- }
146
- const skillName = path.basename(sourcePath);
147
- const outputPath = options.output || sourcePath;
148
- const files = [];
149
- function copyDir(src, dest) {
150
- if (!options.dryRun) {
151
- fs.mkdirSync(dest, { recursive: true });
152
- }
153
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
154
- const srcPath = path.join(src, entry.name);
155
- const destPath = path.join(dest, entry.name);
156
- if (entry.isDirectory()) {
157
- copyDir(srcPath, destPath);
158
- } else {
159
- files.push(path.relative(outputPath, destPath));
160
- if (!options.dryRun) {
161
- fs.copyFileSync(srcPath, destPath);
162
- }
163
- }
164
- }
165
- }
166
- copyDir(sourcePath, outputPath);
167
- return {
168
- type: "skill",
169
- sourcePath,
170
- outputPath,
171
- converted: true,
172
- files
173
- };
174
- }
175
- function convert(type, sourcePath, options = {}) {
176
- switch (type) {
177
- case "agent":
178
- return convertAgent(sourcePath, options);
179
- case "command":
180
- return convertCommand(sourcePath, options);
181
- case "skill":
182
- return convertSkill(sourcePath, options);
183
- default:
184
- throw new Error(`Unknown type: ${type}`);
185
- }
186
- }
187
-
188
- // src/cli.ts
189
12
  var VERSION = "0.1.0";
190
13
  var HELP = `
191
14
  systematic - OpenCode plugin for systematic engineering workflows
@@ -195,32 +18,32 @@ Usage:
195
18
 
196
19
  Commands:
197
20
  list [type] List available skills, agents, or commands
198
- convert <type> <source> [--output <path>] [--dry-run]
199
- Convert Claude Code content to OpenCode format
200
- Types: skill, agent, command
21
+ convert <type> <file> [--mode=primary|subagent]
22
+ Convert and inspect a file (outputs to stdout)
201
23
  config [subcommand] Configuration management
202
24
  show Show configuration
203
25
  path Print config file locations
204
26
 
205
27
  Options:
206
- --output, -o Output path for convert command
207
- --dry-run Preview conversion without writing files
208
28
  -h, --help Show this help message
209
29
  -v, --version Show version
210
30
 
211
31
  Examples:
212
32
  systematic list skills
213
- systematic convert skill /path/to/cep/skills/agent-browser -o ./skills/agent-browser
214
- systematic convert agent /path/to/agent.md --dry-run
33
+ systematic list agents
34
+ systematic convert agent ./agents/my-agent.md
35
+ systematic convert agent ./agents/my-agent.md --mode=primary
36
+ systematic convert skill ./skills/my-skill/SKILL.md
37
+ systematic config show
215
38
  `;
216
39
  function getUserConfigDir() {
217
- return path2.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
40
+ return path.join(process.env.HOME || process.env.USERPROFILE || ".", ".config/opencode");
218
41
  }
219
42
  function getProjectConfigDir() {
220
- return path2.join(process.cwd(), ".opencode");
43
+ return path.join(process.cwd(), ".opencode");
221
44
  }
222
45
  function listItems(type) {
223
- const packageRoot = path2.resolve(import.meta.dirname, "..");
46
+ const packageRoot = path.resolve(import.meta.dirname, "..");
224
47
  const bundledDir = packageRoot;
225
48
  let finder;
226
49
  let subdir;
@@ -241,7 +64,7 @@ function listItems(type) {
241
64
  console.error(`Unknown type: ${type}. Use: skills, agents, commands`);
242
65
  process.exit(1);
243
66
  }
244
- const items = finder(path2.join(bundledDir, subdir), "bundled");
67
+ const items = finder(path.join(bundledDir, subdir), "bundled");
245
68
  if (items.length === 0) {
246
69
  console.log(`No ${type} found.`);
247
70
  return;
@@ -252,71 +75,57 @@ function listItems(type) {
252
75
  console.log(` ${item.name} (${item.sourceType})`);
253
76
  }
254
77
  }
78
+ function runConvert(type, filePath, modeArg) {
79
+ const validTypes = ["skill", "agent", "command"];
80
+ if (!validTypes.includes(type)) {
81
+ console.error(`Invalid type: ${type}. Must be one of: ${validTypes.join(", ")}`);
82
+ process.exit(1);
83
+ }
84
+ const resolvedPath = path.resolve(filePath);
85
+ if (!fs.existsSync(resolvedPath)) {
86
+ console.error(`File not found: ${resolvedPath}`);
87
+ process.exit(1);
88
+ }
89
+ let agentMode = "subagent";
90
+ if (modeArg) {
91
+ const modeMatch = modeArg.match(/^--mode=(primary|subagent)$/);
92
+ if (modeMatch) {
93
+ agentMode = modeMatch[1];
94
+ } else {
95
+ console.error("Invalid --mode flag. Use: --mode=primary or --mode=subagent");
96
+ process.exit(1);
97
+ }
98
+ }
99
+ const content = fs.readFileSync(resolvedPath, "utf8");
100
+ const converted = convertContent(content, type, { agentMode });
101
+ console.log(converted);
102
+ }
255
103
  function configShow() {
256
104
  const userDir = getUserConfigDir();
257
105
  const projectDir = getProjectConfigDir();
258
106
  console.log(`Configuration locations:
259
107
  `);
260
- console.log(` User config: ${path2.join(userDir, "systematic.json")}`);
261
- console.log(` Project config: ${path2.join(projectDir, "systematic.json")}`);
262
- const projectConfig = path2.join(projectDir, "systematic.json");
263
- if (fs2.existsSync(projectConfig)) {
108
+ console.log(` User config: ${path.join(userDir, "systematic.json")}`);
109
+ console.log(` Project config: ${path.join(projectDir, "systematic.json")}`);
110
+ const projectConfig = path.join(projectDir, "systematic.json");
111
+ if (fs.existsSync(projectConfig)) {
264
112
  console.log(`
265
113
  Project configuration:`);
266
- console.log(fs2.readFileSync(projectConfig, "utf-8"));
114
+ console.log(fs.readFileSync(projectConfig, "utf-8"));
267
115
  }
268
- const userConfig = path2.join(userDir, "systematic.json");
269
- if (fs2.existsSync(userConfig)) {
116
+ const userConfig = path.join(userDir, "systematic.json");
117
+ if (fs.existsSync(userConfig)) {
270
118
  console.log(`
271
119
  User configuration:`);
272
- console.log(fs2.readFileSync(userConfig, "utf-8"));
120
+ console.log(fs.readFileSync(userConfig, "utf-8"));
273
121
  }
274
122
  }
275
123
  function configPath() {
276
124
  const userDir = getUserConfigDir();
277
125
  const projectDir = getProjectConfigDir();
278
126
  console.log("Config file paths:");
279
- console.log(` User: ${path2.join(userDir, "systematic.json")}`);
280
- console.log(` Project: ${path2.join(projectDir, "systematic.json")}`);
281
- }
282
- function runConvert(args) {
283
- const typeArg = args[1];
284
- const sourceArg = args[2];
285
- if (!typeArg || !sourceArg) {
286
- console.error("Usage: systematic convert <type> <source> [--output <path>] [--dry-run]");
287
- console.error("Types: skill, agent, command");
288
- process.exit(1);
289
- }
290
- const validTypes = ["skill", "agent", "command"];
291
- if (!validTypes.includes(typeArg)) {
292
- console.error(`Invalid type: ${typeArg}. Must be one of: ${validTypes.join(", ")}`);
293
- process.exit(1);
294
- }
295
- const sourcePath = path2.resolve(sourceArg);
296
- if (!fs2.existsSync(sourcePath)) {
297
- console.error(`Source not found: ${sourcePath}`);
298
- process.exit(1);
299
- }
300
- const outputIndex = args.findIndex((a) => a === "--output" || a === "-o");
301
- const outputPath = outputIndex !== -1 ? path2.resolve(args[outputIndex + 1]) : undefined;
302
- const dryRun = args.includes("--dry-run");
303
- try {
304
- const result = convert(typeArg, sourcePath, { output: outputPath, dryRun });
305
- if (dryRun) {
306
- console.log(`[DRY RUN] Would convert ${result.type}:`);
307
- } else {
308
- console.log(`Converted ${result.type}:`);
309
- }
310
- console.log(` Source: ${result.sourcePath}`);
311
- console.log(` Output: ${result.outputPath}`);
312
- console.log(" Files:");
313
- for (const file of result.files) {
314
- console.log(` - ${file}`);
315
- }
316
- } catch (err) {
317
- console.error(`Conversion failed: ${err.message}`);
318
- process.exit(1);
319
- }
127
+ console.log(` User: ${path.join(userDir, "systematic.json")}`);
128
+ console.log(` Project: ${path.join(projectDir, "systematic.json")}`);
320
129
  }
321
130
  var args = process.argv.slice(2);
322
131
  var command = args[0];
@@ -325,7 +134,12 @@ switch (command) {
325
134
  listItems(args[1] || "skills");
326
135
  break;
327
136
  case "convert":
328
- runConvert(args);
137
+ if (!args[1] || !args[2]) {
138
+ console.error("Usage: systematic convert <type> <file> [--mode=primary|subagent]");
139
+ console.error(" type: skill, agent, or command");
140
+ process.exit(1);
141
+ }
142
+ runConvert(args[1], args[2], args[3]);
329
143
  break;
330
144
  case "config":
331
145
  switch (args[1]) {
@@ -1,9 +1,138 @@
1
- // src/lib/skills-core.ts
1
+ // src/lib/converter.ts
2
2
  import fs from "node:fs";
3
+ var cache = new Map;
4
+ function parseFrontmatter(content) {
5
+ const lines = content.split(/\r?\n/);
6
+ if (lines.length === 0 || lines[0].trim() !== "---") {
7
+ return { data: {}, body: content, raw: "" };
8
+ }
9
+ let endIndex = -1;
10
+ for (let i = 1;i < lines.length; i++) {
11
+ if (lines[i].trim() === "---") {
12
+ endIndex = i;
13
+ break;
14
+ }
15
+ }
16
+ if (endIndex === -1) {
17
+ return { data: {}, body: content, raw: "" };
18
+ }
19
+ const yamlLines = lines.slice(1, endIndex);
20
+ const body = lines.slice(endIndex + 1).join(`
21
+ `);
22
+ const raw = lines.slice(0, endIndex + 1).join(`
23
+ `);
24
+ const data = {};
25
+ for (const line of yamlLines) {
26
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
27
+ if (match) {
28
+ const [, key, value] = match;
29
+ if (value === "true")
30
+ data[key] = true;
31
+ else if (value === "false")
32
+ data[key] = false;
33
+ else if (/^\d+(\.\d+)?$/.test(value))
34
+ data[key] = parseFloat(value);
35
+ else
36
+ data[key] = value;
37
+ }
38
+ }
39
+ return { data, body, raw };
40
+ }
41
+ function formatFrontmatter(data) {
42
+ const lines = ["---"];
43
+ for (const [key, value] of Object.entries(data)) {
44
+ lines.push(`${key}: ${value}`);
45
+ }
46
+ lines.push("---");
47
+ return lines.join(`
48
+ `);
49
+ }
50
+ function inferTemperature(name, description) {
51
+ const sample = `${name} ${description ?? ""}`.toLowerCase();
52
+ if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
53
+ return 0.1;
54
+ }
55
+ if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
56
+ return 0.2;
57
+ }
58
+ if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
59
+ return 0.3;
60
+ }
61
+ if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
62
+ return 0.6;
63
+ }
64
+ return 0.3;
65
+ }
66
+ function normalizeModel(model) {
67
+ if (model.includes("/"))
68
+ return model;
69
+ if (model === "inherit")
70
+ return model;
71
+ if (/^claude-/.test(model))
72
+ return `anthropic/${model}`;
73
+ if (/^(gpt-|o1-|o3-)/.test(model))
74
+ return `openai/${model}`;
75
+ if (/^gemini-/.test(model))
76
+ return `google/${model}`;
77
+ return `anthropic/${model}`;
78
+ }
79
+ function transformAgentFrontmatter(data, agentMode) {
80
+ const name = typeof data.name === "string" ? data.name : "";
81
+ const description = typeof data.description === "string" ? data.description : "";
82
+ const newData = {
83
+ description: description || `${name} agent`,
84
+ mode: agentMode
85
+ };
86
+ if (typeof data.model === "string" && data.model !== "inherit") {
87
+ newData.model = normalizeModel(data.model);
88
+ }
89
+ if (typeof data.temperature === "number") {
90
+ newData.temperature = data.temperature;
91
+ } else {
92
+ newData.temperature = inferTemperature(name, description);
93
+ }
94
+ return newData;
95
+ }
96
+ function convertContent(content, type, options = {}) {
97
+ if (content === "")
98
+ return "";
99
+ const { data, body, raw } = parseFrontmatter(content);
100
+ const hasFrontmatter = raw !== "";
101
+ if (!hasFrontmatter) {
102
+ return content;
103
+ }
104
+ if (type === "agent") {
105
+ const agentMode = options.agentMode ?? "subagent";
106
+ const transformedData = transformAgentFrontmatter(data, agentMode);
107
+ return `${formatFrontmatter(transformedData)}
108
+ ${body}`;
109
+ }
110
+ return content;
111
+ }
112
+ function convertFileWithCache(filePath, type, options = {}) {
113
+ const fd = fs.openSync(filePath, "r");
114
+ try {
115
+ const stats = fs.fstatSync(fd);
116
+ const cacheKey = `${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}`;
117
+ const cached = cache.get(cacheKey);
118
+ if (cached != null && cached.mtimeMs === stats.mtimeMs) {
119
+ return cached.converted;
120
+ }
121
+ const content = fs.readFileSync(fd, "utf8");
122
+ const converted = convertContent(content, type, options);
123
+ cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
124
+ return converted;
125
+ } finally {
126
+ fs.closeSync(fd);
127
+ }
128
+ }
129
+
130
+ // src/lib/skills-core.ts
131
+ import fs2 from "node:fs";
3
132
  import path from "node:path";
4
133
  function extractFrontmatter(filePath) {
5
134
  try {
6
- const content = fs.readFileSync(filePath, "utf8");
135
+ const content = fs2.readFileSync(filePath, "utf8");
7
136
  const lines = content.split(`
8
137
  `);
9
138
  let inFrontmatter = false;
@@ -56,17 +185,17 @@ function stripFrontmatter(content) {
56
185
  }
57
186
  function findSkillsInDir(dir, sourceType, maxDepth = 3) {
58
187
  const skills = [];
59
- if (!fs.existsSync(dir))
188
+ if (!fs2.existsSync(dir))
60
189
  return skills;
61
190
  function recurse(currentDir, depth) {
62
191
  if (depth > maxDepth)
63
192
  return;
64
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
193
+ const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
65
194
  for (const entry of entries) {
66
195
  const fullPath = path.join(currentDir, entry.name);
67
196
  if (entry.isDirectory()) {
68
197
  const skillFile = path.join(fullPath, "SKILL.md");
69
- if (fs.existsSync(skillFile)) {
198
+ if (fs2.existsSync(skillFile)) {
70
199
  const { name, description } = extractFrontmatter(skillFile);
71
200
  skills.push({
72
201
  path: fullPath,
@@ -85,12 +214,12 @@ function findSkillsInDir(dir, sourceType, maxDepth = 3) {
85
214
  }
86
215
  function findAgentsInDir(dir, sourceType, maxDepth = 2) {
87
216
  const agents = [];
88
- if (!fs.existsSync(dir))
217
+ if (!fs2.existsSync(dir))
89
218
  return agents;
90
219
  function recurse(currentDir, depth, category) {
91
220
  if (depth > maxDepth)
92
221
  return;
93
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
222
+ const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
94
223
  for (const entry of entries) {
95
224
  const fullPath = path.join(currentDir, entry.name);
96
225
  if (entry.isDirectory()) {
@@ -110,12 +239,12 @@ function findAgentsInDir(dir, sourceType, maxDepth = 2) {
110
239
  }
111
240
  function findCommandsInDir(dir, sourceType, maxDepth = 2) {
112
241
  const commands = [];
113
- if (!fs.existsSync(dir))
242
+ if (!fs2.existsSync(dir))
114
243
  return commands;
115
244
  function recurse(currentDir, depth, category) {
116
245
  if (depth > maxDepth)
117
246
  return;
118
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
247
+ const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
119
248
  for (const entry of entries) {
120
249
  const fullPath = path.join(currentDir, entry.name);
121
250
  if (entry.isDirectory()) {
@@ -191,4 +320,4 @@ function extractCommandFrontmatter(content) {
191
320
  return { name, description, argumentHint };
192
321
  }
193
322
 
194
- export { stripFrontmatter, findSkillsInDir, findAgentsInDir, findCommandsInDir, extractAgentFrontmatter, extractCommandFrontmatter };
323
+ export { convertContent, convertFileWithCache, stripFrontmatter, findSkillsInDir, findAgentsInDir, findCommandsInDir, extractAgentFrontmatter, extractCommandFrontmatter };
package/dist/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import {
2
+ convertContent,
3
+ convertFileWithCache,
2
4
  extractAgentFrontmatter,
3
5
  extractCommandFrontmatter,
4
6
  findAgentsInDir,
5
7
  findCommandsInDir,
6
8
  findSkillsInDir,
7
9
  stripFrontmatter
8
- } from "./index-v8dhd5s2.js";
10
+ } from "./index-33zyxync.js";
9
11
 
10
12
  // src/index.ts
11
- import fs4 from "node:fs";
13
+ import fs3 from "node:fs";
12
14
  import os2 from "node:os";
13
15
  import path3 from "node:path";
14
16
  import { fileURLToPath } from "node:url";
@@ -64,14 +66,16 @@ function loadConfig(projectDir) {
64
66
  }
65
67
 
66
68
  // src/lib/config-handler.ts
67
- import fs2 from "node:fs";
68
69
  function loadAgentAsConfig(agentInfo) {
69
70
  try {
70
- const content = fs2.readFileSync(agentInfo.file, "utf8");
71
- const { name, description, prompt } = extractAgentFrontmatter(content);
71
+ const converted = convertFileWithCache(agentInfo.file, "agent", {
72
+ source: "bundled",
73
+ agentMode: "subagent"
74
+ });
75
+ const { description, prompt } = extractAgentFrontmatter(converted);
72
76
  return {
73
- description: description || `${name || agentInfo.name} agent`,
74
- prompt: prompt || stripFrontmatter(content)
77
+ description: description || `${agentInfo.name} agent`,
78
+ prompt: prompt || stripFrontmatter(converted)
75
79
  };
76
80
  } catch {
77
81
  return null;
@@ -79,11 +83,11 @@ function loadAgentAsConfig(agentInfo) {
79
83
  }
80
84
  function loadCommandAsConfig(commandInfo) {
81
85
  try {
82
- const content = fs2.readFileSync(commandInfo.file, "utf8");
83
- const { name, description } = extractCommandFrontmatter(content);
86
+ const converted = convertFileWithCache(commandInfo.file, "command", { source: "bundled" });
87
+ const { name, description } = extractCommandFrontmatter(converted);
84
88
  const cleanName = commandInfo.name.replace(/^\//, "");
85
89
  return {
86
- template: stripFrontmatter(content),
90
+ template: stripFrontmatter(converted),
87
91
  description: description || `${name || cleanName} command`
88
92
  };
89
93
  } catch {
@@ -92,9 +96,9 @@ function loadCommandAsConfig(commandInfo) {
92
96
  }
93
97
  function loadSkillAsCommand(skillInfo) {
94
98
  try {
95
- const content = fs2.readFileSync(skillInfo.skillFile, "utf8");
99
+ const converted = convertFileWithCache(skillInfo.skillFile, "skill", { source: "bundled" });
96
100
  return {
97
- template: stripFrontmatter(content),
101
+ template: stripFrontmatter(converted),
98
102
  description: skillInfo.description || `${skillInfo.name} skill`
99
103
  };
100
104
  } catch {
@@ -163,7 +167,7 @@ function createConfigHandler(deps) {
163
167
  }
164
168
 
165
169
  // src/lib/skill-tool.ts
166
- import fs3 from "node:fs";
170
+ import fs2 from "node:fs";
167
171
  import path2 from "node:path";
168
172
  import { tool } from "@opencode-ai/plugin/tool";
169
173
  var HOOK_KEY = "systematic_skill_tool_hooked";
@@ -223,13 +227,14 @@ ${systematicSkillsXml}`;
223
227
  }
224
228
  function wrapSkillContent(skillPath, content) {
225
229
  const skillDir = path2.dirname(skillPath);
226
- const body = stripFrontmatter(content);
227
- return `<skill_instruction>
230
+ const converted = convertContent(content, "skill", { source: "bundled" });
231
+ const body = stripFrontmatter(converted);
232
+ return `<skill-instruction>
228
233
  Base directory for this skill: ${skillDir}/
229
234
  File references (@path) in this skill are relative to this directory.
230
235
 
231
236
  ${body.trim()}
232
- </skill_instruction>`;
237
+ </skill-instruction>`;
233
238
  }
234
239
  function createSkillTool(options) {
235
240
  const { bundledSkillsDir, disabledSkills } = options;
@@ -264,7 +269,7 @@ Use this when a task matches an available skill's description.`;
264
269
  const matchedSkill = skills.find((s) => s.name === normalizedName);
265
270
  if (matchedSkill) {
266
271
  try {
267
- const content = fs3.readFileSync(matchedSkill.skillFile, "utf8");
272
+ const content = fs2.readFileSync(matchedSkill.skillFile, "utf8");
268
273
  const wrapped = wrapSkillContent(matchedSkill.skillFile, content);
269
274
  return `## Skill: systematic:${matchedSkill.name}
270
275
 
@@ -304,9 +309,9 @@ var packageJsonPath = path3.join(packageRoot, "package.json");
304
309
  var hasLoggedInit = false;
305
310
  var getPackageVersion = () => {
306
311
  try {
307
- if (!fs4.existsSync(packageJsonPath))
312
+ if (!fs3.existsSync(packageJsonPath))
308
313
  return "unknown";
309
- const content = fs4.readFileSync(packageJsonPath, "utf8");
314
+ const content = fs3.readFileSync(packageJsonPath, "utf8");
310
315
  const parsed = JSON.parse(content);
311
316
  return parsed.version ?? "unknown";
312
317
  } catch {
@@ -318,14 +323,14 @@ var getBootstrapContent = (config) => {
318
323
  return null;
319
324
  if (config.bootstrap.file) {
320
325
  const customPath = config.bootstrap.file.startsWith("~/") ? path3.join(os2.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
321
- if (fs4.existsSync(customPath)) {
322
- return fs4.readFileSync(customPath, "utf8");
326
+ if (fs3.existsSync(customPath)) {
327
+ return fs3.readFileSync(customPath, "utf8");
323
328
  }
324
329
  }
325
330
  const usingSystematicPath = path3.join(bundledSkillsDir, "using-systematic/SKILL.md");
326
- if (!fs4.existsSync(usingSystematicPath))
331
+ if (!fs3.existsSync(usingSystematicPath))
327
332
  return null;
328
- const fullContent = fs4.readFileSync(usingSystematicPath, "utf8");
333
+ const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
329
334
  const content = stripFrontmatter(fullContent);
330
335
  const toolMapping = `**Tool Mapping for OpenCode:**
331
336
  When skills reference tools you don't have, substitute OpenCode equivalents:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Structured engineering workflows for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",