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
@@ -92,7 +92,8 @@ export function getAgentRoleDescription(agentType: string): string {
92
92
  /**
93
93
  * Format hive msg command examples
94
94
  */
95
- export function formatHiveMsgCommands(agentId?: string): string {
95
+ export function formatHiveMsgCommands(agentId?: string, techLeadSession?: string): string {
96
+ const tlSession = techLeadSession || 'hive-tech-lead';
96
97
  return `
97
98
  ## Communication with Hive Team
98
99
 
@@ -103,7 +104,7 @@ Use \`hive msg\` to communicate with other team members:
103
104
  hive msg send hive-senior-<team> "Your question here"
104
105
 
105
106
  # Send a message to the tech lead
106
- hive msg send hive-tech-lead "Your question here"
107
+ hive msg send ${tlSession} "Your question here"
107
108
 
108
109
  # Check your inbox
109
110
  hive msg inbox ${agentId || 'your-agent-id'}
@@ -108,6 +108,7 @@ describe('context-files module', () => {
108
108
  external_subtask_id: null,
109
109
  external_provider: null,
110
110
  in_sprint: 0,
111
+ markdown_path: null,
111
112
  created_at: new Date().toISOString(),
112
113
  updated_at: new Date().toISOString(),
113
114
  };
@@ -205,6 +206,7 @@ describe('context-files module', () => {
205
206
  checkpoint_threshold: 14000,
206
207
  llm_timeout_ms: 1800000,
207
208
  llm_max_retries: 2,
209
+ chrome_enabled: 'auto',
208
210
  },
209
211
  manager: {
210
212
  fast_poll_interval: 15000,
package/src/db/client.ts CHANGED
@@ -562,6 +562,12 @@ const MIGRATIONS: MigrationDefinition[] = [
562
562
  `);
563
563
  },
564
564
  },
565
+ {
566
+ name: '015-add-story-markdown-path.sql',
567
+ up: db => {
568
+ db.run('ALTER TABLE stories ADD COLUMN markdown_path TEXT');
569
+ },
570
+ },
565
571
  ];
566
572
 
