opencode-hive 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -222,12 +222,23 @@ Use `autoLoadSkills` to automatically inject skills into an agent's system promp
222
222
  }
223
223
  ```
224
224
 
225
+ **Supported skill sources:**
226
+
227
+ `autoLoadSkills` accepts both Hive builtin skill IDs and file-based skill IDs. Resolution order:
228
+
229
+ 1. **Hive builtin** — Skills bundled with opencode-hive (always win if ID matches)
230
+ 2. **Project OpenCode** — `<project>/.opencode/skills/<id>/SKILL.md`
231
+ 3. **Global OpenCode** — `~/.config/opencode/skills/<id>/SKILL.md`
232
+ 4. **Project Claude** — `<project>/.claude/skills/<id>/SKILL.md`
233
+ 5. **Global Claude** — `~/.claude/skills/<id>/SKILL.md`
234
+
235
+ Skill IDs must be safe directory names (no `/`, `\`, `..`, or `.`). Missing or invalid skills emit a warning and are skipped—startup continues without failure.
236
+
225
237
  **How `skills` and `autoLoadSkills` interact:**
226
238
 
227
239
  - `skills` controls what appears in `hive_skill()` — the agent can manually load these on demand
228
240
  - `autoLoadSkills` injects skills unconditionally at session start — no manual loading needed
229
241
  - These are **independent**: a skill can be auto-loaded but not appear in `hive_skill()`, or vice versa
230
- - Both only support Hive's built-in skills (not OpenCode base skills from the `skill()` tool)
231
242
  - User `autoLoadSkills` are **merged** with defaults (use global `disableSkills` to remove defaults)
232
243
 
233
244
  **Default auto-load skills by agent:**
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
14
14
  // src/index.ts
15
15
  import * as path7 from "path";
16
16
  import * as fs9 from "fs";
17
+ import * as os from "os";
17
18
 
18
19
  // ../../node_modules/zod/v4/classic/external.js
19
20
  var exports_external = {};
@@ -14140,6 +14141,107 @@ function getFilteredSkills(disabledSkills = [], agentSkills) {
14140
14141
  return filtered;
14141
14142
  }
14142
14143
 
14144
+ // src/skills/file-loader.ts
14145
+ import * as fs from "node:fs/promises";
14146
+ import * as path from "node:path";
14147
+ function validateSkillId(skillId) {
14148
+ if (!skillId || skillId.trim() === "") {
14149
+ return "Skill ID cannot be empty";
14150
+ }
14151
+ if (skillId.includes("/")) {
14152
+ return `Invalid skill ID "${skillId}": contains path traversal character "/"`;
14153
+ }
14154
+ if (skillId.includes("\\")) {
14155
+ return `Invalid skill ID "${skillId}": contains path traversal character "\\"`;
14156
+ }
14157
+ if (skillId.includes("..")) {
14158
+ return `Invalid skill ID "${skillId}": contains path traversal sequence ".."`;
14159
+ }
14160
+ if (skillId === "." || skillId.includes(".")) {
14161
+ return `Invalid skill ID "${skillId}": contains path traversal character "."`;
14162
+ }
14163
+ return;
14164
+ }
14165
+ function stripQuotes(value) {
14166
+ const trimmed = value.trim();
14167
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
14168
+ return trimmed.slice(1, -1);
14169
+ }
14170
+ return trimmed;
14171
+ }
14172
+ function parseFrontmatter(content) {
14173
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
14174
+ if (!match)
14175
+ return null;
14176
+ const frontmatter = match[1];
14177
+ const body = match[2];
14178
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
14179
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
14180
+ if (!nameMatch || !descMatch)
14181
+ return null;
14182
+ return {
14183
+ name: stripQuotes(nameMatch[1]),
14184
+ description: stripQuotes(descMatch[1]),
14185
+ body
14186
+ };
14187
+ }
14188
+ function getSearchPaths(skillId, projectRoot, homeDir) {
14189
+ return [
14190
+ path.join(projectRoot, ".opencode", "skills", skillId, "SKILL.md"),
14191
+ path.join(homeDir, ".config", "opencode", "skills", skillId, "SKILL.md"),
14192
+ path.join(projectRoot, ".claude", "skills", skillId, "SKILL.md"),
14193
+ path.join(homeDir, ".claude", "skills", skillId, "SKILL.md")
14194
+ ];
14195
+ }
14196
+ async function tryReadFile(filePath) {
14197
+ try {
14198
+ const content = await fs.readFile(filePath, "utf-8");
14199
+ return content;
14200
+ } catch {
14201
+ return null;
14202
+ }
14203
+ }
14204
+ async function loadFileSkill(skillId, projectRoot, homeDir) {
14205
+ const validationError = validateSkillId(skillId);
14206
+ if (validationError) {
14207
+ return { found: false, error: validationError };
14208
+ }
14209
+ const searchPaths = getSearchPaths(skillId, projectRoot, homeDir);
14210
+ for (const skillPath of searchPaths) {
14211
+ const content = await tryReadFile(skillPath);
14212
+ if (content === null) {
14213
+ continue;
14214
+ }
14215
+ const parsed = parseFrontmatter(content);
14216
+ if (!parsed) {
14217
+ return {
14218
+ found: false,
14219
+ error: `Invalid frontmatter in skill file: ${skillPath}. Expected YAML frontmatter with "name" and "description" fields.`
14220
+ };
14221
+ }
14222
+ if (parsed.name !== skillId) {
14223
+ return {
14224
+ found: false,
14225
+ error: `Skill name mismatch in ${skillPath}: frontmatter name "${parsed.name}" does not match skill ID "${skillId}"`
14226
+ };
14227
+ }
14228
+ const skill = {
14229
+ name: parsed.name,
14230
+ description: parsed.description,
14231
+ template: parsed.body
14232
+ };
14233
+ return {
14234
+ found: true,
14235
+ skill,
14236
+ source: skillPath
14237
+ };
14238
+ }
14239
+ return {
14240
+ found: false,
14241
+ error: `Skill "${skillId}" not found in any of the search paths`
14242
+ };
14243
+ }
14244
+
14143
14245
  // src/agents/hive.ts
14144
14246
  var QUEEN_BEE_PROMPT = `# Hive (Hybrid)
