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,73 +0,0 @@
1
- import { z } from 'zod';
2
- import { FeatureService } from '../services/featureService.js';
3
- import { detectContext } from '../utils/detection.js';
4
- export function createFeatureTools(projectRoot) {
5
- const featureService = new FeatureService(projectRoot);
6
- const getActiveFeature = () => {
7
- const ctx = detectContext(projectRoot);
8
- return ctx?.feature || null;
9
- };
10
- return {
11
- hive_feature_create: {
12
- description: 'Create a new feature and set it as active.',
13
- parameters: z.object({
14
- name: z.string().describe('Feature name (will be used as folder name)'),
15
- ticket: z.string().optional().describe('Ticket/issue reference'),
16
- }),
17
- execute: async ({ name, ticket }) => {
18
- const feature = featureService.create(name, ticket);
19
- return {
20
- name: feature.name,
21
- status: feature.status,
22
- path: `.hive/features/${name}`,
23
- message: `Feature '${name}' created and set as active. Write a plan with hive_plan_write.`,
24
- };
25
- },
26
- },
27
- hive_feature_list: {
28
- description: 'List all features.',
29
- parameters: z.object({}),
30
- execute: async () => {
31
- const features = featureService.list();
32
- const active = getActiveFeature();
33
- const details = features.map(name => {
34
- const info = featureService.getInfo(name);
35
- return {
36
- name,
37
- status: info?.status || 'unknown',
38
- taskCount: info?.tasks.length || 0,
39
- isActive: name === active,
40
- };
41
- });
42
- return { features: details, active };
43
- },
44
- },
45
- hive_feature_complete: {
46
- description: 'Mark feature as completed (irreversible)',
47
- parameters: z.object({
48
- _placeholder: z.boolean().describe('Placeholder. Always pass true.'),
49
- name: z.string().optional(),
50
- }),
51
- execute: async ({ name }) => {
52
- const feature = name || getActiveFeature();
53
- if (!feature) {
54
- return { error: 'No active feature.' };
55
- }
56
- const info = featureService.getInfo(feature);
57
- const pendingTasks = info?.tasks.filter((t) => t.status === 'pending' || t.status === 'in_progress') || [];
58
- if (pendingTasks.length > 0) {
59
- return {
60
- error: `Cannot complete: ${pendingTasks.length} task(s) still pending or in progress`,
61
- pendingTasks: pendingTasks.map((t) => t.folder),
62
- };
63
- }
64
- featureService.complete(feature);
65
- return {
66
- completed: true,
67
- name: feature,
68
- message: `Feature '${feature}' marked as completed.`,
69
- };
70
- },
71
- },
72
- };
73
- }
@@ -1,47 +0,0 @@
1
- import { z } from 'zod';
2
- export declare function createPlanTools(projectRoot: string): {
3
- hive_plan_write: {
4
- description: string;
5
- parameters: z.ZodObject<{
6
- content: z.ZodString;
7
- }, z.core.$strip>;
8
- execute: ({ content }: {
9
- content: string;
10
- }) => Promise<{
11
- error: string;
12
- path?: undefined;
13
- message?: undefined;
14
- } | {
15
- path: string;
16
- message: string;
17
- error?: undefined;
18
- }>;
19
- };
20
- hive_plan_read: {
21
- description: string;
22
- parameters: z.ZodObject<{}, z.core.$strip>;
23
- execute: () => Promise<import("../types.js").PlanReadResult | {
24
- error: string;
25
- }>;
26
- };
27
- hive_plan_approve: {
28
- description: string;
29
- parameters: z.ZodObject<{}, z.core.$strip>;
30
- execute: () => Promise<{
31
- error: string;
32
- comments?: undefined;
33
- approved?: undefined;
34
- message?: undefined;
35
- } | {
36
- error: string;
37
- comments: import("../types.js").PlanComment[];
38
- approved?: undefined;
39
- message?: undefined;
40
- } | {
41
- approved: boolean;
42
- message: string;
43
- error?: undefined;
44
- comments?: undefined;
45
- }>;
46
- };
47
- };
@@ -1,65 +0,0 @@
1
- import { z } from 'zod';
2
- import { PlanService } from '../services/planService.js';
3
- import { FeatureService } from '../services/featureService.js';
4
- import { detectContext } from '../utils/detection.js';
5
- export function createPlanTools(projectRoot) {
6
- const planService = new PlanService(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_plan_write: {
14
- description: 'Write plan.md (clears existing comments)',
15
- parameters: z.object({
16
- content: z.string().describe('The markdown content for the plan'),
17
- }),
18
- execute: async ({ content }) => {
19
- const feature = getActiveFeature();
20
- if (!feature) {
21
- return { error: 'No active feature. Create one with hive_feature_create first.' };
22
- }
23
- const path = planService.write(feature, content);
24
- return { path, message: `Plan written to ${path}. Comments cleared for fresh review.` };
25
- },
26
- },
27
- hive_plan_read: {
28
- description: 'Read plan.md and user comments',
29
- parameters: z.object({}),
30
- execute: async () => {
31
- const feature = getActiveFeature();
32
- if (!feature) {
33
- return { error: 'No active feature.' };
34
- }
35
- const result = planService.read(feature);
36
- if (!result) {
37
- return { error: `No plan.md found for feature '${feature}'` };
38
- }
39
- return result;
40
- },
41
- },
42
- hive_plan_approve: {
43
- description: 'Approve plan for execution',
44
- parameters: z.object({}),
45
- execute: async () => {
46
- const feature = getActiveFeature();
47
- if (!feature) {
48
- return { error: 'No active feature.' };
49
- }
50
- const comments = planService.getComments(feature);
51
- if (comments.length > 0) {
52
- return {
53
- error: `Cannot approve: ${comments.length} unresolved comment(s). Address comments and rewrite plan first.`,
54
- comments,
55
- };
56
- }
57
- planService.approve(feature);
58
- return {
59
- approved: true,
60
- message: 'Plan approved. Run hive_tasks_sync to generate tasks from the plan.',
61
- };
62
- },
63
- },
64
- };
65
- }
@@ -1,35 +0,0 @@
1
- import { z } from 'zod';
2
- export declare function createSessionTools(projectRoot: string): {
3
- hive_session_open: {
4
- description: string;
5
- parameters: z.ZodObject<{
6
- feature: z.ZodOptional<z.ZodString>;
7
- task: z.ZodOptional<z.ZodString>;
8
- }, z.core.$strip>;
9
- execute: ({ feature, task }: {
10
- feature?: string;
11
- task?: string;
12
- }, toolContext: unknown) => Promise<Record<string, unknown>>;
13
- };
14
- hive_session_list: {
15
- description: string;
16
- parameters: z.ZodObject<{}, z.core.$strip>;
17
- execute: () => Promise<{
18
- error: string;
19
- feature?: undefined;
20
- master?: undefined;
21
- sessions?: undefined;
22
- } | {
23
- feature: string;
24
- master: string | undefined;
25
- sessions: {
26
- sessionId: string;
27
- taskFolder: string | undefined;
28
- startedAt: string;
29
- lastActiveAt: string;
30
- isMaster: boolean;
31
- }[];
32
- error?: undefined;
33
- }>;
34
- };
35
- };
@@ -1,95 +0,0 @@
1
- import { z } from 'zod';
2
- import { SessionService } from '../services/sessionService.js';
3
- import { FeatureService } from '../services/featureService.js';
4
- import { TaskService } from '../services/taskService.js';
5
- import { PlanService } from '../services/planService.js';
6
- import { ContextService } from '../services/contextService.js';
7
- import { detectContext } from '../utils/detection.js';
8
- export function createSessionTools(projectRoot) {
9
- const sessionService = new SessionService(projectRoot);
10
- const featureService = new FeatureService(projectRoot);
11
- const taskService = new TaskService(projectRoot);
12
- const planService = new PlanService(projectRoot);
13
- const contextService = new ContextService(projectRoot);
14
- const getActiveFeature = () => {
15
- const ctx = detectContext(projectRoot);
16
- return ctx?.feature || null;
17
- };
18
- return {
19
- hive_session_open: {
20
- description: 'Open a Hive session for a feature or task. Returns full context needed to resume work.',
21
- parameters: z.object({
22
- feature: z.string().optional().describe('Feature name (defaults to active)'),
23
- task: z.string().optional().describe('Task folder to focus on'),
24
- }),
25
- execute: async ({ feature, task }, toolContext) => {
26
- const featureName = feature || getActiveFeature();
27
- if (!featureName)
28
- return { error: 'No feature specified and no active feature' };
29
- const featureData = featureService.get(featureName);
30
- if (!featureData)
31
- return { error: `Feature '${featureName}' not found` };
32
- const ctx = toolContext;
33
- if (ctx?.sessionID) {
34
- sessionService.track(featureName, ctx.sessionID, task);
35
- }
36
- const planResult = planService.read(featureName);
37
- const tasks = taskService.list(featureName);
38
- const contextCompiled = contextService.compile(featureName);
39
- const sessions = sessionService.list(featureName);
40
- const response = {
41
- feature: {
42
- name: featureData.name,
43
- status: featureData.status,
44
- ticket: featureData.ticket,
45
- },
46
- plan: planResult ? {
47
- content: planResult.content,
48
- commentCount: planResult.comments.length,
49
- } : null,
50
- tasks: tasks.map(t => ({
51
- folder: t.folder,
52
- name: t.name,
53
- status: t.status,
54
- origin: t.origin,
55
- })),
56
- context: contextCompiled || null,
57
- sessions: sessions.map(s => ({
58
- sessionId: s.sessionId,
59
- taskFolder: s.taskFolder,
60
- isMaster: s.sessionId === sessionService.getMaster(featureName),
61
- })),
62
- };
63
- if (task) {
64
- const taskInfo = taskService.get(featureName, task);
65
- if (taskInfo) {
66
- response.focusedTask = taskInfo;
67
- }
68
- }
69
- return response;
70
- },
71
- },
72
- hive_session_list: {
73
- description: 'List all sessions for the active feature',
74
- parameters: z.object({}),
75
- execute: async () => {
76
- const feature = getActiveFeature();
77
- if (!feature)
78
- return { error: 'No active feature' };
79
- const sessions = sessionService.list(feature);
80
- const master = sessionService.getMaster(feature);
81
- return {
82
- feature,
83
- master,
84
- sessions: sessions.map(s => ({
85
- sessionId: s.sessionId,
86
- taskFolder: s.taskFolder,
87
- startedAt: s.startedAt,
88
- lastActiveAt: s.lastActiveAt,
89
- isMaster: s.sessionId === master,
90
- })),
91
- };
92
- },
93
- },
94
- };
95
- }
@@ -1,79 +0,0 @@
1
- import { z } from 'zod';
2
- export declare function createTaskTools(projectRoot: string): {
3
- hive_tasks_sync: {
4
- description: string;
5
- parameters: z.ZodObject<{}, z.core.$strip>;
6
- execute: () => Promise<{
7
- error: string;
8
- } | {
9
- message: string;
10
- created: string[];
11
- removed: string[];
12
- kept: string[];
13
- manual: string[];
14
- error?: undefined;
15
- }>;
16
- };
17
- hive_task_create: {
18
- description: string;
19
- parameters: z.ZodObject<{
20
- name: z.ZodString;
21
- order: z.ZodOptional<z.ZodNumber>;
22
- }, z.core.$strip>;
23
- execute: ({ name, order }: {
24
- name: string;
25
- order?: number;
26
- }) => Promise<{
27
- error: string;
28
- folder?: undefined;
29
- origin?: undefined;
30
- message?: undefined;
31
- } | {
32
- folder: string;
33
- origin: string;
34
- message: string;
35
- error?: undefined;
36
- }>;
37
- };
38
- hive_task_update: {
39
- description: string;
40
- parameters: z.ZodObject<{
41
- task: z.ZodString;
42
- status: z.ZodOptional<z.ZodEnum<{
43
- pending: "pending";
44
- in_progress: "in_progress";
45
- done: "done";
46
- cancelled: "cancelled";
47
- }>>;
48
- summary: z.ZodOptional<z.ZodString>;
49
- }, z.core.$strip>;
50
- execute: ({ task, status, summary }: {
51
- task: string;
52
- status?: "pending" | "in_progress" | "done" | "cancelled";
53
- summary?: string;
54
- }) => Promise<{
55
- error: string;
56
- updated?: undefined;
57
- task?: undefined;
58
- status?: undefined;
59
- summary?: undefined;
60
- } | {
61
- updated: boolean;
62
- task: string;
63
- status: import("../types.js").TaskStatusType;
64
- summary: string | undefined;
65
- error?: undefined;
66
- }>;
67
- };
68
- hive_task_list: {
69
- description: string;
70
- parameters: z.ZodObject<{}, z.core.$strip>;
71
- execute: () => Promise<{
72
- error: string;
73
- tasks?: undefined;
74
- } | {
75
- tasks: import("../types.js").TaskInfo[];
76
- error?: undefined;
77
- }>;
78
- };
79
- };
@@ -1,86 +0,0 @@
1
- import { z } from 'zod';
2
- import { TaskService } from '../services/taskService.js';
3
- import { FeatureService } from '../services/featureService.js';
4
- import { detectContext } from '../utils/detection.js';
5
- export function createTaskTools(projectRoot) {
6
- const taskService = new TaskService(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_tasks_sync: {
14
- description: 'Generate tasks from approved plan',
15
- parameters: z.object({}),
16
- execute: async () => {
17
- const feature = getActiveFeature();
18
- if (!feature) {
19
- return { error: 'No active feature.' };
20
- }
21
- const featureData = featureService.get(feature);
22
- if (!featureData) {
23
- return { error: `Feature '${feature}' not found` };
24
- }
25
- if (featureData.status === 'planning') {
26
- return { error: 'Plan must be approved before syncing tasks. Run hive_plan_approve first.' };
27
- }
28
- const result = taskService.sync(feature);
29
- if (featureData.status === 'approved') {
30
- featureService.updateStatus(feature, 'executing');
31
- }
32
- return {
33
- ...result,
34
- message: `Synced tasks: ${result.created.length} created, ${result.removed.length} removed, ${result.kept.length} kept, ${result.manual.length} manual`,
35
- };
36
- },
37
- },
38
- hive_task_create: {
39
- description: 'Create manual task (not from plan)',
40
- parameters: z.object({
41
- name: z.string().describe('Task name (will be slugified)'),
42
- order: z.number().optional().describe('Task order number (defaults to next available)'),
43
- }),
44
- execute: async ({ name, order }) => {
45
- const feature = getActiveFeature();
46
- if (!feature) {
47
- return { error: 'No active feature.' };
48
- }
49
- const folder = taskService.create(feature, name, order);
50
- return {
51
- folder,
52
- origin: 'manual',
53
- message: `Created manual task '${folder}'. This task will not be affected by hive_tasks_sync.`,
54
- };
55
- },
56
- },
57
- hive_task_update: {
58
- description: 'Update task status or summary',
59
- parameters: z.object({
60
- task: z.string().describe('Task folder name (e.g., "01-auth-service")'),
61
- status: z.enum(['pending', 'in_progress', 'done', 'cancelled']).optional(),
62
- summary: z.string().optional().describe('Summary of work done'),
63
- }),
64
- execute: async ({ task, status, summary }) => {
65
- const feature = getActiveFeature();
66
- if (!feature) {
67
- return { error: 'No active feature.' };
68
- }
69
- const updated = taskService.update(feature, task, { status, summary });
70
- return { updated: true, task, status: updated.status, summary: updated.summary };
71
- },
72
- },
73
- hive_task_list: {
74
- description: 'List all tasks for the active feature.',
75
- parameters: z.object({}),
76
- execute: async () => {
77
- const feature = getActiveFeature();
78
- if (!feature) {
79
- return { error: 'No active feature.' };
80
- }
81
- const tasks = taskService.list(feature);
82
- return { tasks };
83
- },
84
- },
85
- };
86
- }
package/dist/types.d.ts DELETED
@@ -1,106 +0,0 @@
1
- export type FeatureStatusType = 'planning' | 'approved' | 'executing' | 'completed';
2
- export interface FeatureJson {
3
- name: string;
4
- status: FeatureStatusType;
5
- ticket?: string;
6
- sessionId?: string;
7
- createdAt: string;
8
- approvedAt?: string;
9
- completedAt?: string;
10
- }
11
- export type TaskStatusType = 'pending' | 'in_progress' | 'done' | 'cancelled';
12
- export type TaskOrigin = 'plan' | 'manual';
13
- export type SubtaskType = 'test' | 'implement' | 'review' | 'verify' | 'research' | 'debug' | 'custom';
14
- export interface Subtask {
15
- id: string;
16
- name: string;
17
- folder: string;
18
- status: TaskStatusType;
19
- type?: SubtaskType;
20
- createdAt?: string;
21
- completedAt?: string;
22
- }
23
- export interface SubtaskStatus {
24
- status: TaskStatusType;
25
- type?: SubtaskType;
26
- createdAt: string;
27
- completedAt?: string;
28
- }
29
- export interface TaskStatus {
30
- status: TaskStatusType;
31
- origin: TaskOrigin;
32
- summary?: string;
33
- startedAt?: string;
34
- completedAt?: string;
35
- baseCommit?: string;
36
- subtasks?: Subtask[];
37
- }
38
- export interface PlanComment {
39
- id: string;
40
- line: number;
41
- body: string;
42
- author: string;
43
- timestamp: string;
44
- }
45
- export interface CommentsJson {
46
- threads: PlanComment[];
47
- }
48
- export interface PlanReadResult {
49
- content: string;
50
- status: FeatureStatusType;
51
- comments: PlanComment[];
52
- }
53
- export interface TasksSyncResult {
54
- created: string[];
55
- removed: string[];
56
- kept: string[];
57
- manual: string[];
58
- }
59
- export interface TaskInfo {
60
- folder: string;
61
- name: string;
62
- status: TaskStatusType;
63
- origin: TaskOrigin;
64
- summary?: string;
65
- }
66
- export interface FeatureInfo {
67
- name: string;
68
- status: FeatureStatusType;
69
- tasks: TaskInfo[];
70
- hasPlan: boolean;
71
- commentCount: number;
72
- }
73
- export interface ContextFile {
74
- name: string;
75
- content: string;
76
- updatedAt: string;
77
- }
78
- export interface SessionInfo {
79
- sessionId: string;
80
- taskFolder?: string;
81
- startedAt: string;
82
- lastActiveAt: string;
83
- messageCount?: number;
84
- }
85
- export interface SessionsJson {
86
- master?: string;
87
- sessions: SessionInfo[];
88
- }
89
- export interface TaskSpec {
90
- taskFolder: string;
91
- featureName: string;
92
- planSection: string;
93
- context: string;
94
- priorTasks: Array<{
95
- folder: string;
96
- summary?: string;
97
- }>;
98
- }
99
- export interface HiveConfig {
100
- enableToolsFor: string[];
101
- agents: {
102
- worker: {
103
- visible: boolean;
104
- };
105
- };
106
- }
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,12 +0,0 @@
1
- import { FeatureJson } from '../types.js';
2
- export interface DetectionResult {
3
- projectRoot: string;
4
- feature: string | null;
5
- task: string | null;
6
- isWorktree: boolean;
7
- mainProjectRoot: string | null;
8
- }
9
- export declare function detectContext(cwd: string): DetectionResult;
10
- export declare function listFeatures(projectRoot: string): string[];
11
- export declare function getFeatureData(projectRoot: string, featureName: string): FeatureJson | null;
12
- export declare function findProjectRoot(startDir: string): string | null;
@@ -1,73 +0,0 @@
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
- }