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.
- 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 +4 -2
- 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 +4 -2
- 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/req.d.ts +1 -1
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +7 -5
- package/dist/cli/commands/req.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/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 +1 -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 +19 -11
- 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/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 +24 -12
- 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 +5 -2
- 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/req.ts +9 -5
- package/src/cli/commands/stories.ts +22 -13
- package/src/cli/dashboard/panels/agents.ts +7 -3
- package/src/context-files/generator.ts +3 -2
- package/src/context-files/index.test.ts +1 -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 +24 -11
- package/src/tmux/manager.ts +42 -13
- 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
package/src/cli/commands/req.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
129
|
-
const
|
|
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:
|
|
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
|
|
107
|
+
hive msg send ${tlSession} "Your question here"
|
|
107
108
|
|
|
108
109
|
# Check your inbox
|
|
109
110
|
hive msg inbox ${agentId || 'your-agent-id'}
|
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,
|