14145
14247
 
@@ -14875,10 +14977,10 @@ var createBuiltinMcps = (disabledMcps = []) => {
14875
14977
 
14876
14978
  // ../hive-core/dist/index.js
14877
14979
  import { createRequire as createRequire2 } from "node:module";
14878
- import * as path from "path";
14879
- import * as fs from "fs";
14880
14980
  import * as path2 from "path";
14881
14981
  import * as fs2 from "fs";
14982
+ import * as path22 from "path";
14983
+ import * as fs22 from "fs";
14882
14984
  import * as fs3 from "fs";
14883
14985
  import * as fs4 from "fs";
14884
14986
  import * as fs5 from "fs";
@@ -15784,82 +15886,85 @@ var STATUS_FILE = "status.json";
15784
15886
  var REPORT_FILE = "report.md";
15785
15887
  var APPROVED_FILE = "APPROVED";
15786
15888
  var JOURNAL_FILE = "journal.md";
15889
+ function normalizePath(filePath) {
15890
+ return filePath.replace(/\\/g, "/");
15891
+ }
15787
15892
  function getHivePath(projectRoot) {
15788
- return path.join(projectRoot, HIVE_DIR);
15893
+ return path2.join(projectRoot, HIVE_DIR);
15789
15894
  }
15790
15895
  function getJournalPath(projectRoot) {
15791
- return path.join(getHivePath(projectRoot), JOURNAL_FILE);
15896
+ return path2.join(getHivePath(projectRoot), JOURNAL_FILE);
15792
15897
  }
15793
15898
  function getFeaturesPath(projectRoot) {
15794
- return path.join(getHivePath(projectRoot), FEATURES_DIR);
15899
+ return path2.join(getHivePath(projectRoot), FEATURES_DIR);
15795
15900
  }
15796
15901
  function getFeaturePath(projectRoot, featureName) {
15797
- return path.join(getFeaturesPath(projectRoot), featureName);
15902
+ return path2.join(getFeaturesPath(projectRoot), featureName);
15798
15903
  }
15799
15904
  function getPlanPath(projectRoot, featureName) {
15800
- return path.join(getFeaturePath(projectRoot, featureName), PLAN_FILE);
15905
+ return path2.join(getFeaturePath(projectRoot, featureName), PLAN_FILE);
15801
15906
  }
15802
15907
  function getCommentsPath(projectRoot, featureName) {
15803
- return path.join(getFeaturePath(projectRoot, featureName), COMMENTS_FILE);
15908
+ return path2.join(getFeaturePath(projectRoot, featureName), COMMENTS_FILE);
15804
15909
  }
15805
15910
  function getFeatureJsonPath(projectRoot, featureName) {
15806
- return path.join(getFeaturePath(projectRoot, featureName), FEATURE_FILE);
15911
+ return path2.join(getFeaturePath(projectRoot, featureName), FEATURE_FILE);
15807
15912
  }
15808
15913
  function getContextPath(projectRoot, featureName) {
15809
- return path.join(getFeaturePath(projectRoot, featureName), CONTEXT_DIR);
15914
+ return path2.join(getFeaturePath(projectRoot, featureName), CONTEXT_DIR);
15810
15915
  }
15811
15916
  function getTasksPath(projectRoot, featureName) {
15812
- return path.join(getFeaturePath(projectRoot, featureName), TASKS_DIR);
15917
+ return path2.join(getFeaturePath(projectRoot, featureName), TASKS_DIR);
15813
15918
  }
15814
15919
  function getTaskPath(projectRoot, featureName, taskFolder) {
15815
- return path.join(getTasksPath(projectRoot, featureName), taskFolder);
15920
+ return path2.join(getTasksPath(projectRoot, featureName), taskFolder);
15816
15921
  }
15817
15922
  function getTaskStatusPath(projectRoot, featureName, taskFolder) {
15818
- return path.join(getTaskPath(projectRoot, featureName, taskFolder), STATUS_FILE);
15923
+ return path2.join(getTaskPath(projectRoot, featureName, taskFolder), STATUS_FILE);
15819
15924
  }
15820
15925
  function getTaskReportPath(projectRoot, featureName, taskFolder) {
15821
- return path.join(getTaskPath(projectRoot, featureName, taskFolder), REPORT_FILE);
15926
+ return path2.join(getTaskPath(projectRoot, featureName, taskFolder), REPORT_FILE);
15822
15927
  }
15823
15928
  function getTaskSpecPath(projectRoot, featureName, taskFolder) {
15824
- return path.join(getTaskPath(projectRoot, featureName, taskFolder), "spec.md");
15929
+ return path2.join(getTaskPath(projectRoot, featureName, taskFolder), "spec.md");
15825
15930
  }
15826
15931
  function getApprovedPath(projectRoot, featureName) {
15827
- return path.join(getFeaturePath(projectRoot, featureName), APPROVED_FILE);
15932
+ return path2.join(getFeaturePath(projectRoot, featureName), APPROVED_FILE);
15828
15933
  }
15829
15934
  var SUBTASKS_DIR = "subtasks";
15830
15935
  var SPEC_FILE = "spec.md";
15831
15936
  function getSubtasksPath(projectRoot, featureName, taskFolder) {
15832
- return path.join(getTaskPath(projectRoot, featureName, taskFolder), SUBTASKS_DIR);
15937
+ return path2.join(getTaskPath(projectRoot, featureName, taskFolder), SUBTASKS_DIR);
15833
15938
  }
15834
15939
  function getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder) {
15835
- return path.join(getSubtasksPath(projectRoot, featureName, taskFolder), subtaskFolder);
15940
+ return path2.join(getSubtasksPath(projectRoot, featureName, taskFolder), subtaskFolder);
15836
15941
  }
