opencode-hive 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +0 -2
  2. package/dist/e2e/opencode-runtime-smoke.test.d.ts +1 -0
  3. package/dist/e2e/opencode-runtime-smoke.test.js +243 -0
  4. package/dist/e2e/plugin-smoke.test.d.ts +1 -0
  5. package/dist/e2e/plugin-smoke.test.js +127 -0
  6. package/dist/index.js +273 -75
  7. package/dist/services/contextService.d.ts +15 -0
  8. package/dist/services/contextService.js +59 -0
  9. package/dist/services/featureService.d.ts +0 -2
  10. package/dist/services/featureService.js +1 -11
  11. package/dist/services/featureService.test.d.ts +1 -0
  12. package/dist/services/featureService.test.js +127 -0
  13. package/dist/services/planService.test.d.ts +1 -0
  14. package/dist/services/planService.test.js +115 -0
  15. package/dist/services/sessionService.d.ts +31 -0
  16. package/dist/services/sessionService.js +125 -0
  17. package/dist/services/taskService.d.ts +2 -1
  18. package/dist/services/taskService.js +87 -12
  19. package/dist/services/taskService.test.d.ts +1 -0
  20. package/dist/services/taskService.test.js +159 -0
  21. package/dist/services/worktreeService.js +42 -17
  22. package/dist/services/worktreeService.test.d.ts +1 -0
  23. package/dist/services/worktreeService.test.js +117 -0
  24. package/dist/tools/contextTools.d.ts +93 -0
  25. package/dist/tools/contextTools.js +83 -0
  26. package/dist/tools/execTools.d.ts +3 -9
  27. package/dist/tools/execTools.js +14 -12
  28. package/dist/tools/featureTools.d.ts +4 -48
  29. package/dist/tools/featureTools.js +11 -51
  30. package/dist/tools/planTools.d.ts +5 -15
  31. package/dist/tools/planTools.js +16 -16
  32. package/dist/tools/sessionTools.d.ts +35 -0
  33. package/dist/tools/sessionTools.js +95 -0
  34. package/dist/tools/taskTools.d.ts +6 -18
  35. package/dist/tools/taskTools.js +18 -19
  36. package/dist/types.d.ts +35 -0
  37. package/dist/utils/detection.d.ts +12 -0
  38. package/dist/utils/detection.js +73 -0
  39. package/dist/utils/paths.d.ts +1 -1
  40. package/dist/utils/paths.js +2 -3
  41. package/dist/utils/paths.test.d.ts +1 -0
  42. package/dist/utils/paths.test.js +100 -0
  43. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
