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 +12 -1
- package/dist/index.js +183 -64
- package/dist/skills/file-loader.d.ts +22 -0
- package/dist/skills/index.d.ts +1 -0
- package/package.json +1 -1
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
|
|
15893
|
+
return path2.join(projectRoot, HIVE_DIR);
|
|
15789
15894
|
}
|
|
15790
15895
|
function getJournalPath(projectRoot) {
|
|
15791
|
-
return
|
|
15896
|
+
return path2.join(getHivePath(projectRoot), JOURNAL_FILE);
|
|
15792
15897
|
}
|
|
15793
15898
|
function getFeaturesPath(projectRoot) {
|
|
15794
|
-
return
|
|
15899
|
+
return path2.join(getHivePath(projectRoot), FEATURES_DIR);
|
|
15795
15900
|
}
|
|
15796
15901
|
function getFeaturePath(projectRoot, featureName) {
|
|
15797
|
-
return
|
|
15902
|
+
return path2.join(getFeaturesPath(projectRoot), featureName);
|
|
15798
15903
|
}
|
|
15799
15904
|
function getPlanPath(projectRoot, featureName) {
|
|
15800
|
-
return
|
|
15905
|
+
return path2.join(getFeaturePath(projectRoot, featureName), PLAN_FILE);
|
|
15801
15906
|
}
|
|
15802
15907
|
function getCommentsPath(projectRoot, featureName) {
|
|
15803
|
-
return
|
|
15908
|
+
return path2.join(getFeaturePath(projectRoot, featureName), COMMENTS_FILE);
|
|
15804
15909
|
}
|
|
15805
15910
|
function getFeatureJsonPath(projectRoot, featureName) {
|
|
15806
|
-
return
|
|
15911
|
+
return path2.join(getFeaturePath(projectRoot, featureName), FEATURE_FILE);
|
|
15807
15912
|
}
|
|
15808
15913
|
function getContextPath(projectRoot, featureName) {
|
|
15809
|
-
return
|
|
15914
|
+
return path2.join(getFeaturePath(projectRoot, featureName), CONTEXT_DIR);
|
|
15810
15915
|
}
|
|
15811
15916
|
function getTasksPath(projectRoot, featureName) {
|
|
15812
|
-
return
|
|
15917
|
+
return path2.join(getFeaturePath(projectRoot, featureName), TASKS_DIR);
|
|
15813
15918
|
}
|
|
15814
15919
|
function getTaskPath(projectRoot, featureName, taskFolder) {
|
|
15815
|
-
return
|
|
15920
|
+
return path2.join(getTasksPath(projectRoot, featureName), taskFolder);
|
|
15816
15921
|
}
|
|
15817
15922
|
function getTaskStatusPath(projectRoot, featureName, taskFolder) {
|
|
15818
|
-
return
|
|
15923
|
+
return path2.join(getTaskPath(projectRoot, featureName, taskFolder), STATUS_FILE);
|
|
15819
15924
|
}
|
|
15820
15925
|
function getTaskReportPath(projectRoot, featureName, taskFolder) {
|
|
15821
|
-
return
|
|
15926
|
+
return path2.join(getTaskPath(projectRoot, featureName, taskFolder), REPORT_FILE);
|
|
15822
15927
|
}
|
|
15823
15928
|
function getTaskSpecPath(projectRoot, featureName, taskFolder) {
|
|
15824
|
-
return
|
|
15929
|
+
return path2.join(getTaskPath(projectRoot, featureName, taskFolder), "spec.md");
|
|
15825
15930
|
}
|
|
15826
15931
|
function getApprovedPath(projectRoot, featureName) {
|
|
15827
|
-
return
|
|
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
|
|
15937
|
+
return path2.join(getTaskPath(projectRoot, featureName, taskFolder), SUBTASKS_DIR);
|
|
15833
15938
|
}
|
|
15834
15939
|
function getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder) {
|
|
15835
|
-
return
|
|
15940
|
+
return path2.join(getSubtasksPath(projectRoot, featureName, taskFolder), subtaskFolder);
|
|
15836
15941
|
}
|
|
15837
15942
|
function getSubtaskStatusPath(projectRoot, featureName, taskFolder, subtaskFolder) {
|
|
15838
|
-
return
|
|
15943
|
+
return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), STATUS_FILE);
|
|
15839
15944
|
}
|
|
15840
15945
|
function getSubtaskSpecPath(projectRoot, featureName, taskFolder, subtaskFolder) {
|
|
15841
|
-
return
|
|
15946
|
+
return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), SPEC_FILE);
|
|
15842
15947
|
}
|
|
15843
15948
|
function getSubtaskReportPath(projectRoot, featureName, taskFolder, subtaskFolder) {
|
|
15844
|
-
return
|
|
15949
|
+
return path2.join(getSubtaskPath(projectRoot, featureName, taskFolder, subtaskFolder), REPORT_FILE);
|
|
15845
15950
|
}
|
|
15846
15951
|
function ensureDir(dirPath) {
|
|
15847
|
-
if (!
|
|
15848
|
-
|
|
15952
|
+
if (!fs2.existsSync(dirPath)) {
|
|
15953
|
+
fs2.mkdirSync(dirPath, { recursive: true });
|
|
15849
15954
|
}
|
|
15850
15955
|
}
|
|
15851
15956
|
function fileExists(filePath) {
|
|
15852
|
-
return
|
|
15957
|
+
return fs2.existsSync(filePath);
|
|
15853
15958
|
}
|
|
15854
15959
|
function readJson(filePath) {
|
|
15855
|
-
if (!
|
|
15960
|
+
if (!fs2.existsSync(filePath))
|
|
15856
15961
|
return null;
|
|
15857
|
-
const content =
|
|
15962
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
15858
15963
|
return JSON.parse(content);
|
|
15859
15964
|
}
|
|
15860
15965
|
function writeJson(filePath, data) {
|
|
15861
|
-
ensureDir(
|
|
15862
|
-
|
|
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 =
|
|
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 =
|
|
15893
|
-
|
|
15894
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
16025
|
+
ensureDir(path2.dirname(filePath));
|
|
15921
16026
|
const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
15922
16027
|
try {
|
|
15923
|
-
|
|
15924
|
-
|
|
16028
|
+
fs2.writeFileSync(tempPath, content);
|
|
16029
|
+
fs2.renameSync(tempPath, filePath);
|
|
15925
16030
|
} catch (error45) {
|
|
15926
16031
|
try {
|
|
15927
|
-
|
|
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 (!
|
|
16075
|
+
if (!fs2.existsSync(filePath))
|
|
15971
16076
|
return null;
|
|
15972
|
-
return
|
|
16077
|
+
return fs2.readFileSync(filePath, "utf-8");
|
|
15973
16078
|
}
|
|
15974
16079
|
function writeText(filePath, content) {
|
|
15975
|
-
ensureDir(
|
|
15976
|
-
|
|
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
|
|
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 =
|
|
15996
|
-
if (
|
|
15997
|
-
const stat2 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (!
|
|
16130
|
+
if (!fs22.existsSync(featuresPath))
|
|
16024
16131
|
return [];
|
|
16025
|
-
return
|
|
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
|
-
|
|
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
|
|
23115
|
-
if (
|
|
23116
|
-
|
|
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
|
-
|
|
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>;
|
package/dist/skills/index.d.ts
CHANGED