hungry-ghost-hive 0.43.2 → 0.45.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 (201) hide show
  1. package/dist/agents/base-agent.d.ts +1 -0
  2. package/dist/agents/base-agent.d.ts.map +1 -1
  3. package/dist/agents/base-agent.js +4 -0
  4. package/dist/agents/base-agent.js.map +1 -1
  5. package/dist/agents/intermediate.js +2 -2
  6. package/dist/agents/intermediate.js.map +1 -1
  7. package/dist/agents/junior.js +2 -2
  8. package/dist/agents/junior.js.map +1 -1
  9. package/dist/agents/qa.d.ts.map +1 -1
  10. package/dist/agents/qa.js +5 -5
  11. package/dist/agents/qa.js.map +1 -1
  12. package/dist/agents/senior.d.ts.map +1 -1
  13. package/dist/agents/senior.js +5 -5
  14. package/dist/agents/senior.js.map +1 -1
  15. package/dist/agents/tech-lead.d.ts.map +1 -1
  16. package/dist/agents/tech-lead.js +8 -3
  17. package/dist/agents/tech-lead.js.map +1 -1
  18. package/dist/cli/commands/assign.d.ts.map +1 -1
  19. package/dist/cli/commands/assign.js +4 -2
  20. package/dist/cli/commands/assign.js.map +1 -1
  21. package/dist/cli/commands/assign.test.js +5 -0
  22. package/dist/cli/commands/assign.test.js.map +1 -1
  23. package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
  24. package/dist/cli/commands/manager/handoff-recovery.js +4 -2
  25. package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
  26. package/dist/cli/commands/manager/index.d.ts.map +1 -1
  27. package/dist/cli/commands/manager/index.js +16 -12
  28. package/dist/cli/commands/manager/index.js.map +1 -1
  29. package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
  30. package/dist/cli/commands/manager/tech-lead-lifecycle.js +8 -3
  31. package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
  32. package/dist/cli/commands/msg.d.ts.map +1 -1
  33. package/dist/cli/commands/msg.js +8 -7
  34. package/dist/cli/commands/msg.js.map +1 -1
  35. package/dist/cli/commands/my-stories.js +3 -3
  36. package/dist/cli/commands/my-stories.js.map +1 -1
  37. package/dist/cli/commands/nuke.d.ts.map +1 -1
  38. package/dist/cli/commands/nuke.js +18 -7
  39. package/dist/cli/commands/nuke.js.map +1 -1
  40. package/dist/cli/commands/nuke.test.js +24 -0
  41. package/dist/cli/commands/nuke.test.js.map +1 -1
  42. package/dist/cli/commands/pr.js +5 -0
  43. package/dist/cli/commands/pr.js.map +1 -1
  44. package/dist/cli/commands/pr.test.js +43 -1
  45. package/dist/cli/commands/pr.test.js.map +1 -1
  46. package/dist/cli/commands/req.d.ts +1 -1
  47. package/dist/cli/commands/req.d.ts.map +1 -1
  48. package/dist/cli/commands/req.js +9 -6
  49. package/dist/cli/commands/req.js.map +1 -1
  50. package/dist/cli/commands/resume.d.ts.map +1 -1
  51. package/dist/cli/commands/resume.js +4 -1
  52. package/dist/cli/commands/resume.js.map +1 -1
  53. package/dist/cli/commands/stories.js +3 -3
  54. package/dist/cli/commands/stories.js.map +1 -1
  55. package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
  56. package/dist/cli/dashboard/panels/agents.js +7 -3
  57. package/dist/cli/dashboard/panels/agents.js.map +1 -1
  58. package/dist/cli-runtimes/chrome.d.ts +17 -0
  59. package/dist/cli-runtimes/chrome.d.ts.map +1 -0
  60. package/dist/cli-runtimes/chrome.js +36 -0
  61. package/dist/cli-runtimes/chrome.js.map +1 -0
  62. package/dist/cli-runtimes/claude.d.ts +3 -3
  63. package/dist/cli-runtimes/claude.d.ts.map +1 -1
  64. package/dist/cli-runtimes/claude.js +14 -8
  65. package/dist/cli-runtimes/claude.js.map +1 -1
  66. package/dist/cli-runtimes/codex.d.ts +3 -3
  67. package/dist/cli-runtimes/codex.d.ts.map +1 -1
  68. package/dist/cli-runtimes/codex.js +2 -2
  69. package/dist/cli-runtimes/codex.js.map +1 -1
  70. package/dist/cli-runtimes/gemini.d.ts +3 -3
  71. package/dist/cli-runtimes/gemini.d.ts.map +1 -1
  72. package/dist/cli-runtimes/gemini.js +2 -2
  73. package/dist/cli-runtimes/gemini.js.map +1 -1
  74. package/dist/cli-runtimes/index.d.ts +3 -2
  75. package/dist/cli-runtimes/index.d.ts.map +1 -1
  76. package/dist/cli-runtimes/index.js +1 -0
  77. package/dist/cli-runtimes/index.js.map +1 -1
  78. package/dist/cli-runtimes/index.test.js +133 -1
  79. package/dist/cli-runtimes/index.test.js.map +1 -1
  80. package/dist/cli-runtimes/types.d.ts +9 -2
  81. package/dist/cli-runtimes/types.d.ts.map +1 -1
  82. package/dist/config/schema.d.ts +8 -0
  83. package/dist/config/schema.d.ts.map +1 -1
  84. package/dist/config/schema.js +6 -0
  85. package/dist/config/schema.js.map +1 -1
  86. package/dist/context-files/generator.d.ts +1 -1
  87. package/dist/context-files/generator.d.ts.map +1 -1
  88. package/dist/context-files/generator.js +3 -2
  89. package/dist/context-files/generator.js.map +1 -1
  90. package/dist/context-files/index.test.js +2 -0
  91. package/dist/context-files/index.test.js.map +1 -1
  92. package/dist/db/client.d.ts +1 -0
  93. package/dist/db/client.d.ts.map +1 -1
  94. package/dist/db/client.js +6 -0
  95. package/dist/db/client.js.map +1 -1
  96. package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
  97. package/dist/db/queries/stories.d.ts +3 -3
  98. package/dist/db/queries/stories.d.ts.map +1 -1
  99. package/dist/db/queries/stories.js +23 -5
  100. package/dist/db/queries/stories.js.map +1 -1
  101. package/dist/db/queries/test-helpers.d.ts.map +1 -1
  102. package/dist/db/queries/test-helpers.js +1 -0
  103. package/dist/db/queries/test-helpers.js.map +1 -1
  104. package/dist/git/worktree.d.ts.map +1 -1
  105. package/dist/git/worktree.js +7 -0
  106. package/dist/git/worktree.js.map +1 -1
  107. package/dist/git/worktree.test.js +30 -0
  108. package/dist/git/worktree.test.js.map +1 -1
  109. package/dist/orchestrator/prompt-templates.d.ts +3 -1
  110. package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
  111. package/dist/orchestrator/prompt-templates.js +16 -8
  112. package/dist/orchestrator/prompt-templates.js.map +1 -1
  113. package/dist/orchestrator/prompt-templates.test.js +4 -0
  114. package/dist/orchestrator/prompt-templates.test.js.map +1 -1
  115. package/dist/orchestrator/scheduler.d.ts.map +1 -1
  116. package/dist/orchestrator/scheduler.js +23 -12
  117. package/dist/orchestrator/scheduler.js.map +1 -1
  118. package/dist/orchestrator/scheduler.test.js +1 -0
  119. package/dist/orchestrator/scheduler.test.js.map +1 -1
  120. package/dist/tmux/manager.d.ts +7 -6
  121. package/dist/tmux/manager.d.ts.map +1 -1
  122. package/dist/tmux/manager.js +29 -13
  123. package/dist/tmux/manager.js.map +1 -1
  124. package/dist/utils/auto-merge.d.ts.map +1 -1
  125. package/dist/utils/auto-merge.js +66 -5
  126. package/dist/utils/auto-merge.js.map +1 -1
  127. package/dist/utils/auto-merge.test.js +62 -0
  128. package/dist/utils/auto-merge.test.js.map +1 -1
  129. package/dist/utils/instance.d.ts +32 -0
  130. package/dist/utils/instance.d.ts.map +1 -0
  131. package/dist/utils/instance.js +82 -0
  132. package/dist/utils/instance.js.map +1 -0
  133. package/dist/utils/instance.test.d.ts +2 -0
  134. package/dist/utils/instance.test.d.ts.map +1 -0
  135. package/dist/utils/instance.test.js +103 -0
  136. package/dist/utils/instance.test.js.map +1 -0
  137. package/dist/utils/paths.d.ts +2 -0
  138. package/dist/utils/paths.d.ts.map +1 -1
  139. package/dist/utils/paths.js +2 -0
  140. package/dist/utils/paths.js.map +1 -1
  141. package/dist/utils/paths.test.js +6 -0
  142. package/dist/utils/paths.test.js.map +1 -1
  143. package/dist/utils/story-markdown.d.ts +16 -0
  144. package/dist/utils/story-markdown.d.ts.map +1 -0
  145. package/dist/utils/story-markdown.js +82 -0
  146. package/dist/utils/story-markdown.js.map +1 -0
  147. package/dist/utils/story-markdown.test.d.ts +2 -0
  148. package/dist/utils/story-markdown.test.d.ts.map +1 -0
  149. package/dist/utils/story-markdown.test.js +143 -0
  150. package/dist/utils/story-markdown.test.js.map +1 -0
  151. package/package.json +1 -1
  152. package/src/agents/base-agent.ts +5 -0
  153. package/src/agents/intermediate.ts +2 -2
  154. package/src/agents/junior.ts +2 -2
  155. package/src/agents/qa.ts +13 -8
  156. package/src/agents/senior.ts +21 -11
  157. package/src/agents/tech-lead.ts +28 -13
  158. package/src/cli/commands/assign.test.ts +5 -0
  159. package/src/cli/commands/assign.ts +4 -2
  160. package/src/cli/commands/manager/handoff-recovery.ts +4 -2
  161. package/src/cli/commands/manager/index.ts +16 -11
  162. package/src/cli/commands/manager/tech-lead-lifecycle.ts +9 -3
  163. package/src/cli/commands/msg.ts +8 -7
  164. package/src/cli/commands/my-stories.ts +22 -13
  165. package/src/cli/commands/nuke.test.ts +31 -0
  166. package/src/cli/commands/nuke.ts +18 -7
  167. package/src/cli/commands/pr.test.ts +77 -1
  168. package/src/cli/commands/pr.ts +5 -0
  169. package/src/cli/commands/req.ts +13 -6
  170. package/src/cli/commands/resume.ts +4 -1
  171. package/src/cli/commands/stories.ts +22 -13
  172. package/src/cli/dashboard/panels/agents.ts +7 -3
  173. package/src/cli-runtimes/chrome.ts +43 -0
  174. package/src/cli-runtimes/claude.ts +26 -9
  175. package/src/cli-runtimes/codex.ts +12 -3
  176. package/src/cli-runtimes/gemini.ts +12 -3
  177. package/src/cli-runtimes/index.test.ts +158 -0
  178. package/src/cli-runtimes/index.ts +3 -2
  179. package/src/cli-runtimes/types.ts +19 -2
  180. package/src/config/schema.ts +6 -0
  181. package/src/context-files/generator.ts +3 -2
  182. package/src/context-files/index.test.ts +2 -0
  183. package/src/db/client.ts +7 -0
  184. package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
  185. package/src/db/queries/stories.ts +29 -5
  186. package/src/db/queries/test-helpers.ts +1 -0
  187. package/src/git/worktree.test.ts +43 -0
  188. package/src/git/worktree.ts +10 -0
  189. package/src/orchestrator/prompt-templates.test.ts +4 -0
  190. package/src/orchestrator/prompt-templates.ts +20 -8
  191. package/src/orchestrator/scheduler.test.ts +1 -0
  192. package/src/orchestrator/scheduler.ts +33 -12
  193. package/src/tmux/manager.ts +42 -13
  194. package/src/utils/auto-merge.test.ts +81 -0
  195. package/src/utils/auto-merge.ts +78 -5
  196. package/src/utils/instance.test.ts +129 -0
  197. package/src/utils/instance.ts +95 -0
  198. package/src/utils/paths.test.ts +8 -0
  199. package/src/utils/paths.ts +3 -0
  200. package/src/utils/story-markdown.test.ts +176 -0
  201. package/src/utils/story-markdown.ts +94 -0
