hungry-ghost-hive 0.44.0 → 0.46.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 (238) 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 +4 -2
  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/cluster.d.ts.map +1 -1
  24. package/dist/cli/commands/cluster.js +348 -1
  25. package/dist/cli/commands/cluster.js.map +1 -1
  26. package/dist/cli/commands/cluster.test.js +313 -9
  27. package/dist/cli/commands/cluster.test.js.map +1 -1
  28. package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
  29. package/dist/cli/commands/manager/handoff-recovery.js +4 -2
  30. package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
  31. package/dist/cli/commands/manager/index.d.ts.map +1 -1
  32. package/dist/cli/commands/manager/index.js +16 -12
  33. package/dist/cli/commands/manager/index.js.map +1 -1
  34. package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
  35. package/dist/cli/commands/manager/tech-lead-lifecycle.js +4 -2
  36. package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
  37. package/dist/cli/commands/msg.d.ts.map +1 -1
  38. package/dist/cli/commands/msg.js +8 -7
  39. package/dist/cli/commands/msg.js.map +1 -1
  40. package/dist/cli/commands/my-stories.js +3 -3
  41. package/dist/cli/commands/my-stories.js.map +1 -1
  42. package/dist/cli/commands/nuke.d.ts.map +1 -1
  43. package/dist/cli/commands/nuke.js +18 -7
  44. package/dist/cli/commands/nuke.js.map +1 -1
  45. package/dist/cli/commands/nuke.test.js +24 -0
  46. package/dist/cli/commands/nuke.test.js.map +1 -1
  47. package/dist/cli/commands/req-spawn.test.d.ts +2 -0
  48. package/dist/cli/commands/req-spawn.test.d.ts.map +1 -0
  49. package/dist/cli/commands/req-spawn.test.js +116 -0
  50. package/dist/cli/commands/req-spawn.test.js.map +1 -0
  51. package/dist/cli/commands/req.d.ts +1 -1
  52. package/dist/cli/commands/req.d.ts.map +1 -1
  53. package/dist/cli/commands/req.js +28 -18
  54. package/dist/cli/commands/req.js.map +1 -1
  55. package/dist/cli/commands/stories.js +3 -3
  56. package/dist/cli/commands/stories.js.map +1 -1
  57. package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
  58. package/dist/cli/dashboard/panels/agents.js +7 -3
  59. package/dist/cli/dashboard/panels/agents.js.map +1 -1
  60. package/dist/cluster/cluster-http-server.d.ts +32 -0
  61. package/dist/cluster/cluster-http-server.d.ts.map +1 -1
  62. package/dist/cluster/cluster-http-server.js +42 -0
  63. package/dist/cluster/cluster-http-server.js.map +1 -1
  64. package/dist/cluster/distributed-runtime-coverage.test.js +9 -0
  65. package/dist/cluster/distributed-runtime-coverage.test.js.map +1 -1
  66. package/dist/cluster/distributed-system.test.js +135 -0
  67. package/dist/cluster/distributed-system.test.js.map +1 -1
  68. package/dist/cluster/events.d.ts +23 -0
  69. package/dist/cluster/events.d.ts.map +1 -1
  70. package/dist/cluster/events.js +74 -0
  71. package/dist/cluster/events.js.map +1 -1
  72. package/dist/cluster/heartbeat-manager.d.ts +2 -0
  73. package/dist/cluster/heartbeat-manager.d.ts.map +1 -1
  74. package/dist/cluster/heartbeat-manager.js +42 -6
  75. package/dist/cluster/heartbeat-manager.js.map +1 -1
  76. package/dist/cluster/membership.test.d.ts +2 -0
  77. package/dist/cluster/membership.test.d.ts.map +1 -0
  78. package/dist/cluster/membership.test.js +416 -0
  79. package/dist/cluster/membership.test.js.map +1 -0
  80. package/dist/cluster/partition-safety.test.d.ts +2 -0
  81. package/dist/cluster/partition-safety.test.d.ts.map +1 -0
  82. package/dist/cluster/partition-safety.test.js +440 -0
  83. package/dist/cluster/partition-safety.test.js.map +1 -0
  84. package/dist/cluster/raft-state-machine.d.ts +33 -1
  85. package/dist/cluster/raft-state-machine.d.ts.map +1 -1
  86. package/dist/cluster/raft-state-machine.js +65 -3
  87. package/dist/cluster/raft-state-machine.js.map +1 -1
  88. package/dist/cluster/raft-store.d.ts +26 -1
  89. package/dist/cluster/raft-store.d.ts.map +1 -1
  90. package/dist/cluster/raft-store.js +137 -0
  91. package/dist/cluster/raft-store.js.map +1 -1
  92. package/dist/cluster/replication-lag.test.d.ts +2 -0
  93. package/dist/cluster/replication-lag.test.d.ts.map +1 -0
  94. package/dist/cluster/replication-lag.test.js +239 -0
  95. package/dist/cluster/replication-lag.test.js.map +1 -0
  96. package/dist/cluster/replication.d.ts +2 -2
  97. package/dist/cluster/replication.d.ts.map +1 -1
  98. package/dist/cluster/replication.js +1 -1
  99. package/dist/cluster/replication.js.map +1 -1
  100. package/dist/cluster/runtime.d.ts +78 -0
  101. package/dist/cluster/runtime.d.ts.map +1 -1
  102. package/dist/cluster/runtime.js +400 -13
  103. package/dist/cluster/runtime.js.map +1 -1
  104. package/dist/cluster/state-recovery.test.d.ts +2 -0
  105. package/dist/cluster/state-recovery.test.d.ts.map +1 -0
  106. package/dist/cluster/state-recovery.test.js +310 -0
  107. package/dist/cluster/state-recovery.test.js.map +1 -0
  108. package/dist/cluster/types.d.ts +30 -0
  109. package/dist/cluster/types.d.ts.map +1 -1
  110. package/dist/config/schema.d.ts +48 -0
  111. package/dist/config/schema.d.ts.map +1 -1
  112. package/dist/config/schema.js +11 -0
  113. package/dist/config/schema.js.map +1 -1
  114. package/dist/context-files/generator.d.ts +1 -1
  115. package/dist/context-files/generator.d.ts.map +1 -1
  116. package/dist/context-files/generator.js +4 -3
  117. package/dist/context-files/generator.js.map +1 -1
  118. package/dist/context-files/generator.test.js +51 -0
  119. package/dist/context-files/generator.test.js.map +1 -1
  120. package/dist/context-files/index.test.js +1 -0
  121. package/dist/context-files/index.test.js.map +1 -1
  122. package/dist/db/client.d.ts +1 -0
  123. package/dist/db/client.d.ts.map +1 -1
  124. package/dist/db/client.js +6 -0
  125. package/dist/db/client.js.map +1 -1
  126. package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
  127. package/dist/db/queries/stories.d.ts +3 -3
  128. package/dist/db/queries/stories.d.ts.map +1 -1
  129. package/dist/db/queries/stories.js +23 -5
  130. package/dist/db/queries/stories.js.map +1 -1
  131. package/dist/db/queries/test-helpers.d.ts.map +1 -1
  132. package/dist/db/queries/test-helpers.js +1 -0
  133. package/dist/db/queries/test-helpers.js.map +1 -1
  134. package/dist/git/worktree.d.ts.map +1 -1
  135. package/dist/git/worktree.js +7 -0
  136. package/dist/git/worktree.js.map +1 -1
  137. package/dist/git/worktree.test.js +30 -0
  138. package/dist/git/worktree.test.js.map +1 -1
  139. package/dist/orchestrator/orphan-recovery.d.ts +1 -1
  140. package/dist/orchestrator/orphan-recovery.d.ts.map +1 -1
  141. package/dist/orchestrator/orphan-recovery.js +4 -4
  142. package/dist/orchestrator/orphan-recovery.js.map +1 -1
  143. package/dist/orchestrator/prompt-templates.d.ts +6 -2
  144. package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
  145. package/dist/orchestrator/prompt-templates.js +61 -16
  146. package/dist/orchestrator/prompt-templates.js.map +1 -1
  147. package/dist/orchestrator/prompt-templates.test.js +214 -0
  148. package/dist/orchestrator/prompt-templates.test.js.map +1 -1
  149. package/dist/orchestrator/scheduler.d.ts +1 -0
  150. package/dist/orchestrator/scheduler.d.ts.map +1 -1
  151. package/dist/orchestrator/scheduler.js +30 -17
  152. package/dist/orchestrator/scheduler.js.map +1 -1
  153. package/dist/orchestrator/scheduler.test.js +98 -6
  154. package/dist/orchestrator/scheduler.test.js.map +1 -1
  155. package/dist/tmux/manager.d.ts +7 -6
  156. package/dist/tmux/manager.d.ts.map +1 -1
  157. package/dist/tmux/manager.js +29 -13
  158. package/dist/tmux/manager.js.map +1 -1
  159. package/dist/utils/instance.d.ts +32 -0
  160. package/dist/utils/instance.d.ts.map +1 -0
  161. package/dist/utils/instance.js +82 -0
  162. package/dist/utils/instance.js.map +1 -0
  163. package/dist/utils/instance.test.d.ts +2 -0
  164. package/dist/utils/instance.test.d.ts.map +1 -0
  165. package/dist/utils/instance.test.js +103 -0
  166. package/dist/utils/instance.test.js.map +1 -0
  167. package/dist/utils/paths.d.ts +2 -0
  168. package/dist/utils/paths.d.ts.map +1 -1
  169. package/dist/utils/paths.js +2 -0
  170. package/dist/utils/paths.js.map +1 -1
  171. package/dist/utils/paths.test.js +6 -0
  172. package/dist/utils/paths.test.js.map +1 -1
  173. package/dist/utils/story-markdown.d.ts +16 -0
  174. package/dist/utils/story-markdown.d.ts.map +1 -0
  175. package/dist/utils/story-markdown.js +82 -0
  176. package/dist/utils/story-markdown.js.map +1 -0
  177. package/dist/utils/story-markdown.test.d.ts +2 -0
  178. package/dist/utils/story-markdown.test.d.ts.map +1 -0
  179. package/dist/utils/story-markdown.test.js +143 -0
  180. package/dist/utils/story-markdown.test.js.map +1 -0
  181. package/package.json +1 -1
  182. package/src/agents/base-agent.ts +5 -0
  183. package/src/agents/intermediate.ts +2 -2
  184. package/src/agents/junior.ts +2 -2
  185. package/src/agents/qa.ts +13 -8
  186. package/src/agents/senior.ts +21 -11
  187. package/src/agents/tech-lead.ts +24 -12
  188. package/src/cli/commands/assign.test.ts +5 -0
  189. package/src/cli/commands/assign.ts +4 -2
  190. package/src/cli/commands/cluster.test.ts +387 -9
  191. package/src/cli/commands/cluster.ts +486 -1
  192. package/src/cli/commands/manager/handoff-recovery.ts +4 -2
  193. package/src/cli/commands/manager/index.ts +16 -11
  194. package/src/cli/commands/manager/tech-lead-lifecycle.ts +5 -2
  195. package/src/cli/commands/msg.ts +8 -7
  196. package/src/cli/commands/my-stories.ts +22 -13
  197. package/src/cli/commands/nuke.test.ts +31 -0
  198. package/src/cli/commands/nuke.ts +18 -7
  199. package/src/cli/commands/req-spawn.test.ts +153 -0
  200. package/src/cli/commands/req.ts +40 -23
  201. package/src/cli/commands/stories.ts +22 -13
  202. package/src/cli/dashboard/panels/agents.ts +7 -3
  203. package/src/cluster/cluster-http-server.ts +80 -0
  204. package/src/cluster/distributed-runtime-coverage.test.ts +9 -0
  205. package/src/cluster/distributed-system.test.ts +168 -0
  206. package/src/cluster/events.ts +90 -0
  207. package/src/cluster/heartbeat-manager.ts +48 -6
  208. package/src/cluster/membership.test.ts +498 -0
  209. package/src/cluster/partition-safety.test.ts +523 -0
  210. package/src/cluster/raft-state-machine.ts +76 -4
  211. package/src/cluster/raft-store.ts +167 -1
  212. package/src/cluster/replication-lag.test.ts +284 -0
  213. package/src/cluster/replication.ts +6 -0
  214. package/src/cluster/runtime.ts +551 -12
  215. package/src/cluster/state-recovery.test.ts +420 -0
  216. package/src/cluster/types.ts +32 -0
  217. package/src/config/schema.ts +11 -0
  218. package/src/context-files/generator.test.ts +55 -0
  219. package/src/context-files/generator.ts +8 -7
  220. package/src/context-files/index.test.ts +1 -0
  221. package/src/db/client.ts +7 -0
  222. package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
  223. package/src/db/queries/stories.ts +29 -5
  224. package/src/db/queries/test-helpers.ts +1 -0
  225. package/src/git/worktree.test.ts +43 -0
  226. package/src/git/worktree.ts +10 -0
  227. package/src/orchestrator/orphan-recovery.ts +32 -13
  228. package/src/orchestrator/prompt-templates.test.ts +267 -0
  229. package/src/orchestrator/prompt-templates.ts +69 -16
  230. package/src/orchestrator/scheduler.test.ts +130 -6
  231. package/src/orchestrator/scheduler.ts +66 -27
  232. package/src/tmux/manager.ts +42 -13
  233. package/src/utils/instance.test.ts +129 -0
  234. package/src/utils/instance.ts +95 -0
  235. package/src/utils/paths.test.ts +8 -0
  236. package/src/utils/paths.ts +3 -0
  237. package/src/utils/story-markdown.test.ts +176 -0
  238. package/src/utils/story-markdown.ts +94 -0
