opencode-hive 0.8.0 → 0.8.1

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 -3
  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,66 +0,0 @@
1
- export interface WorktreeInfo {
2
- path: string;
3
- branch: string;
4
- commit: string;
5
- feature: string;
6
- step: string;
7
- }
8
- export interface DiffResult {
9
- hasDiff: boolean;
10
- diffContent: string;
11
- filesChanged: string[];
12
- insertions: number;
13
- deletions: number;
14
- }
15
- export interface ApplyResult {
16
- success: boolean;
17
- error?: string;
18
- filesAffected: string[];
19
- }
20
- export interface CommitResult {
21
- committed: boolean;
22
- sha: string;
23
- message?: string;
24
- }
25
- export interface MergeResult {
26
- success: boolean;
27
- merged: boolean;
28
- sha?: string;
29
- filesChanged?: string[];
30
- conflicts?: string[];
31
- error?: string;
32
- }
33
- export interface WorktreeConfig {
34
- baseDir: string;
35
- hiveDir: string;
36
- }
37
- export declare class WorktreeService {
38
- private config;
39
- constructor(config: WorktreeConfig);
40
- private getGit;
41
- private getWorktreesDir;
42
- private getWorktreePath;
43
- private getStepStatusPath;
44
- private getBranchName;
45
- create(feature: string, step: string, baseBranch?: string): Promise<WorktreeInfo>;
46
- get(feature: string, step: string): Promise<WorktreeInfo | null>;
47
- getDiff(feature: string, step: string, baseCommit?: string): Promise<DiffResult>;
48
- exportPatch(feature: string, step: string, baseBranch?: string): Promise<string>;
49
- applyDiff(feature: string, step: string, baseBranch?: string): Promise<ApplyResult>;
50
- revertDiff(feature: string, step: string, baseBranch?: string): Promise<ApplyResult>;
51
- private parseFilesFromDiff;
52
- revertFromSavedDiff(diffPath: string): Promise<ApplyResult>;
53
- remove(feature: string, step: string, deleteBranch?: boolean): Promise<void>;
54
- list(feature?: string): Promise<WorktreeInfo[]>;
55
- cleanup(feature?: string): Promise<{
56
- removed: string[];
57
- pruned: boolean;
58
- }>;
59
- checkConflicts(feature: string, step: string, baseBranch?: string): Promise<string[]>;
60
- checkConflictsFromSavedDiff(diffPath: string, reverse?: boolean): Promise<string[]>;
61
- commitChanges(feature: string, step: string, message?: string): Promise<CommitResult>;
62
- merge(feature: string, step: string, strategy?: 'merge' | 'squash' | 'rebase'): Promise<MergeResult>;
63
- hasUncommittedChanges(feature: string, step: string): Promise<boolean>;
64
- private parseConflictsFromError;
65
- }
66
- export declare function createWorktreeService(projectDir: string): WorktreeService;
@@ -1,498 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import simpleGit from "simple-git";
4
- export class WorktreeService {
5
- config;
6
- constructor(config) {
7
- this.config = config;
8
- }
9
- getGit(cwd) {
10
- return simpleGit(cwd || this.config.baseDir);
11
- }
12
- getWorktreesDir() {
13
- return path.join(this.config.hiveDir, ".worktrees");
14
- }
15
- getWorktreePath(feature, step) {
16
- return path.join(this.getWorktreesDir(), feature, step);
17
- }
18
- async getStepStatusPath(feature, step) {
19
- const featurePath = path.join(this.config.hiveDir, "features", feature);
20
- // Check v2 structure first (tasks/)
21
- const tasksPath = path.join(featurePath, "tasks", step, "status.json");
22
- try {
23
- await fs.access(tasksPath);
24
- return tasksPath;
25
- }
26
- catch { }
27
- // Fall back to v1 structure (execution/)
28
- return path.join(featurePath, "execution", step, "status.json");
29
- }
30
- getBranchName(feature, step) {
31
- return `hive/${feature}/${step}`;
32
- }
33
- async create(feature, step, baseBranch) {
34
- const worktreePath = this.getWorktreePath(feature, step);
35
- const branchName = this.getBranchName(feature, step);
36
- const git = this.getGit();
37
- await fs.mkdir(path.dirname(worktreePath), { recursive: true });
38
- const base = baseBranch || (await git.revparse(["HEAD"])).trim();
39
- const existing = await this.get(feature, step);
40
- if (existing) {
41
- return existing;
42
- }
43
- try {
44
- await git.raw(["worktree", "add", "-b", branchName, worktreePath, base]);
45
- }
46
- catch {
47
- try {
48
- await git.raw(["worktree", "add", worktreePath, branchName]);
49
- }
50
- catch (retryError) {
51
- throw new Error(`Failed to create worktree: ${retryError}`);
52
- }
53
- }
54
- const worktreeGit = this.getGit(worktreePath);
55
- const commit = (await worktreeGit.revparse(["HEAD"])).trim();
56
- return {
57
- path: worktreePath,
58
- branch: branchName,
59
- commit,
60
- feature,
61
- step,
62
- };
63
- }
64
- async get(feature, step) {
65
- const worktreePath = this.getWorktreePath(feature, step);
66
- const branchName = this.getBranchName(feature, step);
67
- try {
68
- await fs.access(worktreePath);
69
- const worktreeGit = this.getGit(worktreePath);
70
- const commit = (await worktreeGit.revparse(["HEAD"])).trim();
71
- return {
72
- path: worktreePath,
73
- branch: branchName,
74
- commit,
75
- feature,
76
- step,
77
- };
78
- }
79
- catch {
80
- return null;
81
- }
82
- }
83
- async getDiff(feature, step, baseCommit) {
84
- const worktreePath = this.getWorktreePath(feature, step);
85
- const statusPath = await this.getStepStatusPath(feature, step);
86
- let base = baseCommit;
87
- if (!base) {
88
- try {
89
- const status = JSON.parse(await fs.readFile(statusPath, "utf-8"));
90
- base = status.baseCommit; // Read baseCommit directly from task status
91
- }
92
- catch { }
93
- }
94
- if (!base) {
95
- base = "HEAD~1";
96
- }
97
- const worktreeGit = this.getGit(worktreePath);
98
- try {
99
- await worktreeGit.raw(["add", "-A"]);
100
- const status = await worktreeGit.status();
101
- const hasStaged = status.staged.length > 0;
102
- let diffContent = "";
103
- let stat = "";
104
- if (hasStaged) {
105
- diffContent = await worktreeGit.diff(["--cached"]);
106
- stat = diffContent ? await worktreeGit.diff(["--cached", "--stat"]) : "";
107
- }
108
- else {
109
- diffContent = await worktreeGit.diff([`${base}..HEAD`]).catch(() => "");
110
- stat = diffContent ? await worktreeGit.diff([`${base}..HEAD`, "--stat"]) : "";
111
- }
112
- const statLines = stat.split("\n").filter((l) => l.trim());
113
- const filesChanged = statLines
114
- .slice(0, -1)
115
- .map((line) => line.split("|")[0].trim())
116
- .filter(Boolean);
117
- const summaryLine = statLines[statLines.length - 1] || "";
118
- const insertMatch = summaryLine.match(/(\d+) insertion/);
119
- const deleteMatch = summaryLine.match(/(\d+) deletion/);
120
- return {
121
- hasDiff: diffContent.length > 0,
122
- diffContent,
123
- filesChanged,
124
- insertions: insertMatch ? parseInt(insertMatch[1], 10) : 0,
125
- deletions: deleteMatch ? parseInt(deleteMatch[1], 10) : 0,
126
- };
127
- }
128
- catch {
129
- return {
130
- hasDiff: false,
131
- diffContent: "",
132
- filesChanged: [],
133
- insertions: 0,
134
- deletions: 0,
135
- };
136
- }
137
- }
138
- async exportPatch(feature, step, baseBranch) {
139
- const worktreePath = this.getWorktreePath(feature, step);
140
- const patchPath = path.join(worktreePath, "..", `${step}.patch`);
141
- const base = baseBranch || "HEAD~1";
142
- const worktreeGit = this.getGit(worktreePath);
143
- const diff = await worktreeGit.diff([`${base}...HEAD`]);
144
- await fs.writeFile(patchPath, diff);
145
- return patchPath;
146
- }
147
- async applyDiff(feature, step, baseBranch) {
148
- const { hasDiff, diffContent, filesChanged } = await this.getDiff(feature, step, baseBranch);
149
- if (!hasDiff) {
150
- return { success: true, filesAffected: [] };
151
- }
152
- const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
153
- try {
154
- await fs.writeFile(patchPath, diffContent);
155
- const git = this.getGit();
156
- await git.applyPatch(patchPath);
157
- await fs.unlink(patchPath).catch(() => { });
158
- return { success: true, filesAffected: filesChanged };
159
- }
160
- catch (error) {
161
- await fs.unlink(patchPath).catch(() => { });
162
- const err = error;
163
- return {
164
- success: false,
165
- error: err.message || "Failed to apply patch",
166
- filesAffected: [],
167
- };
168
- }
169
- }
170
- async revertDiff(feature, step, baseBranch) {
171
- const { hasDiff, diffContent, filesChanged } = await this.getDiff(feature, step, baseBranch);
172
- if (!hasDiff) {
173
- return { success: true, filesAffected: [] };
174
- }
175
- const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}.patch`);
176
- try {
177
- await fs.writeFile(patchPath, diffContent);
178
- const git = this.getGit();
179
- await git.applyPatch(patchPath, ["-R"]);
180
- await fs.unlink(patchPath).catch(() => { });
181
- return { success: true, filesAffected: filesChanged };
182
- }
183
- catch (error) {
184
- await fs.unlink(patchPath).catch(() => { });
185
- const err = error;
186
- return {
187
- success: false,
188
- error: err.message || "Failed to revert patch",
189
- filesAffected: [],
190
- };
191
- }
192
- }
193
- parseFilesFromDiff(diffContent) {
194
- const files = [];
195
- const regex = /^diff --git a\/(.+?) b\//gm;
196
- let match;
197
- while ((match = regex.exec(diffContent)) !== null) {
198
- files.push(match[1]);
199
- }
200
- return [...new Set(files)];
201
- }
202
- async revertFromSavedDiff(diffPath) {
203
- const diffContent = await fs.readFile(diffPath, "utf-8");
204
- if (!diffContent.trim()) {
205
- return { success: true, filesAffected: [] };
206
- }
207
- const filesChanged = this.parseFilesFromDiff(diffContent);
208
- try {
209
- const git = this.getGit();
210
- await git.applyPatch(diffContent, ["-R"]);
211
- return { success: true, filesAffected: filesChanged };
212
- }
213
- catch (error) {
214
- const err = error;
215
- return {
216
- success: false,
217
- error: err.message || "Failed to revert patch",
218
- filesAffected: [],
219
- };
220
- }
221
- }
222
- async remove(feature, step, deleteBranch = false) {
223
- const worktreePath = this.getWorktreePath(feature, step);
224
- const branchName = this.getBranchName(feature, step);
225
- const git = this.getGit();
226
- try {
227
- await git.raw(["worktree", "remove", worktreePath, "--force"]);
228
- }
229
- catch {
230
- await fs.rm(worktreePath, { recursive: true, force: true });
231
- }
232
- try {
233
- await git.raw(["worktree", "prune"]);
234
- }
235
- catch {
236
- /* intentional */
237
- }
238
- if (deleteBranch) {
239
- try {
240
- await git.deleteLocalBranch(branchName, true);
241
- }
242
- catch {
243
- /* intentional */
244
- }
245
- }
246
- }
247
- async list(feature) {
248
- const worktreesDir = this.getWorktreesDir();
249
- const results = [];
250
- try {
251
- const features = feature ? [feature] : await fs.readdir(worktreesDir);
252
- for (const feat of features) {
253
- const featurePath = path.join(worktreesDir, feat);
254
- const stat = await fs.stat(featurePath).catch(() => null);
255
- if (!stat?.isDirectory())
256
- continue;
257
- const steps = await fs.readdir(featurePath).catch(() => []);
258
- for (const step of steps) {
259
- const info = await this.get(feat, step);
260
- if (info) {
261
- results.push(info);
262
- }
263
- }
264
- }
265
- }
266
- catch {
267
- /* intentional */
268
- }
269
- return results;
270
- }
271
- async cleanup(feature) {
272
- const removed = [];
273
- const git = this.getGit();
274
- try {
275
- await git.raw(["worktree", "prune"]);
276
- }
277
- catch {
278
- /* intentional */
279
- }
280
- const worktreesDir = this.getWorktreesDir();
281
- const features = feature ? [feature] : await fs.readdir(worktreesDir).catch(() => []);
282
- for (const feat of features) {
283
- const featurePath = path.join(worktreesDir, feat);
284
- const stat = await fs.stat(featurePath).catch(() => null);
285
- if (!stat?.isDirectory())
286
- continue;
287
- const steps = await fs.readdir(featurePath).catch(() => []);
288
- for (const step of steps) {
289
- const worktreePath = path.join(featurePath, step);
290
- const stepStat = await fs.stat(worktreePath).catch(() => null);
291
- if (!stepStat?.isDirectory())
292
- continue;
293
- try {
294
- const worktreeGit = this.getGit(worktreePath);
295
- await worktreeGit.revparse(["HEAD"]);
296
- }
297
- catch {
298
- await this.remove(feat, step, false);
299
- removed.push(worktreePath);
300
- }
301
- }
302
- }
303
- return { removed, pruned: true };
304
- }
305
- async checkConflicts(feature, step, baseBranch) {
306
- const { hasDiff, diffContent } = await this.getDiff(feature, step, baseBranch);
307
- if (!hasDiff) {
308
- return [];
309
- }
310
- const patchPath = path.join(this.config.hiveDir, ".worktrees", feature, `${step}-check.patch`);
311
- try {
312
- await fs.writeFile(patchPath, diffContent);
313
- const git = this.getGit();
314
- await git.applyPatch(patchPath, ["--check"]);
315
- await fs.unlink(patchPath).catch(() => { });
316
- return [];
317
- }
318
- catch (error) {
319
- await fs.unlink(patchPath).catch(() => { });
320
- const err = error;
321
- const stderr = err.message || "";
322
- const conflicts = stderr
323
- .split("\n")
324
- .filter((line) => line.includes("error: patch failed:"))
325
- .map((line) => {
326
- const match = line.match(/error: patch failed: (.+):/);
327
- return match ? match[1] : null;
328
- })
329
- .filter((f) => f !== null);
330
- return conflicts;
331
- }
332
- }
333
- async checkConflictsFromSavedDiff(diffPath, reverse = false) {
334
- try {
335
- await fs.access(diffPath);
336
- }
337
- catch {
338
- return [];
339
- }
340
- try {
341
- const git = this.getGit();
342
- const options = reverse ? ["--check", "-R"] : ["--check"];
343
- await git.applyPatch(diffPath, options);
344
- return [];
345
- }
346
- catch (error) {
347
- const err = error;
348
- const stderr = err.message || "";
349
- const conflicts = stderr
350
- .split("\n")
351
- .filter((line) => line.includes("error: patch failed:"))
352
- .map((line) => {
353
- const match = line.match(/error: patch failed: (.+):/);
354
- return match ? match[1] : null;
355
- })
356
- .filter((f) => f !== null);
357
- return conflicts;
358
- }
359
- }
360
- async commitChanges(feature, step, message) {
361
- const worktreePath = this.getWorktreePath(feature, step);
362
- try {
363
- await fs.access(worktreePath);
364
- }
365
- catch {
366
- return { committed: false, sha: "", message: "Worktree not found" };
367
- }
368
- const worktreeGit = this.getGit(worktreePath);
369
- try {
370
- await worktreeGit.add("-A");
371
- const status = await worktreeGit.status();
372
- const hasChanges = status.staged.length > 0 || status.modified.length > 0 || status.not_added.length > 0;
373
- if (!hasChanges) {
374
- const currentSha = (await worktreeGit.revparse(["HEAD"])).trim();
375
- return { committed: false, sha: currentSha, message: "No changes to commit" };
376
- }
377
- const commitMessage = message || `hive(${step}): task changes`;
378
- const result = await worktreeGit.commit(commitMessage, ["--allow-empty-message"]);
379
- return {
380
- committed: true,
381
- sha: result.commit,
382
- message: commitMessage,
383
- };
384
- }
385
- catch (error) {
386
- const err = error;
387
- const currentSha = (await worktreeGit.revparse(["HEAD"]).catch(() => "")).trim();
388
- return {
389
- committed: false,
390
- sha: currentSha,
391
- message: err.message || "Commit failed",
392
- };
393
- }
394
- }
395
- async merge(feature, step, strategy = 'merge') {
396
- const branchName = this.getBranchName(feature, step);
397
- const git = this.getGit();
398
- try {
399
- const branches = await git.branch();
400
- if (!branches.all.includes(branchName)) {
401
- return { success: false, merged: false, error: `Branch ${branchName} not found` };
402
- }
403
- const currentBranch = branches.current;
404
- const diffStat = await git.diff([`${currentBranch}...${branchName}`, "--stat"]);
405
- const filesChanged = diffStat
406
- .split("\n")
407
- .filter(l => l.trim() && l.includes("|"))
408
- .map(l => l.split("|")[0].trim());
409
- if (strategy === 'squash') {
410
- await git.raw(["merge", "--squash", branchName]);
411
- const result = await git.commit(`hive: merge ${step} (squashed)`);
412
- return {
413
- success: true,
414
- merged: true,
415
- sha: result.commit,
416
- filesChanged,
417
- };
418
- }
419
- else if (strategy === 'rebase') {
420
- const commits = await git.log([`${currentBranch}..${branchName}`]);
421
- const commitsToApply = [...commits.all].reverse();
422
- for (const commit of commitsToApply) {
423
- await git.raw(["cherry-pick", commit.hash]);
424
- }
425
- const head = (await git.revparse(["HEAD"])).trim();
426
- return {
427
- success: true,
428
- merged: true,
429
- sha: head,
430
- filesChanged,
431
- };
432
- }
433
- else {
434
- const result = await git.merge([branchName, "--no-ff", "-m", `hive: merge ${step}`]);
435
- const head = (await git.revparse(["HEAD"])).trim();
436
- return {
437
- success: true,
438
- merged: !result.failed,
439
- sha: head,
440
- filesChanged,
441
- conflicts: result.conflicts?.map(c => c.file || String(c)) || [],
442
- };
443
- }
444
- }
445
- catch (error) {
446
- const err = error;
447
- if (err.message?.includes("CONFLICT") || err.message?.includes("conflict")) {
448
- await git.raw(["merge", "--abort"]).catch(() => { });
449
- await git.raw(["rebase", "--abort"]).catch(() => { });
450
- await git.raw(["cherry-pick", "--abort"]).catch(() => { });
451
- return {
452
- success: false,
453
- merged: false,
454
- error: "Merge conflicts detected",
455
- conflicts: this.parseConflictsFromError(err.message || ""),
456
- };
457
- }
458
- return {
459
- success: false,
460
- merged: false,
461
- error: err.message || "Merge failed",
462
- };
463
- }
464
- }
465
- async hasUncommittedChanges(feature, step) {
466
- const worktreePath = this.getWorktreePath(feature, step);
467
- try {
468
- const worktreeGit = this.getGit(worktreePath);
469
- const status = await worktreeGit.status();
470
- return status.modified.length > 0 ||
471
- status.not_added.length > 0 ||
472
- status.staged.length > 0 ||
473
- status.deleted.length > 0 ||
474
- status.created.length > 0;
475
- }
476
- catch {
477
- return false;
478
- }
479
- }
480
- parseConflictsFromError(errorMessage) {
481
- const conflicts = [];
482
- const lines = errorMessage.split("\n");
483
- for (const line of lines) {
484
- if (line.includes("CONFLICT") && line.includes("Merge conflict in")) {
485
- const match = line.match(/Merge conflict in (.+)/);
486
- if (match)
487
- conflicts.push(match[1]);
488
- }
489
- }
490
- return conflicts;
491
- }
492
- }
493
- export function createWorktreeService(projectDir) {
494
- return new WorktreeService({
495
- baseDir: projectDir,
496
- hiveDir: path.join(projectDir, ".hive"),
497
- });
498
- }
@@ -1 +0,0 @@
1
- export {};