@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 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
- | `create-agent-skills` | Expert guidance for writing and refining skills |
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 fs2 from "fs";
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 to get detailed instructions for a specific task.",
311
- "Skills provide specialized knowledge and step-by-step guidance.",
312
- "Use this when a task matches an available skill's description.",
313
- "Only the skills listed here are available:",
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 identifier from available_skills${hint}`;
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
- return [
365
- `## Skill: ${matchedSkill.prefixedName}`,
410
+ const output = [
411
+ `<skill_content name="${matchedSkill.prefixedName}">`,
412
+ `# Skill: ${matchedSkill.prefixedName}`,
366
413
  "",
367
- `**Base directory**: ${dir}`,
414
+ body.trim(),
368
415
  "",
369
- body.trim()
370
- ].join(`
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 (!fs2.existsSync(packageJsonPath))
440
+ if (!fs3.existsSync(packageJsonPath))
387
441
  return "unknown";
388
- const content = fs2.readFileSync(packageJsonPath, "utf8");
442
+ const content = fs3.readFileSync(packageJsonPath, "utf8");
389
443
  const parsed = JSON.parse(content);
390
444
  return parsed.version ?? "unknown";
391
445
  } catch {
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Structured engineering workflows for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",