@@ -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,
@@ -14,7 +14,11 @@ import {
14
14
  * Detect and recover orphaned stories (assigned to terminated agents).
15
15
  * Returns the story IDs that were recovered.
16
16
  */
17
- export function detectAndRecoverOrphanedStories(db: Database, rootDir: string): string[] {
17
+ export function detectAndRecoverOrphanedStories(
18
+ db: Database,
19
+ rootDir: string,
20
+ storiesDir?: string
21
+ ): string[] {
18
22
  const orphanedAssignments = getStoriesWithOrphanedAssignments(db);
19
23
  const staleInProgressStories = getStaleInProgressStoriesWithoutAssignment(db);
20
24
  const inconsistentInProgressAssignments = getInProgressStoriesWithInconsistentAssignments(db);
@@ -26,10 +30,15 @@ export function detectAndRecoverOrphanedStories(db: Database, rootDir: string):
26
30
  if (recoveredSet.has(assignment.id)) continue;
27
31
 
28
32
  // Update story in single atomic operation
29
- updateStory(db, assignment.id, {
30
- assignedAgentId: null,
31
- status: 'planned',
32
- });
33
+ updateStory(
34
+ db,
35
+ assignment.id,
36
+ {
37
+ assignedAgentId: null,
38
+ status: 'planned',
39
+ },
40
+ storiesDir
41
+ );
33
42
  createLog(db, {
34
43
  agentId: 'scheduler',
35
44
  storyId: assignment.id,
@@ -52,10 +61,15 @@ export function detectAndRecoverOrphanedStories(db: Database, rootDir: string):
52
61
  try {
53
62
  if (recoveredSet.has(story.id)) continue;
54
63
 
55
- updateStory(db, story.id, {
56
- assignedAgentId: null,
57
- status: 'planned',
58
- });
64
+ updateStory(
65
+ db,
66
+ story.id,
67
+ {
68
+ assignedAgentId: null,
69
+ status: 'planned',
70
+ },
71
+ storiesDir
72
+ );
59
73
  createLog(db, {
60
74
  agentId: 'scheduler',
61
75
  storyId: story.id,
@@ -78,10 +92,15 @@ export function detectAndRecoverOrphanedStories(db: Database, rootDir: string):
78
92
  try {
79
93
  if (recoveredSet.has(assignment.id)) continue;
80
94
 
81
- updateStory(db, assignment.id, {
82
- assignedAgentId: null,
83
- status: 'planned',
84
- });
95
+ updateStory(
96
+ db,
97
+ assignment.id,
98
+ {
99
+ assignedAgentId: null,
100
+ status: 'planned',
101
+ },
102
+ storiesDir
103
+ );
85
104
  createLog(db, {
86
105
  agentId: 'scheduler',
87
106
  storyId: assignment.id,
@@ -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
  },
@@ -162,6 +164,82 @@ describe('Prompt Templates', () => {
162
164
  expect(prompt).toContain('complexity: ?');
163
165
  });
164
166
 
167
+ it('should include markdown_path in prompt when set on a story', () => {
168
+ const stories: StoryRow[] = [
169
+ {
170
+ id: 'STORY-003',
171
+ title: 'Story With Markdown',
172
+ description: 'DB description',
173
+ complexity_score: 3,
174
+ status: 'planned',
175
+ team_id: 'team-1',
176
+ requirement_id: null,
177
+ acceptance_criteria: null,
178
+ story_points: null,
179
+ assigned_agent_id: null,
180
+ branch_name: null,
181
+ pr_url: null,
182
+ jira_issue_key: null,
183
+ jira_issue_id: null,
184
+ jira_project_key: null,
185
+ jira_subtask_key: null,
186
+ jira_subtask_id: null,
187
+ external_issue_key: null,
188
+ external_issue_id: null,
189
+ external_project_key: null,
190
+ external_subtask_key: null,
191
+ external_subtask_id: null,
192
+ external_provider: null,
193
+ in_sprint: 0,
194
+ markdown_path: '/stories/STORY-003.md',
195
+ created_at: '2024-01-01',
196
+ updated_at: '2024-01-01',
197
+ },
198
+ ];
199
+ const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
200
+
201
+ expect(prompt).toContain('/stories/STORY-003.md');
202
+ expect(prompt).toContain('read the story markdown file');
203
+ });
204
+
205
+ it('should fall back to DB description when markdown_path is null', () => {
206
+ const stories: StoryRow[] = [
207
+ {
208
+ id: 'STORY-004',
209
+ title: 'Story Without Markdown',
210
+ description: 'DB only description',
211
+ complexity_score: 2,
212
+ status: 'planned',
213
+ team_id: 'team-1',
214
+ requirement_id: null,
215
+ acceptance_criteria: null,
216
+ story_points: null,
217
+ assigned_agent_id: null,
218
+ branch_name: null,
219
+ pr_url: null,
220
+ jira_issue_key: null,
221
+ jira_issue_id: null,
222
+ jira_project_key: null,
223
+ jira_subtask_key: null,
224
+ jira_subtask_id: null,
225
+ external_issue_key: null,
226
+ external_issue_id: null,
227
+ external_project_key: null,
228
+ external_subtask_key: null,
229
+ external_subtask_id: null,
230
+ external_provider: null,
231
+ in_sprint: 0,
232
+ markdown_path: null,
233
+ created_at: '2024-01-01',
234
+ updated_at: '2024-01-01',
235
+ },
236
+ ];
237
+ const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
238
+
239
+ expect(prompt).toContain('DB only description');
240
+ expect(prompt).not.toContain('read the story markdown file');
241
+ });
242
+
165
243
  it('should handle empty stories list', () => {
166
244
  const stories: StoryRow[] = [];
167
245
  const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
@@ -230,6 +308,7 @@ describe('Prompt Templates', () => {
230
308
  external_subtask_id: null,
231
309
  external_provider: null,
232
310
  in_sprint: 0,
311
+ markdown_path: null,
233
312
  created_at: '2024-01-01',
234
313
  updated_at: '2024-01-01',
235
314
  },
@@ -258,6 +337,7 @@ describe('Prompt Templates', () => {
258
337
  external_subtask_id: null,
259
338
  external_provider: null,
260
339
  in_sprint: 0,
340
+ markdown_path: null,
261
341
  created_at: '2024-01-01',
262
342
  updated_at: '2024-01-01',
263
343
  },
@@ -810,3 +890,190 @@ describe('Prompt Templates', () => {
810
890
  });
811
891
  });
812
892
  });
893
+
894
+ describe('Chrome Tab Isolation', () => {
895
+ const teamName = 'TestTeam';
896
+ const repoUrl = 'https://github.com/test/repo.git';
897
+ const repoPath = 'repos/test-repo';
898
+ const sessionName = 'hive-test-session';
899
+
900
+ const CHROME_TAB_MARKER = 'Chrome Browser Tab Isolation';
901
+ const CHROME_TAB_CREATE = 'tabs_create_mcp';
902
+
903
+ describe('generateSeniorPrompt', () => {
904
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
905
+ const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, [], 'main', {
906
+ chromeEnabled: true,
907
+ });
908
+ expect(prompt).toContain(CHROME_TAB_MARKER);
909
+ expect(prompt).toContain(CHROME_TAB_CREATE);
910
+ });
911
+
912
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
913
+ const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, [], 'main', {
914
+ chromeEnabled: false,
915
+ });
916
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
917
+ });
918
+
919
+ it('should not include chrome tab isolation section when chromeEnabled is not set', () => {
920
+ const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, []);
921
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
922
+ });
923
+ });
924
+
925
+ describe('generateIntermediatePrompt', () => {
926
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
927
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
928
+ chromeEnabled: true,
929
+ });
930
+ expect(prompt).toContain(CHROME_TAB_MARKER);
931
+ expect(prompt).toContain(CHROME_TAB_CREATE);
932
+ });
933
+
934
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
935
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
936
+ chromeEnabled: false,
937
+ });
938
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
939
+ });
940
+
941
+ it('should not include chrome tab isolation section when options not provided', () => {
942
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName);
943
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
944
+ });
945
+ });
946
+
947
+ describe('generateJuniorPrompt', () => {
948
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
949
+ const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
950
+ chromeEnabled: true,
951
+ });
952
+ expect(prompt).toContain(CHROME_TAB_MARKER);
953
+ expect(prompt).toContain(CHROME_TAB_CREATE);
954
+ });
955
+
956
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
957
+ const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
958
+ chromeEnabled: false,
959
+ });
960
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
961
+ });
962
+
963
+ it('should not include chrome tab isolation section when options not provided', () => {
964
+ const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName);
965
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
966
+ });
967
+ });
968
+
969
+ describe('generateQAPrompt', () => {
970
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
971
+ const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
972
+ chromeEnabled: true,
973
+ });
974
+ expect(prompt).toContain(CHROME_TAB_MARKER);
975
+ expect(prompt).toContain(CHROME_TAB_CREATE);
976
+ });
977
+
978
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
979
+ const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
980
+ chromeEnabled: false,
981
+ });
982
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
983
+ });
984
+
985
+ it('should not include chrome tab isolation section when options not provided', () => {
986
+ const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName);
987
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
988
+ });
989
+ });
990
+
991
+ describe('generateFeatureTestPrompt', () => {
992
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
993
+ const prompt = generateFeatureTestPrompt(
994
+ teamName,
995
+ repoUrl,
996
+ repoPath,
997
+ sessionName,
998
+ 'feature/test',
999
+ 'REQ-001',
1000
+ 'e2e/tests',
1001
+ { chromeEnabled: true }
1002
+ );
1003
+ expect(prompt).toContain(CHROME_TAB_MARKER);
1004
+ expect(prompt).toContain(CHROME_TAB_CREATE);
1005
+ });
1006
+
1007
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
1008
+ const prompt = generateFeatureTestPrompt(
1009
+ teamName,
1010
+ repoUrl,
1011
+ repoPath,
1012
+ sessionName,
1013
+ 'feature/test',
1014
+ 'REQ-001',
1015
+ 'e2e/tests',
1016
+ { chromeEnabled: false }
1017
+ );
1018
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
1019
+ });
1020
+
1021
+ it('should not include chrome tab isolation section when options not provided', () => {
1022
+ const prompt = generateFeatureTestPrompt(
1023
+ teamName,
1024
+ repoUrl,
1025
+ repoPath,
1026
+ sessionName,
1027
+ 'feature/test',
1028
+ 'REQ-001',
1029
+ 'e2e/tests'
1030
+ );
1031
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
1032
+ });
1033
+ });
1034
+
1035
+ describe('generateAuditorPrompt', () => {
1036
+ it('should include chrome tab isolation section when chromeEnabled is true', () => {
1037
+ const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl, {
1038
+ chromeEnabled: true,
1039
+ });
1040
+ expect(prompt).toContain(CHROME_TAB_MARKER);
1041
+ expect(prompt).toContain(CHROME_TAB_CREATE);
1042
+ });
1043
+
1044
+ it('should not include chrome tab isolation section when chromeEnabled is false', () => {
1045
+ const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl, {
1046
+ chromeEnabled: false,
1047
+ });
1048
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
1049
+ });
1050
+
1051
+ it('should not include chrome tab isolation section when options not provided', () => {
1052
+ const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl);
1053
+ expect(prompt).not.toContain(CHROME_TAB_MARKER);
1054
+ });
1055
+ });
1056
+
1057
+ describe('tab isolation instructions content', () => {
1058
+ it('should instruct agents to store and reuse tab ID', () => {
1059
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
1060
+ chromeEnabled: true,
1061
+ });
1062
+ expect(prompt).toContain('tab ID');
1063
+ });
1064
+
1065
+ it('should instruct agents to recreate tab when closed externally', () => {
1066
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
1067
+ chromeEnabled: true,
1068
+ });
1069
+ expect(prompt).toContain('closed externally');
1070
+ });
1071
+
1072
+ it('should warn agents not to use other agents tabs', () => {
1073
+ const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
1074
+ chromeEnabled: true,
1075
+ });
1076
+ expect(prompt).toContain('Never interact with tabs you did not create');
1077
+ });
1078
+ });
1079
+ });
@@ -4,6 +4,10 @@ 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;
9
+ /** Whether Chrome browser tools are enabled for this agent session. */
10
+ chromeEnabled?: boolean;
7
11
  }
