@fro.bot/systematic 3.6.2 → 3.8.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/cli.d.ts +18 -0
- package/dist/cli.js +1071 -127
- package/dist/{index-1mb4baxr.js → index-hjbt4p5s.js} +335 -27
- package/dist/index.js +96 -222
- package/dist/lib/capability-snapshot.d.ts +195 -0
- package/dist/lib/config-schema.d.ts +1 -1
- package/dist/lib/config.d.ts +40 -2
- package/dist/lib/discovered-skills.d.ts +3 -0
- package/package.json +3 -3
|
@@ -16355,6 +16355,13 @@ var SECURITY_OVERLAY_FIELDS = [
|
|
|
16355
16355
|
];
|
|
16356
16356
|
|
|
16357
16357
|
// src/lib/config.ts
|
|
16358
|
+
var CONFIG_AUTHORITY_FIELD_PATHS = [
|
|
16359
|
+
"bootstrap.enabled",
|
|
16360
|
+
"bootstrap.file",
|
|
16361
|
+
"skills_as_commands",
|
|
16362
|
+
"workflow_guard.debug",
|
|
16363
|
+
"workflow_guard.mode"
|
|
16364
|
+
];
|
|
16358
16365
|
var DEFAULT_CONFIG = {
|
|
16359
16366
|
disabled_skills: [],
|
|
16360
16367
|
disabled_agents: [],
|
|
@@ -16371,7 +16378,20 @@ var DEFAULT_CONFIG = {
|
|
|
16371
16378
|
pi_subagents: { categories: {}, agents: {} },
|
|
16372
16379
|
skills_as_commands: true
|
|
16373
16380
|
};
|
|
16374
|
-
var
|
|
16381
|
+
var PROTECTED_OVERLAY_FIELD_PATHS = {
|
|
16382
|
+
agents: {
|
|
16383
|
+
model: "agents.*.model",
|
|
16384
|
+
permission: "agents.*.permission",
|
|
16385
|
+
skills: "agents.*.skills",
|
|
16386
|
+
variant: "agents.*.variant"
|
|
16387
|
+
},
|
|
16388
|
+
categories: {
|
|
16389
|
+
model: "categories.*.model",
|
|
16390
|
+
permission: "categories.*.permission",
|
|
16391
|
+
skills: "categories.*.skills",
|
|
16392
|
+
variant: "categories.*.variant"
|
|
16393
|
+
}
|
|
16394
|
+
};
|
|
16375
16395
|
var PROJECT_PROTECTED_FIELDS = new Set(["workflow_guard"]);
|
|
16376
16396
|
var CURRENT_SKILL_NAMES_SET = new Set(BUNDLED_SKILL_NAMES);
|
|
16377
16397
|
var CURRENT_AGENT_NAMES_SET = new Set([
|
|
@@ -16383,14 +16403,14 @@ function computeDroppedNames(names, allowedSet) {
|
|
|
16383
16403
|
return names.filter((n) => !allowedSet.has(n));
|
|
16384
16404
|
}
|
|
16385
16405
|
var MIGRATION_DOCS_URL = "https://fro.bot/systematic/guides/v3-migration/";
|
|
16386
|
-
function warnDroppedNames(dropped, field, warned, removalVersion) {
|
|
16406
|
+
function warnDroppedNames(dropped, field, warned, removalVersion, warningSink = console.warn) {
|
|
16387
16407
|
for (const name of dropped) {
|
|
16388
16408
|
if (warned.has(name))
|
|
16389
16409
|
continue;
|
|
16390
16410
|
warned.add(name);
|
|
16391
16411
|
const displayName = field === "categories" ? `${field}.${name}` : name;
|
|
16392
16412
|
const removalNote = removalVersion ? ` It was removed in ${removalVersion}.` : "";
|
|
16393
|
-
|
|
16413
|
+
warningSink(`[systematic] "${displayName}" in \`${field}\` is no longer a bundled name and will be ignored.${removalNote} Remove it from your config to silence this warning. See ${MIGRATION_DOCS_URL} for migration guidance.`);
|
|
16394
16414
|
}
|
|
16395
16415
|
}
|
|
16396
16416
|
function resolveConfigPath(dir, basename) {
|
|
@@ -16496,16 +16516,91 @@ ${formatted.map((entry) => ` - ${entry}`).join(`
|
|
|
16496
16516
|
issues
|
|
16497
16517
|
});
|
|
16498
16518
|
}
|
|
16499
|
-
function loadConfigSource(filePath, trust) {
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16519
|
+
function loadConfigSource(filePath, trust, invalidSource) {
|
|
16520
|
+
try {
|
|
16521
|
+
const rawConfig = loadJsoncFile(filePath);
|
|
16522
|
+
if (!rawConfig) {
|
|
16523
|
+
return {
|
|
16524
|
+
metadata: { kind: trust, presence: "absent" },
|
|
16525
|
+
source: null
|
|
16526
|
+
};
|
|
16527
|
+
}
|
|
16528
|
+
const protectedFields = collectProjectProtectedFields(rawConfig, trust);
|
|
16529
|
+
const config2 = trust === "project" ? stripProjectProtectedFields(rawConfig) : rawConfig;
|
|
16530
|
+
const result = SystematicConfigSchema.safeParse(config2);
|
|
16531
|
+
if (!result.success) {
|
|
16532
|
+
throwTopLevelConfigSchemaError(filePath, trust, result.error.issues, config2);
|
|
16533
|
+
}
|
|
16534
|
+
return {
|
|
16535
|
+
metadata: { kind: trust, presence: "present" },
|
|
16536
|
+
source: {
|
|
16537
|
+
canonicalPath: resolveConfigSourcePath(filePath),
|
|
16538
|
+
config: config2,
|
|
16539
|
+
path: filePath,
|
|
16540
|
+
protectedFields,
|
|
16541
|
+
trust
|
|
16542
|
+
}
|
|
16543
|
+
};
|
|
16544
|
+
} catch (error51) {
|
|
16545
|
+
if (invalidSource === "throw")
|
|
16546
|
+
throw error51;
|
|
16547
|
+
return {
|
|
16548
|
+
metadata: {
|
|
16549
|
+
errorCode: classifyConfigSourceError(error51),
|
|
16550
|
+
kind: trust,
|
|
16551
|
+
presence: "invalid"
|
|
16552
|
+
},
|
|
16553
|
+
source: null
|
|
16554
|
+
};
|
|
16507
16555
|
}
|
|
16508
|
-
|
|
16556
|
+
}
|
|
16557
|
+
function classifyConfigSourceError(error51) {
|
|
16558
|
+
if (isConfigSchemaError(error51))
|
|
16559
|
+
return "schema-invalid";
|
|
16560
|
+
if (!(error51 instanceof Error))
|
|
16561
|
+
return "source-invalid";
|
|
16562
|
+
if (error51.message.includes("JSONC parse error"))
|
|
16563
|
+
return "parse-failed";
|
|
16564
|
+
if (error51.message.includes("unable to read file"))
|
|
16565
|
+
return "read-failed";
|
|
16566
|
+
return "source-invalid";
|
|
16567
|
+
}
|
|
16568
|
+
function isConfigSchemaError(error51) {
|
|
16569
|
+
return isRecord2(error51) && error51._tag === "ConfigSchemaError";
|
|
16570
|
+
}
|
|
16571
|
+
function resolveConfigSourcePath(filePath) {
|
|
16572
|
+
try {
|
|
16573
|
+
return fs3.realpathSync(filePath);
|
|
16574
|
+
} catch {
|
|
16575
|
+
return path3.resolve(filePath);
|
|
16576
|
+
}
|
|
16577
|
+
}
|
|
16578
|
+
function collectProjectProtectedFields(rawConfig, trust) {
|
|
16579
|
+
if (trust !== "project")
|
|
16580
|
+
return [];
|
|
16581
|
+
return [
|
|
16582
|
+
...Object.hasOwn(rawConfig, "workflow_guard") ? [
|
|
16583
|
+
{
|
|
16584
|
+
fieldPath: "workflow_guard",
|
|
16585
|
+
outcome: "blocked",
|
|
16586
|
+
sourceKind: "project"
|
|
16587
|
+
}
|
|
16588
|
+
] : [],
|
|
16589
|
+
...collectOverlayProtectedFields(rawConfig.agents, "agents"),
|
|
16590
|
+
...collectOverlayProtectedFields(rawConfig.categories, "categories")
|
|
16591
|
+
];
|
|
16592
|
+
}
|
|
16593
|
+
function collectOverlayProtectedFields(overlayMap, mapKey) {
|
|
16594
|
+
if (!isRecord2(overlayMap))
|
|
16595
|
+
return [];
|
|
16596
|
+
return Object.values(overlayMap).flatMap((value) => isRecord2(value) ? collectProtectedOverlayValue(value, mapKey) : []);
|
|
16597
|
+
}
|
|
16598
|
+
function collectProtectedOverlayValue(value, mapKey) {
|
|
16599
|
+
return [...SECURITY_OVERLAY_FIELDS].filter((field) => Object.hasOwn(value, field)).map((field) => ({
|
|
16600
|
+
fieldPath: PROTECTED_OVERLAY_FIELD_PATHS[mapKey][field],
|
|
16601
|
+
outcome: "blocked",
|
|
16602
|
+
sourceKind: "project"
|
|
16603
|
+
}));
|
|
16509
16604
|
}
|
|
16510
16605
|
function stripProjectProtectedFields(rawConfig) {
|
|
16511
16606
|
const config2 = { ...rawConfig };
|
|
@@ -16532,16 +16627,27 @@ function loadConfig(projectDir, options) {
|
|
|
16532
16627
|
}
|
|
16533
16628
|
function loadConfigWithSources(projectDir, options) {
|
|
16534
16629
|
const includeProject = options?.includeProject ?? true;
|
|
16535
|
-
const
|
|
16536
|
-
const
|
|
16537
|
-
const
|
|
16538
|
-
const
|
|
16630
|
+
const invalidSource = options?.invalidSource ?? "throw";
|
|
16631
|
+
const paths = getConfigPaths(projectDir, options);
|
|
16632
|
+
const warningSink = options?.warningSink ?? console.warn;
|
|
16633
|
+
const user = loadConfigSource(paths.userConfig, "user", invalidSource);
|
|
16634
|
+
const project = includeProject ? loadConfigSource(paths.projectConfig, "project", invalidSource) : {
|
|
16635
|
+
metadata: { kind: "project", presence: "absent" },
|
|
16636
|
+
source: null
|
|
16637
|
+
};
|
|
16638
|
+
const custom2 = paths.customConfig ? loadConfigSource(paths.customConfig, "custom", invalidSource) : {
|
|
16639
|
+
metadata: { kind: "custom", presence: "absent" },
|
|
16640
|
+
source: null
|
|
16641
|
+
};
|
|
16642
|
+
const userSource = user.source;
|
|
16643
|
+
const projectSource = project.source;
|
|
16644
|
+
const customSource = custom2.source;
|
|
16539
16645
|
const sources = [userSource, projectSource, customSource].filter((source) => source !== null);
|
|
16540
16646
|
const mergedOverlays = mergeOverlaySources(sources);
|
|
16541
16647
|
const mergedPiSubagentsOverlays = mergePiSubagentsOverlaySources(sources);
|
|
16542
16648
|
const droppedCategories = Object.keys(mergedOverlays.categories).filter((name) => REMOVED_AGENT_CATEGORIES_SET.has(name));
|
|
16543
16649
|
const warned = new Set;
|
|
16544
|
-
warnDroppedNames(droppedCategories, "categories", warned, "v3.0.0");
|
|
16650
|
+
warnDroppedNames(droppedCategories, "categories", warned, "v3.0.0", warningSink);
|
|
16545
16651
|
const droppedCategorySet = new Set(droppedCategories);
|
|
16546
16652
|
const overlays = droppedCategorySet.size === 0 ? mergedOverlays : {
|
|
16547
16653
|
...mergedOverlays,
|
|
@@ -16574,9 +16680,9 @@ function loadConfigWithSources(projectDir, options) {
|
|
|
16574
16680
|
skills_as_commands: customConfig?.skills_as_commands ?? projectConfig?.skills_as_commands ?? userConfig?.skills_as_commands ?? DEFAULT_CONFIG.skills_as_commands
|
|
16575
16681
|
};
|
|
16576
16682
|
const droppedSkills = computeDroppedNames(result.disabled_skills, CURRENT_SKILL_NAMES_SET);
|
|
16577
|
-
warnDroppedNames(droppedSkills, "disabled_skills", warned);
|
|
16683
|
+
warnDroppedNames(droppedSkills, "disabled_skills", warned, undefined, warningSink);
|
|
16578
16684
|
const droppedAgents = computeDroppedNames(result.disabled_agents, CURRENT_AGENT_NAMES_SET);
|
|
16579
|
-
warnDroppedNames(droppedAgents, "disabled_agents", warned);
|
|
16685
|
+
warnDroppedNames(droppedAgents, "disabled_agents", warned, undefined, warningSink);
|
|
16580
16686
|
const droppedSkillSet = new Set(droppedSkills);
|
|
16581
16687
|
const droppedAgentSet = new Set(droppedAgents);
|
|
16582
16688
|
const effectiveConfig = droppedSkillSet.size === 0 && droppedAgentSet.size === 0 ? result : {
|
|
@@ -16584,7 +16690,76 @@ function loadConfigWithSources(projectDir, options) {
|
|
|
16584
16690
|
disabled_skills: result.disabled_skills.filter((n) => !droppedSkillSet.has(n)),
|
|
16585
16691
|
disabled_agents: result.disabled_agents.filter((n) => !droppedAgentSet.has(n))
|
|
16586
16692
|
};
|
|
16587
|
-
return {
|
|
16693
|
+
return {
|
|
16694
|
+
config: effectiveConfig,
|
|
16695
|
+
metadata: buildConfigObservationMetadata({
|
|
16696
|
+
custom: custom2.metadata,
|
|
16697
|
+
project: project.metadata,
|
|
16698
|
+
sources,
|
|
16699
|
+
user: user.metadata
|
|
16700
|
+
}),
|
|
16701
|
+
overlays
|
|
16702
|
+
};
|
|
16703
|
+
}
|
|
16704
|
+
function buildConfigObservationMetadata(summary) {
|
|
16705
|
+
const authorities = [];
|
|
16706
|
+
const sourceConfigs = new Map;
|
|
16707
|
+
const sourcePaths = new Map;
|
|
16708
|
+
for (const source of summary.sources) {
|
|
16709
|
+
sourceConfigs.set(source.trust, source.config);
|
|
16710
|
+
sourcePaths.set(source.trust, source.canonicalPath);
|
|
16711
|
+
}
|
|
16712
|
+
const firstDefinedSource = (fieldPath, candidates) => {
|
|
16713
|
+
for (const sourceKind of candidates) {
|
|
16714
|
+
const config2 = sourceConfigs.get(sourceKind);
|
|
16715
|
+
if (config2 && hasConfigField(config2, fieldPath)) {
|
|
16716
|
+
return { fieldPath, sourceKind };
|
|
16717
|
+
}
|
|
16718
|
+
}
|
|
16719
|
+
return;
|
|
16720
|
+
};
|
|
16721
|
+
const fieldCandidates = {
|
|
16722
|
+
"bootstrap.enabled": ["custom", "project", "user"],
|
|
16723
|
+
"bootstrap.file": ["custom", "project", "user"],
|
|
16724
|
+
skills_as_commands: ["custom", "project", "user"],
|
|
16725
|
+
"workflow_guard.debug": ["custom", "user"],
|
|
16726
|
+
"workflow_guard.mode": ["custom", "user"]
|
|
16727
|
+
};
|
|
16728
|
+
for (const fieldPath of CONFIG_AUTHORITY_FIELD_PATHS) {
|
|
16729
|
+
const authority = firstDefinedSource(fieldPath, fieldCandidates[fieldPath]);
|
|
16730
|
+
if (authority)
|
|
16731
|
+
authorities.push(authority);
|
|
16732
|
+
}
|
|
16733
|
+
const protectedFields = summary.sources.flatMap((source) => source.protectedFields);
|
|
16734
|
+
const sources = dedupeSourceMetadata([summary.custom, summary.project, summary.user], sourcePaths);
|
|
16735
|
+
return {
|
|
16736
|
+
authorities: sortAuthorities(authorities),
|
|
16737
|
+
protectedFields: sortProtectedFields(protectedFields),
|
|
16738
|
+
sources
|
|
16739
|
+
};
|
|
16740
|
+
}
|
|
16741
|
+
function dedupeSourceMetadata(metadata, sourcePaths) {
|
|
16742
|
+
const seen = new Set;
|
|
16743
|
+
return metadata.filter((source) => {
|
|
16744
|
+
const identity = sourcePaths.get(source.kind) ?? `missing:${source.kind}`;
|
|
16745
|
+
if (seen.has(identity))
|
|
16746
|
+
return false;
|
|
16747
|
+
seen.add(identity);
|
|
16748
|
+
return true;
|
|
16749
|
+
});
|
|
16750
|
+
}
|
|
16751
|
+
function hasConfigField(config2, fieldPath) {
|
|
16752
|
+
const [topLevel, nested] = fieldPath.split(".");
|
|
16753
|
+
if (nested === undefined)
|
|
16754
|
+
return config2[topLevel] !== undefined;
|
|
16755
|
+
const value = config2[topLevel];
|
|
16756
|
+
return isRecord2(value) && value[nested] !== undefined;
|
|
16757
|
+
}
|
|
16758
|
+
function sortAuthorities(authorities) {
|
|
16759
|
+
return [...authorities].sort((left, right) => left.fieldPath === right.fieldPath ? left.sourceKind.localeCompare(right.sourceKind) : left.fieldPath.localeCompare(right.fieldPath));
|
|
16760
|
+
}
|
|
16761
|
+
function sortProtectedFields(fields) {
|
|
16762
|
+
return [...fields].sort((left, right) => left.fieldPath === right.fieldPath ? left.sourceKind.localeCompare(right.sourceKind) : left.fieldPath.localeCompare(right.fieldPath));
|
|
16588
16763
|
}
|
|
16589
16764
|
function mergeOverlaySources(sources) {
|
|
16590
16765
|
const result = {
|
|
@@ -16622,7 +16797,7 @@ function mergeOverlayMap(target, source, mapKey) {
|
|
|
16622
16797
|
}
|
|
16623
16798
|
}
|
|
16624
16799
|
function rejectProjectSecurityOverlay(sourcePath, keyPath, value) {
|
|
16625
|
-
for (const field of
|
|
16800
|
+
for (const field of SECURITY_OVERLAY_FIELDS) {
|
|
16626
16801
|
if (Object.hasOwn(value, field)) {
|
|
16627
16802
|
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath}.${field} is only valid in user config or OPENCODE_CONFIG_DIR config`);
|
|
16628
16803
|
}
|
|
@@ -16630,7 +16805,7 @@ function rejectProjectSecurityOverlay(sourcePath, keyPath, value) {
|
|
|
16630
16805
|
}
|
|
16631
16806
|
function preserveSecurityFields(previous, next) {
|
|
16632
16807
|
const result = { ...next };
|
|
16633
|
-
for (const field of
|
|
16808
|
+
for (const field of SECURITY_OVERLAY_FIELDS) {
|
|
16634
16809
|
if (Object.hasOwn(previous, field)) {
|
|
16635
16810
|
result[field] = previous[field];
|
|
16636
16811
|
}
|
|
@@ -16698,11 +16873,12 @@ function overlayValues(overlays) {
|
|
|
16698
16873
|
function throwInvalidOverlay(sourcePath, keyPath) {
|
|
16699
16874
|
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath} must be an object`);
|
|
16700
16875
|
}
|
|
16701
|
-
function getConfigPaths(projectDir) {
|
|
16702
|
-
const homeDir = os.homedir();
|
|
16703
|
-
const
|
|
16876
|
+
function getConfigPaths(projectDir, options) {
|
|
16877
|
+
const homeDir = options?.homeDir ?? os.homedir();
|
|
16878
|
+
const userConfigDir = options?.userConfigDir ?? path3.join(homeDir, ".config/opencode");
|
|
16879
|
+
const customConfigDir = options !== undefined && Object.hasOwn(options, "customConfigDir") ? options.customConfigDir?.trim() : process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
16704
16880
|
const result = {
|
|
16705
|
-
userConfig: resolveConfigPath(
|
|
16881
|
+
userConfig: resolveConfigPath(userConfigDir, "systematic"),
|
|
16706
16882
|
projectConfig: resolveConfigPath(path3.join(projectDir, ".opencode"), "systematic"),
|
|
16707
16883
|
userDir: path3.join(homeDir, ".config/opencode/systematic"),
|
|
16708
16884
|
projectDir: path3.join(projectDir, ".opencode/systematic"),
|
|
@@ -16788,4 +16964,136 @@ function extractCommandFrontmatter(content) {
|
|
|
16788
16964
|
};
|
|
16789
16965
|
}
|
|
16790
16966
|
|
|
16791
|
-
|
|
16967
|
+
// src/lib/discovered-skills.ts
|
|
16968
|
+
import fs4 from "fs";
|
|
16969
|
+
import path4 from "path";
|
|
16970
|
+
var SKILL_NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
16971
|
+
function isValidSkillName(name) {
|
|
16972
|
+
return name.length >= 1 && name.length <= 64 && SKILL_NAME_REGEX.test(name);
|
|
16973
|
+
}
|
|
16974
|
+
function findGitWorktreeRoot(startDir) {
|
|
16975
|
+
let current = path4.resolve(startDir);
|
|
16976
|
+
while (true) {
|
|
16977
|
+
try {
|
|
16978
|
+
if (fs4.existsSync(path4.join(current, ".git"))) {
|
|
16979
|
+
return current;
|
|
16980
|
+
}
|
|
16981
|
+
} catch {
|
|
16982
|
+
return null;
|
|
16983
|
+
}
|
|
16984
|
+
const parent = path4.dirname(current);
|
|
16985
|
+
if (parent === current)
|
|
16986
|
+
return null;
|
|
16987
|
+
current = parent;
|
|
16988
|
+
}
|
|
16989
|
+
}
|
|
16990
|
+
function upWalk(targets, start, stop) {
|
|
16991
|
+
const results = [];
|
|
16992
|
+
let current = path4.resolve(start);
|
|
16993
|
+
const resolvedStop = stop === undefined ? undefined : path4.resolve(stop);
|
|
16994
|
+
while (true) {
|
|
16995
|
+
for (const target of targets) {
|
|
16996
|
+
const candidate = path4.join(current, target);
|
|
16997
|
+
try {
|
|
16998
|
+
if (fs4.existsSync(candidate)) {
|
|
16999
|
+
results.push(candidate);
|
|
17000
|
+
}
|
|
17001
|
+
} catch {}
|
|
17002
|
+
}
|
|
17003
|
+
if (resolvedStop !== undefined && current === resolvedStop)
|
|
17004
|
+
break;
|
|
17005
|
+
const parent = path4.dirname(current);
|
|
17006
|
+
if (parent === current)
|
|
17007
|
+
break;
|
|
17008
|
+
current = parent;
|
|
17009
|
+
}
|
|
17010
|
+
return results;
|
|
17011
|
+
}
|
|
17012
|
+
function uniqueStrings(values) {
|
|
17013
|
+
return Array.from(new Set(values));
|
|
17014
|
+
}
|
|
17015
|
+
function buildOpencodeConfigDirs(startDir, homeDir, gitRoot, globalConfigDir, opencodeConfigDirOverride) {
|
|
17016
|
+
const dirs = [globalConfigDir];
|
|
17017
|
+
dirs.push(...upWalk([".opencode"], startDir, gitRoot ?? startDir));
|
|
17018
|
+
dirs.push(...upWalk([".opencode"], homeDir, homeDir));
|
|
17019
|
+
if (opencodeConfigDirOverride !== undefined) {
|
|
17020
|
+
dirs.push(opencodeConfigDirOverride);
|
|
17021
|
+
}
|
|
17022
|
+
return uniqueStrings(dirs);
|
|
17023
|
+
}
|
|
17024
|
+
function globSkillFiles(rootDir, subdirNames) {
|
|
17025
|
+
const results = [];
|
|
17026
|
+
for (const subdirName of subdirNames) {
|
|
17027
|
+
const scanRoot = path4.join(rootDir, subdirName);
|
|
17028
|
+
try {
|
|
17029
|
+
if (!fs4.existsSync(scanRoot))
|
|
17030
|
+
continue;
|
|
17031
|
+
const entries = walkDir(scanRoot, {
|
|
17032
|
+
maxDepth: 10,
|
|
17033
|
+
filter: (entry) => entry.name === "SKILL.md"
|
|
17034
|
+
});
|
|
17035
|
+
for (const entry of entries) {
|
|
17036
|
+
results.push(entry.path);
|
|
17037
|
+
}
|
|
17038
|
+
} catch {}
|
|
17039
|
+
}
|
|
17040
|
+
return results;
|
|
17041
|
+
}
|
|
17042
|
+
function toDiscoveredSkill(skillPath, rootId, onIssue) {
|
|
17043
|
+
let content;
|
|
17044
|
+
try {
|
|
17045
|
+
content = fs4.readFileSync(skillPath, "utf8");
|
|
17046
|
+
} catch {
|
|
17047
|
+
onIssue?.("read-failed");
|
|
17048
|
+
return;
|
|
17049
|
+
}
|
|
17050
|
+
if (parseFrontmatter(content).parseError) {
|
|
17051
|
+
onIssue?.("source-invalid");
|
|
17052
|
+
return;
|
|
17053
|
+
}
|
|
17054
|
+
const frontmatter = extractFrontmatterFromContent(content);
|
|
17055
|
+
const name = frontmatter.name;
|
|
17056
|
+
if (!name || !isValidSkillName(name)) {
|
|
17057
|
+
onIssue?.("source-invalid");
|
|
17058
|
+
return;
|
|
17059
|
+
}
|
|
17060
|
+
return {
|
|
17061
|
+
name,
|
|
17062
|
+
description: frontmatter.description,
|
|
17063
|
+
frontmatter,
|
|
17064
|
+
body: parseFrontmatter(content).body,
|
|
17065
|
+
skillPath,
|
|
17066
|
+
root: rootId
|
|
17067
|
+
};
|
|
17068
|
+
}
|
|
17069
|
+
function discoverSkills(options) {
|
|
17070
|
+
const { startDir, homeDir, configDir, onIssue, opencodeConfigDirOverride } = options;
|
|
17071
|
+
const globalConfigDir = configDir ?? path4.join(homeDir, ".config/opencode");
|
|
17072
|
+
const gitRoot = findGitWorktreeRoot(startDir);
|
|
17073
|
+
const byName = new Map;
|
|
17074
|
+
function upsertAll(skillPaths, rootId) {
|
|
17075
|
+
for (const skillPath of skillPaths) {
|
|
17076
|
+
const skill = toDiscoveredSkill(skillPath, rootId, onIssue);
|
|
17077
|
+
if (skill)
|
|
17078
|
+
byName.set(skill.name, skill);
|
|
17079
|
+
}
|
|
17080
|
+
}
|
|
17081
|
+
upsertAll(globSkillFiles(homeDir, [".claude/skills"]), "global-claude");
|
|
17082
|
+
upsertAll(globSkillFiles(homeDir, [".agents/skills"]), "global-agents");
|
|
17083
|
+
const externalLevels = upWalk([".claude", ".agents"], startDir, gitRoot ?? startDir);
|
|
17084
|
+
for (const levelDir of externalLevels) {
|
|
17085
|
+
const isClaudeDir = path4.basename(levelDir) === ".claude";
|
|
17086
|
+
const parentDir = path4.dirname(levelDir);
|
|
17087
|
+
const subdirGlob = isClaudeDir ? ".claude/skills" : ".agents/skills";
|
|
17088
|
+
const rootId = isClaudeDir ? "project-claude" : "project-agents";
|
|
17089
|
+
upsertAll(globSkillFiles(parentDir, [subdirGlob]), rootId);
|
|
17090
|
+
}
|
|
17091
|
+
const configDirs = buildOpencodeConfigDirs(startDir, homeDir, gitRoot, globalConfigDir, opencodeConfigDirOverride);
|
|
17092
|
+
for (const dir of configDirs) {
|
|
17093
|
+
const rootId = dir === globalConfigDir ? "global-opencode-config" : "project-opencode";
|
|
17094
|
+
upsertAll(globSkillFiles(dir, ["skill", "skills"]), rootId);
|
|
17095
|
+
}
|
|
17096
|
+
return Array.from(byName.values());
|
|
17097
|
+
}
|
|
17098
|
+
|
|
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 };
|