opencode-hive 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __export = (target, all) => {
3
4
  for (var name in all)
@@ -8,9 +9,12 @@ var __export = (target, all) => {
8
9
  set: (newValue) => all[name] = () => newValue
9
10
  });
10
11
  };
12
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
13
 
12
14
  // src/index.ts
13
- import * as path6 from "path";
15
+ import * as path5 from "path";
16
+ import * as fs6 from "fs";
17
+ import { fileURLToPath } from "url";
14
18
 
15
19
  // ../../node_modules/.bun/zod@4.1.8/node_modules/zod/v4/classic/external.js
16
20
  var exports_external = {};
@@ -12333,7 +12337,7 @@ function tool(input) {
12333
12337
  }
12334
12338
  tool.schema = exports_external;
12335
12339
  // ../hive-core/dist/index.js
12336
- import { createRequire } from "node:module";
12340
+ import { createRequire as createRequire2 } from "node:module";
12337
12341
  import * as path from "path";
12338
12342
  import * as fs from "fs";
12339
12343
  import * as path2 from "path";
@@ -12341,16 +12345,14 @@ import * as fs2 from "fs";
12341
12345
  import * as fs3 from "fs";
12342
12346
  import * as fs4 from "fs";
12343
12347
  import * as fs5 from "fs";
12344
- import * as fs6 from "fs/promises";
12348
+ import * as fs7 from "fs/promises";
12345
12349
  import * as path3 from "path";
12346
12350
  import { Buffer as Buffer2 } from "node:buffer";
12347
12351
  import { spawn } from "child_process";
12348
12352
  import { normalize } from "node:path";
12349
12353
  import { EventEmitter } from "node:events";
12350
- import * as fs7 from "fs";
12351
- import * as path4 from "path";
12352
12354
  import * as fs8 from "fs";
12353
- import * as path5 from "path";
12355
+ import * as path4 from "path";
12354
12356
  var __create = Object.create;
12355
12357
  var __getProtoOf = Object.getPrototypeOf;
12356
12358
  var __defProp2 = Object.defineProperty;
@@ -12368,7 +12370,7 @@ var __toESM = (mod, isNodeMode, target) => {
12368
12370
  return to;
12369
12371
  };
