hungry-ghost-hive 0.3.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/LICENSE +111 -0
- package/README.md +352 -0
- package/dist/agents/base-agent.d.ts +63 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +189 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/index.d.ts +7 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +7 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/intermediate.d.ts +15 -0
- package/dist/agents/intermediate.d.ts.map +1 -0
- package/dist/agents/intermediate.js +142 -0
- package/dist/agents/intermediate.js.map +1 -0
- package/dist/agents/junior.d.ts +15 -0
- package/dist/agents/junior.d.ts.map +1 -0
- package/dist/agents/junior.js +147 -0
- package/dist/agents/junior.js.map +1 -0
- package/dist/agents/qa.d.ts +23 -0
- package/dist/agents/qa.d.ts.map +1 -0
- package/dist/agents/qa.js +238 -0
- package/dist/agents/qa.js.map +1 -0
- package/dist/agents/senior.d.ts +18 -0
- package/dist/agents/senior.d.ts.map +1 -0
- package/dist/agents/senior.js +267 -0
- package/dist/agents/senior.js.map +1 -0
- package/dist/agents/tech-lead.d.ts +17 -0
- package/dist/agents/tech-lead.d.ts.map +1 -0
- package/dist/agents/tech-lead.js +274 -0
- package/dist/agents/tech-lead.js.map +1 -0
- package/dist/cli/commands/add-repo.d.ts +3 -0
- package/dist/cli/commands/add-repo.d.ts.map +1 -0
- package/dist/cli/commands/add-repo.js +84 -0
- package/dist/cli/commands/add-repo.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +3 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +214 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/assign.d.ts +3 -0
- package/dist/cli/commands/assign.d.ts.map +1 -0
- package/dist/cli/commands/assign.js +81 -0
- package/dist/cli/commands/assign.js.map +1 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +118 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/escalations.d.ts +3 -0
- package/dist/cli/commands/escalations.d.ts.map +1 -0
- package/dist/cli/commands/escalations.js +157 -0
- package/dist/cli/commands/escalations.js.map +1 -0
- package/dist/cli/commands/index.d.ts +17 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +17 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +59 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/manager.d.ts +3 -0
- package/dist/cli/commands/manager.d.ts.map +1 -0
- package/dist/cli/commands/manager.js +775 -0
- package/dist/cli/commands/manager.js.map +1 -0
- package/dist/cli/commands/manager.test.d.ts +2 -0
- package/dist/cli/commands/manager.test.d.ts.map +1 -0
- package/dist/cli/commands/manager.test.js +45 -0
- package/dist/cli/commands/manager.test.js.map +1 -0
- package/dist/cli/commands/msg.d.ts +3 -0
- package/dist/cli/commands/msg.d.ts.map +1 -0
- package/dist/cli/commands/msg.js +190 -0
- package/dist/cli/commands/msg.js.map +1 -0
- package/dist/cli/commands/my-stories.d.ts +3 -0
- package/dist/cli/commands/my-stories.d.ts.map +1 -0
- package/dist/cli/commands/my-stories.js +174 -0
- package/dist/cli/commands/my-stories.js.map +1 -0
- package/dist/cli/commands/nuke.d.ts +3 -0
- package/dist/cli/commands/nuke.d.ts.map +1 -0
- package/dist/cli/commands/nuke.js +189 -0
- package/dist/cli/commands/nuke.js.map +1 -0
- package/dist/cli/commands/pr.d.ts +3 -0
- package/dist/cli/commands/pr.d.ts.map +1 -0
- package/dist/cli/commands/pr.js +488 -0
- package/dist/cli/commands/pr.js.map +1 -0
- package/dist/cli/commands/req.d.ts +3 -0
- package/dist/cli/commands/req.d.ts.map +1 -0
- package/dist/cli/commands/req.js +212 -0
- package/dist/cli/commands/req.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +3 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +114 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +259 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stories.d.ts +3 -0
- package/dist/cli/commands/stories.d.ts.map +1 -0
- package/dist/cli/commands/stories.js +111 -0
- package/dist/cli/commands/stories.js.map +1 -0
- package/dist/cli/commands/teams.d.ts +3 -0
- package/dist/cli/commands/teams.d.ts.map +1 -0
- package/dist/cli/commands/teams.js +137 -0
- package/dist/cli/commands/teams.js.map +1 -0
- package/dist/cli/dashboard/index.d.ts +5 -0
- package/dist/cli/dashboard/index.d.ts.map +1 -0
- package/dist/cli/dashboard/index.js +128 -0
- package/dist/cli/dashboard/index.js.map +1 -0
- package/dist/cli/dashboard/panels/activity.d.ts +5 -0
- package/dist/cli/dashboard/panels/activity.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/activity.js +64 -0
- package/dist/cli/dashboard/panels/activity.js.map +1 -0
- package/dist/cli/dashboard/panels/agents.d.ts +5 -0
- package/dist/cli/dashboard/panels/agents.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/agents.js +196 -0
- package/dist/cli/dashboard/panels/agents.js.map +1 -0
- package/dist/cli/dashboard/panels/escalations.d.ts +5 -0
- package/dist/cli/dashboard/panels/escalations.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/escalations.js +93 -0
- package/dist/cli/dashboard/panels/escalations.js.map +1 -0
- package/dist/cli/dashboard/panels/merge-queue.d.ts +5 -0
- package/dist/cli/dashboard/panels/merge-queue.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/merge-queue.js +57 -0
- package/dist/cli/dashboard/panels/merge-queue.js.map +1 -0
- package/dist/cli/dashboard/panels/pipeline.d.ts +5 -0
- package/dist/cli/dashboard/panels/pipeline.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/pipeline.js +54 -0
- package/dist/cli/dashboard/panels/pipeline.js.map +1 -0
- package/dist/cli/dashboard/panels/stories.d.ts +5 -0
- package/dist/cli/dashboard/panels/stories.d.ts.map +1 -0
- package/dist/cli/dashboard/panels/stories.js +79 -0
- package/dist/cli/dashboard/panels/stories.js.map +1 -0
- package/dist/cli-runtimes/claude.d.ts +8 -0
- package/dist/cli-runtimes/claude.d.ts.map +1 -0
- package/dist/cli-runtimes/claude.js +27 -0
- package/dist/cli-runtimes/claude.js.map +1 -0
- package/dist/cli-runtimes/codex.d.ts +8 -0
- package/dist/cli-runtimes/codex.d.ts.map +1 -0
- package/dist/cli-runtimes/codex.js +27 -0
- package/dist/cli-runtimes/codex.js.map +1 -0
- package/dist/cli-runtimes/gemini.d.ts +8 -0
- package/dist/cli-runtimes/gemini.d.ts.map +1 -0
- package/dist/cli-runtimes/gemini.js +29 -0
- package/dist/cli-runtimes/gemini.js.map +1 -0
- package/dist/cli-runtimes/index.d.ts +25 -0
- package/dist/cli-runtimes/index.d.ts.map +1 -0
- package/dist/cli-runtimes/index.js +48 -0
- package/dist/cli-runtimes/index.js.map +1 -0
- package/dist/cli-runtimes/index.test.d.ts +2 -0
- package/dist/cli-runtimes/index.test.d.ts.map +1 -0
- package/dist/cli-runtimes/index.test.js +216 -0
- package/dist/cli-runtimes/index.test.js.map +1 -0
- package/dist/cli-runtimes/types.d.ts +27 -0
- package/dist/cli-runtimes/types.d.ts.map +1 -0
- package/dist/cli-runtimes/types.js +2 -0
- package/dist/cli-runtimes/types.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +72 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +660 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +217 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/schema.test.d.ts +2 -0
- package/dist/config/schema.test.d.ts.map +1 -0
- package/dist/config/schema.test.js +123 -0
- package/dist/config/schema.test.js.map +1 -0
- package/dist/context-files/generator.d.ts +32 -0
- package/dist/context-files/generator.d.ts.map +1 -0
- package/dist/context-files/generator.js +120 -0
- package/dist/context-files/generator.js.map +1 -0
- package/dist/context-files/index.d.ts +38 -0
- package/dist/context-files/index.d.ts.map +1 -0
- package/dist/context-files/index.js +76 -0
- package/dist/context-files/index.js.map +1 -0
- package/dist/context-files/index.test.d.ts +2 -0
- package/dist/context-files/index.test.d.ts.map +1 -0
- package/dist/context-files/index.test.js +265 -0
- package/dist/context-files/index.test.js.map +1 -0
- package/dist/context-files/templates.d.ts +19 -0
- package/dist/context-files/templates.d.ts.map +1 -0
- package/dist/context-files/templates.js +266 -0
- package/dist/context-files/templates.js.map +1 -0
- package/dist/db/client.d.ts +95 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +343 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/lock.d.ts +25 -0
- package/dist/db/lock.d.ts.map +1 -0
- package/dist/db/lock.js +56 -0
- package/dist/db/lock.js.map +1 -0
- package/dist/db/lock.test.d.ts +2 -0
- package/dist/db/lock.test.d.ts.map +1 -0
- package/dist/db/lock.test.js +73 -0
- package/dist/db/lock.test.js.map +1 -0
- package/dist/db/queries/agents.d.ts +31 -0
- package/dist/db/queries/agents.d.ts.map +1 -0
- package/dist/db/queries/agents.js +76 -0
- package/dist/db/queries/agents.js.map +1 -0
- package/dist/db/queries/escalations.d.ts +29 -0
- package/dist/db/queries/escalations.d.ts.map +1 -0
- package/dist/db/queries/escalations.js +105 -0
- package/dist/db/queries/escalations.js.map +1 -0
- package/dist/db/queries/heartbeat.d.ts +20 -0
- package/dist/db/queries/heartbeat.d.ts.map +1 -0
- package/dist/db/queries/heartbeat.js +61 -0
- package/dist/db/queries/heartbeat.js.map +1 -0
- package/dist/db/queries/index.d.ts +8 -0
- package/dist/db/queries/index.d.ts.map +1 -0
- package/dist/db/queries/index.js +8 -0
- package/dist/db/queries/index.js.map +1 -0
- package/dist/db/queries/logs.d.ts +21 -0
- package/dist/db/queries/logs.d.ts.map +1 -0
- package/dist/db/queries/logs.js +72 -0
- package/dist/db/queries/logs.js.map +1 -0
- package/dist/db/queries/messages.d.ts +17 -0
- package/dist/db/queries/messages.d.ts.map +1 -0
- package/dist/db/queries/messages.js +22 -0
- package/dist/db/queries/messages.js.map +1 -0
- package/dist/db/queries/pull-requests.d.ts +33 -0
- package/dist/db/queries/pull-requests.d.ts.map +1 -0
- package/dist/db/queries/pull-requests.js +130 -0
- package/dist/db/queries/pull-requests.js.map +1 -0
- package/dist/db/queries/requirements.d.ts +22 -0
- package/dist/db/queries/requirements.d.ts.map +1 -0
- package/dist/db/queries/requirements.js +53 -0
- package/dist/db/queries/requirements.js.map +1 -0
- package/dist/db/queries/stories.d.ts +42 -0
- package/dist/db/queries/stories.d.ts.map +1 -0
- package/dist/db/queries/stories.js +163 -0
- package/dist/db/queries/stories.js.map +1 -0
- package/dist/db/queries/teams.d.ts +14 -0
- package/dist/db/queries/teams.d.ts.map +1 -0
- package/dist/db/queries/teams.js +24 -0
- package/dist/db/queries/teams.js.map +1 -0
- package/dist/git/branches.d.ts +52 -0
- package/dist/git/branches.d.ts.map +1 -0
- package/dist/git/branches.js +133 -0
- package/dist/git/branches.js.map +1 -0
- package/dist/git/github.d.ts +75 -0
- package/dist/git/github.d.ts.map +1 -0
- package/dist/git/github.js +162 -0
- package/dist/git/github.js.map +1 -0
- package/dist/git/index.d.ts +4 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +4 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/submodules.d.ts +47 -0
- package/dist/git/submodules.d.ts.map +1 -0
- package/dist/git/submodules.js +115 -0
- package/dist/git/submodules.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +18 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +111 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/index.d.ts +6 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/openai.d.ts +18 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +103 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +38 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +17 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/orchestrator/index.d.ts +4 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +4 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/scaler.d.ts +42 -0
- package/dist/orchestrator/scaler.d.ts.map +1 -0
- package/dist/orchestrator/scaler.js +154 -0
- package/dist/orchestrator/scaler.js.map +1 -0
- package/dist/orchestrator/scheduler.d.ts +90 -0
- package/dist/orchestrator/scheduler.d.ts.map +1 -0
- package/dist/orchestrator/scheduler.js +1003 -0
- package/dist/orchestrator/scheduler.js.map +1 -0
- package/dist/orchestrator/scheduler.test.d.ts +2 -0
- package/dist/orchestrator/scheduler.test.d.ts.map +1 -0
- package/dist/orchestrator/scheduler.test.js +242 -0
- package/dist/orchestrator/scheduler.test.js.map +1 -0
- package/dist/orchestrator/workflow.d.ts +18 -0
- package/dist/orchestrator/workflow.d.ts.map +1 -0
- package/dist/orchestrator/workflow.js +106 -0
- package/dist/orchestrator/workflow.js.map +1 -0
- package/dist/state-detectors/claude.d.ts +33 -0
- package/dist/state-detectors/claude.d.ts.map +1 -0
- package/dist/state-detectors/claude.js +237 -0
- package/dist/state-detectors/claude.js.map +1 -0
- package/dist/state-detectors/claude.test.d.ts +2 -0
- package/dist/state-detectors/claude.test.d.ts.map +1 -0
- package/dist/state-detectors/claude.test.js +127 -0
- package/dist/state-detectors/claude.test.js.map +1 -0
- package/dist/state-detectors/codex.d.ts +34 -0
- package/dist/state-detectors/codex.d.ts.map +1 -0
- package/dist/state-detectors/codex.js +233 -0
- package/dist/state-detectors/codex.js.map +1 -0
- package/dist/state-detectors/codex.test.d.ts +2 -0
- package/dist/state-detectors/codex.test.d.ts.map +1 -0
- package/dist/state-detectors/codex.test.js +85 -0
- package/dist/state-detectors/codex.test.js.map +1 -0
- package/dist/state-detectors/factory.d.ts +22 -0
- package/dist/state-detectors/factory.d.ts.map +1 -0
- package/dist/state-detectors/factory.js +37 -0
- package/dist/state-detectors/factory.js.map +1 -0
- package/dist/state-detectors/factory.test.d.ts +2 -0
- package/dist/state-detectors/factory.test.d.ts.map +1 -0
- package/dist/state-detectors/factory.test.js +44 -0
- package/dist/state-detectors/factory.test.js.map +1 -0
- package/dist/state-detectors/gemini.d.ts +34 -0
- package/dist/state-detectors/gemini.d.ts.map +1 -0
- package/dist/state-detectors/gemini.js +236 -0
- package/dist/state-detectors/gemini.js.map +1 -0
- package/dist/state-detectors/gemini.test.d.ts +2 -0
- package/dist/state-detectors/gemini.test.d.ts.map +1 -0
- package/dist/state-detectors/gemini.test.js +93 -0
- package/dist/state-detectors/gemini.test.js.map +1 -0
- package/dist/state-detectors/index.d.ts +20 -0
- package/dist/state-detectors/index.d.ts.map +1 -0
- package/dist/state-detectors/index.js +21 -0
- package/dist/state-detectors/index.js.map +1 -0
- package/dist/state-detectors/types.d.ts +67 -0
- package/dist/state-detectors/types.d.ts.map +1 -0
- package/dist/state-detectors/types.js +28 -0
- package/dist/state-detectors/types.js.map +1 -0
- package/dist/tmux/index.d.ts +2 -0
- package/dist/tmux/index.d.ts.map +1 -0
- package/dist/tmux/index.js +2 -0
- package/dist/tmux/index.js.map +1 -0
- package/dist/tmux/manager.d.ts +45 -0
- package/dist/tmux/manager.d.ts.map +1 -0
- package/dist/tmux/manager.js +252 -0
- package/dist/tmux/manager.js.map +1 -0
- package/dist/utils/claude-code-state.d.ts +46 -0
- package/dist/utils/claude-code-state.d.ts.map +1 -0
- package/dist/utils/claude-code-state.js +252 -0
- package/dist/utils/claude-code-state.js.map +1 -0
- package/dist/utils/cli-builder.d.ts +19 -0
- package/dist/utils/cli-builder.d.ts.map +1 -0
- package/dist/utils/cli-builder.js +58 -0
- package/dist/utils/cli-builder.js.map +1 -0
- package/dist/utils/cli-commands.d.ts +27 -0
- package/dist/utils/cli-commands.d.ts.map +1 -0
- package/dist/utils/cli-commands.js +69 -0
- package/dist/utils/cli-commands.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +77 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +17 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +33 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/timeout.d.ts +25 -0
- package/dist/utils/timeout.d.ts.map +1 -0
- package/dist/utils/timeout.js +57 -0
- package/dist/utils/timeout.js.map +1 -0
- package/package.json +78 -0
- package/src/agents/base-agent.ts +255 -0
- package/src/agents/index.ts +6 -0
- package/src/agents/intermediate.ts +161 -0
- package/src/agents/junior.ts +166 -0
- package/src/agents/qa.ts +272 -0
- package/src/agents/senior.ts +307 -0
- package/src/agents/tech-lead.ts +324 -0
- package/src/cli/commands/add-repo.ts +89 -0
- package/src/cli/commands/agents.ts +247 -0
- package/src/cli/commands/assign.ts +86 -0
- package/src/cli/commands/config.ts +121 -0
- package/src/cli/commands/escalations.ts +179 -0
- package/src/cli/commands/index.ts +16 -0
- package/src/cli/commands/init.ts +66 -0
- package/src/cli/commands/manager.test.ts +52 -0
- package/src/cli/commands/manager.ts +916 -0
- package/src/cli/commands/msg.ts +232 -0
- package/src/cli/commands/my-stories.ts +198 -0
- package/src/cli/commands/nuke.ts +223 -0
- package/src/cli/commands/pr.ts +559 -0
- package/src/cli/commands/req.ts +231 -0
- package/src/cli/commands/resume.ts +129 -0
- package/src/cli/commands/status.ts +284 -0
- package/src/cli/commands/stories.ts +131 -0
- package/src/cli/commands/teams.ts +158 -0
- package/src/cli/dashboard/index.ts +141 -0
- package/src/cli/dashboard/panels/activity.ts +77 -0
- package/src/cli/dashboard/panels/agents.ts +244 -0
- package/src/cli/dashboard/panels/escalations.ts +109 -0
- package/src/cli/dashboard/panels/merge-queue.ts +65 -0
- package/src/cli/dashboard/panels/pipeline.ts +65 -0
- package/src/cli/dashboard/panels/stories.ts +87 -0
- package/src/cli-runtimes/claude.ts +31 -0
- package/src/cli-runtimes/codex.ts +31 -0
- package/src/cli-runtimes/gemini.ts +33 -0
- package/src/cli-runtimes/index.test.ts +261 -0
- package/src/cli-runtimes/index.ts +52 -0
- package/src/cli-runtimes/types.ts +30 -0
- package/src/config/index.ts +2 -0
- package/src/config/loader.ts +89 -0
- package/src/config/schema.test.ts +135 -0
- package/src/config/schema.ts +238 -0
- package/src/context-files/generator.ts +132 -0
- package/src/context-files/index.test.ts +323 -0
- package/src/context-files/index.ts +102 -0
- package/src/context-files/templates.ts +279 -0
- package/src/db/client.ts +475 -0
- package/src/db/lock.test.ts +93 -0
- package/src/db/lock.ts +74 -0
- package/src/db/migrations/001-initial.sql +121 -0
- package/src/db/migrations/005-add-agent-heartbeat.sql +4 -0
- package/src/db/queries/agents.ts +113 -0
- package/src/db/queries/escalations.ts +140 -0
- package/src/db/queries/heartbeat.ts +92 -0
- package/src/db/queries/index.ts +7 -0
- package/src/db/queries/logs.ts +136 -0
- package/src/db/queries/messages.ts +38 -0
- package/src/db/queries/pull-requests.ts +170 -0
- package/src/db/queries/requirements.ts +81 -0
- package/src/db/queries/stories.ts +223 -0
- package/src/db/queries/teams.ts +39 -0
- package/src/git/branches.ts +186 -0
- package/src/git/github.ts +247 -0
- package/src/git/index.ts +3 -0
- package/src/git/submodules.ts +141 -0
- package/src/index.ts +93 -0
- package/src/llm/anthropic.ts +134 -0
- package/src/llm/index.ts +26 -0
- package/src/llm/openai.ts +125 -0
- package/src/llm/provider.ts +60 -0
- package/src/orchestrator/index.ts +3 -0
- package/src/orchestrator/scaler.ts +201 -0
- package/src/orchestrator/scheduler.test.ts +288 -0
- package/src/orchestrator/scheduler.ts +1130 -0
- package/src/orchestrator/workflow.ts +137 -0
- package/src/state-detectors/claude.test.ts +149 -0
- package/src/state-detectors/claude.ts +256 -0
- package/src/state-detectors/codex.test.ts +100 -0
- package/src/state-detectors/codex.ts +252 -0
- package/src/state-detectors/factory.test.ts +51 -0
- package/src/state-detectors/factory.ts +40 -0
- package/src/state-detectors/gemini.test.ts +110 -0
- package/src/state-detectors/gemini.ts +255 -0
- package/src/state-detectors/index.ts +25 -0
- package/src/state-detectors/types.ts +80 -0
- package/src/tmux/index.ts +1 -0
- package/src/tmux/manager.ts +310 -0
- package/src/types/sql.js.d.ts +34 -0
- package/src/utils/claude-code-state.ts +281 -0
- package/src/utils/cli-builder.ts +78 -0
- package/src/utils/cli-commands.ts +84 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +93 -0
- package/src/utils/paths.ts +49 -0
- package/src/utils/timeout.ts +84 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
import { getPlannedStories, updateStory, getStoryPointsByTeam, getStoryDependencies } from '../db/queries/stories.js';
|
|
2
|
+
import { getAgentsByTeam, getAgentById, createAgent, updateAgent } from '../db/queries/agents.js';
|
|
3
|
+
import { getTeamById, getAllTeams } from '../db/queries/teams.js';
|
|
4
|
+
import { queryOne, queryAll } from '../db/client.js';
|
|
5
|
+
import { createLog } from '../db/queries/logs.js';
|
|
6
|
+
import { spawnTmuxSession, generateSessionName, isTmuxSessionRunning, sendToTmuxSession, startManager, isManagerRunning, getHiveSessions, waitForTmuxSessionReady, forceBypassMode, killTmuxSession } from '../tmux/manager.js';
|
|
7
|
+
import { getCliRuntimeBuilder } from '../cli-runtimes/index.js';
|
|
8
|
+
export class Scheduler {
|
|
9
|
+
db;
|
|
10
|
+
config;
|
|
11
|
+
constructor(db, config) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a git worktree for an agent
|
|
17
|
+
* Returns the worktree path
|
|
18
|
+
*/
|
|
19
|
+
async createWorktree(agentId, teamId, repoPath) {
|
|
20
|
+
const { execSync } = await import('child_process');
|
|
21
|
+
// Construct worktree path: repos/<team-id>-<agent-id>/
|
|
22
|
+
const worktreePath = `repos/${teamId}-${agentId}`;
|
|
23
|
+
const fullWorktreePath = `${this.config.rootDir}/${worktreePath}`;
|
|
24
|
+
const fullRepoPath = `${this.config.rootDir}/${repoPath}`;
|
|
25
|
+
// Branch name: agent/<agent-id>
|
|
26
|
+
const branchName = `agent/${agentId}`;
|
|
27
|
+
try {
|
|
28
|
+
// Create worktree from main branch
|
|
29
|
+
execSync(`git worktree add "${fullWorktreePath}" -b "${branchName}"`, {
|
|
30
|
+
cwd: fullRepoPath,
|
|
31
|
+
stdio: 'pipe',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
// If worktree or branch already exists, try to add without creating branch
|
|
36
|
+
try {
|
|
37
|
+
execSync(`git worktree add "${fullWorktreePath}" "${branchName}"`, {
|
|
38
|
+
cwd: fullRepoPath,
|
|
39
|
+
stdio: 'pipe',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// If that fails too, log and throw
|
|
44
|
+
throw new Error(`Failed to create worktree at ${fullWorktreePath}: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return worktreePath;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove a git worktree for an agent
|
|
51
|
+
*/
|
|
52
|
+
async removeWorktree(worktreePath) {
|
|
53
|
+
if (!worktreePath)
|
|
54
|
+
return;
|
|
55
|
+
const { execSync } = await import('child_process');
|
|
56
|
+
const fullWorktreePath = `${this.config.rootDir}/${worktreePath}`;
|
|
57
|
+
try {
|
|
58
|
+
execSync(`git worktree remove "${fullWorktreePath}" --force`, {
|
|
59
|
+
cwd: this.config.rootDir,
|
|
60
|
+
stdio: 'pipe',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// Log error but don't throw - worktree might already be removed
|
|
65
|
+
console.error(`Warning: Failed to remove worktree at ${fullWorktreePath}: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build a dependency graph for stories
|
|
70
|
+
* Returns a map of story ID to its direct dependencies
|
|
71
|
+
*/
|
|
72
|
+
buildDependencyGraph(stories) {
|
|
73
|
+
const graph = new Map();
|
|
74
|
+
const storyIds = new Set(stories.map(s => s.id));
|
|
75
|
+
// Initialize all stories in the graph
|
|
76
|
+
for (const story of stories) {
|
|
77
|
+
if (!graph.has(story.id)) {
|
|
78
|
+
graph.set(story.id, new Set());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Add dependencies (only within the planned set; external deps handled by areDependenciesSatisfied)
|
|
82
|
+
for (const story of stories) {
|
|
83
|
+
const dependencies = getStoryDependencies(this.db, story.id);
|
|
84
|
+
for (const dep of dependencies) {
|
|
85
|
+
if (graph.has(story.id) && storyIds.has(dep.id)) {
|
|
86
|
+
graph.get(story.id).add(dep.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return graph;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Topological sort of stories based on dependencies
|
|
94
|
+
* Returns stories in order where dependencies come before dependents
|
|
95
|
+
* Returns null if circular dependency is detected
|
|
96
|
+
*/
|
|
97
|
+
topologicalSort(stories) {
|
|
98
|
+
const graph = this.buildDependencyGraph(stories);
|
|
99
|
+
const storyMap = new Map(stories.map(s => [s.id, s]));
|
|
100
|
+
// Kahn's algorithm for topological sort
|
|
101
|
+
const inDegree = new Map();
|
|
102
|
+
const result = [];
|
|
103
|
+
// Calculate in-degrees: count how many dependencies each story has
|
|
104
|
+
for (const [storyId, dependencies] of graph.entries()) {
|
|
105
|
+
inDegree.set(storyId, dependencies.size);
|
|
106
|
+
}
|
|
107
|
+
// Find all nodes with in-degree 0 (no dependencies)
|
|
108
|
+
const queue = [];
|
|
109
|
+
for (const [storyId, degree] of inDegree.entries()) {
|
|
110
|
+
if (degree === 0) {
|
|
111
|
+
queue.push(storyId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Process queue using Kahn's algorithm
|
|
115
|
+
while (queue.length > 0) {
|
|
116
|
+
const storyId = queue.shift();
|
|
117
|
+
const story = storyMap.get(storyId);
|
|
118
|
+
if (story) {
|
|
119
|
+
result.push(story);
|
|
120
|
+
}
|
|
121
|
+
// For each story that depends on this one, reduce in-degree
|
|
122
|
+
for (const [otherStoryId, dependencies] of graph.entries()) {
|
|
123
|
+
if (dependencies.has(storyId)) {
|
|
124
|
+
const newDegree = (inDegree.get(otherStoryId) || 0) - 1;
|
|
125
|
+
inDegree.set(otherStoryId, newDegree);
|
|
126
|
+
if (newDegree === 0) {
|
|
127
|
+
queue.push(otherStoryId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Check for circular dependencies
|
|
133
|
+
if (result.length !== stories.length) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a story's dependencies are satisfied
|
|
140
|
+
* A dependency is satisfied if it's completed (merged) or in progress (being worked on)
|
|
141
|
+
*/
|
|
142
|
+
areDependenciesSatisfied(storyId) {
|
|
143
|
+
const dependencies = getStoryDependencies(this.db, storyId);
|
|
144
|
+
for (const dep of dependencies) {
|
|
145
|
+
// Check if dependency is in a terminal or in-progress state
|
|
146
|
+
if (dep.status !== 'merged' && dep.status !== 'pr_submitted' && dep.status !== 'in_progress' && dep.status !== 'review' && dep.status !== 'qa' && dep.status !== 'qa_failed') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Select the agent with the least workload (queue-depth aware)
|
|
154
|
+
* Returns the agent with fewest active stories; breaks ties by creation order
|
|
155
|
+
*/
|
|
156
|
+
selectAgentWithLeastWorkload(agents) {
|
|
157
|
+
let selectedAgent = agents[0];
|
|
158
|
+
let minWorkload = this.getAgentWorkload(selectedAgent.id);
|
|
159
|
+
for (let i = 1; i < agents.length; i++) {
|
|
160
|
+
const workload = this.getAgentWorkload(agents[i].id);
|
|
161
|
+
if (workload < minWorkload) {
|
|
162
|
+
minWorkload = workload;
|
|
163
|
+
selectedAgent = agents[i];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return selectedAgent;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Calculate queue depth for an agent (number of active stories)
|
|
170
|
+
*/
|
|
171
|
+
getAgentWorkload(agentId) {
|
|
172
|
+
const activeStories = queryAll(this.db, `
|
|
173
|
+
SELECT * FROM stories
|
|
174
|
+
WHERE assigned_agent_id = ?
|
|
175
|
+
AND status IN ('in_progress', 'review', 'qa', 'qa_failed')
|
|
176
|
+
`, [agentId]);
|
|
177
|
+
return activeStories.length;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Assign planned stories to available agents
|
|
181
|
+
*/
|
|
182
|
+
async assignStories() {
|
|
183
|
+
const plannedStories = getPlannedStories(this.db);
|
|
184
|
+
const errors = [];
|
|
185
|
+
let assigned = 0;
|
|
186
|
+
// Topological sort stories to respect dependencies
|
|
187
|
+
const sortedStories = this.topologicalSort(plannedStories);
|
|
188
|
+
if (sortedStories === null) {
|
|
189
|
+
errors.push('Circular dependency detected in planned stories');
|
|
190
|
+
return { assigned, errors };
|
|
191
|
+
}
|
|
192
|
+
// Group stories by team
|
|
193
|
+
const storiesByTeam = new Map();
|
|
194
|
+
for (const story of sortedStories) {
|
|
195
|
+
if (!story.team_id)
|
|
196
|
+
continue;
|
|
197
|
+
const existing = storiesByTeam.get(story.team_id) || [];
|
|
198
|
+
existing.push(story);
|
|
199
|
+
storiesByTeam.set(story.team_id, existing);
|
|
200
|
+
}
|
|
201
|
+
// Process each team
|
|
202
|
+
for (const [teamId, stories] of storiesByTeam) {
|
|
203
|
+
const team = getTeamById(this.db, teamId);
|
|
204
|
+
if (!team)
|
|
205
|
+
continue;
|
|
206
|
+
// Get available agents for this team
|
|
207
|
+
const agents = getAgentsByTeam(this.db, teamId)
|
|
208
|
+
.filter(a => a.status === 'idle' && a.type !== 'qa');
|
|
209
|
+
// Find or create a Senior for delegation
|
|
210
|
+
let senior = agents.find(a => a.type === 'senior');
|
|
211
|
+
if (!senior) {
|
|
212
|
+
try {
|
|
213
|
+
senior = await this.spawnSenior(teamId, team.name, team.repo_path);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
errors.push(`Failed to spawn Senior for team ${team.name}: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Assign stories based on complexity
|
|
221
|
+
for (const story of stories) {
|
|
222
|
+
// Check if dependencies are satisfied before assigning
|
|
223
|
+
if (!this.areDependenciesSatisfied(story.id)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const complexity = story.complexity_score || 5;
|
|
227
|
+
let targetAgent;
|
|
228
|
+
if (complexity <= this.config.scaling.junior_max_complexity) {
|
|
229
|
+
// Assign to Junior with least workload
|
|
230
|
+
const juniors = agents.filter(a => a.type === 'junior' && a.status === 'idle');
|
|
231
|
+
targetAgent = juniors.length > 0 ? this.selectAgentWithLeastWorkload(juniors) : undefined;
|
|
232
|
+
if (!targetAgent) {
|
|
233
|
+
try {
|
|
234
|
+
targetAgent = await this.spawnJunior(teamId, team.name, team.repo_path);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Fall back to Intermediate or Senior
|
|
238
|
+
const intermediates = agents.filter(a => a.type === 'intermediate' && a.status === 'idle');
|
|
239
|
+
targetAgent = intermediates.length > 0 ? this.selectAgentWithLeastWorkload(intermediates) : senior;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (complexity <= this.config.scaling.intermediate_max_complexity) {
|
|
244
|
+
// Assign to Intermediate with least workload
|
|
245
|
+
const intermediates = agents.filter(a => a.type === 'intermediate' && a.status === 'idle');
|
|
246
|
+
targetAgent = intermediates.length > 0 ? this.selectAgentWithLeastWorkload(intermediates) : undefined;
|
|
247
|
+
if (!targetAgent) {
|
|
248
|
+
try {
|
|
249
|
+
targetAgent = await this.spawnIntermediate(teamId, team.name, team.repo_path);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Fall back to Senior
|
|
253
|
+
targetAgent = senior;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Senior handles directly
|
|
259
|
+
targetAgent = senior;
|
|
260
|
+
}
|
|
261
|
+
if (!targetAgent) {
|
|
262
|
+
errors.push(`No available agent for story ${story.id}`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
// Assign the story
|
|
266
|
+
updateStory(this.db, story.id, {
|
|
267
|
+
assignedAgentId: targetAgent.id,
|
|
268
|
+
status: 'in_progress',
|
|
269
|
+
});
|
|
270
|
+
updateAgent(this.db, targetAgent.id, {
|
|
271
|
+
status: 'working',
|
|
272
|
+
currentStoryId: story.id,
|
|
273
|
+
});
|
|
274
|
+
createLog(this.db, {
|
|
275
|
+
agentId: targetAgent.id,
|
|
276
|
+
storyId: story.id,
|
|
277
|
+
eventType: 'STORY_ASSIGNED',
|
|
278
|
+
message: `Assigned to ${targetAgent.type}`,
|
|
279
|
+
});
|
|
280
|
+
assigned++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { assigned, errors };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get the next story to work on for a specific agent
|
|
287
|
+
*/
|
|
288
|
+
getNextStoryForAgent(agentId) {
|
|
289
|
+
const agent = getAgentById(this.db, agentId);
|
|
290
|
+
if (!agent || !agent.team_id)
|
|
291
|
+
return null;
|
|
292
|
+
// Find an unassigned planned story for this team
|
|
293
|
+
const story = queryOne(this.db, `
|
|
294
|
+
SELECT * FROM stories
|
|
295
|
+
WHERE team_id = ?
|
|
296
|
+
AND status = 'planned'
|
|
297
|
+
AND assigned_agent_id IS NULL
|
|
298
|
+
ORDER BY story_points DESC, created_at
|
|
299
|
+
LIMIT 1
|
|
300
|
+
`, [agent.team_id]);
|
|
301
|
+
return story || null;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Check if scaling is needed based on workload
|
|
305
|
+
*/
|
|
306
|
+
async checkScaling() {
|
|
307
|
+
const teams = getAllTeams(this.db);
|
|
308
|
+
for (const team of teams) {
|
|
309
|
+
const storyPoints = getStoryPointsByTeam(this.db, team.id);
|
|
310
|
+
const seniors = getAgentsByTeam(this.db, team.id).filter(a => a.type === 'senior' && a.status !== 'terminated');
|
|
311
|
+
// Calculate needed seniors
|
|
312
|
+
const seniorCapacity = this.config.scaling.senior_capacity;
|
|
313
|
+
const neededSeniors = Math.ceil(storyPoints / seniorCapacity);
|
|
314
|
+
const currentSeniors = seniors.length;
|
|
315
|
+
if (neededSeniors > currentSeniors) {
|
|
316
|
+
// Scale up
|
|
317
|
+
const toSpawn = neededSeniors - currentSeniors;
|
|
318
|
+
for (let i = 0; i < toSpawn; i++) {
|
|
319
|
+
try {
|
|
320
|
+
await this.spawnSenior(team.id, team.name, team.repo_path, currentSeniors + i + 1);
|
|
321
|
+
createLog(this.db, {
|
|
322
|
+
agentId: 'scheduler',
|
|
323
|
+
eventType: 'TEAM_SCALED_UP',
|
|
324
|
+
message: `Spawned additional Senior for team ${team.name}`,
|
|
325
|
+
metadata: { teamId: team.id, totalSeniors: currentSeniors + i + 1 },
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// Log error but continue
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Health check: sync agent status with actual tmux sessions
|
|
337
|
+
* Returns number of agents whose status was corrected
|
|
338
|
+
*/
|
|
339
|
+
async healthCheck() {
|
|
340
|
+
const allAgents = queryAll(this.db, `
|
|
341
|
+
SELECT * FROM agents WHERE status != 'terminated'
|
|
342
|
+
`);
|
|
343
|
+
const liveSessions = await getHiveSessions();
|
|
344
|
+
const liveSessionNames = new Set(liveSessions.map(s => s.name));
|
|
345
|
+
let terminated = 0;
|
|
346
|
+
const revived = [];
|
|
347
|
+
for (const agent of allAgents) {
|
|
348
|
+
if (!agent.tmux_session)
|
|
349
|
+
continue;
|
|
350
|
+
const sessionAlive = liveSessionNames.has(agent.tmux_session);
|
|
351
|
+
// Only terminate if tmux session is actually dead
|
|
352
|
+
// Heartbeat staleness alone is not sufficient since Claude Code sessions
|
|
353
|
+
// don't have a mechanism to send heartbeats
|
|
354
|
+
if (!sessionAlive && agent.status !== 'terminated') {
|
|
355
|
+
// Remove worktree if exists
|
|
356
|
+
if (agent.worktree_path) {
|
|
357
|
+
await this.removeWorktree(agent.worktree_path);
|
|
358
|
+
}
|
|
359
|
+
updateAgent(this.db, agent.id, { status: 'terminated', currentStoryId: null, worktreePath: null });
|
|
360
|
+
createLog(this.db, {
|
|
361
|
+
agentId: agent.id,
|
|
362
|
+
eventType: 'AGENT_TERMINATED',
|
|
363
|
+
message: `Session ${agent.tmux_session} no longer running`,
|
|
364
|
+
});
|
|
365
|
+
terminated++;
|
|
366
|
+
// If agent was working on a story, mark it for reassignment
|
|
367
|
+
if (agent.current_story_id) {
|
|
368
|
+
updateStory(this.db, agent.current_story_id, {
|
|
369
|
+
status: 'planned',
|
|
370
|
+
assignedAgentId: null,
|
|
371
|
+
});
|
|
372
|
+
revived.push(agent.current_story_id);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { terminated, revived };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Check merge queue and spawn QA agents if needed
|
|
380
|
+
* Scales QA agents based on pending work: 1 QA per 2-3 pending PRs, max 5
|
|
381
|
+
*/
|
|
382
|
+
async checkMergeQueue() {
|
|
383
|
+
const teams = getAllTeams(this.db);
|
|
384
|
+
for (const team of teams) {
|
|
385
|
+
await this.scaleQAAgents(team.id, team.name, team.repo_path);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Scale QA agents based on pending work
|
|
390
|
+
* - Count stories with status 'pr_submitted' or 'qa'
|
|
391
|
+
* - Calculate needed QA agents: 1 QA per 2-3 pending PRs, max 5
|
|
392
|
+
* - Spawn QA agents in parallel with unique session names
|
|
393
|
+
* - Scale down excess QA agents when queue shrinks
|
|
394
|
+
*/
|
|
395
|
+
async scaleQAAgents(teamId, teamName, repoPath) {
|
|
396
|
+
// Count pending QA work: stories in 'qa' or 'pr_submitted' status
|
|
397
|
+
const qaStories = queryAll(this.db, `
|
|
398
|
+
SELECT * FROM stories
|
|
399
|
+
WHERE team_id = ? AND status IN ('qa', 'pr_submitted')
|
|
400
|
+
`, [teamId]);
|
|
401
|
+
const pendingCount = qaStories.length;
|
|
402
|
+
// Calculate needed QA agents: 1 per 2-3 pending PRs, max 5
|
|
403
|
+
// If no pending work, scale down to 0 agents
|
|
404
|
+
const neededQAs = pendingCount > 0 ? Math.min(Math.ceil(pendingCount / 2.5), 5) : 0;
|
|
405
|
+
// Get currently active QA agents for this team
|
|
406
|
+
const activeQAs = getAgentsByTeam(this.db, teamId)
|
|
407
|
+
.filter(a => a.type === 'qa' && a.status !== 'terminated');
|
|
408
|
+
const currentQACount = activeQAs.length;
|
|
409
|
+
if (neededQAs > currentQACount) {
|
|
410
|
+
// Scale up: spawn additional QA agents in parallel
|
|
411
|
+
const toSpawn = neededQAs - currentQACount;
|
|
412
|
+
const spawnPromises = [];
|
|
413
|
+
for (let i = 0; i < toSpawn; i++) {
|
|
414
|
+
const index = currentQACount + i + 1;
|
|
415
|
+
spawnPromises.push(this.spawnQA(teamId, teamName, repoPath, index));
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
await Promise.all(spawnPromises);
|
|
419
|
+
createLog(this.db, {
|
|
420
|
+
agentId: 'scheduler',
|
|
421
|
+
eventType: 'TEAM_SCALED_UP',
|
|
422
|
+
message: `Scaled QA agents for team ${teamName}: ${currentQACount} → ${neededQAs} (${pendingCount} pending stories)`,
|
|
423
|
+
metadata: { teamId, agentType: 'qa', previousCount: currentQACount, newCount: neededQAs, pendingCount },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
createLog(this.db, {
|
|
428
|
+
agentId: 'scheduler',
|
|
429
|
+
eventType: 'AGENT_SPAWNED',
|
|
430
|
+
status: 'error',
|
|
431
|
+
message: `Failed to scale QA agents for team ${teamName}: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
432
|
+
metadata: { teamId, agentType: 'qa', error: String(err) },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else if (neededQAs < currentQACount) {
|
|
437
|
+
// Scale down: terminate excess QA agents
|
|
438
|
+
const toTerminate = currentQACount - neededQAs;
|
|
439
|
+
// Identify QA agents to terminate (remove the ones with highest indices first)
|
|
440
|
+
const sortedAgents = activeQAs.sort((a, b) => {
|
|
441
|
+
const aIndex = parseInt(a.id.split('-').pop() || '0', 10);
|
|
442
|
+
const bIndex = parseInt(b.id.split('-').pop() || '0', 10);
|
|
443
|
+
return bIndex - aIndex; // Descending order to remove highest indices first
|
|
444
|
+
});
|
|
445
|
+
const qaAgentsToTerminate = sortedAgents.slice(0, toTerminate);
|
|
446
|
+
try {
|
|
447
|
+
for (const agent of qaAgentsToTerminate) {
|
|
448
|
+
// Kill tmux session
|
|
449
|
+
if (agent.tmux_session) {
|
|
450
|
+
await killTmuxSession(agent.tmux_session);
|
|
451
|
+
}
|
|
452
|
+
// Remove worktree
|
|
453
|
+
if (agent.worktree_path) {
|
|
454
|
+
await this.removeWorktree(agent.worktree_path);
|
|
455
|
+
}
|
|
456
|
+
// Update database
|
|
457
|
+
updateAgent(this.db, agent.id, {
|
|
458
|
+
status: 'terminated',
|
|
459
|
+
currentStoryId: null,
|
|
460
|
+
worktreePath: null,
|
|
461
|
+
});
|
|
462
|
+
// Log the event
|
|
463
|
+
createLog(this.db, {
|
|
464
|
+
agentId: agent.id,
|
|
465
|
+
eventType: 'AGENT_TERMINATED',
|
|
466
|
+
message: 'QA agent scaled down due to reduced PR queue',
|
|
467
|
+
metadata: { teamId, agentType: 'qa', reason: 'queue_shrink', pendingCount },
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
createLog(this.db, {
|
|
471
|
+
agentId: 'scheduler',
|
|
472
|
+
eventType: 'TEAM_SCALED_DOWN',
|
|
473
|
+
message: `Scaled down QA agents for team ${teamName}: ${currentQACount} → ${neededQAs} (${pendingCount} pending stories)`,
|
|
474
|
+
metadata: { teamId, agentType: 'qa', previousCount: currentQACount, newCount: neededQAs, pendingCount },
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
createLog(this.db, {
|
|
479
|
+
agentId: 'scheduler',
|
|
480
|
+
eventType: 'AGENT_TERMINATED',
|
|
481
|
+
status: 'error',
|
|
482
|
+
message: `Failed to scale down QA agents for team ${teamName}: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
483
|
+
metadata: { teamId, agentType: 'qa', error: String(err) },
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async spawnQA(teamId, teamName, repoPath, index = 1) {
|
|
489
|
+
const agent = createAgent(this.db, {
|
|
490
|
+
type: 'qa',
|
|
491
|
+
teamId,
|
|
492
|
+
model: 'sonnet',
|
|
493
|
+
});
|
|
494
|
+
// Create git worktree for this agent
|
|
495
|
+
const worktreePath = await this.createWorktree(agent.id, teamId, repoPath);
|
|
496
|
+
const workDir = `${this.config.rootDir}/${worktreePath}`;
|
|
497
|
+
const sessionName = generateSessionName('qa', teamName, index);
|
|
498
|
+
if (!await isTmuxSessionRunning(sessionName)) {
|
|
499
|
+
// Build CLI command using the configured runtime for QA agents
|
|
500
|
+
const cliTool = this.config.models.qa.cli_tool;
|
|
501
|
+
const model = 'sonnet';
|
|
502
|
+
const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(model);
|
|
503
|
+
const command = commandArgs.join(' ');
|
|
504
|
+
await spawnTmuxSession({
|
|
505
|
+
sessionName,
|
|
506
|
+
workDir,
|
|
507
|
+
command,
|
|
508
|
+
});
|
|
509
|
+
// Wait for Claude to be ready before sending prompt
|
|
510
|
+
await waitForTmuxSessionReady(sessionName);
|
|
511
|
+
// Force bypass permissions mode to enable autonomous work
|
|
512
|
+
await forceBypassMode(sessionName, 'claude');
|
|
513
|
+
const team = getTeamById(this.db, teamId);
|
|
514
|
+
const prompt = generateQAPrompt(teamName, team?.repo_url || '', worktreePath, sessionName);
|
|
515
|
+
await sendToTmuxSession(sessionName, prompt);
|
|
516
|
+
// Auto-start manager when spawning agents
|
|
517
|
+
await this.ensureManagerRunning();
|
|
518
|
+
}
|
|
519
|
+
updateAgent(this.db, agent.id, {
|
|
520
|
+
tmuxSession: sessionName,
|
|
521
|
+
status: 'working',
|
|
522
|
+
worktreePath,
|
|
523
|
+
});
|
|
524
|
+
return agent;
|
|
525
|
+
}
|
|
526
|
+
async ensureManagerRunning() {
|
|
527
|
+
if (!await isManagerRunning()) {
|
|
528
|
+
await startManager(60);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async spawnSenior(teamId, teamName, repoPath, index) {
|
|
532
|
+
const sessionName = generateSessionName('senior', teamName, index);
|
|
533
|
+
// Prevent creating duplicate agents on same tmux session
|
|
534
|
+
const existingSeniors = getAgentsByTeam(this.db, teamId).filter(a => a.type === 'senior');
|
|
535
|
+
const existingOnSession = existingSeniors.find(a => a.tmux_session === sessionName && a.status !== 'terminated');
|
|
536
|
+
if (existingOnSession && await isTmuxSessionRunning(sessionName)) {
|
|
537
|
+
return existingOnSession;
|
|
538
|
+
}
|
|
539
|
+
const agent = createAgent(this.db, {
|
|
540
|
+
type: 'senior',
|
|
541
|
+
teamId,
|
|
542
|
+
model: 'sonnet',
|
|
543
|
+
});
|
|
544
|
+
// Create git worktree for this agent
|
|
545
|
+
const worktreePath = await this.createWorktree(agent.id, teamId, repoPath);
|
|
546
|
+
const workDir = `${this.config.rootDir}/${worktreePath}`;
|
|
547
|
+
if (!await isTmuxSessionRunning(sessionName)) {
|
|
548
|
+
// Build CLI command using the configured runtime for Senior agents
|
|
549
|
+
const cliTool = this.config.models.senior.cli_tool;
|
|
550
|
+
const model = 'sonnet';
|
|
551
|
+
const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(model);
|
|
552
|
+
const command = commandArgs.join(' ');
|
|
553
|
+
await spawnTmuxSession({
|
|
554
|
+
sessionName,
|
|
555
|
+
workDir,
|
|
556
|
+
command,
|
|
557
|
+
});
|
|
558
|
+
// Wait for Claude to be ready before sending prompt
|
|
559
|
+
await waitForTmuxSessionReady(sessionName);
|
|
560
|
+
// Force bypass permissions mode to enable autonomous work
|
|
561
|
+
await forceBypassMode(sessionName, 'claude');
|
|
562
|
+
const team = getTeamById(this.db, teamId);
|
|
563
|
+
const stories = this.getTeamStories(teamId);
|
|
564
|
+
const prompt = generateSeniorPrompt(teamName, team?.repo_url || '', worktreePath, stories);
|
|
565
|
+
await sendToTmuxSession(sessionName, prompt);
|
|
566
|
+
// Auto-start manager when spawning agents
|
|
567
|
+
await this.ensureManagerRunning();
|
|
568
|
+
}
|
|
569
|
+
updateAgent(this.db, agent.id, {
|
|
570
|
+
tmuxSession: sessionName,
|
|
571
|
+
status: 'working',
|
|
572
|
+
worktreePath,
|
|
573
|
+
});
|
|
574
|
+
return agent;
|
|
575
|
+
}
|
|
576
|
+
async spawnIntermediate(teamId, teamName, repoPath) {
|
|
577
|
+
const existing = getAgentsByTeam(this.db, teamId).filter(a => a.type === 'intermediate');
|
|
578
|
+
const index = existing.length + 1;
|
|
579
|
+
const sessionName = generateSessionName('intermediate', teamName, index);
|
|
580
|
+
// Prevent creating duplicate agents on same tmux session
|
|
581
|
+
const existingOnSession = existing.find(a => a.tmux_session === sessionName && a.status !== 'terminated');
|
|
582
|
+
if (existingOnSession && await isTmuxSessionRunning(sessionName)) {
|
|
583
|
+
return existingOnSession;
|
|
584
|
+
}
|
|
585
|
+
const agent = createAgent(this.db, {
|
|
586
|
+
type: 'intermediate',
|
|
587
|
+
teamId,
|
|
588
|
+
model: 'haiku',
|
|
589
|
+
});
|
|
590
|
+
// Create git worktree for this agent
|
|
591
|
+
const worktreePath = await this.createWorktree(agent.id, teamId, repoPath);
|
|
592
|
+
const workDir = `${this.config.rootDir}/${worktreePath}`;
|
|
593
|
+
if (!await isTmuxSessionRunning(sessionName)) {
|
|
594
|
+
// Build CLI command using the configured runtime for Intermediate agents
|
|
595
|
+
const cliTool = this.config.models.intermediate.cli_tool;
|
|
596
|
+
const model = 'haiku';
|
|
597
|
+
const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(model);
|
|
598
|
+
const command = commandArgs.join(' ');
|
|
599
|
+
await spawnTmuxSession({
|
|
600
|
+
sessionName,
|
|
601
|
+
workDir,
|
|
602
|
+
command,
|
|
603
|
+
});
|
|
604
|
+
// Wait for Claude to be ready before sending prompt
|
|
605
|
+
await waitForTmuxSessionReady(sessionName);
|
|
606
|
+
// Force bypass permissions mode to enable autonomous work
|
|
607
|
+
await forceBypassMode(sessionName, 'claude');
|
|
608
|
+
const team = getTeamById(this.db, teamId);
|
|
609
|
+
const prompt = generateIntermediatePrompt(teamName, team?.repo_url || '', worktreePath, sessionName);
|
|
610
|
+
await sendToTmuxSession(sessionName, prompt);
|
|
611
|
+
// Auto-start manager when spawning agents
|
|
612
|
+
await this.ensureManagerRunning();
|
|
613
|
+
}
|
|
614
|
+
updateAgent(this.db, agent.id, {
|
|
615
|
+
tmuxSession: sessionName,
|
|
616
|
+
status: 'working',
|
|
617
|
+
worktreePath,
|
|
618
|
+
});
|
|
619
|
+
return agent;
|
|
620
|
+
}
|
|
621
|
+
async spawnJunior(teamId, teamName, repoPath) {
|
|
622
|
+
const existing = getAgentsByTeam(this.db, teamId).filter(a => a.type === 'junior');
|
|
623
|
+
const index = existing.length + 1;
|
|
624
|
+
const sessionName = generateSessionName('junior', teamName, index);
|
|
625
|
+
// Prevent creating duplicate agents on same tmux session
|
|
626
|
+
const existingOnSession = existing.find(a => a.tmux_session === sessionName && a.status !== 'terminated');
|
|
627
|
+
if (existingOnSession && await isTmuxSessionRunning(sessionName)) {
|
|
628
|
+
return existingOnSession;
|
|
629
|
+
}
|
|
630
|
+
const agent = createAgent(this.db, {
|
|
631
|
+
type: 'junior',
|
|
632
|
+
teamId,
|
|
633
|
+
model: 'haiku',
|
|
634
|
+
});
|
|
635
|
+
// Create git worktree for this agent
|
|
636
|
+
const worktreePath = await this.createWorktree(agent.id, teamId, repoPath);
|
|
637
|
+
const workDir = `${this.config.rootDir}/${worktreePath}`;
|
|
638
|
+
if (!await isTmuxSessionRunning(sessionName)) {
|
|
639
|
+
// Build CLI command using the configured runtime for Junior agents
|
|
640
|
+
// Note: Spec calls for gpt-4o-mini but using haiku until OpenAI integration is added
|
|
641
|
+
const cliTool = this.config.models.junior.cli_tool;
|
|
642
|
+
const model = 'haiku';
|
|
643
|
+
const commandArgs = getCliRuntimeBuilder(cliTool).buildSpawnCommand(model);
|
|
644
|
+
const command = commandArgs.join(' ');
|
|
645
|
+
await spawnTmuxSession({
|
|
646
|
+
sessionName,
|
|
647
|
+
workDir,
|
|
648
|
+
command,
|
|
649
|
+
});
|
|
650
|
+
// Wait for Claude to be ready before sending prompt
|
|
651
|
+
await waitForTmuxSessionReady(sessionName);
|
|
652
|
+
// Force bypass permissions mode to enable autonomous work
|
|
653
|
+
await forceBypassMode(sessionName, 'claude');
|
|
654
|
+
const team = getTeamById(this.db, teamId);
|
|
655
|
+
const prompt = generateJuniorPrompt(teamName, team?.repo_url || '', worktreePath, sessionName);
|
|
656
|
+
await sendToTmuxSession(sessionName, prompt);
|
|
657
|
+
// Auto-start manager when spawning agents
|
|
658
|
+
await this.ensureManagerRunning();
|
|
659
|
+
}
|
|
660
|
+
updateAgent(this.db, agent.id, {
|
|
661
|
+
tmuxSession: sessionName,
|
|
662
|
+
status: 'working',
|
|
663
|
+
worktreePath,
|
|
664
|
+
});
|
|
665
|
+
return agent;
|
|
666
|
+
}
|
|
667
|
+
getTeamStories(teamId) {
|
|
668
|
+
return queryAll(this.db, `
|
|
669
|
+
SELECT * FROM stories
|
|
670
|
+
WHERE team_id = ? AND status IN ('planned', 'estimated')
|
|
671
|
+
ORDER BY complexity_score DESC
|
|
672
|
+
`, [teamId]);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Prompt generation functions
|
|
676
|
+
function generateSeniorPrompt(teamName, repoUrl, repoPath, stories) {
|
|
677
|
+
const storyList = stories.map(s => `- [${s.id}] ${s.title} (complexity: ${s.complexity_score || '?'})\n ${s.description}`).join('\n\n');
|
|
678
|
+
const sessionName = `hive-senior-${teamName.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
|
|
679
|
+
return `You are a Senior Developer on Team ${teamName}.
|
|
680
|
+
Your tmux session: ${sessionName}
|
|
681
|
+
|
|
682
|
+
## Your Repository
|
|
683
|
+
- Local path: ${repoPath}
|
|
684
|
+
- Remote: ${repoUrl}
|
|
685
|
+
|
|
686
|
+
## Your Responsibilities
|
|
687
|
+
1. Implement assigned stories
|
|
688
|
+
2. Review code quality
|
|
689
|
+
3. Delegate simpler tasks to Intermediate/Junior developers
|
|
690
|
+
4. Ensure tests pass and code meets standards
|
|
691
|
+
|
|
692
|
+
## Pending Stories for Your Team
|
|
693
|
+
${storyList || 'No stories assigned yet.'}
|
|
694
|
+
|
|
695
|
+
## Finding Your Stories
|
|
696
|
+
Check your assigned stories:
|
|
697
|
+
\`\`\`bash
|
|
698
|
+
hive my-stories ${sessionName}
|
|
699
|
+
\`\`\`
|
|
700
|
+
|
|
701
|
+
See all team stories:
|
|
702
|
+
\`\`\`bash
|
|
703
|
+
hive my-stories ${sessionName} --all
|
|
704
|
+
\`\`\`
|
|
705
|
+
|
|
706
|
+
Claim a story:
|
|
707
|
+
\`\`\`bash
|
|
708
|
+
hive my-stories claim <story-id> --session ${sessionName}
|
|
709
|
+
\`\`\`
|
|
710
|
+
|
|
711
|
+
Mark story complete:
|
|
712
|
+
\`\`\`bash
|
|
713
|
+
hive my-stories complete <story-id>
|
|
714
|
+
\`\`\`
|
|
715
|
+
|
|
716
|
+
## Workflow
|
|
717
|
+
1. Run \`hive my-stories ${sessionName}\` to see your assigned work
|
|
718
|
+
2. Create a feature branch: \`git checkout -b feature/<story-id>-<short-description>\`
|
|
719
|
+
3. Implement the changes
|
|
720
|
+
4. Run tests and linting
|
|
721
|
+
5. Commit with a clear message referencing the story ID
|
|
722
|
+
6. Create a PR using \`gh pr create\`
|
|
723
|
+
7. Submit to merge queue for QA review:
|
|
724
|
+
\`\`\`bash
|
|
725
|
+
hive pr submit -b feature/<story-id>-<description> -s <story-id> --from ${sessionName}
|
|
726
|
+
\`\`\`
|
|
727
|
+
|
|
728
|
+
## Submitting PRs
|
|
729
|
+
After creating your GitHub PR, submit it to the merge queue:
|
|
730
|
+
\`\`\`bash
|
|
731
|
+
gh pr create --title "Story <story-id>: <title>" --body "..."
|
|
732
|
+
hive pr submit -b <branch-name> -s <story-id> --pr-url <github-pr-url> --from ${sessionName}
|
|
733
|
+
\`\`\`
|
|
734
|
+
|
|
735
|
+
Check your PR status:
|
|
736
|
+
\`\`\`bash
|
|
737
|
+
hive pr queue
|
|
738
|
+
\`\`\`
|
|
739
|
+
|
|
740
|
+
## Communication with Tech Lead
|
|
741
|
+
If you have questions or need guidance, message the Tech Lead:
|
|
742
|
+
\`\`\`bash
|
|
743
|
+
hive msg send hive-tech-lead "Your question here" --from ${sessionName}
|
|
744
|
+
\`\`\`
|
|
745
|
+
|
|
746
|
+
Check for replies:
|
|
747
|
+
\`\`\`bash
|
|
748
|
+
hive msg outbox ${sessionName}
|
|
749
|
+
\`\`\`
|
|
750
|
+
|
|
751
|
+
## Guidelines
|
|
752
|
+
- Follow existing code patterns in the repository
|
|
753
|
+
- Write tests for new functionality
|
|
754
|
+
- Keep commits atomic and well-documented
|
|
755
|
+
- Message the Tech Lead if blocked or need clarification
|
|
756
|
+
|
|
757
|
+
## IMPORTANT: Autonomous Workflow
|
|
758
|
+
You are an autonomous agent. DO NOT ask "Is there anything else?" or wait for instructions.
|
|
759
|
+
After completing a story:
|
|
760
|
+
1. Run \`hive my-stories ${sessionName}\` to get your next assignment
|
|
761
|
+
2. If no stories assigned, run \`hive my-stories ${sessionName} --all\` to see available work
|
|
762
|
+
3. Claim available work with \`hive my-stories claim <story-id> --session ${sessionName}\`
|
|
763
|
+
4. ALWAYS submit PRs to hive after creating them on GitHub:
|
|
764
|
+
\`hive pr submit -b <branch> -s <story-id> --pr-url <github-url> --from ${sessionName}\`
|
|
765
|
+
|
|
766
|
+
Start by exploring the codebase to understand its structure, then begin working on the highest priority story.`;
|
|
767
|
+
}
|
|
768
|
+
function generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName) {
|
|
769
|
+
const seniorSession = `hive-senior-${teamName.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
|
|
770
|
+
return `You are an Intermediate Developer on Team ${teamName}.
|
|
771
|
+
Your tmux session: ${sessionName}
|
|
772
|
+
|
|
773
|
+
## Your Repository
|
|
774
|
+
- Local path: ${repoPath}
|
|
775
|
+
- Remote: ${repoUrl}
|
|
776
|
+
|
|
777
|
+
## Your Responsibilities
|
|
778
|
+
1. Implement assigned stories (moderate complexity)
|
|
779
|
+
2. Write clean, tested code
|
|
780
|
+
3. Follow team coding standards
|
|
781
|
+
4. Ask Senior for help if stuck
|
|
782
|
+
|
|
783
|
+
## Finding Your Stories
|
|
784
|
+
Check your assigned stories:
|
|
785
|
+
\`\`\`bash
|
|
786
|
+
hive my-stories ${sessionName}
|
|
787
|
+
\`\`\`
|
|
788
|
+
|
|
789
|
+
Claim a story:
|
|
790
|
+
\`\`\`bash
|
|
791
|
+
hive my-stories claim <story-id> --session ${sessionName}
|
|
792
|
+
\`\`\`
|
|
793
|
+
|
|
794
|
+
Mark story complete:
|
|
795
|
+
\`\`\`bash
|
|
796
|
+
hive my-stories complete <story-id>
|
|
797
|
+
\`\`\`
|
|
798
|
+
|
|
799
|
+
## Workflow
|
|
800
|
+
1. Run \`hive my-stories ${sessionName}\` to see your assigned work
|
|
801
|
+
2. Create a feature branch: \`git checkout -b feature/<story-id>-<description>\`
|
|
802
|
+
3. Implement the changes
|
|
803
|
+
4. Run tests and linting
|
|
804
|
+
5. Commit and create a PR using \`gh pr create\`
|
|
805
|
+
6. Submit to merge queue:
|
|
806
|
+
\`\`\`bash
|
|
807
|
+
hive pr submit -b <branch-name> -s <story-id> --from ${sessionName}
|
|
808
|
+
\`\`\`
|
|
809
|
+
|
|
810
|
+
## Submitting PRs
|
|
811
|
+
After creating your GitHub PR:
|
|
812
|
+
\`\`\`bash
|
|
813
|
+
gh pr create --title "Story <story-id>: <title>" --body "..."
|
|
814
|
+
hive pr submit -b <branch-name> -s <story-id> --pr-url <github-pr-url> --from ${sessionName}
|
|
815
|
+
\`\`\`
|
|
816
|
+
|
|
817
|
+
## Communication
|
|
818
|
+
If you have questions, message your Senior or the Tech Lead:
|
|
819
|
+
\`\`\`bash
|
|
820
|
+
hive msg send ${seniorSession} "Your question" --from ${sessionName}
|
|
821
|
+
hive msg send hive-tech-lead "Your question" --from ${sessionName}
|
|
822
|
+
\`\`\`
|
|
823
|
+
|
|
824
|
+
Check for replies:
|
|
825
|
+
\`\`\`bash
|
|
826
|
+
hive msg outbox ${sessionName}
|
|
827
|
+
\`\`\`
|
|
828
|
+
|
|
829
|
+
## Guidelines
|
|
830
|
+
- Follow existing code patterns
|
|
831
|
+
- Write tests for your changes
|
|
832
|
+
- Keep commits focused and clear
|
|
833
|
+
- Message Senior or Tech Lead if blocked
|
|
834
|
+
|
|
835
|
+
## IMPORTANT: Autonomous Workflow
|
|
836
|
+
You are an autonomous agent. DO NOT ask "Is there anything else?" or wait for instructions.
|
|
837
|
+
After completing a story:
|
|
838
|
+
1. Run \`hive my-stories ${sessionName}\` to get your next assignment
|
|
839
|
+
2. If no stories assigned, run \`hive my-stories ${sessionName} --all\` to see available work
|
|
840
|
+
3. Claim available work with \`hive my-stories claim <story-id> --session ${sessionName}\`
|
|
841
|
+
4. ALWAYS submit PRs to hive after creating them on GitHub:
|
|
842
|
+
\`hive pr submit -b <branch> -s <story-id> --pr-url <github-url> --from ${sessionName}\`
|
|
843
|
+
|
|
844
|
+
Start by exploring the codebase, then run \`hive my-stories ${sessionName}\` to see your assignments.`;
|
|
845
|
+
}
|
|
846
|
+
function generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName) {
|
|
847
|
+
const seniorSession = `hive-senior-${teamName.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
|
|
848
|
+
return `You are a Junior Developer on Team ${teamName}.
|
|
849
|
+
Your tmux session: ${sessionName}
|
|
850
|
+
|
|
851
|
+
## Your Repository
|
|
852
|
+
- Local path: ${repoPath}
|
|
853
|
+
- Remote: ${repoUrl}
|
|
854
|
+
|
|
855
|
+
## Your Responsibilities
|
|
856
|
+
1. Implement simple, well-defined stories
|
|
857
|
+
2. Learn the codebase patterns
|
|
858
|
+
3. Write tests for your changes
|
|
859
|
+
4. Ask for help when needed
|
|
860
|
+
|
|
861
|
+
## Finding Your Stories
|
|
862
|
+
Check your assigned stories:
|
|
863
|
+
\`\`\`bash
|
|
864
|
+
hive my-stories ${sessionName}
|
|
865
|
+
\`\`\`
|
|
866
|
+
|
|
867
|
+
Claim a story:
|
|
868
|
+
\`\`\`bash
|
|
869
|
+
hive my-stories claim <story-id> --session ${sessionName}
|
|
870
|
+
\`\`\`
|
|
871
|
+
|
|
872
|
+
Mark story complete:
|
|
873
|
+
\`\`\`bash
|
|
874
|
+
hive my-stories complete <story-id>
|
|
875
|
+
\`\`\`
|
|
876
|
+
|
|
877
|
+
## Workflow
|
|
878
|
+
1. Run \`hive my-stories ${sessionName}\` to see your assigned work
|
|
879
|
+
2. Create a feature branch: \`git checkout -b feature/<story-id>-<description>\`
|
|
880
|
+
3. Implement the changes carefully
|
|
881
|
+
4. Run tests before committing
|
|
882
|
+
5. Commit and create a PR using \`gh pr create\`
|
|
883
|
+
6. Submit to merge queue:
|
|
884
|
+
\`\`\`bash
|
|
885
|
+
hive pr submit -b <branch-name> -s <story-id> --from ${sessionName}
|
|
886
|
+
\`\`\`
|
|
887
|
+
|
|
888
|
+
## Submitting PRs
|
|
889
|
+
After creating your GitHub PR:
|
|
890
|
+
\`\`\`bash
|
|
891
|
+
gh pr create --title "Story <story-id>: <title>" --body "..."
|
|
892
|
+
hive pr submit -b <branch-name> -s <story-id> --pr-url <github-pr-url> --from ${sessionName}
|
|
893
|
+
\`\`\`
|
|
894
|
+
|
|
895
|
+
## Communication
|
|
896
|
+
If you have questions, message your Senior or the Tech Lead:
|
|
897
|
+
\`\`\`bash
|
|
898
|
+
hive msg send ${seniorSession} "Your question" --from ${sessionName}
|
|
899
|
+
hive msg send hive-tech-lead "Your question" --from ${sessionName}
|
|
900
|
+
\`\`\`
|
|
901
|
+
|
|
902
|
+
Check for replies:
|
|
903
|
+
\`\`\`bash
|
|
904
|
+
hive msg outbox ${sessionName}
|
|
905
|
+
\`\`\`
|
|
906
|
+
|
|
907
|
+
## Guidelines
|
|
908
|
+
- Follow existing patterns exactly
|
|
909
|
+
- Ask questions if requirements are unclear
|
|
910
|
+
- Test thoroughly before submitting
|
|
911
|
+
- Keep changes small and focused
|
|
912
|
+
|
|
913
|
+
## IMPORTANT: Autonomous Workflow
|
|
914
|
+
You are an autonomous agent. DO NOT ask "Is there anything else?" or wait for instructions.
|
|
915
|
+
After completing a story:
|
|
916
|
+
1. Run \`hive my-stories ${sessionName}\` to get your next assignment
|
|
917
|
+
2. If no stories assigned, run \`hive my-stories ${sessionName} --all\` to see available work
|
|
918
|
+
3. Claim available work with \`hive my-stories claim <story-id> --session ${sessionName}\`
|
|
919
|
+
4. ALWAYS submit PRs to hive after creating them on GitHub:
|
|
920
|
+
\`hive pr submit -b <branch> -s <story-id> --pr-url <github-url> --from ${sessionName}\`
|
|
921
|
+
|
|
922
|
+
Start by exploring the codebase to understand how things work, then run \`hive my-stories ${sessionName}\` to see your assignments.`;
|
|
923
|
+
}
|
|
924
|
+
function generateQAPrompt(teamName, repoUrl, repoPath, sessionName) {
|
|
925
|
+
return `You are a QA Engineer on Team ${teamName}.
|
|
926
|
+
Your tmux session: ${sessionName}
|
|
927
|
+
|
|
928
|
+
## Your Repository
|
|
929
|
+
- Local path: ${repoPath}
|
|
930
|
+
- Remote: ${repoUrl}
|
|
931
|
+
|
|
932
|
+
## Your Responsibilities
|
|
933
|
+
1. Review PRs in the merge queue
|
|
934
|
+
2. Check for merge conflicts
|
|
935
|
+
3. Run tests and verify functionality
|
|
936
|
+
4. Check code quality and standards
|
|
937
|
+
5. Approve and merge good PRs
|
|
938
|
+
6. Reject PRs that need fixes
|
|
939
|
+
|
|
940
|
+
## Merge Queue Workflow
|
|
941
|
+
|
|
942
|
+
### Check the merge queue:
|
|
943
|
+
\`\`\`bash
|
|
944
|
+
hive pr queue
|
|
945
|
+
\`\`\`
|
|
946
|
+
|
|
947
|
+
### Claim the next PR for review:
|
|
948
|
+
\`\`\`bash
|
|
949
|
+
hive pr review --from ${sessionName}
|
|
950
|
+
\`\`\`
|
|
951
|
+
|
|
952
|
+
### View PR details:
|
|
953
|
+
\`\`\`bash
|
|
954
|
+
hive pr show <pr-id>
|
|
955
|
+
\`\`\`
|
|
956
|
+
|
|
957
|
+
### After reviewing:
|
|
958
|
+
|
|
959
|
+
**If the PR is good - approve and merge:**
|
|
960
|
+
\`\`\`bash
|
|
961
|
+
# First, merge via GitHub CLI
|
|
962
|
+
gh pr merge <pr-number> --merge
|
|
963
|
+
|
|
964
|
+
# Then mark as merged in Hive
|
|
965
|
+
hive pr approve <pr-id> --from ${sessionName}
|
|
966
|
+
\`\`\`
|
|
967
|
+
|
|
968
|
+
**If the PR has issues - reject with feedback:**
|
|
969
|
+
\`\`\`bash
|
|
970
|
+
hive pr reject <pr-id> --reason "Description of issues" --from ${sessionName}
|
|
971
|
+
|
|
972
|
+
# Notify the developer
|
|
973
|
+
hive msg send <developer-session> "Your PR was rejected: <reason>" --from ${sessionName}
|
|
974
|
+
\`\`\`
|
|
975
|
+
|
|
976
|
+
## Review Checklist
|
|
977
|
+
For each PR, verify:
|
|
978
|
+
1. **No merge conflicts** - Check with \`git fetch && git merge --no-commit origin/main\`
|
|
979
|
+
2. **Tests pass** - Run the project's test suite
|
|
980
|
+
3. **Code quality** - Check for code standards, no obvious bugs
|
|
981
|
+
4. **Functionality** - Test that the changes work as expected
|
|
982
|
+
5. **Story requirements** - Verify acceptance criteria are met
|
|
983
|
+
|
|
984
|
+
## Communication
|
|
985
|
+
If you need clarification from a developer:
|
|
986
|
+
\`\`\`bash
|
|
987
|
+
hive msg send <developer-session> "Your question" --from ${sessionName}
|
|
988
|
+
\`\`\`
|
|
989
|
+
|
|
990
|
+
Check for replies:
|
|
991
|
+
\`\`\`bash
|
|
992
|
+
hive msg outbox ${sessionName}
|
|
993
|
+
\`\`\`
|
|
994
|
+
|
|
995
|
+
## Guidelines
|
|
996
|
+
- Review PRs in queue order (first in, first out)
|
|
997
|
+
- Be thorough but efficient
|
|
998
|
+
- Provide clear feedback when rejecting
|
|
999
|
+
- Ensure main branch stays stable
|
|
1000
|
+
|
|
1001
|
+
Start by running \`hive pr queue\` to see PRs waiting for review.`;
|
|
1002
|
+
}
|
|
1003
|
+
//# sourceMappingURL=scheduler.js.map
|