15837
15942
  function getSubtaskStatusPath(projectRoot, featureName, taskFolder, subtaskFolder) {
15838
- return path.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), STATUS_FILE);
15943
+ return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), STATUS_FILE);
15839
15944
  }
15840
15945
  function getSubtaskSpecPath(projectRoot, featureName, taskFolder, subtaskFolder) {
15841
- return path.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), SPEC_FILE);
15946
+ return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), SPEC_FILE);
15842
15947
  }
15843
15948
  function getSubtaskReportPath(projectRoot, featureName, taskFolder, subtaskFolder) {
15844
- return path.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), REPORT_FILE);
15949
+ return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), REPORT_FILE);
15845
15950
  }
15846
15951
  function ensureDir(dirPath) {
15847
- if (!fs.existsSync(dirPath)) {
15848
- fs.mkdirSync(dirPath, { recursive: true });
15952
+ if (!fs2.existsSync(dirPath)) {
15953
+ fs2.mkdirSync(dirPath, { recursive: true });
15849
15954
  }
15850
15955
  }
15851
15956
  function fileExists(filePath) {
15852
- return fs.existsSync(filePath);
15957
+ return fs2.existsSync(filePath);
15853
15958
  }
15854
15959
  function readJson(filePath) {
15855
- if (!fs.existsSync(filePath))
15960
+ if (!fs2.existsSync(filePath))
15856
15961
  return null;
15857
- const content = fs.readFileSync(filePath, "utf-8");
15962
+ const content = fs2.readFileSync(filePath, "utf-8");
15858
15963
  return JSON.parse(content);
15859
15964
  }
15860
15965
  function writeJson(filePath, data) {
15861
- ensureDir(path.dirname(filePath));
15862
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
15966
+ ensureDir(path2.dirname(filePath));
15967
+ fs2.writeFileSync(filePath, JSON.stringify(data, null, 2));
15863
15968
  }
15864
15969
  var DEFAULT_LOCK_OPTIONS = {
15865
15970
  timeout: 5000,
@@ -15871,7 +15976,7 @@ function getLockPath(filePath) {
15871
15976
  }
15872
15977
  function isLockStale(lockPath, staleTTL) {
15873
15978
  try {
15874
- const stat2 = fs.statSync(lockPath);
15979
+ const stat2 = fs2.statSync(lockPath);
15875
15980
  const age = Date.now() - stat2.mtimeMs;
15876
15981
  return age > staleTTL;
15877
15982
  } catch {
@@ -15889,12 +15994,12 @@ function acquireLockSync(filePath, options = {}) {
15889
15994
  });
15890
15995
  while (true) {
15891
15996
  try {
15892
- const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
15893
- fs.writeSync(fd, lockContent);
15894
- fs.closeSync(fd);
15997
+ const fd = fs2.openSync(lockPath, fs2.constants.O_CREAT | fs2.constants.O_EXCL | fs2.constants.O_WRONLY);
15998
+ fs2.writeSync(fd, lockContent);
15999
+ fs2.closeSync(fd);
15895
16000
  return () => {
15896
16001
  try {
15897
- fs.unlinkSync(lockPath);
16002
+ fs2.unlinkSync(lockPath);
15898
16003
  } catch {}
15899
16004
  };
15900
16005
  } catch (err) {
@@ -15904,7 +16009,7 @@ function acquireLockSync(filePath, options = {}) {
15904
16009
  }
15905
16010
  if (isLockStale(lockPath, opts.staleLockTTL)) {
15906
16011
  try {
15907
- fs.unlinkSync(lockPath);
16012
+ fs2.unlinkSync(lockPath);
15908
16013
  continue;
15909
16014
  } catch {}
15910
16015
  }
@@ -15917,14 +16022,14 @@ function acquireLockSync(filePath, options = {}) {
15917
16022
  }
15918
16023
  }
15919
16024
  function writeAtomic(filePath, content) {
15920
- ensureDir(path.dirname(filePath));
16025
+ ensureDir(path2.dirname(filePath));
15921
16026
  const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
15922
16027
  try {
15923
- fs.writeFileSync(tempPath, content);
15924
- fs.renameSync(tempPath, filePath);
16028
+ fs2.writeFileSync(tempPath, content);
16029
+ fs2.renameSync(tempPath, filePath);
15925
16030
  } catch (error45) {
15926
16031
  try {
15927
- fs.unlinkSync(tempPath);
16032
+ fs2.unlinkSync(tempPath);
15928
16033
  } catch {}
15929
16034
  throw error45;
15930
16035
  }
@@ -15967,13 +16072,13 @@ function patchJsonLockedSync(filePath, patch, options = {}) {
15967
16072
  }
15968
16073
  }
15969
16074
  function readText(filePath) {
15970
- if (!fs.existsSync(filePath))
16075
+ if (!fs2.existsSync(filePath))
15971
16076
  return null;
15972
- return fs.readFileSync(filePath, "utf-8");
16077
+ return fs2.readFileSync(filePath, "utf-8");
15973
16078
  }
15974
16079
  function writeText(filePath, content) {
15975
- ensureDir(path.dirname(filePath));
15976
- fs.writeFileSync(filePath, content);
16080
+ ensureDir(path2.dirname(filePath));
16081
+ fs2.writeFileSync(filePath, content);
15977
16082
  }
15978
16083
  function detectContext(cwd) {
15979
16084
  const result = {
@@ -15983,7 +16088,8 @@ function detectContext(cwd) {
15983
16088
  isWorktree: false,
15984
16089
  mainProjectRoot: null
15985
16090
  };
15986
- const worktreeMatch = cwd.match(/(.+)\/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
16091
+ const normalizedCwd = normalizePath(cwd);
16092
+ const worktreeMatch = normalizedCwd.match(/(.+)\/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
15987
16093
  if (worktreeMatch) {
15988
16094
  result.mainProjectRoot = worktreeMatch[1];
15989
16095
  result.feature = worktreeMatch[2];
@@ -15992,18 +16098,19 @@ function detectContext(cwd) {
15992
16098
  result.projectRoot = worktreeMatch[1];
15993
16099
  return result;
15994
16100
  }
15995
- const gitPath = path2.join(cwd, ".git");
15996
- if (fs2.existsSync(gitPath)) {
15997
- const stat2 = fs2.statSync(gitPath);
16101
+ const gitPath = path22.join(cwd, ".git");
16102
+ if (fs22.existsSync(gitPath)) {
16103
+ const stat2 = fs22.statSync(gitPath);
15998
16104
  if (stat2.isFile()) {
15999
- const gitContent = fs2.readFileSync(gitPath, "utf-8").trim();
16105
+ const gitContent = fs22.readFileSync(gitPath, "utf-8").trim();
16000
16106
  const gitdirMatch = gitContent.match(/gitdir:\s*(.+)/);
16001
16107
  if (gitdirMatch) {
16002
16108
  const gitdir = gitdirMatch[1];
16003
- const worktreePathMatch = gitdir.match(/(.+)\/\.git\/worktrees\/(.+)/);
16109
+ const normalizedGitdir = normalizePath(gitdir);
16110
+ const worktreePathMatch = normalizedGitdir.match(/(.+)\/\.git\/worktrees\/(.+)/);
16004
16111
  if (worktreePathMatch) {
16005
16112
  const mainRepo = worktreePathMatch[1];
16006
- const cwdWorktreeMatch = cwd.match(/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
16113
+ const cwdWorktreeMatch = normalizedCwd.match(/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
16007
16114
  if (cwdWorktreeMatch) {
16008
16115
  result.mainProjectRoot = mainRepo;
16009
16116
  result.feature = cwdWorktreeMatch[1];
@@ -16020,9 +16127,9 @@ function detectContext(cwd) {
16020
16127
  }
16021
16128
  function listFeatures(projectRoot) {
16022
16129
  const featuresPath = getFeaturesPath(projectRoot);
16023
- if (!fs2.existsSync(featuresPath))
16130
+ if (!fs22.existsSync(featuresPath))
16024
16131
  return [];
16025
- return fs2.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
16132
+ return fs22.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
16026
16133
  }
16027
16134
  var JOURNAL_TEMPLATE = `# Hive Journal
16028
16135
 
@@ -21682,7 +21789,13 @@ function isValidPromptFilePath(filePath, workspaceRoot) {
21682
21789
  try {
21683
21790
  const normalizedFilePath = path5.resolve(filePath);
21684
21791
  const normalizedWorkspace = path5.resolve(workspaceRoot);
21685
- if (!normalizedFilePath.startsWith(normalizedWorkspace + path5.sep) && normalizedFilePath !== normalizedWorkspace) {
21792
+ let normalizedFilePathForCompare = normalizePath(normalizedFilePath);
21793
+ let normalizedWorkspaceForCompare = normalizePath(normalizedWorkspace);
21794
+ if (process.platform === "win32") {
21795
+ normalizedFilePathForCompare = normalizedFilePathForCompare.toLowerCase();
21796
+ normalizedWorkspaceForCompare = normalizedWorkspaceForCompare.toLowerCase();
21797
+ }
21798
+ if (!normalizedFilePathForCompare.startsWith(normalizedWorkspaceForCompare + "/") && normalizedFilePathForCompare !== normalizedWorkspaceForCompare) {
21686
21799
  return false;
21687
21800
  }
21688
21801
  return true;
@@ -23103,20 +23216,26 @@ function formatSkillsXml(skills) {
23103
23216
  ${skillsXml}
23104
23217
  </available_skills>`;
23105
23218
  }
23106
- function buildAutoLoadedSkillsContent(agentName, configService) {
23219
+ async function buildAutoLoadedSkillsContent(agentName, configService, projectRoot) {
23107
23220
  const agentConfig = configService.getAgentConfig(agentName);
23108
23221
  const autoLoadSkills = agentConfig.autoLoadSkills ?? [];
23109
23222
  if (autoLoadSkills.length === 0) {
23110
23223
  return "";
23111
23224
  }
23225
+ const homeDir = process.env.HOME || os.homedir();
23112
23226
  const skillTemplates = [];
23113
23227
  for (const skillId of autoLoadSkills) {
23114
- const skill = BUILTIN_SKILLS.find((entry) => entry.name === skillId);
23115
- if (!skill) {
23116
- console.warn(`[hive] Unknown skill id "${skillId}" for agent "${agentName}"`);
23228
+ const builtinSkill = BUILTIN_SKILLS.find((entry) => entry.name === skillId);
23229
+ if (builtinSkill) {
23230
+ skillTemplates.push(builtinSkill.template);
23231
+ continue;
23232
+ }
23233
+ const fileResult = await loadFileSkill(skillId, projectRoot, homeDir);
23234
+ if (fileResult.found && fileResult.skill) {
23235
+ skillTemplates.push(fileResult.skill.template);
23117
23236
  continue;
23118
23237
  }
23119
- skillTemplates.push(skill.template);
23238
+ console.warn(`[hive] Unknown skill id "${skillId}" for agent "${agentName}"`);
23120
23239
  }
23121
23240
  if (skillTemplates.length === 0) {
23122
23241
  return "";
@@ -23705,7 +23824,7 @@ ${priorTasksFormatted}
23705
23824
  });
23706
23825
  const hiveDir = path7.join(directory, ".hive");
23707
23826
  const workerPromptPath = writeWorkerPromptFile(feature, task, workerPrompt, hiveDir);
23708
- const relativePromptPath = path7.relative(directory, workerPromptPath);
23827
+ const relativePromptPath = normalizePath(path7.relative(directory, workerPromptPath));
23709
23828
  const PREVIEW_MAX_LENGTH = 200;
23710
23829
  const workerPromptPreview = workerPrompt.length > PREVIEW_MAX_LENGTH ? workerPrompt.slice(0, PREVIEW_MAX_LENGTH) + "..." : workerPrompt;
23711
23830
  const hiveBackgroundInstructions = `## Delegation Required
@@ -24262,7 +24381,7 @@ Make the requested changes, then call hive_request_review again.`;
24262
24381
  config: async (opencodeConfig) => {
24263
24382
  configService.init();
24264
24383
  const hiveUserConfig = configService.getAgentConfig("hive-master");
24265
- const hiveAutoLoadedSkills = buildAutoLoadedSkillsContent("hive-master", configService);
24384
+ const hiveAutoLoadedSkills = await buildAutoLoadedSkillsContent("hive-master", configService, directory);
24266
24385
  const hiveConfig = {
24267
24386
  model: hiveUserConfig.model,
24268
24387
  temperature: hiveUserConfig.temperature ?? 0.5,
@@ -24279,7 +24398,7 @@ Make the requested changes, then call hive_request_review again.`;
24279
24398
  }
24280
24399
  };
24281
24400
  const architectUserConfig = configService.getAgentConfig("architect-planner");
24282
- const architectAutoLoadedSkills = buildAutoLoadedSkillsContent("architect-planner", configService);
24401
+ const architectAutoLoadedSkills = await buildAutoLoadedSkillsContent("architect-planner", configService, directory);
24283
24402
  const architectConfig = {
24284
24403
  model: architectUserConfig.model,
24285
24404
  temperature: architectUserConfig.temperature ?? 0.7,
@@ -24299,7 +24418,7 @@ Make the requested changes, then call hive_request_review again.`;
24299
24418
  }
24300
24419
  };
24301
24420
  const swarmUserConfig = configService.getAgentConfig("swarm-orchestrator");
24302
- const swarmAutoLoadedSkills = buildAutoLoadedSkillsContent("swarm-orchestrator", configService);
24421
+ const swarmAutoLoadedSkills = await buildAutoLoadedSkillsContent("swarm-orchestrator", configService, directory);
24303
24422
  const swarmConfig = {
24304
24423
  model: swarmUserConfig.model,
24305
24424
  temperature: swarmUserConfig.temperature ?? 0.5,
@@ -24316,7 +24435,7 @@ Make the requested changes, then call hive_request_review again.`;
24316
24435
  }
24317
24436
  };
24318
24437
  const scoutUserConfig = configService.getAgentConfig("scout-researcher");
24319
- const scoutAutoLoadedSkills = buildAutoLoadedSkillsContent("scout-researcher", configService);
24438
+ const scoutAutoLoadedSkills = await buildAutoLoadedSkillsContent("scout-researcher", configService, directory);
24320
24439
  const scoutConfig = {
24321
24440
  model: scoutUserConfig.model,
24322
24441
  temperature: scoutUserConfig.temperature ?? 0.5,
@@ -24330,7 +24449,7 @@ Make the requested changes, then call hive_request_review again.`;
24330
24449
  }
24331
24450
  };
24332
24451
  const foragerUserConfig = configService.getAgentConfig("forager-worker");
24333
- const foragerAutoLoadedSkills = buildAutoLoadedSkillsContent("forager-worker", configService);
24452
+ const foragerAutoLoadedSkills = await buildAutoLoadedSkillsContent("forager-worker", configService, directory);
24334
24453
  const foragerConfig = {
24335
24454
  model: foragerUserConfig.model,
24336
24455
  temperature: foragerUserConfig.temperature ?? 0.3,
@@ -24342,7 +24461,7 @@ Make the requested changes, then call hive_request_review again.`;
24342
24461
  }
24343
24462
  };
24344
24463
  const hygienicUserConfig = configService.getAgentConfig("hygienic-reviewer");
24345
- const hygienicAutoLoadedSkills = buildAutoLoadedSkillsContent("hygienic-reviewer", configService);
24464
+ const hygienicAutoLoadedSkills = await buildAutoLoadedSkillsContent("hygienic-reviewer", configService, directory);
24346
24465
  const hygienicConfig = {
24347
24466
  model: hygienicUserConfig.model,
24348
24467
  temperature: hygienicUserConfig.temperature ?? 0.3,
@@ -0,0 +1,22 @@
1
+ /**
2
+ * File-based Skill Loader
3
+ *
4
+ * Resolves and loads skill files from OpenCode and Claude-compatible paths.
5
+ * Implements strict skill ID validation and deterministic search order.
6
+ */
7
+ import type { SkillLoadResult } from './types.js';
8
+ /**
9
+ * Load a skill from file-based locations.
10
+ *
11
+ * Searches for skill files in the following order:
12
+ * 1. Project OpenCode: `<projectRoot>/.opencode/skills/<skillId>/SKILL.md`
13
+ * 2. Global OpenCode: `~/.config/opencode/skills/<skillId>/SKILL.md`
14
+ * 3. Project Claude-compatible: `<projectRoot>/.claude/skills/<skillId>/SKILL.md`
15
+ * 4. Global Claude-compatible: `~/.claude/skills/<skillId>/SKILL.md`
16
+ *
17
+ * @param skillId - The skill ID to load
18
+ * @param projectRoot - The project root directory
19
+ * @param homeDir - The user's home directory
20
+ * @returns The skill load result
21
+ */
22
+ export declare function loadFileSkill(skillId: string, projectRoot: string, homeDir: string): Promise<SkillLoadResult>;
@@ -5,3 +5,4 @@
5
5
  */
6
6
  export type { SkillDefinition, SkillLoadResult } from './types.js';
7
7
  export { BUILTIN_SKILLS, loadBuiltinSkill, getBuiltinSkills, getFilteredSkills, getBuiltinSkillsXml, type BuiltinSkillName } from './builtin.js';
8
+ export { loadFileSkill } from './file-loader.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-hive",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent Hive - from vibe coding to hive coding",
6
6
  "license": "MIT WITH Commons-Clause",