opencode-hive 0.8.0 → 0.8.2

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 (48) hide show
  1. package/dist/index.js +19251 -587
  2. package/package.json +4 -2
  3. package/dist/e2e/opencode-runtime-smoke.test.d.ts +0 -1
  4. package/dist/e2e/opencode-runtime-smoke.test.js +0 -243
  5. package/dist/e2e/plugin-smoke.test.d.ts +0 -1
  6. package/dist/e2e/plugin-smoke.test.js +0 -127
  7. package/dist/services/contextService.d.ts +0 -15
  8. package/dist/services/contextService.js +0 -59
  9. package/dist/services/featureService.d.ts +0 -14
  10. package/dist/services/featureService.js +0 -107
  11. package/dist/services/featureService.test.d.ts +0 -1
  12. package/dist/services/featureService.test.js +0 -127
  13. package/dist/services/index.d.ts +0 -5
  14. package/dist/services/index.js +0 -4
  15. package/dist/services/planService.d.ts +0 -11
  16. package/dist/services/planService.js +0 -59
  17. package/dist/services/planService.test.d.ts +0 -1
  18. package/dist/services/planService.test.js +0 -115
  19. package/dist/services/sessionService.d.ts +0 -31
  20. package/dist/services/sessionService.js +0 -125
  21. package/dist/services/taskService.d.ts +0 -29
  22. package/dist/services/taskService.js +0 -382
  23. package/dist/services/taskService.test.d.ts +0 -1
  24. package/dist/services/taskService.test.js +0 -290
  25. package/dist/services/worktreeService.d.ts +0 -66
  26. package/dist/services/worktreeService.js +0 -498
  27. package/dist/services/worktreeService.test.d.ts +0 -1
  28. package/dist/services/worktreeService.test.js +0 -185
  29. package/dist/tools/contextTools.d.ts +0 -93
  30. package/dist/tools/contextTools.js +0 -83
  31. package/dist/tools/execTools.d.ts +0 -66
  32. package/dist/tools/execTools.js +0 -125
  33. package/dist/tools/featureTools.d.ts +0 -60
  34. package/dist/tools/featureTools.js +0 -73
  35. package/dist/tools/planTools.d.ts +0 -47
  36. package/dist/tools/planTools.js +0 -65
  37. package/dist/tools/sessionTools.d.ts +0 -35
  38. package/dist/tools/sessionTools.js +0 -95
  39. package/dist/tools/taskTools.d.ts +0 -79
  40. package/dist/tools/taskTools.js +0 -86
  41. package/dist/types.d.ts +0 -106
  42. package/dist/types.js +0 -1
  43. package/dist/utils/detection.d.ts +0 -12
  44. package/dist/utils/detection.js +0 -73
  45. package/dist/utils/paths.d.ts +0 -23
  46. package/dist/utils/paths.js +0 -92
  47. package/dist/utils/paths.test.d.ts +0 -1
  48. package/dist/utils/paths.test.js +0 -100