8
12
 
9
13
  /**
@@ -60,6 +64,39 @@ function shouldIncludeProgressUpdates(options?: AgentPromptOptions): boolean {
60
64
  return options?.includeProgressUpdates ?? true;
61
65
  }
62
66
 
67
+ function resolveTechLeadSession(options?: AgentPromptOptions): string {
68
+ return options?.techLeadSession || 'hive-tech-lead';
69
+ }
70
+
71
+ /**
72
+ * Generate the Chrome tab isolation section for agent prompts.
73
+ * Instructs agents to create a dedicated tab at session start and use it
74
+ * exclusively for all browser operations, preventing tab interference between
75
+ * concurrent agents.
76
+ */
77
+ function chromeTabIsolationSection(): string {
78
+ return `# Chrome Browser Tab Isolation
79
+ Chrome browser tools are enabled for this session. Each agent must use its own dedicated tab to prevent interference with other agents running concurrently.
80
+
81
+ ## Tab Lifecycle
82
+
83
+ **At session start**, create your dedicated tab immediately:
84
+ \`\`\`
85
+ Use mcp__claude-in-chrome__tabs_create_mcp to create a new tab.
86
+ Store the returned tab ID — you will use it for all browser operations.
87
+ \`\`\`
88
+
89
+ **For all browser operations**, always pass your stored tab ID to every \`mcp__claude-in-chrome__*\` tool call. Never interact with tabs you did not create.
90
+
91
+ **If your tab is closed externally** (tool returns a tab-not-found error):
92
+ \`\`\`
93
+ Call mcp__claude-in-chrome__tabs_create_mcp again to get a new tab ID.
94
+ Update your stored tab ID and continue.
95
+ \`\`\`
96
+
97
+ **At session end**, close your tab using the browser tools to free resources.`;
98
+ }
99
+
63
100
  function repositorySection(repoPath: string, repoUrl: string): string {
64
101
  return `## Your Repository
65
102
  - Local path: ${repoPath}
@@ -231,20 +268,25 @@ export function generateSeniorPrompt(
231
268
  sessionNameOverride?: string
232
269
  ): string {
233
270
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
271
+ const techLeadSession = resolveTechLeadSession(options);
234
272
  const storyList = stories
235
273
  .map(s => {
236
274
  const externalInfo = s.external_subtask_key
237
275
  ? ` | External Subtask: ${s.external_subtask_key}`
238
276
  : '';
239
- return `- [${s.id}] ${s.title} (complexity: ${s.complexity_score || '?'}${externalInfo})\n ${s.description}`;
277
+ const descriptionOrMarkdown = s.markdown_path
278
+ ? `For full details, read the story markdown file: \`${s.markdown_path}\``
279
+ : s.description;
280
+ return `- [${s.id}] ${s.title} (complexity: ${s.complexity_score || '?'}${externalInfo})\n ${descriptionOrMarkdown}`;
240
281
  })
