@fro.bot/systematic 3.9.0 → 3.10.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/dist/index.js CHANGED
@@ -14,125 +14,18 @@ import {
14
14
  loadConfig,
15
15
  loadConfigWithSources,
16
16
  parseFrontmatter
17
- } from "./index-hjbt4p5s.js";
17
+ } from "./index-y8nc60tz.js";
18
18
 
19
19
  // src/index.ts
20
- import { createHash as createHash5 } from "crypto";
21
- import fs11 from "fs";
22
- import path10 from "path";
23
- import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "url";
20
+ import { createHash as createHash4 } from "crypto";
21
+ import fs10 from "fs";
22
+ import path8 from "path";
23
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
24
24
 
25
25
  // src/lib/bootstrap.ts
26
- import fs3 from "fs";
27
- import os from "os";
28
- import path2 from "path";
29
-
30
- // src/lib/skill-catalog.ts
31
- import { pathToFileURL } from "url";
32
-
33
- // src/lib/skill-loader.ts
34
26
  import fs2 from "fs";
27
+ import os from "os";
35
28
  import path from "path";
36
- var SKILL_PREFIX = "systematic:";
37
- var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
38
- function formatSkillCommandName(name2) {
39
- if (name2.includes(":")) {
40
- return name2;
41
- }
42
- return `${SKILL_PREFIX}${name2}`;
43
- }
44
- function formatSkillDescription(description, fallbackName) {
45
- const desc = description || `${fallbackName} skill`;
46
- if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
47
- return desc;
48
- }
49
- return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
50
- }
51
- function wrapSkillTemplate(skillPath, body2) {
52
- const skillDir = path.dirname(skillPath);
53
- return `<skill-instruction>
54
- Base directory for this skill: ${skillDir}/
55
- File references (@path) in this skill are relative to this directory.
56
-
57
- ${body2.trim()}
58
- </skill-instruction>
59
-
60
- <user-request>
61
- $ARGUMENTS
62
- </user-request>`;
63
- }
64
- function extractSkillBody(wrappedTemplate) {
65
- const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
66
- return match ? match[1].trim() : wrappedTemplate;
67
- }
68
- function loadSkill(skillInfo) {
69
- try {
70
- const content = fs2.readFileSync(skillInfo.skillFile, "utf8");
71
- const { body: body2 } = parseFrontmatter(content);
72
- const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body2);
73
- return {
74
- name: skillInfo.name,
75
- prefixedName: formatSkillCommandName(skillInfo.name),
76
- description: formatSkillDescription(skillInfo.description, skillInfo.name),
77
- path: skillInfo.path,
78
- skillFile: skillInfo.skillFile,
79
- wrappedTemplate,
80
- disableModelInvocation: skillInfo.disableModelInvocation,
81
- userInvocable: skillInfo.userInvocable,
82
- subtask: skillInfo.subtask,
83
- agent: skillInfo.agent,
84
- model: skillInfo.model,
85
- argumentHint: skillInfo.argumentHint
86
- };
87
- } catch {
88
- return null;
89
- }
90
- }
91
-
92
- // src/lib/skill-catalog.ts
93
- function escapeXml(text) {
94
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
95
- }
96
- function buildCatalogEntries(options) {
97
- const { bundledSkillsDir, disabledSkills } = options;
98
- return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
99
- name: s.name,
100
- prefixedName: formatSkillCommandName(s.name),
101
- description: s.description,
102
- path: s.path,
103
- skillFile: s.skillFile
104
- }));
105
- }
106
- function renderCatalogVerbose(options) {
107
- const entries = buildCatalogEntries(options);
108
- if (entries.length === 0)
109
- return "";
110
- const skillLines = entries.flatMap((entry) => [
111
- " <skill>",
112
- ` <name>${escapeXml(entry.prefixedName)}</name>`,
113
- ` <description>${escapeXml(entry.description)}</description>`,
114
- ` <location>${pathToFileURL(entry.path).href}</location>`,
115
- " </skill>"
116
- ]);
117
- return ["<available_skills>", ...skillLines, "</available_skills>"].join(`
118
- `);
119
- }
120
- function renderCatalogCompact(options) {
121
- const entries = buildCatalogEntries(options);
122
- const heading = "## Available Systematic Skills";
123
- if (entries.length === 0) {
124
- return `${heading}
125
-
126
- No Systematic skills are currently available.`;
127
- }
128
- const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
129
- `);
130
- return `${heading}
131
-
132
- ${bullets}`;
133
- }
134
-
135
- // src/lib/bootstrap.ts
136
29
  var INTERNAL_AGENT_SIGNATURES = [
137
30
  "You are a title generator",
138
31
  "You are a helpful AI assistant tasked with summarizing conversations",
@@ -190,9 +83,9 @@ var applyBootstrapContent = (output, content) => {
190
83
  ${content}` : content;
191
84
  };