+ import * as fs from "fs";
3
+ import { TaskService } from "./taskService";
4
+ import { FeatureService } from "./featureService";
5
+ import { PlanService } from "./planService";
6
+ import { getTaskPath, getTaskStatusPath, getTaskReportPath } from "../utils/paths";
7
+ const TEST_ROOT = "/tmp/hive-test-task";
8
+ describe("TaskService", () => {
9
+ let taskService;
10
+ let featureService;
11
+ let planService;
12
+ beforeEach(() => {
13
+ fs.rmSync(TEST_ROOT, { recursive: true, force: true });
14
+ fs.mkdirSync(TEST_ROOT, { recursive: true });
15
+ featureService = new FeatureService(TEST_ROOT);
16
+ planService = new PlanService(TEST_ROOT);
17
+ taskService = new TaskService(TEST_ROOT);
18
+ featureService.create("test-feature");
19
+ });
20
+ afterEach(() => {
21
+ fs.rmSync(TEST_ROOT, { recursive: true, force: true });
22
+ });
23
+ describe("sync", () => {
24
+ it("throws when no plan exists", () => {
25
+ featureService.create("no-plan");
26
+ expect(() => taskService.sync("no-plan")).toThrow();
27
+ });
28
+ it("creates tasks from plan", () => {
29
+ planService.write("test-feature", `# Plan
30
+
31
+ ## Tasks
32
+
33
+ ### 1. Setup Database
34
+ Description
35
+
36
+ ### 2. Create API
37
+ Description
38
+ `);
39
+ const result = taskService.sync("test-feature");
40
+ expect(result.created).toContain("01-setup-database");
41
+ expect(result.created).toContain("02-create-api");
42
+ expect(result.created.length).toBe(2);
43
+ });
44
+ it("keeps done tasks even if removed from plan", () => {
45
+ planService.write("test-feature", `# Plan\n\n## Tasks\n\n### 1. First Task\nDesc`);
46
+ taskService.sync("test-feature");
47
+ taskService.update("test-feature", "01-first-task", { status: "done" });
48
+ planService.write("test-feature", `# Plan\n\n## Tasks\n\n### 1. Different Task\nDesc`);
49
+ const result = taskService.sync("test-feature");
50
+ expect(result.kept).toContain("01-first-task");
51
+ });
52
+ it("removes cancelled tasks", () => {
53
+ planService.write("test-feature", `# Plan\n\n## Tasks\n\n### 1. Task One\nDesc`);
54
+ taskService.sync("test-feature");
55
+ taskService.update("test-feature", "01-task-one", { status: "cancelled" });
56
+ const result = taskService.sync("test-feature");
57
+ expect(result.removed).toContain("01-task-one");
58
+ });
59
+ it("preserves manual tasks", () => {
60
+ planService.write("test-feature", `# Plan\n\n## Tasks\n\n### 1. Plan Task\nDesc`);
61
+ taskService.sync("test-feature");
62
+ taskService.create("test-feature", "manual-task");
63
+ const result = taskService.sync("test-feature");
64
+ expect(result.manual).toContain("02-manual-task");
65
+ });
66
+ });
67
+ describe("create", () => {
68
+ it("creates a manual task", () => {
69
+ const folder = taskService.create("test-feature", "my-task");
70
+ expect(folder).toBe("01-my-task");
71
+ expect(fs.existsSync(getTaskPath(TEST_ROOT, "test-feature", folder))).toBe(true);
72
+ });
73
+ it("auto-increments order", () => {
74
+ taskService.create("test-feature", "first");
75
+ taskService.create("test-feature", "second");
76
+ const third = taskService.create("test-feature", "third");
77
+ expect(third).toBe("03-third");
78
+ });
79
+ it("respects explicit order", () => {
80
+ const folder = taskService.create("test-feature", "specific", 10);
81
+ expect(folder).toBe("10-specific");
82
+ });
83
+ it("creates task with pending status and manual origin", () => {
84
+ const folder = taskService.create("test-feature", "test");
85
+ const task = taskService.get("test-feature", folder);
86
+ expect(task?.status).toBe("pending");
87
+ expect(task?.origin).toBe("manual");
88
+ });
89
+ });
90
+ describe("update", () => {
91
+ it("updates task status", () => {
92
+ const folder = taskService.create("test-feature", "task");
93
+ taskService.update("test-feature", folder, { status: "in_progress" });
94
+ const task = taskService.get("test-feature", folder);
95
+ expect(task?.status).toBe("in_progress");
96
+ });
97
+ it("sets startedAt when status becomes in_progress", () => {
98
+ const folder = taskService.create("test-feature", "task");
99
+ taskService.update("test-feature", folder, { status: "in_progress" });
100
+ const statusPath = getTaskStatusPath(TEST_ROOT, "test-feature", folder);
101
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
102
+ expect(status.startedAt).toBeDefined();
103
+ });
104
+ it("sets completedAt when status becomes done", () => {
105
+ const folder = taskService.create("test-feature", "task");
106
+ taskService.update("test-feature", folder, { status: "done" });
107
+ const statusPath = getTaskStatusPath(TEST_ROOT, "test-feature", folder);
108
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
109
+ expect(status.completedAt).toBeDefined();
110
+ });
111
+ it("updates summary", () => {
112
+ const folder = taskService.create("test-feature", "task");
113
+ taskService.update("test-feature", folder, { summary: "Completed setup" });
114
+ const task = taskService.get("test-feature", folder);
115
+ expect(task?.summary).toBe("Completed setup");
116
+ });
117
+ it("throws for non-existing task", () => {
118
+ expect(() => taskService.update("test-feature", "nope", { status: "done" })).toThrow();
119
+ });
120
+ });
121
+ describe("get", () => {
122
+ it("returns task info", () => {
123
+ const folder = taskService.create("test-feature", "my-task");
124
+ const task = taskService.get("test-feature", folder);
125
+ expect(task).not.toBeNull();
126
+ expect(task.folder).toBe("01-my-task");
127
+ expect(task.name).toBe("my-task");
128
+ expect(task.status).toBe("pending");
129
+ expect(task.origin).toBe("manual");
130
+ });
131
+ it("returns null for non-existing task", () => {
132
+ expect(taskService.get("test-feature", "nope")).toBeNull();
133
+ });
134
+ });
135
+ describe("list", () => {
136
+ it("returns empty array when no tasks", () => {
137
+ expect(taskService.list("test-feature")).toEqual([]);
138
+ });
139
+ it("returns all tasks sorted", () => {
140
+ taskService.create("test-feature", "third", 3);
141
+ taskService.create("test-feature", "first", 1);
142
+ taskService.create("test-feature", "second", 2);
143
+ const tasks = taskService.list("test-feature");
144
+ expect(tasks.length).toBe(3);
145
+ expect(tasks[0].folder).toBe("01-first");
146
+ expect(tasks[1].folder).toBe("02-second");
147
+ expect(tasks[2].folder).toBe("03-third");
148
+ });
149
+ });
150
+ describe("writeReport", () => {
151
+ it("writes report file", () => {
152
+ const folder = taskService.create("test-feature", "task");
153
+ const report = "## Summary\n\nCompleted the task successfully.";
154
+ taskService.writeReport("test-feature", folder, report);
155
+ const reportPath = getTaskReportPath(TEST_ROOT, "test-feature", folder);
156
+ expect(fs.readFileSync(reportPath, "utf-8")).toBe(report);
157
+ });
158
+ });
159
+ });
@@ -87,7 +87,7 @@ export class WorktreeService {
87
87
  if (!base) {
88
88
  try {
89
89
  const status = JSON.parse(await fs.readFile(statusPath, "utf-8"));
90
- base = status.execution?.baseCommit;
90
+ base = status.baseCommit; // Read baseCommit directly from task status
91
91
  }
92
92
  catch { }
93
93
  }
@@ -139,12 +139,16 @@ export class WorktreeService {
139
139
  if (!hasDiff) {
140
140
  return { success: true, filesAffected: [] };
141
141
  }
142
+ const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
142
143
  try {
144
+ await fs.writeFile(patchPath, diffContent);
143
145
  const git = this.getGit();
144
- await git.applyPatch(diffContent);
146
+ await git.applyPatch(patchPath);
147
+ await fs.unlink(patchPath).catch(() => { });
145
148
  return { success: true, filesAffected: filesChanged };
146
149
  }
147
150
  catch (error) {
151
+ await fs.unlink(patchPath).catch(() => { });
148
152
  const err = error;
149
153
  return {
150
154
  success: false,
@@ -158,12 +162,16 @@ export class WorktreeService {
158
162
  if (!hasDiff) {
159
163
  return { success: true, filesAffected: [] };
160
164
  }
165
+ const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
161
166
  try {
167
+ await fs.writeFile(patchPath, diffContent);
162
168
  const git = this.getGit();
163
- await git.applyPatch(diffContent, ["-R"]);
169
+ await git.applyPatch(patchPath, ["-R"]);
170
+ await fs.unlink(patchPath).catch(() => { });
164
171
  return { success: true, filesAffected: filesChanged };
165
172
  }
166
173
  catch (error) {
174
+ await fs.unlink(patchPath).catch(() => { });
167
175
  const err = error;
168
176
  return {
169
177
  success: false,
@@ -259,16 +267,27 @@ export class WorktreeService {
259
267
  catch {
260
268
  /* intentional */
261
269
  }
262
- const worktrees = await this.list(feature);
263
- for (const wt of worktrees) {
264
- try {
265
- await fs.access(wt.path);
266
- const worktreeGit = this.getGit(wt.path);
267
- await worktreeGit.revparse(["HEAD"]);
268
- }
269
- catch {
270
- await this.remove(wt.feature, wt.step, false);
271
- removed.push(wt.path);
270
+ const worktreesDir = this.getWorktreesDir();
271
+ const features = feature ? [feature] : await fs.readdir(worktreesDir).catch(() => []);
272
+ for (const feat of features) {
273
+ const featurePath = path.join(worktreesDir, feat);
274
+ const stat = await fs.stat(featurePath).catch(() => null);
275
+ if (!stat?.isDirectory())
276
+ continue;
277
+ const steps = await fs.readdir(featurePath).catch(() => []);
278
+ for (const step of steps) {
279
+ const worktreePath = path.join(featurePath, step);
280
+ const stepStat = await fs.stat(worktreePath).catch(() => null);
281
+ if (!stepStat?.isDirectory())
282
+ continue;
283
+ try {
284
+ const worktreeGit = this.getGit(worktreePath);
285
+ await worktreeGit.revparse(["HEAD"]);
286
+ }
287
+ catch {
288
+ await this.remove(feat, step, false);
289
+ removed.push(worktreePath);
290
+ }
272
291
  }
273
292
  }
274
293
  return { removed, pruned: true };
@@ -278,12 +297,16 @@ export class WorktreeService {
278
297
  if (!hasDiff) {
279
298
  return [];
280
299
  }
300
+ const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}-check.patch`);
281
301
  try {
302
+ await fs.writeFile(patchPath, diffContent);
282
303
  const git = this.getGit();
283
- await git.applyPatch(diffContent, ["--check"]);
304
+ await git.applyPatch(patchPath, ["--check"]);
305
+ await fs.unlink(patchPath).catch(() => { });
284
306
  return [];
285
307
  }
286
308
  catch (error) {
309
+ await fs.unlink(patchPath).catch(() => { });
287
310
  const err = error;
288
311
  const stderr = err.message || "";
289
312
  const conflicts = stderr
@@ -298,14 +321,16 @@ export class WorktreeService {
298
321
  }
299
322
  }
300
323
  async checkConflictsFromSavedDiff(diffPath, reverse = false) {
301
- const diffContent = await fs.readFile(diffPath, "utf-8");
302
- if (!diffContent.trim()) {
324
+ try {
325
+ await fs.access(diffPath);
326
+ }
327
+ catch {
303
328
  return [];
304
329
  }
305
330
  try {
306
331
  const git = this.getGit();
307
332
  const options = reverse ? ["--check", "-R"] : ["--check"];
308
- await git.applyPatch(diffContent, options);
333
+ await git.applyPatch(diffPath, options);
309
334
  return [];
310
335
  }
311
336
  catch (error) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { WorktreeService } from "./worktreeService";
5
+ const TEST_ROOT = "/tmp/hive-test-worktree";
6
+ describe("WorktreeService", () => {
7
+ let service;
8
+ beforeEach(async () => {
9
+ fs.rmSync(TEST_ROOT, { recursive: true, force: true });
10
+ fs.mkdirSync(TEST_ROOT, { recursive: true });
11
+ const { execSync } = await import("child_process");
12
+ execSync("git init", { cwd: TEST_ROOT });
13
+ execSync("git config user.email 'test@test.com'", { cwd: TEST_ROOT });
14
+ execSync("git config user.name 'Test'", { cwd: TEST_ROOT });
15
+ fs.writeFileSync(path.join(TEST_ROOT, "README.md"), "# Test");
16
+ execSync("git add . && git commit -m 'init'", { cwd: TEST_ROOT });
17
+ service = new WorktreeService({ baseDir: TEST_ROOT, hiveDir: path.join(TEST_ROOT, ".hive") });
18
+ });
19
+ afterEach(() => {
20
+ fs.rmSync(TEST_ROOT, { recursive: true, force: true });
21
+ });
22
+ describe("create", () => {
23
+ it("creates a worktree", async () => {
24
+ const result = await service.create("my-feature", "01-setup");
25
+ expect(result.path).toContain(".hive/.worktrees/my-feature/01-setup");
26
+ expect(result.branch).toBe("hive/my-feature/01-setup");
27
+ expect(fs.existsSync(result.path)).toBe(true);
28
+ });
29
+ it("worktree contains files from base branch", async () => {
30
+ const result = await service.create("my-feature", "01-setup");
31
+ expect(fs.existsSync(path.join(result.path, "README.md"))).toBe(true);
32
+ });
33
+ });
34
+ describe("get", () => {
35
+ it("returns null for non-existing worktree", async () => {
36
+ const result = await service.get("nope", "nope");
37
+ expect(result).toBeNull();
38
+ });
39
+ it("returns worktree info after creation", async () => {
40
+ await service.create("my-feature", "01-task");
41
+ const result = await service.get("my-feature", "01-task");
42
+ expect(result).not.toBeNull();
43
+ expect(result.feature).toBe("my-feature");
44
+ expect(result.step).toBe("01-task");
45
+ });
46
+ });
47
+ describe("list", () => {
48
+ it("returns empty array when no worktrees", async () => {
49
+ const result = await service.list();
50
+ expect(result).toEqual([]);
51
+ });
52
+ it("lists all worktrees", async () => {
53
+ await service.create("feature-a", "01-task");
54
+ await service.create("feature-b", "01-task");
55
+ const result = await service.list();
56
+ expect(result.length).toBe(2);
57
+ });
58
+ it("filters by feature", async () => {
59
+ await service.create("feature-a", "01-task");
60
+ await service.create("feature-a", "02-task");
61
+ await service.create("feature-b", "01-task");
62
+ const result = await service.list("feature-a");
63
+ expect(result.length).toBe(2);
64
+ expect(result.every(w => w.feature === "feature-a")).toBe(true);
65
+ });
66
+ });
67
+ describe("remove", () => {
68
+ it("removes worktree", async () => {
69
+ const created = await service.create("my-feature", "01-task");
70
+ expect(fs.existsSync(created.path)).toBe(true);
71
+ await service.remove("my-feature", "01-task");
72
+ expect(fs.existsSync(created.path)).toBe(false);
73
+ });
74
+ it("removes branch when deleteBranch is true", async () => {
75
+ await service.create("my-feature", "01-task");
76
+ const { execSync } = await import("child_process");
77
+ await service.remove("my-feature", "01-task", true);
78
+ const branches = execSync("git branch", { cwd: TEST_ROOT, encoding: "utf-8" });
79
+ expect(branches).not.toContain("hive/my-feature/01-task");
80
+ });
81
+ });
82
+ describe("getDiff", () => {
83
+ it("returns empty diff when no changes", async () => {
84
+ await service.create("my-feature", "01-task");
85
+ const diff = await service.getDiff("my-feature", "01-task");
86
+ expect(diff.hasDiff).toBe(false);
87
+ expect(diff.diffContent).toBe("");
88
+ expect(diff.filesChanged).toEqual([]);
89
+ });
90
+ it("returns diff when files changed", async () => {
91
+ const worktree = await service.create("my-feature", "01-task");
92
+ fs.writeFileSync(path.join(worktree.path, "new-file.txt"), "content");
93
+ const { execSync } = await import("child_process");
94
+ execSync("git add .", { cwd: worktree.path });
95
+ execSync("git commit -m 'add file'", { cwd: worktree.path });
96
+ const diff = await service.getDiff("my-feature", "01-task");
97
+ expect(diff.hasDiff).toBe(true);
98
+ expect(diff.filesChanged).toContain("new-file.txt");
99
+ });
100
+ });
101
+ describe("cleanup", () => {
102
+ it("removes invalid worktrees for a feature", async () => {
103
+ const wt1 = await service.create("cleanup-test", "01-task");
104
+ const wt2 = await service.create("cleanup-test", "02-task");
105
+ fs.writeFileSync(path.join(wt1.path, ".git"), "gitdir: /nonexistent\n");
106
+ fs.writeFileSync(path.join(wt2.path, ".git"), "gitdir: /nonexistent\n");
107
+ expect(fs.existsSync(wt1.path)).toBe(true);
108
+ expect(fs.existsSync(wt2.path)).toBe(true);
109
+ const result = await service.cleanup("cleanup-test");
110
+ expect(result.removed.length).toBe(2);
111
+ expect(fs.existsSync(wt1.path)).toBe(false);
112
+ expect(fs.existsSync(wt2.path)).toBe(false);
113
+ const remaining = await service.list("cleanup-test");
114
+ expect(remaining).toEqual([]);
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ export declare function createContextTools(projectRoot: string): {
3
+ hive_context_write: {
4
+ description: string;
5
+ parameters: z.ZodObject<{
6
+ name: z.ZodString;
7
+ content: z.ZodString;
8
+ }, z.core.$strip>;
9
+ execute: ({ name, content }: {
10
+ name: string;
11
+ content: string;
12
+ }) => Promise<{
13
+ error: string;
14
+ success?: undefined;
15
+ path?: undefined;
16
+ } | {
17
+ success: boolean;
18
+ path: string;
19
+ error?: undefined;
20
+ }>;
21
+ };
22
+ hive_context_read: {
23
+ description: string;
24
+ parameters: z.ZodObject<{
25
+ name: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ execute: ({ name }: {
28
+ name?: string;
29
+ }) => Promise<{
30
+ error: string;
31
+ name?: undefined;
32
+ content?: undefined;
33
+ message?: undefined;
34
+ compiled?: undefined;
35
+ } | {
36
+ name: string;
37
+ content: string;
38
+ error?: undefined;
39
+ message?: undefined;
40
+ compiled?: undefined;
41
+ } | {
42
+ message: string;
43
+ error?: undefined;
44
+ name?: undefined;
45
+ content?: undefined;
46
+ compiled?: undefined;
47
+ } | {
48
+ compiled: string;
49
+ error?: undefined;
50
+ name?: undefined;
51
+ content?: undefined;
52
+ message?: undefined;
53
+ }>;
54
+ };
55
+ hive_context_list: {
56
+ description: string;
57
+ parameters: z.ZodObject<{}, z.core.$strip>;
58
+ execute: () => Promise<{
59
+ error: string;
60
+ files?: undefined;
61
+ message?: undefined;
62
+ } | {
63
+ files: never[];
64
+ message: string;
65
+ error?: undefined;
66
+ } | {
67
+ files: {
68
+ name: string;
69
+ updatedAt: string;
70
+ previewLength: number;
71
+ }[];
72
+ error?: undefined;
73
+ message?: undefined;
74
+ }>;
75
+ };
76
+ hive_context_delete: {
77
+ description: string;
78
+ parameters: z.ZodObject<{
79
+ name: z.ZodString;
80
+ }, z.core.$strip>;
81
+ execute: ({ name }: {
82
+ name: string;
83
+ }) => Promise<{
84
+ error: string;
85
+ success?: undefined;
86
+ deleted?: undefined;
87
+ } | {
88
+ success: boolean;
89
+ deleted: string;
90
+ error?: undefined;
91
+ }>;
92
+ };
93
+ };
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ import { ContextService } from '../services/contextService.js';
3
+ import { FeatureService } from '../services/featureService.js';
4
+ import { detectContext } from '../utils/detection.js';
5
+ export function createContextTools(projectRoot) {
6
+ const contextService = new ContextService(projectRoot);
7
+ const featureService = new FeatureService(projectRoot);
8
+ const getActiveFeature = () => {
9
+ const ctx = detectContext(projectRoot);
10
+ return ctx?.feature || null;
11
+ };
12
+ return {
13
+ hive_context_write: {
14
+ description: 'Write a context file for the active feature. Context files store persistent notes, decisions, and reference material.',
15
+ parameters: z.object({
16
+ name: z.string().describe('Context file name (e.g., "architecture", "decisions", "notes")'),
17
+ content: z.string().describe('Markdown content to write'),
18
+ }),
19
+ execute: async ({ name, content }) => {
20
+ const feature = getActiveFeature();
21
+ if (!feature)
22
+ return { error: 'No active feature' };
23
+ const filePath = contextService.write(feature, name, content);
24
+ return { success: true, path: filePath };
25
+ },
26
+ },
27
+ hive_context_read: {
28
+ description: 'Read a specific context file or all context for the active feature',
29
+ parameters: z.object({
30
+ name: z.string().optional().describe('Context file name. If omitted, returns all context compiled.'),
31
+ }),
32
+ execute: async ({ name }) => {
33
+ const feature = getActiveFeature();
34
+ if (!feature)
35
+ return { error: 'No active feature' };
36
+ if (name) {
37
+ const content = contextService.read(feature, name);
38
+ if (!content)
39
+ return { error: `Context file '${name}' not found` };
40
+ return { name, content };
41
+ }
42
+ const compiled = contextService.compile(feature);
43
+ if (!compiled)
44
+ return { message: 'No context files found' };
45
+ return { compiled };
46
+ },
47
+ },
48
+ hive_context_list: {
49
+ description: 'List all context files for the active feature',
50
+ parameters: z.object({}),
51
+ execute: async () => {
52
+ const feature = getActiveFeature();
53
+ if (!feature)
54
+ return { error: 'No active feature' };
55
+ const files = contextService.list(feature);
56
+ if (files.length === 0)
57
+ return { files: [], message: 'No context files' };
58
+ return {
59
+ files: files.map(f => ({
60
+ name: f.name,
61
+ updatedAt: f.updatedAt,
62
+ previewLength: f.content.length,
63
+ })),
64
+ };
65
+ },
66
+ },
67
+ hive_context_delete: {
68
+ description: 'Delete a context file',
69
+ parameters: z.object({
70
+ name: z.string().describe('Context file name to delete'),
71
+ }),
72
+ execute: async ({ name }) => {
73
+ const feature = getActiveFeature();
74
+ if (!feature)
75
+ return { error: 'No active feature' };
76
+ const deleted = contextService.delete(feature, name);
77
+ if (!deleted)
78
+ return { error: `Context file '${name}' not found` };
79
+ return { success: true, deleted: name };
80
+ },
81
+ },
82
+ };
83
+ }
@@ -4,11 +4,9 @@ export declare function createExecTools(projectRoot: string): {
4
4
  description: string;
5
5
  parameters: z.ZodObject<{
6
6
  task: z.ZodString;
7
- featureName: z.ZodOptional<z.ZodString>;
8
7
  }, z.core.$strip>;
9
- execute: ({ task, featureName }: {
8
+ execute: ({ task }: {
10
9
  task: string;
11
- featureName?: string;
12
10
  }) => Promise<{
13
11
  error: string;
14
12
  worktreePath?: undefined;
@@ -27,13 +25,11 @@ export declare function createExecTools(projectRoot: string): {
27
25
  task: z.ZodString;
28
26
  summary: z.ZodString;
29
27
  report: z.ZodOptional<z.ZodString>;
30
- featureName: z.ZodOptional<z.ZodString>;
31
28
  }, z.core.$strip>;
32
- execute: ({ task, summary, report, featureName }: {
29
+ execute: ({ task, summary, report }: {
33
30
  task: string;
34
31
  summary: string;
35
32
  report?: string;
36
- featureName?: string;
37
33
  }) => Promise<{
38
34
  error: string;
39
35
  completed?: undefined;
@@ -52,11 +48,9 @@ export declare function createExecTools(projectRoot: string): {
52
48
  description: string;
53
49
  parameters: z.ZodObject<{
54
50
  task: z.ZodString;
55
- featureName: z.ZodOptional<z.ZodString>;
56
51
  }, z.core.$strip>;
57
- execute: ({ task, featureName }: {
52
+ execute: ({ task }: {
58
53
  task: string;
59
- featureName?: string;
60
54
  }) => Promise<{
61
55
  error: string;
62
56
  aborted?: undefined;