@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.
@@ -15,253 +15,10 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __require = import.meta.require;
17
17
 
18
- // src/lib/skills.ts
19
- import fs2 from "fs";
20
- import path2 from "path";
21
-
22
- // src/lib/frontmatter.ts
23
- import yaml from "js-yaml";
24
- function parseFrontmatter(content) {
25
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
26
- const match = content.match(frontmatterRegex);
27
- if (!match) {
28
- return {
29
- data: {},
30
- body: content,
31
- hadFrontmatter: false,
32
- parseError: false
33
- };
34
- }
35
- const yamlContent = match[1];
36
- const body = match[2];
37
- try {
38
- const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
39
- const data = parsed ?? {};
40
- return { data, body, hadFrontmatter: true, parseError: false };
41
- } catch {
42
- return { data: {}, body, hadFrontmatter: true, parseError: true };
43
- }
44
- }
45
-
46
- // src/lib/validation.ts
47
- function isRecord(value) {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
- function isPermissionSetting(value) {
51
- return value === "ask" || value === "allow" || value === "deny";
52
- }
53
- function isToolsMap(value) {
54
- if (!isRecord(value))
55
- return false;
56
- return Object.values(value).every((entry) => typeof entry === "boolean");
57
- }
58
- function isAgentMode(value) {
59
- return value === "subagent" || value === "primary" || value === "all";
60
- }
61
- function extractSimplePermission(data, key) {
62
- if (!(key in data))
63
- return;
64
- const value = data[key];
65
- return isPermissionSetting(value) ? value : null;
66
- }
67
- function extractBashPermission(data) {
68
- if (!("bash" in data))
69
- return;
70
- const bash = data.bash;
71
- if (isPermissionSetting(bash))
72
- return bash;
73
- if (isRecord(bash)) {
74
- const entries = Object.entries(bash);
75
- if (entries.every(([, setting]) => isPermissionSetting(setting))) {
76
- return Object.fromEntries(entries);
77
- }
78
- }
79
- return null;
80
- }
81
- function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
82
- const permission = {};
83
- if (edit)
84
- permission.edit = edit;
85
- if (bash)
86
- permission.bash = bash;
87
- if (webfetch)
88
- permission.webfetch = webfetch;
89
- if (doom_loop)
90
- permission.doom_loop = doom_loop;
91
- if (external_directory)
92
- permission.external_directory = external_directory;
93
- if (task)
94
- permission.task = task;
95
- if (skill)
96
- permission.skill = skill;
97
- return Object.keys(permission).length > 0 ? permission : undefined;
98
- }
99
- function normalizePermission(value) {
100
- if (!isRecord(value))
101
- return;
102
- const bash = extractBashPermission(value);
103
- if (bash === null)
104
- return;
105
- const edit = extractSimplePermission(value, "edit");
106
- if (edit === null)
107
- return;
108
- const webfetch = extractSimplePermission(value, "webfetch");
109
- if (webfetch === null)
110
- return;
111
- const doom_loop = extractSimplePermission(value, "doom_loop");
112
- if (doom_loop === null)
113
- return;
114
- const external_directory = extractSimplePermission(value, "external_directory");
115
- if (external_directory === null)
116
- return;
117
- const task = extractSimplePermission(value, "task");
118
- if (task === null)
119
- return;
120
- const skill = extractSimplePermission(value, "skill");
121
- if (skill === null)
122
- return;
123
- return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
124
- }
125
- function extractString(data, key, fallback = "") {
126
- const value = data[key];
127
- return typeof value === "string" ? value : fallback;
128
- }
129
- function extractNonEmptyString(data, key) {
130
- const value = data[key];
131
- if (typeof value !== "string")
132
- return;
133
- const trimmed = value.trim();
134
- return trimmed !== "" ? trimmed : undefined;
135
- }
136
- function extractNumber(data, key) {
137
- const value = data[key];
138
- return typeof value === "number" ? value : undefined;
139
- }
140
- function extractBoolean(data, key) {
141
- const value = data[key];
142
- if (typeof value === "boolean")
143
- return value;
144
- if (typeof value === "string") {
145
- const normalized = value.trim().toLowerCase();
146
- if (normalized === "true")
147
- return true;
148
- if (normalized === "false")
149
- return false;
150
- }
151
- return;
152
- }
153
-
154
- // src/lib/walk-dir.ts
155
- import fs from "fs";
156
- import path from "path";
157
- function walkDir(rootDir, options = {}) {
158
- const { maxDepth = 3, filter } = options;
159
- const results = [];
160
- if (!fs.existsSync(rootDir))
161
- return results;
162
- function recurse(currentDir, depth, category) {
163
- if (depth > maxDepth)
164
- return;
165
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
166
- for (const entry of entries) {
167
- const fullPath = path.join(currentDir, entry.name);
168
- const walkEntry = {
169
- path: fullPath,
170
- name: entry.name,
171
- isDirectory: entry.isDirectory(),
172
- depth,
173
- category
174
- };
175
- if (!filter || filter(walkEntry)) {
176
- results.push(walkEntry);
177
- }
178
- if (entry.isDirectory()) {
179
- recurse(fullPath, depth + 1, entry.name);
180
- }
181
- }
182
- }
183
- recurse(rootDir, 0);
184
- return results;
185
- }
186
-
187
- // src/lib/skills.ts
188
- function parseMetadata(data) {
189
- const metadataRaw = data.metadata;
190
- if (!isRecord(metadataRaw)) {
191
- return;
192
- }
193
- const entries = Object.entries(metadataRaw);
194
- if (!entries.every(([, v]) => typeof v === "string")) {
195
- return;
196
- }
197
- return Object.fromEntries(entries);
198
- }
199
- function extractFrontmatterFromContent(content) {
200
- const { data, parseError } = parseFrontmatter(content);
201
- if (parseError) {
202
- return { name: "", description: "" };
203
- }
204
- const metadata = parseMetadata(data);
205
- const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
206
- const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
207
- return {
208
- name: extractString(data, "name"),
209
- description: extractString(data, "description"),
210
- license: extractNonEmptyString(data, "license"),
211
- compatibility: extractNonEmptyString(data, "compatibility"),
212
- metadata,
213
- disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
214
- userInvocable: extractBoolean(data, "user-invocable"),
215
- subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
216
- agent: extractNonEmptyString(data, "agent"),
217
- model: extractNonEmptyString(data, "model"),
218
- argumentHint: argumentHint !== "" ? argumentHint : undefined,
219
- allowedTools: extractNonEmptyString(data, "allowed-tools")
220
- };
221
- }
222
- function extractFrontmatter(filePath) {
223
- try {
224
- const content = fs2.readFileSync(filePath, "utf8");
225
- return extractFrontmatterFromContent(content);
226
- } catch {
227
- return { name: "", description: "" };
228
- }
229
- }
230
- function findSkillsInDir(dir, maxDepth = 3) {
231
- const skills = [];
232
- const entries = walkDir(dir, {
233
- maxDepth,
234
- filter: (e) => e.isDirectory
235
- });
236
- for (const entry of entries) {
237
- const skillFile = path2.join(entry.path, "SKILL.md");
238
- if (fs2.existsSync(skillFile)) {
239
- const frontmatter = extractFrontmatter(skillFile);
240
- skills.push({
241
- path: entry.path,
242
- skillFile,
243
- name: frontmatter.name || entry.name,
244
- description: frontmatter.description || "",
245
- license: frontmatter.license,
246
- compatibility: frontmatter.compatibility,
247
- metadata: frontmatter.metadata,
248
- disableModelInvocation: frontmatter.disableModelInvocation,
249
- userInvocable: frontmatter.userInvocable,
250
- subtask: frontmatter.subtask,
251
- agent: frontmatter.agent,
252
- model: frontmatter.model,
253
- argumentHint: frontmatter.argumentHint,
254
- allowedTools: frontmatter.allowedTools
255
- });
256
- }
257
- }
258
- return skills;
259
- }
260
-
261
18
  // src/lib/config.ts