192
85
  function readHarnessProfile(bundledSkillsDir, name2) {
193
- const profilePath = path2.join(bundledSkillsDir, "using-systematic/references", `${name2}-profile.md`);
86
+ const profilePath = path.join(bundledSkillsDir, "using-systematic/references", `${name2}-profile.md`);
194
87
  try {
195
- return fs3.readFileSync(profilePath, "utf8");
88
+ return fs2.readFileSync(profilePath, "utf8");
196
89
  } catch (error) {
197
90
  console.error(`Failed to read harness profile ${profilePath}: ${error instanceof Error ? error.message : String(error)}`);
198
91
  return null;
@@ -216,25 +109,18 @@ function getBootstrapContent(config, deps) {
216
109
  if (!config.bootstrap.enabled)
217
110
  return null;
218
111
  if (config.bootstrap.file) {
219
- const customPath = config.bootstrap.file.startsWith("~/") ? path2.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
220
- if (fs3.existsSync(customPath)) {
221
- return fs3.readFileSync(customPath, "utf8");
112
+ const customPath = config.bootstrap.file.startsWith("~/") ? path.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
113
+ if (fs2.existsSync(customPath)) {
114
+ return fs2.readFileSync(customPath, "utf8");
222
115
  }
223
116
  }
224
- const usingSystematicPath = path2.join(bundledSkillsDir, "using-systematic/SKILL.md");
225
- if (!fs3.existsSync(usingSystematicPath))
117
+ const usingSystematicPath = path.join(bundledSkillsDir, "using-systematic/SKILL.md");
118
+ if (!fs2.existsSync(usingSystematicPath))
226
119
  return null;
227
- const fullContent = fs3.readFileSync(usingSystematicPath, "utf8");
120
+ const fullContent = fs2.readFileSync(usingSystematicPath, "utf8");
228
121
  const { body: body2 } = parseFrontmatter(fullContent);
229
122
  const content = body2.trim();
230
123
  const skillUsage = usageTemplate ?? getSkillUsageTemplate();
231
- const catalog = renderCatalogVerbose({
232
- bundledSkillsDir,
233
- disabledSkills: config.disabled_skills
234
- });
235
- const catalogSection = catalog.length > 0 ? `
236
-
237
- ${catalog}` : "";
238
124
  const profileSection = profileBlock === undefined ? "" : `
239
125
 
240
126
  ${profileBlock}`;
@@ -245,222 +131,32 @@ You have access to structured engineering workflows via the Systematic plugin.
245
131
 
246
132
  ${content}
247
133
 
248
- ${skillUsage}${profileSection}${catalogSection}
134
+ ${skillUsage}${profileSection}
249
135
  </SYSTEMATIC_WORKFLOWS>`;
250
136
  }
251
137
 
252
138
  // src/lib/config-handler.ts
253
- import fs6 from "fs";
254
- import os3 from "os";
255
- import path6 from "path";
256
-
257
- // src/lib/agent-overlays.ts
258
- import fs4 from "fs";
139
+ import fs5 from "fs";
140
+ import os2 from "os";
259
141
  import path4 from "path";
260
142
 
261
- // src/lib/source-model-defaults.ts
262
- import * as path3 from "path";
263
- import { fileURLToPath } from "url";
264
- var __filename2 = fileURLToPath(import.meta.url);
265
- var __dirname2 = path3.dirname(__filename2);
266
- var packageRoot = path3.resolve(__dirname2, "..", "..");
267
- var bundledAgentsDir = path3.join(packageRoot, "agents");
268
- var ProviderID = exports_external.union([
269
- exports_external.literal("vercel"),
270
- exports_external.literal("opencode"),
271
- exports_external.literal("github-copilot"),
272
- exports_external.literal("opencode-go"),
273
- exports_external.literal("openai"),
274
- exports_external.literal("anthropic"),
275
- exports_external.literal("google")
276
- ]);
277
- var variantSchema = exports_external.string().min(1, "variant must be a non-empty string").max(128, "variant must be at most 128 characters").regex(/^\S+$/, "variant must not contain whitespace");
278
- var tableCellSchema = exports_external.string().min(1, "must be a non-empty string").regex(/^[^|\r\n]+$/, "must not contain pipe (|) or newline characters \u2014 these break Markdown table generation");
279
- var ModelEntrySchema = exports_external.object({
280
- model: exports_external.string().min(1, "model must be a non-empty string"),
281
- variant: variantSchema.optional()
282
- }).strict();
283
- var ProviderEntrySchema = exports_external.object({
284
- provider: ProviderID,
285
- models: exports_external.array(ModelEntrySchema).min(1, "models must be non-empty \u2014 every provider entry must list at least one model")
286
- }).strict().refine((entry) => {
287
- const seen = new Set;
288
- for (const m of entry.models) {
289
- const key = `${m.model}::${m.variant ?? ""}`;
290
- if (seen.has(key))
291
- return false;
292
- seen.add(key);
293
- }
294
- return true;
295
- }, {
296
- message: "duplicate (model, variant) pair within a provider entry \u2014 each model+variant combination must be unique"
297
- });
298
- var CategoryDefaultSchema = exports_external.object({
299
- rationale: tableCellSchema,
300
- whenToOverride: tableCellSchema.optional(),
301
- providers: exports_external.array(ProviderEntrySchema).min(1, "providers must be non-empty \u2014 every category must list at least one provider")
302
- }).strict().refine((cat) => {
303
- const seen = new Set;
304
- for (const p of cat.providers) {
305
- if (seen.has(p.provider))
306
- return false;
307
- seen.add(p.provider);
308
- }
309
- return true;
310
- }, {
311
- message: "duplicate provider ID within a category \u2014 each provider must appear at most once per category"
312
- });
313
- var SourceCategoryDefaultsSchema = exports_external.record(exports_external.string(), CategoryDefaultSchema);
314
- var SOURCE_CATEGORY_MODEL_DEFAULTS = {
315
- design: {
316
- rationale: "High-judgment UX, product, and design work benefits from a strong general reasoning model with broad creative capability.",
317
- whenToOverride: "Override to a faster/cheaper model when design tasks are primarily templating or low-stakes layout work.",
318
- providers: [
319
- {
320
- provider: "github-copilot",
321
- models: [{ model: "gemini-3.1-pro-preview" }]
322
- },
323
- {
324
- provider: "openai",
325
- models: [{ model: "gpt-5.5", variant: "high" }]
326
- },
327
- {
328
- provider: "anthropic",
329
- models: [{ model: "claude-opus-4-7", variant: "max" }]
330
- },
331
- {
332
- provider: "vercel",
333
- models: [{ model: "v0-1.5-md" }]
334
- }
335
- ]
336
- },
337
- "document-review": {
338
- rationale: "Requirements and plan critique benefit from the strongest nuanced reasoning to surface non-obvious gaps and contradictions.",
339
- providers: [
340
- {
341
- provider: "anthropic",
342
- models: [{ model: "claude-opus-4-7", variant: "max" }]
343
- },
344
- {
345
- provider: "openai",
346
- models: [{ model: "gpt-5.5", variant: "high" }]
347
- },
348
- {
349
- provider: "github-copilot",
350
- models: [{ model: "gemini-3.1-pro-preview" }]
351
- },
352
- {
353
- provider: "opencode",
354
- models: [{ model: "claude-sonnet-4-6" }]
355
- }
356
- ]
357
- },
358
- research: {
359
- rationale: "Tool-heavy synthesis and source evaluation benefit from a strong general reasoning model with broad knowledge.",
360
- providers: [
361
- {
362
- provider: "openai",
363
- models: [{ model: "gpt-5.4-mini" }]
364
- },
365
- {
366
- provider: "anthropic",
367
- models: [{ model: "claude-sonnet-4-6" }]
368
- },
369
- {
370
- provider: "github-copilot",
371
- models: [{ model: "gemini-3.1-pro-preview" }]
372
- },
373
- {
374
- provider: "opencode",
375
- models: [{ model: "claude-sonnet-4-6" }]
376
- }
377
- ]
378
- },
379
- review: {
380
- rationale: "Code, security, and adversarial review benefits from the strongest reasoning to catch subtle bugs and security issues.",
381
- whenToOverride: "Override to a faster model when review tasks are primarily style or formatting checks rather than correctness or security analysis.",
382
- providers: [
383
- {
384
- provider: "anthropic",
385
- models: [{ model: "claude-sonnet-4-6" }]
386
- },
387
- {
388
- provider: "openai",
389
- models: [{ model: "gpt-5.3-codex" }]
390
- },
391
- {
392
- provider: "github-copilot",
393
- models: [{ model: "gemini-3.1-pro-preview" }]
394
- },
395
- {
396
- provider: "opencode",
397
- models: [{ model: "claude-sonnet-4-6" }]
398
- }
399
- ]
400
- },
401
- workflow: {
402
- rationale: "Orchestration and bounded implementation tasks should default cheaper and faster; strong reasoning is rarely needed for routing decisions.",
403
- providers: [
404
- {
405
- provider: "openai",
406
- models: [{ model: "gpt-5.4-mini" }]
407
- },
408
- {
409
- provider: "anthropic",
410
- models: [{ model: "claude-sonnet-4-6" }]
411
- },
412
- {
413
- provider: "opencode",
414
- models: [{ model: "claude-haiku-4-5" }]
415
- },
416
- {
417
- provider: "opencode-go",
418
- models: [{ model: "claude-haiku-4-5" }]
419
- }
420
- ]
421
- }
422
- };
423
- function resolveSourceModel(category, availabilitySet) {
424
- const categoryDefault = SOURCE_CATEGORY_MODEL_DEFAULTS[category];
425
- if (!categoryDefault) {
426
- throw new Error(`resolveSourceModel: unknown category "${category}". Valid categories: ${Object.keys(SOURCE_CATEGORY_MODEL_DEFAULTS).join(", ")}`);
427
- }
428
- for (const providerEntry of categoryDefault.providers) {
429
- for (const modelEntry of providerEntry.models) {
430
- const key = `${providerEntry.provider}/${modelEntry.model}`;
431
- if (availabilitySet.has(key)) {
432
- return {
433
- provider: providerEntry.provider,
434
- model: modelEntry.model,
435
- variant: modelEntry.variant
436
- };
437
- }
438
- }
439
- }
440
- const firstProvider = categoryDefault.providers[0];
441
- const firstModel = firstProvider.models[0];
442
- return {
443
- provider: firstProvider.provider,
444
- model: firstModel.model,
445
- variant: firstModel.variant
446
- };
447
- }
448
-
449
143
  // src/lib/agent-overlays.ts
144
+ import fs3 from "fs";
145
+ import path2 from "path";
450
146
  function buildBundledAgentInventory(agentsDir, disabledAgents) {
451
147
  const categories = readCategoryDirs(agentsDir);
452
148
  const agentsByQualifiedId = {};
453
149
  const stemCategories = new Map;
454
150
  const disabledSet = new Set(disabledAgents);
455
151
  for (const category of categories) {
456
- for (const fileName of readMarkdownFiles(path4.join(agentsDir, category))) {
152
+ for (const fileName of readMarkdownFiles(path2.join(agentsDir, category))) {
457
153
  const key = fileName.replace(/\.md$/, "");
458
154
  const id = `${category}/${key}`;
459
155
  agentsByQualifiedId[id] = {
460
156
  id,
461
157
  key,
462
158
  category,
463
- file: path4.join(agentsDir, category, fileName),
159
+ file: path2.join(agentsDir, category, fileName),
464
160
  disabled: disabledSet.has(key) || disabledSet.has(id)
465
161
  };
466
162
  const existing = stemCategories.get(key) ?? [];
@@ -497,12 +193,6 @@ function resolveAgentOverlaySet(overlays) {
497
193
  categoriesByKey: new Map(overlays.categories.map((overlay) => [overlay.key, overlay]))
498
194
  };
499
195
  }
500
- function assertSourceCategoryModelCoverage(categories) {
501
- const missingCategories = categories.filter((category) => !Object.hasOwn(SOURCE_CATEGORY_MODEL_DEFAULTS, category));
502
- if (missingCategories.length > 0) {
503
- throw new Error(`Source category model defaults missing intentional coverage for: ${missingCategories.join(", ")}`);
504
- }
505
- }
506
196
  function validateExactAgentOverlays(inventory, overlays, nativeAgents, enabledSkills) {
507
197
  const result = [];
508
198
  const seenTargets = new Map;
@@ -585,14 +275,14 @@ function validateOverlaySkills(overlay, skills, enabledSkills) {
585
275
  }
586
276
  function readCategoryDirs(agentsDir) {
587
277
  try {
588
- return fs4.readdirSync(agentsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
278
+ return fs3.readdirSync(agentsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
589
279
  } catch {
590
280
  return [];
591
281
  }
592
282
  }
593
283
  function readMarkdownFiles(categoryDir) {
594
284
  try {
595
- return fs4.readdirSync(categoryDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
285
+ return fs3.readdirSync(categoryDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
596
286
  } catch {
597
287
  return [];
598
288
  }
@@ -604,169 +294,63 @@ function throwConfigError(sourcePath, keyPath, message) {
604
294
  throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath} ${message}`);
605
295
  }
606
296
 
607
- // src/lib/model-availability.ts
608
- import { createHash } from "crypto";
609
- import fs5 from "fs";
610
- import os2 from "os";
611
- import path5 from "path";
612
- function emptyAvailability() {
613
- return { status: "unknown", models: new Set };
614
- }
615
- var MAX_CACHE_FILE_BYTES = 16 * 1024 * 1024;
616
- var DEFAULT_API_TIMEOUT_MS = 1500;
617
- var MODELS_JSON_FILENAME = "models.json";
618
- var availabilityCache = new WeakMap;
619
- function resolveCacheDir() {
620
- const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
621
- const cacheBase = xdgCacheHome && path5.isAbsolute(xdgCacheHome) ? xdgCacheHome : path5.join(os2.homedir(), ".cache");
622
- return path5.join(cacheBase, "opencode");
623
- }
624
- function fastHash(input) {
625
- return createHash("sha1").update(input).digest("hex");
626
- }
627
- function isProviderRecord(value) {
628
- if (!isRecord(value))
629
- return false;
630
- for (const entry of Object.values(value)) {
631
- if (!isRecord(entry))
632
- return false;
633
- if (!isRecord(entry.models))
634
- return false;
297
+ // src/lib/skill-loader.ts
298
+ import fs4 from "fs";
299
+ import path3 from "path";
300
+ var SKILL_PREFIX = "systematic:";
301
+ var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
302
+ function formatSkillCommandName(name2) {
303
+ if (name2.includes(":")) {
304
+ return name2;
635
305
  }
636
- return true;
306
+ return `${SKILL_PREFIX}${name2}`;
637
307
  }
638
- function readModelsFromCache(filePath) {
639
- let fd;
640
- try {
641
- fd = fs5.openSync(filePath, "r");
642
- } catch {
643
- return null;
644
- }
645
- let raw;
646
- try {
647
- let stat;
648
- try {
649
- stat = fs5.fstatSync(fd);
650
- } catch {
651
- return null;
652
- }
653
- if (!stat.isFile())
654
- return null;
655
- if (stat.size === 0)
656
- return null;
657
- if (stat.size > MAX_CACHE_FILE_BYTES) {
658
- console.warn(`[systematic] models.json at ${filePath} is ${stat.size} bytes (>${MAX_CACHE_FILE_BYTES}); treating as cache miss.`);
659
- return null;
660
- }
661
- const buffer = Buffer.alloc(stat.size);
662
- let bytesRead;
663
- try {
664
- bytesRead = fs5.readSync(fd, buffer, 0, stat.size, 0);
665
- } catch {
666
- return null;
667
- }
668
- if (bytesRead !== stat.size)
669
- return null;
670
- raw = buffer.toString("utf8");
671
- } finally {
672
- try {
673
- fs5.closeSync(fd);
674
- } catch {}
675
- }
676
- if (raw.trim().length === 0)
677
- return null;
678
- let parsed;
679
- try {
680
- parsed = JSON.parse(raw);
681
- } catch {
682
- return null;
683
- }
684
- if (!isProviderRecord(parsed)) {
685
- console.warn(`[systematic] models.json schema mismatch at ${filePath}; treating as cache miss. Upstream shape may have drifted.`);
686
- return null;
687
- }
688
- const result = new Set;
689
- for (const [providerId, providerData] of Object.entries(parsed)) {
690
- for (const modelId of Object.keys(providerData.models)) {
691
- result.add(`${providerId}/${modelId}`);
692
- }
308
+ function formatSkillDescription(description, fallbackName) {
309
+ const desc = description || `${fallbackName} skill`;
310
+ if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
311
+ return desc;
693
312
  }
694
- return result;
313
+ return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
695
314
  }
696
- function readFallbackCache() {
697
- const cacheDir = resolveCacheDir();
698
- const openCodeModelsUrl = process.env.OPENCODE_MODELS_URL?.trim();
699
- if (openCodeModelsUrl) {
700
- const urlDerivedPath = path5.join(cacheDir, `models-${fastHash(openCodeModelsUrl)}.json`);
701
- const urlResult = readModelsFromCache(urlDerivedPath);
702
- if (urlResult !== null && urlResult.size > 0) {
703
- return { status: "cache", models: urlResult };
704
- }
705
- return emptyAvailability();
706
- }
707
- const defaultPath = path5.join(cacheDir, MODELS_JSON_FILENAME);
708
- const defaultResult = readModelsFromCache(defaultPath);
709
- if (defaultResult !== null && defaultResult.size > 0) {
710
- return { status: "cache", models: defaultResult };
711
- }
712
- return emptyAvailability();
315
+ function wrapSkillTemplate(skillPath, body2) {
316
+ const skillDir = path3.dirname(skillPath);
317
+ return `<skill-instruction>
318
+ Base directory for this skill: ${skillDir}/
319
+ File references (@path) in this skill are relative to this directory.
320
+
321
+ ${body2.trim()}
322
+ </skill-instruction>
323
+
324
+ <user-request>
325
+ $ARGUMENTS
326
+ </user-request>`;
713
327
  }
714
- function buildSetFromProviders(providers) {
715
- const result = new Set;
716
- for (const provider of providers) {
717
- for (const modelId of Object.keys(provider.models)) {
718
- result.add(`${provider.id}/${modelId}`);
719
- }
720
- }
721
- return result;
328
+ function extractSkillBody(wrappedTemplate) {
329
+ const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
330
+ return match ? match[1].trim() : wrappedTemplate;
722
331
  }
723
- async function getAvailableModels(client, options = {}) {
724
- const cached = availabilityCache.get(client);
725
- if (cached !== undefined) {
726
- return cached;
727
- }
728
- const cacheAndReturn = (envelope) => {
729
- if (envelope.status !== "unknown") {
730
- availabilityCache.set(client, envelope);
731
- }
732
- return envelope;
733
- };
734
- const timeoutMs = options.apiTimeoutMs === undefined ? DEFAULT_API_TIMEOUT_MS : options.apiTimeoutMs;
735
- if (typeof client.config?.providers !== "function") {
736
- return cacheAndReturn(readFallbackCache());
737
- }
738
- const apiCall = client.config.providers();
739
- let response;
332
+ function loadSkill(skillInfo) {
740
333
  try {
741
- if (timeoutMs === null) {
742
- response = await apiCall;
743
- } else {
744
- const TIMEOUT_SENTINEL = Symbol("timeout");
745
- const timeoutPromise = new Promise((resolve2) => {
746
- const timer = setTimeout(() => resolve2(TIMEOUT_SENTINEL), timeoutMs);
747
- timer.unref?.();
748
- });
749
- const raced = await Promise.race([apiCall, timeoutPromise]);
750
- if (raced === TIMEOUT_SENTINEL) {
751
- console.warn(`[systematic] client.config.providers() exceeded ${timeoutMs}ms; falling back to models.json cache.`);
752
- return cacheAndReturn(readFallbackCache());
753
- }
754
- response = raced;
755
- }
334
+ const content = fs4.readFileSync(skillInfo.skillFile, "utf8");
335
+ const { body: body2 } = parseFrontmatter(content);
336
+ const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body2);
337
+ return {
338
+ name: skillInfo.name,
339
+ prefixedName: formatSkillCommandName(skillInfo.name),
340
+ description: formatSkillDescription(skillInfo.description, skillInfo.name),
341
+ path: skillInfo.path,
342
+ skillFile: skillInfo.skillFile,
343
+ wrappedTemplate,
344
+ disableModelInvocation: skillInfo.disableModelInvocation,
345
+ userInvocable: skillInfo.userInvocable,
346
+ subtask: skillInfo.subtask,
347
+ agent: skillInfo.agent,
348
+ model: skillInfo.model,
349
+ argumentHint: skillInfo.argumentHint
350
+ };
756
351
  } catch {
757
- return cacheAndReturn(readFallbackCache());
758
- }
759
- if (response.error !== undefined || response.data === undefined) {
760
- return cacheAndReturn(readFallbackCache());
761
- }
762
- const models = buildSetFromProviders(response.data.providers);
763
- if (models.size === 0) {
764
- return cacheAndReturn(emptyAvailability());
352
+ return null;
765
353
  }
766
- return cacheAndReturn({
767
- status: "api",
768
- models
769
- });
770
354
  }
771
355
 
772
356
  // src/lib/config-handler.ts
@@ -805,7 +389,7 @@ function formatAgentDescription(name2, description) {
805
389
  }
806
390
  function loadAgentAsConfig(agentInfo) {
807
391
  try {
808
- const content = fs6.readFileSync(agentInfo.file, "utf8");
392
+ const content = fs5.readFileSync(agentInfo.file, "utf8");
809
393
  const {
810
394
  description,
811
395
  prompt,
@@ -854,7 +438,7 @@ function loadAgentAsConfig(agentInfo) {
854
438
  }
855
439
  function loadCommandAsConfig(commandInfo) {
856
440
  try {
857
- const content = fs6.readFileSync(commandInfo.file, "utf8");
441
+ const content = fs5.readFileSync(commandInfo.file, "utf8");
858
442
  const { name: name2, description, agent, model, subtask } = extractCommandFrontmatter(content);
859
443
  const { body: body2 } = parseFrontmatter(content);
860
444
  const cleanName = commandInfo.name.replace(/^\//, "");
@@ -894,7 +478,7 @@ function loadSkillAsCommand(loaded) {
894
478
  config.subtask = loaded.subtask;
895
479
  return config;
896
480
  }
897
- function collectAgents(dir, disabledAgents, nativeAgents, overlays, availabilitySet) {
481
+ function collectAgents(dir, disabledAgents, nativeAgents, overlays) {
898
482
  const agents = {};
899
483
  const agentList = findAgentsInDir(dir);
900
484
  const disabledSet = new Set(disabledAgents);
@@ -909,12 +493,12 @@ function collectAgents(dir, disabledAgents, nativeAgents, overlays, availability
909
493
  continue;
910
494
  const config = loadAgentAsConfig(agentInfo);
911
495
  if (config) {
912
- agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays, availabilitySet);
496
+ agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays);
913
497
  }
914
498
  }
915
499
  return agents;
916
500
  }
917
- function applyAgentOverlays(config, agentInfo, overlays, availabilitySet) {
501
+ function applyAgentOverlays(config, agentInfo, overlays) {
918
502
  const id = agentInfo.category ? `${agentInfo.category}/${agentInfo.name}` : agentInfo.name;
919
503
  const categoryOverlay = agentInfo.category ? overlays.categoriesByKey.get(agentInfo.category) : undefined;
920
504
  const exactOverlay = overlays.agentsByTargetId.get(id);
@@ -924,23 +508,11 @@ function applyAgentOverlays(config, agentInfo, overlays, availabilitySet) {
924
508
  if (hasPermissionOverlay && isRecord(config.permission)) {
925
509
  addPermissionRules(permissionRules, config.permission);
926
510
  }
927
- applySourceModelDefault(result, agentInfo, availabilitySet);
928
511
  applyAgentOverlay(result, categoryOverlay?.value, permissionRules);
929
512
  applyAgentOverlay(result, exactOverlay?.value, permissionRules);
930
513
  applyPermissionOverlay(result, permissionRules, hasPermissionOverlay);
931
514
  return result;
932
515
  }
933
- function applySourceModelDefault(target, agentInfo, availabilitySet) {
934
- if (!agentInfo.category || availabilitySet === undefined)
935
- return;
936
- const resolved = resolveSourceModel(agentInfo.category, availabilitySet);
937
- target.model = `${resolved.provider}/${resolved.model}`;
938
- if (resolved.variant !== undefined) {
939
- target.variant = resolved.variant;
940
- } else {
941
- delete target.variant;
942
- }
943
- }
944
516
  function applyAgentOverlay(target, overlay, permissionRules) {
945
517
  if (overlay === undefined)
946
518
  return;
@@ -1113,9 +685,9 @@ function collectEnabledSkillNames(dir, disabledSkills) {
1113
685
  return findSkillsInDir(dir).filter((skillInfo) => !disabledSet.has(skillInfo.name)).map((skillInfo) => skillInfo.name);
1114
686
  }
1115
687
  function createConfigHandler(deps) {
1116
- const { directory, bundledSkillsDir, bundledAgentsDir: bundledAgentsDir2, bundledCommandsDir } = deps;
1117
- const homeDir = deps.homeDir ?? os3.homedir();
1118
- const opencodeConfigDir = deps.opencodeConfigDir ?? path6.join(homeDir, ".config/opencode");
688
+ const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
689
+ const homeDir = deps.homeDir ?? os2.homedir();
690
+ const opencodeConfigDir = deps.opencodeConfigDir ?? path4.join(homeDir, ".config/opencode");
1119
691
  const opencodeConfigDirOverride = process.env.OPENCODE_CONFIG_DIR?.trim() ? process.env.OPENCODE_CONFIG_DIR : undefined;
1120
692
  return async (config) => {
1121
693
  const { config: systematicConfig, overlays } = loadConfigWithSources(directory);
@@ -1124,10 +696,7 @@ function createConfigHandler(deps) {
1124
696
  const nativeAgents = Object.fromEntries(Object.entries(existingAgents).filter(([, agent]) => !isSystematicAgentConfig(agent)));
1125
697
  const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
1126
698
  const enabledSkillNames = collectEnabledSkillNames(bundledSkillsDir, systematicConfig.disabled_skills);
1127
- const inventory = buildBundledAgentInventory(bundledAgentsDir2, systematicConfig.disabled_agents);
1128
- const availability = deps.client ? await getAvailableModels(deps.client) : undefined;
1129
- const availabilitySet = availability && availability.status !== "unknown" ? availability.models : undefined;
1130
- assertSourceCategoryModelCoverage(inventory.categories);
699
+ const inventory = buildBundledAgentInventory(bundledAgentsDir, systematicConfig.disabled_agents);
1131
700
  const validatedOverlays = validateAgentOverlays({
1132
701
  inventory,
1133
702
  overlays,
@@ -1135,7 +704,7 @@ function createConfigHandler(deps) {
1135
704
  enabledSkills: enabledSkillNames
1136
705
  });
1137
706
  const resolvedOverlays = resolveAgentOverlaySet(validatedOverlays);
1138
- const bundledAgents = collectAgents(bundledAgentsDir2, systematicConfig.disabled_agents, nativeAgents, resolvedOverlays, availabilitySet);
707
+ const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents, nativeAgents, resolvedOverlays);
1139
708
  const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
1140
709
  const discoveredSkillCommands = systematicConfig.skills_as_commands !== false ? collectDiscoveredSkillsAsCommands(directory, homeDir, opencodeConfigDir, opencodeConfigDirOverride, systematicConfig.disabled_commands) : {};
1141
710
  const bundledAgentKeys = new Set(Object.keys(bundledAgents));
@@ -1161,21 +730,21 @@ function registerSkillsPaths(config, skillsDir) {
1161
730
  };
1162
731
  }
1163
732
  function removeSystematicSkillPaths(paths) {
1164
- return paths.filter((path7) => !isSystematicSkillPath(path7));
733
+ return paths.filter((path5) => !isSystematicSkillPath(path5));
1165
734
  }
1166
- function isSystematicSkillPath(path7) {
1167
- const normalizedPath = normalizePath(path7);
735
+ function isSystematicSkillPath(path5) {
736
+ const normalizedPath = normalizePath(path5);
1168
737
  return normalizedPath.endsWith("/.config/opencode/systematic/skills") || normalizedPath.endsWith("/.cache/opencode/systematic/skills") || normalizedPath.endsWith("/.local/share/opencode/systematic/skills") || normalizedPath.endsWith("/.opencode/systematic/skills") || /(?:^|\/)\.cache\/opencode\/packages\/@fro\.bot\/systematic@[^/]+\/node_modules\/@fro\.bot\/systematic\/skills(?:$|\/)/u.test(normalizedPath);
1169
738
  }
1170
- function normalizePath(path7) {
1171
- return path7.replaceAll("\\", "/").replace(/\/+$/u, "");
739
+ function normalizePath(path5) {
740
+ return path5.replaceAll("\\", "/").replace(/\/+$/u, "");
1172
741
  }
1173
742
 
1174
743
  // src/lib/opencode-operation-observer.ts
1175
744
  import { spawnSync } from "child_process";
1176
- import { createHash as createHash2 } from "crypto";
1177
- import fs7 from "fs";
1178
- import path7 from "path";
745
+ import { createHash } from "crypto";
746
+ import fs6 from "fs";
747
+ import path5 from "path";
1179
748
  var DEFAULT_LIMITS = {
1180
749
  maxCommandOutputBytes: 1048576,
1181
750
  maxCommandTimeoutMs: 5000,
@@ -1196,7 +765,7 @@ function unavailable(result) {
1196
765
  return { status: "unavailable", reasonCode: result.reasonCode };
1197
766
  }
1198
767
  function digest(domain, facts) {
1199
- const hash = createHash2("sha256");
768
+ const hash = createHash("sha256");
1200
769
  hash.update(`systematic:opencode-observer:${domain}:v1\x00`);
1201
770
  for (const fact of facts) {
1202
771
  hash.update(fact);
@@ -1287,11 +856,11 @@ function parseStageEntries(output, maxPathBytes) {
1287
856
  return entries.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
1288
857
  }
1289
858
  function safePath(root, relativePath) {
1290
- if (path7.isAbsolute(relativePath))
859
+ if (path5.isAbsolute(relativePath))
1291
860
  return;
1292
- const absolutePath = path7.resolve(root, relativePath);
1293
- const relative = path7.relative(root, absolutePath);
1294
- if (relative === "" || relative === ".." || relative.startsWith(`..${path7.sep}`)) {
861
+ const absolutePath = path5.resolve(root, relativePath);
862
+ const relative = path5.relative(root, absolutePath);
863
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path5.sep}`)) {
1295
864
  return;
1296
865
  }
1297
866
  return absolutePath;
@@ -1314,25 +883,25 @@ function runCommand(runner, args2, cwd, limits) {
1314
883
  }
1315
884
  return { status: "ok", output: { stdout: result.stdout } };
1316
885
  }
1317
- function canonicalPath(filePath, realPath = fs7.realpathSync) {
886
+ function canonicalPath(filePath, realPath = fs6.realpathSync) {
1318
887
  try {
1319
888
  return realPath(filePath);
1320
889
  } catch {
1321
890
  return;
1322
891
  }
1323
892
  }
1324
- function requiredGitPath(runner, args2, cwd, limits, realPath = fs7.realpathSync) {
893
+ function requiredGitPath(runner, args2, cwd, limits, realPath = fs6.realpathSync) {
1325
894
  const result = runCommand(runner, args2, cwd, limits);
1326
895
  if (result.status === "error")
1327
896
  return result;
1328
897
  const rawValue = result.output.stdout.trim();
1329
- if (!path7.isAbsolute(rawValue)) {
898
+ if (!path5.isAbsolute(rawValue)) {
1330
899
  return { status: "error", reasonCode: "target-unavailable" };
1331
900
  }
1332
901
  const value = canonicalPath(rawValue, realPath);
1333
902
  return value === undefined ? { status: "error", reasonCode: "target-unavailable" } : { status: "ok", value };
1334
903
  }
1335
- function captureParentIdentity(targetDirectory, runner, limits, realPath = fs7.realpathSync) {
904
+ function captureParentIdentity(targetDirectory, runner, limits, realPath = fs6.realpathSync) {
1336
905
  const targetRoot = requiredGitPath(runner, ["rev-parse", "--show-toplevel"], targetDirectory, limits, realPath);
1337
906
  if (targetRoot.status === "error")
1338
907
  return targetRoot;
@@ -1360,53 +929,53 @@ function captureParentIdentity(targetDirectory, runner, limits, realPath = fs7.r
1360
929
  }
1361
930
  } : { status: "error", reasonCode: "target-unavailable" };
1362
931
  }
1363
- function readGitfileTarget(gitfilePath, realPath = fs7.realpathSync) {
932
+ function readGitfileTarget(gitfilePath, realPath = fs6.realpathSync) {
1364
933
  let contents;
1365
934
  try {
1366
- contents = fs7.readFileSync(gitfilePath, "utf8");
935
+ contents = fs6.readFileSync(gitfilePath, "utf8");
1367
936
  } catch {
1368
937
  return;
1369
938
  }
1370
939
  const match = /^gitdir:\s*(.+?)\s*$/im.exec(contents);
1371
940
  if (!match)
1372
941
  return;
1373
- const target = path7.isAbsolute(match[1]) ? match[1] : path7.resolve(path7.dirname(gitfilePath), match[1]);
942
+ const target = path5.isAbsolute(match[1]) ? match[1] : path5.resolve(path5.dirname(gitfilePath), match[1]);
1374
943
  return canonicalPath(target, realPath);
1375
944
  }
1376
945
  function readGitdirBacklink(backlinkPath, realPath) {
1377
946
  let contents;
1378
947
  try {
1379
- contents = fs7.readFileSync(backlinkPath, "utf8").trim();
948
+ contents = fs6.readFileSync(backlinkPath, "utf8").trim();
1380
949
  } catch {
1381
950
  return;
1382
951
  }
1383
952
  if (contents.length === 0)
1384
953
  return;
1385
- const target = path7.isAbsolute(contents) ? contents : path7.resolve(path7.dirname(backlinkPath), contents);
954
+ const target = path5.isAbsolute(contents) ? contents : path5.resolve(path5.dirname(backlinkPath), contents);
1386
955
  return canonicalPath(target, realPath);
1387
956
  }
1388
957
  function isPathWithin(root, candidate) {
1389
- const relative = path7.relative(root, candidate);
1390
- return relative !== "" && relative !== ".." && !relative.startsWith(`..${path7.sep}`) && !path7.isAbsolute(relative);
958
+ const relative = path5.relative(root, candidate);
959
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path5.sep}`) && !path5.isAbsolute(relative);
1391
960
  }
1392
961
  function validateDotGitLinkage(targetRoot, gitDir, commonDir, realPath) {
1393
- const dotGit = path7.join(targetRoot, ".git");
962
+ const dotGit = path5.join(targetRoot, ".git");
1394
963
  let dotGitStat;
1395
964
  try {
1396
- dotGitStat = fs7.lstatSync(dotGit);
965
+ dotGitStat = fs6.lstatSync(dotGit);
1397
966
  } catch {
1398
967
  return false;
1399
968
  }
1400
969
  if (dotGitStat.isDirectory()) {
1401
970
  return gitDir === commonDir && canonicalPath(dotGit, realPath) === commonDir;
1402
971
  }
1403
- if (!dotGitStat.isFile() || dotGitStat.isSymbolicLink() || !isPathWithin(path7.join(commonDir, "worktrees"), gitDir) || readGitfileTarget(dotGit, realPath) !== gitDir) {
972
+ if (!dotGitStat.isFile() || dotGitStat.isSymbolicLink() || !isPathWithin(path5.join(commonDir, "worktrees"), gitDir) || readGitfileTarget(dotGit, realPath) !== gitDir) {
1404
973
  return false;
1405
974
  }
1406
- const backlinkPath = path7.join(gitDir, "gitdir");
975
+ const backlinkPath = path5.join(gitDir, "gitdir");
1407
976
  let backlinkStat;
1408
977
  try {
1409
- backlinkStat = fs7.lstatSync(backlinkPath);
978
+ backlinkStat = fs6.lstatSync(backlinkPath);
1410
979
  } catch {
1411
980
  return false;
1412
981
  }
@@ -1415,7 +984,7 @@ function validateDotGitLinkage(targetRoot, gitDir, commonDir, realPath) {
1415
984
  function validateRegisteredWorktree(candidateDirectory, parentIdentity, options = {}) {
1416
985
  const runner = options.commandRunner ?? defaultCommandRunner;
1417
986
  const limits = mergeLimits(options.limits);
1418
- const realPath = options.realPath ?? fs7.realpathSync;
987
+ const realPath = options.realPath ?? fs6.realpathSync;
1419
988
  const candidateRoot = canonicalPath(candidateDirectory, realPath);
1420
989
  if (candidateRoot === undefined) {
1421
990
  return { status: "error", reasonCode: "target-unavailable" };
@@ -1582,7 +1151,7 @@ function readWorktreeRevisionV2(runner, fileReader, symlinkReader, statReader, c
1582
1151
  if (paths.length > limits.maxFiles) {
1583
1152
  return { status: "error", reasonCode: "file-limit" };
1584
1153
  }
1585
- const hash = createHash2("sha256");
1154
+ const hash = createHash("sha256");
1586
1155
  hash.update("systematic:opencode-observer:worktree:v2\x00");
1587
1156
  let totalBytes = 0;
1588
1157
  for (const relativePath of paths) {
@@ -1800,19 +1369,19 @@ function createOpencodeOperationObserver(options) {
1800
1369
  const limits = mergeLimits(options.limits);
1801
1370
  let targetDirectory;
1802
1371
  try {
1803
- targetDirectory = (options.realPath ?? fs7.realpathSync)(options.targetDirectory);
1372
+ targetDirectory = (options.realPath ?? fs6.realpathSync)(options.targetDirectory);
1804
1373
  } catch {
1805
- targetDirectory = path7.resolve(options.targetDirectory);
1374
+ targetDirectory = path5.resolve(options.targetDirectory);
1806
1375
  }
1807
1376
  const runner = options.commandRunner ?? defaultCommandRunner;
1808
1377
  const remoteRunner = options.remoteCommandRunner ?? defaultRemoteCommandRunner;
1809
- const realPath = options.realPath ?? fs7.realpathSync;
1378
+ const realPath = options.realPath ?? fs6.realpathSync;
1810
1379
  const parentIdentityResult = captureParentIdentity(targetDirectory, runner, limits, realPath);
1811
1380
  const targetDigest = digest("target", [targetDirectory]);
1812
- const fileReader = options.fileReader ?? fs7.readFileSync;
1813
- const symlinkReader = options.symlinkReader ?? fs7.readlinkSync;
1381
+ const fileReader = options.fileReader ?? fs6.readFileSync;
1382
+ const symlinkReader = options.symlinkReader ?? fs6.readlinkSync;
1814
1383
  const statReader = options.statReader ?? ((filePath) => {
1815
- const stat = fs7.lstatSync(filePath);
1384
+ const stat = fs6.lstatSync(filePath);
1816
1385
  return {
1817
1386
  isFile: stat.isFile(),
1818
1387
  isSymbolicLink: stat.isSymbolicLink(),
@@ -1875,11 +1444,11 @@ function createOpencodeOperationObserver(options) {
1875
1444
 
1876
1445
  // src/lib/opencode-workflow-guard.ts
1877
1446
  import { randomBytes as randomBytes4 } from "crypto";
1878
- import fs9 from "fs";
1879
- import path8 from "path";
1447
+ import fs8 from "fs";
1448
+ import path6 from "path";
1880
1449
 
1881
1450
  // src/lib/question-attestation.ts
1882
- import { createHash as createHash3, randomBytes } from "crypto";
1451
+ import { createHash as createHash2, randomBytes } from "crypto";
1883
1452
  var CANONICAL_QUESTION_WORDING = "Confirm the requested guarded transition.";
1884
1453
  var MAX_ID_LENGTH = 128;
1885
1454
  var MAX_RESOURCE_LENGTH = 512;
@@ -1910,7 +1479,7 @@ function isQuestionPurpose(value) {
1910
1479
  return value === "transition" || value === "session-disablement";
1911
1480
  }
1912
1481
  function digestResource(resource) {
1913
- return `sha256:${createHash3("sha256").update(`systematic-question-attestation:v1:resource:${resource}`, "utf8").digest("hex")}`;
1482
+ return `sha256:${createHash2("sha256").update(`systematic-question-attestation:v1:resource:${resource}`, "utf8").digest("hex")}`;
1914
1483
  }
1915
1484
  function bindingKey(input, resourceDigest) {
1916
1485
  return [
@@ -2396,7 +1965,7 @@ function createQuestionAttestation(options = {}) {
2396
1965
  import { randomBytes as randomBytes2 } from "crypto";
2397
1966
 
2398
1967
  // src/lib/receipt-readback.ts
2399
- import { createHash as createHash4 } from "crypto";
1968
+ import { createHash as createHash3 } from "crypto";
2400
1969
  var RECEIPT_READBACK_SCHEMA_VERSION = 2;
2401
1970
  var RECEIPT_READBACK_PROTOCOL_VERSION = 2;
2402
1971
  var LEGACY_RECEIPT_READBACK_SCHEMA_VERSION = 1;
@@ -2567,7 +2136,7 @@ function sessionSaltHex(value) {
2567
2136
  }
2568
2137
  function digestReceiptIdentity(domain, identity, sessionSalt) {
2569
2138
  const salt = sessionSaltHex(sessionSalt) ?? "invalid-salt";
2570
- return createHash4("sha256").update(`systematic/receipt-ledger/${domain}/v1/${salt}/${identity}`).digest("hex");
2139
+ return createHash3("sha256").update(`systematic/receipt-ledger/${domain}/v1/${salt}/${identity}`).digest("hex");
2571
2140
  }
2572
2141
  function decodeSessionSalt(value) {
2573
2142
  return Uint8Array.from(Buffer.from(value, "hex"));
@@ -2730,7 +2299,7 @@ function serializeEnvelope(envelope) {
2730
2299
  });
2731
2300
  }
2732
2301
  function hashIntegrity(scope, value) {
2733
- return createHash4("sha256").update(`systematic/receipt-readback/${scope}/v1/${value}`).digest("hex");
2302
+ return createHash3("sha256").update(`systematic/receipt-readback/${scope}/v1/${value}`).digest("hex");
2734
2303
  }
2735
2304
  function mintIntegrity(envelope, sessionSalt) {
2736
2305
  return hashIntegrity("mint", `${sessionSalt}/${serializeEnvelope(envelope)}`);
@@ -4416,8 +3985,8 @@ function normalizeCapabilities(input) {
4416
3985
  import { randomBytes as randomBytes3 } from "crypto";
4417
3986
 
4418
3987
  // src/lib/receipt-classifier.ts
4419
- import fs8 from "fs";
4420
- import { fileURLToPath as fileURLToPath2 } from "url";
3988
+ import fs7 from "fs";
3989
+ import { fileURLToPath } from "url";
4421
3990
 
4422
3991
  // node_modules/.bun/web-tree-sitter@0.25.10/node_modules/web-tree-sitter/tree-sitter.js
4423
3992
  var __defProp = Object.defineProperty;
@@ -5853,8 +5422,8 @@ var Module2 = (() => {
5853
5422
  var moduleRtn;
5854
5423
  var Module = moduleArg;
5855
5424
  var readyPromiseResolve, readyPromiseReject;
5856
- var readyPromise = new Promise((resolve2, reject) => {
5857
- readyPromiseResolve = resolve2;
5425
+ var readyPromise = new Promise((resolve, reject) => {
5426
+ readyPromiseResolve = resolve;
5858
5427
  readyPromiseReject = reject;
5859
5428
  });
5860
5429
  var ENVIRONMENT_IS_WEB = typeof window == "object";
@@ -5876,11 +5445,11 @@ var Module2 = (() => {
5876
5445
  throw toThrow;
5877
5446
  }, "quit_");
5878
5447
  var scriptDirectory = "";
5879
- function locateFile(path8) {
5448
+ function locateFile(path6) {
5880
5449
  if (Module["locateFile"]) {
5881
- return Module["locateFile"](path8, scriptDirectory);
5450
+ return Module["locateFile"](path6, scriptDirectory);
5882
5451
  }
5883
- return scriptDirectory + path8;
5452
+ return scriptDirectory + path6;
5884
5453
  }
5885
5454
  __name(locateFile, "locateFile");
5886
5455
  var readAsync, readBinary;
@@ -5934,13 +5503,13 @@ var Module2 = (() => {
5934
5503
  }
5935
5504
  readAsync = /* @__PURE__ */ __name(async (url) => {
5936
5505
  if (isFileURI(url)) {
5937
- return new Promise((resolve2, reject) => {
5506
+ return new Promise((resolve, reject) => {
5938
5507
  var xhr = new XMLHttpRequest;
5939
5508
  xhr.open("GET", url, true);
5940
5509
  xhr.responseType = "arraybuffer";
5941
5510
  xhr.onload = () => {
5942
5511
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
5943
- resolve2(xhr.response);
5512
+ resolve(xhr.response);
5944
5513
  return;
5945
5514
  }
5946
5515
  reject(xhr.status);
@@ -6160,10 +5729,10 @@ var Module2 = (() => {
6160
5729
  __name(receiveInstantiationResult, "receiveInstantiationResult");
6161
5730
  var info2 = getWasmImports();
6162
5731
  if (Module["instantiateWasm"]) {
6163
- return new Promise((resolve2, reject) => {
5732
+ return new Promise((resolve, reject) => {
6164
5733
  Module["instantiateWasm"](info2, (mod, inst) => {
6165
5734
  receiveInstance(mod, inst);
6166
- resolve2(mod.exports);
5735
+ resolve(mod.exports);
6167
5736
  });
6168
5737
  });
6169
5738
  }
@@ -7954,7 +7523,7 @@ function matchesToolIdentity(tool, executable) {
7954
7523
  async function initializeParserAssets(options) {
7955
7524
  const treeSitterWasmPath = options.treeSitterWasmPath ?? resolveAsset("web-tree-sitter/tree-sitter.wasm");
7956
7525
  const bashWasmPath = options.bashWasmPath ?? resolveAsset("tree-sitter-bash/tree-sitter-bash.wasm");
7957
- if (!fs8.existsSync(treeSitterWasmPath) || !fs8.existsSync(bashWasmPath)) {
7526
+ if (!fs7.existsSync(treeSitterWasmPath) || !fs7.existsSync(bashWasmPath)) {
7958
7527
  throw new Error("parser-asset-unavailable");
7959
7528
  }
7960
7529
  try {
@@ -7975,7 +7544,7 @@ async function initializeParserAssets(options) {
7975
7544
  }
7976
7545
  function resolveAsset(specifier) {
7977
7546
  const resolved = import.meta.resolve(specifier);
7978
- return resolved.startsWith("file://") ? fileURLToPath2(resolved) : resolved;
7547
+ return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
7979
7548
  }
7980
7549
  function classifyParserFailure(error) {
7981
7550
  if (error instanceof Error && error.message === "grammar-incompatible") {
@@ -10832,16 +10401,16 @@ function canonicalFileTarget(filePath, realPath) {
10832
10401
  const existing = canonicalExistingPath(filePath, realPath);
10833
10402
  if (existing)
10834
10403
  return existing;
10835
- const parent = canonicalExistingPath(path8.dirname(filePath), realPath);
10836
- const basename = path8.basename(filePath);
10837
- return parent && basename && basename !== "." && basename !== ".." ? path8.join(parent, basename) : undefined;
10404
+ const parent = canonicalExistingPath(path6.dirname(filePath), realPath);
10405
+ const basename = path6.basename(filePath);
10406
+ return parent && basename && basename !== "." && basename !== ".." ? path6.join(parent, basename) : undefined;
10838
10407
  }
10839
10408
  function pathWithinOrEqual(root, candidate) {
10840
- const relative = path8.relative(root, candidate);
10841
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path8.sep}`) && !path8.isAbsolute(relative);
10409
+ const relative = path6.relative(root, candidate);
10410
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
10842
10411
  }
10843
10412
  function targetIsGitAdminStorage(targetPath, validation) {
10844
- return pathWithinOrEqual(path8.join(validation.targetRoot, ".git"), targetPath) || pathWithinOrEqual(validation.gitDir, targetPath) || pathWithinOrEqual(validation.commonDir, targetPath);
10413
+ return pathWithinOrEqual(path6.join(validation.targetRoot, ".git"), targetPath) || pathWithinOrEqual(validation.gitDir, targetPath) || pathWithinOrEqual(validation.commonDir, targetPath);
10845
10414
  }
10846
10415
  function targetResultFromValidation(candidatePath, validation) {
10847
10416
  if (validation.status === "error")
@@ -10857,7 +10426,7 @@ function targetResultFromValidation(candidatePath, validation) {
10857
10426
  function trustedParentTarget(parentTargetRoot, candidatePath) {
10858
10427
  if (!pathWithinOrEqual(parentTargetRoot, candidatePath))
10859
10428
  return;
10860
- if (pathWithinOrEqual(path8.join(parentTargetRoot, ".git"), candidatePath)) {
10429
+ if (pathWithinOrEqual(path6.join(parentTargetRoot, ".git"), candidatePath)) {
10861
10430
  return unavailableOperationTarget();
10862
10431
  }
10863
10432
  return { status: "available", targetRoot: parentTargetRoot };
@@ -10870,14 +10439,14 @@ function validationForCandidate(candidatePath, options, targetPath = candidatePa
10870
10439
  }
10871
10440
  }
10872
10441
  function deriveFileTarget(rawPath, baseDirectory, parentTargetRoot, options, realPath, allowParentFastPath = true) {
10873
- const resolvedPath = path8.resolve(baseDirectory, rawPath);
10442
+ const resolvedPath = path6.resolve(baseDirectory, rawPath);
10874
10443
  const canonicalPath2 = canonicalFileTarget(resolvedPath, realPath);
10875
10444
  if (!canonicalPath2)
10876
10445
  return unavailableOperationTarget();
10877
- const parentResult = allowParentFastPath && !path8.isAbsolute(rawPath) ? trustedParentTarget(parentTargetRoot, canonicalPath2) : undefined;
10446
+ const parentResult = allowParentFastPath && !path6.isAbsolute(rawPath) ? trustedParentTarget(parentTargetRoot, canonicalPath2) : undefined;
10878
10447
  if (parentResult)
10879
10448
  return parentResult;
10880
- return validationForCandidate(path8.dirname(canonicalPath2), options, canonicalPath2);
10449
+ return validationForCandidate(path6.dirname(canonicalPath2), options, canonicalPath2);
10881
10450
  }
10882
10451
  function sharedTargetRoot(targets, extraRoots = []) {
10883
10452
  const roots = [...extraRoots];
@@ -10890,10 +10459,10 @@ function sharedTargetRoot(targets, extraRoots = []) {
10890
10459
  return targetRoot && roots.every((root) => root === targetRoot) ? targetRoot : undefined;
10891
10460
  }
10892
10461
  function deriveDirectoryTarget(rawPath, baseDirectory, parentTargetRoot, options, realPath) {
10893
- const resolvedPath = canonicalExistingPath(path8.resolve(baseDirectory, rawPath), realPath);
10462
+ const resolvedPath = canonicalExistingPath(path6.resolve(baseDirectory, rawPath), realPath);
10894
10463
  if (!resolvedPath)
10895
10464
  return { status: "unavailable" };
10896
- const parentResult = path8.isAbsolute(rawPath) ? undefined : trustedParentTarget(parentTargetRoot, resolvedPath);
10465
+ const parentResult = path6.isAbsolute(rawPath) ? undefined : trustedParentTarget(parentTargetRoot, resolvedPath);
10897
10466
  if (parentResult?.status === "unavailable") {
10898
10467
  return { status: "unavailable" };
10899
10468
  }
@@ -11001,14 +10570,14 @@ function deriveBashTarget(args2, sessionLocation, parentTargetRoot, options, rea
11001
10570
  if (typeof args2.workdir !== "string" || args2.workdir.length === 0) {
11002
10571
  return unavailableOperationTarget();
11003
10572
  }
11004
- const candidatePath = canonicalExistingPath(path8.resolve(sessionLocation, args2.workdir), realPath);
10573
+ const candidatePath = canonicalExistingPath(path6.resolve(sessionLocation, args2.workdir), realPath);
11005
10574
  if (!candidatePath)
11006
10575
  return unavailableOperationTarget();
11007
- const parentResult = path8.isAbsolute(args2.workdir) ? undefined : trustedParentTarget(parentTargetRoot, candidatePath);
10576
+ const parentResult = path6.isAbsolute(args2.workdir) ? undefined : trustedParentTarget(parentTargetRoot, candidatePath);
11008
10577
  return parentResult ?? validationForCandidate(candidatePath, options);
11009
10578
  }
11010
10579
  function deriveOpencodeOperationTarget(tool, args2, options) {
11011
- const realPath = options.realPath ?? fs9.realpathSync;
10580
+ const realPath = options.realPath ?? fs8.realpathSync;
11012
10581
  const parentTargetRoot = canonicalExistingPath(options.parentTargetRoot, realPath);
11013
10582
  if (!parentTargetRoot)
11014
10583
  return unavailableOperationTarget();
@@ -11085,7 +10654,7 @@ function canonicalPatchText(patchText, sessionLocation) {
11085
10654
  const filePath = line.slice(prefix.length).trim();
11086
10655
  if (!filePath)
11087
10656
  return;
11088
- segments[index] = `${prefix}${path8.resolve(sessionLocation, filePath)}`;
10657
+ segments[index] = `${prefix}${path6.resolve(sessionLocation, filePath)}`;
11089
10658
  }
11090
10659
  return segments.join("");
11091
10660
  }
@@ -11099,13 +10668,13 @@ function canonicalHunks(value, sessionLocation) {
11099
10668
  }
11100
10669
  const canonical = {
11101
10670
  ...hunk,
11102
- path: path8.resolve(sessionLocation, hunk.path)
10671
+ path: path6.resolve(sessionLocation, hunk.path)
11103
10672
  };
11104
10673
  if (hunk.move_path !== undefined) {
11105
10674
  if (typeof hunk.move_path !== "string" || !hunk.move_path) {
11106
10675
  return;
11107
10676
  }
11108
- canonical.move_path = path8.resolve(sessionLocation, hunk.move_path);
10677
+ canonical.move_path = path6.resolve(sessionLocation, hunk.move_path);
11109
10678
  }
11110
10679
  hunks.push(canonical);
11111
10680
  }
@@ -11394,7 +10963,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11394
10963
  function parentObserverRegistration() {
11395
10964
  if (!options.observer)
11396
10965
  return;
11397
- const targetRoot = canonicalExistingPath(options.targetDirectory ?? process.cwd(), fs9.realpathSync);
10966
+ const targetRoot = canonicalExistingPath(options.targetDirectory ?? process.cwd(), fs8.realpathSync);
11398
10967
  if (!targetRoot)
11399
10968
  return;
11400
10969
  let validation;
@@ -12343,7 +11912,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12343
11912
  markUnavailable();
12344
11913
  return;
12345
11914
  }
12346
- const canonicalParentTargetRoot = canonicalExistingPath(parentTargetRoot, fs9.realpathSync);
11915
+ const canonicalParentTargetRoot = canonicalExistingPath(parentTargetRoot, fs8.realpathSync);
12347
11916
  if (!canonicalParentTargetRoot) {
12348
11917
  markUnavailable();
12349
11918
  return;
@@ -13379,9 +12948,35 @@ function createOpencodeWorkflowGuard(options) {
13379
12948
  }
13380
12949
 
13381
12950
  // src/lib/skill-resolver.ts
13382
- import fs10 from "fs";
13383
- import path9 from "path";
13384
- import { pathToFileURL as pathToFileURL2 } from "url";
12951
+ import fs9 from "fs";
12952
+ import path7 from "path";
12953
+ import { pathToFileURL } from "url";
12954
+
12955
+ // src/lib/skill-catalog.ts
12956
+ function buildCatalogEntries(options) {
12957
+ const { bundledSkillsDir, disabledSkills } = options;
12958
+ return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
12959
+ name: s.name,
12960
+ prefixedName: formatSkillCommandName(s.name),
12961
+ description: s.description
12962
+ }));
12963
+ }
12964
+ function renderCatalogCompact(options) {
12965
+ const entries = buildCatalogEntries(options);
12966
+ const heading = "## Available Systematic Skills";
12967
+ if (entries.length === 0) {
12968
+ return `${heading}
12969
+
12970
+ No Systematic skills are currently available.`;
12971
+ }
12972
+ const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
12973
+ `);
12974
+ return `${heading}
12975
+
12976
+ ${bullets}`;
12977
+ }
12978
+
12979
+ // src/lib/skill-resolver.ts
13385
12980
  function getAllSkills(options) {
13386
12981
  const { bundledSkillsDir, disabledSkills } = options;
13387
12982
  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));
@@ -13416,8 +13011,8 @@ function buildSkillToolParameterHint(options) {
13416
13011
  }
13417
13012
  function buildSkillContentOutput(matchedSkill) {
13418
13013
  const body2 = extractSkillBody(matchedSkill.wrappedTemplate);
13419
- const dir = path9.dirname(matchedSkill.skillFile);
13420
- const base = pathToFileURL2(dir).href;
13014
+ const dir = path7.dirname(matchedSkill.skillFile);
13015
+ const base = pathToFileURL(dir).href;
13421
13016
  const files = discoverSkillFiles(dir);
13422
13017
  const lines = [
13423
13018
  `<skill_content name="${matchedSkill.prefixedName}">`,
@@ -13447,17 +13042,17 @@ function discoverSkillFiles(dir, limit = 10) {
13447
13042
  function handleEntry(entry, currentDir) {
13448
13043
  if (entry.isDirectory()) {
13449
13044
  if (!shouldSkipDirectory(entry.name)) {
13450
- recurse(path9.resolve(currentDir, entry.name));
13045
+ recurse(path7.resolve(currentDir, entry.name));
13451
13046
  }
13452
13047
  } else if (shouldIncludeFile(entry.name)) {
13453
- files.push(path9.resolve(currentDir, entry.name));
13048
+ files.push(path7.resolve(currentDir, entry.name));
13454
13049
  }
13455
13050
  }
13456
13051
  function recurse(currentDir) {
13457
13052
  if (files.length >= limit)
13458
13053
  return;
13459
13054
  try {
13460
- const entries = fs10.readdirSync(currentDir, { withFileTypes: true });
13055
+ const entries = fs9.readdirSync(currentDir, { withFileTypes: true });
13461
13056
  for (const entry of entries) {
13462
13057
  if (files.length >= limit)
13463
13058
  break;
@@ -13469,6 +13064,7 @@ function discoverSkillFiles(dir, limit = 10) {
13469
13064
  return files.map((file) => ` <file>${file}</file>`).join(`
13470
13065
  `);
13471
13066
  }
13067
+
13472
13068
  // src/lib/skill-tool.ts
13473
13069
  function createSkillTool(options) {
13474
13070
  const { bundledSkillsDir, disabledSkills } = options;
@@ -13514,19 +13110,19 @@ function createSkillTool(options) {
13514
13110
  }
13515
13111
 
13516
13112
  // src/index.ts
13517
- var __dirname3 = path10.dirname(fileURLToPath3(import.meta.url));
13518
- var packageRoot2 = path10.resolve(__dirname3, "..");
13519
- var bundledSkillsDir = path10.join(packageRoot2, "skills");
13520
- var bundledAgentsDir2 = path10.join(packageRoot2, "agents");
13521
- var bundledCommandsDir = path10.join(packageRoot2, "commands");
13522
- var packageJsonPath = path10.join(packageRoot2, "package.json");
13523
- var canonicalPackageSource = pathToFileURL3(fs11.realpathSync(packageRoot2)).href;
13524
- var registrationSourceIdentity = createHash5("sha256").update(`systematic/opencode-registration-source/v1/${canonicalPackageSource}`).digest("hex");
13113
+ var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
13114
+ var packageRoot = path8.resolve(__dirname2, "..");
13115
+ var bundledSkillsDir = path8.join(packageRoot, "skills");
13116
+ var bundledAgentsDir = path8.join(packageRoot, "agents");
13117
+ var bundledCommandsDir = path8.join(packageRoot, "commands");
13118
+ var packageJsonPath = path8.join(packageRoot, "package.json");
13119
+ var canonicalPackageSource = pathToFileURL2(fs10.realpathSync(packageRoot)).href;
13120
+ var registrationSourceIdentity = createHash4("sha256").update(`systematic/opencode-registration-source/v1/${canonicalPackageSource}`).digest("hex");
13525
13121
  var getPackageVersion = () => {
13526
13122
  try {
13527
- if (!fs11.existsSync(packageJsonPath))
13123
+ if (!fs10.existsSync(packageJsonPath))
13528
13124
  return "unknown";
13529
- const content = fs11.readFileSync(packageJsonPath, "utf8");
13125
+ const content = fs10.readFileSync(packageJsonPath, "utf8");
13530
13126
  const parsed = JSON.parse(content);
13531
13127
  return parsed.version ?? "unknown";
13532
13128
  } catch {
@@ -13547,9 +13143,8 @@ var initializePlugin = async ({
13547
13143
  const configHandler = createConfigHandler({
13548
13144
  directory,
13549
13145
  bundledSkillsDir,
13550
- bundledAgentsDir: bundledAgentsDir2,
13551
- bundledCommandsDir,
13552
- client
13146
+ bundledAgentsDir,
13147
+ bundledCommandsDir
13553
13148
  });
13554
13149
  const observer = createOpencodeOperationObserver({
13555
13150
  targetDirectory: typeof worktree === "string" ? worktree : directory