12370
12372
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
12371
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
12373
+ var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
12372
12374
  var require_ms = __commonJS((exports, module) => {
12373
12375
  var s = 1000;
12374
12376
  var m = s * 60;
@@ -12817,8 +12819,8 @@ var require_has_flag = __commonJS((exports, module) => {
12817
12819
  };
12818
12820
  });
12819
12821
  var require_supports_color = __commonJS((exports, module) => {
12820
- var os = __require("os");
12821
- var tty = __require("tty");
12822
+ var os = __require2("os");
12823
+ var tty = __require2("tty");
12822
12824
  var hasFlag = require_has_flag();
12823
12825
  var { env } = process;
12824
12826
  var forceColor;
@@ -12914,8 +12916,8 @@ var require_supports_color = __commonJS((exports, module) => {
12914
12916
  };
12915
12917
  });
12916
12918
  var require_node = __commonJS((exports, module) => {
12917
- var tty = __require("tty");
12918
- var util = __require("util");
12919
+ var tty = __require2("tty");
12920
+ var util = __require2("util");
12919
12921
  exports.init = init;
12920
12922
  exports.log = log;
12921
12923
  exports.formatArgs = formatArgs;
@@ -13094,7 +13096,7 @@ var require_src2 = __commonJS((exports) => {
13094
13096
  return mod && mod.__esModule ? mod : { default: mod };
13095
13097
  };
13096
13098
  Object.defineProperty(exports, "__esModule", { value: true });
13097
- var fs_1 = __require("fs");
13099
+ var fs_1 = __require2("fs");
13098
13100
  var debug_1 = __importDefault(require_src());
13099
13101
  var log = debug_1.default("@kwsites/file-exists");
13100
13102
  function check2(path32, isFile, isDirectory) {
@@ -13183,6 +13185,7 @@ var COMMENTS_FILE = "comments.json";
13183
13185
  var FEATURE_FILE = "feature.json";
13184
13186
  var STATUS_FILE = "status.json";
13185
13187
  var REPORT_FILE = "report.md";
13188
+ var APPROVED_FILE = "APPROVED";
13186
13189
  function getHivePath(projectRoot) {
13187
13190
  return path.join(projectRoot, HIVE_DIR);
13188
13191
  }
@@ -13219,6 +13222,9 @@ function getTaskReportPath(projectRoot, featureName, taskFolder) {
13219
13222
  function getTaskSpecPath(projectRoot, featureName, taskFolder) {
13220
13223
  return path.join(getTaskPath(projectRoot, featureName, taskFolder), "spec.md");
13221
13224
  }
13225
+ function getApprovedPath(projectRoot, featureName) {
13226
+ return path.join(getFeaturePath(projectRoot, featureName), APPROVED_FILE);
13227
+ }
13222
13228
  var SUBTASKS_DIR = "subtasks";
13223
13229
  var SPEC_FILE = "spec.md";
13224
13230
  function getSubtasksPath(projectRoot, featureName, taskFolder) {
@@ -13343,6 +13349,16 @@ class FeatureService {
13343
13349
  return [];
13344
13350
  return fs3.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
13345
13351
  }
13352
+ getActive() {
13353
+ const features = this.list();
13354
+ for (const name of features) {
13355
+ const feature = this.get(name);
13356
+ if (feature && feature.status !== "completed") {
13357
+ return feature;
13358
+ }
13359
+ }
13360
+ return null;
13361
+ }
13346
13362
  updateStatus(name, status) {
13347
13363
  const feature = this.get(name);
13348
13364
  if (!feature)
@@ -13422,6 +13438,7 @@ class PlanService {
13422
13438
  const planPath = getPlanPath(this.projectRoot, featureName);
13423
13439
  writeText(planPath, content);
13424
13440
  this.clearComments(featureName);
13441
+ this.revokeApproval(featureName);
13425
13442
  return planPath;
13426
13443
  }
13427
13444
  read(featureName) {
@@ -13429,25 +13446,45 @@ class PlanService {
13429
13446
  const content = readText(planPath);
13430
13447
  if (content === null)
13431
13448
  return null;
13432
- const feature = readJson(getFeatureJsonPath(this.projectRoot, featureName));
13433
13449
  const comments = this.getComments(featureName);
13450
+ const isApproved = this.isApproved(featureName);
13434
13451
  return {
13435
13452
  content,
13436
- status: feature?.status || "planning",
13453
+ status: isApproved ? "approved" : "planning",
13437
13454
  comments
13438
13455
  };
13439
13456
  }
13440
13457
  approve(featureName) {
13441
- const featurePath = getFeatureJsonPath(this.projectRoot, featureName);
13442
- const feature = readJson(featurePath);
13443
- if (!feature)
13444
- throw new Error(`Feature '${featureName}' not found`);
13445
13458
  if (!fileExists(getPlanPath(this.projectRoot, featureName))) {
13446
13459
  throw new Error(`No plan.md found for feature '${featureName}'`);
13447
13460
  }
13448
- feature.status = "approved";
13449
- feature.approvedAt = new Date().toISOString();
13450
- writeJson(featurePath, feature);
13461
+ const approvedPath = getApprovedPath(this.projectRoot, featureName);
13462
+ const timestamp = new Date().toISOString();
13463
+ fs4.writeFileSync(approvedPath, `Approved at ${timestamp}
13464
+ `);
13465
+ const featurePath = getFeatureJsonPath(this.projectRoot, featureName);
13466
+ const feature = readJson(featurePath);
13467
+ if (feature) {
13468
+ feature.status = "approved";
13469
+ feature.approvedAt = timestamp;
13470
+ writeJson(featurePath, feature);
13471
+ }
13472
+ }
13473
+ isApproved(featureName) {
13474
+ return fileExists(getApprovedPath(this.projectRoot, featureName));
13475
+ }
13476
+ revokeApproval(featureName) {
13477
+ const approvedPath = getApprovedPath(this.projectRoot, featureName);
13478
+ if (fileExists(approvedPath)) {
13479
+ fs4.unlinkSync(approvedPath);
13480
+ }
13481
+ const featurePath = getFeatureJsonPath(this.projectRoot, featureName);
13482
+ const feature = readJson(featurePath);
13483
+ if (feature && feature.status === "approved") {
13484
+ feature.status = "planning";
13485
+ delete feature.approvedAt;
13486
+ writeJson(featurePath, feature);
13487
+ }
13451
13488
  }
13452
13489
  getComments(featureName) {
13453
13490
  const commentsPath = getCommentsPath(this.projectRoot, featureName);
@@ -13629,12 +13666,12 @@ class TaskService {
13629
13666
  const tasksPath = getTasksPath(this.projectRoot, featureName);
13630
13667
  if (!fileExists(tasksPath))
13631
13668
  return [];
13632
- return fs4.readdirSync(tasksPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
13669
+ return fs5.readdirSync(tasksPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
13633
13670
  }
13634
13671
  deleteTask(featureName, taskFolder) {
13635
13672
  const taskPath = getTaskPath(this.projectRoot, featureName, taskFolder);
13636
13673
  if (fileExists(taskPath)) {
13637
- fs4.rmSync(taskPath, { recursive: true });
13674
+ fs5.rmSync(taskPath, { recursive: true });
13638
13675
  }
13639
13676
  }
13640
13677
  getNextOrder(existingFolders) {
@@ -13774,7 +13811,7 @@ _Add detailed instructions here_
13774
13811
  }
13775
13812
  const subtaskPath = getSubtaskPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13776
13813
  if (fileExists(subtaskPath)) {
13777
- fs4.rmSync(subtaskPath, { recursive: true });
13814
+ fs5.rmSync(subtaskPath, { recursive: true });
13778
13815
  }
13779
13816
  }
13780
13817
  getSubtask(featureName, taskFolder, subtaskId) {
@@ -13834,7 +13871,7 @@ _Add detailed instructions here_
13834
13871
  const subtasksPath = getSubtasksPath(this.projectRoot, featureName, taskFolder);
13835
13872
  if (!fileExists(subtasksPath))
13836
13873
  return [];
13837
- return fs4.readdirSync(subtasksPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
13874
+ return fs5.readdirSync(subtasksPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
13838
13875
  }
13839
13876
  findSubtaskFolder(featureName, taskFolder, subtaskId) {
13840
13877
  const folders = this.listSubtaskFolders(featureName, taskFolder);
@@ -13845,170 +13882,6 @@ _Add detailed instructions here_
13845
13882
  return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
13846
13883
  }
13847
13884
  }
13848
-
13849
- class SubtaskService {
13850
- projectRoot;
13851
- constructor(projectRoot) {
13852
- this.projectRoot = projectRoot;
13853
- }
13854
- create(featureName, taskFolder, name, type) {
13855
- const subtasksPath = getSubtasksPath(this.projectRoot, featureName, taskFolder);
13856
- ensureDir(subtasksPath);
13857
- const existingFolders = this.listFolders(featureName, taskFolder);
13858
- const taskOrder = parseInt(taskFolder.split("-")[0], 10);
13859
- const nextOrder = existingFolders.length + 1;
13860
- const subtaskId = `${taskOrder}.${nextOrder}`;
13861
- const folderName = `${nextOrder}-${this.slugify(name)}`;
13862
- const subtaskPath = getSubtaskPath(this.projectRoot, featureName, taskFolder, folderName);
13863
- ensureDir(subtaskPath);
13864
- const subtaskStatus = {
13865
- status: "pending",
13866
- type,
13867
- createdAt: new Date().toISOString()
13868
- };
13869
- writeJson(getSubtaskStatusPath(this.projectRoot, featureName, taskFolder, folderName), subtaskStatus);
13870
- const specContent = `# Subtask: ${name}
13871
-
13872
- **Type:** ${type || "custom"}
13873
- **ID:** ${subtaskId}
13874
-
13875
- ## Instructions
13876
-
13877
- _Add detailed instructions here_
13878
- `;
13879
- writeText(getSubtaskSpecPath(this.projectRoot, featureName, taskFolder, folderName), specContent);
13880
- return {
13881
- id: subtaskId,
13882
- name,
13883
- folder: folderName,
13884
- status: "pending",
13885
- type,
13886
- createdAt: subtaskStatus.createdAt
13887
- };
13888
- }
13889
- update(featureName, taskFolder, subtaskId, status) {
13890
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13891
- if (!subtaskFolder) {
13892
- throw new Error(`Subtask '${subtaskId}' not found in task '${taskFolder}'`);
13893
- }
13894
- const statusPath = getSubtaskStatusPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13895
- const current = readJson(statusPath);
13896
- if (!current) {
13897
- throw new Error(`Subtask status not found for '${subtaskId}'`);
13898
- }
13899
- const updated = { ...current, status };
13900
- if (status === "done" && !current.completedAt) {
13901
- updated.completedAt = new Date().toISOString();
13902
- }
13903
- writeJson(statusPath, updated);
13904
- const name = subtaskFolder.replace(/^\d+-/, "");
13905
- return {
13906
- id: subtaskId,
13907
- name,
13908
- folder: subtaskFolder,
13909
- status,
13910
- type: current.type,
13911
- createdAt: current.createdAt,
13912
- completedAt: updated.completedAt
13913
- };
13914
- }
13915
- list(featureName, taskFolder) {
13916
- const folders = this.listFolders(featureName, taskFolder);
13917
- const taskOrder = parseInt(taskFolder.split("-")[0], 10);
13918
- return folders.map((folder, index) => {
13919
- const statusPath = getSubtaskStatusPath(this.projectRoot, featureName, taskFolder, folder);
13920
- const status = readJson(statusPath);
13921
- const name = folder.replace(/^\d+-/, "");
13922
- const subtaskOrder = parseInt(folder.split("-")[0], 10) || index + 1;
13923
- return {
13924
- id: `${taskOrder}.${subtaskOrder}`,
13925
- name,
13926
- folder,
13927
- status: status?.status || "pending",
13928
- type: status?.type,
13929
- createdAt: status?.createdAt,
13930
- completedAt: status?.completedAt
13931
- };
13932
- });
13933
- }
13934
- get(featureName, taskFolder, subtaskId) {
13935
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13936
- if (!subtaskFolder)
13937
- return null;
13938
- const statusPath = getSubtaskStatusPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13939
- const status = readJson(statusPath);
13940
- if (!status)
13941
- return null;
13942
- const taskOrder = parseInt(taskFolder.split("-")[0], 10);
13943
- const subtaskOrder = parseInt(subtaskFolder.split("-")[0], 10);
13944
- const name = subtaskFolder.replace(/^\d+-/, "");
13945
- return {
13946
- id: `${taskOrder}.${subtaskOrder}`,
13947
- name,
13948
- folder: subtaskFolder,
13949
- status: status.status,
13950
- type: status.type,
13951
- createdAt: status.createdAt,
13952
- completedAt: status.completedAt
13953
- };
13954
- }
13955
- writeSpec(featureName, taskFolder, subtaskId, content) {
13956
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13957
- if (!subtaskFolder) {
13958
- throw new Error(`Subtask '${subtaskId}' not found in task '${taskFolder}'`);
13959
- }
13960
- const specPath = getSubtaskSpecPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13961
- writeText(specPath, content);
13962
- return specPath;
13963
- }
13964
- writeReport(featureName, taskFolder, subtaskId, content) {
13965
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13966
- if (!subtaskFolder) {
13967
- throw new Error(`Subtask '${subtaskId}' not found in task '${taskFolder}'`);
13968
- }
13969
- const reportPath = getSubtaskReportPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13970
- writeText(reportPath, content);
13971
- return reportPath;
13972
- }
13973
- readSpec(featureName, taskFolder, subtaskId) {
13974
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13975
- if (!subtaskFolder)
13976
- return null;
13977
- const specPath = getSubtaskSpecPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13978
- return readText(specPath);
13979
- }
13980
- readReport(featureName, taskFolder, subtaskId) {
13981
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13982
- if (!subtaskFolder)
13983
- return null;
13984
- const reportPath = getSubtaskReportPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13985
- return readText(reportPath);
13986
- }
13987
- delete(featureName, taskFolder, subtaskId) {
13988
- const subtaskFolder = this.findFolder(featureName, taskFolder, subtaskId);
13989
- if (!subtaskFolder) {
13990
- throw new Error(`Subtask '${subtaskId}' not found in task '${taskFolder}'`);
13991
- }
13992
- const subtaskPath = getSubtaskPath(this.projectRoot, featureName, taskFolder, subtaskFolder);
13993
- if (fileExists(subtaskPath)) {
13994
- fs5.rmSync(subtaskPath, { recursive: true });
13995
- }
13996
- }
13997
- listFolders(featureName, taskFolder) {
13998
- const subtasksPath = getSubtasksPath(this.projectRoot, featureName, taskFolder);
13999
- if (!fileExists(subtasksPath))
14000
- return [];
14001
- return fs5.readdirSync(subtasksPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
14002
- }
14003
- findFolder(featureName, taskFolder, subtaskId) {
14004
- const folders = this.listFolders(featureName, taskFolder);
14005
- const subtaskOrder = subtaskId.split(".")[1];
14006
- return folders.find((f) => f.startsWith(`${subtaskOrder}-`)) || null;
14007
- }
14008
- slugify(name) {
14009
- return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
14010
- }
14011
- }
14012
13885
  var import_file_exists = __toESM(require_dist(), 1);
14013
13886
  var import_debug = __toESM(require_src(), 1);
14014
13887
  var import_promise_deferred = __toESM(require_dist2(), 1);
@@ -18028,7 +17901,7 @@ class WorktreeService {
18028
17901
  const featurePath = path3.join(this.config.hiveDir, "features", feature);
18029
17902
  const tasksPath = path3.join(featurePath, "tasks", step, "status.json");
18030
17903
  try {
18031
- await fs6.access(tasksPath);
17904
+ await fs7.access(tasksPath);
18032
17905
  return tasksPath;
18033
17906
  } catch {}
18034
17907
  return path3.join(featurePath, "execution", step, "status.json");
@@ -18040,7 +17913,7 @@ class WorktreeService {
18040
17913
  const worktreePath = this.getWorktreePath(feature, step);
18041
17914
  const branchName = this.getBranchName(feature, step);
18042
17915
  const git = this.getGit();
18043
- await fs6.mkdir(path3.dirname(worktreePath), { recursive: true });
17916
+ await fs7.mkdir(path3.dirname(worktreePath), { recursive: true });
18044
17917
  const base = baseBranch || (await git.revparse(["HEAD"])).trim();
18045
17918
  const existing = await this.get(feature, step);
18046
17919
  if (existing) {
@@ -18069,7 +17942,7 @@ class WorktreeService {
18069
17942
  const worktreePath = this.getWorktreePath(feature, step);
18070
17943
  const branchName = this.getBranchName(feature, step);
18071
17944
  try {
18072
- await fs6.access(worktreePath);
17945
+ await fs7.access(worktreePath);
18073
17946
  const worktreeGit = this.getGit(worktreePath);
18074
17947
  const commit = (await worktreeGit.revparse(["HEAD"])).trim();
18075
17948
  return {
@@ -18089,7 +17962,7 @@ class WorktreeService {
18089
17962
  let base = baseCommit;
18090
17963
  if (!base) {
18091
17964
  try {
18092
- const status = JSON.parse(await fs6.readFile(statusPath, "utf-8"));
17965
+ const status = JSON.parse(await fs7.readFile(statusPath, "utf-8"));
18093
17966
  base = status.baseCommit;
18094
17967
  } catch {}
18095
17968
  }
@@ -18139,7 +18012,7 @@ class WorktreeService {
18139
18012
  const base = baseBranch || "HEAD~1";
18140
18013
  const worktreeGit = this.getGit(worktreePath);
18141
18014
  const diff = await worktreeGit.diff([`${base}...HEAD`]);
18142
- await fs6.writeFile(patchPath, diff);
18015
+ await fs7.writeFile(patchPath, diff);
18143
18016
  return patchPath;
18144
18017
  }
18145
18018
  async applyDiff(feature, step, baseBranch) {
@@ -18149,13 +18022,13 @@ class WorktreeService {
18149
18022
  }
18150
18023
  const patchPath = path3.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
18151
18024
  try {
18152
- await fs6.writeFile(patchPath, diffContent);
18025
+ await fs7.writeFile(patchPath, diffContent);
18153
18026
  const git = this.getGit();
18154
18027
  await git.applyPatch(patchPath);
18155
- await fs6.unlink(patchPath).catch(() => {});
18028
+ await fs7.unlink(patchPath).catch(() => {});
18156
18029
  return { success: true, filesAffected: filesChanged };
18157
18030
  } catch (error45) {
18158
- await fs6.unlink(patchPath).catch(() => {});
18031
+ await fs7.unlink(patchPath).catch(() => {});
18159
18032
  const err = error45;
18160
18033
  return {
18161
18034
  success: false,
@@ -18171,13 +18044,13 @@ class WorktreeService {
18171
18044
  }
18172
18045
  const patchPath = path3.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
18173
18046
  try {
18174
- await fs6.writeFile(patchPath, diffContent);
18047
+ await fs7.writeFile(patchPath, diffContent);
18175
18048
  const git = this.getGit();
18176
18049
  await git.applyPatch(patchPath, ["-R"]);
18177
- await fs6.unlink(patchPath).catch(() => {});
18050
+ await fs7.unlink(patchPath).catch(() => {});
18178
18051
  return { success: true, filesAffected: filesChanged };
18179
18052
  } catch (error45) {
18180
- await fs6.unlink(patchPath).catch(() => {});
18053
+ await fs7.unlink(patchPath).catch(() => {});
18181
18054
  const err = error45;
18182
18055
  return {
18183
18056
  success: false,
@@ -18196,7 +18069,7 @@ class WorktreeService {
18196
18069
  return [...new Set(files)];
18197
18070
  }
18198
18071
  async revertFromSavedDiff(diffPath) {
18199
- const diffContent = await fs6.readFile(diffPath, "utf-8");
18072
+ const diffContent = await fs7.readFile(diffPath, "utf-8");
18200
18073
  if (!diffContent.trim()) {
18201
18074
  return { success: true, filesAffected: [] };
18202
18075
  }
@@ -18221,7 +18094,7 @@ class WorktreeService {
18221
18094
  try {
18222
18095
  await git.raw(["worktree", "remove", worktreePath, "--force"]);
18223
18096
  } catch {
18224
- await fs6.rm(worktreePath, { recursive: true, force: true });
18097
+ await fs7.rm(worktreePath, { recursive: true, force: true });
18225
18098
  }
18226
18099
  try {
18227
18100
  await git.raw(["worktree", "prune"]);
@@ -18236,13 +18109,13 @@ class WorktreeService {
18236
18109
  const worktreesDir = this.getWorktreesDir();
18237
18110
  const results = [];
18238
18111
  try {
18239
- const features = feature ? [feature] : await fs6.readdir(worktreesDir);
18112
+ const features = feature ? [feature] : await fs7.readdir(worktreesDir);
18240
18113
  for (const feat of features) {
18241
18114
  const featurePath = path3.join(worktreesDir, feat);
18242
- const stat2 = await fs6.stat(featurePath).catch(() => null);
18115
+ const stat2 = await fs7.stat(featurePath).catch(() => null);
18243
18116
  if (!stat2?.isDirectory())
18244
18117
  continue;
18245
- const steps = await fs6.readdir(featurePath).catch(() => []);
18118
+ const steps = await fs7.readdir(featurePath).catch(() => []);
18246
18119
  for (const step of steps) {
18247
18120
  const info = await this.get(feat, step);
18248
18121
  if (info) {
@@ -18260,16 +18133,16 @@ class WorktreeService {
18260
18133
  await git.raw(["worktree", "prune"]);
18261
18134
  } catch {}
18262
18135
  const worktreesDir = this.getWorktreesDir();
18263
- const features = feature ? [feature] : await fs6.readdir(worktreesDir).catch(() => []);
18136
+ const features = feature ? [feature] : await fs7.readdir(worktreesDir).catch(() => []);
18264
18137
  for (const feat of features) {
18265
18138
  const featurePath = path3.join(worktreesDir, feat);
18266
- const stat2 = await fs6.stat(featurePath).catch(() => null);
18139
+ const stat2 = await fs7.stat(featurePath).catch(() => null);
18267
18140
  if (!stat2?.isDirectory())
18268
18141
  continue;
18269
- const steps = await fs6.readdir(featurePath).catch(() => []);
18142
+ const steps = await fs7.readdir(featurePath).catch(() => []);
18270
18143
  for (const step of steps) {
18271
18144
  const worktreePath = path3.join(featurePath, step);
18272
- const stepStat = await fs6.stat(worktreePath).catch(() => null);
18145
+ const stepStat = await fs7.stat(worktreePath).catch(() => null);
18273
18146
  if (!stepStat?.isDirectory())
18274
18147
  continue;
18275
18148
  try {
@@ -18290,13 +18163,13 @@ class WorktreeService {
18290
18163
  }
18291
18164
  const patchPath = path3.join(this.config.hiveDir, ".worktrees", feature, `${step}-check.patch`);
18292
18165
  try {
18293
- await fs6.writeFile(patchPath, diffContent);
18166
+ await fs7.writeFile(patchPath, diffContent);
18294
18167
  const git = this.getGit();
18295
18168
  await git.applyPatch(patchPath, ["--check"]);
18296
- await fs6.unlink(patchPath).catch(() => {});
18169
+ await fs7.unlink(patchPath).catch(() => {});
18297
18170
  return [];
18298
18171
  } catch (error45) {
18299
- await fs6.unlink(patchPath).catch(() => {});
18172
+ await fs7.unlink(patchPath).catch(() => {});
18300
18173
  const err = error45;
18301
18174
  const stderr = err.message || "";
18302
18175
  const conflicts2 = stderr.split(`
@@ -18309,7 +18182,7 @@ class WorktreeService {
18309
18182
  }
18310
18183
  async checkConflictsFromSavedDiff(diffPath, reverse = false) {
18311
18184
  try {
18312
- await fs6.access(diffPath);
18185
+ await fs7.access(diffPath);
18313
18186
  } catch {
18314
18187
  return [];
18315
18188
  }
@@ -18332,7 +18205,7 @@ class WorktreeService {
18332
18205
  async commitChanges(feature, step, message) {
18333
18206
  const worktreePath = this.getWorktreePath(feature, step);
18334
18207
  try {
18335
- await fs6.access(worktreePath);
18208
+ await fs7.access(worktreePath);
18336
18209
  } catch {
18337
18210
  return { committed: false, sha: "", message: "Worktree not found" };
18338
18211
  }
@@ -18472,10 +18345,10 @@ class ContextService {
18472
18345
  const contextPath = getContextPath(this.projectRoot, featureName);
18473
18346
  if (!fileExists(contextPath))
18474
18347
  return [];
18475
- const files = fs7.readdirSync(contextPath, { withFileTypes: true }).filter((f) => f.isFile() && f.name.endsWith(".md")).map((f) => f.name);
18348
+ const files = fs8.readdirSync(contextPath, { withFileTypes: true }).filter((f) => f.isFile() && f.name.endsWith(".md")).map((f) => f.name);
18476
18349
  return files.map((name) => {
18477
18350
  const filePath = path4.join(contextPath, name);
18478
- const stat2 = fs7.statSync(filePath);
18351
+ const stat2 = fs8.statSync(filePath);
18479
18352
  const content = readText(filePath) || "";
18480
18353
  return {
18481
18354
  name: name.replace(/\.md$/, ""),
@@ -18488,7 +18361,7 @@ class ContextService {
18488
18361
  const contextPath = getContextPath(this.projectRoot, featureName);
18489
18362
  const filePath = path4.join(contextPath, this.normalizeFileName(fileName));
18490
18363
  if (fileExists(filePath)) {
18491
- fs7.unlinkSync(filePath);
18364
+ fs8.unlinkSync(filePath);
18492
18365
  return true;
18493
18366
  }
18494
18367
  return false;
@@ -18512,139 +18385,457 @@ ${f.content}`);
18512
18385
  }
18513
18386
  }
18514
18387
 
18515
- class SessionService {
18516
- projectRoot;
18517
- constructor(projectRoot) {
18518
- this.projectRoot = projectRoot;
18519
- }
18520
- getSessionsPath(featureName) {
18521
- return path5.join(getFeaturePath(this.projectRoot, featureName), "sessions.json");
18522
- }
18523
- getSessions(featureName) {
18524
- const sessionsPath = this.getSessionsPath(featureName);
18525
- return readJson(sessionsPath) || { sessions: [] };
18526
- }
18527
- saveSessions(featureName, data) {
18528
- const sessionsPath = this.getSessionsPath(featureName);
18529
- ensureDir(path5.dirname(sessionsPath));
18530
- writeJson(sessionsPath, data);
18531
- }
18532
- track(featureName, sessionId, taskFolder) {
18533
- const data = this.getSessions(featureName);
18534
- const now = new Date().toISOString();
18535
- let session = data.sessions.find((s) => s.sessionId === sessionId);
18536
- if (session) {
18537
- session.lastActiveAt = now;
18538
- if (taskFolder)
18539
- session.taskFolder = taskFolder;
18540
- } else {
18541
- session = {
18542
- sessionId,
18543
- taskFolder,
18544
- startedAt: now,
18545
- lastActiveAt: now
18546
- };
18547
- data.sessions.push(session);
18548
- }
18549
- if (!data.master) {
18550
- data.master = sessionId;
18551
- }
18552
- this.saveSessions(featureName, data);
18553
- return session;
18554
- }
18555
- setMaster(featureName, sessionId) {
18556
- const data = this.getSessions(featureName);
18557
- data.master = sessionId;
18558
- this.saveSessions(featureName, data);
18388
+ // src/utils/agent-selector.ts
18389
+ var AGENT_PATTERNS = [
18390
+ { pattern: /\b(find|search|locate|where|grep|explore|scan)\b/i, agent: "explore" },
18391
+ { pattern: /\b(research|investigate|learn|understand|docs?|documentation|library|api)\b/i, agent: "librarian" },
18392
+ { pattern: /\b(ui|ux|component|frontend|react|vue|svelte|css|style|layout|design|button|form|modal)\b/i, agent: "frontend-ui-ux-engineer" },
18393
+ { pattern: /\b(refactor|simplify|clean|reduce|complexity|review|optimize)\b/i, agent: "code-simplicity-reviewer" },
18394
+ { pattern: /\b(readme|document|explain|write.*doc|comment|jsdoc|tsdoc)\b/i, agent: "document-writer" },
18395
+ { pattern: /\b(image|screenshot|visual|diagram|mockup|pdf|picture|photo)\b/i, agent: "multimodal-looker" },
18396
+ { pattern: /\b(architect|design.*decision|tradeoff|approach|strategy|choose|decide|compare)\b/i, agent: "oracle" }
18397
+ ];
18398
+ function selectAgent(taskName, spec) {
18399
+ const content = `${taskName} ${spec}`.toLowerCase();
18400
+ for (const { pattern, agent } of AGENT_PATTERNS) {
18401
+ if (pattern.test(content)) {
18402
+ return agent;
18403
+ }
18404
+ }
18405
+ return "general";
18406
+ }
18407
+
18408
+ // src/utils/worker-prompt.ts
18409
+ function buildWorkerPrompt(params) {
18410
+ const {
18411
+ feature,
18412
+ task,
18413
+ taskOrder,
18414
+ worktreePath,
18415
+ branch,
18416
+ plan,
18417
+ contextFiles,
18418
+ spec,
18419
+ previousTasks,
18420
+ continueFrom
18421
+ } = params;
18422
+ const contextSection = contextFiles.length > 0 ? contextFiles.map((f) => `### ${f.name}
18423
+ ${f.content}`).join(`
18424
+
18425
+ `) : "_No context files available._";
18426
+ const previousSection = previousTasks?.length ? previousTasks.map((t) => `- **${t.name}**: ${t.summary}`).join(`
18427
+ `) : "_This is the first task._";
18428
+ const continuationSection = continueFrom ? `
18429
+ ## Continuation from Blocked State
18430
+
18431
+ Previous worker was blocked and exited. Here's the context:
18432
+
18433
+ **Previous Progress**: ${continueFrom.previousSummary}
18434
+
18435
+ **User Decision**: ${continueFrom.decision}
18436
+
18437
+ Continue from where the previous worker left off, incorporating the user's decision.
18438
+ The worktree already contains the previous worker's progress.
18439
+ ` : "";
18440
+ return `# Hive Worker Assignment
18441
+
18442
+ You are a worker agent executing a task in an isolated git worktree.
18443
+
18444
+ ## Assignment Details
18445
+
18446
+ | Field | Value |
18447
+ |-------|-------|
18448
+ | Feature | ${feature} |
18449
+ | Task | ${task} |
18450
+ | Task # | ${taskOrder} |
18451
+ | Branch | ${branch} |
18452
+ | Worktree | ${worktreePath} |
18453
+
18454
+ **CRITICAL**: All file operations MUST be within this worktree path:
18455
+ \`${worktreePath}\`
18456
+
18457
+ Do NOT modify files outside this directory.
18458
+ ${continuationSection}
18459
+ ---
18460
+
18461
+ ## Plan Context
18462
+
18463
+ ${plan}
18464
+
18465
+ ---
18466
+
18467
+ ## Context Files (Royal Jelly)
18468
+
18469
+ ${contextSection}
18470
+
18471
+ ---
18472
+
18473
+ ## Previous Tasks Completed
18474
+
18475
+ ${previousSection}
18476
+
18477
+ ---
18478
+
18479
+ ## Your Mission
18480
+
18481
+ ${spec}
18482
+
18483
+ ---
18484
+
18485
+ ## Blocker Protocol
18486
+
18487
+ If you hit a blocker requiring human decision, **DO NOT** use the question tool directly.
18488
+ Instead, escalate via the blocker protocol:
18489
+
18490
+ 1. **Save your progress** to the worktree (commit if appropriate)
18491
+ 2. **Call hive_exec_complete** with blocker info:
18492
+
18493
+ \`\`\`
18494
+ hive_exec_complete({
18495
+ task: "${task}",
18496
+ status: "blocked",
18497
+ summary: "What you accomplished so far",
18498
+ blocker: {
18499
+ reason: "Why you're blocked - be specific",
18500
+ options: ["Option A", "Option B", "Option C"],
18501
+ recommendation: "Your suggested choice with reasoning",
18502
+ context: "Relevant background the user needs to decide"
18503
+ }
18504
+ })
18505
+ \`\`\`
18506
+
18507
+ The Hive Master will:
18508
+ 1. Receive your blocker info
18509
+ 2. Ask the user via question()
18510
+ 3. Spawn a NEW worker to continue with the decision
18511
+
18512
+ This keeps the user focused on ONE conversation (Hive Master) instead of multiple worker panes.
18513
+
18514
+ ---
18515
+
18516
+ ## Completion Protocol
18517
+
18518
+ When your task is **fully complete**:
18519
+
18520
+ \`\`\`
18521
+ hive_exec_complete({
18522
+ task: "${task}",
18523
+ status: "completed",
18524
+ summary: "Concise summary of what you accomplished"
18525
+ })
18526
+ \`\`\`
18527
+
18528
+ If you encounter an **unrecoverable error**:
18529
+
18530
+ \`\`\`
18531
+ hive_exec_complete({
18532
+ task: "${task}",
18533
+ status: "failed",
18534
+ summary: "What went wrong and what was attempted"
18535
+ })
18536
+ \`\`\`
18537
+
18538
+ If you made **partial progress** but can't continue:
18539
+
18540
+ \`\`\`
18541
+ hive_exec_complete({
18542
+ task: "${task}",
18543
+ status: "partial",
18544
+ summary: "What was completed and what remains"
18545
+ })
18546
+ \`\`\`
18547
+
18548
+ ---
18549
+
18550
+ ## Tool Access
18551
+
18552
+ **You have access to:**
18553
+ - All standard tools (read, write, edit, bash, glob, grep)
18554
+ - \`hive_exec_complete\` - Signal task done/blocked/failed
18555
+ - \`hive_exec_abort\` - Abort and discard changes
18556
+ - \`hive_plan_read\` - Re-read plan if needed
18557
+ - \`hive_context_write\` - Save learnings for future tasks
18558
+
18559
+ **You do NOT have access to (or should not use):**
18560
+ - \`question\` - Escalate via blocker protocol instead
18561
+ - \`hive_exec_start\` - No spawning sub-workers
18562
+ - \`hive_merge\` - Only Hive Master merges
18563
+ - \`background_task\` - No recursive delegation
18564
+
18565
+ ---
18566
+
18567
+ ## Guidelines
18568
+
18569
+ 1. **Work methodically** - Break down the mission into steps
18570
+ 2. **Stay in scope** - Only do what the spec asks
18571
+ 3. **Escalate blockers** - Don't guess on important decisions
18572
+ 4. **Save context** - Use hive_context_write for discoveries
18573
+ 5. **Complete cleanly** - Always call hive_exec_complete when done
18574
+
18575
+ ---
18576
+
18577
+ Begin your task now.
18578
+ `;
18579
+ }
18580
+
18581
+ // src/agents/hive.ts
18582
+ var HIVE_AGENT_BASE = `# Hive Agent
18583
+
18584
+ You are the Hive Master - a hybrid planner-orchestrator for structured feature development.
18585
+
18586
+ ## Your Role
18587
+
18588
+ - **Plan** features via hive_plan_write (explicit, reviewable)
18589
+ - **Delegate** execution via hive_exec_start (workers in tmux panes)
18590
+ - **Ask questions** on behalf of blocked workers (single point of contact)
18591
+ - **Do simple work** directly if user explicitly asks
18592
+
18593
+ ## Core Workflow
18594
+
18595
+ 1. **Plan** - Create feature, write plan, get user approval
18596
+ 2. **Execute** - Spawn workers for each task via hive_exec_start
18597
+ 3. **Monitor** - Check progress with hive_worker_status
18598
+ 4. **Handle blockers** - Workers exit with blocker info, you ask user and resume
18599
+ 5. **Merge** - Integrate completed work via hive_merge
18600
+
18601
+ ## When No Feature is Active
18602
+
18603
+ Work directly on user requests. You're a capable coding agent.
18604
+ Use hive_feature_create when the task is complex enough to benefit from structure.
18605
+
18606
+ Signs you should create a feature:
18607
+ - Multiple files to change
18608
+ - Task requires planning
18609
+ - Work should be reviewed before merging
18610
+ - User mentions "feature", "implement", or describes multi-step work
18611
+
18612
+ ## When Feature is Active
18613
+
18614
+ Follow Hive workflow strictly:
18615
+ 1. Write plan via hive_plan_write
18616
+ 2. Wait for user to review and add comments
18617
+ 3. Read comments via hive_plan_read, revise if needed
18618
+ 4. Get approval (explicit or via hive_plan_approve)
18619
+ 5. Generate tasks via hive_tasks_sync
18620
+ 6. Execute tasks via hive_exec_start (spawns workers)
18621
+ 7. Monitor workers via hive_worker_status
18622
+ 8. Handle any blocked workers
18623
+ 9. Merge completed work via hive_merge
18624
+
18625
+ ## Blocker Handling Protocol
18626
+
18627
+ When a worker returns status: 'blocked':
18628
+
18629
+ 1. **Read** the blocker info from hive_worker_status:
18630
+ - reason: Why they're blocked
18631
+ - options: Available choices
18632
+ - recommendation: Worker's suggestion
18633
+
18634
+ 2. **Ask** the user via question():
18635
+ \`\`\`
18636
+ question({
18637
+ questions: [{
18638
+ header: "Decision Needed",
18639
+ question: "Worker blocked: {reason}. {recommendation}",
18640
+ options: [
18641
+ { label: "Option A", description: "..." },
18642
+ { label: "Option B", description: "..." }
18643
+ ]
18644
+ }]
18645
+ })
18646
+ \`\`\`
18647
+
18648
+ 3. **Resume** with the decision:
18649
+ \`\`\`
18650
+ hive_exec_start({
18651
+ task: "the-task",
18652
+ continueFrom: "blocked",
18653
+ decision: "User chose Option A because..."
18654
+ })
18655
+ \`\`\`
18656
+
18657
+ This keeps the user focused on ONE conversation (you) instead of multiple worker panes.
18658
+
18659
+ ## Communication Style
18660
+
18661
+ - Be concise, no preamble
18662
+ - Start work immediately
18663
+ - Challenge wrong approaches professionally
18664
+ - Don't summarize unless asked
18665
+ - Use hive tools proactively when in feature context
18666
+ `;
18667
+ function buildFeatureContextSection(ctx) {
18668
+ return `
18669
+ ## Active Feature: ${ctx.name}
18670
+
18671
+ **Plan Status:** ${ctx.planStatus}
18672
+ **Tasks:** ${ctx.tasksSummary}
18673
+ **Context Files:** ${ctx.contextList.length > 0 ? ctx.contextList.join(", ") : "none"}
18674
+
18675
+ You are in feature context. Use Hive workflow.
18676
+ `;
18677
+ }
18678
+ var OOM_SLIM_SECTION = `
18679
+ ## OMO-Slim Detected
18680
+
18681
+ Workers spawn in tmux panes with specialized agents:
18682
+ - **explorer** - Codebase search and pattern matching
18683
+ - **librarian** - External docs and library research
18684
+ - **oracle** - Architecture decisions and guidance
18685
+ - **designer** - UI/UX implementation
18686
+ - **general** - Default implementation
18687
+
18688
+ Agent is auto-selected based on task content.
18689
+ Watch workers in tmux panes for real-time progress.
18690
+ `;
18691
+ function buildHiveAgentPrompt(featureContext, omoSlimDetected) {
18692
+ let prompt = HIVE_AGENT_BASE;
18693
+ if (featureContext) {
18694
+ prompt += buildFeatureContextSection(featureContext);
18559
18695
  }
18560
- getMaster(featureName) {
18561
- return this.getSessions(featureName).master;
18696
+ if (omoSlimDetected) {
18697
+ prompt += OOM_SLIM_SECTION;
18562
18698
  }
18563
- list(featureName) {
18564
- return this.getSessions(featureName).sessions;
18699
+ return prompt;
18700
+ }
18701
+
18702
+ // src/index.ts
18703
+ function parseFrontmatter(content) {
18704
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
18705
+ if (!match) {
18706
+ return { meta: {}, body: content.trim() };
18707
+ }
18708
+ const meta = {};
18709
+ const frontmatter = match[1];
18710
+ const body = match[2];
18711
+ for (const line of frontmatter.split(`
18712
+ `)) {
18713
+ const colonIdx = line.indexOf(":");
18714
+ if (colonIdx > 0) {
18715
+ const key = line.slice(0, colonIdx).trim();
18716
+ const value = line.slice(colonIdx + 1).trim();
18717
+ meta[key] = value;
18718
+ }
18565
18719
  }
18566
- get(featureName, sessionId) {
18567
- return this.getSessions(featureName).sessions.find((s) => s.sessionId === sessionId);
18720
+ return { meta, body: body.trim() };
18721
+ }
18722
+ function getSkillsDir() {
18723
+ const filename = fileURLToPath(import.meta.url);
18724
+ const dirname5 = path5.dirname(filename);
18725
+ return path5.join(dirname5, "..", "skills");
18726
+ }
18727
+ function discoverHiveSkills() {
18728
+ const skillsDir = getSkillsDir();
18729
+ const skills = [];
18730
+ if (!fs6.existsSync(skillsDir)) {
18731
+ return skills;
18568
18732
  }
18569
- getByTask(featureName, taskFolder) {
18570
- return this.getSessions(featureName).sessions.find((s) => s.taskFolder === taskFolder);
18733
+ const entries = fs6.readdirSync(skillsDir, { withFileTypes: true });
18734
+ for (const entry of entries) {
18735
+ if (!entry.isDirectory())
18736
+ continue;
18737
+ const skillPath = path5.join(skillsDir, entry.name, "SKILL.md");
18738
+ if (!fs6.existsSync(skillPath))
18739
+ continue;
18740
+ try {
18741
+ const content = fs6.readFileSync(skillPath, "utf-8");
18742
+ const { meta, body } = parseFrontmatter(content);
18743
+ skills.push({
18744
+ name: meta.name || entry.name,
18745
+ description: meta.description || "",
18746
+ path: skillPath,
18747
+ body
18748
+ });
18749
+ } catch {}
18571
18750
  }
18572
- remove(featureName, sessionId) {
18573
- const data = this.getSessions(featureName);
18574
- const index = data.sessions.findIndex((s) => s.sessionId === sessionId);
18575
- if (index === -1)
18576
- return false;
18577
- data.sessions.splice(index, 1);
18578
- if (data.master === sessionId) {
18579
- data.master = data.sessions[0]?.sessionId;
18751
+ return skills;
18752
+ }
18753
+ function formatSkillsXml(skills) {
18754
+ if (skills.length === 0)
18755
+ return "";
18756
+ const skillsXml = skills.map((skill) => {
18757
+ return [
18758
+ " <skill>",
18759
+ ` <name>${skill.name}</name>`,
18760
+ ` <description>(hive - Skill) ${skill.description}</description>`,
18761
+ " </skill>"
18762
+ ].join(`
18763
+ `);
18764
+ }).join(`
18765
+ `);
18766
+ return `
18767
+
18768
+ <available_skills>
18769
+ ${skillsXml}
18770
+ </available_skills>`;
18771
+ }
18772
+ function createHiveSkillTool() {
18773
+ let cachedSkills = null;
18774
+ let cachedDescription = null;
18775
+ const getSkills = () => {
18776
+ if (cachedSkills)
18777
+ return cachedSkills;
18778
+ cachedSkills = discoverHiveSkills();
18779
+ return cachedSkills;
18780
+ };
18781
+ const getDescription = () => {
18782
+ if (cachedDescription)
18783
+ return cachedDescription;
18784
+ const skills = getSkills();
18785
+ const base = "Load a Hive skill to get detailed instructions for a specific workflow.";
18786
+ if (skills.length === 0) {
18787
+ cachedDescription = base + `
18788
+
18789
+ No Hive skills available.`;
18790
+ } else {
18791
+ cachedDescription = base + formatSkillsXml(skills);
18580
18792
  }
18581
- this.saveSessions(featureName, data);
18582
- return true;
18583
- }
18584
- findFeatureBySession(sessionId) {
18585
- const featuresPath = path5.join(this.projectRoot, ".hive", "features");
18586
- if (!fs8.existsSync(featuresPath))
18587
- return null;
18588
- const features = fs8.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
18589
- for (const feature of features) {
18590
- const sessions = this.getSessions(feature);
18591
- if (sessions.sessions.some((s) => s.sessionId === sessionId)) {
18592
- return feature;
18593
- }
18594
- if (sessions.master === sessionId) {
18595
- return feature;
18596
- }
18793
+ return cachedDescription;
18794
+ };
18795
+ getDescription();
18796
+ return tool({
18797
+ get description() {
18798
+ return cachedDescription ?? "Load a Hive skill to get detailed instructions for a specific workflow.";
18799
+ },
18800
+ args: {
18801
+ name: tool.schema.string().describe("The skill name from available_skills")
18802
+ },
18803
+ async execute({ name }) {
18804
+ const skills = getSkills();
18805
+ const skill = skills.find((s) => s.name === name);
18806
+ if (!skill) {
18807
+ const available = skills.map((s) => s.name).join(", ");
18808
+ throw new Error(`Skill "${name}" not found. Available Hive skills: ${available || "none"}`);
18809
+ }
18810
+ return [
18811
+ `## Hive Skill: ${skill.name}`,
18812
+ "",
18813
+ `**Description**: ${skill.description}`,
18814
+ "",
18815
+ skill.body
18816
+ ].join(`
18817
+ `);
18597
18818
  }
18598
- return null;
18599
- }
18600
- fork(featureName, fromSessionId) {
18601
- const data = this.getSessions(featureName);
18602
- const now = new Date().toISOString();
18603
- const sourceSession = fromSessionId ? data.sessions.find((s) => s.sessionId === fromSessionId) : data.sessions.find((s) => s.sessionId === data.master);
18604
- const newSessionId = `ses_fork_${Date.now()}`;
18605
- const newSession = {
18606
- sessionId: newSessionId,
18607
- taskFolder: sourceSession?.taskFolder,
18608
- startedAt: now,
18609
- lastActiveAt: now
18610
- };
18611
- data.sessions.push(newSession);
18612
- this.saveSessions(featureName, data);
18613
- return newSession;
18614
- }
18615
- fresh(featureName, title) {
18616
- const data = this.getSessions(featureName);
18617
- const now = new Date().toISOString();
18618
- const newSessionId = `ses_${title ? title.replace(/\s+/g, "_").toLowerCase() : Date.now()}`;
18619
- const newSession = {
18620
- sessionId: newSessionId,
18621
- startedAt: now,
18622
- lastActiveAt: now
18623
- };
18624
- data.sessions.push(newSession);
18625
- this.saveSessions(featureName, data);
18626
- return newSession;
18627
- }
18819
+ });
18628
18820
  }
18629
-
18630
- // src/index.ts
18631
18821
  var HIVE_SYSTEM_PROMPT = `
18632
18822
  ## Hive - Feature Development System
18633
18823
 
18634
18824
  Plan-first development: Write plan → User reviews → Approve → Execute tasks
18635
18825
 
18636
- ### Tools (24 total)
18826
+ ### Tools (19 total)
18637
18827
 
18638
18828
  | Domain | Tools |
18639
18829
  |--------|-------|
18640
18830
  | Feature | hive_feature_create, hive_feature_list, hive_feature_complete |
18641
18831
  | Plan | hive_plan_write, hive_plan_read, hive_plan_approve |
18642
18832
  | Task | hive_tasks_sync, hive_task_create, hive_task_update |
18643
- | Subtask | hive_subtask_create, hive_subtask_update, hive_subtask_list, hive_subtask_spec_write, hive_subtask_report_write |
18644
18833
  | Exec | hive_exec_start, hive_exec_complete, hive_exec_abort |
18834
+ | Worker | hive_worker_status |
18645
18835
  | Merge | hive_merge, hive_worktree_list |
18646
- | Context | hive_context_write, hive_context_read, hive_context_list |
18647
- | Session | hive_session_open, hive_session_list |
18836
+ | Context | hive_context_write |
18837
+ | Status | hive_status |
18838
+ | Skill | hive_skill |
18648
18839
 
18649
18840
  ### Workflow
18650
18841
 
@@ -18659,37 +18850,31 @@ Plan-first development: Write plan → User reviews → Approve → Execute task
18659
18850
  **Important:** \`hive_exec_complete\` commits changes to task branch but does NOT merge.
18660
18851
  Use \`hive_merge\` to explicitly integrate changes. Worktrees persist until manually removed.
18661
18852
 
18662
- ### Subtasks & TDD
18663
-
18664
- For complex tasks, break work into subtasks:
18665
-
18666
- \`\`\`
18667
- hive_subtask_create(task, "Write failing tests", "test")
18668
- hive_subtask_create(task, "Implement until green", "implement")
18669
- hive_subtask_create(task, "Run test suite", "verify")
18670
- \`\`\`
18671
-
18672
- Subtask types: test, implement, review, verify, research, debug, custom
18853
+ ### Delegated Execution (OMO-Slim Integration)
18673
18854
 
18674
- **Test-Driven Development**: For implementation tasks, consider writing tests first.
18675
- Tests define "done" and provide feedback loops that improve quality.
18855
+ When OMO-Slim is installed, \`hive_exec_start\` spawns worker agents in tmux panes:
18676
18856
 
18677
- ### Plan Format
18857
+ 1. \`hive_exec_start(task)\` → Creates worktree + spawns worker via \`background_task\`
18858
+ 2. Worker appears in tmux pane - watch it work in real-time
18859
+ 3. Worker completes → calls \`hive_exec_complete(status: "completed")\`
18860
+ 4. Worker blocked → calls \`hive_exec_complete(status: "blocked", blocker: {...})\`
18678
18861
 
18679
- \`\`\`markdown
18680
- # Feature Name
18862
+ **Handling blocked workers:**
18863
+ 1. Check blockers with \`hive_worker_status()\`
18864
+ 2. Read the blocker info (reason, options, recommendation)
18865
+ 3. Ask user via \`question()\` tool
18866
+ 4. Resume with \`hive_exec_start(task, continueFrom: "blocked", decision: answer)\`
18681
18867
 
18682
- ## Overview
18683
- What we're building and why.
18868
+ **Agent auto-selection** based on task content:
18869
+ | Pattern | Agent |
18870
+ |---------|-------|
18871
+ | find, search, explore | explorer |
18872
+ | research, docs | librarian |
18873
+ | ui, component, react | designer |
18874
+ | architect, decision | oracle |
18875
+ | (default) | general |
18684
18876
 
18685
- ## Tasks
18686
-
18687
- ### 1. Task Name
18688
- Description of what to do.
18689
-
18690
- ### 2. Another Task
18691
- Description.
18692
- \`\`\`
18877
+ Without OMO-Slim: \`hive_exec_start\` falls back to inline mode (work in same session).
18693
18878
 
18694
18879
  ### Planning Phase - Context Management REQUIRED
18695
18880
 
@@ -18702,22 +18887,40 @@ As you research and plan, CONTINUOUSLY save findings using \`hive_context_write\
18702
18887
  **Update existing context files** when new info emerges - dont create duplicates.
18703
18888
  Workers depend on context for background. Without it, they work blind.
18704
18889
 
18705
- Save context BEFORE writing the plan, and UPDATE it as planning iterates.
18706
-
18707
18890
  \`hive_tasks_sync\` parses \`### N. Task Name\` headers.
18891
+
18892
+ ### Execution Phase - Stay Aligned
18893
+
18894
+ During execution, call \`hive_status\` periodically to:
18895
+ - Check current progress and pending work
18896
+ - See context files to read
18897
+ - Get reminded of next actions
18708
18898
  `;
18709
18899
  var plugin = async (ctx) => {
18710
18900
  const { directory } = ctx;
18711
18901
  const featureService = new FeatureService(directory);
18712
18902
  const planService = new PlanService(directory);
18713
18903
  const taskService = new TaskService(directory);
18714
- const subtaskService = new SubtaskService(directory);
18715
18904
  const contextService = new ContextService(directory);
18716
- const sessionService = new SessionService(directory);
18717
18905
  const worktreeService = new WorktreeService({
18718
18906
  baseDir: directory,
18719
- hiveDir: path6.join(directory, ".hive")
18907
+ hiveDir: path5.join(directory, ".hive")
18720
18908
  });
18909
+ let omoSlimDetected = false;
18910
+ let detectionDone = false;
18911
+ const detectOmoSlim = (toolContext) => {
18912
+ if (detectionDone)
18913
+ return omoSlimDetected;
18914
+ const ctx2 = toolContext;
18915
+ if (ctx2?.tools?.includes?.("background_task") || ctx2?.background_task || typeof ctx2?.callTool === "function") {
18916
+ omoSlimDetected = true;
18917
+ }
18918
+ detectionDone = true;
18919
+ if (omoSlimDetected) {
18920
+ console.log("[Hive] OMO-Slim detected: delegated execution with tmux panes enabled");
18921
+ }
18922
+ return omoSlimDetected;
18923
+ };
18721
18924
  const resolveFeature = (explicit) => {
18722
18925
  if (explicit)
18723
18926
  return explicit;
@@ -18738,6 +18941,20 @@ var plugin = async (ctx) => {
18738
18941
  }
18739
18942
  }
18740
18943
  };
18944
+ const checkBlocked = (feature) => {
18945
+ const fs9 = __require("fs");
18946
+ const blockedPath = path5.join(directory, ".hive", "features", feature, "BLOCKED");
18947
+ if (fs9.existsSync(blockedPath)) {
18948
+ const reason = fs9.readFileSync(blockedPath, "utf-8").trim();
18949
+ return `⛔ BLOCKED by Beekeeper
18950
+
18951
+ ${reason || "(No reason provided)"}
18952
+
18953
+ The human has blocked this feature. Wait for them to unblock it.
18954
+ To unblock: Remove .hive/features/${feature}/BLOCKED`;
18955
+ }
18956
+ return null;
18957
+ };
18741
18958
  return {
18742
18959
  "experimental.chat.system.transform": async (_input, output) => {
18743
18960
  output.system.push(HIVE_SYSTEM_PROMPT);
@@ -18761,6 +18978,7 @@ var plugin = async (ctx) => {
18761
18978
  }
18762
18979
  },
18763
18980
  tool: {
18981
+ hive_skill: createHiveSkillTool(),
18764
18982
  hive_feature_create: tool({
18765
18983
  description: "Create a new feature and set it as active",
18766
18984
  args: {
@@ -18880,7 +19098,8 @@ var plugin = async (ctx) => {
18880
19098
  if (!feature)
18881
19099
  return "Error: No feature specified. Create a feature or provide feature param.";
18882
19100
  const folder = taskService.create(feature, name, order);
18883
- return `Manual task created: ${folder}`;
19101
+ return `Manual task created: ${folder}
19102
+ Reminder: start work with hive_exec_start to use its worktree, and ensure any subagents work in that worktree too.`;
18884
19103
  }
18885
19104
  }),
18886
19105
  hive_task_update: tool({
@@ -18903,21 +19122,36 @@ var plugin = async (ctx) => {
18903
19122
  }
18904
19123
  }),
18905
19124
  hive_exec_start: tool({
18906
- description: "Create worktree and begin work on task",
19125
+ description: "Create worktree and begin work on task. When OMO-Slim is installed, spawns worker agent in tmux pane.",
18907
19126
  args: {
18908
19127
  task: tool.schema.string().describe("Task folder name"),
18909
- feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)")
19128
+ feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)"),
19129
+ continueFrom: tool.schema.enum(["blocked"]).optional().describe("Resume a blocked task"),
19130
+ decision: tool.schema.string().optional().describe("Answer to blocker question when continuing")
18910
19131
  },
18911
- async execute({ task, feature: explicitFeature }) {
19132
+ async execute({ task, feature: explicitFeature, continueFrom, decision }, toolContext) {
18912
19133
  const feature = resolveFeature(explicitFeature);
18913
19134
  if (!feature)
18914
19135
  return "Error: No feature specified. Create a feature or provide feature param.";
19136
+ const blocked = checkBlocked(feature);
19137
+ if (blocked)
19138
+ return blocked;
18915
19139
  const taskInfo = taskService.get(feature, task);
18916
19140
  if (!taskInfo)
18917
19141
  return `Error: Task "${task}" not found`;
18918
19142
  if (taskInfo.status === "done")
18919
19143
  return "Error: Task already completed";
18920
- const worktree = await worktreeService.create(feature, task);
19144
+ if (continueFrom === "blocked" && taskInfo.status !== "blocked") {
19145
+ return "Error: Task is not in blocked state. Use without continueFrom.";
19146
+ }
19147
+ let worktree;
19148
+ if (continueFrom === "blocked") {
19149
+ worktree = await worktreeService.get(feature, task);
19150
+ if (!worktree)
19151
+ return "Error: No worktree found for blocked task";
19152
+ } else {
19153
+ worktree = await worktreeService.create(feature, task);
19154
+ }
18921
19155
  taskService.update(feature, task, {
18922
19156
  status: "in_progress",
18923
19157
  baseCommit: worktree.commit
@@ -18933,7 +19167,7 @@ var plugin = async (ctx) => {
18933
19167
 
18934
19168
  `;
18935
19169
  if (planResult) {
18936
- const taskMatch = planResult.content.match(new RegExp(`###\\s*\\d+\\.\\s*${taskInfo.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?(?=###|$)`, "i"));
19170
+ const taskMatch = planResult.content.match(new RegExp(`###\\s*\\d+\\.\\s*${taskInfo.name.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}[\\s\\S]*?(?=###|$)`, "i"));
18937
19171
  if (taskMatch) {
18938
19172
  specContent += `## Plan Section
18939
19173
 
@@ -18958,36 +19192,118 @@ ${priorTasks.join(`
18958
19192
  `;
18959
19193
  }
18960
19194
  taskService.writeSpec(feature, task, specContent);
19195
+ detectOmoSlim(toolContext);
19196
+ if (omoSlimDetected) {
19197
+ const contextFiles = [];
19198
+ const contextDir = path5.join(directory, ".hive", "features", feature, "context");
19199
+ if (fs6.existsSync(contextDir)) {
19200
+ const files = fs6.readdirSync(contextDir).filter((f) => f.endsWith(".md"));
19201
+ for (const file2 of files) {
19202
+ const content = fs6.readFileSync(path5.join(contextDir, file2), "utf-8");
19203
+ contextFiles.push({ name: file2, content });
19204
+ }
19205
+ }
19206
+ const previousTasks = allTasks.filter((t) => t.status === "done" && t.summary).map((t) => ({ name: t.folder, summary: t.summary }));
19207
+ const workerPrompt = buildWorkerPrompt({
19208
+ feature,
19209
+ task,
19210
+ taskOrder: parseInt(taskInfo.folder.match(/^(\d+)/)?.[1] || "0", 10),
19211
+ worktreePath: worktree.path,
19212
+ branch: worktree.branch,
19213
+ plan: planResult?.content || "No plan available",
19214
+ contextFiles,
19215
+ spec: specContent,
19216
+ previousTasks,
19217
+ continueFrom: continueFrom === "blocked" ? {
19218
+ status: "blocked",
19219
+ previousSummary: taskInfo.summary || "No previous summary",
19220
+ decision: decision || "No decision provided"
19221
+ } : undefined
19222
+ });
19223
+ const agent = selectAgent(taskInfo.name, specContent);
19224
+ try {
19225
+ const ctx2 = toolContext;
19226
+ if (ctx2.callTool) {
19227
+ const result = await ctx2.callTool("background_task", {
19228
+ agent,
19229
+ prompt: workerPrompt,
19230
+ description: `Hive: ${task}`,
19231
+ sync: false
19232
+ });
19233
+ taskService.update(feature, task, {
19234
+ status: "in_progress",
19235
+ workerId: result?.task_id,
19236
+ agent,
19237
+ mode: "omo-slim"
19238
+ });
19239
+ return JSON.stringify({
19240
+ worktreePath: worktree.path,
19241
+ branch: worktree.branch,
19242
+ mode: "delegated",
19243
+ agent,
19244
+ taskId: result?.task_id,
19245
+ message: `Worker spawned via OMO-Slim (${agent} agent). Watch in tmux pane. Use hive_worker_status to check progress.`
19246
+ }, null, 2);
19247
+ }
19248
+ } catch (e) {
19249
+ console.log("[Hive] OMO-Slim delegation failed, falling back to inline:", e.message);
19250
+ }
19251
+ }
18961
19252
  return `Worktree created at ${worktree.path}
18962
19253
  Branch: ${worktree.branch}
18963
19254
  Base commit: ${worktree.commit}
18964
- Spec: ${task}/spec.md generated`;
19255
+ Spec: ${task}/spec.md generated
19256
+ Reminder: do all work inside this worktree and ensure any subagents do the same.`;
18965
19257
  }
18966
19258
  }),
18967
19259
  hive_exec_complete: tool({
18968
- description: "Complete task: commit changes to branch, write report (does NOT merge or cleanup)",
19260
+ description: "Complete task: commit changes to branch, write report. Supports blocked/failed/partial status for worker communication.",
18969
19261
  args: {
18970
19262
  task: tool.schema.string().describe("Task folder name"),
18971
19263
  summary: tool.schema.string().describe("Summary of what was done"),
19264
+ status: tool.schema.enum(["completed", "blocked", "failed", "partial"]).optional().default("completed").describe("Task completion status"),
19265
+ blocker: tool.schema.object({
19266
+ reason: tool.schema.string().describe("Why the task is blocked"),
19267
+ options: tool.schema.array(tool.schema.string()).optional().describe("Available options for the user"),
19268
+ recommendation: tool.schema.string().optional().describe("Your recommended choice"),
19269
+ context: tool.schema.string().optional().describe("Additional context for the decision")
19270
+ }).optional().describe("Blocker info when status is blocked"),
18972
19271
  feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)")
18973
19272
  },
18974
- async execute({ task, summary, feature: explicitFeature }) {
19273
+ async execute({ task, summary, status = "completed", blocker, feature: explicitFeature }) {
18975
19274
  const feature = resolveFeature(explicitFeature);
18976
19275
  if (!feature)
18977
19276
  return "Error: No feature specified. Create a feature or provide feature param.";
18978
19277
  const taskInfo = taskService.get(feature, task);
18979
19278
  if (!taskInfo)
18980
19279
  return `Error: Task "${task}" not found`;
18981
- if (taskInfo.status !== "in_progress")
19280
+ if (taskInfo.status !== "in_progress" && taskInfo.status !== "blocked")
18982
19281
  return "Error: Task not in progress";
19282
+ if (status === "blocked") {
19283
+ taskService.update(feature, task, {
19284
+ status: "blocked",
19285
+ summary,
19286
+ blocker
19287
+ });
19288
+ const worktree2 = await worktreeService.get(feature, task);
19289
+ return JSON.stringify({
19290
+ status: "blocked",
19291
+ task,
19292
+ summary,
19293
+ blocker,
19294
+ worktreePath: worktree2?.path,
19295
+ message: 'Task blocked. Hive Master will ask user and resume with hive_exec_start(continueFrom: "blocked", decision: answer)'
19296
+ }, null, 2);
19297
+ }
18983
19298
  const commitResult = await worktreeService.commitChanges(feature, task, `hive(${task}): ${summary.slice(0, 50)}`);
18984
19299
  const diff = await worktreeService.getDiff(feature, task);
19300
+ const statusLabel = status === "completed" ? "success" : status;
18985
19301
  const reportLines = [
18986
19302
  `# Task Report: ${task}`,
18987
19303
  "",
18988
19304
  `**Feature:** ${feature}`,
18989
19305
  `**Completed:** ${new Date().toISOString()}`,
18990
- `**Status:** success`,
19306
+ `**Status:** ${statusLabel}`,
18991
19307
  `**Commit:** ${commitResult.sha || "none"}`,
18992
19308
  "",
18993
19309
  "---",
@@ -19011,9 +19327,10 @@ Spec: ${task}/spec.md generated`;
19011
19327
  }
19012
19328
  taskService.writeReport(feature, task, reportLines.join(`
19013
19329
  `));
19014
- taskService.update(feature, task, { status: "done", summary });
19330
+ const finalStatus = status === "completed" ? "done" : status;
19331
+ taskService.update(feature, task, { status: finalStatus, summary });
19015
19332
  const worktree = await worktreeService.get(feature, task);
19016
- return `Task "${task}" completed. Changes committed to branch ${worktree?.branch || "unknown"}.
19333
+ return `Task "${task}" ${status}. Changes committed to branch ${worktree?.branch || "unknown"}.
19017
19334
  Use hive_merge to integrate changes. Worktree preserved at ${worktree?.path || "unknown"}.`;
19018
19335
  }
19019
19336
  }),
@@ -19032,6 +19349,50 @@ Use hive_merge to integrate changes. Worktree preserved at ${worktree?.path || "
19032
19349
  return `Task "${task}" aborted. Status reset to pending.`;
19033
19350
  }
19034
19351
  }),
19352
+ hive_worker_status: tool({
19353
+ description: "Check status of delegated workers. Shows running workers, blockers, and progress.",
19354
+ args: {
19355
+ task: tool.schema.string().optional().describe("Specific task to check, or omit for all"),
19356
+ feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19357
+ },
19358
+ async execute({ task: specificTask, feature: explicitFeature }) {
19359
+ const feature = resolveFeature(explicitFeature);
19360
+ if (!feature)
19361
+ return "Error: No feature specified. Create a feature or provide feature param.";
19362
+ const STUCK_THRESHOLD = 10 * 60 * 1000;
19363
+ const now = Date.now();
19364
+ const tasks = taskService.list(feature);
19365
+ const inProgressTasks = tasks.filter((t) => (t.status === "in_progress" || t.status === "blocked") && (!specificTask || t.folder === specificTask));
19366
+ if (inProgressTasks.length === 0) {
19367
+ return specificTask ? `No active worker for task "${specificTask}"` : "No active workers.";
19368
+ }
19369
+ const workers = await Promise.all(inProgressTasks.map(async (t) => {
19370
+ const worktree = await worktreeService.get(feature, t.folder);
19371
+ const taskData = t;
19372
+ const startedAt = taskData.startedAt ? new Date(taskData.startedAt).getTime() : now;
19373
+ const maybeStuck = now - startedAt > STUCK_THRESHOLD && t.status === "in_progress";
19374
+ return {
19375
+ task: t.folder,
19376
+ name: t.name,
19377
+ status: t.status,
19378
+ agent: taskData.agent || "inline",
19379
+ mode: taskData.mode || "inline",
19380
+ workerId: taskData.workerId || null,
19381
+ worktreePath: worktree?.path || null,
19382
+ branch: worktree?.branch || null,
19383
+ maybeStuck,
19384
+ blocker: taskData.blocker || null,
19385
+ summary: t.summary || null
19386
+ };
19387
+ }));
19388
+ return JSON.stringify({
19389
+ feature,
19390
+ omoSlimDetected,
19391
+ workers,
19392
+ hint: workers.some((w) => w.status === "blocked") ? 'Use hive_exec_start(task, continueFrom: "blocked", decision: answer) to resume blocked workers' : workers.some((w) => w.maybeStuck) ? "Some workers may be stuck. Check tmux panes or abort with hive_exec_abort." : "Workers in progress. Watch tmux panes for live updates."
19393
+ }, null, 2);
19394
+ }
19395
+ }),
19035
19396
  hive_merge: tool({
19036
19397
  description: "Merge completed task branch into current branch (explicit integration)",
19037
19398
  args: {
@@ -19100,218 +19461,166 @@ Files changed: ${result.filesChanged?.length || 0}`;
19100
19461
  return `Context file written: ${filePath}`;
19101
19462
  }
19102
19463
  }),
19103
- hive_context_read: tool({
19104
- description: "Read a specific context file or all context for the feature",
19105
- args: {
19106
- name: tool.schema.string().optional().describe("Context file name. If omitted, returns all context compiled."),
19107
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19108
- },
19109
- async execute({ name, feature: explicitFeature }) {
19110
- const feature = resolveFeature(explicitFeature);
19111
- if (!feature)
19112
- return "Error: No feature specified. Create a feature or provide feature param.";
19113
- if (name) {
19114
- const content = contextService.read(feature, name);
19115
- if (!content)
19116
- return `Error: Context file '${name}' not found`;
19117
- return content;
19118
- }
19119
- const compiled = contextService.compile(feature);
19120
- if (!compiled)
19121
- return "No context files found";
19122
- return compiled;
19123
- }
19124
- }),
19125
- hive_context_list: tool({
19126
- description: "List all context files for the feature",
19464
+ hive_status: tool({
19465
+ description: "Get comprehensive status of a feature including plan, tasks, and context. Returns JSON with all relevant state for resuming work.",
19127
19466
  args: {
19128
19467
  feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19129
19468
  },
19130
19469
  async execute({ feature: explicitFeature }) {
19131
19470
  const feature = resolveFeature(explicitFeature);
19132
- if (!feature)
19133
- return "Error: No feature specified. Create a feature or provide feature param.";
19134
- const files = contextService.list(feature);
19135
- if (files.length === 0)
19136
- return "No context files";
19137
- return files.map((f) => `${f.name} (${f.content.length} chars, updated ${f.updatedAt})`).join(`
19138
- `);
19139
- }
19140
- }),
19141
- hive_session_open: tool({
19142
- description: "Open session, return full context for a feature",
19143
- args: {
19144
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)"),
19145
- task: tool.schema.string().optional().describe("Task folder to focus on")
19146
- },
19147
- async execute({ feature: explicitFeature, task }, toolContext) {
19148
- const feature = resolveFeature(explicitFeature);
19149
- if (!feature)
19150
- return "Error: No feature specified. Create a feature or provide feature param.";
19471
+ if (!feature) {
19472
+ return JSON.stringify({
19473
+ error: "No feature specified and no active feature found",
19474
+ hint: "Use hive_feature_create to create a new feature"
19475
+ });
19476
+ }
19151
19477
  const featureData = featureService.get(feature);
19152
- if (!featureData)
19153
- return `Error: Feature '${feature}' not found`;
19154
- const ctx2 = toolContext;
19155
- if (ctx2?.sessionID) {
19156
- sessionService.track(feature, ctx2.sessionID, task);
19478
+ if (!featureData) {
19479
+ return JSON.stringify({
19480
+ error: `Feature '${feature}' not found`,
19481
+ availableFeatures: featureService.list()
19482
+ });
19157
19483
  }
19158
- const planResult = planService.read(feature);
19484
+ const blocked = checkBlocked(feature);
19485
+ if (blocked)
19486
+ return blocked;
19487
+ const plan = planService.read(feature);
19159
19488
  const tasks = taskService.list(feature);
19160
- const contextCompiled = contextService.compile(feature);
19161
- const sessions = sessionService.list(feature);
19162
- let output = `## Feature: ${feature} [${featureData.status}]
19163
-
19164
- `;
19165
- if (planResult) {
19166
- output += `### Plan
19167
- ${planResult.content.substring(0, 500)}...
19168
-
19169
- `;
19170
- }
19171
- output += `### Tasks (${tasks.length})
19172
- `;
19173
- tasks.forEach((t) => {
19174
- output += `- ${t.folder}: ${t.name} [${t.status}]
19175
- `;
19176
- });
19177
- if (contextCompiled) {
19178
- output += `
19179
- ### Context
19180
- ${contextCompiled.substring(0, 500)}...
19181
- `;
19182
- }
19183
- output += `
19184
- ### Sessions (${sessions.length})
19185
- `;
19186
- sessions.forEach((s) => {
19187
- output += `- ${s.sessionId} (${s.taskFolder || "no task"})
19188
- `;
19489
+ const contextFiles = contextService.list(feature);
19490
+ const tasksSummary = tasks.map((t) => ({
19491
+ folder: t.folder,
19492
+ name: t.name,
19493
+ status: t.status,
19494
+ origin: t.origin || "plan"
19495
+ }));
19496
+ const contextSummary = contextFiles.map((c) => ({
19497
+ name: c.name,
19498
+ chars: c.content.length,
19499
+ updatedAt: c.updatedAt
19500
+ }));
19501
+ const pendingTasks = tasksSummary.filter((t) => t.status === "pending");
19502
+ const inProgressTasks = tasksSummary.filter((t) => t.status === "in_progress");
19503
+ const doneTasks = tasksSummary.filter((t) => t.status === "done");
19504
+ const getNextAction = (planStatus2, tasks2) => {
19505
+ if (!planStatus2 || planStatus2 === "draft") {
19506
+ return "Write or revise plan with hive_plan_write, then get approval";
19507
+ }
19508
+ if (planStatus2 === "review") {
19509
+ return "Wait for plan approval or revise based on comments";
19510
+ }
19511
+ if (tasks2.length === 0) {
19512
+ return "Generate tasks from plan with hive_tasks_sync";
19513
+ }
19514
+ const inProgress = tasks2.find((t) => t.status === "in_progress");
19515
+ if (inProgress) {
19516
+ return `Continue work on task: ${inProgress.folder}`;
19517
+ }
19518
+ const pending = tasks2.find((t) => t.status === "pending");
19519
+ if (pending) {
19520
+ return `Start next task with hive_exec_start: ${pending.folder}`;
19521
+ }
19522
+ return "All tasks complete. Review and merge or complete feature.";
19523
+ };
19524
+ const planStatus = featureData.status === "planning" ? "draft" : featureData.status === "approved" ? "approved" : featureData.status === "executing" ? "locked" : "none";
19525
+ return JSON.stringify({
19526
+ feature: {
19527
+ name: feature,
19528
+ status: featureData.status,
19529
+ ticket: featureData.ticket || null,
19530
+ createdAt: featureData.createdAt
19531
+ },
19532
+ plan: {
19533
+ exists: !!plan,
19534
+ status: planStatus,
19535
+ approved: planStatus === "approved" || planStatus === "locked"
19536
+ },
19537
+ tasks: {
19538
+ total: tasks.length,
19539
+ pending: pendingTasks.length,
19540
+ inProgress: inProgressTasks.length,
19541
+ done: doneTasks.length,
19542
+ list: tasksSummary
19543
+ },
19544
+ context: {
19545
+ fileCount: contextFiles.length,
19546
+ files: contextSummary
19547
+ },
19548
+ nextAction: getNextAction(planStatus, tasksSummary)
19189
19549
  });
19190
- return output;
19191
19550
  }
19192
19551
  }),
19193
- hive_session_list: tool({
19194
- description: "List all sessions for the feature",
19195
- args: {
19196
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19197
- },
19198
- async execute({ feature: explicitFeature }) {
19199
- const feature = resolveFeature(explicitFeature);
19200
- if (!feature)
19201
- return "Error: No feature specified. Create a feature or provide feature param.";
19202
- const sessions = sessionService.list(feature);
19203
- const master = sessionService.getMaster(feature);
19204
- if (sessions.length === 0)
19205
- return "No sessions";
19206
- return sessions.map((s) => {
19207
- const masterMark = s.sessionId === master ? " (master)" : "";
19208
- return `${s.sessionId}${masterMark} - ${s.taskFolder || "no task"} - ${s.lastActiveAt}`;
19209
- }).join(`
19210
- `);
19211
- }
19212
- }),
19213
- hive_subtask_create: tool({
19214
- description: "Create a subtask within a task. Use for TDD: create test/implement/verify subtasks.",
19552
+ hive_request_review: tool({
19553
+ description: "Request human review of completed task. BLOCKS until human approves or requests changes. Call after completing work, before merging.",
19215
19554
  args: {
19216
19555
  task: tool.schema.string().describe("Task folder name"),
19217
- name: tool.schema.string().describe("Subtask description"),
19218
- type: tool.schema.enum(["test", "implement", "review", "verify", "research", "debug", "custom"]).optional().describe("Subtask type"),
19556
+ summary: tool.schema.string().describe("Summary of what you did for human to review"),
19219
19557
  feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19220
19558
  },
19221
- async execute({ task, name, type, feature: explicitFeature }) {
19222
- const feature = resolveFeature(explicitFeature);
19223
- if (!feature)
19224
- return "Error: No feature specified. Create a feature or provide feature param.";
19225
- try {
19226
- const subtask = subtaskService.create(feature, task, name, type);
19227
- return `Subtask created: ${subtask.id} - ${subtask.name} [${subtask.type || "custom"}]`;
19228
- } catch (e) {
19229
- return `Error: ${e.message}`;
19230
- }
19231
- }
19232
- }),
19233
- hive_subtask_update: tool({
19234
- description: "Update subtask status",
19235
- args: {
19236
- task: tool.schema.string().describe("Task folder name"),
19237
- subtask: tool.schema.string().describe('Subtask ID (e.g., "1.1")'),
19238
- status: tool.schema.enum(["pending", "in_progress", "done", "cancelled"]).describe("New status"),
19239
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19240
- },
19241
- async execute({ task, subtask, status, feature: explicitFeature }) {
19559
+ async execute({ task, summary, feature: explicitFeature }) {
19242
19560
  const feature = resolveFeature(explicitFeature);
19243
19561
  if (!feature)
19244
- return "Error: No feature specified. Create a feature or provide feature param.";
19245
- try {
19246
- const updated = subtaskService.update(feature, task, subtask, status);
19247
- return `Subtask ${updated.id} updated: ${updated.status}`;
19248
- } catch (e) {
19249
- return `Error: ${e.message}`;
19562
+ return "Error: No feature specified.";
19563
+ const taskDir = path5.join(directory, ".hive", "features", feature, "tasks", task);
19564
+ if (!fs6.existsSync(taskDir)) {
19565
+ return `Error: Task '${task}' not found in feature '${feature}'`;
19250
19566
  }
19251
- }
19252
- }),
19253
- hive_subtask_list: tool({
19254
- description: "List all subtasks for a task",
19255
- args: {
19256
- task: tool.schema.string().describe("Task folder name"),
19257
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19258
- },
19259
- async execute({ task, feature: explicitFeature }) {
19260
- const feature = resolveFeature(explicitFeature);
19261
- if (!feature)
19262
- return "Error: No feature specified. Create a feature or provide feature param.";
19263
- try {
19264
- const subtasks = subtaskService.list(feature, task);
19265
- if (subtasks.length === 0)
19266
- return "No subtasks for this task.";
19267
- return subtasks.map((s) => {
19268
- const typeTag = s.type ? ` [${s.type}]` : "";
19269
- const statusIcon = s.status === "done" ? "✓" : s.status === "in_progress" ? "→" : "○";
19270
- return `${statusIcon} ${s.id}: ${s.name}${typeTag}`;
19271
- }).join(`
19272
- `);
19273
- } catch (e) {
19274
- return `Error: ${e.message}`;
19567
+ const reportPath = path5.join(taskDir, "report.md");
19568
+ const existingReport = fs6.existsSync(reportPath) ? fs6.readFileSync(reportPath, "utf-8") : `# Task Report
19569
+ `;
19570
+ const attemptCount = (existingReport.match(/## Attempt \d+/g) || []).length + 1;
19571
+ const timestamp = new Date().toISOString();
19572
+ const newContent = existingReport + `
19573
+ ## Attempt ${attemptCount}
19574
+
19575
+ **Requested**: ${timestamp}
19576
+
19577
+ ### Summary
19578
+
19579
+ ${summary}
19580
+
19581
+ `;
19582
+ fs6.writeFileSync(reportPath, newContent);
19583
+ const pendingPath = path5.join(taskDir, "PENDING_REVIEW");
19584
+ fs6.writeFileSync(pendingPath, JSON.stringify({
19585
+ attempt: attemptCount,
19586
+ requestedAt: timestamp,
19587
+ summary: summary.substring(0, 200) + (summary.length > 200 ? "..." : "")
19588
+ }, null, 2));
19589
+ const pollInterval = 2000;
19590
+ const maxWait = 30 * 60 * 1000;
19591
+ const startTime = Date.now();
19592
+ while (fs6.existsSync(pendingPath)) {
19593
+ if (Date.now() - startTime > maxWait) {
19594
+ return "Review timed out after 30 minutes. Human did not respond.";
19595
+ }
19596
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
19275
19597
  }
19276
- }
19277
- }),
19278
- hive_subtask_spec_write: tool({
19279
- description: "Write spec.md for a subtask (detailed instructions)",
19280
- args: {
19281
- task: tool.schema.string().describe("Task folder name"),
19282
- subtask: tool.schema.string().describe('Subtask ID (e.g., "1.1")'),
19283
- content: tool.schema.string().describe("Spec content (markdown)"),
19284
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19285
- },
19286
- async execute({ task, subtask, content, feature: explicitFeature }) {
19287
- const feature = resolveFeature(explicitFeature);
19288
- if (!feature)
19289
- return "Error: No feature specified. Create a feature or provide feature param.";
19290
- try {
19291
- const specPath = subtaskService.writeSpec(feature, task, subtask, content);
19292
- return `Subtask spec written: ${specPath}`;
19293
- } catch (e) {
19294
- return `Error: ${e.message}`;
19598
+ const resultPath = path5.join(taskDir, "REVIEW_RESULT");
19599
+ if (!fs6.existsSync(resultPath)) {
19600
+ return "Review cancelled (PENDING_REVIEW removed but no REVIEW_RESULT).";
19295
19601
  }
19296
- }
19297
- }),
19298
- hive_subtask_report_write: tool({
19299
- description: "Write report.md for a subtask (what was done)",
19300
- args: {
19301
- task: tool.schema.string().describe("Task folder name"),
19302
- subtask: tool.schema.string().describe('Subtask ID (e.g., "1.1")'),
19303
- content: tool.schema.string().describe("Report content (markdown)"),
19304
- feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
19305
- },
19306
- async execute({ task, subtask, content, feature: explicitFeature }) {
19307
- const feature = resolveFeature(explicitFeature);
19308
- if (!feature)
19309
- return "Error: No feature specified. Create a feature or provide feature param.";
19310
- try {
19311
- const reportPath = subtaskService.writeReport(feature, task, subtask, content);
19312
- return `Subtask report written: ${reportPath}`;
19313
- } catch (e) {
19314
- return `Error: ${e.message}`;
19602
+ const result = fs6.readFileSync(resultPath, "utf-8").trim();
19603
+ fs6.appendFileSync(reportPath, `### Review Result
19604
+
19605
+ ${result}
19606
+
19607
+ ---
19608
+
19609
+ `);
19610
+ if (result.toUpperCase() === "APPROVED") {
19611
+ return `✅ APPROVED
19612
+
19613
+ Your work has been approved. You may now merge:
19614
+
19615
+ hive_merge(task="${task}")
19616
+
19617
+ After merging, proceed to the next task.`;
19618
+ } else {
19619
+ return `\uD83D\uDD04 Changes Requested
19620
+
19621
+ ${result}
19622
+
19623
+ Make the requested changes, then call hive_request_review again.`;
19315
19624
  }
19316
19625
  }
19317
19626
  })
@@ -19326,6 +19635,62 @@ ${contextCompiled.substring(0, 500)}...
19326
19635
  return `Create feature "${name}" using hive_feature_create tool.`;
19327
19636
  }
19328
19637
  }
19638
+ },
19639
+ agent: {
19640
+ hive: {
19641
+ model: undefined,
19642
+ temperature: 0.7,
19643
+ description: "Hive Master - plan-first development with structured workflow and worker delegation",
19644
+ prompt: buildHiveAgentPrompt(undefined, false)
19645
+ }
19646
+ },
19647
+ systemPrompt: (existingPrompt) => {
19648
+ if (!existingPrompt.includes("Hive Master")) {
19649
+ return existingPrompt;
19650
+ }
19651
+ const featureNames = listFeatures(directory);
19652
+ let featureContext;
19653
+ for (const featureName of featureNames) {
19654
+ const feature = featureService.get(featureName);
19655
+ if (feature && ["planning", "executing"].includes(feature.status)) {
19656
+ const tasks = taskService.list(featureName);
19657
+ const pendingCount = tasks.filter((t) => t.status === "pending").length;
19658
+ const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
19659
+ const doneCount = tasks.filter((t) => t.status === "done").length;
19660
+ const contextDir = path5.join(directory, ".hive", "features", featureName, "context");
19661
+ const contextList = fs6.existsSync(contextDir) ? fs6.readdirSync(contextDir).filter((f) => f.endsWith(".md")) : [];
19662
+ const planResult = planService.read(featureName);
19663
+ let planStatus = "none";
19664
+ if (planResult) {
19665
+ planStatus = planResult.status === "approved" || planResult.status === "executing" ? "approved" : "draft";
19666
+ }
19667
+ featureContext = {
19668
+ name: featureName,
19669
+ planStatus,
19670
+ tasksSummary: `${doneCount} done, ${inProgressCount} in progress, ${pendingCount} pending`,
19671
+ contextList
19672
+ };
19673
+ break;
19674
+ }
19675
+ }
19676
+ if (featureContext || omoSlimDetected) {
19677
+ return buildHiveAgentPrompt(featureContext, omoSlimDetected);
19678
+ }
19679
+ return existingPrompt;
19680
+ },
19681
+ config: async (opencodeConfig) => {
19682
+ const hiveAgentConfig = {
19683
+ model: undefined,
19684
+ temperature: 0.7,
19685
+ description: "Hive Master - plan-first development with structured workflow and worker delegation",
19686
+ prompt: buildHiveAgentPrompt(undefined, false)
19687
+ };
19688
+ const configAgent = opencodeConfig.agent;
19689
+ if (!configAgent) {
19690
+ opencodeConfig.agent = { hive: hiveAgentConfig };
19691
+ } else {
19692
+ configAgent.hive = hiveAgentConfig;
19693
+ }
19329
19694
  }
19330
19695
  };
19331
19696
  };