hungry-ghost-hive 0.44.0 → 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 (148) 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/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 +4 -2
  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/req.d.ts +1 -1
  43. package/dist/cli/commands/req.d.ts.map +1 -1
  44. package/dist/cli/commands/req.js +7 -5
  45. package/dist/cli/commands/req.js.map +1 -1
  46. package/dist/cli/commands/stories.js +3 -3
  47. package/dist/cli/commands/stories.js.map +1 -1
  48. package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
  49. package/dist/cli/dashboard/panels/agents.js +7 -3
  50. package/dist/cli/dashboard/panels/agents.js.map +1 -1
  51. package/dist/context-files/generator.d.ts +1 -1
  52. package/dist/context-files/generator.d.ts.map +1 -1
  53. package/dist/context-files/generator.js +3 -2
  54. package/dist/context-files/generator.js.map +1 -1
  55. package/dist/context-files/index.test.js +1 -0
  56. package/dist/context-files/index.test.js.map +1 -1
  57. package/dist/db/client.d.ts +1 -0
  58. package/dist/db/client.d.ts.map +1 -1
  59. package/dist/db/client.js +6 -0
  60. package/dist/db/client.js.map +1 -1
  61. package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
  62. package/dist/db/queries/stories.d.ts +3 -3
  63. package/dist/db/queries/stories.d.ts.map +1 -1
  64. package/dist/db/queries/stories.js +23 -5
  65. package/dist/db/queries/stories.js.map +1 -1
  66. package/dist/db/queries/test-helpers.d.ts.map +1 -1
  67. package/dist/db/queries/test-helpers.js +1 -0
  68. package/dist/db/queries/test-helpers.js.map +1 -1
  69. package/dist/git/worktree.d.ts.map +1 -1
  70. package/dist/git/worktree.js +7 -0
  71. package/dist/git/worktree.js.map +1 -1
  72. package/dist/git/worktree.test.js +30 -0
  73. package/dist/git/worktree.test.js.map +1 -1
  74. package/dist/orchestrator/prompt-templates.d.ts +3 -1
  75. package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
  76. package/dist/orchestrator/prompt-templates.js +16 -8
  77. package/dist/orchestrator/prompt-templates.js.map +1 -1
  78. package/dist/orchestrator/prompt-templates.test.js +4 -0
  79. package/dist/orchestrator/prompt-templates.test.js.map +1 -1
  80. package/dist/orchestrator/scheduler.d.ts.map +1 -1
  81. package/dist/orchestrator/scheduler.js +19 -11
  82. package/dist/orchestrator/scheduler.js.map +1 -1
  83. package/dist/orchestrator/scheduler.test.js +1 -0
  84. package/dist/orchestrator/scheduler.test.js.map +1 -1
  85. package/dist/tmux/manager.d.ts +7 -6
  86. package/dist/tmux/manager.d.ts.map +1 -1
  87. package/dist/tmux/manager.js +29 -13
  88. package/dist/tmux/manager.js.map +1 -1
  89. package/dist/utils/instance.d.ts +32 -0
  90. package/dist/utils/instance.d.ts.map +1 -0
  91. package/dist/utils/instance.js +82 -0
  92. package/dist/utils/instance.js.map +1 -0
  93. package/dist/utils/instance.test.d.ts +2 -0
  94. package/dist/utils/instance.test.d.ts.map +1 -0
  95. package/dist/utils/instance.test.js +103 -0
  96. package/dist/utils/instance.test.js.map +1 -0
  97. package/dist/utils/paths.d.ts +2 -0
  98. package/dist/utils/paths.d.ts.map +1 -1
  99. package/dist/utils/paths.js +2 -0
  100. package/dist/utils/paths.js.map +1 -1
  101. package/dist/utils/paths.test.js +6 -0
  102. package/dist/utils/paths.test.js.map +1 -1
  103. package/dist/utils/story-markdown.d.ts +16 -0
  104. package/dist/utils/story-markdown.d.ts.map +1 -0
  105. package/dist/utils/story-markdown.js +82 -0
  106. package/dist/utils/story-markdown.js.map +1 -0
  107. package/dist/utils/story-markdown.test.d.ts +2 -0
  108. package/dist/utils/story-markdown.test.d.ts.map +1 -0
  109. package/dist/utils/story-markdown.test.js +143 -0
  110. package/dist/utils/story-markdown.test.js.map +1 -0
  111. package/package.json +1 -1
  112. package/src/agents/base-agent.ts +5 -0
  113. package/src/agents/intermediate.ts +2 -2
  114. package/src/agents/junior.ts +2 -2
  115. package/src/agents/qa.ts +13 -8
  116. package/src/agents/senior.ts +21 -11
  117. package/src/agents/tech-lead.ts +24 -12
  118. package/src/cli/commands/assign.test.ts +5 -0
  119. package/src/cli/commands/assign.ts +4 -2
  120. package/src/cli/commands/manager/handoff-recovery.ts +4 -2
  121. package/src/cli/commands/manager/index.ts +16 -11
  122. package/src/cli/commands/manager/tech-lead-lifecycle.ts +5 -2
  123. package/src/cli/commands/msg.ts +8 -7
  124. package/src/cli/commands/my-stories.ts +22 -13
  125. package/src/cli/commands/nuke.test.ts +31 -0
  126. package/src/cli/commands/nuke.ts +18 -7
  127. package/src/cli/commands/req.ts +9 -5
  128. package/src/cli/commands/stories.ts +22 -13
  129. package/src/cli/dashboard/panels/agents.ts +7 -3
  130. package/src/context-files/generator.ts +3 -2
  131. package/src/context-files/index.test.ts +1 -0
  132. package/src/db/client.ts +7 -0
  133. package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
  134. package/src/db/queries/stories.ts +29 -5
  135. package/src/db/queries/test-helpers.ts +1 -0
  136. package/src/git/worktree.test.ts +43 -0
  137. package/src/git/worktree.ts +10 -0
  138. package/src/orchestrator/prompt-templates.test.ts +4 -0
  139. package/src/orchestrator/prompt-templates.ts +20 -8
  140. package/src/orchestrator/scheduler.test.ts +1 -0
  141. package/src/orchestrator/scheduler.ts +24 -11
  142. package/src/tmux/manager.ts +42 -13
  143. package/src/utils/instance.test.ts +129 -0
  144. package/src/utils/instance.ts +95 -0
  145. package/src/utils/paths.test.ts +8 -0
  146. package/src/utils/paths.ts +3 -0
  147. package/src/utils/story-markdown.test.ts +176 -0
  148. package/src/utils/story-markdown.ts +94 -0
