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/agents/hive.d.ts +30 -0
- package/dist/index.js +930 -565
- package/dist/skills/builtin.d.ts +25 -0
- package/dist/skills/index.d.ts +7 -0
- package/dist/skills/types.d.ts +29 -0
- package/dist/utils/agent-selector.d.ts +21 -0
- package/dist/utils/worker-prompt.d.ts +42 -0
- package/package.json +6 -2
- package/skills/hive-execution/SKILL.md +293 -0
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
12821
|
-
var 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 =
|
|
12918
|
-
var 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 =
|
|
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:
|
|
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
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
18025
|
+
await fs7.writeFile(patchPath, diffContent);
|
|
18153
18026
|
const git = this.getGit();
|
|
18154
18027
|
await git.applyPatch(patchPath);
|
|
18155
|
-
await
|
|
18028
|
+
await fs7.unlink(patchPath).catch(() => {});
|
|
18156
18029
|
return { success: true, filesAffected: filesChanged };
|
|
18157
18030
|
} catch (error45) {
|
|
18158
|
-
await
|
|
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
|
|
18047
|
+
await fs7.writeFile(patchPath, diffContent);
|
|
18175
18048
|
const git = this.getGit();
|
|
18176
18049
|
await git.applyPatch(patchPath, ["-R"]);
|
|
18177
|
-
await
|
|
18050
|
+
await fs7.unlink(patchPath).catch(() => {});
|
|
18178
18051
|
return { success: true, filesAffected: filesChanged };
|
|
18179
18052
|
} catch (error45) {
|
|
18180
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
18115
|
+
const stat2 = await fs7.stat(featurePath).catch(() => null);
|
|
18243
18116
|
if (!stat2?.isDirectory())
|
|
18244
18117
|
continue;
|
|
18245
|
-
const steps = await
|
|
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
|
|
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
|
|
18139
|
+
const stat2 = await fs7.stat(featurePath).catch(() => null);
|
|
18267
18140
|
if (!stat2?.isDirectory())
|
|
18268
18141
|
continue;
|
|
18269
|
-
const steps = await
|
|
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
|
|
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
|
|
18166
|
+
await fs7.writeFile(patchPath, diffContent);
|
|
18294
18167
|
const git = this.getGit();
|
|
18295
18168
|
await git.applyPatch(patchPath, ["--check"]);
|
|
18296
|
-
await
|
|
18169
|
+
await fs7.unlink(patchPath).catch(() => {});
|
|
18297
18170
|
return [];
|
|
18298
18171
|
} catch (error45) {
|
|
18299
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
18516
|
-
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
}
|
|
18520
|
-
|
|
18521
|
-
|
|
18522
|
-
}
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
}
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
}
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
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
|
-
|
|
18561
|
-
|
|
18696
|
+
if (omoSlimDetected) {
|
|
18697
|
+
prompt += OOM_SLIM_SECTION;
|
|
18562
18698
|
}
|
|
18563
|
-
|
|
18564
|
-
|
|
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
|
-
|
|
18567
|
-
|
|
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
|
-
|
|
18570
|
-
|
|
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
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
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
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
if (
|
|
18595
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
18647
|
-
|
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18680
|
-
|
|
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
|
-
|
|
18683
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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(/[.*+?^${}()|[
|
|
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
|
|
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:**
|
|
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
|
-
|
|
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}"
|
|
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
|
-
|
|
19104
|
-
description: "
|
|
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
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
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
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19478
|
+
if (!featureData) {
|
|
19479
|
+
return JSON.stringify({
|
|
19480
|
+
error: `Feature '${feature}' not found`,
|
|
19481
|
+
availableFeatures: featureService.list()
|
|
19482
|
+
});
|
|
19157
19483
|
}
|
|
19158
|
-
const
|
|
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
|
|
19161
|
-
const
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
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
|
-
|
|
19194
|
-
description: "
|
|
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
|
-
|
|
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,
|
|
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.
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
return `
|
|
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
|
-
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
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
|
};
|