@fro.bot/systematic 1.8.1 → 1.9.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 +1 -1
- package/dist/index.js +68 -14
- package/dist/lib/skill-tool.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Skills are structured workflows that guide the AI through systematic engineering
|
|
|
86
86
|
| `agent-browser` | Browser automation using Vercel's agent-browser CLI |
|
|
87
87
|
| `agent-native-architecture` | Design systems where AI agents are first-class citizens |
|
|
88
88
|
| `compound-docs` | Capture solved problems as categorized documentation |
|
|
89
|
-
| `
|
|
89
|
+
| `creating-agent-skills` | Expert guidance for writing and refining skills |
|
|
90
90
|
| `file-todos` | File-based todo tracking with status and dependency management |
|
|
91
91
|
| `git-worktree` | Manage git worktrees for isolated parallel development |
|
|
92
92
|
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./index-yxbcy3s7.js";
|
|
12
12
|
|
|
13
13
|
// src/index.ts
|
|
14
|
-
import
|
|
14
|
+
import fs3 from "fs";
|
|
15
15
|
import path4 from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
|
|
@@ -276,7 +276,9 @@ function createConfigHandler(deps) {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// src/lib/skill-tool.ts
|
|
279
|
+
import fs2 from "fs";
|
|
279
280
|
import path3 from "path";
|
|
281
|
+
import { pathToFileURL } from "url";
|
|
280
282
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
281
283
|
function formatSkillsXml(skills) {
|
|
282
284
|
if (skills.length === 0)
|
|
@@ -285,10 +287,44 @@ function formatSkillsXml(skills) {
|
|
|
285
287
|
" <skill>",
|
|
286
288
|
` <name>systematic:${skill.name}</name>`,
|
|
287
289
|
` <description>${skill.description}</description>`,
|
|
290
|
+
` <location>${pathToFileURL(skill.path).href}</location>`,
|
|
288
291
|
" </skill>"
|
|
289
292
|
]);
|
|
290
293
|
return ["<available_skills>", ...skillLines, "</available_skills>"].join(" ");
|
|
291
294
|
}
|
|
295
|
+
function discoverSkillFiles(dir, limit = 10) {
|
|
296
|
+
const files = [];
|
|
297
|
+
function shouldSkipDirectory(name) {
|
|
298
|
+
return name === ".git";
|
|
299
|
+
}
|
|
300
|
+
function shouldIncludeFile(name) {
|
|
301
|
+
return name !== "SKILL.md";
|
|
302
|
+
}
|
|
303
|
+
function handleEntry(entry, currentDir) {
|
|
304
|
+
if (entry.isDirectory()) {
|
|
305
|
+
if (!shouldSkipDirectory(entry.name)) {
|
|
306
|
+
recurse(path3.resolve(currentDir, entry.name));
|
|
307
|
+
}
|
|
308
|
+
} else if (shouldIncludeFile(entry.name)) {
|
|
309
|
+
files.push(path3.resolve(currentDir, entry.name));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function recurse(currentDir) {
|
|
313
|
+
if (files.length >= limit)
|
|
314
|
+
return;
|
|
315
|
+
try {
|
|
316
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (files.length >= limit)
|
|
319
|
+
break;
|
|
320
|
+
handleEntry(entry, currentDir);
|
|
321
|
+
}
|
|
322
|
+
} catch {}
|
|
323
|
+
}
|
|
324
|
+
recurse(dir);
|
|
325
|
+
return files.map((file) => ` <file>${file}</file>`).join(`
|
|
326
|
+
`);
|
|
327
|
+
}
|
|
292
328
|
function createSkillTool(options) {
|
|
293
329
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
294
330
|
const getSystematicSkills = () => {
|
|
@@ -307,18 +343,26 @@ function createSkillTool(options) {
|
|
|
307
343
|
}));
|
|
308
344
|
const systematicXml = formatSkillsXml(skillInfos);
|
|
309
345
|
return [
|
|
310
|
-
"Load a skill
|
|
311
|
-
"
|
|
312
|
-
"
|
|
313
|
-
"
|
|
346
|
+
"Load a specialized skill that provides domain-specific instructions and workflows.",
|
|
347
|
+
"",
|
|
348
|
+
"When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.",
|
|
349
|
+
"",
|
|
350
|
+
"The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context.",
|
|
351
|
+
"",
|
|
352
|
+
'Tool output includes a `<skill_content name="...">` block with the loaded content.',
|
|
353
|
+
"",
|
|
354
|
+
"The following skills provide specialized sets of instructions for particular tasks.",
|
|
355
|
+
"Invoke this tool to load a skill when a task matches one of the available skills listed below:",
|
|
356
|
+
"",
|
|
314
357
|
systematicXml
|
|
315
|
-
].join(
|
|
358
|
+
].join(`
|
|
359
|
+
`);
|
|
316
360
|
};
|
|
317
361
|
const buildParameterHint = () => {
|
|
318
362
|
const skills = getSystematicSkills();
|
|
319
363
|
const examples = skills.slice(0, 3).map((s) => `'systematic:${s.name}'`).join(", ");
|
|
320
364
|
const hint = examples.length > 0 ? ` (e.g., ${examples}, ...)` : "";
|
|
321
|
-
return `The skill
|
|
365
|
+
return `The name of the skill from available_skills${hint}`;
|
|
322
366
|
};
|
|
323
367
|
let cachedDescription = null;
|
|
324
368
|
let cachedParameterHint = null;
|
|
@@ -348,6 +392,8 @@ function createSkillTool(options) {
|
|
|
348
392
|
}
|
|
349
393
|
const body = extractSkillBody(matchedSkill.wrappedTemplate);
|
|
350
394
|
const dir = path3.dirname(matchedSkill.skillFile);
|
|
395
|
+
const base = pathToFileURL(dir).href;
|
|
396
|
+
const files = discoverSkillFiles(dir);
|
|
351
397
|
await context.ask({
|
|
352
398
|
permission: "skill",
|
|
353
399
|
patterns: [matchedSkill.prefixedName],
|
|
@@ -361,13 +407,21 @@ function createSkillTool(options) {
|
|
|
361
407
|
dir
|
|
362
408
|
}
|
|
363
409
|
});
|
|
364
|
-
|
|
365
|
-
|
|
410
|
+
const output = [
|
|
411
|
+
`<skill_content name="${matchedSkill.prefixedName}">`,
|
|
412
|
+
`# Skill: ${matchedSkill.prefixedName}`,
|
|
366
413
|
"",
|
|
367
|
-
|
|
414
|
+
body.trim(),
|
|
368
415
|
"",
|
|
369
|
-
|
|
370
|
-
|
|
416
|
+
`Base directory for this skill: ${base}`,
|
|
417
|
+
"Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.",
|
|
418
|
+
"Note: file list is sampled."
|
|
419
|
+
];
|
|
420
|
+
if (files) {
|
|
421
|
+
output.push("", "<skill_files>", files, "</skill_files>");
|
|
422
|
+
}
|
|
423
|
+
output.push("</skill_content>");
|
|
424
|
+
return output.join(`
|
|
371
425
|
`);
|
|
372
426
|
}
|
|
373
427
|
});
|
|
@@ -383,9 +437,9 @@ var packageJsonPath = path4.join(packageRoot, "package.json");
|
|
|
383
437
|
var hasLoggedInit = false;
|
|
384
438
|
var getPackageVersion = () => {
|
|
385
439
|
try {
|
|
386
|
-
if (!
|
|
440
|
+
if (!fs3.existsSync(packageJsonPath))
|
|
387
441
|
return "unknown";
|
|
388
|
-
const content =
|
|
442
|
+
const content = fs3.readFileSync(packageJsonPath, "utf8");
|
|
389
443
|
const parsed = JSON.parse(content);
|
|
390
444
|
return parsed.version ?? "unknown";
|
|
391
445
|
} catch {
|
package/dist/lib/skill-tool.d.ts
CHANGED
|
@@ -9,4 +9,14 @@ export interface SkillToolOptions {
|
|
|
9
9
|
* Uses indented format matching OpenCode's native skill tool.
|
|
10
10
|
*/
|
|
11
11
|
export declare function formatSkillsXml(skills: SkillInfo[]): string;
|
|
12
|
+
/**
|
|
13
|
+
* Discovers skill files in a directory and formats them as XML tags.
|
|
14
|
+
* Recursively searches subdirectories, includes hidden files, excludes .git and SKILL.md.
|
|
15
|
+
* Matches OpenCode v1.1.50 behavior exactly.
|
|
16
|
+
*
|
|
17
|
+
* @param dir - Directory path to search for skill files
|
|
18
|
+
* @param limit - Maximum number of files to return (default: 10)
|
|
19
|
+
* @returns String with absolute file paths formatted as XML tags, one per line
|
|
20
|
+
*/
|
|
21
|
+
export declare function discoverSkillFiles(dir: string, limit?: number): string;
|
|
12
22
|
export declare function createSkillTool(options: SkillToolOptions): ToolDefinition;
|