241
282
  .join('\n\n');
242
283
 
243
284
  const sessionName = sessionNameOverride || formatSeniorSessionName(teamName);
285
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
244
286
 
245
287
  return `You are a Senior Developer on Team ${teamName}.
246
288
  Your tmux session: ${sessionName}
247
-
289
+ ${chromeSection}
248
290
  ${repositorySection(repoPath, repoUrl)}
249
291
 
250
292
  ## Your Responsibilities
@@ -283,7 +325,7 @@ hive pr queue
283
325
  ## Communication with Tech Lead
284
326
  If you have questions or need guidance, message the Tech Lead:
285
327
  \`\`\`bash
286
- hive msg send hive-tech-lead "Your question here" --from ${sessionName}
328
+ hive msg send ${techLeadSession} "Your question here" --from ${sessionName}
287
329
  \`\`\`
288
330
 
289
331
  Check for replies:
@@ -318,11 +360,13 @@ export function generateIntermediatePrompt(
318
360
  options?: AgentPromptOptions
319
361
  ): string {
320
362
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
363
+ const techLeadSession = resolveTechLeadSession(options);
321
364
  const seniorSession = formatSeniorSessionName(teamName);
365
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
322
366
 
323
367
  return `You are an Intermediate Developer on Team ${teamName}.
