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,125 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import type { LLMProvider, Message, CompletionOptions, CompletionResult } from './provider.js';
|
|
3
|
+
import { withTimeout } from '../utils/timeout.js';
|
|
4
|
+
|
|
5
|
+
export class OpenAIProvider implements LLMProvider {
|
|
6
|
+
name = 'openai';
|
|
7
|
+
private client: OpenAI;
|
|
8
|
+
private model: string;
|
|
9
|
+
private defaultMaxTokens: number;
|
|
10
|
+
private defaultTemperature: number;
|
|
11
|
+
|
|
12
|
+
constructor(options: {
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
} = {}) {
|
|
18
|
+
this.client = new OpenAI({
|
|
19
|
+
apiKey: options.apiKey || process.env.OPENAI_API_KEY,
|
|
20
|
+
});
|
|
21
|
+
this.model = options.model || 'gpt-4o-mini';
|
|
22
|
+
this.defaultMaxTokens = options.maxTokens || 4000;
|
|
23
|
+
this.defaultTemperature = options.temperature ?? 0.2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async complete(messages: Message[], options?: CompletionOptions): Promise<CompletionResult> {
|
|
27
|
+
const apiCall = async () => {
|
|
28
|
+
const response = await this.client.chat.completions.create({
|
|
29
|
+
model: this.model,
|
|
30
|
+
max_tokens: options?.maxTokens ?? this.defaultMaxTokens,
|
|
31
|
+
temperature: options?.temperature ?? this.defaultTemperature,
|
|
32
|
+
messages: messages.map(m => ({
|
|
33
|
+
role: m.role,
|
|
34
|
+
content: m.content,
|
|
35
|
+
})),
|
|
36
|
+
stop: options?.stopSequences,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const choice = response.choices[0];
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content: choice.message.content || '',
|
|
43
|
+
stopReason: this.mapStopReason(choice.finish_reason),
|
|
44
|
+
usage: {
|
|
45
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
46
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Apply timeout if specified
|
|
52
|
+
if (options?.timeoutMs) {
|
|
53
|
+
return withTimeout(
|
|
54
|
+
apiCall(),
|
|
55
|
+
options.timeoutMs,
|
|
56
|
+
`OpenAI API call timed out after ${options.timeoutMs}ms`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return apiCall();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async *streamComplete(messages: Message[], options?: CompletionOptions): AsyncIterable<string> {
|
|
64
|
+
// Capture instance properties to avoid 'this' binding issues in generator
|
|
65
|
+
const client = this.client;
|
|
66
|
+
const model = this.model;
|
|
67
|
+
const defaultMaxTokens = this.defaultMaxTokens;
|
|
68
|
+
const defaultTemperature = this.defaultTemperature;
|
|
69
|
+
|
|
70
|
+
const streamGenerator = async function* () {
|
|
71
|
+
const stream = await client.chat.completions.create({
|
|
72
|
+
model: model,
|
|
73
|
+
max_tokens: options?.maxTokens ?? defaultMaxTokens,
|
|
74
|
+
temperature: options?.temperature ?? defaultTemperature,
|
|
75
|
+
messages: messages.map(m => ({
|
|
76
|
+
role: m.role,
|
|
77
|
+
content: m.content,
|
|
78
|
+
})),
|
|
79
|
+
stop: options?.stopSequences,
|
|
80
|
+
stream: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
for await (const chunk of stream) {
|
|
84
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
85
|
+
if (delta) {
|
|
86
|
+
yield delta;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Apply timeout if specified
|
|
92
|
+
if (options?.timeoutMs) {
|
|
93
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
reject(new Error(`OpenAI streaming API call timed out after ${options.timeoutMs}ms`));
|
|
96
|
+
}, options.timeoutMs);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const generator = streamGenerator();
|
|
100
|
+
|
|
101
|
+
while (true) {
|
|
102
|
+
const result = await Promise.race([
|
|
103
|
+
generator.next(),
|
|
104
|
+
timeoutPromise
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
if (result.done) break;
|
|
108
|
+
yield result.value;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
yield* streamGenerator();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private mapStopReason(reason: string | null): CompletionResult['stopReason'] {
|
|
116
|
+
switch (reason) {
|
|
117
|
+
case 'stop':
|
|
118
|
+
return 'end_turn';
|
|
119
|
+
case 'length':
|
|
120
|
+
return 'max_tokens';
|
|
121
|
+
default:
|
|
122
|
+
return 'error';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
role: 'user' | 'assistant' | 'system';
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CompletionOptions {
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
stopSequences?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Timeout in milliseconds for this completion request.
|
|
12
|
+
* If the LLM call doesn't complete within this time, it will be cancelled
|
|
13
|
+
* and a TimeoutError will be thrown.
|
|
14
|
+
*/
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CompletionResult {
|
|
19
|
+
content: string;
|
|
20
|
+
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'error';
|
|
21
|
+
usage: {
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LLMProvider {
|
|
28
|
+
name: string;
|
|
29
|
+
complete(messages: Message[], options?: CompletionOptions): Promise<CompletionResult>;
|
|
30
|
+
streamComplete?(messages: Message[], options?: CompletionOptions): AsyncIterable<string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ProviderType = 'anthropic' | 'openai';
|
|
34
|
+
|
|
35
|
+
export interface ProviderConfig {
|
|
36
|
+
provider: ProviderType;
|
|
37
|
+
model: string;
|
|
38
|
+
maxTokens?: number;
|
|
39
|
+
temperature?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getProviderApiKey(provider: ProviderType): string | undefined {
|
|
43
|
+
switch (provider) {
|
|
44
|
+
case 'anthropic':
|
|
45
|
+
return process.env.ANTHROPIC_API_KEY;
|
|
46
|
+
case 'openai':
|
|
47
|
+
return process.env.OPENAI_API_KEY;
|
|
48
|
+
default:
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function validateProviderConfig(config: ProviderConfig): void {
|
|
54
|
+
const apiKey = getProviderApiKey(config.provider);
|
|
55
|
+
if (!apiKey) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Missing API key for ${config.provider}. Set ${config.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'} environment variable.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { Database } from 'sql.js';
|
|
2
|
+
import { getStoryPointsByTeam } from '../db/queries/stories.js';
|
|
3
|
+
import { getAgentsByTeam, getTechLead, terminateAgent, type AgentRow } from '../db/queries/agents.js';
|
|
4
|
+
import { getAllTeams, type TeamRow } from '../db/queries/teams.js';
|
|
5
|
+
import { createLog } from '../db/queries/logs.js';
|
|
6
|
+
import { killTmuxSession } from '../tmux/manager.js';
|
|
7
|
+
import type { ScalingConfig } from '../config/schema.js';
|
|
8
|
+
|
|
9
|
+
export interface ScalerConfig {
|
|
10
|
+
scaling: ScalingConfig;
|
|
11
|
+
rootDir: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ScalingRecommendation {
|
|
15
|
+
teamId: string;
|
|
16
|
+
teamName: string;
|
|
17
|
+
currentSeniors: number;
|
|
18
|
+
recommendedSeniors: number;
|
|
19
|
+
action: 'scale_up' | 'scale_down' | 'none';
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Scaler {
|
|
24
|
+
private db: Database;
|
|
25
|
+
private config: ScalerConfig;
|
|
26
|
+
|
|
27
|
+
constructor(db: Database, config: ScalerConfig) {
|
|
28
|
+
this.db = db;
|
|
29
|
+
this.config = config;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Analyze current workload and recommend scaling actions
|
|
34
|
+
*/
|
|
35
|
+
analyzeScaling(): ScalingRecommendation[] {
|
|
36
|
+
const teams = getAllTeams(this.db);
|
|
37
|
+
const recommendations: ScalingRecommendation[] = [];
|
|
38
|
+
|
|
39
|
+
for (const team of teams) {
|
|
40
|
+
const recommendation = this.analyzeTeam(team);
|
|
41
|
+
recommendations.push(recommendation);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return recommendations;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private analyzeTeam(team: TeamRow): ScalingRecommendation {
|
|
48
|
+
const storyPoints = getStoryPointsByTeam(this.db, team.id);
|
|
49
|
+
const agents = getAgentsByTeam(this.db, team.id);
|
|
50
|
+
const activeSeniors = agents.filter(
|
|
51
|
+
a => a.type === 'senior' && a.status !== 'terminated'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const seniorCapacity = this.config.scaling.senior_capacity;
|
|
55
|
+
const recommendedSeniors = Math.max(1, Math.ceil(storyPoints / seniorCapacity));
|
|
56
|
+
const currentSeniors = activeSeniors.length;
|
|
57
|
+
|
|
58
|
+
let action: ScalingRecommendation['action'] = 'none';
|
|
59
|
+
let reason = '';
|
|
60
|
+
|
|
61
|
+
if (recommendedSeniors > currentSeniors) {
|
|
62
|
+
action = 'scale_up';
|
|
63
|
+
reason = `${storyPoints} story points exceeds capacity of ${currentSeniors} senior(s) (${currentSeniors * seniorCapacity} points)`;
|
|
64
|
+
} else if (recommendedSeniors < currentSeniors && currentSeniors > 1) {
|
|
65
|
+
action = 'scale_down';
|
|
66
|
+
reason = `Only ${storyPoints} story points, can be handled by ${recommendedSeniors} senior(s)`;
|
|
67
|
+
} else {
|
|
68
|
+
reason = `Current capacity is appropriate for ${storyPoints} story points`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
teamId: team.id,
|
|
73
|
+
teamName: team.name,
|
|
74
|
+
currentSeniors,
|
|
75
|
+
recommendedSeniors,
|
|
76
|
+
action,
|
|
77
|
+
reason,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Scale down idle agents when workload decreases
|
|
83
|
+
*/
|
|
84
|
+
async scaleDown(): Promise<number> {
|
|
85
|
+
const teams = getAllTeams(this.db);
|
|
86
|
+
let terminated = 0;
|
|
87
|
+
|
|
88
|
+
for (const team of teams) {
|
|
89
|
+
const storyPoints = getStoryPointsByTeam(this.db, team.id);
|
|
90
|
+
const agents = getAgentsByTeam(this.db, team.id);
|
|
91
|
+
|
|
92
|
+
// Only scale down if no active work
|
|
93
|
+
if (storyPoints > 0) continue;
|
|
94
|
+
|
|
95
|
+
// Find idle agents (except the first Senior)
|
|
96
|
+
const idleAgents = agents.filter(a =>
|
|
97
|
+
a.status === 'idle' &&
|
|
98
|
+
a.type !== 'tech_lead' &&
|
|
99
|
+
!a.current_story_id
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Keep at least one Senior
|
|
103
|
+
const seniorsToKeep = agents.filter(a => a.type === 'senior' && a.status !== 'terminated')[0];
|
|
104
|
+
|
|
105
|
+
for (const agent of idleAgents) {
|
|
106
|
+
if (agent.type === 'senior' && agent.id === seniorsToKeep?.id) {
|
|
107
|
+
continue; // Keep the primary Senior
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await this.terminateAgent(agent);
|
|
111
|
+
terminated++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return terminated;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private async terminateAgent(agent: AgentRow): Promise<void> {
|
|
119
|
+
// Kill tmux session if exists
|
|
120
|
+
if (agent.tmux_session) {
|
|
121
|
+
await killTmuxSession(agent.tmux_session);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Remove worktree if exists
|
|
125
|
+
if (agent.worktree_path) {
|
|
126
|
+
try {
|
|
127
|
+
const { execSync } = await import('child_process');
|
|
128
|
+
const fullWorktreePath = `${this.config.rootDir}/${agent.worktree_path}`;
|
|
129
|
+
execSync(`git worktree remove "${fullWorktreePath}" --force`, {
|
|
130
|
+
cwd: this.config.rootDir,
|
|
131
|
+
stdio: 'pipe',
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// Log error but don't throw - worktree might already be removed
|
|
135
|
+
console.error(`Warning: Failed to remove worktree: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Mark as terminated
|
|
140
|
+
terminateAgent(this.db, agent.id);
|
|
141
|
+
|
|
142
|
+
// Log the event
|
|
143
|
+
createLog(this.db, {
|
|
144
|
+
agentId: agent.id,
|
|
145
|
+
eventType: 'AGENT_TERMINATED',
|
|
146
|
+
message: 'Scaled down due to reduced workload',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get scaling statistics
|
|
152
|
+
*/
|
|
153
|
+
getStatistics(): {
|
|
154
|
+
totalAgents: number;
|
|
155
|
+
activeAgents: number;
|
|
156
|
+
agentsByType: Record<string, number>;
|
|
157
|
+
teamCapacity: Record<string, { agents: number; storyPoints: number }>;
|
|
158
|
+
} {
|
|
159
|
+
const teams = getAllTeams(this.db);
|
|
160
|
+
const agentsByType: Record<string, number> = {};
|
|
161
|
+
const teamCapacity: Record<string, { agents: number; storyPoints: number }> = {};
|
|
162
|
+
let totalAgents = 0;
|
|
163
|
+
let activeAgents = 0;
|
|
164
|
+
|
|
165
|
+
for (const team of teams) {
|
|
166
|
+
const agents = getAgentsByTeam(this.db, team.id);
|
|
167
|
+
const storyPoints = getStoryPointsByTeam(this.db, team.id);
|
|
168
|
+
|
|
169
|
+
teamCapacity[team.name] = {
|
|
170
|
+
agents: agents.filter(a => a.status !== 'terminated').length,
|
|
171
|
+
storyPoints,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
for (const agent of agents) {
|
|
175
|
+
if (agent.status === 'terminated') continue;
|
|
176
|
+
totalAgents++;
|
|
177
|
+
agentsByType[agent.type] = (agentsByType[agent.type] || 0) + 1;
|
|
178
|
+
if (agent.status === 'working') {
|
|
179
|
+
activeAgents++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add Tech Lead
|
|
185
|
+
const techLead = getTechLead(this.db);
|
|
186
|
+
if (techLead && techLead.status !== 'terminated') {
|
|
187
|
+
totalAgents++;
|
|
188
|
+
agentsByType['tech_lead'] = 1;
|
|
189
|
+
if (techLead.status === 'working') {
|
|
190
|
+
activeAgents++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
totalAgents,
|
|
196
|
+
activeAgents,
|
|
197
|
+
agentsByType,
|
|
198
|
+
teamCapacity,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import type { Database } from 'sql.js';
|
|
3
|
+
import initSqlJs from 'sql.js';
|
|
4
|
+
import { Scheduler } from './scheduler.js';
|
|
5
|
+
import { createStory, addStoryDependency, updateStory } from '../db/queries/stories.js';
|
|
6
|
+
import { createTeam } from '../db/queries/teams.js';
|
|
7
|
+
import type { StoryRow } from '../db/queries/stories.js';
|
|
8
|
+
|
|
9
|
+
let db: Database;
|
|
10
|
+
let scheduler: Scheduler;
|
|
11
|
+
|
|
12
|
+
const mockConfig = {
|
|
13
|
+
scaling: {
|
|
14
|
+
junior_max_complexity: 3,
|
|
15
|
+
intermediate_max_complexity: 5,
|
|
16
|
+
senior_capacity: 50,
|
|
17
|
+
},
|
|
18
|
+
models: {
|
|
19
|
+
tech_lead: { provider: 'anthropic', model: 'claude-opus-4-20250514', max_tokens: 16000, temperature: 0.7, cli_tool: 'claude' },
|
|
20
|
+
senior: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', max_tokens: 8000, temperature: 0.5, cli_tool: 'claude' },
|
|
21
|
+
intermediate: { provider: 'anthropic', model: 'claude-haiku-3-5-20241022', max_tokens: 4000, temperature: 0.3, cli_tool: 'claude' },
|
|
22
|
+
junior: { provider: 'openai', model: 'gpt-4o-mini', max_tokens: 4000, temperature: 0.2, cli_tool: 'claude' },
|
|
23
|
+
qa: { provider: 'anthropic', model: 'claude-sonnet-4-20250514', max_tokens: 8000, temperature: 0.2, cli_tool: 'claude' },
|
|
24
|
+
},
|
|
25
|
+
rootDir: '/tmp',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Migration SQL to initialize database for tests
|
|
29
|
+
const INITIAL_MIGRATION = `
|
|
30
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
name TEXT NOT NULL UNIQUE,
|
|
33
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
repo_url TEXT NOT NULL,
|
|
39
|
+
repo_path TEXT NOT NULL,
|
|
40
|
+
name TEXT NOT NULL,
|
|
41
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
type TEXT NOT NULL CHECK (type IN ('tech_lead', 'senior', 'intermediate', 'junior', 'qa')),
|
|
47
|
+
team_id TEXT REFERENCES teams(id),
|
|
48
|
+
tmux_session TEXT,
|
|
49
|
+
model TEXT,
|
|
50
|
+
status TEXT DEFAULT 'idle' CHECK (status IN ('idle', 'working', 'blocked', 'terminated')),
|
|
51
|
+
current_story_id TEXT,
|
|
52
|
+
memory_state TEXT,
|
|
53
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
54
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS stories (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
requirement_id TEXT,
|
|
60
|
+
team_id TEXT REFERENCES teams(id),
|
|
61
|
+
title TEXT NOT NULL,
|
|
62
|
+
description TEXT NOT NULL,
|
|
63
|
+
acceptance_criteria TEXT,
|
|
64
|
+
complexity_score INTEGER CHECK (complexity_score BETWEEN 1 AND 13),
|
|
65
|
+
story_points INTEGER,
|
|
66
|
+
status TEXT DEFAULT 'draft' CHECK (status IN (
|
|
67
|
+
'draft',
|
|
68
|
+
'estimated',
|
|
69
|
+
'planned',
|
|
70
|
+
'in_progress',
|
|
71
|
+
'review',
|
|
72
|
+
'qa',
|
|
73
|
+
'qa_failed',
|
|
74
|
+
'pr_submitted',
|
|
75
|
+
'merged'
|
|
76
|
+
)),
|
|
77
|
+
assigned_agent_id TEXT REFERENCES agents(id),
|
|
78
|
+
branch_name TEXT,
|
|
79
|
+
pr_url TEXT,
|
|
80
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
81
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS story_dependencies (
|
|
85
|
+
story_id TEXT REFERENCES stories(id),
|
|
86
|
+
depends_on_story_id TEXT REFERENCES stories(id),
|
|
87
|
+
PRIMARY KEY (story_id, depends_on_story_id)
|
|
88
|
+
);
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
const SQL = await initSqlJs();
|
|
93
|
+
db = new SQL.Database();
|
|
94
|
+
db.run('PRAGMA foreign_keys = ON');
|
|
95
|
+
db.run(INITIAL_MIGRATION);
|
|
96
|
+
db.run("INSERT INTO migrations (name) VALUES ('001-initial.sql')");
|
|
97
|
+
|
|
98
|
+
scheduler = new Scheduler(db, mockConfig as any);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('Scheduler Topological Sort', () => {
|
|
102
|
+
it('should handle stories with no dependencies', () => {
|
|
103
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
104
|
+
const story1 = createStory(db, { teamId: team.id, title: 'Story 1', description: 'Test' });
|
|
105
|
+
const story2 = createStory(db, { teamId: team.id, title: 'Story 2', description: 'Test' });
|
|
106
|
+
|
|
107
|
+
// Mock the private method by accessing it through reflection
|
|
108
|
+
const sortMethod = (scheduler as any).topologicalSort;
|
|
109
|
+
const sorted = sortMethod.call(scheduler, [story1, story2]);
|
|
110
|
+
|
|
111
|
+
expect(sorted).not.toBeNull();
|
|
112
|
+
expect(sorted).toHaveLength(2);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should respect linear dependencies (A -> B -> C)', () => {
|
|
116
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
117
|
+
const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
|
|
118
|
+
const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
|
|
119
|
+
const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
|
|
120
|
+
|
|
121
|
+
// B depends on A, C depends on B
|
|
122
|
+
addStoryDependency(db, storyB.id, storyA.id);
|
|
123
|
+
addStoryDependency(db, storyC.id, storyB.id);
|
|
124
|
+
|
|
125
|
+
const sortMethod = (scheduler as any).topologicalSort;
|
|
126
|
+
const sorted = sortMethod.call(scheduler, [storyC, storyA, storyB]);
|
|
127
|
+
|
|
128
|
+
expect(sorted).not.toBeNull();
|
|
129
|
+
expect(sorted).toHaveLength(3);
|
|
130
|
+
// A should come first, then B, then C
|
|
131
|
+
const ids = sorted!.map((s: StoryRow) => s.id);
|
|
132
|
+
expect(ids.indexOf(storyA.id)).toBeLessThan(ids.indexOf(storyB.id));
|
|
133
|
+
expect(ids.indexOf(storyB.id)).toBeLessThan(ids.indexOf(storyC.id));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should respect diamond dependencies (A -> B, A -> C, B -> D, C -> D)', () => {
|
|
137
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
138
|
+
const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
|
|
139
|
+
const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
|
|
140
|
+
const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
|
|
141
|
+
const storyD = createStory(db, { teamId: team.id, title: 'Story D', description: 'Test' });
|
|
142
|
+
|
|
143
|
+
// B and C depend on A, D depends on both B and C
|
|
144
|
+
addStoryDependency(db, storyB.id, storyA.id);
|
|
145
|
+
addStoryDependency(db, storyC.id, storyA.id);
|
|
146
|
+
addStoryDependency(db, storyD.id, storyB.id);
|
|
147
|
+
addStoryDependency(db, storyD.id, storyC.id);
|
|
148
|
+
|
|
149
|
+
const sortMethod = (scheduler as any).topologicalSort;
|
|
150
|
+
const sorted = sortMethod.call(scheduler, [storyD, storyB, storyA, storyC]);
|
|
151
|
+
|
|
152
|
+
expect(sorted).not.toBeNull();
|
|
153
|
+
expect(sorted).toHaveLength(4);
|
|
154
|
+
const ids = sorted!.map((s: StoryRow) => s.id);
|
|
155
|
+
|
|
156
|
+
// A should come first
|
|
157
|
+
expect(ids.indexOf(storyA.id)).toBe(0);
|
|
158
|
+
// D should come last
|
|
159
|
+
expect(ids.indexOf(storyD.id)).toBe(3);
|
|
160
|
+
// B and C should come between A and D
|
|
161
|
+
expect(ids.indexOf(storyB.id)).toBeGreaterThan(ids.indexOf(storyA.id));
|
|
162
|
+
expect(ids.indexOf(storyC.id)).toBeGreaterThan(ids.indexOf(storyA.id));
|
|
163
|
+
expect(ids.indexOf(storyB.id)).toBeLessThan(ids.indexOf(storyD.id));
|
|
164
|
+
expect(ids.indexOf(storyC.id)).toBeLessThan(ids.indexOf(storyD.id));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should detect circular dependencies', () => {
|
|
168
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
169
|
+
const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
|
|
170
|
+
const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
|
|
171
|
+
|
|
172
|
+
// Create circular dependency: A -> B -> A
|
|
173
|
+
addStoryDependency(db, storyB.id, storyA.id);
|
|
174
|
+
addStoryDependency(db, storyA.id, storyB.id);
|
|
175
|
+
|
|
176
|
+
const sortMethod = (scheduler as any).topologicalSort;
|
|
177
|
+
const sorted = sortMethod.call(scheduler, [storyA, storyB]);
|
|
178
|
+
|
|
179
|
+
expect(sorted).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Scheduler Dependency Satisfaction', () => {
|
|
184
|
+
it('should consider merged stories as satisfying dependencies', () => {
|
|
185
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
186
|
+
const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
|
|
187
|
+
const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
|
|
188
|
+
|
|
189
|
+
addStoryDependency(db, mainStory.id, depStory.id);
|
|
190
|
+
|
|
191
|
+
// Initially, dependencies are not satisfied
|
|
192
|
+
let isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
193
|
+
expect(isSatisfied).toBe(false);
|
|
194
|
+
|
|
195
|
+
// Mark dependency as merged
|
|
196
|
+
updateStory(db, depStory.id, { status: 'merged' });
|
|
197
|
+
isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
198
|
+
expect(isSatisfied).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should consider in-progress stories as satisfying dependencies', () => {
|
|
202
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
203
|
+
const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
|
|
204
|
+
const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
|
|
205
|
+
|
|
206
|
+
addStoryDependency(db, mainStory.id, depStory.id);
|
|
207
|
+
|
|
208
|
+
// Mark dependency as in_progress
|
|
209
|
+
updateStory(db, depStory.id, { status: 'in_progress' });
|
|
210
|
+
const isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
211
|
+
expect(isSatisfied).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should not consider planned stories as satisfying dependencies', () => {
|
|
215
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
216
|
+
const depStory = createStory(db, { teamId: team.id, title: 'Dependency', description: 'Test' });
|
|
217
|
+
const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
|
|
218
|
+
|
|
219
|
+
addStoryDependency(db, mainStory.id, depStory.id);
|
|
220
|
+
|
|
221
|
+
// Update main story status to planned (default)
|
|
222
|
+
updateStory(db, mainStory.id, { status: 'planned' });
|
|
223
|
+
|
|
224
|
+
const isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
225
|
+
expect(isSatisfied).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should handle multiple dependencies', () => {
|
|
229
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
230
|
+
const dep1 = createStory(db, { teamId: team.id, title: 'Dep 1', description: 'Test' });
|
|
231
|
+
const dep2 = createStory(db, { teamId: team.id, title: 'Dep 2', description: 'Test' });
|
|
232
|
+
const mainStory = createStory(db, { teamId: team.id, title: 'Main Story', description: 'Test' });
|
|
233
|
+
|
|
234
|
+
addStoryDependency(db, mainStory.id, dep1.id);
|
|
235
|
+
addStoryDependency(db, mainStory.id, dep2.id);
|
|
236
|
+
|
|
237
|
+
// Mark only first dependency as merged
|
|
238
|
+
updateStory(db, dep1.id, { status: 'merged' });
|
|
239
|
+
let isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
240
|
+
expect(isSatisfied).toBe(false);
|
|
241
|
+
|
|
242
|
+
// Mark second dependency as merged too
|
|
243
|
+
updateStory(db, dep2.id, { status: 'merged' });
|
|
244
|
+
isSatisfied = (scheduler as any).areDependenciesSatisfied.call(scheduler, mainStory.id);
|
|
245
|
+
expect(isSatisfied).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('Scheduler Build Dependency Graph', () => {
|
|
250
|
+
it('should correctly build a dependency graph', () => {
|
|
251
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
252
|
+
const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
|
|
253
|
+
const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
|
|
254
|
+
const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
|
|
255
|
+
|
|
256
|
+
addStoryDependency(db, storyB.id, storyA.id);
|
|
257
|
+
addStoryDependency(db, storyC.id, storyA.id);
|
|
258
|
+
|
|
259
|
+
const graphMethod = (scheduler as any).buildDependencyGraph;
|
|
260
|
+
const graph = graphMethod.call(scheduler, [storyA, storyB, storyC]);
|
|
261
|
+
|
|
262
|
+
expect(graph.has(storyA.id)).toBe(true);
|
|
263
|
+
expect(graph.has(storyB.id)).toBe(true);
|
|
264
|
+
expect(graph.has(storyC.id)).toBe(true);
|
|
265
|
+
|
|
266
|
+
expect(graph.get(storyA.id)).toEqual(new Set());
|
|
267
|
+
expect(graph.get(storyB.id)).toEqual(new Set([storyA.id]));
|
|
268
|
+
expect(graph.get(storyC.id)).toEqual(new Set([storyA.id]));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should include only stories in the input list', () => {
|
|
272
|
+
const team = createTeam(db, { name: 'Test Team', repoUrl: 'https://github.com/test/repo', repoPath: 'test' });
|
|
273
|
+
const storyA = createStory(db, { teamId: team.id, title: 'Story A', description: 'Test' });
|
|
274
|
+
const storyB = createStory(db, { teamId: team.id, title: 'Story B', description: 'Test' });
|
|
275
|
+
const storyC = createStory(db, { teamId: team.id, title: 'Story C', description: 'Test' });
|
|
276
|
+
|
|
277
|
+
// B depends on A (A is not in the filter list)
|
|
278
|
+
addStoryDependency(db, storyB.id, storyA.id);
|
|
279
|
+
|
|
280
|
+
const graphMethod = (scheduler as any).buildDependencyGraph;
|
|
281
|
+
// Only include B and C in the graph
|
|
282
|
+
const graph = graphMethod.call(scheduler, [storyB, storyC]);
|
|
283
|
+
|
|
284
|
+
expect(graph.has(storyB.id)).toBe(true);
|
|
285
|
+
expect(graph.has(storyC.id)).toBe(true);
|
|
286
|
+
expect(graph.has(storyA.id)).toBe(false);
|
|
287
|
+
});
|
|
288
|
+
});
|