262
- import fs3 from "fs";
19
+ import fs from "fs";
263
20
  import os from "os";
264
- import path3 from "path";
21
+ import path from "path";
265
22
 
266
23
  // node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
267
24
  function createScanner(text, ignoreTrivia = false) {
@@ -1063,12 +820,12 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
1063
820
  }
1064
821
  return result;
1065
822
  }
1066
- function findNodeAtLocation(root, path3) {
823
+ function findNodeAtLocation(root, path) {
1067
824
  if (!root) {
1068
825
  return;
1069
826
  }
1070
827
  let node = root;
1071
- for (let segment of path3) {
828
+ for (let segment of path) {
1072
829
  if (typeof segment === "string") {
1073
830
  if (node.type !== "object" || !Array.isArray(node.children)) {
1074
831
  return;
@@ -1369,14 +1126,14 @@ function getNodeType(value) {
1369
1126
 
1370
1127
  // node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/edit.js
1371
1128
  function setProperty(text, originalPath, value, options) {
1372
- const path3 = originalPath.slice();
1129
+ const path = originalPath.slice();
1373
1130
  const errors = [];
1374
1131
  const root = parseTree(text, errors);
1375
1132
  let parent = undefined;
1376
1133
  let lastSegment = undefined;
1377
- while (path3.length > 0) {
1378
- lastSegment = path3.pop();
1379
- parent = findNodeAtLocation(root, path3);
1134
+ while (path.length > 0) {
1135
+ lastSegment = path.pop();
1136
+ parent = findNodeAtLocation(root, path);
1380
1137
  if (parent === undefined && value !== undefined) {
1381
1138
  if (typeof lastSegment === "string") {
1382
1139
  value = { [lastSegment]: value };
@@ -1600,8 +1357,8 @@ function printParseErrorCode(code) {
1600
1357
  }
1601
1358
  return "<unknown ParseErrorCode>";
1602
1359
  }
1603
- function modify(text, path3, value, options) {
1604
- return setProperty(text, path3, value, options);
1360
+ function modify(text, path, value, options) {
1361
+ return setProperty(text, path, value, options);
1605
1362
  }
1606
1363
  function applyEdits(text, edits) {
1607
1364
  let sortedEdits = edits.slice(0).sort((a, b) => {
@@ -2501,10 +2258,10 @@ function mergeDefs(...defs) {
2501
2258
  function cloneDef(schema) {
2502
2259
  return mergeDefs(schema._zod.def);
2503
2260
  }
2504
- function getElementAtPath(obj, path3) {
2505
- if (!path3)
2261
+ function getElementAtPath(obj, path) {
2262
+ if (!path)
2506
2263
  return obj;
2507
- return path3.reduce((acc, key) => acc?.[key], obj);
2264
+ return path.reduce((acc, key) => acc?.[key], obj);
2508
2265
  }
2509
2266
  function promiseAllObject(promisesObj) {
2510
2267
  const keys = Object.keys(promisesObj);
@@ -2912,11 +2669,11 @@ function explicitlyAborted(x, startIndex = 0) {
2912
2669
  }
2913
2670
  return false;
2914
2671
  }
2915
- function prefixIssues(path3, issues) {
2672
+ function prefixIssues(path, issues) {
2916
2673
  return issues.map((iss) => {
2917
2674
  var _a2;
2918
2675
  (_a2 = iss).path ?? (_a2.path = []);
2919
- iss.path.unshift(path3);
2676
+ iss.path.unshift(path);
2920
2677
  return iss;
2921
2678
  });
2922
2679
  }
@@ -3063,16 +2820,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
3063
2820
  }
3064
2821
  function formatError(error, mapper = (issue2) => issue2.message) {
3065
2822
  const fieldErrors = { _errors: [] };
3066
- const processError = (error2, path3 = []) => {
2823
+ const processError = (error2, path = []) => {
3067
2824
  for (const issue2 of error2.issues) {
3068
2825
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3069
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
2826
+ issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
3070
2827
  } else if (issue2.code === "invalid_key") {
3071
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
2828
+ processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3072
2829
  } else if (issue2.code === "invalid_element") {
3073
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
2830
+ processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3074
2831
  } else {
3075
- const fullpath = [...path3, ...issue2.path];
2832
+ const fullpath = [...path, ...issue2.path];
3076
2833
  if (fullpath.length === 0) {
3077
2834
  fieldErrors._errors.push(mapper(issue2));
3078
2835
  } else {
@@ -3099,17 +2856,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
3099
2856
  }
3100
2857
  function treeifyError(error, mapper = (issue2) => issue2.message) {
3101
2858
  const result = { errors: [] };
3102
- const processError = (error2, path3 = []) => {
2859
+ const processError = (error2, path = []) => {
3103
2860
  var _a2, _b;
3104
2861
  for (const issue2 of error2.issues) {
3105
2862
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3106
- issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
2863
+ issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
3107
2864
  } else if (issue2.code === "invalid_key") {
3108
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
2865
+ processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3109
2866
  } else if (issue2.code === "invalid_element") {
3110
- processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
2867
+ processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3111
2868
  } else {
3112
- const fullpath = [...path3, ...issue2.path];
2869
+ const fullpath = [...path, ...issue2.path];
3113
2870
  if (fullpath.length === 0) {
3114
2871
  result.errors.push(mapper(issue2));
3115
2872
  continue;
@@ -3141,8 +2898,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
3141
2898
  }
3142
2899
  function toDotPath(_path) {
3143
2900
  const segs = [];
3144
- const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3145
- for (const seg of path3) {
2901
+ const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2902
+ for (const seg of path) {
3146
2903
  if (typeof seg === "number")
3147
2904
  segs.push(`[${seg}]`);
3148
2905
  else if (typeof seg === "symbol")
@@ -15601,13 +15358,13 @@ function resolveRef(ref, ctx) {
15601
15358
  if (!ref.startsWith("#")) {
15602
15359
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15603
15360
  }
15604
- const path3 = ref.slice(1).split("/").filter(Boolean);
15605
- if (path3.length === 0) {
15361
+ const path = ref.slice(1).split("/").filter(Boolean);
15362
+ if (path.length === 0) {
15606
15363
  return ctx.rootSchema;
15607
15364
  }
15608
15365
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15609
- if (path3[0] === defsKey) {
15610
- const key = path3[1];
15366
+ if (path[0] === defsKey) {
15367
+ const key = path[1];
15611
15368
  if (!key || !ctx.defs[key]) {
15612
15369
  throw new Error(`Reference not found: ${ref}`);
15613
15370
  }
@@ -16414,10 +16171,10 @@ function warnDroppedNames(dropped, field, warned, removalVersion, warningSink =
16414
16171
  }
16415
16172
  }
16416
16173
  function resolveConfigPath(dir, basename) {
16417
- const jsoncPath = path3.join(dir, `${basename}.jsonc`);
16418
- if (fs3.existsSync(jsoncPath))
16174
+ const jsoncPath = path.join(dir, `${basename}.jsonc`);
16175
+ if (fs.existsSync(jsoncPath))
16419
16176
  return jsoncPath;
16420
- return path3.join(dir, `${basename}.json`);
16177
+ return path.join(dir, `${basename}.json`);
16421
16178
  }
16422
16179
  function isErrorWithCode(error51) {
16423
16180
  return error51 instanceof Error && "code" in error51;
@@ -16425,7 +16182,7 @@ function isErrorWithCode(error51) {
16425
16182
  function loadJsoncFile(filePath) {
16426
16183
  let content;
16427
16184
  try {
16428
- content = fs3.readFileSync(filePath, "utf-8");
16185
+ content = fs.readFileSync(filePath, "utf-8");
16429
16186
  } catch (error51) {
16430
16187
  if (isErrorWithCode(error51) && error51.code === "ENOENT")
16431
16188
  return null;
@@ -16438,7 +16195,7 @@ function loadJsoncFile(filePath) {
16438
16195
  const message = error51 ? `${printParseErrorCode(error51.error)} at offset ${error51.offset}` : "unknown parse error";
16439
16196
  throw new Error(`Invalid Systematic config in ${filePath}: JSONC parse error: ${message}`);
16440
16197
  }
16441
- if (!isRecord2(parsed)) {
16198
+ if (!isRecord(parsed)) {
16442
16199
  throw new Error(`Invalid Systematic config in ${filePath}: root must be an object`);
16443
16200
  }
16444
16201
  return parsed;
@@ -16478,9 +16235,9 @@ function enrichUnrecognizedKeyIssues(issues, rawInput) {
16478
16235
  return issue2;
16479
16236
  });
16480
16237
  }
16481
- function resolveValueAtPath(root, path4) {
16238
+ function resolveValueAtPath(root, path2) {
16482
16239
  let current = root;
16483
- for (const segment of path4) {
16240
+ for (const segment of path2) {
16484
16241
  if (typeof segment === "symbol") {
16485
16242
  return;
16486
16243
  }
@@ -16489,7 +16246,7 @@ function resolveValueAtPath(root, path4) {
16489
16246
  return;
16490
16247
  current = current[segment];
16491
16248
  } else {
16492
- if (!isRecord2(current))
16249
+ if (!isRecord(current))
16493
16250
  return;
16494
16251
  current = current[segment];
16495
16252
  }
@@ -16566,13 +16323,13 @@ function classifyConfigSourceError(error51) {
16566
16323
  return "source-invalid";
16567
16324
  }
16568
16325
  function isConfigSchemaError(error51) {
16569
- return isRecord2(error51) && error51._tag === "ConfigSchemaError";
16326
+ return isRecord(error51) && error51._tag === "ConfigSchemaError";
16570
16327
  }
16571
16328
  function resolveConfigSourcePath(filePath) {
16572
16329
  try {
16573
- return fs3.realpathSync(filePath);
16330
+ return fs.realpathSync(filePath);
16574
16331
  } catch {
16575
- return path3.resolve(filePath);
16332
+ return path.resolve(filePath);
16576
16333
  }
16577
16334
  }
16578
16335
  function collectProjectProtectedFields(rawConfig, trust) {
@@ -16591,9 +16348,9 @@ function collectProjectProtectedFields(rawConfig, trust) {
16591
16348
  ];
16592
16349
  }
16593
16350
  function collectOverlayProtectedFields(overlayMap, mapKey) {
16594
- if (!isRecord2(overlayMap))
16351
+ if (!isRecord(overlayMap))
16595
16352
  return [];
16596
- return Object.values(overlayMap).flatMap((value) => isRecord2(value) ? collectProtectedOverlayValue(value, mapKey) : []);
16353
+ return Object.values(overlayMap).flatMap((value) => isRecord(value) ? collectProtectedOverlayValue(value, mapKey) : []);
16597
16354
  }
16598
16355
  function collectProtectedOverlayValue(value, mapKey) {
16599
16356
  return [...SECURITY_OVERLAY_FIELDS].filter((field) => Object.hasOwn(value, field)).map((field) => ({
@@ -16609,7 +16366,7 @@ function stripProjectProtectedFields(rawConfig) {
16609
16366
  }
16610
16367
  return config2;
16611
16368
  }
16612
- function isRecord2(value) {
16369
+ function isRecord(value) {
16613
16370
  return typeof value === "object" && value !== null && !Array.isArray(value);
16614
16371
  }
16615
16372
  function mergeArraysUnique(arr1, arr2) {
@@ -16753,7 +16510,7 @@ function hasConfigField(config2, fieldPath) {
16753
16510
  if (nested === undefined)
16754
16511
  return config2[topLevel] !== undefined;
16755
16512
  const value = config2[topLevel];
16756
- return isRecord2(value) && value[nested] !== undefined;
16513
+ return isRecord(value) && value[nested] !== undefined;
16757
16514
  }
16758
16515
  function sortAuthorities(authorities) {
16759
16516
  return [...authorities].sort((left, right) => left.fieldPath === right.fieldPath ? left.sourceKind.localeCompare(right.sourceKind) : left.fieldPath.localeCompare(right.fieldPath));
@@ -16776,12 +16533,12 @@ function mergeOverlayMap(target, source, mapKey) {
16776
16533
  const overlayMap = source.config[mapKey];
16777
16534
  if (overlayMap === undefined)
16778
16535
  return;
16779
- if (!isRecord2(overlayMap)) {
16536
+ if (!isRecord(overlayMap)) {
16780
16537
  throwInvalidOverlay(source.path, mapKey);
16781
16538
  }
16782
16539
  for (const [key, value] of Object.entries(overlayMap)) {
16783
16540
  const keyPath = `${mapKey}.${key}`;
16784
- if (!isRecord2(value)) {
16541
+ if (!isRecord(value)) {
16785
16542
  throwInvalidOverlay(source.path, keyPath);
16786
16543
  }
16787
16544
  if (source.trust === "project") {
@@ -16846,12 +16603,12 @@ function mergePiSubagentsOverlayMap(target, source, mapKey) {
16846
16603
  const overlayMap = source.config.pi_subagents?.[mapKey];
16847
16604
  if (overlayMap === undefined)
16848
16605
  return;
16849
- if (!isRecord2(overlayMap)) {
16606
+ if (!isRecord(overlayMap)) {
16850
16607
  throwInvalidOverlay(source.path, `pi_subagents.${mapKey}`);
16851
16608
  }
16852
16609
  for (const [key, rawValue] of Object.entries(overlayMap)) {
16853
16610
  const keyPath = `pi_subagents.${mapKey}.${key}`;
16854
- if (!isRecord2(rawValue)) {
16611
+ if (!isRecord(rawValue)) {
16855
16612
  throwInvalidOverlay(source.path, keyPath);
16856
16613
  }
16857
16614
  const previous = target[key];
@@ -16875,21 +16632,186 @@ function throwInvalidOverlay(sourcePath, keyPath) {
16875
16632
  }
16876
16633
  function getConfigPaths(projectDir, options) {
16877
16634
  const homeDir = options?.homeDir ?? os.homedir();
16878
- const userConfigDir = options?.userConfigDir ?? path3.join(homeDir, ".config/opencode");
16635
+ const userConfigDir = options?.userConfigDir ?? path.join(homeDir, ".config/opencode");
16879
16636
  const customConfigDir = options !== undefined && Object.hasOwn(options, "customConfigDir") ? options.customConfigDir?.trim() : process.env.OPENCODE_CONFIG_DIR?.trim();
16880
16637
  const result = {
16881
16638
  userConfig: resolveConfigPath(userConfigDir, "systematic"),
16882
- projectConfig: resolveConfigPath(path3.join(projectDir, ".opencode"), "systematic"),
16883
- userDir: path3.join(homeDir, ".config/opencode/systematic"),
16884
- projectDir: path3.join(projectDir, ".opencode/systematic"),
16639
+ projectConfig: resolveConfigPath(path.join(projectDir, ".opencode"), "systematic"),
16640
+ userDir: path.join(homeDir, ".config/opencode/systematic"),
16641
+ projectDir: path.join(projectDir, ".opencode/systematic"),
16885
16642
  ...customConfigDir && {
16886
16643
  customConfig: resolveConfigPath(customConfigDir, "systematic"),
16887
- customDir: path3.join(customConfigDir, "systematic")
16644
+ customDir: path.join(customConfigDir, "systematic")
16888
16645
  }
16889
16646
  };
16890
16647
  return result;
16891
16648
  }
16892
16649
 
16650
+ // src/lib/frontmatter.ts
16651
+ import yaml from "js-yaml";
16652
+ function parseFrontmatter(content) {
16653
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
16654
+ const match = content.match(frontmatterRegex);
16655
+ if (!match) {
16656
+ return {
16657
+ data: {},
16658
+ body: content,
16659
+ hadFrontmatter: false,
16660
+ parseError: false
16661
+ };
16662
+ }
16663
+ const yamlContent = match[1];
16664
+ const body = match[2];
16665
+ try {
16666
+ const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
16667
+ const data = parsed ?? {};
16668
+ return { data, body, hadFrontmatter: true, parseError: false };
16669
+ } catch {
16670
+ return { data: {}, body, hadFrontmatter: true, parseError: true };
16671
+ }
16672
+ }
16673
+
16674
+ // src/lib/validation.ts
16675
+ function isRecord2(value) {
16676
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16677
+ }
16678
+ function isPermissionSetting(value) {
16679
+ return value === "ask" || value === "allow" || value === "deny";
16680
+ }
16681
+ function isToolsMap(value) {
16682
+ if (!isRecord2(value))
16683
+ return false;
16684
+ return Object.values(value).every((entry) => typeof entry === "boolean");
16685
+ }
16686
+ function isAgentMode(value) {
16687
+ return value === "subagent" || value === "primary" || value === "all";
16688
+ }
16689
+ function extractSimplePermission(data, key) {
16690
+ if (!(key in data))
16691
+ return;
16692
+ const value = data[key];
16693
+ return isPermissionSetting(value) ? value : null;
16694
+ }
16695
+ function extractBashPermission(data) {
16696
+ if (!("bash" in data))
16697
+ return;
16698
+ const bash = data.bash;
16699
+ if (isPermissionSetting(bash))
16700
+ return bash;
16701
+ if (isRecord2(bash)) {
16702
+ const entries = Object.entries(bash);
16703
+ if (entries.every(([, setting]) => isPermissionSetting(setting))) {
16704
+ return Object.fromEntries(entries);
16705
+ }
16706
+ }
16707
+ return null;
16708
+ }
16709
+ function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
16710
+ const permission = {};
16711
+ if (edit)
16712
+ permission.edit = edit;
16713
+ if (bash)
16714
+ permission.bash = bash;
16715
+ if (webfetch)
16716
+ permission.webfetch = webfetch;
16717
+ if (doom_loop)
16718
+ permission.doom_loop = doom_loop;
16719
+ if (external_directory)
16720
+ permission.external_directory = external_directory;
16721
+ if (task)
16722
+ permission.task = task;
16723
+ if (skill)
16724
+ permission.skill = skill;
16725
+ return Object.keys(permission).length > 0 ? permission : undefined;
16726
+ }
16727
+ function normalizePermission(value) {
16728
+ if (!isRecord2(value))
16729
+ return;
16730
+ const bash = extractBashPermission(value);
16731
+ if (bash === null)
16732
+ return;
16733
+ const edit = extractSimplePermission(value, "edit");
16734
+ if (edit === null)
16735
+ return;
16736
+ const webfetch = extractSimplePermission(value, "webfetch");
16737
+ if (webfetch === null)
16738
+ return;
16739
+ const doom_loop = extractSimplePermission(value, "doom_loop");
16740
+ if (doom_loop === null)
16741
+ return;
16742
+ const external_directory = extractSimplePermission(value, "external_directory");
16743
+ if (external_directory === null)
16744
+ return;
16745
+ const task = extractSimplePermission(value, "task");
16746
+ if (task === null)
16747
+ return;
16748
+ const skill = extractSimplePermission(value, "skill");
16749
+ if (skill === null)
16750
+ return;
16751
+ return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
16752
+ }
16753
+ function extractString(data, key, fallback = "") {
16754
+ const value = data[key];
16755
+ return typeof value === "string" ? value : fallback;
16756
+ }
16757
+ function extractNonEmptyString(data, key) {
16758
+ const value = data[key];
16759
+ if (typeof value !== "string")
16760
+ return;
16761
+ const trimmed = value.trim();
16762
+ return trimmed !== "" ? trimmed : undefined;
16763
+ }
16764
+ function extractNumber(data, key) {
16765
+ const value = data[key];
16766
+ return typeof value === "number" ? value : undefined;
16767
+ }
16768
+ function extractBoolean(data, key) {
16769
+ const value = data[key];
16770
+ if (typeof value === "boolean")
16771
+ return value;
16772
+ if (typeof value === "string") {
16773
+ const normalized = value.trim().toLowerCase();
16774
+ if (normalized === "true")
16775
+ return true;
16776
+ if (normalized === "false")
16777
+ return false;
16778
+ }
16779
+ return;
16780
+ }
16781
+
16782
+ // src/lib/walk-dir.ts
16783
+ import fs2 from "fs";
16784
+ import path2 from "path";
16785
+ function walkDir(rootDir, options = {}) {
16786
+ const { maxDepth = 3, filter } = options;
16787
+ const results = [];
16788
+ if (!fs2.existsSync(rootDir))
16789
+ return results;
16790
+ function recurse(currentDir, depth, category) {
16791
+ if (depth > maxDepth)
16792
+ return;
16793
+ const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
16794
+ for (const entry of entries) {
16795
+ const fullPath = path2.join(currentDir, entry.name);
16796
+ const walkEntry = {
16797
+ path: fullPath,
16798
+ name: entry.name,
16799
+ isDirectory: entry.isDirectory(),
16800
+ depth,
16801
+ category
16802
+ };
16803
+ if (!filter || filter(walkEntry)) {
16804
+ results.push(walkEntry);
16805
+ }
16806
+ if (entry.isDirectory()) {
16807
+ recurse(fullPath, depth + 1, entry.name);
16808
+ }
16809
+ }
16810
+ }
16811
+ recurse(rootDir, 0);
16812
+ return results;
16813
+ }
16814
+
16893
16815
  // src/lib/agents.ts
16894
16816
  function findAgentsInDir(dir, maxDepth = 2) {
16895
16817
  const entries = walkDir(dir, {
@@ -16964,6 +16886,82 @@ function extractCommandFrontmatter(content) {
16964
16886
  };
16965
16887
  }
16966
16888
 
16889
+ // src/lib/skills.ts
16890
+ import fs3 from "fs";
16891
+ import path3 from "path";
16892
+ function parseMetadata(data) {
16893
+ const metadataRaw = data.metadata;
16894
+ if (!isRecord2(metadataRaw)) {
16895
+ return;
16896
+ }
16897
+ const entries = Object.entries(metadataRaw);
16898
+ if (!entries.every(([, v]) => typeof v === "string")) {
16899
+ return;
16900
+ }
16901
+ return Object.fromEntries(entries);
16902
+ }
16903
+ function extractFrontmatterFromContent(content) {
16904
+ const { data, parseError } = parseFrontmatter(content);
16905
+ if (parseError) {
16906
+ return { name: "", description: "" };
16907
+ }
16908
+ const metadata = parseMetadata(data);
16909
+ const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
16910
+ const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
16911
+ return {
16912
+ name: extractString(data, "name"),
16913
+ description: extractString(data, "description"),
16914
+ license: extractNonEmptyString(data, "license"),
16915
+ compatibility: extractNonEmptyString(data, "compatibility"),
16916
+ metadata,
16917
+ disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
16918
+ userInvocable: extractBoolean(data, "user-invocable"),
16919
+ subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
16920
+ agent: extractNonEmptyString(data, "agent"),
16921
+ model: extractNonEmptyString(data, "model"),
16922
+ argumentHint: argumentHint !== "" ? argumentHint : undefined,
16923
+ allowedTools: extractNonEmptyString(data, "allowed-tools")
16924
+ };
16925
+ }
16926
+ function extractFrontmatter(filePath) {
16927
+ try {
16928
+ const content = fs3.readFileSync(filePath, "utf8");
16929
+ return extractFrontmatterFromContent(content);
16930
+ } catch {
16931
+ return { name: "", description: "" };
16932
+ }
16933
+ }
16934
+ function findSkillsInDir(dir, maxDepth = 3) {
16935
+ const skills = [];
16936
+ const entries = walkDir(dir, {
16937
+ maxDepth,
16938
+ filter: (e) => e.isDirectory
16939
+ });
16940
+ for (const entry of entries) {
16941
+ const skillFile = path3.join(entry.path, "SKILL.md");
16942
+ if (fs3.existsSync(skillFile)) {
16943
+ const frontmatter = extractFrontmatter(skillFile);
16944
+ skills.push({
16945
+ path: entry.path,
16946
+ skillFile,
16947
+ name: frontmatter.name || entry.name,
16948
+ description: frontmatter.description || "",
16949
+ license: frontmatter.license,
16950
+ compatibility: frontmatter.compatibility,
16951
+ metadata: frontmatter.metadata,
16952
+ disableModelInvocation: frontmatter.disableModelInvocation,
16953
+ userInvocable: frontmatter.userInvocable,
16954
+ subtask: frontmatter.subtask,
16955
+ agent: frontmatter.agent,
16956
+ model: frontmatter.model,
16957
+ argumentHint: frontmatter.argumentHint,
16958
+ allowedTools: frontmatter.allowedTools
16959
+ });
16960
+ }
16961
+ }
16962
+ return skills;
16963
+ }
16964
+
16967
16965
  // src/lib/discovered-skills.ts
16968
16966
  import fs4 from "fs";
16969
16967
  import path4 from "path";
@@ -17096,4 +17094,4 @@ function discoverSkills(options) {
17096
17094
  return Array.from(byName.values());
17097
17095
  }
17098
17096
 
17099
- export { __require, parseFrontmatter, isRecord, extractString, findSkillsInDir, parse2 as parse, parseTree2 as parseTree, modify, applyEdits, exports_external, AgentOverlaySchema, CategoryOverlaySchema, loadConfig, loadConfigWithSources, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, discoverSkills };
17097
+ export { __require, parseFrontmatter, parse2 as parse, parseTree2 as parseTree, modify, applyEdits, exports_external, AgentOverlaySchema, CategoryOverlaySchema, loadConfig, loadConfigWithSources, getConfigPaths, isRecord2 as isRecord, extractString, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, findSkillsInDir, discoverSkills };