@fro.bot/systematic 3.8.0 → 3.9.1

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/pi.js CHANGED
@@ -3316,196 +3316,13 @@ function resolveToolAllowlist(toolsSource) {
3316
3316
  }
3317
3317
 
3318
3318
  // src/lib/bootstrap.ts
3319
- import fs5 from "node:fs";
3320
- import os from "node:os";
3321
- import path4 from "node:path";
3322
-
3323
- // src/lib/skill-catalog.ts
3324
- import { pathToFileURL } from "node:url";
3325
-
3326
- // src/lib/skill-loader.ts
3327
3319
  import fs3 from "node:fs";
3320
+ import os from "node:os";
3328
3321
  import path2 from "node:path";
3329
- var SKILL_PREFIX = "systematic:";
3330
- var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
3331
- function formatSkillCommandName(name) {
3332
- if (name.includes(":")) {
3333
- return name;
3334
- }
3335
- return `${SKILL_PREFIX}${name}`;
3336
- }
3337
- function formatSkillDescription(description, fallbackName) {
3338
- const desc = description || `${fallbackName} skill`;
3339
- if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
3340
- return desc;
3341
- }
3342
- return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
3343
- }
3344
- function wrapSkillTemplate(skillPath, body) {
3345
- const skillDir = path2.dirname(skillPath);
3346
- return `<skill-instruction>
3347
- Base directory for this skill: ${skillDir}/
3348
- File references (@path) in this skill are relative to this directory.
3349
-
3350
- ${body.trim()}
3351
- </skill-instruction>
3352
-
3353
- <user-request>
3354
- $ARGUMENTS
3355
- </user-request>`;
3356
- }
3357
- function extractSkillBody(wrappedTemplate) {
3358
- const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
3359
- return match ? match[1].trim() : wrappedTemplate;
3360
- }
3361
- function loadSkill(skillInfo) {
3362
- try {
3363
- const content = fs3.readFileSync(skillInfo.skillFile, "utf8");
3364
- const { body } = parseFrontmatter(content);
3365
- const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
3366
- return {
3367
- name: skillInfo.name,
3368
- prefixedName: formatSkillCommandName(skillInfo.name),
3369
- description: formatSkillDescription(skillInfo.description, skillInfo.name),
3370
- path: skillInfo.path,
3371
- skillFile: skillInfo.skillFile,
3372
- wrappedTemplate,
3373
- disableModelInvocation: skillInfo.disableModelInvocation,
3374
- userInvocable: skillInfo.userInvocable,
3375
- subtask: skillInfo.subtask,
3376
- agent: skillInfo.agent,
3377
- model: skillInfo.model,
3378
- argumentHint: skillInfo.argumentHint
3379
- };
3380
- } catch {
3381
- return null;
3382
- }
3383
- }
3384
-
3385
- // src/lib/skills.ts
3386
- import fs4 from "node:fs";
3387
- import path3 from "node:path";
3388
- function parseMetadata(data) {
3389
- const metadataRaw = data.metadata;
3390
- if (!isRecord(metadataRaw)) {
3391
- return;
3392
- }
3393
- const entries = Object.entries(metadataRaw);
3394
- if (!entries.every(([, v]) => typeof v === "string")) {
3395
- return;
3396
- }
3397
- return Object.fromEntries(entries);
3398
- }
3399
- function extractFrontmatterFromContent(content) {
3400
- const { data, parseError } = parseFrontmatter(content);
3401
- if (parseError) {
3402
- return { name: "", description: "" };
3403
- }
3404
- const metadata = parseMetadata(data);
3405
- const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
3406
- const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
3407
- return {
3408
- name: extractString(data, "name"),
3409
- description: extractString(data, "description"),
3410
- license: extractNonEmptyString(data, "license"),
3411
- compatibility: extractNonEmptyString(data, "compatibility"),
3412
- metadata,
3413
- disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
3414
- userInvocable: extractBoolean(data, "user-invocable"),
3415
- subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
3416
- agent: extractNonEmptyString(data, "agent"),
3417
- model: extractNonEmptyString(data, "model"),
3418
- argumentHint: argumentHint !== "" ? argumentHint : undefined,
3419
- allowedTools: extractNonEmptyString(data, "allowed-tools")
3420
- };
3421
- }
3422
- function extractFrontmatter(filePath) {
3423
- try {
3424
- const content = fs4.readFileSync(filePath, "utf8");
3425
- return extractFrontmatterFromContent(content);
3426
- } catch {
3427
- return { name: "", description: "" };
3428
- }
3429
- }
3430
- function findSkillsInDir(dir, maxDepth = 3) {
3431
- const skills = [];
3432
- const entries = walkDir(dir, {
3433
- maxDepth,
3434
- filter: (e) => e.isDirectory
3435
- });
3436
- for (const entry of entries) {
3437
- const skillFile = path3.join(entry.path, "SKILL.md");
3438
- if (fs4.existsSync(skillFile)) {
3439
- const frontmatter = extractFrontmatter(skillFile);
3440
- skills.push({
3441
- path: entry.path,
3442
- skillFile,
3443
- name: frontmatter.name || entry.name,
3444
- description: frontmatter.description || "",
3445
- license: frontmatter.license,
3446
- compatibility: frontmatter.compatibility,
3447
- metadata: frontmatter.metadata,
3448
- disableModelInvocation: frontmatter.disableModelInvocation,
3449
- userInvocable: frontmatter.userInvocable,
3450
- subtask: frontmatter.subtask,
3451
- agent: frontmatter.agent,
3452
- model: frontmatter.model,
3453
- argumentHint: frontmatter.argumentHint,
3454
- allowedTools: frontmatter.allowedTools
3455
- });
3456
- }
3457
- }
3458
- return skills;
3459
- }
3460
-
3461
- // src/lib/skill-catalog.ts
3462
- function escapeXml(text) {
3463
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3464
- }
3465
- function buildCatalogEntries(options) {
3466
- const { bundledSkillsDir, disabledSkills } = options;
3467
- return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
3468
- name: s.name,
3469
- prefixedName: formatSkillCommandName(s.name),
3470
- description: s.description,
3471
- path: s.path,
3472
- skillFile: s.skillFile
3473
- }));
3474
- }
3475
- function renderCatalogVerbose(options) {
3476
- const entries = buildCatalogEntries(options);
3477
- if (entries.length === 0)
3478
- return "";
3479
- const skillLines = entries.flatMap((entry) => [
3480
- " <skill>",
3481
- ` <name>${escapeXml(entry.prefixedName)}</name>`,
3482
- ` <description>${escapeXml(entry.description)}</description>`,
3483
- ` <location>${pathToFileURL(entry.path).href}</location>`,
3484
- " </skill>"
3485
- ]);
3486
- return ["<available_skills>", ...skillLines, "</available_skills>"].join(`
3487
- `);
3488
- }
3489
- function renderCatalogCompact(options) {
3490
- const entries = buildCatalogEntries(options);
3491
- const heading = "## Available Systematic Skills";
3492
- if (entries.length === 0) {
3493
- return `${heading}
3494
-
3495
- No Systematic skills are currently available.`;
3496
- }
3497
- const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
3498
- `);
3499
- return `${heading}
3500
-
3501
- ${bullets}`;
3502
- }
3503
-
3504
- // src/lib/bootstrap.ts
3505
3322
  function readHarnessProfile(bundledSkillsDir, name) {
3506
- const profilePath = path4.join(bundledSkillsDir, "using-systematic/references", `${name}-profile.md`);
3323
+ const profilePath = path2.join(bundledSkillsDir, "using-systematic/references", `${name}-profile.md`);
3507
3324
  try {
3508
- return fs5.readFileSync(profilePath, "utf8");
3325
+ return fs3.readFileSync(profilePath, "utf8");
3509
3326
  } catch (error) {
3510
3327
  console.error(`Failed to read harness profile ${profilePath}: ${error instanceof Error ? error.message : String(error)}`);
3511
3328
  return null;
@@ -3557,25 +3374,18 @@ function getBootstrapContent(config, deps) {
3557
3374
  if (!config.bootstrap.enabled)
3558
3375
  return null;
3559
3376
  if (config.bootstrap.file) {
3560
- const customPath = config.bootstrap.file.startsWith("~/") ? path4.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
3561
- if (fs5.existsSync(customPath)) {
3562
- return fs5.readFileSync(customPath, "utf8");
3377
+ const customPath = config.bootstrap.file.startsWith("~/") ? path2.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
3378
+ if (fs3.existsSync(customPath)) {
3379
+ return fs3.readFileSync(customPath, "utf8");
3563
3380
  }
3564
3381
  }
3565
- const usingSystematicPath = path4.join(bundledSkillsDir, "using-systematic/SKILL.md");
3566
- if (!fs5.existsSync(usingSystematicPath))
3382
+ const usingSystematicPath = path2.join(bundledSkillsDir, "using-systematic/SKILL.md");
3383
+ if (!fs3.existsSync(usingSystematicPath))
3567
3384
  return null;
3568
- const fullContent = fs5.readFileSync(usingSystematicPath, "utf8");
3385
+ const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
3569
3386
  const { body } = parseFrontmatter(fullContent);
3570
3387
  const content = body.trim();
3571
3388
  const skillUsage = usageTemplate ?? getSkillUsageTemplate();
3572
- const catalog = renderCatalogVerbose({
3573
- bundledSkillsDir,
3574
- disabledSkills: config.disabled_skills
3575
- });
3576
- const catalogSection = catalog.length > 0 ? `
3577
-
3578
- ${catalog}` : "";
3579
3389
  const profileSection = profileBlock === undefined ? "" : `
3580
3390
 
3581
3391
  ${profileBlock}`;
@@ -3586,7 +3396,7 @@ You have access to structured engineering workflows via the Systematic plugin.
3586
3396
 
3587
3397
  ${content}
3588
3398
 
3589
- ${skillUsage}${profileSection}${catalogSection}
3399
+ ${skillUsage}${profileSection}
3590
3400
  </SYSTEMATIC_WORKFLOWS>`;
3591
3401
  }