324
368
  Your tmux session: ${sessionName}
325
-
369
+ ${chromeSection}
326
370
  ${repositorySection(repoPath, repoUrl)}
327
371
 
328
372
  ## Your Responsibilities
@@ -354,7 +398,7 @@ ${prSubmissionSection(sessionName, targetBranch)}
354
398
  If you have questions, message your Senior or the Tech Lead:
355
399
  \`\`\`bash
356
400
  hive msg send ${seniorSession} "Your question" --from ${sessionName}
357
- hive msg send hive-tech-lead "Your question" --from ${sessionName}
401
+ hive msg send ${techLeadSession} "Your question" --from ${sessionName}
358
402
  \`\`\`
359
403
 
360
404
  Check for replies:
@@ -389,11 +433,13 @@ export function generateJuniorPrompt(
389
433
  options?: AgentPromptOptions
390
434
  ): string {
391
435
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
436
+ const techLeadSession = resolveTechLeadSession(options);
392
437
  const seniorSession = formatSeniorSessionName(teamName);
438
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
393
439
 
394
440
  return `You are a Junior Developer on Team ${teamName}.
395
441
  Your tmux session: ${sessionName}
396
-
442
+ ${chromeSection}
397
443
  ${repositorySection(repoPath, repoUrl)}
398
444
 
