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
package/src/agents/senior.ts
CHANGED
|
@@ -118,10 +118,15 @@ This will help with story estimation and implementation.`;
|
|
|
118
118
|
|
|
119
119
|
private async implementStory(story: StoryRow): Promise<void> {
|
|
120
120
|
// Update assignment
|
|
121
|
-
updateStory(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
updateStory(
|
|
122
|
+
this.db,
|
|
123
|
+
story.id,
|
|
124
|
+
{
|
|
125
|
+
assignedAgentId: this.agentId,
|
|
126
|
+
status: 'in_progress',
|
|
127
|
+
},
|
|
128
|
+
this.storiesDir
|
|
129
|
+
);
|
|
125
130
|
updateAgent(this.db, this.agentId, { currentStoryId: story.id });
|
|
126
131
|
|
|
127
132
|
// Create feature branch
|
|
@@ -162,10 +167,15 @@ Let me know when you're ready to proceed or if you have questions.`;
|
|
|
162
167
|
this.log('STORY_PROGRESS_UPDATE', response.substring(0, 200), { storyId: story.id });
|
|
163
168
|
|
|
164
169
|
// Mark for review when done (simplified)
|
|
165
|
-
updateStory(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
updateStory(
|
|
171
|
+
this.db,
|
|
172
|
+
story.id,
|
|
173
|
+
{
|
|
174
|
+
branchName,
|
|
175
|
+
status: 'review',
|
|
176
|
+
},
|
|
177
|
+
this.storiesDir
|
|
178
|
+
);
|
|
169
179
|
|
|
170
180
|
this.log('STORY_COMPLETED', `Implementation complete, ready for review`, {
|
|
171
181
|
storyId: story.id,
|
|
@@ -176,7 +186,7 @@ Let me know when you're ready to proceed or if you have questions.`;
|
|
|
176
186
|
private async reviewStory(story: StoryRow): Promise<void> {
|
|
177
187
|
if (story.assigned_agent_id === this.agentId) {
|
|
178
188
|
// Self-review, move to QA
|
|
179
|
-
updateStory(this.db, story.id, { status: 'qa' });
|
|
189
|
+
updateStory(this.db, story.id, { status: 'qa' }, this.storiesDir);
|
|
180
190
|
this.log('STORY_REVIEW_REQUESTED', 'Self-implemented, moving to QA', { storyId: story.id });
|
|
181
191
|
return;
|
|
182
192
|
}
|
|
@@ -207,14 +217,14 @@ If issues found, describe them. If approved, confirm.`;
|
|
|
207
217
|
|
|
208
218
|
if (hasIssues) {
|
|
209
219
|
// Send back for fixes
|
|
210
|
-
updateStory(this.db, story.id, { status: 'in_progress' });
|
|
220
|
+
updateStory(this.db, story.id, { status: 'in_progress' }, this.storiesDir);
|
|
211
221
|
this.log('STORY_PROGRESS_UPDATE', 'Review issues found, sent back for fixes', {
|
|
212
222
|
storyId: story.id,
|
|
213
223
|
review: review.substring(0, 200),
|
|
214
224
|
});
|
|
215
225
|
} else {
|
|
216
226
|
// Approve and move to QA
|
|
217
|
-
updateStory(this.db, story.id, { status: 'qa' });
|
|
227
|
+
updateStory(this.db, story.id, { status: 'qa' }, this.storiesDir);
|
|
218
228
|
this.log('STORY_REVIEW_REQUESTED', 'Review passed, moving to QA', { storyId: story.id });
|
|
219
229
|
}
|
|
220
230
|
}
|
package/src/agents/tech-lead.ts
CHANGED
|
@@ -196,23 +196,35 @@ Respond in JSON format:
|
|
|
196
196
|
const storyIds: string[] = [];
|
|
197
197
|
const storyIdMap: Record<string, string> = {};
|
|
198
198
|
|
|
199
|
+
const hiveRoot = findHiveRoot(this.workDir);
|
|
200
|
+
const storiesDir = hiveRoot ? getHivePaths(hiveRoot).storiesDir : undefined;
|
|
201
|
+
|
|
199
202
|
for (const story of analysis.stories) {
|
|
200
203
|
const team = this.teams.find(t => t.name === story.teamName);
|
|
201
204
|
|
|
202
|
-
const storyRow = createStory(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
const storyRow = createStory(
|
|
206
|
+
this.db,
|
|
207
|
+
{
|
|
208
|
+
requirementId: this.requirement!.id,
|
|
209
|
+
teamId: team?.id,
|
|
210
|
+
title: story.title,
|
|
211
|
+
description: story.description,
|
|
212
|
+
acceptanceCriteria: story.acceptanceCriteria,
|
|
213
|
+
},
|
|
214
|
+
storiesDir
|
|
215
|
+
);
|
|
209
216
|
|
|
210
217
|
// Update with complexity
|
|
211
|
-
updateStory(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
updateStory(
|
|
219
|
+
this.db,
|
|
220
|
+
storyRow.id,
|
|
221
|
+
{
|
|
222
|
+
complexityScore: story.estimatedComplexity,
|
|
223
|
+
storyPoints: story.estimatedComplexity,
|
|
224
|
+
status: 'estimated',
|
|
225
|
+
},
|
|
226
|
+
storiesDir
|
|
227
|
+
);
|
|
216
228
|
|
|
217
229
|
storyIds.push(storyRow.id);
|
|
218
230
|
storyIdMap[story.title] = storyRow.id;
|
|
@@ -282,8 +294,11 @@ Respond in JSON format:
|
|
|
282
294
|
const model = resolveRuntimeModelForCli(agentConfig.model, cliTool);
|
|
283
295
|
|
|
284
296
|
// Build spawn command using CLI runtime builder (spawn fresh session, will be resumed later)
|
|
297
|
+
const chromeEnabled = config.agents?.chrome_enabled === true && cliTool === 'claude';
|
|
285
298
|
const runtimeBuilder = getCliRuntimeBuilder(cliTool);
|
|
286
|
-
const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode
|
|
299
|
+
const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode, {
|
|
300
|
+
chrome: chromeEnabled,
|
|
301
|
+
});
|
|
287
302
|
|
|
288
303
|
await spawnTmuxSession({
|
|
289
304
|
sessionName,
|
|
@@ -133,6 +133,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
133
133
|
external_subtask_id: null,
|
|
134
134
|
external_provider: null,
|
|
135
135
|
in_sprint: 0,
|
|
136
|
+
markdown_path: null,
|
|
136
137
|
created_at: new Date().toISOString(),
|
|
137
138
|
updated_at: new Date().toISOString(),
|
|
138
139
|
},
|
|
@@ -161,6 +162,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
161
162
|
external_subtask_id: null,
|
|
162
163
|
external_provider: null,
|
|
163
164
|
in_sprint: 0,
|
|
165
|
+
markdown_path: null,
|
|
164
166
|
created_at: new Date().toISOString(),
|
|
165
167
|
updated_at: new Date().toISOString(),
|
|
166
168
|
},
|
|
@@ -246,6 +248,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
246
248
|
external_subtask_id: null,
|
|
247
249
|
external_provider: null,
|
|
248
250
|
in_sprint: 0,
|
|
251
|
+
markdown_path: null,
|
|
249
252
|
created_at: new Date().toISOString(),
|
|
250
253
|
updated_at: new Date().toISOString(),
|
|
251
254
|
},
|
|
@@ -288,6 +291,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
288
291
|
external_subtask_id: null,
|
|
289
292
|
external_provider: null,
|
|
290
293
|
in_sprint: 0,
|
|
294
|
+
markdown_path: null,
|
|
291
295
|
created_at: new Date().toISOString(),
|
|
292
296
|
updated_at: new Date().toISOString(),
|
|
293
297
|
},
|
|
@@ -316,6 +320,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
316
320
|
external_subtask_id: null,
|
|
317
321
|
external_provider: null,
|
|
318
322
|
in_sprint: 0,
|
|
323
|
+
markdown_path: null,
|
|
319
324
|
created_at: new Date().toISOString(),
|
|
320
325
|
updated_at: new Date().toISOString(),
|
|
321
326
|
},
|
|
@@ -18,8 +18,10 @@ export const assignCommand = new Command('assign')
|
|
|
18
18
|
.action(async (options: { dryRun?: boolean }) => {
|
|
19
19
|
// Track if we need to start the manager after DB is closed
|
|
20
20
|
let shouldStartManager = false;
|
|
21
|
+
let savedHiveDir: string | undefined;
|
|
21
22
|
|
|
22
23
|
await withHiveContext(async ({ root, paths, db }) => {
|
|
24
|
+
savedHiveDir = paths.hiveDir;
|
|
23
25
|
const spinner = ora('Assigning stories...').start();
|
|
24
26
|
|
|
25
27
|
try {
|
|
@@ -167,7 +169,7 @@ export const assignCommand = new Command('assign')
|
|
|
167
169
|
// Determine if we should start the manager, but don't start it yet
|
|
168
170
|
// Wait until after withHiveContext closes the DB to prevent race condition
|
|
169
171
|
if (result.assigned > 0) {
|
|
170
|
-
shouldStartManager = !(await isManagerRunning());
|
|
172
|
+
shouldStartManager = !(await isManagerRunning(paths.hiveDir));
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
console.log();
|
|
@@ -185,7 +187,7 @@ export const assignCommand = new Command('assign')
|
|
|
185
187
|
// This prevents the race condition where manager loads stale DB state
|
|
186
188
|
if (shouldStartManager) {
|
|
187
189
|
const spinner = ora('Starting manager daemon...').start();
|
|
188
|
-
const started = await startManager(60);
|
|
190
|
+
const started = await startManager(60, savedHiveDir);
|
|
189
191
|
if (started) {
|
|
190
192
|
spinner.succeed(chalk.green('Manager daemon started (checking every 60s)'));
|
|
191
193
|
} else {
|
|
@@ -10,6 +10,7 @@ import { createLog } from '../../../db/queries/logs.js';
|
|
|
10
10
|
import { updateRequirement } from '../../../db/queries/requirements.js';
|
|
11
11
|
import { getStoriesByStatus, updateStory } from '../../../db/queries/stories.js';
|
|
12
12
|
import { isTmuxSessionRunning } from '../../../tmux/manager.js';
|
|
13
|
+
import { getTechLeadSessionName } from '../../../utils/instance.js';
|
|
13
14
|
import {
|
|
14
15
|
createManagerNudgeEnvelope,
|
|
15
16
|
sendToTmuxSession,
|
|
@@ -86,14 +87,15 @@ async function nudgeTechLeadForStalledHandoff(
|
|
|
86
87
|
estimatedCount: number
|
|
87
88
|
): Promise<boolean> {
|
|
88
89
|
// Brief lock for DB read
|
|
90
|
+
const fallbackSession = getTechLeadSessionName(ctx.paths.hiveDir);
|
|
89
91
|
const techLeadInfo = await ctx.withDb(async db => {
|
|
90
92
|
const techLead = getTechLead(db.db);
|
|
91
93
|
return techLead
|
|
92
94
|
? {
|
|
93
|
-
sessionName: techLead.tmux_session ||
|
|
95
|
+
sessionName: techLead.tmux_session || fallbackSession,
|
|
94
96
|
cliTool: (techLead.cli_tool || 'claude') as CLITool,
|
|
95
97
|
}
|
|
96
|
-
: { sessionName:
|
|
98
|
+
: { sessionName: fallbackSession, cliTool: 'claude' as CLITool };
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
if (!(await isTmuxSessionRunning(techLeadInfo.sessionName))) {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
|
-
import { join } from 'path';
|
|
7
6
|
import { ClusterRuntime, fetchLocalClusterStatus } from '../../../cluster/runtime.js';
|
|
8
7
|
import { loadConfig } from '../../../config/loader.js';
|
|
9
8
|
import type { HiveConfig } from '../../../config/schema.js';
|
|
@@ -26,6 +25,7 @@ import { AgentState } from '../../../state-detectors/types.js';
|
|
|
26
25
|
import {
|
|
27
26
|
captureTmuxPane,
|
|
28
27
|
getHiveSessions,
|
|
28
|
+
getManagerSession,
|
|
29
29
|
isManagerRunning,
|
|
30
30
|
isTmuxSessionRunning,
|
|
31
31
|
killTmuxSession,
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import type { WithLockFn } from '../../../utils/auto-merge.js';
|
|
35
35
|
import { autoMergeApprovedPRs } from '../../../utils/auto-merge.js';
|
|
36
36
|
import type { CLITool } from '../../../utils/cli-commands.js';
|
|
37
|
+
import { getManagerLockPath, getTechLeadSessionName } from '../../../utils/instance.js';
|
|
37
38
|
import { withHiveContext, withHiveRoot } from '../../../utils/with-hive-context.js';
|
|
38
39
|
import {
|
|
39
40
|
agentStates,
|
|
@@ -265,7 +266,7 @@ managerCommand
|
|
|
265
266
|
const config = loadConfig(paths.hiveDir);
|
|
266
267
|
let clusterRuntime: ClusterRuntime | null = null;
|
|
267
268
|
|
|
268
|
-
const lockPath =
|
|
269
|
+
const lockPath = getManagerLockPath(paths.hiveDir);
|
|
269
270
|
|
|
270
271
|
// Acquire manager lock to ensure singleton
|
|
271
272
|
let releaseLock: (() => Promise<void>) | null = null;
|
|
@@ -474,10 +475,12 @@ managerCommand
|
|
|
474
475
|
.command('status')
|
|
475
476
|
.description('Check if the manager daemon is running')
|
|
476
477
|
.action(async () => {
|
|
477
|
-
const
|
|
478
|
+
const { paths } = withHiveRoot(c => c);
|
|
479
|
+
const managerSession = getManagerSession(paths.hiveDir);
|
|
480
|
+
const running = await isManagerRunning(paths.hiveDir);
|
|
478
481
|
if (running) {
|
|
479
|
-
console.log(chalk.green(
|
|
480
|
-
console.log(chalk.gray(
|
|
482
|
+
console.log(chalk.green(`Manager daemon is running (${managerSession} tmux session)`));
|
|
483
|
+
console.log(chalk.gray(`To view: tmux attach -t ${managerSession}`));
|
|
481
484
|
console.log(chalk.gray('To stop: hive manager stop'));
|
|
482
485
|
} else {
|
|
483
486
|
console.log(chalk.yellow('Manager daemon is not running'));
|
|
@@ -490,7 +493,8 @@ managerCommand
|
|
|
490
493
|
.command('stop')
|
|
491
494
|
.description('Stop the manager daemon')
|
|
492
495
|
.action(async () => {
|
|
493
|
-
const
|
|
496
|
+
const { paths: stopPaths } = withHiveRoot(c => c);
|
|
497
|
+
const stopped = await stopManagerSession(stopPaths.hiveDir);
|
|
494
498
|
if (stopped) {
|
|
495
499
|
console.log(chalk.green('Manager daemon stopped'));
|
|
496
500
|
} else {
|
|
@@ -535,8 +539,9 @@ async function managerCheck(
|
|
|
535
539
|
const syncResult = await clusterRuntime.sync(db.db);
|
|
536
540
|
if (!clusterRuntime.isLeader()) {
|
|
537
541
|
const status = clusterRuntime.getStatus();
|
|
538
|
-
|
|
539
|
-
|
|
542
|
+
const techLeadSession = getTechLeadSessionName(paths.hiveDir);
|
|
543
|
+
if (await isTmuxSessionRunning(techLeadSession)) {
|
|
544
|
+
await killTmuxSession(techLeadSession);
|
|
540
545
|
}
|
|
541
546
|
const details = [];
|
|
542
547
|
if (syncResult.local_events_emitted > 0) {
|
|
@@ -648,8 +653,8 @@ async function managerCheck(
|
|
|
648
653
|
|
|
649
654
|
// Discover active tmux sessions
|
|
650
655
|
verboseLogCtx(ctx, 'Step: discover hive tmux sessions');
|
|
651
|
-
const sessions = await getHiveSessions();
|
|
652
|
-
ctx.hiveSessions = sessions
|
|
656
|
+
const sessions = await getHiveSessions(ctx.paths.hiveDir);
|
|
657
|
+
ctx.hiveSessions = sessions;
|
|
653
658
|
verboseLogCtx(ctx, `Discovered ${ctx.hiveSessions.length} hive session(s)`);
|
|
654
659
|
await resolveOrphanedSessionEscalations(ctx);
|
|
655
660
|
|
|
@@ -966,7 +971,7 @@ async function scanAgentSessions(ctx: ManagerCheckContext): Promise<void> {
|
|
|
966
971
|
|
|
967
972
|
// Phase 2: Per-session processing (tmux/AI outside lock, DB writes under brief locks)
|
|
968
973
|
for (const session of ctx.hiveSessions) {
|
|
969
|
-
if (session.name ===
|
|
974
|
+
if (session.name === getManagerSession(ctx.paths.hiveDir)) continue;
|
|
970
975
|
|
|
971
976
|
const agent = ctx.agentsBySessionName.get(session.name);
|
|
972
977
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
spawnTmuxSession,
|
|
16
16
|
} from '../../../tmux/manager.js';
|
|
17
17
|
import type { CLITool } from '../../../utils/cli-commands.js';
|
|
18
|
+
import { getTechLeadSessionName } from '../../../utils/instance.js';
|
|
18
19
|
import { findHiveRoot as findHiveRootFromDir, getHivePaths } from '../../../utils/paths.js';
|
|
19
20
|
import { generateTechLeadPrompt } from '../req.js';
|
|
20
21
|
import { detectAgentState } from './agent-monitoring.js';
|
|
@@ -130,8 +131,11 @@ export async function restartStaleTechLead(ctx: ManagerCheckContext): Promise<vo
|
|
|
130
131
|
const safetyMode = agentConfig.safety_mode;
|
|
131
132
|
const model = resolveRuntimeModelForCli(agentConfig.model, cliTool);
|
|
132
133
|
|
|
134
|
+
const chromeEnabled = config.agents?.chrome_enabled === true && cliTool === 'claude';
|
|
133
135
|
const runtimeBuilder = getCliRuntimeBuilder(cliTool);
|
|
134
|
-
const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode
|
|
136
|
+
const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode, {
|
|
137
|
+
chrome: chromeEnabled,
|
|
138
|
+
});
|
|
135
139
|
|
|
136
140
|
// Look up active requirement and teams to provide context to the restarted tech lead
|
|
137
141
|
const initialPrompt = await ctx.withDb(async db => {
|
|
@@ -147,10 +151,12 @@ export async function restartStaleTechLead(ctx: ManagerCheckContext): Promise<vo
|
|
|
147
151
|
activeReq.description,
|
|
148
152
|
teams,
|
|
149
153
|
activeReq.godmode === 1,
|
|
150
|
-
activeReq.target_branch || 'main'
|
|
154
|
+
activeReq.target_branch || 'main',
|
|
155
|
+
getTechLeadSessionName(paths.hiveDir)
|
|
151
156
|
);
|
|
152
157
|
}
|
|
153
158
|
|
|
159
|
+
const techLeadInbox = getTechLeadSessionName(paths.hiveDir);
|
|
154
160
|
return `You are the Tech Lead of Hive, an AI development team orchestrator.
|
|
155
161
|
|
|
156
162
|
You have been restarted to refresh your context. No active requirement is currently being planned.
|
|
@@ -164,7 +170,7 @@ hive status
|
|
|
164
170
|
|
|
165
171
|
2. Check your inbox for messages from developers:
|
|
166
172
|
\`\`\`bash
|
|
167
|
-
hive msg inbox
|
|
173
|
+
hive msg inbox ${techLeadInbox}
|
|
168
174
|
\`\`\`
|
|
169
175
|
|
|
170
176
|
3. If there are pending requirements, begin planning them. If all work is complete, monitor for new requirements.`;
|
package/src/cli/commands/msg.ts
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import { queryAll, queryOne, run } from '../../db/client.js';
|
|
7
|
+
import { getTechLeadSessionName } from '../../utils/instance.js';
|
|
7
8
|
import { withHiveContext, withReadOnlyHiveContext } from '../../utils/with-hive-context.js';
|
|
8
9
|
|
|
9
10
|
interface MessageRow {
|
|
@@ -24,12 +25,12 @@ msgCommand
|
|
|
24
25
|
.command('send <to-session> <message>')
|
|
25
26
|
.description('Send a message to another agent')
|
|
26
27
|
.option('-s, --subject <subject>', 'Message subject')
|
|
27
|
-
.option('-f, --from <session>', 'Your session name (defaults to
|
|
28
|
+
.option('-f, --from <session>', 'Your session name (defaults to tech lead session)')
|
|
28
29
|
.action(
|
|
29
30
|
async (toSession: string, message: string, options: { subject?: string; from?: string }) => {
|
|
30
|
-
await withHiveContext(async ({ db }) => {
|
|
31
|
+
await withHiveContext(async ({ db, paths }) => {
|
|
31
32
|
const id = `msg-${nanoid(8)}`;
|
|
32
|
-
const fromSession = options.from ||
|
|
33
|
+
const fromSession = options.from || getTechLeadSessionName(paths.hiveDir);
|
|
33
34
|
|
|
34
35
|
run(
|
|
35
36
|
db.db,
|
|
@@ -53,8 +54,8 @@ msgCommand
|
|
|
53
54
|
.description('Check inbox for messages')
|
|
54
55
|
.option('--all', 'Show all messages including read')
|
|
55
56
|
.action(async (session: string | undefined, options: { all?: boolean }) => {
|
|
56
|
-
await withReadOnlyHiveContext(async ({ db }) => {
|
|
57
|
-
const targetSession = session ||
|
|
57
|
+
await withReadOnlyHiveContext(async ({ db, paths }) => {
|
|
58
|
+
const targetSession = session || getTechLeadSessionName(paths.hiveDir);
|
|
58
59
|
|
|
59
60
|
let query = `
|
|
60
61
|
SELECT * FROM messages
|
|
@@ -165,8 +166,8 @@ msgCommand
|
|
|
165
166
|
.command('outbox [session]')
|
|
166
167
|
.description('Check sent messages and their replies')
|
|
167
168
|
.action(async (session: string | undefined) => {
|
|
168
|
-
await withReadOnlyHiveContext(async ({ db }) => {
|
|
169
|
-
const fromSession = session ||
|
|
169
|
+
await withReadOnlyHiveContext(async ({ db, paths }) => {
|
|
170
|
+
const fromSession = session || getTechLeadSessionName(paths.hiveDir);
|
|
170
171
|
|
|
171
172
|
const messages = queryAll<MessageRow>(
|
|
172
173
|
db.db,
|
|
@@ -228,7 +228,7 @@ myStoriesCommand
|
|
|
228
228
|
process.exit(1);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
await withHiveContext(async ({ db }) => {
|
|
231
|
+
await withHiveContext(async ({ paths, db }) => {
|
|
232
232
|
const agent = requireAgentBySession(db.db, options.session);
|
|
233
233
|
|
|
234
234
|
if (!agent.team_id) {
|
|
@@ -245,19 +245,28 @@ myStoriesCommand
|
|
|
245
245
|
? trimmedTitle
|
|
246
246
|
: `Refactor: ${trimmedTitle}`;
|
|
247
247
|
|
|
248
|
-
const story = createStory(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
const story = createStory(
|
|
249
|
+
db.db,
|
|
250
|
+
{
|
|
251
|
+
teamId: agent.team_id,
|
|
252
|
+
title: normalizedTitle,
|
|
253
|
+
description: options.description.trim(),
|
|
254
|
+
acceptanceCriteria:
|
|
255
|
+
options.criteria && options.criteria.length > 0 ? options.criteria : null,
|
|
256
|
+
},
|
|
257
|
+
paths.storiesDir
|
|
258
|
+
);
|
|
255
259
|
|
|
256
|
-
const updatedStory = updateStory(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
const updatedStory = updateStory(
|
|
261
|
+
db.db,
|
|
262
|
+
story.id,
|
|
263
|
+
{
|
|
264
|
+
complexityScore: points,
|
|
265
|
+
storyPoints: points,
|
|
266
|
+
status,
|
|
267
|
+
},
|
|
268
|
+
paths.storiesDir
|
|
269
|
+
);
|
|
261
270
|
|
|
262
271
|
createLog(db.db, {
|
|
263
272
|
agentId: agent.id,
|
|
@@ -23,6 +23,16 @@ vi.mock('../../utils/with-hive-context.js', () => ({
|
|
|
23
23
|
),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
vi.mock('fs', async importOriginal => {
|
|
27
|
+
const actual = await importOriginal<typeof import('fs')>();
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
existsSync: vi.fn(() => true),
|
|
31
|
+
unlinkSync: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import { existsSync } from 'fs';
|
|
26
36
|
import { queryAll, run } from '../../db/client.js';
|
|
27
37
|
import { removeWorktree } from '../../git/worktree.js';
|
|
28
38
|
import { killAllHiveSessions } from '../../tmux/manager.js';
|
|
@@ -31,6 +41,7 @@ import { nukeCommand } from './nuke.js';
|
|
|
31
41
|
describe('nuke command', () => {
|
|
32
42
|
beforeEach(() => {
|
|
33
43
|
vi.clearAllMocks();
|
|
44
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
34
45
|
});
|
|
35
46
|
|
|
36
47
|
describe('command structure', () => {
|
|
@@ -145,6 +156,26 @@ describe('nuke command', () => {
|
|
|
145
156
|
|
|
146
157
|
expect(killAllHiveSessions).toHaveBeenCalled();
|
|
147
158
|
});
|
|
159
|
+
|
|
160
|
+
it('should skip worktree removal when path does not exist on disk', async () => {
|
|
161
|
+
vi.mocked(queryAll).mockReturnValue([{ worktree_path: 'repos/team-agent-stale' }]);
|
|
162
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
163
|
+
|
|
164
|
+
const agentsCmd = nukeCommand.commands.find(cmd => cmd.name() === 'agents');
|
|
165
|
+
await agentsCmd?.parseAsync(['--force'], { from: 'user' });
|
|
166
|
+
|
|
167
|
+
expect(removeWorktree).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should call removeWorktree when worktree path exists on disk', async () => {
|
|
171
|
+
vi.mocked(queryAll).mockReturnValue([{ worktree_path: 'repos/team-agent-abc123' }]);
|
|
172
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
173
|
+
|
|
174
|
+
const agentsCmd = nukeCommand.commands.find(cmd => cmd.name() === 'agents');
|
|
175
|
+
await agentsCmd?.parseAsync(['--force'], { from: 'user' });
|
|
176
|
+
|
|
177
|
+
expect(removeWorktree).toHaveBeenCalledWith('/root', 'repos/team-agent-abc123');
|
|
178
|
+
});
|
|
148
179
|
});
|
|
149
180
|
|
|
150
181
|
describe('nuke all worktree and table cleanup', () => {
|
package/src/cli/commands/nuke.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { existsSync, unlinkSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
6
|
+
import { join, resolve } from 'path';
|
|
7
7
|
import readline from 'readline';
|
|
8
8
|
import { queryAll, queryOne, run } from '../../db/client.js';
|
|
9
9
|
import { removeWorktree } from '../../git/worktree.js';
|
|
@@ -101,6 +101,13 @@ async function removeAgentWorktrees(
|
|
|
101
101
|
let removed = 0;
|
|
102
102
|
for (const agent of agents) {
|
|
103
103
|
if (agent.worktree_path) {
|
|
104
|
+
const fullPath = resolve(root, agent.worktree_path);
|
|
105
|
+
if (!existsSync(fullPath)) {
|
|
106
|
+
if (process.env.HIVE_DEBUG) {
|
|
107
|
+
console.log(`[debug] skipping missing worktree: ${fullPath}`);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
104
111
|
const result = removeWorktree(root, agent.worktree_path);
|
|
105
112
|
if (result.success) {
|
|
106
113
|
removed++;
|
|
@@ -174,7 +181,7 @@ export const nukeCommand = new Command('nuke')
|
|
|
174
181
|
.option('--force', 'Skip confirmation')
|
|
175
182
|
.action(async (options: { force?: boolean }) => {
|
|
176
183
|
try {
|
|
177
|
-
await withHiveContext(async ({ db, root }) => {
|
|
184
|
+
await withHiveContext(async ({ db, root, paths }) => {
|
|
178
185
|
const count = queryOne<{ count: number }>(
|
|
179
186
|
db.db,
|
|
180
187
|
'SELECT COUNT(*) as count FROM agents'
|
|
@@ -199,7 +206,7 @@ export const nukeCommand = new Command('nuke')
|
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
// Kill all hive tmux sessions first (this should always work regardless of DB state)
|
|
202
|
-
const killed = await killAllHiveSessions();
|
|
209
|
+
const killed = await killAllHiveSessions(paths.hiveDir);
|
|
203
210
|
console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
|
|
204
211
|
|
|
205
212
|
// Remove agent worktrees from filesystem before deleting agents from DB
|
|
@@ -232,7 +239,9 @@ export const nukeCommand = new Command('nuke')
|
|
|
232
239
|
) {
|
|
233
240
|
console.error(chalk.red(`\nDatabase is corrupted: ${message}`));
|
|
234
241
|
console.log(chalk.yellow('Killing tmux sessions anyway...'));
|
|
235
|
-
const
|
|
242
|
+
const hiveRoot = findHiveRoot();
|
|
243
|
+
const hDir = hiveRoot ? getHivePaths(hiveRoot).hiveDir : undefined;
|
|
244
|
+
const killed = await killAllHiveSessions(hDir);
|
|
236
245
|
console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
|
|
237
246
|
console.log(
|
|
238
247
|
chalk.yellow('Agent records could not be deleted from the corrupted database.')
|
|
@@ -309,7 +318,7 @@ export const nukeCommand = new Command('nuke')
|
|
|
309
318
|
.option('--force', 'Skip confirmation')
|
|
310
319
|
.action(async (options: { force?: boolean }) => {
|
|
311
320
|
try {
|
|
312
|
-
await withHiveContext(async ({ db, root }) => {
|
|
321
|
+
await withHiveContext(async ({ db, root, paths }) => {
|
|
313
322
|
console.log(chalk.red('\nThis will:'));
|
|
314
323
|
console.log(chalk.yellow(' - Kill all hive tmux sessions'));
|
|
315
324
|
console.log(chalk.yellow(' - Remove all agent git worktrees'));
|
|
@@ -333,7 +342,7 @@ export const nukeCommand = new Command('nuke')
|
|
|
333
342
|
}
|
|
334
343
|
|
|
335
344
|
// Kill all hive tmux sessions first (always works regardless of DB state)
|
|
336
|
-
const killed = await killAllHiveSessions();
|
|
345
|
+
const killed = await killAllHiveSessions(paths.hiveDir);
|
|
337
346
|
console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
|
|
338
347
|
|
|
339
348
|
// Remove agent worktrees from filesystem before deleting agents from DB
|
|
@@ -371,7 +380,9 @@ export const nukeCommand = new Command('nuke')
|
|
|
371
380
|
) {
|
|
372
381
|
console.error(chalk.red(`\nDatabase is corrupted: ${message}`));
|
|
373
382
|
console.log(chalk.yellow('Killing tmux sessions anyway...'));
|
|
374
|
-
const
|
|
383
|
+
const hiveRoot2 = findHiveRoot();
|
|
384
|
+
const hDir2 = hiveRoot2 ? getHivePaths(hiveRoot2).hiveDir : undefined;
|
|
385
|
+
const killed = await killAllHiveSessions(hDir2);
|
|
375
386
|
console.log(chalk.gray(`Killed ${killed} tmux sessions.`));
|
|
376
387
|
console.log(
|
|
377
388
|
chalk.yellow('Data records could not be deleted from the corrupted database.')
|