@@ -15,6 +15,7 @@ import { createLog } from '../../db/queries/logs.js';
15
15
  import { createRequirement, updateRequirement } from '../../db/queries/requirements.js';
16
16
  import { getAllTeams } from '../../db/queries/teams.js';
17
17
  import { isTmuxAvailable, spawnTmuxSession } from '../../tmux/manager.js';
18
+ import { getTechLeadSessionName } from '../../utils/instance.js';
18
19
  import { withHiveContext } from '../../utils/with-hive-context.js';
19
20
  import { startDashboard } from '../dashboard/index.js';
20
21
 
@@ -209,14 +210,15 @@ export const reqCommand = new Command('req')
209
210
  });
210
211
 
211
212
  // Spawn Tech Lead tmux session
212
- const sessionName = `hive-tech-lead`;
213
+ const sessionName = getTechLeadSessionName(paths.hiveDir);
213
214
  const techLeadPrompt = generateTechLeadPrompt(
214
215
  req.id,
215
216
  title,
216
217
  description,
217
218
  teams,
218
219
  options.godmode,
219
- targetBranch
220
+ targetBranch,
221
+ sessionName
220
222
  );
221
223
 
222
224
  try {
@@ -321,8 +323,10 @@ export function generateTechLeadPrompt(
321
323
  description: string,
322
324
  teams: { id: string; name: string; repo_path: string; repo_url: string }[],
323
325
  godmode?: boolean,
324
- targetBranch?: string
326
+ targetBranch?: string,
327
+ techLeadSession?: string
325
328
  ): string {
329
+ const tlSession = techLeadSession || 'hive-tech-lead';
326
330
  const teamList = teams.map(t => `- ${t.name}: ${t.repo_path} (${t.repo_url})`).join('\n');
327
331
  const godmodeNotice = godmode
328
332
  ? `
@@ -387,7 +391,7 @@ The SQLite database is at .hive/hive.db
387
391
 
388
392
  Check your inbox for messages from developers:
389
393
  \`\`\`bash
390
- hive msg inbox hive-tech-lead
394
+ hive msg inbox ${tlSession}
391
395
  \`\`\`
392
396
 
393
397
  Read a specific message:
@@ -400,7 +404,7 @@ Reply to a message:
400
404
  hive msg reply <msg-id> "Your response here"
401
405
  \`\`\`
402
406
 
403
- **IMPORTANT:** Periodically run \`hive msg inbox hive-tech-lead\` to check if any developers need guidance. Answer their questions promptly to keep the team unblocked.
407
+ **IMPORTANT:** Periodically run \`hive msg inbox ${tlSession}\` to check if any developers need guidance. Answer their questions promptly to keep the team unblocked.
404
408
 
405
409
  When done planning, update the requirement status to 'planned' and run \`hive assign\` to spawn Senior developers who will implement the stories.
406
410
  `;
@@ -87,23 +87,32 @@ storiesCommand
87
87
  criteria?: string[];
88
88
  json?: boolean;
89
89
  }) => {
90
- await withHiveContext(async ({ root, db }) => {
90
+ await withHiveContext(async ({ root, paths, db }) => {
91
91
  // Create local story
92
- const story = createStory(db.db, {
93
- requirementId: options.requirement || null,
94
- teamId: options.team || null,
95
- title: options.title,
96
- description: options.description,
97
- acceptanceCriteria: options.criteria || null,
98
- });
92
+ const story = createStory(
93
+ db.db,
94
+ {
95
+ requirementId: options.requirement || null,
96
+ teamId: options.team || null,
97
+ title: options.title,
98
+ description: options.description,
99
+ acceptanceCriteria: options.criteria || null,
100
+ },
101
+ paths.storiesDir
102
+ );
99
103
 
100
104
  // Update with optional fields
101
105
  if (options.points !== undefined || options.complexity !== undefined) {
102
- updateStory(db.db, story.id, {
103
- storyPoints: options.points ?? null,
104
- complexityScore: options.complexity ?? null,
105
- status: 'estimated',
106
- });
106
+ updateStory(
107
+ db.db,
108
+ story.id,
109
+ {
110
+ storyPoints: options.points ?? null,
111
+ complexityScore: options.complexity ?? null,
112
+ status: 'estimated',
113
+ },
114
+ paths.storiesDir
115
+ );
107
116
  }
108
117
 
109
118
  // Sync to PM provider if configured
@@ -9,6 +9,7 @@ import type { ModelsConfig } from '../../../config/schema.js';
9
9
  import { getActiveAgents, type AgentRow } from '../../../db/queries/agents.js';
10
10
  import { getTeamById } from '../../../db/queries/teams.js';
11
11
  import { getHiveSessions } from '../../../tmux/manager.js';
12
+ import { getManagerSessionName } from '../../../utils/instance.js';
12
13
  import { findHiveRoot, getHivePaths } from '../../../utils/paths.js';
13
14
 
14
15
  function debugLog(msg: string) {
@@ -125,8 +126,11 @@ export async function updateAgentsPanel(list: Widgets.ListElement, db: Database)
125
126
  }
126
127
 
127
128
  // Check for manager session (not in DB)
128
- const hiveSessions = await getHiveSessions();
129
- const managerSession = hiveSessions.find(s => s.name === 'hive-manager');
129
+ const hiveRoot = findHiveRoot();
130
+ const hDir = hiveRoot ? getHivePaths(hiveRoot).hiveDir : undefined;
131
+ const managerName = hDir ? getManagerSessionName(hDir) : 'hive-manager';
132
+ const hiveSessions = await getHiveSessions(hDir);
133
+ const managerSession = hiveSessions.find(s => s.name === managerName);
130
134
 
131
135
  // Build combined list - manager first if running
132
136
  const displayAgents: DisplayAgent[] = [];
@@ -137,7 +141,7 @@ export async function updateAgentsPanel(list: Widgets.ListElement, db: Database)
137
141
  id: 'manager',
138
142
  type: 'manager' as AgentRow['type'],
139
143
  team_id: null,
140
- tmux_session: 'hive-manager',
144
+ tmux_session: managerName,
141
145
  model: '-',
142
146
  status: 'working',
143
147
  current_story_id: null,
@@ -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
  };
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,