@@ -118,10 +118,15 @@ This will help with story estimation and implementation.`;
118
118
 
119
119
  private async implementStory(story: StoryRow): Promise<void> {
120
120
  // Update assignment
121
- updateStory(this.db, story.id, {
122
- assignedAgentId: this.agentId,
123
- status: 'in_progress',
124
- });
121
+ updateStory(
122
+ this.db,
123
+ story.id,
124
+ {
125
+ assignedAgentId: this.agentId,
126
+ status: 'in_progress',
127
+ },
128
+ this.storiesDir
129
+ );
125
130
  updateAgent(this.db, this.agentId, { currentStoryId: story.id });
126
131
 
127
132
  // Create feature branch
@@ -162,10 +167,15 @@ Let me know when you're ready to proceed or if you have questions.`;
162
167
  this.log('STORY_PROGRESS_UPDATE', response.substring(0, 200), { storyId: story.id });
163
168
 
164
169
  // Mark for review when done (simplified)
165
- updateStory(this.db, story.id, {
166
- branchName,
167
- status: 'review',
168
- });
170
+ updateStory(
171
+ this.db,
172
+ story.id,
173
+ {
174
+ branchName,
175
+ status: 'review',
176
+ },
177
+ this.storiesDir
178
+ );
169
179
 
170
180
  this.log('STORY_COMPLETED', `Implementation complete, ready for review`, {
171
181
  storyId: story.id,
@@ -176,7 +186,7 @@ Let me know when you're ready to proceed or if you have questions.`;
176
186
  private async reviewStory(story: StoryRow): Promise<void> {
177
187
  if (story.assigned_agent_id === this.agentId) {
178
188
  // Self-review, move to QA
179
- updateStory(this.db, story.id, { status: 'qa' });
189
+ updateStory(this.db, story.id, { status: 'qa' }, this.storiesDir);
180
190
  this.log('STORY_REVIEW_REQUESTED', 'Self-implemented, moving to QA', { storyId: story.id });
181
191
  return;
182
192
  }
@@ -207,14 +217,14 @@ If issues found, describe them. If approved, confirm.`;
207
217
 
208
218
  if (hasIssues) {
209
219
  // Send back for fixes
210
- updateStory(this.db, story.id, { status: 'in_progress' });
220
+ updateStory(this.db, story.id, { status: 'in_progress' }, this.storiesDir);
211
221
  this.log('STORY_PROGRESS_UPDATE', 'Review issues found, sent back for fixes', {
212
222
  storyId: story.id,
213
223
  review: review.substring(0, 200),
214
224
  });
215
225
  } else {
216
226
  // Approve and move to QA
217
- updateStory(this.db, story.id, { status: 'qa' });
227
+ updateStory(this.db, story.id, { status: 'qa' }, this.storiesDir);
218
228
  this.log('STORY_REVIEW_REQUESTED', 'Review passed, moving to QA', { storyId: story.id });
219
229
  }
220
230
  }
@@ -196,23 +196,35 @@ Respond in JSON format:
196
196
  const storyIds: string[] = [];
197
197
  const storyIdMap: Record<string, string> = {};
198
198
 
199
+ const hiveRoot = findHiveRoot(this.workDir);
200
+ const storiesDir = hiveRoot ? getHivePaths(hiveRoot).storiesDir : undefined;
201
+
199
202
  for (const story of analysis.stories) {
200
203
  const team = this.teams.find(t => t.name === story.teamName);
201
204
 
202
- const storyRow = createStory(this.db, {
203
- requirementId: this.requirement!.id,
204
- teamId: team?.id,
205
- title: story.title,
206
- description: story.description,
207
- acceptanceCriteria: story.acceptanceCriteria,
208
- });
205
+ const storyRow = createStory(
206
+ this.db,
207
+ {
208
+ requirementId: this.requirement!.id,
209
+ teamId: team?.id,
210
+ title: story.title,
211
+ description: story.description,
212
+ acceptanceCriteria: story.acceptanceCriteria,
213
+ },
214
+ storiesDir
215
+ );
209
216
 
210
217
  // Update with complexity
211
- updateStory(this.db, storyRow.id, {
212
- complexityScore: story.estimatedComplexity,
213
- storyPoints: story.estimatedComplexity,
214
- status: 'estimated',
215
- });
218
+ updateStory(
219
+ this.db,
220
+ storyRow.id,
221
+ {
222
+ complexityScore: story.estimatedComplexity,
223
+ storyPoints: story.estimatedComplexity,
224
+ status: 'estimated',
225
+ },
226
+ storiesDir
227
+ );
216
228
 
217
229
  storyIds.push(storyRow.id);
218
230
  storyIdMap[story.title] = storyRow.id;
@@ -282,8 +294,11 @@ Respond in JSON format:
282
294
  const model = resolveRuntimeModelForCli(agentConfig.model, cliTool);
283
295
 
284
296
  // Build spawn command using CLI runtime builder (spawn fresh session, will be resumed later)
297
+ const chromeEnabled = config.agents?.chrome_enabled === true && cliTool === 'claude';
285
298
  const runtimeBuilder = getCliRuntimeBuilder(cliTool);
286
- const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode);
299
+ const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode, {
300
+ chrome: chromeEnabled,
301
+ });
287
302
 
288
303
  await spawnTmuxSession({
289
304
  sessionName,
@@ -133,6 +133,7 @@ CREATE TABLE IF NOT EXISTS stories (
133
133
  external_subtask_id: null,
134
134
  external_provider: null,
135
135
  in_sprint: 0,
136
+ markdown_path: null,
136
137
  created_at: new Date().toISOString(),
137
138
  updated_at: new Date().toISOString(),
138
139
  },
@@ -161,6 +162,7 @@ CREATE TABLE IF NOT EXISTS stories (
161
162
  external_subtask_id: null,
162
163
  external_provider: null,
163
164
  in_sprint: 0,
165
+ markdown_path: null,
164
166
  created_at: new Date().toISOString(),
165
167
  updated_at: new Date().toISOString(),
166
168
  },
@@ -246,6 +248,7 @@ CREATE TABLE IF NOT EXISTS stories (
246
248
  external_subtask_id: null,
247
249
  external_provider: null,
248
250
  in_sprint: 0,
251
+ markdown_path: null,
249
252
  created_at: new Date().toISOString(),
250
253
  updated_at: new Date().toISOString(),
251
254
  },
@@ -288,6 +291,7 @@ CREATE TABLE IF NOT EXISTS stories (
288
291
  external_subtask_id: null,
289
292
  external_provider: null,
290
293
  in_sprint: 0,
294
+ markdown_path: null,
291
295
  created_at: new Date().toISOString(),
292
296
  updated_at: new Date().toISOString(),
293
297
  },
@@ -316,6 +320,7 @@ CREATE TABLE IF NOT EXISTS stories (
316
320
  external_subtask_id: null,
317
321
  external_provider: null,
318
322
  in_sprint: 0,
323
+ markdown_path: null,
319
324
  created_at: new Date().toISOString(),
320
325
  updated_at: new Date().toISOString(),
321
326
  },
@@ -18,8 +18,10 @@ export const assignCommand = new Command('assign')
18
18
  .action(async (options: { dryRun?: boolean }) => {
19
19
  // Track if we need to start the manager after DB is closed
20
20
  let shouldStartManager = false;
21
+ let savedHiveDir: string | undefined;
21
22
 
22
23
  await withHiveContext(async ({ root, paths, db }) => {
24
+ savedHiveDir = paths.hiveDir;
23
25
  const spinner = ora('Assigning stories...').start();
24
26
 
25
27
  try {
@@ -167,7 +169,7 @@ export const assignCommand = new Command('assign')
167
169
  // Determine if we should start the manager, but don't start it yet
168
170
  // Wait until after withHiveContext closes the DB to prevent race condition
169
171
  if (result.assigned > 0) {
170
- shouldStartManager = !(await isManagerRunning());
172
+ shouldStartManager = !(await isManagerRunning(paths.hiveDir));
171
173
  }
172
174
 
173
175
  console.log();
@@ -185,7 +187,7 @@ export const assignCommand = new Command('assign')
185
187
  // This prevents the race condition where manager loads stale DB state
186
188
  if (shouldStartManager) {
187
189
  const spinner = ora('Starting manager daemon...').start();
188
- const started = await startManager(60);
190
+ const started = await startManager(60, savedHiveDir);
189
191
  if (started) {
190
192
  spinner.succeed(chalk.green('Manager daemon started (checking every 60s)'));
191
193
  } else {
@@ -10,6 +10,7 @@ import { createLog } from '../../../db/queries/logs.js';
10
10
  import { updateRequirement } from '../../../db/queries/requirements.js';
11
11
  import { getStoriesByStatus, updateStory } from '../../../db/queries/stories.js';
12
12
  import { isTmuxSessionRunning } from '../../../tmux/manager.js';
13
+ import { getTechLeadSessionName } from '../../../utils/instance.js';
13
14
  import {
14
15
  createManagerNudgeEnvelope,
15
16
  sendToTmuxSession,
@@ -86,14 +87,15 @@ async function nudgeTechLeadForStalledHandoff(
86
87
  estimatedCount: number
87
88
  ): Promise<boolean> {
88
89
  // Brief lock for DB read
90
+ const fallbackSession = getTechLeadSessionName(ctx.paths.hiveDir);
89
91
  const techLeadInfo = await ctx.withDb(async db => {
90
92
  const techLead = getTechLead(db.db);
91
93
  return techLead
92
94
  ? {
93
- sessionName: techLead.tmux_session || 'hive-tech-lead',
95
+ sessionName: techLead.tmux_session || fallbackSession,
94
96
  cliTool: (techLead.cli_tool || 'claude') as CLITool,
95
97
  }
96
- : { sessionName: 'hive-tech-lead', cliTool: 'claude' as CLITool };
98
+ : { sessionName: fallbackSession, cliTool: 'claude' as CLITool };
97
99
  });
98
100
 
99
101
  if (!(await isTmuxSessionRunning(techLeadInfo.sessionName))) {
@@ -3,7 +3,6 @@
3
3
  import chalk from 'chalk';
4
4
  import { Command } from 'commander';
5
5
  import { createHash } from 'crypto';
6
- import { join } from 'path';
7
6
  import { ClusterRuntime, fetchLocalClusterStatus } from '../../../cluster/runtime.js';
8
7
  import { loadConfig } from '../../../config/loader.js';
9
8
  import type { HiveConfig } from '../../../config/schema.js';
@@ -26,6 +25,7 @@ import { AgentState } from '../../../state-detectors/types.js';
26
25
  import {
27
26
  captureTmuxPane,
28
27
  getHiveSessions,
28
+ getManagerSession,
29
29
  isManagerRunning,
30
30
  isTmuxSessionRunning,
31
31
  killTmuxSession,
@@ -34,6 +34,7 @@ import {
34
34
  import type { WithLockFn } from '../../../utils/auto-merge.js';
35
35
  import { autoMergeApprovedPRs } from '../../../utils/auto-merge.js';
36
36
  import type { CLITool } from '../../../utils/cli-commands.js';
37
+ import { getManagerLockPath, getTechLeadSessionName } from '../../../utils/instance.js';
37
38
  import { withHiveContext, withHiveRoot } from '../../../utils/with-hive-context.js';
38
39
  import {
39
40
  agentStates,
@@ -265,7 +266,7 @@ managerCommand
265
266
  const config = loadConfig(paths.hiveDir);
266
267
  let clusterRuntime: ClusterRuntime | null = null;
267
268
 
268
- const lockPath = join(paths.hiveDir, 'manager.lock');
269
+ const lockPath = getManagerLockPath(paths.hiveDir);
269
270
 
270
271
  // Acquire manager lock to ensure singleton
271
272
  let releaseLock: (() => Promise<void>) | null = null;
@@ -474,10 +475,12 @@ managerCommand
474
475
  .command('status')
475
476
  .description('Check if the manager daemon is running')
476
477
  .action(async () => {
477
- const running = await isManagerRunning();
478
+ const { paths } = withHiveRoot(c => c);
479
+ const managerSession = getManagerSession(paths.hiveDir);
480
+ const running = await isManagerRunning(paths.hiveDir);
478
481
  if (running) {
479
- console.log(chalk.green('Manager daemon is running (hive-manager tmux session)'));
480
- console.log(chalk.gray('To view: tmux attach -t hive-manager'));
482
+ console.log(chalk.green(`Manager daemon is running (${managerSession} tmux session)`));
483
+ console.log(chalk.gray(`To view: tmux attach -t ${managerSession}`));
481
484
  console.log(chalk.gray('To stop: hive manager stop'));
482
485
  } else {
483
486
  console.log(chalk.yellow('Manager daemon is not running'));
@@ -490,7 +493,8 @@ managerCommand
490
493
  .command('stop')
491
494
  .description('Stop the manager daemon')
492
495
  .action(async () => {
493
- const stopped = await stopManagerSession();
496
+ const { paths: stopPaths } = withHiveRoot(c => c);
497
+ const stopped = await stopManagerSession(stopPaths.hiveDir);
494
498
  if (stopped) {
495
499
  console.log(chalk.green('Manager daemon stopped'));
496
500
  } else {
@@ -535,8 +539,9 @@ async function managerCheck(
535
539
  const syncResult = await clusterRuntime.sync(db.db);
536
540
  if (!clusterRuntime.isLeader()) {
537
541
  const status = clusterRuntime.getStatus();
538
- if (await isTmuxSessionRunning('hive-tech-lead')) {
539
- await killTmuxSession('hive-tech-lead');
542
+ const techLeadSession = getTechLeadSessionName(paths.hiveDir);
543
+ if (await isTmuxSessionRunning(techLeadSession)) {
544
+ await killTmuxSession(techLeadSession);
540
545
  }
541
546
  const details = [];
542
547
  if (syncResult.local_events_emitted > 0) {
@@ -648,8 +653,8 @@ async function managerCheck(
648
653
 
649
654
  // Discover active tmux sessions
650
655
  verboseLogCtx(ctx, 'Step: discover hive tmux sessions');
651
- const sessions = await getHiveSessions();
652
- ctx.hiveSessions = sessions.filter(s => s.name.startsWith('hive-'));
656
+ const sessions = await getHiveSessions(ctx.paths.hiveDir);
657
+ ctx.hiveSessions = sessions;
653
658
  verboseLogCtx(ctx, `Discovered ${ctx.hiveSessions.length} hive session(s)`);
654
659
  await resolveOrphanedSessionEscalations(ctx);
655
660
 
@@ -966,7 +971,7 @@ async function scanAgentSessions(ctx: ManagerCheckContext): Promise<void> {
966
971
 
967
972
  // Phase 2: Per-session processing (tmux/AI outside lock, DB writes under brief locks)
968
973
  for (const session of ctx.hiveSessions) {
969
- if (session.name === 'hive-manager') continue;
974
+ if (session.name === getManagerSession(ctx.paths.hiveDir)) continue;
970
975
 
971
976
  const agent = ctx.agentsBySessionName.get(session.name);
972
977
 
@@ -15,6 +15,7 @@ import {
15
15
  spawnTmuxSession,
16
16
  } from '../../../tmux/manager.js';
17
17
  import type { CLITool } from '../../../utils/cli-commands.js';
18
+ import { getTechLeadSessionName } from '../../../utils/instance.js';
18
19
  import { findHiveRoot as findHiveRootFromDir, getHivePaths } from '../../../utils/paths.js';
19
20
  import { generateTechLeadPrompt } from '../req.js';
20
21
  import { detectAgentState } from './agent-monitoring.js';
@@ -130,8 +131,11 @@ export async function restartStaleTechLead(ctx: ManagerCheckContext): Promise<vo
130
131
  const safetyMode = agentConfig.safety_mode;
131
132
  const model = resolveRuntimeModelForCli(agentConfig.model, cliTool);
132
133
 
134
+ const chromeEnabled = config.agents?.chrome_enabled === true && cliTool === 'claude';
133
135
  const runtimeBuilder = getCliRuntimeBuilder(cliTool);
134
- const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode);
136
+ const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode, {
137
+ chrome: chromeEnabled,
138
+ });
135
139
 
136
140
  // Look up active requirement and teams to provide context to the restarted tech lead
137
141
  const initialPrompt = await ctx.withDb(async db => {
@@ -147,10 +151,12 @@ export async function restartStaleTechLead(ctx: ManagerCheckContext): Promise<vo
147
151
  activeReq.description,
148
152
  teams,
149
153
  activeReq.godmode === 1,
150
- activeReq.target_branch || 'main'
154
+ activeReq.target_branch || 'main',
155
+ getTechLeadSessionName(paths.hiveDir)
151
156
  );
152
157
  }
153
158
 
159
+ const techLeadInbox = getTechLeadSessionName(paths.hiveDir);
154
160
  return `You are the Tech Lead of Hive, an AI development team orchestrator.
155
161
 
156
162
  You have been restarted to refresh your context. No active requirement is currently being planned.
@@ -164,7 +170,7 @@ hive status
164
170
 
165
171
  2. Check your inbox for messages from developers:
166
172
  \`\`\`bash
167
- hive msg inbox hive-tech-lead
173
+ hive msg inbox ${techLeadInbox}
168
174
  \`\`\`
169
175
 
170
176
  3. If there are pending requirements, begin planning them. If all work is complete, monitor for new requirements.`;
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import { Command } from 'commander';
5
5
  import { nanoid } from 'nanoid';
6
6
  import { queryAll, queryOne, run } from '../../db/client.js';
7
+ import { getTechLeadSessionName } from '../../utils/instance.js';
7
8
  import { withHiveContext, withReadOnlyHiveContext } from '../../utils/with-hive-context.js';
8
9
 
9
10
  interface MessageRow {
@@ -24,12 +25,12 @@ msgCommand
24
25
  .command('send <to-session> <message>')
25
26
  .description('Send a message to another agent')
26
27
  .option('-s, --subject <subject>', 'Message subject')
27
- .option('-f, --from <session>', 'Your session name (defaults to hive-tech-lead)')
28
+ .option('-f, --from <session>', 'Your session name (defaults to tech lead session)')
28
29
  .action(
29
30
  async (toSession: string, message: string, options: { subject?: string; from?: string }) => {
30
- await withHiveContext(async ({ db }) => {
31
+ await withHiveContext(async ({ db, paths }) => {
31
32
  const id = `msg-${nanoid(8)}`;
32
- const fromSession = options.from || 'hive-tech-lead';
33
+ const fromSession = options.from || getTechLeadSessionName(paths.hiveDir);
33
34
 
34
35
  run(
35
36
  db.db,
@@ -53,8 +54,8 @@ msgCommand
53
54
  .description('Check inbox for messages')
54
55
  .option('--all', 'Show all messages including read')
55
56
  .action(async (session: string | undefined, options: { all?: boolean }) => {
56
- await withReadOnlyHiveContext(async ({ db }) => {
57
- const targetSession = session || 'hive-tech-lead';
57
+ await withReadOnlyHiveContext(async ({ db, paths }) => {
58
+ const targetSession = session || getTechLeadSessionName(paths.hiveDir);
58
59
 
59
60
  let query = `
60
61
  SELECT * FROM messages
@@ -165,8 +166,8 @@ msgCommand
165
166
  .command('outbox [session]')
166
167
  .description('Check sent messages and their replies')
167
168
  .action(async (session: string | undefined) => {
168
- await withReadOnlyHiveContext(async ({ db }) => {
169
- const fromSession = session || 'hive-tech-lead';
169
+ await withReadOnlyHiveContext(async ({ db, paths }) => {
170
+ const fromSession = session || getTechLeadSessionName(paths.hiveDir);
170
171
 
171
172
  const messages = queryAll<MessageRow>(
172
173
  db.db,
@@ -228,7 +228,7 @@ myStoriesCommand
228
228
  process.exit(1);
229
229
  }
230
230
 
231
- await withHiveContext(async ({ db }) => {
231
+ await withHiveContext(async ({ paths, db }) => {
232
232
  const agent = requireAgentBySession(db.db, options.session);
233
233
 
234
234
  if (!agent.team_id) {
@@ -245,19 +245,28 @@ myStoriesCommand
245
245
  ? trimmedTitle
246
246
  : `Refactor: ${trimmedTitle}`;
247
247
 
248
- const story = createStory(db.db, {
249
- teamId: agent.team_id,
250
- title: normalizedTitle,
251
- description: options.description.trim(),
252
- acceptanceCriteria:
253
- options.criteria && options.criteria.length > 0 ? options.criteria : null,
254
- });
248
+ const story = createStory(
249
+ db.db,
250
+ {
251
+ teamId: agent.team_id,
252
+ title: normalizedTitle,
253
+ description: options.description.trim(),
254
+ acceptanceCriteria:
255
+ options.criteria && options.criteria.length > 0 ? options.criteria : null,
256
+ },
257
+ paths.storiesDir
258
+ );
255
259
 
256
- const updatedStory = updateStory(db.db, story.id, {
257
- complexityScore: points,
258
- storyPoints: points,
259
- status,
260
- });
260
+ const updatedStory = updateStory(
261
+ db.db,
262
+ story.id,
263
+ {
264
+ complexityScore: points,
265
+ storyPoints: points,
266
+ status,
267
+ },
268
+ paths.storiesDir
269
+ );
261
270
 
262
271
  createLog(db.db, {
263
272
  agentId: agent.id,
@@ -23,6 +23,16 @@ vi.mock('../../utils/with-hive-context.js', () => ({
23
23
  ),
24
24
  }));
25
25
 
26
+ vi.mock('fs', async importOriginal => {
27
+ const actual = await importOriginal<typeof import('fs')>();
28
+ return {
29
+ ...actual,
30
+ existsSync: vi.fn(() => true),
31
+ unlinkSync: vi.fn(),
32
+ };
33
+ });
34
+
35
+ import { existsSync } from 'fs';
26
36
  import { queryAll, run } from '../../db/client.js';
27
37
  import { removeWorktree } from '../../git/worktree.js';
28
38
  import { killAllHiveSessions } from '../../tmux/manager.js';
@@ -31,6 +41,7 @@ import { nukeCommand } from './nuke.js';
31
41
  describe('nuke command', () => {
32
42
  beforeEach(() => {
33
43
  vi.clearAllMocks();
44
+ vi.mocked(existsSync).mockReturnValue(true);
34
45
  });
35
46
 
36
47
  describe('command structure', () => {
@@ -145,6 +156,26 @@ describe('nuke command', () => {
145
156
 
146
157
  expect(killAllHiveSessions).toHaveBeenCalled();
147
158
  });
159
+
160
+ it('should skip worktree removal when path does not exist on disk', async () => {
161
+ vi.mocked(queryAll).mockReturnValue([{ worktree_path: 'repos/team-agent-stale' }]);
162
+ vi.mocked(existsSync).mockReturnValue(false);
163
+
164
+ const agentsCmd = nukeCommand.commands.find(cmd => cmd.name() === 'agents');
165
+ await agentsCmd?.parseAsync(['--force'], { from: 'user' });
166
+
167
+ expect(removeWorktree).not.toHaveBeenCalled();
168
+ });
169
+
170
+ it('should call removeWorktree when worktree path exists on disk', async () => {
171
+ vi.mocked(queryAll).mockReturnValue([{ worktree_path: 'repos/team-agent-abc123' }]);
172
+ vi.mocked(existsSync).mockReturnValue(true);
173
+
174
+ const agentsCmd = nukeCommand.commands.find(cmd => cmd.name() === 'agents');
175
+ await agentsCmd?.parseAsync(['--force'], { from: 'user' });
176
+
177
+ expect(removeWorktree).toHaveBeenCalledWith('/root', 'repos/team-agent-abc123');
178
+ });
148
179
  });
149
180
 
150
181
  describe('nuke all worktree and table cleanup', () => {
@@ -3,7 +3,7 @@
3
3
  import chalk from 'chalk';
4
4
  import { Command } from 'commander';
5
5
  import { existsSync, unlinkSync } from 'fs';
6
- import { join } from 'path';
6
+ import { join, resolve } from 'path';
7
7
  import readline from 'readline';
8
8
  import { queryAll, queryOne, run } from '../../db/client.js';
9
9
  import { removeWorktree } from '../../git/worktree.js';
@@ -101,6 +101,13 @@ async function removeAgentWorktrees(
101
101
  let removed = 0;
102
102
  for (const agent of agents) {
103
103
  if (agent.worktree_path) {
104
+ const fullPath = resolve(root, agent.worktree_path);
105
+ if (!existsSync(fullPath)) {
106
+ if (process.env.HIVE_DEBUG) {
107
+ console.log(`[debug] skipping missing worktree: ${fullPath}`);
108
+ }
109
+ continue;
110
+ }
104
111
  const result = removeWorktree(root, agent.worktree_path);
105
112
  if (result.success) {
106
113
  removed++;
@@ -174,7 +181,7 @@ export const nukeCommand = new Command('nuke')
174
181
  .option('--force', 'Skip confirmation')
175
182
  .action(async (options: { force?: boolean }) => {
176
183
  try {
177
- await withHiveContext(async ({ db, root }) => {
184
+ await withHiveContext(async ({ db, root, paths }) => {
178
185
  const count = queryOne<{ count: number }>(
179
186
  db.db,
180
187
  'SELECT COUNT(*) as count FROM agents'
@@ -199,7 +206,7 @@ export const nukeCommand = new Command('nuke')
199
206
  }
200
207
 
201
208
  // Kill all hive tmux sessions first (this should always work regardless of DB state)
202
- const killed = await killAllHiveSessions();
209
+ const killed = await killAllHiveSessions(paths.hiveDir);
203
210
  console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
204
211
 
205
212
  // Remove agent worktrees from filesystem before deleting agents from DB
@@ -232,7 +239,9 @@ export const nukeCommand = new Command('nuke')
232
239
  ) {
233
240
  console.error(chalk.red(`\nDatabase is corrupted: ${message}`));
234
241
  console.log(chalk.yellow('Killing tmux sessions anyway...'));
235
- const killed = await killAllHiveSessions();
242
+ const hiveRoot = findHiveRoot();
243
+ const hDir = hiveRoot ? getHivePaths(hiveRoot).hiveDir : undefined;
244
+ const killed = await killAllHiveSessions(hDir);
236
245
  console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
237
246
  console.log(
238
247
  chalk.yellow('Agent records could not be deleted from the corrupted database.')
@@ -309,7 +318,7 @@ export const nukeCommand = new Command('nuke')
309
318
  .option('--force', 'Skip confirmation')
310
319
  .action(async (options: { force?: boolean }) => {
311
320
  try {
312
- await withHiveContext(async ({ db, root }) => {
321
+ await withHiveContext(async ({ db, root, paths }) => {
313
322
  console.log(chalk.red('\nThis will:'));
314
323
  console.log(chalk.yellow(' - Kill all hive tmux sessions'));
315
324
  console.log(chalk.yellow(' - Remove all agent git worktrees'));
@@ -333,7 +342,7 @@ export const nukeCommand = new Command('nuke')
333
342
  }
334
343
 
335
344
  // Kill all hive tmux sessions first (always works regardless of DB state)
336
- const killed = await killAllHiveSessions();
345
+ const killed = await killAllHiveSessions(paths.hiveDir);
337
346
  console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
338
347
 
339
348
  // Remove agent worktrees from filesystem before deleting agents from DB
@@ -371,7 +380,9 @@ export const nukeCommand = new Command('nuke')
371
380
  ) {
372
381
  console.error(chalk.red(`\nDatabase is corrupted: ${message}`));
373
382
  console.log(chalk.yellow('Killing tmux sessions anyway...'));
374
- const killed = await killAllHiveSessions();
383
+ const hiveRoot2 = findHiveRoot();
384
+ const hDir2 = hiveRoot2 ? getHivePaths(hiveRoot2).hiveDir : undefined;
385
+ const killed = await killAllHiveSessions(hDir2);
375
386
  console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
376
387
  console.log(
377
388
  chalk.yellow('Data records could not be deleted from the corrupted database.')