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
package/dist/index.js CHANGED
@@ -4,19 +4,24 @@ import { WorktreeService } from "./services/worktreeService.js";
4
4
  import { FeatureService } from "./services/featureService.js";
5
5
  import { PlanService } from "./services/planService.js";
6
6
  import { TaskService } from "./services/taskService.js";
7
+ import { ContextService } from "./services/contextService.js";
8
+ import { SessionService } from "./services/sessionService.js";
9
+ import { detectContext, listFeatures } from "./utils/detection.js";
7
10
  const HIVE_SYSTEM_PROMPT = `
8
11
  ## Hive - Feature Development System
9
12
 
10
13
  Plan-first development: Write plan → User reviews → Approve → Execute tasks
11
14
 
12
- ### Tools (13 total)
15
+ ### Tools (16 total)
13
16
 
14
17
  | Domain | Tools |
15
18
  |--------|-------|
16
- | Feature | hive_feature_create, hive_feature_list, hive_feature_switch, hive_feature_complete, hive_status |
19
+ | Feature | hive_feature_create, hive_feature_list, hive_feature_complete |
17
20
  | Plan | hive_plan_write, hive_plan_read, hive_plan_approve |
18
21
  | Task | hive_tasks_sync, hive_task_create, hive_task_update |
19
22
  | Exec | hive_exec_start, hive_exec_complete, hive_exec_abort |
23
+ | Context | hive_context_write, hive_context_read, hive_context_list |
24
+ | Session | hive_session_open, hive_session_list |
20
25
 
21
26
  ### Workflow
22
27
 
@@ -44,6 +49,19 @@ Description of what to do.
44
49
  Description.
45
50
  \`\`\`
46
51
 
52
+ ### Planning Phase - Context Management REQUIRED
53
+
54
+ As you research and plan, CONTINUOUSLY save findings using \`hive_context_write\`:
55
+ - Research findings (API patterns, library docs, codebase structure)
56
+ - User preferences ("we use Zustand, not Redux")
57
+ - Rejected alternatives ("tried X, too complex")
58
+ - Architecture decisions ("auth lives in /lib/auth")
59
+
60
+ **Update existing context files** when new info emerges - dont create duplicates.
61
+ Workers depend on context for background. Without it, they work blind.
62
+
63
+ Save context BEFORE writing the plan, and UPDATE it as planning iterates.
64
+
47
65
  \`hive_tasks_sync\` parses \`### N. Task Name\` headers.
48
66
  `;