@@ -1,127 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import * as fs from "fs";
3
- import { FeatureService } from "./featureService";
4
- import { getFeatureJsonPath, getFeaturePath } from "../utils/paths";
5
- const TEST_ROOT = "/tmp/hive-test-feature";
6
- describe("FeatureService", () => {
7
- let service;
8
- beforeEach(() => {
9
- fs.rmSync(TEST_ROOT, { recursive: true, force: true });
10
- fs.mkdirSync(TEST_ROOT, { recursive: true });
11
- service = new FeatureService(TEST_ROOT);
12
- });
13
- afterEach(() => {
14
- fs.rmSync(TEST_ROOT, { recursive: true, force: true });
15
- });
16
- describe("create", () => {
17
- it("creates a new feature with default status", () => {
18
- const feature = service.create("test-feature");
19
- expect(feature.name).toBe("test-feature");
20
- expect(feature.status).toBe("planning");
21
- expect(feature.createdAt).toBeDefined();
22
- expect(feature.ticket).toBeUndefined();
23
- });
24
- it("creates a feature with ticket", () => {
25
- const feature = service.create("test-feature", "JIRA-123");
26
- expect(feature.ticket).toBe("JIRA-123");
27
- });
28
- it("creates feature directory structure", () => {
29
- service.create("my-feature");
30
- const featurePath = getFeaturePath(TEST_ROOT, "my-feature");
31
- expect(fs.existsSync(featurePath)).toBe(true);
32
- expect(fs.existsSync(getFeatureJsonPath(TEST_ROOT, "my-feature"))).toBe(true);
33
- });
34
- it("throws if feature already exists", () => {
35
- service.create("existing");
36
- expect(() => service.create("existing")).toThrow();
37
- });
38
- });
39
- describe("get", () => {
40
- it("returns feature data", () => {
41
- service.create("test-feature", "TICKET-1");
42
- const feature = service.get("test-feature");
43
- expect(feature).not.toBeNull();
44
- expect(feature.name).toBe("test-feature");
45
- expect(feature.ticket).toBe("TICKET-1");
46
- });
47
- it("returns null for non-existing feature", () => {
48
- expect(service.get("nope")).toBeNull();
49
- });
50
- });
51
- describe("list", () => {
52
- it("returns empty array when no features", () => {
53
- expect(service.list()).toEqual([]);
54
- });
55
- it("returns all feature names", () => {
56
- service.create("feature-a");
57
- service.create("feature-b");
58
- service.create("feature-c");
59
- const features = service.list();
60
- expect(features).toContain("feature-a");
61
- expect(features).toContain("feature-b");
62
- expect(features).toContain("feature-c");
63
- expect(features.length).toBe(3);
64
- });
65
- });
66
- describe("updateStatus", () => {
67
- it("updates feature status", () => {
68
- service.create("test");
69
- service.updateStatus("test", "approved");
70
- const feature = service.get("test");
71
- expect(feature.status).toBe("approved");
72
- });
73
- it("sets approvedAt when status becomes approved", () => {
74
- service.create("test");
75
- service.updateStatus("test", "approved");
76
- const feature = service.get("test");
77
- expect(feature.approvedAt).toBeDefined();
78
- });
79
- it("sets completedAt when status becomes completed", () => {
80
- service.create("test");
81
- service.updateStatus("test", "completed");
82
- const feature = service.get("test");
83
- expect(feature.completedAt).toBeDefined();
84
- });
85
- });
86
- describe("complete", () => {
87
- it("marks feature as completed", () => {
88
- service.create("to-complete");
89
- service.complete("to-complete");
90
- const feature = service.get("to-complete");
91
- expect(feature.status).toBe("completed");
92
- expect(feature.completedAt).toBeDefined();
93
- });
94
- });
95
- describe("getInfo", () => {
96
- it("returns null for non-existing feature", () => {
97
- expect(service.getInfo("nope")).toBeNull();
98
- });
99
- it("returns feature info with tasks array", () => {
100
- service.create("info-test");
101
- const info = service.getInfo("info-test");
102
- expect(info).not.toBeNull();
103
- expect(info.name).toBe("info-test");
104
- expect(info.status).toBe("planning");
105
- expect(info.tasks).toEqual([]);
106
- expect(info.hasPlan).toBe(false);
107
- expect(info.commentCount).toBe(0);
108
- });
109
- });
110
- describe("session management", () => {
111
- it("setSession stores session ID", () => {
112
- service.create("session-test");
113
- service.setSession("session-test", "sess_12345");
114
- const feature = service.get("session-test");
115
- expect(feature.sessionId).toBe("sess_12345");
116
- });
117
- it("getSession retrieves session ID", () => {
118
- service.create("session-test");
119
- service.setSession("session-test", "sess_67890");
120
- expect(service.getSession("session-test")).toBe("sess_67890");
121
- });
122
- it("getSession returns undefined when no session", () => {
123
- service.create("no-session");
124
- expect(service.getSession("no-session")).toBeUndefined();
125
- });
126
- });
127
- });
@@ -1,5 +0,0 @@
1
- export { WorktreeService, createWorktreeService } from "./worktreeService.js";
2
- export type { WorktreeInfo, DiffResult, ApplyResult, WorktreeConfig } from "./worktreeService.js";
3
- export { FeatureService } from "./featureService.js";
4
- export { PlanService } from "./planService.js";
5
- export { TaskService } from "./taskService.js";
@@ -1,4 +0,0 @@
1
- export { WorktreeService, createWorktreeService } from "./worktreeService.js";
2
- export { FeatureService } from "./featureService.js";
3
- export { PlanService } from "./planService.js";
4
- export { TaskService } from "./taskService.js";
@@ -1,11 +0,0 @@
1
- import { PlanComment, PlanReadResult } from '../types.js';
2
- export declare class PlanService {
3
- private projectRoot;
4
- constructor(projectRoot: string);
5
- write(featureName: string, content: string): string;
6
- read(featureName: string): PlanReadResult | null;
7
- approve(featureName: string): void;
8
- getComments(featureName: string): PlanComment[];
9
- addComment(featureName: string, comment: Omit<PlanComment, 'id' | 'timestamp'>): PlanComment;
10
- clearComments(featureName: string): void;
11
- }
@@ -1,59 +0,0 @@
1
- import { getPlanPath, getCommentsPath, getFeatureJsonPath, readJson, writeJson, readText, writeText, fileExists, } from '../utils/paths.js';
2
- export class PlanService {
3
- projectRoot;
4
- constructor(projectRoot) {
5
- this.projectRoot = projectRoot;
6
- }
7
- write(featureName, content) {
8
- const planPath = getPlanPath(this.projectRoot, featureName);
9
- writeText(planPath, content);
10
- this.clearComments(featureName);
11
- return planPath;
12
- }
13
- read(featureName) {
14
- const planPath = getPlanPath(this.projectRoot, featureName);
15
- const content = readText(planPath);
16
- if (content === null)
17
- return null;
18
- const feature = readJson(getFeatureJsonPath(this.projectRoot, featureName));
19
- const comments = this.getComments(featureName);
20
- return {
21
- content,
22
- status: feature?.status || 'planning',
23
- comments,
24
- };
25
- }
26
- approve(featureName) {
27
- const featurePath = getFeatureJsonPath(this.projectRoot, featureName);
28
- const feature = readJson(featurePath);
29
- if (!feature)
30
- throw new Error(`Feature '${featureName}' not found`);
31
- if (!fileExists(getPlanPath(this.projectRoot, featureName))) {
32
- throw new Error(`No plan.md found for feature '${featureName}'`);
33
- }
34
- feature.status = 'approved';
35
- feature.approvedAt = new Date().toISOString();
36
- writeJson(featurePath, feature);
37
- }
38
- getComments(featureName) {
39
- const commentsPath = getCommentsPath(this.projectRoot, featureName);
40
- const data = readJson(commentsPath);
41
- return data?.threads || [];
42
- }
43
- addComment(featureName, comment) {
44
- const commentsPath = getCommentsPath(this.projectRoot, featureName);
45
- const data = readJson(commentsPath) || { threads: [] };
46
- const newComment = {
47
- ...comment,
48
- id: `comment-${Date.now()}`,
49
- timestamp: new Date().toISOString(),
50
- };
51
- data.threads.push(newComment);
52
- writeJson(commentsPath, data);
53
- return newComment;
54
- }
55
- clearComments(featureName) {
56
- const commentsPath = getCommentsPath(this.projectRoot, featureName);
57
- writeJson(commentsPath, { threads: [] });
58
- }
59
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import * as fs from "fs";
3
- import { PlanService } from "./planService";
4
- import { FeatureService } from "./featureService";
5
- import { getPlanPath, getCommentsPath } from "../utils/paths";
6
- const TEST_ROOT = "/tmp/hive-test-plan";
7
- describe("PlanService", () => {
8
- let planService;
9
- let featureService;
10
- beforeEach(() => {
11
- fs.rmSync(TEST_ROOT, { recursive: true, force: true });
12
- fs.mkdirSync(TEST_ROOT, { recursive: true });
13
- featureService = new FeatureService(TEST_ROOT);
14
- planService = new PlanService(TEST_ROOT);
15
- featureService.create("test-feature");
16
- });
17
- afterEach(() => {
18
- fs.rmSync(TEST_ROOT, { recursive: true, force: true });
19
- });
20
- describe("write", () => {
21
- it("writes plan content to file", () => {
22
- const content = "# My Plan\n\n## Tasks\n\n### 1. First Task";
23
- planService.write("test-feature", content);
24
- const planPath = getPlanPath(TEST_ROOT, "test-feature");
25
- expect(fs.readFileSync(planPath, "utf-8")).toBe(content);
26
- });
27
- it("clears existing comments when writing", () => {
28
- const commentsPath = getCommentsPath(TEST_ROOT, "test-feature");
29
- fs.writeFileSync(commentsPath, JSON.stringify({
30
- threads: [{ id: "1", line: 1, body: "test", author: "user", timestamp: new Date().toISOString() }]
31
- }));
32
- planService.write("test-feature", "# New Plan");
33
- const comments = planService.getComments("test-feature");
34
- expect(comments).toEqual([]);
35
- });
36
- });
37
- describe("read", () => {
38
- it("returns null when no plan exists", () => {
39
- featureService.create("empty-feature");
40
- const result = planService.read("empty-feature");
41
- expect(result).toBeNull();
42
- });
43
- it("returns plan content and status", () => {
44
- const content = "# Test Plan";
45
- planService.write("test-feature", content);
46
- const result = planService.read("test-feature");
47
- expect(result).not.toBeNull();
48
- expect(result.content).toBe(content);
49
- expect(result.status).toBe("planning");
50
- expect(result.comments).toEqual([]);
51
- });
52
- it("includes comments in result", () => {
53
- planService.write("test-feature", "# Plan");
54
- planService.addComment("test-feature", {
55
- line: 1,
56
- body: "Looks good!",
57
- author: "reviewer",
58
- });
59
- const result = planService.read("test-feature");
60
- expect(result.comments.length).toBe(1);
61
- expect(result.comments[0].body).toBe("Looks good!");
62
- });
63
- });
64
- describe("approve", () => {
65
- it("sets feature status to approved", () => {
66
- planService.write("test-feature", "# Plan");
67
- planService.approve("test-feature");
68
- const feature = featureService.get("test-feature");
69
- expect(feature.status).toBe("approved");
70
- });
71
- it("read returns approved status", () => {
72
- planService.write("test-feature", "# Plan");
73
- planService.approve("test-feature");
74
- const result = planService.read("test-feature");
75
- expect(result.status).toBe("approved");
76
- });
77
- });
78
- describe("comments", () => {
79
- it("addComment adds a comment", () => {
80
- planService.write("test-feature", "# Plan");
81
- planService.addComment("test-feature", {
82
- line: 5,
83
- body: "What about error handling?",
84
- author: "reviewer",
85
- });
86
- const comments = planService.getComments("test-feature");
87
- expect(comments.length).toBe(1);
88
- expect(comments[0].body).toBe("What about error handling?");
89
- expect(comments[0].id).toBeDefined();
90
- expect(comments[0].timestamp).toBeDefined();
91
- });
92
- it("getComments returns empty array when no comments", () => {
93
- planService.write("test-feature", "# Plan");
94
- expect(planService.getComments("test-feature")).toEqual([]);
95
- });
96
- it("clearComments removes all comments", () => {
97
- planService.write("test-feature", "# Plan");
98
- planService.addComment("test-feature", {
99
- line: 1,
100
- body: "Comment 1",
101
- author: "a",
102
- });
103
- planService.addComment("test-feature", {
104
- line: 2,
105
- body: "Comment 2",
106
- author: "b",
107
- });
108
- planService.clearComments("test-feature");
109
- expect(planService.getComments("test-feature")).toEqual([]);
110
- });
111
- it("getComments returns empty array for non-existing feature", () => {
112
- expect(planService.getComments("nope")).toEqual([]);
113
- });
114
- });
115
- });
@@ -1,31 +0,0 @@
1
- export interface SessionInfo {
2
- sessionId: string;
3
- taskFolder?: string;
4
- startedAt: string;
5
- lastActiveAt: string;
6
- messageCount?: number;
7
- }
8
- export interface SessionsJson {
9
- master?: string;
10
- sessions: SessionInfo[];
11
- }
12
- export declare class SessionService {
13
- private projectRoot;
14
- constructor(projectRoot: string);
15
- private getSessionsPath;
16
- private getSessions;
17
- private saveSessions;
18
- track(featureName: string, sessionId: string, taskFolder?: string): SessionInfo;
19
- setMaster(featureName: string, sessionId: string): void;
20
- getMaster(featureName: string): string | undefined;
21
- list(featureName: string): SessionInfo[];
22
- get(featureName: string, sessionId: string): SessionInfo | undefined;
23
- getByTask(featureName: string, taskFolder: string): SessionInfo | undefined;
24
- remove(featureName: string, sessionId: string): boolean;
25
- /**
26
- * Find which feature a session belongs to by searching all features
27
- */
28
- findFeatureBySession(sessionId: string): string | null;
29
- fork(featureName: string, fromSessionId?: string): SessionInfo;
30
- fresh(featureName: string, title?: string): SessionInfo;
31
- }
@@ -1,125 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { getFeaturePath, ensureDir, readJson, writeJson } from '../utils/paths.js';
4
- export class SessionService {
5
- projectRoot;
6
- constructor(projectRoot) {
7
- this.projectRoot = projectRoot;
8
- }
9
- getSessionsPath(featureName) {
10
- return path.join(getFeaturePath(this.projectRoot, featureName), 'sessions.json');
11
- }
12
- getSessions(featureName) {
13
- const sessionsPath = this.getSessionsPath(featureName);
14
- return readJson(sessionsPath) || { sessions: [] };
15
- }
16
- saveSessions(featureName, data) {
17
- const sessionsPath = this.getSessionsPath(featureName);
18
- ensureDir(path.dirname(sessionsPath));
19
- writeJson(sessionsPath, data);
20
- }
21
- track(featureName, sessionId, taskFolder) {
22
- const data = this.getSessions(featureName);
23
- const now = new Date().toISOString();
24
- let session = data.sessions.find(s => s.sessionId === sessionId);
25
- if (session) {
26
- session.lastActiveAt = now;
27
- if (taskFolder)
28
- session.taskFolder = taskFolder;
29
- }
30
- else {
31
- session = {
32
- sessionId,
33
- taskFolder,
34
- startedAt: now,
35
- lastActiveAt: now,
36
- };
37
- data.sessions.push(session);
38
- }
39
- if (!data.master) {
40
- data.master = sessionId;
41
- }
42
- this.saveSessions(featureName, data);
43
- return session;
44
- }
45
- setMaster(featureName, sessionId) {
46
- const data = this.getSessions(featureName);
47
- data.master = sessionId;
48
- this.saveSessions(featureName, data);
49
- }
50
- getMaster(featureName) {
51
- return this.getSessions(featureName).master;
52
- }
53
- list(featureName) {
54
- return this.getSessions(featureName).sessions;
55
- }
56
- get(featureName, sessionId) {
57
- return this.getSessions(featureName).sessions.find(s => s.sessionId === sessionId);
58
- }
59
- getByTask(featureName, taskFolder) {
60
- return this.getSessions(featureName).sessions.find(s => s.taskFolder === taskFolder);
61
- }
62
- remove(featureName, sessionId) {
63
- const data = this.getSessions(featureName);
64
- const index = data.sessions.findIndex(s => s.sessionId === sessionId);
65
- if (index === -1)
66
- return false;
67
- data.sessions.splice(index, 1);
68
- if (data.master === sessionId) {
69
- data.master = data.sessions[0]?.sessionId;
70
- }
71
- this.saveSessions(featureName, data);
72
- return true;
73
- }
74
- /**
75
- * Find which feature a session belongs to by searching all features
76
- */
77
- findFeatureBySession(sessionId) {
78
- const featuresPath = path.join(this.projectRoot, '.hive', 'features');
79
- if (!fs.existsSync(featuresPath))
80
- return null;
81
- const features = fs.readdirSync(featuresPath, { withFileTypes: true })
82
- .filter(d => d.isDirectory())
83
- .map(d => d.name);
84
- for (const feature of features) {
85
- const sessions = this.getSessions(feature);
86
- if (sessions.sessions.some(s => s.sessionId === sessionId)) {
87
- return feature;
88
- }
89
- if (sessions.master === sessionId) {
90
- return feature;
91
- }
92
- }
93
- return null;
94
- }
95
- fork(featureName, fromSessionId) {
96
- const data = this.getSessions(featureName);
97
- const now = new Date().toISOString();
98
- const sourceSession = fromSessionId
99
- ? data.sessions.find(s => s.sessionId === fromSessionId)
100
- : data.sessions.find(s => s.sessionId === data.master);
101
- const newSessionId = `ses_fork_${Date.now()}`;
102
- const newSession = {
103
- sessionId: newSessionId,
104
- taskFolder: sourceSession?.taskFolder,
105
- startedAt: now,
106
- lastActiveAt: now,
107
- };
108
- data.sessions.push(newSession);
109
- this.saveSessions(featureName, data);
110
- return newSession;
111
- }
112
- fresh(featureName, title) {
113
- const data = this.getSessions(featureName);
114
- const now = new Date().toISOString();
115
- const newSessionId = `ses_${title ? title.replace(/\s+/g, '_').toLowerCase() : Date.now()}`;
116
- const newSession = {
117
- sessionId: newSessionId,
118
- startedAt: now,
119
- lastActiveAt: now,
120
- };
121
- data.sessions.push(newSession);
122
- this.saveSessions(featureName, data);
123
- return newSession;
124
- }
125
- }
@@ -1,29 +0,0 @@
1
- import { TaskStatus, TaskStatusType, TasksSyncResult, TaskInfo, Subtask, SubtaskType } from '../types.js';
2
- export declare class TaskService {
3
- private projectRoot;
4
- constructor(projectRoot: string);
5
- sync(featureName: string): TasksSyncResult;
6
- create(featureName: string, name: string, order?: number): string;
7
- private createFromPlan;
8
- writeSpec(featureName: string, taskFolder: string, content: string): string;
9
- update(featureName: string, taskFolder: string, updates: Partial<Pick<TaskStatus, 'status' | 'summary' | 'baseCommit'>>): TaskStatus;
10
- get(featureName: string, taskFolder: string): TaskInfo | null;
11
- list(featureName: string): TaskInfo[];
12
- writeReport(featureName: string, taskFolder: string, report: string): string;
13
- private listFolders;
14
- private deleteTask;
15
- private getNextOrder;
16
- private parseTasksFromPlan;
17
- createSubtask(featureName: string, taskFolder: string, name: string, type?: SubtaskType): Subtask;
18
- updateSubtask(featureName: string, taskFolder: string, subtaskId: string, status: TaskStatusType): Subtask;
19
- listSubtasks(featureName: string, taskFolder: string): Subtask[];
20
- deleteSubtask(featureName: string, taskFolder: string, subtaskId: string): void;
21
- getSubtask(featureName: string, taskFolder: string, subtaskId: string): Subtask | null;
22
- writeSubtaskSpec(featureName: string, taskFolder: string, subtaskId: string, content: string): string;
23
- writeSubtaskReport(featureName: string, taskFolder: string, subtaskId: string, content: string): string;
24
- readSubtaskSpec(featureName: string, taskFolder: string, subtaskId: string): string | null;
25
- readSubtaskReport(featureName: string, taskFolder: string, subtaskId: string): string | null;
26
- private listSubtaskFolders;
27
- private findSubtaskFolder;
28
- private slugify;
29
- }