3592
3402
 
@@ -3920,7 +3730,168 @@ var createRealPiDelegateSession = createDelegateSessionWith(REAL_PI_DELEGATE_SES
3920
3730
  // src/lib/skill-resolver.ts
3921
3731
  import fs6 from "node:fs";
3922
3732
  import path5 from "node:path";
3923
- import { pathToFileURL as pathToFileURL2 } from "node:url";
3733
+ import { pathToFileURL } from "node:url";
3734
+
3735
+ // src/lib/skill-loader.ts
3736
+ import fs4 from "node:fs";
3737
+ import path3 from "node:path";
3738
+ var SKILL_PREFIX = "systematic:";
3739
+ var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
3740
+ function formatSkillCommandName(name) {
3741
+ if (name.includes(":")) {
3742
+ return name;
3743
+ }
3744
+ return `${SKILL_PREFIX}${name}`;
3745
+ }
3746
+ function formatSkillDescription(description, fallbackName) {
3747
+ const desc = description || `${fallbackName} skill`;
3748
+ if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
3749
+ return desc;
3750
+ }
3751
+ return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
3752
+ }
3753
+ function wrapSkillTemplate(skillPath, body) {
3754
+ const skillDir = path3.dirname(skillPath);
3755
+ return `<skill-instruction>
3756
+ Base directory for this skill: ${skillDir}/
3757
+ File references (@path) in this skill are relative to this directory.
3758
+
3759
+ ${body.trim()}
3760
+ </skill-instruction>
3761
+
3762
+ <user-request>
3763
+ $ARGUMENTS
3764
+ </user-request>`;
3765
+ }
3766
+ function extractSkillBody(wrappedTemplate) {
3767
+ const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
3768
+ return match ? match[1].trim() : wrappedTemplate;
3769
+ }
3770
+ function loadSkill(skillInfo) {
3771
+ try {
3772
+ const content = fs4.readFileSync(skillInfo.skillFile, "utf8");
3773
+ const { body } = parseFrontmatter(content);
3774
+ const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body);
3775
+ return {
3776
+ name: skillInfo.name,
3777
+ prefixedName: formatSkillCommandName(skillInfo.name),
3778
+ description: formatSkillDescription(skillInfo.description, skillInfo.name),
3779
+ path: skillInfo.path,
3780
+ skillFile: skillInfo.skillFile,
3781
+ wrappedTemplate,
3782
+ disableModelInvocation: skillInfo.disableModelInvocation,
3783
+ userInvocable: skillInfo.userInvocable,
3784
+ subtask: skillInfo.subtask,
3785
+ agent: skillInfo.agent,
3786
+ model: skillInfo.model,
3787
+ argumentHint: skillInfo.argumentHint
3788
+ };
3789
+ } catch {
3790
+ return null;
3791
+ }
3792
+ }
3793
+
3794
+ // src/lib/skills.ts
3795
+ import fs5 from "node:fs";
3796
+ import path4 from "node:path";
3797
+ function parseMetadata(data) {
3798
+ const metadataRaw = data.metadata;
3799
+ if (!isRecord(metadataRaw)) {
3800
+ return;
3801
+ }
3802
+ const entries = Object.entries(metadataRaw);
3803
+ if (!entries.every(([, v]) => typeof v === "string")) {
3804
+ return;
3805
+ }
3806
+ return Object.fromEntries(entries);
3807
+ }
3808
+ function extractFrontmatterFromContent(content) {
3809
+ const { data, parseError } = parseFrontmatter(content);
3810
+ if (parseError) {
3811
+ return { name: "", description: "" };
3812
+ }
3813
+ const metadata = parseMetadata(data);
3814
+ const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
3815
+ const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
3816
+ return {
3817
+ name: extractString(data, "name"),
3818
+ description: extractString(data, "description"),
3819
+ license: extractNonEmptyString(data, "license"),
3820
+ compatibility: extractNonEmptyString(data, "compatibility"),
3821
+ metadata,
3822
+ disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
3823
+ userInvocable: extractBoolean(data, "user-invocable"),
3824
+ subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
3825
+ agent: extractNonEmptyString(data, "agent"),
3826
+ model: extractNonEmptyString(data, "model"),
3827
+ argumentHint: argumentHint !== "" ? argumentHint : undefined,
3828
+ allowedTools: extractNonEmptyString(data, "allowed-tools")
3829
+ };
3830
+ }
3831
+ function extractFrontmatter(filePath) {
3832
+ try {
3833
+ const content = fs5.readFileSync(filePath, "utf8");
3834
+ return extractFrontmatterFromContent(content);
3835
+ } catch {
3836
+ return { name: "", description: "" };
3837
+ }
3838
+ }
3839
+ function findSkillsInDir(dir, maxDepth = 3) {
3840
+ const skills = [];
3841
+ const entries = walkDir(dir, {
3842
+ maxDepth,
3843
+ filter: (e) => e.isDirectory
3844
+ });
3845
+ for (const entry of entries) {
3846
+ const skillFile = path4.join(entry.path, "SKILL.md");
3847
+ if (fs5.existsSync(skillFile)) {
3848
+ const frontmatter = extractFrontmatter(skillFile);
3849
+ skills.push({
3850
+ path: entry.path,
3851
+ skillFile,
3852
+ name: frontmatter.name || entry.name,
3853
+ description: frontmatter.description || "",
3854
+ license: frontmatter.license,
3855
+ compatibility: frontmatter.compatibility,
3856
+ metadata: frontmatter.metadata,
3857
+ disableModelInvocation: frontmatter.disableModelInvocation,
3858
+ userInvocable: frontmatter.userInvocable,
3859
+ subtask: frontmatter.subtask,
3860
+ agent: frontmatter.agent,
3861
+ model: frontmatter.model,
3862
+ argumentHint: frontmatter.argumentHint,
3863
+ allowedTools: frontmatter.allowedTools
3864
+ });
3865
+ }
3866
+ }
3867
+ return skills;
3868
+ }
3869
+
3870
+ // src/lib/skill-catalog.ts
3871
+ function buildCatalogEntries(options) {
3872
+ const { bundledSkillsDir, disabledSkills } = options;
3873
+ return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
3874
+ name: s.name,
3875
+ prefixedName: formatSkillCommandName(s.name),
3876
+ description: s.description
3877
+ }));
3878
+ }
3879
+ function renderCatalogCompact(options) {
3880
+ const entries = buildCatalogEntries(options);
3881
+ const heading = "## Available Systematic Skills";
3882
+ if (entries.length === 0) {
3883
+ return `${heading}
3884
+
3885
+ No Systematic skills are currently available.`;
3886
+ }
3887
+ const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
3888
+ `);
3889
+ return `${heading}
3890
+
3891
+ ${bullets}`;
3892
+ }
3893
+
3894
+ // src/lib/skill-resolver.ts
3924
3895
  function getAllSkills(options) {
3925
3896
  const { bundledSkillsDir, disabledSkills } = options;
3926
3897
  return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).map((skillInfo) => loadSkill(skillInfo)).filter((s) => s !== null).sort((a, b) => a.name.localeCompare(b.name));
@@ -3956,7 +3927,7 @@ function buildSkillToolParameterHint(options) {
3956
3927
  function buildSkillContentOutput(matchedSkill) {
3957
3928
  const body = extractSkillBody(matchedSkill.wrappedTemplate);
3958
3929
  const dir = path5.dirname(matchedSkill.skillFile);
3959
- const base = pathToFileURL2(dir).href;
3930
+ const base = pathToFileURL(dir).href;
3960
3931
  const files = discoverSkillFiles(dir);
3961
3932
  const lines = [
3962
3933
  `<skill_content name="${matchedSkill.prefixedName}">`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "3.8.0",
3
+ "version": "3.9.1",
4
4
  "description": "Compound-engineering loops for OpenCode, Pi, and Claude Code",
5
5
  "type": "module",
6
6
  "homepage": "https://fro.bot/systematic",
@@ -93,8 +93,8 @@
93
93
  "devDependencies": {
94
94
  "@biomejs/biome": "2.5.8",
95
95
  "@earendil-works/pi-coding-agent": "0.83.0",
96
- "@opencode-ai/plugin": "1.18.16",
97
- "@opencode-ai/sdk": "1.18.16",
96
+ "@opencode-ai/plugin": "1.18.18",
97
+ "@opencode-ai/sdk": "1.18.18",
98
98
  "@semantic-release/exec": "7.1.0",
99
99
  "@tintinweb/pi-subagents": "0.14.3",
100
100
  "@types/bun": "latest",
@@ -108,7 +108,7 @@
108
108
  "rimraf": "6.1.3",
109
109
  "semantic-release": "25.0.9",
110
110
  "semantic-release-export-data": "1.2.0",
111
- "typebox": "1.3.12",
111
+ "typebox": "1.3.13",
112
112
  "typescript": "7.0.2"
113
113
  },
114
114
  "dependencies": {