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.
- package/dist/agents/base-agent.d.ts +1 -0
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +4 -0
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/intermediate.js +2 -2
- package/dist/agents/intermediate.js.map +1 -1
- package/dist/agents/junior.js +2 -2
- package/dist/agents/junior.js.map +1 -1
- package/dist/agents/qa.d.ts.map +1 -1
- package/dist/agents/qa.js +5 -5
- package/dist/agents/qa.js.map +1 -1
- package/dist/agents/senior.d.ts.map +1 -1
- package/dist/agents/senior.js +5 -5
- package/dist/agents/senior.js.map +1 -1
- package/dist/agents/tech-lead.d.ts.map +1 -1
- package/dist/agents/tech-lead.js +8 -3
- package/dist/agents/tech-lead.js.map +1 -1
- package/dist/cli/commands/assign.d.ts.map +1 -1
- package/dist/cli/commands/assign.js +4 -2
- package/dist/cli/commands/assign.js.map +1 -1
- package/dist/cli/commands/assign.test.js +5 -0
- package/dist/cli/commands/assign.test.js.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.js +4 -2
- package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
- package/dist/cli/commands/manager/index.d.ts.map +1 -1
- package/dist/cli/commands/manager/index.js +16 -12
- package/dist/cli/commands/manager/index.js.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.js +8 -3
- package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
- package/dist/cli/commands/msg.d.ts.map +1 -1
- package/dist/cli/commands/msg.js +8 -7
- package/dist/cli/commands/msg.js.map +1 -1
- package/dist/cli/commands/my-stories.js +3 -3
- package/dist/cli/commands/my-stories.js.map +1 -1
- package/dist/cli/commands/nuke.d.ts.map +1 -1
- package/dist/cli/commands/nuke.js +18 -7
- package/dist/cli/commands/nuke.js.map +1 -1
- package/dist/cli/commands/nuke.test.js +24 -0
- package/dist/cli/commands/nuke.test.js.map +1 -1
- package/dist/cli/commands/pr.js +5 -0
- package/dist/cli/commands/pr.js.map +1 -1
- package/dist/cli/commands/pr.test.js +43 -1
- package/dist/cli/commands/pr.test.js.map +1 -1
- package/dist/cli/commands/req.d.ts +1 -1
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +9 -6
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +4 -1
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/stories.js +3 -3
- package/dist/cli/commands/stories.js.map +1 -1
- package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/agents.js +7 -3
- package/dist/cli/dashboard/panels/agents.js.map +1 -1
- package/dist/cli-runtimes/chrome.d.ts +17 -0
- package/dist/cli-runtimes/chrome.d.ts.map +1 -0
- package/dist/cli-runtimes/chrome.js +36 -0
- package/dist/cli-runtimes/chrome.js.map +1 -0
- package/dist/cli-runtimes/claude.d.ts +3 -3
- package/dist/cli-runtimes/claude.d.ts.map +1 -1
- package/dist/cli-runtimes/claude.js +14 -8
- package/dist/cli-runtimes/claude.js.map +1 -1
- package/dist/cli-runtimes/codex.d.ts +3 -3
- package/dist/cli-runtimes/codex.d.ts.map +1 -1
- package/dist/cli-runtimes/codex.js +2 -2
- package/dist/cli-runtimes/codex.js.map +1 -1
- package/dist/cli-runtimes/gemini.d.ts +3 -3
- package/dist/cli-runtimes/gemini.d.ts.map +1 -1
- package/dist/cli-runtimes/gemini.js +2 -2
- package/dist/cli-runtimes/gemini.js.map +1 -1
- package/dist/cli-runtimes/index.d.ts +3 -2
- package/dist/cli-runtimes/index.d.ts.map +1 -1
- package/dist/cli-runtimes/index.js +1 -0
- package/dist/cli-runtimes/index.js.map +1 -1
- package/dist/cli-runtimes/index.test.js +133 -1
- package/dist/cli-runtimes/index.test.js.map +1 -1
- package/dist/cli-runtimes/types.d.ts +9 -2
- package/dist/cli-runtimes/types.d.ts.map +1 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/context-files/generator.d.ts +1 -1
- package/dist/context-files/generator.d.ts.map +1 -1
- package/dist/context-files/generator.js +3 -2
- package/dist/context-files/generator.js.map +1 -1
- package/dist/context-files/index.test.js +2 -0
- package/dist/context-files/index.test.js.map +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +6 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/dist/db/queries/stories.d.ts +3 -3
- package/dist/db/queries/stories.d.ts.map +1 -1
- package/dist/db/queries/stories.js +23 -5
- package/dist/db/queries/stories.js.map +1 -1
- package/dist/db/queries/test-helpers.d.ts.map +1 -1
- package/dist/db/queries/test-helpers.js +1 -0
- package/dist/db/queries/test-helpers.js.map +1 -1
- package/dist/git/worktree.d.ts.map +1 -1
- package/dist/git/worktree.js +7 -0
- package/dist/git/worktree.js.map +1 -1
- package/dist/git/worktree.test.js +30 -0
- package/dist/git/worktree.test.js.map +1 -1
- package/dist/orchestrator/prompt-templates.d.ts +3 -1
- package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
- package/dist/orchestrator/prompt-templates.js +16 -8
- package/dist/orchestrator/prompt-templates.js.map +1 -1
- package/dist/orchestrator/prompt-templates.test.js +4 -0
- package/dist/orchestrator/prompt-templates.test.js.map +1 -1
- package/dist/orchestrator/scheduler.d.ts.map +1 -1
- package/dist/orchestrator/scheduler.js +23 -12
- package/dist/orchestrator/scheduler.js.map +1 -1
- package/dist/orchestrator/scheduler.test.js +1 -0
- package/dist/orchestrator/scheduler.test.js.map +1 -1
- package/dist/tmux/manager.d.ts +7 -6
- package/dist/tmux/manager.d.ts.map +1 -1
- package/dist/tmux/manager.js +29 -13
- package/dist/tmux/manager.js.map +1 -1
- package/dist/utils/auto-merge.d.ts.map +1 -1
- package/dist/utils/auto-merge.js +66 -5
- package/dist/utils/auto-merge.js.map +1 -1
- package/dist/utils/auto-merge.test.js +62 -0
- package/dist/utils/auto-merge.test.js.map +1 -1
- package/dist/utils/instance.d.ts +32 -0
- package/dist/utils/instance.d.ts.map +1 -0
- package/dist/utils/instance.js +82 -0
- package/dist/utils/instance.js.map +1 -0
- package/dist/utils/instance.test.d.ts +2 -0
- package/dist/utils/instance.test.d.ts.map +1 -0
- package/dist/utils/instance.test.js +103 -0
- package/dist/utils/instance.test.js.map +1 -0
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +2 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/paths.test.js +6 -0
- package/dist/utils/paths.test.js.map +1 -1
- package/dist/utils/story-markdown.d.ts +16 -0
- package/dist/utils/story-markdown.d.ts.map +1 -0
- package/dist/utils/story-markdown.js +82 -0
- package/dist/utils/story-markdown.js.map +1 -0
- package/dist/utils/story-markdown.test.d.ts +2 -0
- package/dist/utils/story-markdown.test.d.ts.map +1 -0
- package/dist/utils/story-markdown.test.js +143 -0
- package/dist/utils/story-markdown.test.js.map +1 -0
- package/package.json +1 -1
- package/src/agents/base-agent.ts +5 -0
- package/src/agents/intermediate.ts +2 -2
- package/src/agents/junior.ts +2 -2
- package/src/agents/qa.ts +13 -8
- package/src/agents/senior.ts +21 -11
- package/src/agents/tech-lead.ts +28 -13
- package/src/cli/commands/assign.test.ts +5 -0
- package/src/cli/commands/assign.ts +4 -2
- package/src/cli/commands/manager/handoff-recovery.ts +4 -2
- package/src/cli/commands/manager/index.ts +16 -11
- package/src/cli/commands/manager/tech-lead-lifecycle.ts +9 -3
- package/src/cli/commands/msg.ts +8 -7
- package/src/cli/commands/my-stories.ts +22 -13
- package/src/cli/commands/nuke.test.ts +31 -0
- package/src/cli/commands/nuke.ts +18 -7
- package/src/cli/commands/pr.test.ts +77 -1
- package/src/cli/commands/pr.ts +5 -0
- package/src/cli/commands/req.ts +13 -6
- package/src/cli/commands/resume.ts +4 -1
- package/src/cli/commands/stories.ts +22 -13
- package/src/cli/dashboard/panels/agents.ts +7 -3
- package/src/cli-runtimes/chrome.ts +43 -0
- package/src/cli-runtimes/claude.ts +26 -9
- package/src/cli-runtimes/codex.ts +12 -3
- package/src/cli-runtimes/gemini.ts +12 -3
- package/src/cli-runtimes/index.test.ts +158 -0
- package/src/cli-runtimes/index.ts +3 -2
- package/src/cli-runtimes/types.ts +19 -2
- package/src/config/schema.ts +6 -0
- package/src/context-files/generator.ts +3 -2
- package/src/context-files/index.test.ts +2 -0
- package/src/db/client.ts +7 -0
- package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/src/db/queries/stories.ts +29 -5
- package/src/db/queries/test-helpers.ts +1 -0
- package/src/git/worktree.test.ts +43 -0
- package/src/git/worktree.ts +10 -0
- package/src/orchestrator/prompt-templates.test.ts +4 -0
- package/src/orchestrator/prompt-templates.ts +20 -8
- package/src/orchestrator/scheduler.test.ts +1 -0
- package/src/orchestrator/scheduler.ts +33 -12
- package/src/tmux/manager.ts +42 -13
- package/src/utils/auto-merge.test.ts +81 -0
- package/src/utils/auto-merge.ts +78 -5
- package/src/utils/instance.test.ts +129 -0
- package/src/utils/instance.ts +95 -0
- package/src/utils/paths.test.ts +8 -0
- package/src/utils/paths.ts +3 -0
- package/src/utils/story-markdown.test.ts +176 -0
- 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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
);
|
package/src/git/worktree.test.ts
CHANGED
|
@@ -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
|
});
|
package/src/git/worktree.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -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(
|
|
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
|
-
|
|
889
|
-
|
|
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
|
-
?
|
|
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
|
|
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
|
package/src/tmux/manager.ts
CHANGED
|
@@ -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(
|
|
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
|
|
530
|
-
|
|
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(
|
|
534
|
-
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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(
|
|
587
|
+
await killTmuxSession(session);
|
|
559
588
|
return true;
|
|
560
589
|
}
|