399
445
  ## Your Responsibilities
@@ -425,7 +471,7 @@ ${prSubmissionSection(sessionName, targetBranch)}
425
471
  If you have questions, message your Senior or the Tech Lead:
426
472
  \`\`\`bash
427
473
  hive msg send ${seniorSession} "Your question" --from ${sessionName}
428
- hive msg send hive-tech-lead "Your question" --from ${sessionName}
474
+ hive msg send ${techLeadSession} "Your question" --from ${sessionName}
429
475
  \`\`\`
430
476
 
431
477
  Check for replies:
@@ -456,11 +502,13 @@ export function generateQAPrompt(
456
502
  repoUrl: string,
457
503
  repoPath: string,
458
504
  sessionName: string,
459
- targetBranch: string = 'main'
505
+ targetBranch: string = 'main',
506
+ options?: AgentPromptOptions
460
507
  ): string {
508
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
461
509
  return `You are a QA Engineer on Team ${teamName}.
462
510
  Your tmux session: ${sessionName}
463
-
511
+ ${chromeSection}
464
512
  ${repositorySection(repoPath, repoUrl)}
465
513
 
466
514
  ## Your Responsibilities
@@ -557,6 +605,8 @@ export function generateFeatureTestPrompt(
557
605
  options?: AgentPromptOptions
558
606
  ): string {
559
607
  const includeProgressUpdates = shouldIncludeProgressUpdates(options);
608
+ const techLeadSession = resolveTechLeadSession(options);
609
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
560
610
  const reportResultsSection = includeProgressUpdates
561
611
  ? `**If all tests pass:**
562
612
  \`\`\`bash
@@ -572,17 +622,17 @@ Report results directly to the Tech Lead:
572
622
 
573
623
  **If all tests pass:**
574
624
  \`\`\`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}
625
+ hive msg send ${techLeadSession} "E2E tests PASSED for ${requirementId} on ${featureBranch}. [Include test summary: X passed, 0 failed. Total time: Xs]" --from ${sessionName}
576
626
  \`\`\`
577
627
 
578
628
  **If any tests fail:**
579
629
  \`\`\`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}
630
+ 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
631
  \`\`\``;
582
632
 
583
633
  return `You are a Feature Test Agent on Team ${teamName}.
584
634
  Your tmux session: ${sessionName}
585
-
635
+ ${chromeSection}
586
636
  ${repositorySection(repoPath, repoUrl)}
587
637
 
588
638
  ## Your Mission
@@ -631,7 +681,7 @@ ${reportResultsSection}
631
681
  ## Communication
632
682
  If you encounter issues running the tests, message the Tech Lead:
633
683
  \`\`\`bash
634
- hive msg send hive-tech-lead "Issue running E2E tests for ${requirementId}: [describe issue]" --from ${sessionName}
684
+ hive msg send ${techLeadSession} "Issue running E2E tests for ${requirementId}: [describe issue]" --from ${sessionName}
635
685
  \`\`\`
636
686
 
637
687
  Check for replies:
@@ -656,11 +706,14 @@ Start by checking out the feature branch and reading the TESTING.md file.`;
656
706
  export function generateAuditorPrompt(
657
707
  sessionName: string,
658
708
  repoPath: string,
659
- repoUrl: string
709
+ repoUrl: string,
710
+ options?: AgentPromptOptions
660
711
  ): string {
712
+ const techLeadSession = resolveTechLeadSession(options);
713
+ const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
661
714
  return `You are a Hive Auditor Agent.
662
715
  Your tmux session: ${sessionName}
663
-
716
+ ${chromeSection}
664
717
  ${repositorySection(repoPath, repoUrl)}
665
718
 
666
719
  ## Your Mission
@@ -725,7 +778,7 @@ tmux send-keys -t <session-name> Enter
725
778
  **Other unfixable issues:** Any issue you cannot resolve with the above actions.
726
779
  - Escalate to tech lead:
727
780
  \`\`\`bash
728
- hive msg send hive-tech-lead "AUDITOR: <description of issue, including agent id and story id>" --from ${sessionName}
781
+ hive msg send ${techLeadSession} "AUDITOR: <description of issue, including agent id and story id>" --from ${sessionName}
729
782
  \`\`\`
730
783
 
731
784
  ### 5. Self-terminate