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.
- package/README.md +0 -2
- package/dist/e2e/opencode-runtime-smoke.test.d.ts +1 -0
- package/dist/e2e/opencode-runtime-smoke.test.js +243 -0
- package/dist/e2e/plugin-smoke.test.d.ts +1 -0
- package/dist/e2e/plugin-smoke.test.js +127 -0
- package/dist/index.js +273 -75
- package/dist/services/contextService.d.ts +15 -0
- package/dist/services/contextService.js +59 -0
- package/dist/services/featureService.d.ts +0 -2
- package/dist/services/featureService.js +1 -11
- package/dist/services/featureService.test.d.ts +1 -0
- package/dist/services/featureService.test.js +127 -0
- package/dist/services/planService.test.d.ts +1 -0
- package/dist/services/planService.test.js +115 -0
- package/dist/services/sessionService.d.ts +31 -0
- package/dist/services/sessionService.js +125 -0
- package/dist/services/taskService.d.ts +2 -1
- package/dist/services/taskService.js +87 -12
- package/dist/services/taskService.test.d.ts +1 -0
- package/dist/services/taskService.test.js +159 -0
- package/dist/services/worktreeService.js +42 -17
- package/dist/services/worktreeService.test.d.ts +1 -0
- package/dist/services/worktreeService.test.js +117 -0
- package/dist/tools/contextTools.d.ts +93 -0
- package/dist/tools/contextTools.js +83 -0
- package/dist/tools/execTools.d.ts +3 -9
- package/dist/tools/execTools.js +14 -12
- package/dist/tools/featureTools.d.ts +4 -48
- package/dist/tools/featureTools.js +11 -51
- package/dist/tools/planTools.d.ts +5 -15
- package/dist/tools/planTools.js +16 -16
- package/dist/tools/sessionTools.d.ts +35 -0
- package/dist/tools/sessionTools.js +95 -0
- package/dist/tools/taskTools.d.ts +6 -18
- package/dist/tools/taskTools.js +18 -19
- package/dist/types.d.ts +35 -0
- package/dist/utils/detection.d.ts +12 -0
- package/dist/utils/detection.js +73 -0
- package/dist/utils/paths.d.ts +1 -1
- package/dist/utils/paths.js +2 -3
- package/dist/utils/paths.test.d.ts +1 -0
- package/dist/utils/paths.test.js +100 -0
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { getFeaturesPath, getFeaturePath, readJson } from './paths.js';
|
|
4
|
+
export function detectContext(cwd) {
|
|
5
|
+
const result = {
|
|
6
|
+
projectRoot: cwd,
|
|
7
|
+
feature: null,
|
|
8
|
+
task: null,
|
|
9
|
+
isWorktree: false,
|
|
10
|
+
mainProjectRoot: null,
|
|
11
|
+
};
|
|
12
|
+
const worktreeMatch = cwd.match(/(.+)\/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
|
|
13
|
+
if (worktreeMatch) {
|
|
14
|
+
result.mainProjectRoot = worktreeMatch[1];
|
|
15
|
+
result.feature = worktreeMatch[2];
|
|
16
|
+
result.task = worktreeMatch[3];
|
|
17
|
+
result.isWorktree = true;
|
|
18
|
+
result.projectRoot = worktreeMatch[1];
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
const gitPath = path.join(cwd, '.git');
|
|
22
|
+
if (fs.existsSync(gitPath)) {
|
|
23
|
+
const stat = fs.statSync(gitPath);
|
|
24
|
+
if (stat.isFile()) {
|
|
25
|
+
const gitContent = fs.readFileSync(gitPath, 'utf-8').trim();
|
|
26
|
+
const gitdirMatch = gitContent.match(/gitdir:\s*(.+)/);
|
|
27
|
+
if (gitdirMatch) {
|
|
28
|
+
const gitdir = gitdirMatch[1];
|
|
29
|
+
const worktreePathMatch = gitdir.match(/(.+)\/\.git\/worktrees\/(.+)/);
|
|
30
|
+
if (worktreePathMatch) {
|
|
31
|
+
const mainRepo = worktreePathMatch[1];
|
|
32
|
+
const cwdWorktreeMatch = cwd.match(/\.hive\/\.worktrees\/([^/]+)\/([^/]+)/);
|
|
33
|
+
if (cwdWorktreeMatch) {
|
|
34
|
+
result.mainProjectRoot = mainRepo;
|
|
35
|
+
result.feature = cwdWorktreeMatch[1];
|
|
36
|
+
result.task = cwdWorktreeMatch[2];
|
|
37
|
+
result.isWorktree = true;
|
|
38
|
+
result.projectRoot = mainRepo;
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export function listFeatures(projectRoot) {
|
|
48
|
+
const featuresPath = getFeaturesPath(projectRoot);
|
|
49
|
+
if (!fs.existsSync(featuresPath))
|
|
50
|
+
return [];
|
|
51
|
+
return fs.readdirSync(featuresPath, { withFileTypes: true })
|
|
52
|
+
.filter(d => d.isDirectory())
|
|
53
|
+
.map(d => d.name);
|
|
54
|
+
}
|
|
55
|
+
export function getFeatureData(projectRoot, featureName) {
|
|
56
|
+
const featurePath = getFeaturePath(projectRoot, featureName);
|
|
57
|
+
const featureJsonPath = path.join(featurePath, 'feature.json');
|
|
58
|
+
return readJson(featureJsonPath);
|
|
59
|
+
}
|
|
60
|
+
export function findProjectRoot(startDir) {
|
|
61
|
+
let current = startDir;
|
|
62
|
+
const root = path.parse(current).root;
|
|
63
|
+
while (current !== root) {
|
|
64
|
+
if (fs.existsSync(path.join(current, '.hive'))) {
|
|
65
|
+
return current;
|
|
66
|
+
}
|
|
67
|
+
if (fs.existsSync(path.join(current, '.git'))) {
|
|
68
|
+
return current;
|
|
69
|
+
}
|
|
70
|
+
current = path.dirname(current);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function getTasksPath(projectRoot: string, featureName: string):
|
|
|
9
9
|
export declare function getTaskPath(projectRoot: string, featureName: string, taskFolder: string): string;
|
|
10
10
|
export declare function getTaskStatusPath(projectRoot: string, featureName: string, taskFolder: string): string;
|
|
11
11
|
export declare function getTaskReportPath(projectRoot: string, featureName: string, taskFolder: string): string;
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function getTaskSpecPath(projectRoot: string, featureName: string, taskFolder: string): string;
|
|
13
13
|
export declare function ensureDir(dirPath: string): void;
|
|
14
14
|
export declare function fileExists(filePath: string): boolean;
|
|
15
15
|
export declare function readJson<T>(filePath: string): T | null;
|
package/dist/utils/paths.js
CHANGED
|
@@ -9,7 +9,6 @@ const COMMENTS_FILE = 'comments.json';
|
|
|
9
9
|
const FEATURE_FILE = 'feature.json';
|
|
10
10
|
const STATUS_FILE = 'status.json';
|
|
11
11
|
const REPORT_FILE = 'report.md';
|
|
12
|
-
const ACTIVE_FILE = 'active-feature';
|
|
13
12
|
export function getHivePath(projectRoot) {
|
|
14
13
|
return path.join(projectRoot, HIVE_DIR);
|
|
15
14
|
}
|
|
@@ -43,8 +42,8 @@ export function getTaskStatusPath(projectRoot, featureName, taskFolder) {
|
|
|
43
42
|
export function getTaskReportPath(projectRoot, featureName, taskFolder) {
|
|
44
43
|
return path.join(getTaskPath(projectRoot, featureName, taskFolder), REPORT_FILE);
|
|
45
44
|
}
|
|
46
|
-
export function
|
|
47
|
-
return path.join(
|
|
45
|
+
export function getTaskSpecPath(projectRoot, featureName, taskFolder) {
|
|
46
|
+
return path.join(getTaskPath(projectRoot, featureName, taskFolder), 'spec.md');
|
|
48
47
|
}
|
|
49
48
|
export function ensureDir(dirPath) {
|
|
50
49
|
if (!fs.existsSync(dirPath)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { getHivePath, getFeaturesPath, getFeaturePath, getPlanPath, getCommentsPath, getFeatureJsonPath, getContextPath, getTasksPath, getTaskPath, getTaskStatusPath, getTaskReportPath, ensureDir, fileExists, readJson, writeJson, readText, writeText, } from "./paths";
|
|
5
|
+
const TEST_ROOT = "/tmp/hive-test-paths";
|
|
6
|
+
describe("paths.ts", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
9
|
+
fs.mkdirSync(TEST_ROOT, { recursive: true });
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
describe("path generators", () => {
|
|
15
|
+
it("getHivePath returns correct path", () => {
|
|
16
|
+
expect(getHivePath(TEST_ROOT)).toBe(path.join(TEST_ROOT, ".hive"));
|
|
17
|
+
});
|
|
18
|
+
it("getFeaturesPath returns correct path", () => {
|
|
19
|
+
expect(getFeaturesPath(TEST_ROOT)).toBe(path.join(TEST_ROOT, ".hive", "features"));
|
|
20
|
+
});
|
|
21
|
+
it("getFeaturePath returns correct path", () => {
|
|
22
|
+
expect(getFeaturePath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature"));
|
|
23
|
+
});
|
|
24
|
+
it("getPlanPath returns correct path", () => {
|
|
25
|
+
expect(getPlanPath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "plan.md"));
|
|
26
|
+
});
|
|
27
|
+
it("getCommentsPath returns correct path", () => {
|
|
28
|
+
expect(getCommentsPath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "comments.json"));
|
|
29
|
+
});
|
|
30
|
+
it("getFeatureJsonPath returns correct path", () => {
|
|
31
|
+
expect(getFeatureJsonPath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "feature.json"));
|
|
32
|
+
});
|
|
33
|
+
it("getContextPath returns correct path", () => {
|
|
34
|
+
expect(getContextPath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "context"));
|
|
35
|
+
});
|
|
36
|
+
it("getTasksPath returns correct path", () => {
|
|
37
|
+
expect(getTasksPath(TEST_ROOT, "my-feature")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "tasks"));
|
|
38
|
+
});
|
|
39
|
+
it("getTaskPath returns correct path", () => {
|
|
40
|
+
expect(getTaskPath(TEST_ROOT, "my-feature", "01-setup")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "tasks", "01-setup"));
|
|
41
|
+
});
|
|
42
|
+
it("getTaskStatusPath returns correct path", () => {
|
|
43
|
+
expect(getTaskStatusPath(TEST_ROOT, "my-feature", "01-setup")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "tasks", "01-setup", "status.json"));
|
|
44
|
+
});
|
|
45
|
+
it("getTaskReportPath returns correct path", () => {
|
|
46
|
+
expect(getTaskReportPath(TEST_ROOT, "my-feature", "01-setup")).toBe(path.join(TEST_ROOT, ".hive", "features", "my-feature", "tasks", "01-setup", "report.md"));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("file utilities", () => {
|
|
50
|
+
it("ensureDir creates directory recursively", () => {
|
|
51
|
+
const deepPath = path.join(TEST_ROOT, "a", "b", "c");
|
|
52
|
+
ensureDir(deepPath);
|
|
53
|
+
expect(fs.existsSync(deepPath)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it("ensureDir is idempotent", () => {
|
|
56
|
+
const dirPath = path.join(TEST_ROOT, "existing");
|
|
57
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
58
|
+
expect(() => ensureDir(dirPath)).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
it("fileExists returns true for existing file", () => {
|
|
61
|
+
const filePath = path.join(TEST_ROOT, "exists.txt");
|
|
62
|
+
fs.writeFileSync(filePath, "content");
|
|
63
|
+
expect(fileExists(filePath)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it("fileExists returns false for non-existing file", () => {
|
|
66
|
+
expect(fileExists(path.join(TEST_ROOT, "nope.txt"))).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it("writeJson and readJson roundtrip", () => {
|
|
69
|
+
const filePath = path.join(TEST_ROOT, "data.json");
|
|
70
|
+
const data = { name: "test", count: 42, nested: { value: true } };
|
|
71
|
+
writeJson(filePath, data);
|
|
72
|
+
const result = readJson(filePath);
|
|
73
|
+
expect(result).toEqual(data);
|
|
74
|
+
});
|
|
75
|
+
it("readJson returns null for non-existing file", () => {
|
|
76
|
+
expect(readJson(path.join(TEST_ROOT, "nope.json"))).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
it("writeText and readText roundtrip", () => {
|
|
79
|
+
const filePath = path.join(TEST_ROOT, "text.md");
|
|
80
|
+
const content = "# Hello\n\nThis is content.";
|
|
81
|
+
writeText(filePath, content);
|
|
82
|
+
const result = readText(filePath);
|
|
83
|
+
expect(result).toBe(content);
|
|
84
|
+
});
|
|
85
|
+
it("readText returns null for non-existing file", () => {
|
|
86
|
+
expect(readText(path.join(TEST_ROOT, "nope.md"))).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
it("writeJson creates parent directories", () => {
|
|
89
|
+
const filePath = path.join(TEST_ROOT, "deep", "nested", "data.json");
|
|
90
|
+
const expected = { test: true };
|
|
91
|
+
writeJson(filePath, expected);
|
|
92
|
+
expect(readJson(filePath)).toEqual(expected);
|
|
93
|
+
});
|
|
94
|
+
it("writeText creates parent directories", () => {
|
|
95
|
+
const filePath = path.join(TEST_ROOT, "deep", "nested", "file.txt");
|
|
96
|
+
writeText(filePath, "content");
|
|
97
|
+
expect(readText(filePath)).toBe("content");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|