567
573
  function runMigrations(db: SqlJsDatabase): void {
@@ -833,6 +839,7 @@ export interface StoryRow {
833
839
  external_subtask_id: string | null;
834
840
  external_provider: string | null;
835
841
  in_sprint: number;
842
+ markdown_path: string | null;
836
843
  created_at: string;
837
844
  updated_at: string;
838
845
  }
@@ -0,0 +1,5 @@
1
+ -- Migration 015: Add markdown_path column to stories table
2
+ -- Stories are now stored as markdown files in .hive/stories/ directory.
3
+ -- The DB stores the file path for reference.
4
+
5
+ ALTER TABLE stories ADD COLUMN markdown_path TEXT;
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { nanoid } from 'nanoid';
4
4
  import type { Database } from 'sql.js';
5
+ import { deleteStoryMarkdown, writeStoryMarkdown } from '../../utils/story-markdown.js';
5
6
  import { queryAll, queryOne, run, type StoryRow } from '../client.js';
6
7
 
7
8
  export type { StoryRow };
@@ -55,7 +56,7 @@ export interface UpdateStoryInput {
55
56
  inSprint?: boolean;
56
57
  }
57
58
 
58
- export function createStory(db: Database, input: CreateStoryInput): StoryRow {
59
+ export function createStory(db: Database, input: CreateStoryInput, storiesDir?: string): StoryRow {
59
60
  const id = `STORY-${nanoid(6).toUpperCase()}`;
60
61
  const acceptanceCriteria = input.acceptanceCriteria
61
62
  ? JSON.stringify(input.acceptanceCriteria)
@@ -80,7 +81,15 @@ export function createStory(db: Database, input: CreateStoryInput): StoryRow {
80
81
  ]
81
82
  );
82
83
 
83
- return getStoryById(db, id)!;
84
+ const story = getStoryById(db, id)!;
85
+
86
+ if (storiesDir) {
87
+ const markdownPath = writeStoryMarkdown(storiesDir, story);
88
+ run(db, 'UPDATE stories SET markdown_path = ? WHERE id = ?', [markdownPath, id]);
89
+ story.markdown_path = markdownPath;
90
+ }
91
+
92
+ return story;
84
93
  }
85
94
 
86
95
  export function getStoryById(db: Database, id: string): StoryRow | undefined {
@@ -170,7 +179,8 @@ export function getStoryPointsByTeam(db: Database, teamId: string): number {
170
179
  export function updateStory(
171
180
  db: Database,
172
181
  id: string,
173
- input: UpdateStoryInput
182
+ input: UpdateStoryInput,
183
+ storiesDir?: string
174
184
  ): StoryRow | undefined {
175
185
  const updates: string[] = ['updated_at = ?'];
176
186
  const values: (string | number | null)[] = [new Date().toISOString()];
@@ -271,10 +281,24 @@ export function updateStory(
271
281
 
272
282
  values.push(id);
273
283
  run(db, `UPDATE stories SET ${updates.join(', ')} WHERE id = ?`, values);
274
- return getStoryById(db, id);
284
+
285
+ const updatedStory = getStoryById(db, id);
286
+
287
+ if (storiesDir && updatedStory) {
288
+ const markdownPath = writeStoryMarkdown(storiesDir, updatedStory);
289
+ if (updatedStory.markdown_path !== markdownPath) {
290
+ run(db, 'UPDATE stories SET markdown_path = ? WHERE id = ?', [markdownPath, id]);
291
+ updatedStory.markdown_path = markdownPath;
292
+ }
293
+ }
294
+
295
+ return updatedStory;
275
296
  }
276
297
 
277
- export function deleteStory(db: Database, id: string): void {
298
+ export function deleteStory(db: Database, id: string, storiesDir?: string): void {
299
+ if (storiesDir) {
300
+ deleteStoryMarkdown(storiesDir, id);
301
+ }
278
302
  run(db, 'DELETE FROM story_dependencies WHERE story_id = ? OR depends_on_story_id = ?', [id, id]);
279
303
  run(db, 'DELETE FROM stories WHERE id = ?', [id]);
280
304
  }
@@ -90,6 +90,7 @@ export async function createTestDatabase(): Promise<SqlJsDatabase> {
90
90
  external_subtask_id TEXT,
91
91
  external_provider TEXT,
92
92
  in_sprint INTEGER DEFAULT 0,
93
+ markdown_path TEXT,
93
94
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
94
95
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
95
96
  );
@@ -7,12 +7,19 @@ vi.mock('child_process', () => ({
7
7
  execSync: vi.fn(),
8
8
  }));
9
9
 
10
+ vi.mock('fs', () => ({
11
+ existsSync: vi.fn(() => true),
12
+ }));
13
+
10
14
  import { execSync } from 'child_process';
15
+ import { existsSync } from 'fs';
11
16
 
12
17
  const mockExecSync = vi.mocked(execSync);
18
+ const mockExistsSync = vi.mocked(existsSync);
13
19
 
14
20
  beforeEach(() => {
15
21
  vi.clearAllMocks();
22
+ mockExistsSync.mockReturnValue(true);
16
23
  });
17
24
 
18
25
  describe('removeWorktree', () => {
@@ -81,4 +88,40 @@ describe('removeWorktree', () => {
81
88
  expect(result.success).toBe(false);
82
89
  expect(result.error).toBe('Unknown error');
83
90
  });
91
+
92
+ it('should return success without running git when worktree path does not exist on disk', () => {
93
+ mockExistsSync.mockReturnValue(false);
94
+
95
+ const result = removeWorktree('/root', 'repos/team-agent-stale');
96
+
97
+ expect(result.success).toBe(true);
98
+ expect(result.fullWorktreePath).toBe('/root/repos/team-agent-stale');
99
+ expect(mockExecSync).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it('should log debug message when path missing and HIVE_DEBUG is set', () => {
103
+ mockExistsSync.mockReturnValue(false);
104
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
105
+ process.env.HIVE_DEBUG = '1';
106
+
107
+ removeWorktree('/root', 'repos/team-agent-stale');
108
+
109
+ expect(consoleSpy).toHaveBeenCalledWith(
110
+ expect.stringContaining('does not exist on disk, skipping removal')
111
+ );
112
+
113
+ delete process.env.HIVE_DEBUG;
114
+ consoleSpy.mockRestore();
115
+ });
116
+
117
+ it('should not log when path missing and HIVE_DEBUG is not set', () => {
118
+ mockExistsSync.mockReturnValue(false);
119
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
120
+ delete process.env.HIVE_DEBUG;
121
+
122
+ removeWorktree('/root', 'repos/team-agent-stale');
123
+
124
+ expect(consoleSpy).not.toHaveBeenCalled();
125
+ consoleSpy.mockRestore();
126
+ });
84
127
  });
@@ -1,6 +1,7 @@
1
1
  // Licensed under the Hungry Ghost Hive License. See LICENSE.
2
2
 
3
3
  import { execSync } from 'child_process';
4
+ import { existsSync } from 'fs';
4
5
 
5
6
  export interface RemoveWorktreeResult {
6
7
  success: boolean;
@@ -27,6 +28,15 @@ export function removeWorktree(
27
28
  return { success: true, fullWorktreePath };
28
29
  }
29
30
 
31
+ if (!existsSync(fullWorktreePath)) {
32
+ if (process.env.HIVE_DEBUG) {
33
+ console.log(
34
+ `[debug] worktree path ${fullWorktreePath} does not exist on disk, skipping removal`
35
+ );
36
+ }
37
+ return { success: true, fullWorktreePath };
38
+ }
39
+
30
40
  try {
31
41
  execSync(`git worktree remove "${fullWorktreePath}" --force`, {
32
42
  cwd: rootDir,
@@ -114,6 +114,7 @@ describe('Prompt Templates', () => {
114
114
  external_subtask_id: null,
115
115
  external_provider: null,
116
116
  in_sprint: 0,
117
+ markdown_path: null,
117
118
  created_at: '2024-01-01',
118
119
  updated_at: '2024-01-01',
119
120
  },
@@ -153,6 +154,7 @@ describe('Prompt Templates', () => {
153
154
  external_subtask_id: null,
154
155
  external_provider: null,
155
156
  in_sprint: 0,
157
+ markdown_path: null,
156
158
  created_at: '2024-01-01',
157
159
  updated_at: '2024-01-01',
158
160
  },
@@ -230,6 +232,7 @@ describe('Prompt Templates', () => {
230
232
  external_subtask_id: null,
231
233
  external_provider: null,
232
234
  in_sprint: 0,
235
+ markdown_path: null,
233
236
  created_at: '2024-01-01',
234
237
  updated_at: '2024-01-01',
235
238
  },
@@ -258,6 +261,7 @@ describe('Prompt Templates', () => {
258
261
  external_subtask_id: null,
259
262
  external_provider: null,
260
263
  in_sprint: 0,
264
+ markdown_path: null,
261
265
  created_at: '2024-01-01',
262
266
  updated_at: '2024-01-01',
263
267
  },
@@ -4,6 +4,8 @@ import type { StoryRow } from '../db/client.js';
4
4
 
5
5
  export interface AgentPromptOptions {
6
6
  includeProgressUpdates?: boolean;
7
+ /** The tech lead tmux session name for messaging. Defaults to 'hive-tech-lead' for backwards compatibility. */
8
+ techLeadSession?: string;
7
9
  }
8
10
 
9
11
  /**
@@ -60,6 +62,10 @@ function shouldIncludeProgressUpdates(options?: AgentPromptOptions): boolean {
60
62
  return options?.includeProgressUpdates ?? true;
61
63
  }
62
64
 
65
+ function resolveTechLeadSession(options?: AgentPromptOptions): string {
66
+ return options?.techLeadSession || 'hive-tech-lead';
67
+ }
68
+
63
69
  function repositorySection(repoPath: string, repoUrl: string): string {
64
70
  return `## Your Repository
65
71
  - Local path: ${repoPath}
@@ -231,6 +237,7 @@ export function generateSeniorPrompt(
231
237
  sessionNameOverride?: string
232
238
  ): string {
233
239
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
240
+ const techLeadSession = resolveTechLeadSession(options);
234
241
  const storyList = stories
235
242
  .map(s => {
236
243
  const externalInfo = s.external_subtask_key
@@ -283,7 +290,7 @@ hive pr queue
283
290
  ## Communication with Tech Lead
284
291
  If you have questions or need guidance, message the Tech Lead:
285
292
  \`\`\`bash
286
- hive msg send hive-tech-lead "Your question here" --from ${sessionName}
293
+ hive msg send ${techLeadSession} "Your question here" --from ${sessionName}
287
294
  \`\`\`
288
295
 
289
296
  Check for replies:
@@ -318,6 +325,7 @@ export function generateIntermediatePrompt(
318
325
  options?: AgentPromptOptions
319
326
  ): string {
320
327
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
328
+ const techLeadSession = resolveTechLeadSession(options);
321
329
  const seniorSession = formatSeniorSessionName(teamName);
322
330
 
323
331
  return `You are an Intermediate Developer on Team ${teamName}.
@@ -354,7 +362,7 @@ ${prSubmissionSection(sessionName, targetBranch)}
354
362
  If you have questions, message your Senior or the Tech Lead:
355
363
  \`\`\`bash
356
364
  hive msg send ${seniorSession} "Your question" --from ${sessionName}
357
- hive msg send hive-tech-lead "Your question" --from ${sessionName}
365
+ hive msg send ${techLeadSession} "Your question" --from ${sessionName}
358
366
  \`\`\`
359
367
 
360
368
  Check for replies:
@@ -389,6 +397,7 @@ export function generateJuniorPrompt(
389
397
  options?: AgentPromptOptions
390
398
  ): string {
391
399
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
400
+ const techLeadSession = resolveTechLeadSession(options);
392
401
  const seniorSession = formatSeniorSessionName(teamName);
393
402
 
394
403
  return `You are a Junior Developer on Team ${teamName}.
@@ -425,7 +434,7 @@ ${prSubmissionSection(sessionName, targetBranch)}
425
434
  If you have questions, message your Senior or the Tech Lead:
426
435
  \`\`\`bash
427
436
  hive msg send ${seniorSession} "Your question" --from ${sessionName}
428
- hive msg send hive-tech-lead "Your question" --from ${sessionName}
437
+ hive msg send ${techLeadSession} "Your question" --from ${sessionName}
429
438
  \`\`\`
430
439
 
431
440
  Check for replies:
@@ -557,6 +566,7 @@ export function generateFeatureTestPrompt(
557
566
  options?: AgentPromptOptions
558
567
  ): string {
559
568
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
569
+ const techLeadSession = resolveTechLeadSession(options);
560
570
  const reportResultsSection = includeProgressUpdates
561
571
  ? `**If all tests pass:**
562
572
  \`\`\`bash
@@ -572,12 +582,12 @@ Report results directly to the Tech Lead:
572
582
 
573
583
  **If all tests pass:**
574
584
  \`\`\`bash
575
- hive msg send hive-tech-lead "E2E tests PASSED for ${requirementId} on ${featureBranch}. [Include test summary: X passed, 0 failed. Total time: Xs]" --from ${sessionName}
585
+ hive msg send ${techLeadSession} "E2E tests PASSED for ${requirementId} on ${featureBranch}. [Include test summary: X passed, 0 failed. Total time: Xs]" --from ${sessionName}
576
586
  \`\`\`
577
587
 
578
588
  **If any tests fail:**
579
589
  \`\`\`bash
580
- hive msg send hive-tech-lead "E2E tests FAILED for ${requirementId} on ${featureBranch}. [Include failure details: X passed, Y failed. Failed tests: list. Error details: summary]" --from ${sessionName}
590
+ hive msg send ${techLeadSession} "E2E tests FAILED for ${requirementId} on ${featureBranch}. [Include failure details: X passed, Y failed. Failed tests: list. Error details: summary]" --from ${sessionName}
581
591
  \`\`\``;
582
592
 
583
593
  return `You are a Feature Test Agent on Team ${teamName}.
@@ -631,7 +641,7 @@ ${reportResultsSection}
631
641
  ## Communication
632
642
  If you encounter issues running the tests, message the Tech Lead:
633
643
  \`\`\`bash
634
- hive msg send hive-tech-lead "Issue running E2E tests for ${requirementId}: [describe issue]" --from ${sessionName}
644
+ hive msg send ${techLeadSession} "Issue running E2E tests for ${requirementId}: [describe issue]" --from ${sessionName}
635
645
  \`\`\`
636
646
 
637
647
  Check for replies:
@@ -656,8 +666,10 @@ Start by checking out the feature branch and reading the TESTING.md file.`;
656
666
  export function generateAuditorPrompt(
657
667
  sessionName: string,
658
668
  repoPath: string,
659
- repoUrl: string
669
+ repoUrl: string,
670
+ options?: AgentPromptOptions
660
671
  ): string {
672
+ const techLeadSession = resolveTechLeadSession(options);
661
673
  return `You are a Hive Auditor Agent.
662
674
  Your tmux session: ${sessionName}
663
675
 
@@ -725,7 +737,7 @@ tmux send-keys -t <session-name> Enter
725
737
  **Other unfixable issues:** Any issue you cannot resolve with the above actions.
726
738
  - Escalate to tech lead:
727
739
  \`\`\`bash
728
- hive msg send hive-tech-lead "AUDITOR: <description of issue, including agent id and story id>" --from ${sessionName}
740
+ hive msg send ${techLeadSession} "AUDITOR: <description of issue, including agent id and story id>" --from ${sessionName}
729
741
  \`\`\`
730
742
 
731
743
  ### 5. Self-terminate
@@ -945,6 +945,7 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
945
945
  external_subtask_id: null,
946
946
  external_provider: null,
947
947
  in_sprint: 0,
948
+ markdown_path: null,
948
949
  created_at: new Date().toISOString(),
949
950
  updated_at: new Date().toISOString(),
950
951
  };
@@ -1,5 +1,6 @@
1
1
  // Licensed under the Hungry Ghost Hive License. See LICENSE.
2
2
 
3
+ import { join } from 'path';
3
4
  import type { Database } from 'sql.js';
4
5
  import {
5
6
  getCliRuntimeBuilder,
@@ -45,6 +46,7 @@ import {
45
46
  spawnTmuxSession,
46
47
  startManager,
47
48
  } from '../tmux/manager.js';
49
+ import { getTechLeadSessionName } from '../utils/instance.js';
48
50
  import * as logger from '../utils/logger.js';
49
51
  import { selectAgentWithLeastWorkload } from './agent-selector.js';
50
52
  import { getCapacityPoints, selectStoriesForCapacity } from './capacity-planner.js';
@@ -243,7 +245,12 @@ export class Scheduler {
243
245
  const activeSeniors = getAgentsByTeam(this.db, teamId).filter(
244
246
  a => a.type === 'senior' && a.status !== 'terminated'
245
247
  );
246
- const seniorSessionPrefix = generateSessionName('senior', team.name);
248
+ const seniorSessionPrefix = generateSessionName(
249
+ 'senior',
250
+ team.name,
251
+ undefined,
252
+ join(this.config.rootDir, '.hive')
253
+ );
247
254
  const indexedSeniorSessions = activeSeniors
248
255
  .map(senior => {
249
256
  if (!senior.tmux_session) return null;
@@ -670,7 +677,7 @@ export class Scheduler {
670
677
  `
671
678
  );
672
679
 
673
- const liveSessions = await getHiveSessions();
680
+ const liveSessions = await getHiveSessions(join(this.config.rootDir, '.hive'));
674
681
  const liveSessionNames = new Set(liveSessions.map(s => s.name));
675
682
 
676
683
  let terminated = 0;
@@ -885,8 +892,9 @@ export class Scheduler {
885
892
  }
886
893
 
887
894
  private async ensureManagerRunning(): Promise<void> {
888
- if (!(await isManagerRunning())) {
889
- await startManager(DEFAULT_MANAGER_INTERVAL_SECONDS);
895
+ const hiveDir = join(this.config.rootDir, '.hive');
896
+ if (!(await isManagerRunning(hiveDir))) {
897
+ await startManager(DEFAULT_MANAGER_INTERVAL_SECONDS, hiveDir);
890
898
  }
891
899
  }
892
900
 
@@ -907,10 +915,11 @@ export class Scheduler {
907
915
  }
908
916
  ): Promise<AgentRow> {
909
917
  // Auditor uses a timestamp-based session name since it's ephemeral
918
+ const hiveDir = join(this.config.rootDir, '.hive');
910
919
  const sessionName =
911
920
  type === 'auditor'
912
- ? `hive-auditor-${Date.now()}`
913
- : generateSessionName(type, teamName, index);
921
+ ? generateSessionName('auditor', `${Date.now()}`, undefined, hiveDir)
922
+ : generateSessionName(type, teamName, index, hiveDir);
914
923
 
915
924
  // Prevent creating duplicate agents on same tmux session (for senior agents)
916
925
  if (type === 'senior') {
@@ -997,6 +1006,8 @@ export class Scheduler {
997
1006
  // Build the initial prompt for this agent type
998
1007
  const team = getTeamById(this.db, teamId);
999
1008
  const includeProgressUpdates = this.shouldIncludeProgressUpdates();
1009
+ const hiveDir = join(this.config.rootDir, '.hive');
1010
+ const techLeadSession = getTechLeadSessionName(hiveDir);
1000
1011
  let prompt: string;
1001
1012
 
1002
1013
  if (type === 'senior') {
@@ -1007,7 +1018,7 @@ export class Scheduler {
1007
1018
  worktreePath,
1008
1019
  stories,
1009
1020
  targetBranch,
1010
- { includeProgressUpdates },
1021
+ { includeProgressUpdates, techLeadSession },
1011
1022
  sessionName
1012
1023
  );
1013
1024
  } else if (type === 'intermediate') {
@@ -1017,7 +1028,7 @@ export class Scheduler {
1017
1028
  worktreePath,
1018
1029
  sessionName,
1019
1030
  targetBranch,
1020
- { includeProgressUpdates }
1031
+ { includeProgressUpdates, techLeadSession }
1021
1032
  );
1022
1033
  } else if (type === 'junior') {
1023
1034
  prompt = generateJuniorPrompt(
@@ -1026,7 +1037,7 @@ export class Scheduler {
1026
1037
  worktreePath,
1027
1038
  sessionName,
1028
1039
  targetBranch,
1029
- { includeProgressUpdates }
1040
+ { includeProgressUpdates, techLeadSession }
1030
1041
  );
1031
1042
  } else if (type === 'feature_test' && featureTestContext) {
1032
1043
  prompt = generateFeatureTestPrompt(
@@ -1037,10 +1048,12 @@ export class Scheduler {
1037
1048
  featureTestContext.featureBranch,
1038
1049
  featureTestContext.requirementId,
1039
1050
  featureTestContext.e2eTestsPath,
1040
- { includeProgressUpdates }
1051
+ { includeProgressUpdates, techLeadSession }
1041
1052
  );
1042
1053
  } else if (type === 'auditor') {
1043
- prompt = generateAuditorPrompt(sessionName, worktreePath, team?.repo_url || '');
1054
+ prompt = generateAuditorPrompt(sessionName, worktreePath, team?.repo_url || '', {
1055
+ techLeadSession,
1056
+ });
1044
1057
  } else {
1045
1058
  prompt = generateQAPrompt(
1046
1059
  teamName,
@@ -1052,7 +1065,15 @@ export class Scheduler {
1052
1065
  }
1053
1066
 
1054
1067
  // Build CLI command using the configured runtime
1055
- const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(runtimeModel, safetyMode);
1068
+ const chromeEnabled =
1069
+ this.config.hiveConfig?.agents?.chrome_enabled === true && cliTool === 'claude';
1070
+ const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(
1071
+ runtimeModel,
1072
+ safetyMode,
1073
+ {
1074
+ chrome: chromeEnabled,
1075
+ }
1076
+ );
1056
1077
 
1057
1078
  // Pass the prompt as initialPrompt so it's included as a CLI positional
1058
1079
  // argument via $(cat ...). This delivers the full multi-line prompt
@@ -4,6 +4,11 @@ import { execa } from 'execa';
4
4
  import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'fs';
5
5
  import { tmpdir } from 'os';
6
6
  import { dirname, isAbsolute, join, resolve } from 'path';
7
+ import {
8
+ buildInstanceSessionName,
9
+ getInstancePrefix,
10
+ getManagerSessionName,
11
+ } from '../utils/instance.js';
7
12
 
8
13
  // --- Named constants (extracted from inline magic numbers) ---
9
14
 
@@ -162,8 +167,12 @@ export async function listTmuxSessions(): Promise<TmuxSession[]> {
162
167
  }
163
168
  }
164
169
 
165
- export async function getHiveSessions(): Promise<TmuxSession[]> {
170
+ export async function getHiveSessions(hiveDir?: string): Promise<TmuxSession[]> {
166
171
  const sessions = await listTmuxSessions();
172
+ if (hiveDir) {
173
+ const prefix = getInstancePrefix(hiveDir);
174
+ return sessions.filter(s => s.name.startsWith(prefix));
175
+ }
167
176
  return sessions.filter(s => s.name.startsWith('hive-'));
168
177
  }
169
178
 
@@ -235,8 +244,8 @@ export async function killTmuxSession(sessionName: string): Promise<void> {
235
244
  }
236
245
  }
237
246
 
238
- export async function killAllHiveSessions(): Promise<number> {
239
- const sessions = await getHiveSessions();
247
+ export async function killAllHiveSessions(hiveDir?: string): Promise<number> {
248
+ const sessions = await getHiveSessions(hiveDir);
240
249
  let killed = 0;
241
250
 
242
251
  for (const session of sessions) {
@@ -513,7 +522,15 @@ export async function autoApprovePermission(
513
522
  return false;
514
523
  }
515
524
 
516
- export function generateSessionName(agentType: string, teamName?: string, index?: number): string {
525
+ export function generateSessionName(
526
+ agentType: string,
527
+ teamName?: string,
528
+ index?: number,
529
+ hiveDir?: string
530
+ ): string {
531
+ if (hiveDir) {
532
+ return buildInstanceSessionName(hiveDir, agentType, teamName, index);
533
+ }
517
534
  let name = `hive-${agentType}`;
518
535
  if (teamName) {
519
536
  name += `-${teamName}`;
@@ -526,12 +543,23 @@ export function generateSessionName(agentType: string, teamName?: string, index?
526
543
 
527
544
  const MANAGER_SESSION = 'hive-manager';
528
545
 
529
- export async function isManagerRunning(): Promise<boolean> {
530
- return isTmuxSessionRunning(MANAGER_SESSION);
546
+ export function getManagerSession(hiveDir?: string): string {
547
+ if (hiveDir) {
548
+ return getManagerSessionName(hiveDir);
549
+ }
550
+ return MANAGER_SESSION;
551
+ }
552
+
553
+ export async function isManagerRunning(hiveDir?: string): Promise<boolean> {
554
+ return isTmuxSessionRunning(getManagerSession(hiveDir));
531
555
  }
532
556
 
533
- export async function startManager(interval = DEFAULT_MANAGER_INTERVAL): Promise<boolean> {
534
- if (await isManagerRunning()) {
557
+ export async function startManager(
558
+ interval = DEFAULT_MANAGER_INTERVAL,
559
+ hiveDir?: string
560
+ ): Promise<boolean> {
561
+ const session = getManagerSession(hiveDir);
562
+ if (await isTmuxSessionRunning(session)) {
535
563
  return false; // Already running
536
564
  }
537
565
 
@@ -539,22 +567,23 @@ export async function startManager(interval = DEFAULT_MANAGER_INTERVAL): Promise
539
567
  const sessionEnv = buildTmuxSessionEnv(workDir);
540
568
 
541
569
  // Start the manager in a detached tmux session
542
- await execa('tmux', ['new-session', '-d', '-s', MANAGER_SESSION, '-c', workDir], {
570
+ await execa('tmux', ['new-session', '-d', '-s', session, '-c', workDir], {
543
571
  env: sessionEnv,
544
572
  });
545
573
 
546
574
  // Send the manager command
547
575
  const managerCommand = `${buildHiveInvokeCommand()} manager start -i ${interval}`;
548
- await execa('tmux', ['send-keys', '-t', MANAGER_SESSION, managerCommand, 'Enter']);
576
+ await execa('tmux', ['send-keys', '-t', session, managerCommand, 'Enter']);
549
577
 
550
578
  return true;
551
579
  }
552
580
 
553
- export async function stopManager(): Promise<boolean> {
554
- if (!(await isManagerRunning())) {
581
+ export async function stopManager(hiveDir?: string): Promise<boolean> {
582
+ const session = getManagerSession(hiveDir);
583
+ if (!(await isTmuxSessionRunning(session))) {
555
584
  return false; // Not running
556
585
  }
557
586
 
558
- await killTmuxSession(MANAGER_SESSION);
587
+ await killTmuxSession(session);
559
588
  return true;
560
589
  }