49
67
  const plugin = async (ctx) => {
@@ -51,26 +69,36 @@ const plugin = async (ctx) => {
51
69
  const featureService = new FeatureService(directory);
52
70
  const planService = new PlanService(directory);
53
71
  const taskService = new TaskService(directory);
72
+ const contextService = new ContextService(directory);
73
+ const sessionService = new SessionService(directory);
54
74
  const worktreeService = new WorktreeService({
55
75
  baseDir: directory,
56
76
  hiveDir: path.join(directory, '.hive'),
57
77
  });
58
- const captureSession = (toolContext) => {
59
- const activeFeature = featureService.getActive();
60
- if (!activeFeature)
61
- return;
78
+ const resolveFeature = (explicit) => {
79
+ if (explicit)
80
+ return explicit;
81
+ const context = detectContext(directory);
82
+ if (context.feature)
83
+ return context.feature;
84
+ const features = listFeatures(directory);
85
+ if (features.length === 1)
86
+ return features[0];
87
+ return null;
88
+ };
89
+ const captureSession = (feature, toolContext) => {
62
90
  const ctx = toolContext;
63
91
  if (ctx?.sessionID) {
64
- const currentSession = featureService.getSession(activeFeature);
92
+ const currentSession = featureService.getSession(feature);
65
93
  if (currentSession !== ctx.sessionID) {
66
- featureService.setSession(activeFeature, ctx.sessionID);
94
+ featureService.setSession(feature, ctx.sessionID);
67
95
  }
68
96
  }
69
97
  };
70
98
  return {
71
99
  "experimental.chat.system.transform": async (_input, output) => {
72
100
  output.system.push(HIVE_SYSTEM_PROMPT);
73
- const activeFeature = featureService.getActive();
101
+ const activeFeature = resolveFeature();
74
102
  if (activeFeature) {
75
103
  const info = featureService.getInfo(activeFeature);
76
104
  if (info) {
@@ -101,7 +129,7 @@ const plugin = async (ctx) => {
101
129
  args: {},
102
130
  async execute() {
103
131
  const features = featureService.list();
104
- const active = featureService.getActive();
132
+ const active = resolveFeature();
105
133
  if (features.length === 0)
106
134
  return "No features found.";
107
135
  const list = features.map(f => {
@@ -111,58 +139,42 @@ const plugin = async (ctx) => {
111
139
  return list.join('\n');
112
140
  },
113
141
  }),
114
- hive_feature_switch: tool({
115
- description: 'Switch to a different feature',
116
- args: { name: tool.schema.string().describe('Feature name') },
117
- async execute({ name }) {
118
- featureService.setActive(name);
119
- return `Switched to feature "${name}"`;
120
- },
121
- }),
122
142
  hive_feature_complete: tool({
123
143
  description: 'Mark feature as completed (irreversible)',
124
144
  args: { name: tool.schema.string().optional().describe('Feature name (defaults to active)') },
125
145
  async execute({ name }) {
126
- const feature = name || featureService.getActive();
146
+ const feature = resolveFeature(name);
127
147
  if (!feature)
128
- return "Error: No active feature";
148
+ return "Error: No feature specified. Create a feature or provide name.";
129
149
  featureService.complete(feature);
130
150
  return `Feature "${feature}" marked as completed`;
131
151
  },
132
152
  }),
133
- hive_status: tool({
134
- description: 'Get overview of active feature',
135
- args: { name: tool.schema.string().optional().describe('Feature name (defaults to active)') },
136
- async execute({ name }) {
137
- const feature = name || featureService.getActive();
138
- if (!feature)
139
- return "Error: No active feature";
140
- const info = featureService.getInfo(feature);
141
- if (!info)
142
- return `Error: Feature "${feature}" not found`;
143
- return JSON.stringify(info, null, 2);
144
- },
145
- }),
146
153
  hive_plan_write: tool({
147
154
  description: 'Write plan.md (clears existing comments)',
148
- args: { content: tool.schema.string().describe('Plan markdown content') },
149
- async execute({ content }, toolContext) {
150
- captureSession(toolContext);
151
- const feature = featureService.getActive();
155
+ args: {
156
+ content: tool.schema.string().describe('Plan markdown content'),
157
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
158
+ },
159
+ async execute({ content, feature: explicitFeature }, toolContext) {
160
+ const feature = resolveFeature(explicitFeature);
152
161
  if (!feature)
153
- return "Error: No active feature";
162
+ return "Error: No feature specified. Create a feature or provide feature param.";
163
+ captureSession(feature, toolContext);
154
164
  const planPath = planService.write(feature, content);
155
165
  return `Plan written to ${planPath}. Comments cleared for fresh review.`;
156
166
  },
157
167
  }),
158
168
  hive_plan_read: tool({
159
169
  description: 'Read plan.md and user comments',
160
- args: {},
161
- async execute(_args, toolContext) {
162
- captureSession(toolContext);
163
- const feature = featureService.getActive();
170
+ args: {
171
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
172
+ },
173
+ async execute({ feature: explicitFeature }, toolContext) {
174
+ const feature = resolveFeature(explicitFeature);
164
175
  if (!feature)
165
- return "Error: No active feature";
176
+ return "Error: No feature specified. Create a feature or provide feature param.";
177
+ captureSession(feature, toolContext);
166
178
  const result = planService.read(feature);
167
179
  if (!result)
168
180
  return "Error: No plan.md found";
@@ -171,12 +183,14 @@ const plugin = async (ctx) => {
171
183
  }),
172
184
  hive_plan_approve: tool({
173
185
  description: 'Approve plan for execution',
174
- args: {},
175
- async execute(_args, toolContext) {
176
- captureSession(toolContext);
177
- const feature = featureService.getActive();
186
+ args: {
187
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
188
+ },
189
+ async execute({ feature: explicitFeature }, toolContext) {
190
+ const feature = resolveFeature(explicitFeature);
178
191
  if (!feature)
179
- return "Error: No active feature";
192
+ return "Error: No feature specified. Create a feature or provide feature param.";
193
+ captureSession(feature, toolContext);
180
194
  const comments = planService.getComments(feature);
181
195
  if (comments.length > 0) {
182
196
  return `Error: Cannot approve - ${comments.length} unresolved comment(s). Address them first.`;
@@ -187,11 +201,13 @@ const plugin = async (ctx) => {
187
201
  }),
188
202
  hive_tasks_sync: tool({
189
203
  description: 'Generate tasks from approved plan',
190
- args: {},
191
- async execute() {
192
- const feature = featureService.getActive();
204
+ args: {
205
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
206
+ },
207
+ async execute({ feature: explicitFeature }) {
208
+ const feature = resolveFeature(explicitFeature);
193
209
  if (!feature)
194
- return "Error: No active feature";
210
+ return "Error: No feature specified. Create a feature or provide feature param.";
195
211
  const featureData = featureService.get(feature);
196
212
  if (!featureData || featureData.status === 'planning') {
197
213
  return "Error: Plan must be approved first";
@@ -208,11 +224,12 @@ const plugin = async (ctx) => {
208
224
  args: {
209
225
  name: tool.schema.string().describe('Task name'),
210
226
  order: tool.schema.number().optional().describe('Task order'),
227
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
211
228
  },
212
- async execute({ name, order }) {
213
- const feature = featureService.getActive();
229
+ async execute({ name, order, feature: explicitFeature }) {
230
+ const feature = resolveFeature(explicitFeature);
214
231
  if (!feature)
215
- return "Error: No active feature";
232
+ return "Error: No feature specified. Create a feature or provide feature param.";
216
233
  const folder = taskService.create(feature, name, order);
217
234
  return `Manual task created: ${folder}`;
218
235
  },
@@ -223,11 +240,12 @@ const plugin = async (ctx) => {
223
240
  task: tool.schema.string().describe('Task folder name'),
224
241
  status: tool.schema.string().optional().describe('New status: pending, in_progress, done, cancelled'),
225
242
  summary: tool.schema.string().optional().describe('Summary of work'),
243
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
226
244
  },
227
- async execute({ task, status, summary }) {
228
- const feature = featureService.getActive();
245
+ async execute({ task, status, summary, feature: explicitFeature }) {
246
+ const feature = resolveFeature(explicitFeature);
229
247
  if (!feature)
230
- return "Error: No active feature";
248
+ return "Error: No feature specified. Create a feature or provide feature param.";
231
249
  const updated = taskService.update(feature, task, {
232
250
  status: status,
233
251
  summary,
@@ -237,19 +255,47 @@ const plugin = async (ctx) => {
237
255
  }),
238
256
  hive_exec_start: tool({
239
257
  description: 'Create worktree and begin work on task',
240
- args: { task: tool.schema.string().describe('Task folder name') },
241
- async execute({ task }) {
242
- const feature = featureService.getActive();
258
+ args: {
259
+ task: tool.schema.string().describe('Task folder name'),
260
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
261
+ },
262
+ async execute({ task, feature: explicitFeature }) {
263
+ const feature = resolveFeature(explicitFeature);
243
264
  if (!feature)
244
- return "Error: No active feature";
265
+ return "Error: No feature specified. Create a feature or provide feature param.";
245
266
  const taskInfo = taskService.get(feature, task);
246
267
  if (!taskInfo)
247
268
  return `Error: Task "${task}" not found`;
248
269
  if (taskInfo.status === 'done')
249
270
  return "Error: Task already completed";
250
271
  const worktree = await worktreeService.create(feature, task);
251
- taskService.update(feature, task, { status: 'in_progress' });
252
- return `Worktree created at ${worktree.path}\nBranch: ${worktree.branch}`;
272
+ taskService.update(feature, task, {
273
+ status: 'in_progress',
274
+ baseCommit: worktree.commit,
275
+ });
276
+ // Generate spec.md with context for task
277
+ const planResult = planService.read(feature);
278
+ const contextCompiled = contextService.compile(feature);
279
+ const allTasks = taskService.list(feature);
280
+ const priorTasks = allTasks
281
+ .filter(t => t.status === 'done')
282
+ .map(t => `- ${t.folder}: ${t.summary || 'No summary'}`);
283
+ let specContent = `# Task: ${task}\n\n`;
284
+ specContent += `## Feature: ${feature}\n\n`;
285
+ if (planResult) {
286
+ const taskMatch = planResult.content.match(new RegExp(`###\\s*\\d+\\.\\s*${taskInfo.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=###|$)`, 'i'));
287
+ if (taskMatch) {
288
+ specContent += `## Plan Section\n\n${taskMatch[0].trim()}\n\n`;
289
+ }
290
+ }
291
+ if (contextCompiled) {
292
+ specContent += `## Context\n\n${contextCompiled}\n\n`;
293
+ }
294
+ if (priorTasks.length > 0) {
295
+ specContent += `## Completed Tasks\n\n${priorTasks.join('\n')}\n\n`;
296
+ }
297
+ taskService.writeSpec(feature, task, specContent);
298
+ return `Worktree created at ${worktree.path}\nBranch: ${worktree.branch}\nBase commit: ${worktree.commit}\nSpec: ${task}/spec.md generated`;
253
299
  },
254
300
  }),
255
301
  hive_exec_complete: tool({
@@ -257,39 +303,191 @@ const plugin = async (ctx) => {
257
303
  args: {
258
304
  task: tool.schema.string().describe('Task folder name'),
259
305
  summary: tool.schema.string().describe('Summary of what was done'),
306
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
260
307
  },
261
- async execute({ task, summary }) {
262
- const feature = featureService.getActive();
308
+ async execute({ task, summary, feature: explicitFeature }) {
309
+ const feature = resolveFeature(explicitFeature);
263
310
  if (!feature)
264
- return "Error: No active feature";
311
+ return "Error: No feature specified. Create a feature or provide feature param.";
265
312
  const taskInfo = taskService.get(feature, task);
266
313
  if (!taskInfo)
267
314
  return `Error: Task "${task}" not found`;
268
315
  if (taskInfo.status !== 'in_progress')
269
316
  return "Error: Task not in progress";
270
317
  const diff = await worktreeService.getDiff(feature, task);
318
+ let changesApplied = false;
319
+ let applyError = "";
271
320
  if (diff?.hasDiff) {
272
- await worktreeService.applyDiff(feature, task);
321
+ const result = await worktreeService.applyDiff(feature, task);
322
+ changesApplied = result.success;
323
+ if (!result.success) {
324
+ applyError = result.error || "Unknown apply error";
325
+ }
326
+ }
327
+ const reportLines = [
328
+ `# Task Report: ${task}`,
329
+ '',
330
+ `**Feature:** ${feature}`,
331
+ `**Completed:** ${new Date().toISOString()}`,
332
+ `**Status:** ${applyError ? 'completed with errors' : 'success'}`,
333
+ '',
334
+ '---',
335
+ '',
336
+ '## Summary',
337
+ '',
338
+ summary,
339
+ '',
340
+ ];
341
+ if (diff?.hasDiff) {
342
+ reportLines.push('---', '', '## Changes', '', `- **Files changed:** ${diff.filesChanged.length}`, `- **Insertions:** +${diff.insertions}`, `- **Deletions:** -${diff.deletions}`, '');
343
+ if (diff.filesChanged.length > 0) {
344
+ reportLines.push('### Files Modified', '');
345
+ for (const file of diff.filesChanged) {
346
+ reportLines.push(`- \`${file}\``);
347
+ }
348
+ reportLines.push('');
349
+ }
350
+ }
351
+ else {
352
+ reportLines.push('---', '', '## Changes', '', '_No file changes detected_', '');
273
353
  }
274
- const report = `# ${task}\n\n## Summary\n\n${summary}\n`;
275
- taskService.writeReport(feature, task, report);
354
+ taskService.writeReport(feature, task, reportLines.join('\n'));
276
355
  taskService.update(feature, task, { status: 'done', summary });
277
356
  await worktreeService.remove(feature, task);
278
- return `Task "${task}" completed. Changes applied.`;
357
+ if (applyError) {
358
+ return `Task "${task}" completed but changes failed to apply: ${applyError}`;
359
+ }
360
+ return `Task "${task}" completed.${changesApplied ? " Changes applied." : ""}`;
279
361
  },
280
362
  }),
281
363
  hive_exec_abort: tool({
282
364
  description: 'Abort task: discard changes, reset status',
283
- args: { task: tool.schema.string().describe('Task folder name') },
284
- async execute({ task }) {
285
- const feature = featureService.getActive();
365
+ args: {
366
+ task: tool.schema.string().describe('Task folder name'),
367
+ feature: tool.schema.string().optional().describe('Feature name (defaults to detection or single feature)'),
368
+ },
369
+ async execute({ task, feature: explicitFeature }) {
370
+ const feature = resolveFeature(explicitFeature);
286
371
  if (!feature)
287
- return "Error: No active feature";
372
+ return "Error: No feature specified. Create a feature or provide feature param.";
288
373
  await worktreeService.remove(feature, task);
289
374
  taskService.update(feature, task, { status: 'pending' });
290
375
  return `Task "${task}" aborted. Status reset to pending.`;
291
376
  },
292
377
  }),
378
+ // Context Tools
379
+ hive_context_write: tool({
380
+ description: 'Write a context file for the feature. Context files store persistent notes, decisions, and reference material.',
381
+ args: {
382
+ name: tool.schema.string().describe('Context file name (e.g., "decisions", "architecture", "notes")'),
383
+ content: tool.schema.string().describe('Markdown content to write'),
384
+ feature: tool.schema.string().optional().describe('Feature name (defaults to active)'),
385
+ },
386
+ async execute({ name, content, feature: explicitFeature }) {
387
+ const feature = resolveFeature(explicitFeature);
388
+ if (!feature)
389
+ return "Error: No feature specified. Create a feature or provide feature param.";
390
+ const filePath = contextService.write(feature, name, content);
391
+ return `Context file written: ${filePath}`;
392
+ },
393
+ }),
394
+ hive_context_read: tool({
395
+ description: 'Read a specific context file or all context for the feature',
396
+ args: {
397
+ name: tool.schema.string().optional().describe('Context file name. If omitted, returns all context compiled.'),
398
+ feature: tool.schema.string().optional().describe('Feature name (defaults to active)'),
399
+ },
400
+ async execute({ name, feature: explicitFeature }) {
401
+ const feature = resolveFeature(explicitFeature);
402
+ if (!feature)
403
+ return "Error: No feature specified. Create a feature or provide feature param.";
404
+ if (name) {
405
+ const content = contextService.read(feature, name);
406
+ if (!content)
407
+ return `Error: Context file '${name}' not found`;
408
+ return content;
409
+ }
410
+ const compiled = contextService.compile(feature);
411
+ if (!compiled)
412
+ return "No context files found";
413
+ return compiled;
414
+ },
415
+ }),
416
+ hive_context_list: tool({
417
+ description: 'List all context files for the feature',
418
+ args: {
419
+ feature: tool.schema.string().optional().describe('Feature name (defaults to active)'),
420
+ },
421
+ async execute({ feature: explicitFeature }) {
422
+ const feature = resolveFeature(explicitFeature);
423
+ if (!feature)
424
+ return "Error: No feature specified. Create a feature or provide feature param.";
425
+ const files = contextService.list(feature);
426
+ if (files.length === 0)
427
+ return "No context files";
428
+ return files.map(f => `${f.name} (${f.content.length} chars, updated ${f.updatedAt})`).join('\n');
429
+ },
430
+ }),
431
+ // Session Tools
432
+ hive_session_open: tool({
433
+ description: 'Open session, return full context for a feature',
434
+ args: {
435
+ feature: tool.schema.string().optional().describe('Feature name (defaults to active)'),
436
+ task: tool.schema.string().optional().describe('Task folder to focus on'),
437
+ },
438
+ async execute({ feature: explicitFeature, task }, toolContext) {
439
+ const feature = resolveFeature(explicitFeature);
440
+ if (!feature)
441
+ return "Error: No feature specified. Create a feature or provide feature param.";
442
+ const featureData = featureService.get(feature);
443
+ if (!featureData)
444
+ return `Error: Feature '${feature}' not found`;
445
+ // Track session
446
+ const ctx = toolContext;
447
+ if (ctx?.sessionID) {
448
+ sessionService.track(feature, ctx.sessionID, task);
449
+ }
450
+ const planResult = planService.read(feature);
451
+ const tasks = taskService.list(feature);
452
+ const contextCompiled = contextService.compile(feature);
453
+ const sessions = sessionService.list(feature);
454
+ let output = `## Feature: ${feature} [${featureData.status}]\n\n`;
455
+ if (planResult) {
456
+ output += `### Plan\n${planResult.content.substring(0, 500)}...\n\n`;
457
+ }
458
+ output += `### Tasks (${tasks.length})\n`;
459
+ tasks.forEach(t => {
460
+ output += `- ${t.folder}: ${t.name} [${t.status}]\n`;
461
+ });
462
+ if (contextCompiled) {
463
+ output += `\n### Context\n${contextCompiled.substring(0, 500)}...\n`;
464
+ }
465
+ output += `\n### Sessions (${sessions.length})\n`;
466
+ sessions.forEach(s => {
467
+ output += `- ${s.sessionId} (${s.taskFolder || 'no task'})\n`;
468
+ });
469
+ return output;
470
+ },
471
+ }),
472
+ hive_session_list: tool({
473
+ description: 'List all sessions for the feature',
474
+ args: {
475
+ feature: tool.schema.string().optional().describe('Feature name (defaults to active)'),
476
+ },
477
+ async execute({ feature: explicitFeature }) {
478
+ const feature = resolveFeature(explicitFeature);
479
+ if (!feature)
480
+ return "Error: No feature specified. Create a feature or provide feature param.";
481
+ const sessions = sessionService.list(feature);
482
+ const master = sessionService.getMaster(feature);
483
+ if (sessions.length === 0)
484
+ return "No sessions";
485
+ return sessions.map(s => {
486
+ const masterMark = s.sessionId === master ? ' (master)' : '';
487
+ return `${s.sessionId}${masterMark} - ${s.taskFolder || 'no task'} - ${s.lastActiveAt}`;
488
+ }).join('\n');
489
+ },
490
+ }),
293
491
  },
294
492
  command: {
295
493
  hive: {
@@ -0,0 +1,15 @@
1
+ export interface ContextFile {
2
+ name: string;
3
+ content: string;
4
+ updatedAt: string;
5
+ }
6
+ export declare class ContextService {
7
+ private projectRoot;
8
+ constructor(projectRoot: string);
9
+ write(featureName: string, fileName: string, content: string): string;
10
+ read(featureName: string, fileName: string): string | null;
11
+ list(featureName: string): ContextFile[];
12
+ delete(featureName: string, fileName: string): boolean;
13
+ compile(featureName: string): string;
14
+ private normalizeFileName;
15
+ }
@@ -0,0 +1,59 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { getContextPath, ensureDir, fileExists, readText, writeText } from '../utils/paths.js';
4
+ export class ContextService {
5
+ projectRoot;
6
+ constructor(projectRoot) {
7
+ this.projectRoot = projectRoot;
8
+ }
9
+ write(featureName, fileName, content) {
10
+ const contextPath = getContextPath(this.projectRoot, featureName);
11
+ ensureDir(contextPath);
12
+ const filePath = path.join(contextPath, this.normalizeFileName(fileName));
13
+ writeText(filePath, content);
14
+ return filePath;
15
+ }
16
+ read(featureName, fileName) {
17
+ const contextPath = getContextPath(this.projectRoot, featureName);
18
+ const filePath = path.join(contextPath, this.normalizeFileName(fileName));
19
+ return readText(filePath);
20
+ }
21
+ list(featureName) {
22
+ const contextPath = getContextPath(this.projectRoot, featureName);
23
+ if (!fileExists(contextPath))
24
+ return [];
25
+ const files = fs.readdirSync(contextPath, { withFileTypes: true })
26
+ .filter(f => f.isFile() && f.name.endsWith('.md'))
27
+ .map(f => f.name);
28
+ return files.map(name => {
29
+ const filePath = path.join(contextPath, name);
30
+ const stat = fs.statSync(filePath);
31
+ const content = readText(filePath) || '';
32
+ return {
33
+ name: name.replace(/\.md$/, ''),
34
+ content,
35
+ updatedAt: stat.mtime.toISOString(),
36
+ };
37
+ });
38
+ }
39
+ delete(featureName, fileName) {
40
+ const contextPath = getContextPath(this.projectRoot, featureName);
41
+ const filePath = path.join(contextPath, this.normalizeFileName(fileName));
42
+ if (fileExists(filePath)) {
43
+ fs.unlinkSync(filePath);
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+ compile(featureName) {
49
+ const files = this.list(featureName);
50
+ if (files.length === 0)
51
+ return '';
52
+ const sections = files.map(f => `## ${f.name}\n\n${f.content}`);
53
+ return sections.join('\n\n---\n\n');
54
+ }
55
+ normalizeFileName(name) {
56
+ const normalized = name.replace(/\.md$/, '');
57
+ return `${normalized}.md`;
58
+ }
59
+ }
@@ -5,8 +5,6 @@ export declare class FeatureService {
5
5
  create(name: string, ticket?: string): FeatureJson;
6
6
  get(name: string): FeatureJson | null;
7
7
  list(): string[];
8
- getActive(): string | null;
9
- setActive(name: string): void;
10
8
  updateStatus(name: string, status: FeatureStatusType): FeatureJson;
11
9
  getInfo(name: string): FeatureInfo | null;
12
10
  private getTasks;
@@ -1,5 +1,5 @@
1
1
  import * as fs from 'fs';
2
- import { getFeaturePath, getFeaturesPath, getFeatureJsonPath, getContextPath, getTasksPath, getActiveFeaturePath, getPlanPath, getCommentsPath, ensureDir, readJson, writeJson, readText, writeText, fileExists, } from '../utils/paths.js';
2
+ import { getFeaturePath, getFeaturesPath, getFeatureJsonPath, getContextPath, getTasksPath, getPlanPath, getCommentsPath, ensureDir, readJson, writeJson, fileExists, } from '../utils/paths.js';
3
3
  export class FeatureService {
4
4
  projectRoot;
5
5
  constructor(projectRoot) {
@@ -20,7 +20,6 @@ export class FeatureService {
20
20
  createdAt: new Date().toISOString(),
21
21
  };
22
22
  writeJson(getFeatureJsonPath(this.projectRoot, name), feature);
23
- this.setActive(name);
24
23
  return feature;
25
24
  }
26
25
  get(name) {
@@ -34,15 +33,6 @@ export class FeatureService {
34
33
  .filter(d => d.isDirectory())
35
34
  .map(d => d.name);
36
35
  }
37
- getActive() {
38
- return readText(getActiveFeaturePath(this.projectRoot))?.trim() || null;
39
- }
40
- setActive(name) {
41
- const feature = this.get(name);
42
- if (!feature)
43
- throw new Error(`Feature '${name}' not found`);
44
- writeText(getActiveFeaturePath(this.projectRoot), name);
45
- }
46
36
  updateStatus(name, status) {
47
37
  const feature = this.get(name);
48
38
  if (!feature)
@@ -0,0 +1 @@
1
+ export {};