hungry-ghost-hive 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/base-agent.d.ts +11 -11
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +25 -25
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/base-agent.test.js +2 -1
- package/dist/agents/base-agent.test.js.map +1 -1
- package/dist/agents/intermediate.d.ts +2 -0
- package/dist/agents/intermediate.d.ts.map +1 -1
- package/dist/agents/intermediate.js +25 -18
- package/dist/agents/intermediate.js.map +1 -1
- package/dist/agents/junior.d.ts +2 -0
- package/dist/agents/junior.d.ts.map +1 -1
- package/dist/agents/junior.js +25 -18
- package/dist/agents/junior.js.map +1 -1
- package/dist/agents/qa.d.ts +2 -0
- package/dist/agents/qa.d.ts.map +1 -1
- package/dist/agents/qa.js +47 -38
- package/dist/agents/qa.js.map +1 -1
- package/dist/agents/senior.d.ts +2 -0
- package/dist/agents/senior.d.ts.map +1 -1
- package/dist/agents/senior.js +40 -27
- package/dist/agents/senior.js.map +1 -1
- package/dist/agents/tech-lead.d.ts +2 -0
- package/dist/agents/tech-lead.d.ts.map +1 -1
- package/dist/agents/tech-lead.js +37 -31
- package/dist/agents/tech-lead.js.map +1 -1
- package/dist/cli/commands/add-repo.js +2 -2
- package/dist/cli/commands/add-repo.js.map +1 -1
- package/dist/cli/commands/add-repo.test.js +1 -1
- package/dist/cli/commands/add-repo.test.js.map +1 -1
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +12 -10
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/agents.test.js +7 -7
- package/dist/cli/commands/agents.test.js.map +1 -1
- package/dist/cli/commands/approach.js +2 -2
- package/dist/cli/commands/approach.js.map +1 -1
- package/dist/cli/commands/approvals.js +7 -7
- package/dist/cli/commands/approvals.js.map +1 -1
- package/dist/cli/commands/approvals.test.js +8 -8
- package/dist/cli/commands/approvals.test.js.map +1 -1
- package/dist/cli/commands/assign.js +4 -4
- package/dist/cli/commands/assign.js.map +1 -1
- package/dist/cli/commands/assign.test.js +18 -16
- package/dist/cli/commands/assign.test.js.map +1 -1
- package/dist/cli/commands/cleanup.d.ts.map +1 -1
- package/dist/cli/commands/cleanup.js +8 -8
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/cleanup.test.js +5 -1
- package/dist/cli/commands/cleanup.test.js.map +1 -1
- package/dist/cli/commands/escalations.js +9 -7
- package/dist/cli/commands/escalations.js.map +1 -1
- package/dist/cli/commands/escalations.test.js +2 -2
- package/dist/cli/commands/escalations.test.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +48 -5
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +4 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/commands/manager/agent-monitoring.d.ts +2 -2
- package/dist/cli/commands/manager/agent-monitoring.d.ts.map +1 -1
- package/dist/cli/commands/manager/agent-monitoring.js +1 -1
- package/dist/cli/commands/manager/agent-monitoring.js.map +1 -1
- package/dist/cli/commands/manager/auditor-lifecycle.js +3 -3
- package/dist/cli/commands/manager/auditor-lifecycle.js.map +1 -1
- package/dist/cli/commands/manager/auditor-lifecycle.test.js +21 -14
- package/dist/cli/commands/manager/auditor-lifecycle.test.js.map +1 -1
- package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.js +28 -23
- package/dist/cli/commands/manager/auto-reject-comment-only-reviews.test.js.map +1 -1
- package/dist/cli/commands/manager/escalation-handler.d.ts +2 -2
- package/dist/cli/commands/manager/escalation-handler.d.ts.map +1 -1
- package/dist/cli/commands/manager/escalation-handler.js +11 -10
- package/dist/cli/commands/manager/escalation-handler.js.map +1 -1
- package/dist/cli/commands/manager/escalation-handler.test.js +8 -8
- package/dist/cli/commands/manager/escalation-handler.test.js.map +1 -1
- package/dist/cli/commands/manager/feature-sign-off.js +7 -7
- package/dist/cli/commands/manager/feature-sign-off.js.map +1 -1
- package/dist/cli/commands/manager/feature-sign-off.test.js +40 -31
- package/dist/cli/commands/manager/feature-sign-off.test.js.map +1 -1
- package/dist/cli/commands/manager/feature-test-result.d.ts.map +1 -1
- package/dist/cli/commands/manager/feature-test-result.js +12 -13
- package/dist/cli/commands/manager/feature-test-result.js.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.js +14 -15
- package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
- package/dist/cli/commands/manager/index.d.ts.map +1 -1
- package/dist/cli/commands/manager/index.js +26 -26
- package/dist/cli/commands/manager/index.js.map +1 -1
- package/dist/cli/commands/manager/index.test.js +3 -3
- package/dist/cli/commands/manager/index.test.js.map +1 -1
- package/dist/cli/commands/manager/merged-story-cleanup.d.ts +2 -2
- package/dist/cli/commands/manager/merged-story-cleanup.d.ts.map +1 -1
- package/dist/cli/commands/manager/merged-story-cleanup.js +6 -7
- package/dist/cli/commands/manager/merged-story-cleanup.js.map +1 -1
- package/dist/cli/commands/manager/merged-story-cleanup.test.js +27 -18
- package/dist/cli/commands/manager/merged-story-cleanup.test.js.map +1 -1
- package/dist/cli/commands/manager/pr-sync-orchestrator.d.ts.map +1 -1
- package/dist/cli/commands/manager/pr-sync-orchestrator.js +46 -38
- package/dist/cli/commands/manager/pr-sync-orchestrator.js.map +1 -1
- package/dist/cli/commands/manager/qa-review-handler.d.ts.map +1 -1
- package/dist/cli/commands/manager/qa-review-handler.js +25 -22
- package/dist/cli/commands/manager/qa-review-handler.js.map +1 -1
- package/dist/cli/commands/manager/spin-down.d.ts.map +1 -1
- package/dist/cli/commands/manager/spin-down.js +23 -19
- package/dist/cli/commands/manager/spin-down.js.map +1 -1
- package/dist/cli/commands/manager/stale-escalations.d.ts +2 -3
- package/dist/cli/commands/manager/stale-escalations.d.ts.map +1 -1
- package/dist/cli/commands/manager/stale-escalations.js.map +1 -1
- package/dist/cli/commands/manager/stuck-story-helpers.js +8 -8
- package/dist/cli/commands/manager/stuck-story-helpers.js.map +1 -1
- package/dist/cli/commands/manager/stuck-story-processor.d.ts +2 -2
- package/dist/cli/commands/manager/stuck-story-processor.d.ts.map +1 -1
- package/dist/cli/commands/manager/stuck-story-processor.js +23 -22
- package/dist/cli/commands/manager/stuck-story-processor.js.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.js +6 -6
- package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
- package/dist/cli/commands/manager/types.d.ts +2 -3
- package/dist/cli/commands/manager/types.d.ts.map +1 -1
- package/dist/cli/commands/manager/types.js.map +1 -1
- package/dist/cli/commands/msg.test.js +2 -2
- package/dist/cli/commands/msg.test.js.map +1 -1
- package/dist/cli/commands/my-stories.d.ts.map +1 -1
- package/dist/cli/commands/my-stories.js +17 -18
- package/dist/cli/commands/my-stories.js.map +1 -1
- package/dist/cli/commands/my-stories.test.js +2 -2
- package/dist/cli/commands/my-stories.test.js.map +1 -1
- package/dist/cli/commands/nuke.test.js +1 -1
- package/dist/cli/commands/nuke.test.js.map +1 -1
- package/dist/cli/commands/pr.js +32 -32
- package/dist/cli/commands/pr.js.map +1 -1
- package/dist/cli/commands/pr.test.js +10 -6
- package/dist/cli/commands/pr.test.js.map +1 -1
- package/dist/cli/commands/progress.d.ts.map +1 -1
- package/dist/cli/commands/progress.js +4 -5
- package/dist/cli/commands/progress.js.map +1 -1
- package/dist/cli/commands/progress.test.js +1 -1
- package/dist/cli/commands/progress.test.js.map +1 -1
- package/dist/cli/commands/req-headless.test.d.ts +2 -0
- package/dist/cli/commands/req-headless.test.d.ts.map +1 -0
- package/dist/cli/commands/req-headless.test.js +128 -0
- package/dist/cli/commands/req-headless.test.js.map +1 -0
- package/dist/cli/commands/req-spawn.test.js +5 -1
- package/dist/cli/commands/req-spawn.test.js.map +1 -1
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +13 -14
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +7 -8
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/resume.test.js +1 -1
- package/dist/cli/commands/resume.test.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +42 -40
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/status.test.js +1 -1
- package/dist/cli/commands/status.test.js.map +1 -1
- package/dist/cli/commands/stories.js +9 -9
- package/dist/cli/commands/stories.js.map +1 -1
- package/dist/cli/commands/stories.test.js +2 -2
- package/dist/cli/commands/stories.test.js.map +1 -1
- package/dist/cli/commands/teams.js +11 -11
- package/dist/cli/commands/teams.js.map +1 -1
- package/dist/cli/commands/teams.test.js +2 -2
- package/dist/cli/commands/teams.test.js.map +1 -1
- package/dist/cli/dashboard/index.d.ts +2 -2
- package/dist/cli/dashboard/index.d.ts.map +1 -1
- package/dist/cli/dashboard/index.js +29 -20
- package/dist/cli/dashboard/index.js.map +1 -1
- package/dist/cli/dashboard/index.test.js +34 -32
- package/dist/cli/dashboard/index.test.js.map +1 -1
- package/dist/cli/dashboard/panels/activity.d.ts +3 -3
- package/dist/cli/dashboard/panels/activity.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/activity.js +1 -1
- package/dist/cli/dashboard/panels/activity.js.map +1 -1
- package/dist/cli/dashboard/panels/agents.d.ts +3 -3
- package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/agents.js +2 -2
- package/dist/cli/dashboard/panels/agents.js.map +1 -1
- package/dist/cli/dashboard/panels/escalations.d.ts +3 -3
- package/dist/cli/dashboard/panels/escalations.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/escalations.js +1 -1
- package/dist/cli/dashboard/panels/escalations.js.map +1 -1
- package/dist/cli/dashboard/panels/merge-queue.d.ts +3 -3
- package/dist/cli/dashboard/panels/merge-queue.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/merge-queue.js +1 -1
- package/dist/cli/dashboard/panels/merge-queue.js.map +1 -1
- package/dist/cli/dashboard/panels/pipeline.d.ts +3 -3
- package/dist/cli/dashboard/panels/pipeline.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/pipeline.js +1 -1
- package/dist/cli/dashboard/panels/pipeline.js.map +1 -1
- package/dist/config/schema.d.ts +85 -82
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/connectors/project-management/operations.d.ts +7 -7
- package/dist/connectors/project-management/operations.d.ts.map +1 -1
- package/dist/connectors/project-management/operations.js +2 -3
- package/dist/connectors/project-management/operations.js.map +1 -1
- package/dist/context-files/index.test.js +1 -0
- package/dist/context-files/index.test.js.map +1 -1
- package/dist/db/client.d.ts +6 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/postgres-provider.d.ts +43 -0
- package/dist/db/postgres-provider.d.ts.map +1 -0
- package/dist/db/postgres-provider.integration.test.d.ts +2 -0
- package/dist/db/postgres-provider.integration.test.d.ts.map +1 -0
- package/dist/db/postgres-provider.integration.test.js +399 -0
- package/dist/db/postgres-provider.integration.test.js.map +1 -0
- package/dist/db/postgres-provider.js +315 -0
- package/dist/db/postgres-provider.js.map +1 -0
- package/dist/db/postgres-provider.test.d.ts +2 -0
- package/dist/db/postgres-provider.test.d.ts.map +1 -0
- package/dist/db/postgres-provider.test.js +72 -0
- package/dist/db/postgres-provider.test.js.map +1 -0
- package/dist/db/provider.d.ts +59 -0
- package/dist/db/provider.d.ts.map +1 -0
- package/dist/db/provider.js +121 -0
- package/dist/db/provider.js.map +1 -0
- package/dist/db/provider.test.d.ts +2 -0
- package/dist/db/provider.test.d.ts.map +1 -0
- package/dist/db/provider.test.js +226 -0
- package/dist/db/provider.test.js.map +1 -0
- package/dist/db/queries/agents.d.ts +13 -13
- package/dist/db/queries/agents.d.ts.map +1 -1
- package/dist/db/queries/agents.js +27 -28
- package/dist/db/queries/agents.js.map +1 -1
- package/dist/db/queries/agents.test.js +113 -111
- package/dist/db/queries/agents.test.js.map +1 -1
- package/dist/db/queries/escalations.d.ts +16 -16
- package/dist/db/queries/escalations.d.ts.map +1 -1
- package/dist/db/queries/escalations.js +34 -35
- package/dist/db/queries/escalations.js.map +1 -1
- package/dist/db/queries/escalations.test.js +133 -131
- package/dist/db/queries/escalations.test.js.map +1 -1
- package/dist/db/queries/heartbeat.d.ts +5 -5
- package/dist/db/queries/heartbeat.d.ts.map +1 -1
- package/dist/db/queries/heartbeat.js +7 -23
- package/dist/db/queries/heartbeat.js.map +1 -1
- package/dist/db/queries/heartbeat.test.js +76 -76
- package/dist/db/queries/heartbeat.test.js.map +1 -1
- package/dist/db/queries/integration-sync.d.ts +7 -7
- package/dist/db/queries/integration-sync.d.ts.map +1 -1
- package/dist/db/queries/integration-sync.js +13 -14
- package/dist/db/queries/integration-sync.js.map +1 -1
- package/dist/db/queries/logs.d.ts +10 -10
- package/dist/db/queries/logs.d.ts.map +1 -1
- package/dist/db/queries/logs.js +44 -42
- package/dist/db/queries/logs.js.map +1 -1
- package/dist/db/queries/logs.test.js +149 -146
- package/dist/db/queries/logs.test.js.map +1 -1
- package/dist/db/queries/messages.d.ts +6 -6
- package/dist/db/queries/messages.d.ts.map +1 -1
- package/dist/db/queries/messages.js +12 -11
- package/dist/db/queries/messages.js.map +1 -1
- package/dist/db/queries/messages.test.js +47 -46
- package/dist/db/queries/messages.test.js.map +1 -1
- package/dist/db/queries/pull-requests.d.ts +18 -18
- package/dist/db/queries/pull-requests.d.ts.map +1 -1
- package/dist/db/queries/pull-requests.js +50 -48
- package/dist/db/queries/pull-requests.js.map +1 -1
- package/dist/db/queries/pull-requests.test.js +195 -198
- package/dist/db/queries/pull-requests.test.js.map +1 -1
- package/dist/db/queries/requirements.d.ts +8 -8
- package/dist/db/queries/requirements.d.ts.map +1 -1
- package/dist/db/queries/requirements.js +17 -18
- package/dist/db/queries/requirements.js.map +1 -1
- package/dist/db/queries/requirements.test.js +83 -81
- package/dist/db/queries/requirements.test.js.map +1 -1
- package/dist/db/queries/stories.d.ts +29 -29
- package/dist/db/queries/stories.d.ts.map +1 -1
- package/dist/db/queries/stories.js +58 -64
- package/dist/db/queries/stories.js.map +1 -1
- package/dist/db/queries/stories.test.js +172 -170
- package/dist/db/queries/stories.test.js.map +1 -1
- package/dist/db/queries/teams.d.ts +6 -6
- package/dist/db/queries/teams.d.ts.map +1 -1
- package/dist/db/queries/teams.js +11 -12
- package/dist/db/queries/teams.js.map +1 -1
- package/dist/db/queries/teams.test.js +36 -34
- package/dist/db/queries/teams.test.js.map +1 -1
- package/dist/integrations/jira/repair.test.js +26 -24
- package/dist/integrations/jira/repair.test.js.map +1 -1
- package/dist/integrations/jira/stories.d.ts +3 -3
- package/dist/integrations/jira/stories.d.ts.map +1 -1
- package/dist/integrations/jira/stories.js +12 -12
- package/dist/integrations/jira/stories.js.map +1 -1
- package/dist/integrations/jira/stories.test.js +10 -8
- package/dist/integrations/jira/stories.test.js.map +1 -1
- package/dist/integrations/jira/sync.d.ts +7 -7
- package/dist/integrations/jira/sync.d.ts.map +1 -1
- package/dist/integrations/jira/sync.js +17 -20
- package/dist/integrations/jira/sync.js.map +1 -1
- package/dist/integrations/jira/sync.test.js +63 -62
- package/dist/integrations/jira/sync.test.js.map +1 -1
- package/dist/integrations/jira/transitions.d.ts +3 -3
- package/dist/integrations/jira/transitions.d.ts.map +1 -1
- package/dist/integrations/jira/transitions.js +3 -3
- package/dist/integrations/jira/transitions.js.map +1 -1
- package/dist/orchestrator/agent-selector.d.ts +3 -3
- package/dist/orchestrator/agent-selector.d.ts.map +1 -1
- package/dist/orchestrator/agent-selector.js +5 -6
- package/dist/orchestrator/agent-selector.js.map +1 -1
- package/dist/orchestrator/dependency-resolver.d.ts +4 -4
- package/dist/orchestrator/dependency-resolver.d.ts.map +1 -1
- package/dist/orchestrator/dependency-resolver.js +6 -6
- package/dist/orchestrator/dependency-resolver.js.map +1 -1
- package/dist/orchestrator/feature-branch.d.ts +3 -3
- package/dist/orchestrator/feature-branch.d.ts.map +1 -1
- package/dist/orchestrator/feature-branch.js +9 -10
- package/dist/orchestrator/feature-branch.js.map +1 -1
- package/dist/orchestrator/feature-branch.test.js +80 -78
- package/dist/orchestrator/feature-branch.test.js.map +1 -1
- package/dist/orchestrator/orphan-recovery.d.ts +2 -2
- package/dist/orchestrator/orphan-recovery.d.ts.map +1 -1
- package/dist/orchestrator/orphan-recovery.js +10 -10
- package/dist/orchestrator/orphan-recovery.js.map +1 -1
- package/dist/orchestrator/scheduler.d.ts +4 -4
- package/dist/orchestrator/scheduler.d.ts.map +1 -1
- package/dist/orchestrator/scheduler.js +90 -76
- package/dist/orchestrator/scheduler.js.map +1 -1
- package/dist/orchestrator/scheduler.test.js +496 -374
- package/dist/orchestrator/scheduler.test.js.map +1 -1
- package/dist/utils/auto-merge.d.ts.map +1 -1
- package/dist/utils/auto-merge.js +74 -56
- package/dist/utils/auto-merge.js.map +1 -1
- package/dist/utils/auto-merge.test.js +101 -66
- package/dist/utils/auto-merge.test.js.map +1 -1
- package/dist/utils/cli-helpers.d.ts +5 -5
- package/dist/utils/cli-helpers.d.ts.map +1 -1
- package/dist/utils/cli-helpers.js +8 -9
- package/dist/utils/cli-helpers.js.map +1 -1
- package/dist/utils/cli-helpers.test.js +28 -30
- package/dist/utils/cli-helpers.test.js.map +1 -1
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +12 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/paths.test.js +1 -0
- package/dist/utils/paths.test.js.map +1 -1
- package/dist/utils/pr-sync.d.ts +10 -10
- package/dist/utils/pr-sync.d.ts.map +1 -1
- package/dist/utils/pr-sync.js +20 -21
- package/dist/utils/pr-sync.js.map +1 -1
- package/dist/utils/pr-sync.test.js +52 -50
- package/dist/utils/pr-sync.test.js.map +1 -1
- package/dist/utils/with-hive-context.d.ts.map +1 -1
- package/dist/utils/with-hive-context.js +70 -1
- package/dist/utils/with-hive-context.js.map +1 -1
- package/package.json +3 -1
- package/src/agents/base-agent.test.ts +2 -1
- package/src/agents/base-agent.ts +32 -28
- package/src/agents/intermediate.ts +27 -18
- package/src/agents/junior.ts +27 -18
- package/src/agents/qa.ts +54 -40
- package/src/agents/senior.ts +42 -27
- package/src/agents/tech-lead.ts +42 -32
- package/src/cli/commands/add-repo.test.ts +1 -1
- package/src/cli/commands/add-repo.ts +2 -2
- package/src/cli/commands/agents.test.ts +7 -7
- package/src/cli/commands/agents.ts +12 -10
- package/src/cli/commands/approach.ts +2 -2
- package/src/cli/commands/approvals.test.ts +8 -8
- package/src/cli/commands/approvals.ts +9 -7
- package/src/cli/commands/assign.test.ts +19 -18
- package/src/cli/commands/assign.ts +4 -4
- package/src/cli/commands/cleanup.test.ts +5 -1
- package/src/cli/commands/cleanup.ts +11 -9
- package/src/cli/commands/escalations.test.ts +2 -2
- package/src/cli/commands/escalations.ts +9 -7
- package/src/cli/commands/init.test.ts +5 -0
- package/src/cli/commands/init.ts +53 -5
- package/src/cli/commands/manager/agent-monitoring.ts +3 -3
- package/src/cli/commands/manager/auditor-lifecycle.test.ts +21 -14
- package/src/cli/commands/manager/auditor-lifecycle.ts +3 -3
- package/src/cli/commands/manager/auto-reject-comment-only-reviews.test.ts +28 -23
- package/src/cli/commands/manager/escalation-handler.test.ts +13 -13
- package/src/cli/commands/manager/escalation-handler.ts +19 -12
- package/src/cli/commands/manager/feature-sign-off.test.ts +40 -31
- package/src/cli/commands/manager/feature-sign-off.ts +7 -7
- package/src/cli/commands/manager/feature-test-result.ts +13 -16
- package/src/cli/commands/manager/handoff-recovery.ts +20 -20
- package/src/cli/commands/manager/index.test.ts +4 -4
- package/src/cli/commands/manager/index.ts +58 -59
- package/src/cli/commands/manager/merged-story-cleanup.test.ts +28 -19
- package/src/cli/commands/manager/merged-story-cleanup.ts +11 -14
- package/src/cli/commands/manager/pr-sync-orchestrator.ts +115 -110
- package/src/cli/commands/manager/qa-review-handler.ts +50 -63
- package/src/cli/commands/manager/spin-down.ts +27 -25
- package/src/cli/commands/manager/stale-escalations.ts +2 -3
- package/src/cli/commands/manager/stuck-story-helpers.ts +10 -10
- package/src/cli/commands/manager/stuck-story-processor.ts +56 -62
- package/src/cli/commands/manager/tech-lead-lifecycle.ts +6 -6
- package/src/cli/commands/manager/types.ts +2 -3
- package/src/cli/commands/msg.test.ts +2 -2
- package/src/cli/commands/my-stories.test.ts +4 -2
- package/src/cli/commands/my-stories.ts +22 -27
- package/src/cli/commands/nuke.test.ts +1 -1
- package/src/cli/commands/pr.test.ts +10 -6
- package/src/cli/commands/pr.ts +41 -32
- package/src/cli/commands/progress.test.ts +1 -1
- package/src/cli/commands/progress.ts +11 -6
- package/src/cli/commands/req-headless.test.ts +170 -0
- package/src/cli/commands/req-spawn.test.ts +12 -2
- package/src/cli/commands/req.ts +13 -14
- package/src/cli/commands/resume.test.ts +1 -1
- package/src/cli/commands/resume.ts +7 -8
- package/src/cli/commands/status.test.ts +1 -1
- package/src/cli/commands/status.ts +52 -40
- package/src/cli/commands/stories.test.ts +4 -2
- package/src/cli/commands/stories.ts +11 -11
- package/src/cli/commands/teams.test.ts +2 -2
- package/src/cli/commands/teams.ts +11 -11
- package/src/cli/dashboard/index.test.ts +35 -34
- package/src/cli/dashboard/index.ts +34 -23
- package/src/cli/dashboard/panels/activity.ts +10 -4
- package/src/cli/dashboard/panels/agents.ts +8 -5
- package/src/cli/dashboard/panels/escalations.ts +4 -4
- package/src/cli/dashboard/panels/merge-queue.ts +4 -4
- package/src/cli/dashboard/panels/pipeline.ts +10 -4
- package/src/config/schema.ts +1 -0
- package/src/connectors/project-management/operations.ts +9 -10
- package/src/context-files/index.test.ts +1 -0
- package/src/db/client.ts +17 -0
- package/src/db/pg-migrations/001-full-schema.sql +209 -0
- package/src/db/postgres-provider.integration.test.ts +574 -0
- package/src/db/postgres-provider.test.ts +97 -0
- package/src/db/postgres-provider.ts +364 -0
- package/src/db/provider.test.ts +283 -0
- package/src/db/provider.ts +161 -0
- package/src/db/queries/agents.test.ts +114 -113
- package/src/db/queries/agents.ts +50 -36
- package/src/db/queries/escalations.test.ts +134 -133
- package/src/db/queries/escalations.ts +72 -57
- package/src/db/queries/heartbeat.test.ts +77 -78
- package/src/db/queries/heartbeat.ts +24 -46
- package/src/db/queries/integration-sync.ts +26 -26
- package/src/db/queries/logs.test.ts +151 -148
- package/src/db/queries/logs.ts +78 -53
- package/src/db/queries/messages.test.ts +48 -50
- package/src/db/queries/messages.ts +26 -18
- package/src/db/queries/pull-requests.test.ts +194 -199
- package/src/db/queries/pull-requests.ts +117 -88
- package/src/db/queries/requirements.test.ts +84 -83
- package/src/db/queries/requirements.ts +33 -28
- package/src/db/queries/stories.test.ts +173 -172
- package/src/db/queries/stories.ts +141 -110
- package/src/db/queries/teams.test.ts +37 -36
- package/src/db/queries/teams.ts +22 -14
- package/src/integrations/jira/repair.test.ts +27 -26
- package/src/integrations/jira/stories.test.ts +15 -16
- package/src/integrations/jira/stories.ts +15 -15
- package/src/integrations/jira/sync.test.ts +68 -68
- package/src/integrations/jira/sync.ts +29 -39
- package/src/integrations/jira/transitions.ts +6 -6
- package/src/orchestrator/agent-selector.ts +9 -8
- package/src/orchestrator/dependency-resolver.ts +16 -7
- package/src/orchestrator/feature-branch.test.ts +85 -80
- package/src/orchestrator/feature-branch.ts +13 -14
- package/src/orchestrator/orphan-recovery.ts +14 -13
- package/src/orchestrator/scheduler.test.ts +536 -394
- package/src/orchestrator/scheduler.ts +129 -115
- package/src/utils/auto-merge.test.ts +102 -68
- package/src/utils/auto-merge.ts +161 -168
- package/src/utils/cli-helpers.test.ts +30 -32
- package/src/utils/cli-helpers.ts +15 -11
- package/src/utils/paths.test.ts +1 -0
- package/src/utils/paths.ts +14 -1
- package/src/utils/pr-sync.test.ts +55 -52
- package/src/utils/pr-sync.ts +27 -32
- package/src/utils/with-hive-context.ts +89 -1
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import type { Database } from 'sql.js';
|
|
7
6
|
import initSqlJs from 'sql.js';
|
|
8
7
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { SqliteProvider } from '../db/provider.js';
|
|
9
9
|
|
|
10
10
|
import { getLogsByEventType } from '../db/queries/logs.js';
|
|
11
11
|
import { createPullRequest } from '../db/queries/pull-requests.js';
|
|
@@ -39,7 +39,7 @@ vi.mock('../git/worktree.js', () => ({
|
|
|
39
39
|
removeWorktree: vi.fn().mockResolvedValue(true),
|
|
40
40
|
}));
|
|
41
41
|
|
|
42
|
-
let db:
|
|
42
|
+
let db: SqliteProvider;
|
|
43
43
|
let scheduler: Scheduler;
|
|
44
44
|
|
|
45
45
|
const mockConfig = {
|
|
@@ -184,46 +184,67 @@ CREATE TABLE IF NOT EXISTS requirements (
|
|
|
184
184
|
|
|
185
185
|
beforeEach(async () => {
|
|
186
186
|
const SQL = await initSqlJs();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
const rawDb = new SQL.Database();
|
|
188
|
+
rawDb.run('PRAGMA foreign_keys = ON');
|
|
189
|
+
rawDb.run(INITIAL_MIGRATION);
|
|
190
|
+
rawDb.run("INSERT INTO migrations (name) VALUES ('001-initial.sql')");
|
|
191
|
+
db = new SqliteProvider(rawDb);
|
|
191
192
|
|
|
192
193
|
scheduler = new Scheduler(db, mockConfig as any);
|
|
193
194
|
});
|
|
194
195
|
|
|
195
196
|
describe('Scheduler Topological Sort', () => {
|
|
196
|
-
it('should handle stories with no dependencies', () => {
|
|
197
|
-
const team = createTeam(db, {
|
|
197
|
+
it('should handle stories with no dependencies', async () => {
|
|
198
|
+
const team = await createTeam(db, {
|
|
198
199
|
name: 'Test Team',
|
|
199
200
|
repoUrl: 'https://github.com/test/repo',
|
|
200
201
|
repoPath: 'test',
|
|
201
202
|
});
|
|
202
|
-
const story1 = createStory(db, {
|
|
203
|
-
|
|
203
|
+
const story1 = await createStory(db, {
|
|
204
|
+
teamId: team.id,
|
|
205
|
+
title: 'Story 1',
|
|
206
|
+
description: 'Test',
|
|
207
|
+
});
|
|
208
|
+
const story2 = await createStory(db, {
|
|
209
|
+
teamId: team.id,
|
|
210
|
+
title: 'Story 2',
|
|
211
|
+
description: 'Test',
|
|
212
|
+
});
|
|
204
213
|
|
|
205
214
|
// Mock the private method by accessing it through reflection
|
|
206
|
-
const sorted = topologicalSort(db, [story1, story2]);
|
|
215
|
+
const sorted = await topologicalSort(db, [story1, story2]);
|
|
207
216
|
|
|
208
217
|
expect(sorted).not.toBeNull();
|
|
209
218
|
expect(sorted).toHaveLength(2);
|
|
210
219
|
});
|
|
211
220
|
|
|
212
|
-
it('should respect linear dependencies (A -> B -> C)', () => {
|
|
213
|
-
const team = createTeam(db, {
|
|
221
|
+
it('should respect linear dependencies (A -> B -> C)', async () => {
|
|
222
|
+
const team = await createTeam(db, {
|
|
214
223
|
name: 'Test Team',
|
|
215
224
|
repoUrl: 'https://github.com/test/repo',
|
|
216
225
|
repoPath: 'test',
|
|
217
226
|
});
|
|
218
|
-
const storyA = createStory(db, {
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
const storyA = await createStory(db, {
|
|
228
|
+
teamId: team.id,
|
|
229
|
+
title: 'Story A',
|
|
230
|
+
description: 'Test',
|
|
231
|
+
});
|
|
232
|
+
const storyB = await createStory(db, {
|
|
233
|
+
teamId: team.id,
|
|
234
|
+
title: 'Story B',
|
|
235
|
+
description: 'Test',
|
|
236
|
+
});
|
|
237
|
+
const storyC = await createStory(db, {
|
|
238
|
+
teamId: team.id,
|
|
239
|
+
title: 'Story C',
|
|
240
|
+
description: 'Test',
|
|
241
|
+
});
|
|
221
242
|
|
|
222
243
|
// B depends on A, C depends on B
|
|
223
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
224
|
-
addStoryDependency(db, storyC.id, storyB.id);
|
|
244
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
245
|
+
await addStoryDependency(db, storyC.id, storyB.id);
|
|
225
246
|
|
|
226
|
-
const sorted = topologicalSort(db, [storyC, storyA, storyB]);
|
|
247
|
+
const sorted = await topologicalSort(db, [storyC, storyA, storyB]);
|
|
227
248
|
|
|
228
249
|
expect(sorted).not.toBeNull();
|
|
229
250
|
expect(sorted).toHaveLength(3);
|
|
@@ -233,24 +254,40 @@ describe('Scheduler Topological Sort', () => {
|
|
|
233
254
|
expect(ids.indexOf(storyB.id)).toBeLessThan(ids.indexOf(storyC.id));
|
|
234
255
|
});
|
|
235
256
|
|
|
236
|
-
it('should respect diamond dependencies (A -> B, A -> C, B -> D, C -> D)', () => {
|
|
237
|
-
const team = createTeam(db, {
|
|
257
|
+
it('should respect diamond dependencies (A -> B, A -> C, B -> D, C -> D)', async () => {
|
|
258
|
+
const team = await createTeam(db, {
|
|
238
259
|
name: 'Test Team',
|
|
239
260
|
repoUrl: 'https://github.com/test/repo',
|
|
240
261
|
repoPath: 'test',
|
|
241
262
|
});
|
|
242
|
-
const storyA = createStory(db, {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
const storyA = await createStory(db, {
|
|
264
|
+
teamId: team.id,
|
|
265
|
+
title: 'Story A',
|
|
266
|
+
description: 'Test',
|
|
267
|
+
});
|
|
268
|
+
const storyB = await createStory(db, {
|
|
269
|
+
teamId: team.id,
|
|
270
|
+
title: 'Story B',
|
|
271
|
+
description: 'Test',
|
|
272
|
+
});
|
|
273
|
+
const storyC = await createStory(db, {
|
|
274
|
+
teamId: team.id,
|
|
275
|
+
title: 'Story C',
|
|
276
|
+
description: 'Test',
|
|
277
|
+
});
|
|
278
|
+
const storyD = await createStory(db, {
|
|
279
|
+
teamId: team.id,
|
|
280
|
+
title: 'Story D',
|
|
281
|
+
description: 'Test',
|
|
282
|
+
});
|
|
246
283
|
|
|
247
284
|
// B and C depend on A, D depends on both B and C
|
|
248
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
249
|
-
addStoryDependency(db, storyC.id, storyA.id);
|
|
250
|
-
addStoryDependency(db, storyD.id, storyB.id);
|
|
251
|
-
addStoryDependency(db, storyD.id, storyC.id);
|
|
285
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
286
|
+
await addStoryDependency(db, storyC.id, storyA.id);
|
|
287
|
+
await addStoryDependency(db, storyD.id, storyB.id);
|
|
288
|
+
await addStoryDependency(db, storyD.id, storyC.id);
|
|
252
289
|
|
|
253
|
-
const sorted = topologicalSort(db, [storyD, storyB, storyA, storyC]);
|
|
290
|
+
const sorted = await topologicalSort(db, [storyD, storyB, storyA, storyC]);
|
|
254
291
|
|
|
255
292
|
expect(sorted).not.toBeNull();
|
|
256
293
|
expect(sorted).toHaveLength(4);
|
|
@@ -267,138 +304,170 @@ describe('Scheduler Topological Sort', () => {
|
|
|
267
304
|
expect(ids.indexOf(storyC.id)).toBeLessThan(ids.indexOf(storyD.id));
|
|
268
305
|
});
|
|
269
306
|
|
|
270
|
-
it('should detect circular dependencies', () => {
|
|
271
|
-
const team = createTeam(db, {
|
|
307
|
+
it('should detect circular dependencies', async () => {
|
|
308
|
+
const team = await createTeam(db, {
|
|
272
309
|
name: 'Test Team',
|
|
273
310
|
repoUrl: 'https://github.com/test/repo',
|
|
274
311
|
repoPath: 'test',
|
|
275
312
|
});
|
|
276
|
-
const storyA = createStory(db, {
|
|
277
|
-
|
|
313
|
+
const storyA = await createStory(db, {
|
|
314
|
+
teamId: team.id,
|
|
315
|
+
title: 'Story A',
|
|
316
|
+
description: 'Test',
|
|
317
|
+
});
|
|
318
|
+
const storyB = await createStory(db, {
|
|
319
|
+
teamId: team.id,
|
|
320
|
+
title: 'Story B',
|
|
321
|
+
description: 'Test',
|
|
322
|
+
});
|
|
278
323
|
|
|
279
324
|
// Create circular dependency: A -> B -> A
|
|
280
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
281
|
-
addStoryDependency(db, storyA.id, storyB.id);
|
|
325
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
326
|
+
await addStoryDependency(db, storyA.id, storyB.id);
|
|
282
327
|
|
|
283
|
-
const sorted = topologicalSort(db, [storyA, storyB]);
|
|
328
|
+
const sorted = await topologicalSort(db, [storyA, storyB]);
|
|
284
329
|
|
|
285
330
|
expect(sorted).toBeNull();
|
|
286
331
|
});
|
|
287
332
|
});
|
|
288
333
|
|
|
289
334
|
describe('Scheduler Dependency Satisfaction', () => {
|
|
290
|
-
it('should consider merged stories as satisfying dependencies', () => {
|
|
291
|
-
const team = createTeam(db, {
|
|
335
|
+
it('should consider merged stories as satisfying dependencies', async () => {
|
|
336
|
+
const team = await createTeam(db, {
|
|
292
337
|
name: 'Test Team',
|
|
293
338
|
repoUrl: 'https://github.com/test/repo',
|
|
294
339
|
repoPath: 'test',
|
|
295
340
|
});
|
|
296
|
-
const depStory = createStory(db, {
|
|
297
|
-
|
|
341
|
+
const depStory = await createStory(db, {
|
|
342
|
+
teamId: team.id,
|
|
343
|
+
title: 'Dependency',
|
|
344
|
+
description: 'Test',
|
|
345
|
+
});
|
|
346
|
+
const mainStory = await createStory(db, {
|
|
298
347
|
teamId: team.id,
|
|
299
348
|
title: 'Main Story',
|
|
300
349
|
description: 'Test',
|
|
301
350
|
});
|
|
302
351
|
|
|
303
|
-
addStoryDependency(db, mainStory.id, depStory.id);
|
|
352
|
+
await addStoryDependency(db, mainStory.id, depStory.id);
|
|
304
353
|
|
|
305
354
|
// Initially, dependencies are not satisfied
|
|
306
|
-
let isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
355
|
+
let isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
307
356
|
expect(isSatisfied).toBe(false);
|
|
308
357
|
|
|
309
358
|
// Mark dependency as merged
|
|
310
|
-
updateStory(db, depStory.id, { status: 'merged' });
|
|
311
|
-
isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
359
|
+
await updateStory(db, depStory.id, { status: 'merged' });
|
|
360
|
+
isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
312
361
|
expect(isSatisfied).toBe(true);
|
|
313
362
|
});
|
|
314
363
|
|
|
315
|
-
it('should not consider in-progress stories as satisfying dependencies', () => {
|
|
316
|
-
const team = createTeam(db, {
|
|
364
|
+
it('should not consider in-progress stories as satisfying dependencies', async () => {
|
|
365
|
+
const team = await createTeam(db, {
|
|
317
366
|
name: 'Test Team',
|
|
318
367
|
repoUrl: 'https://github.com/test/repo',
|
|
319
368
|
repoPath: 'test',
|
|
320
369
|
});
|
|
321
|
-
const depStory = createStory(db, {
|
|
322
|
-
|
|
370
|
+
const depStory = await createStory(db, {
|
|
371
|
+
teamId: team.id,
|
|
372
|
+
title: 'Dependency',
|
|
373
|
+
description: 'Test',
|
|
374
|
+
});
|
|
375
|
+
const mainStory = await createStory(db, {
|
|
323
376
|
teamId: team.id,
|
|
324
377
|
title: 'Main Story',
|
|
325
378
|
description: 'Test',
|
|
326
379
|
});
|
|
327
380
|
|
|
328
|
-
addStoryDependency(db, mainStory.id, depStory.id);
|
|
381
|
+
await addStoryDependency(db, mainStory.id, depStory.id);
|
|
329
382
|
|
|
330
383
|
// Mark dependency as in_progress - this should NOT satisfy the dependency
|
|
331
|
-
updateStory(db, depStory.id, { status: 'in_progress' });
|
|
332
|
-
const isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
384
|
+
await updateStory(db, depStory.id, { status: 'in_progress' });
|
|
385
|
+
const isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
333
386
|
expect(isSatisfied).toBe(false);
|
|
334
387
|
});
|
|
335
388
|
|
|
336
|
-
it('should not consider planned stories as satisfying dependencies', () => {
|
|
337
|
-
const team = createTeam(db, {
|
|
389
|
+
it('should not consider planned stories as satisfying dependencies', async () => {
|
|
390
|
+
const team = await createTeam(db, {
|
|
338
391
|
name: 'Test Team',
|
|
339
392
|
repoUrl: 'https://github.com/test/repo',
|
|
340
393
|
repoPath: 'test',
|
|
341
394
|
});
|
|
342
|
-
const depStory = createStory(db, {
|
|
343
|
-
|
|
395
|
+
const depStory = await createStory(db, {
|
|
396
|
+
teamId: team.id,
|
|
397
|
+
title: 'Dependency',
|
|
398
|
+
description: 'Test',
|
|
399
|
+
});
|
|
400
|
+
const mainStory = await createStory(db, {
|
|
344
401
|
teamId: team.id,
|
|
345
402
|
title: 'Main Story',
|
|
346
403
|
description: 'Test',
|
|
347
404
|
});
|
|
348
405
|
|
|
349
|
-
addStoryDependency(db, mainStory.id, depStory.id);
|
|
406
|
+
await addStoryDependency(db, mainStory.id, depStory.id);
|
|
350
407
|
|
|
351
408
|
// Update main story status to planned (default)
|
|
352
|
-
updateStory(db, mainStory.id, { status: 'planned' });
|
|
409
|
+
await updateStory(db, mainStory.id, { status: 'planned' });
|
|
353
410
|
|
|
354
|
-
const isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
411
|
+
const isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
355
412
|
expect(isSatisfied).toBe(false);
|
|
356
413
|
});
|
|
357
414
|
|
|
358
|
-
it('should handle multiple dependencies', () => {
|
|
359
|
-
const team = createTeam(db, {
|
|
415
|
+
it('should handle multiple dependencies', async () => {
|
|
416
|
+
const team = await createTeam(db, {
|
|
360
417
|
name: 'Test Team',
|
|
361
418
|
repoUrl: 'https://github.com/test/repo',
|
|
362
419
|
repoPath: 'test',
|
|
363
420
|
});
|
|
364
|
-
const dep1 = createStory(db, { teamId: team.id, title: 'Dep 1', description: 'Test' });
|
|
365
|
-
const dep2 = createStory(db, { teamId: team.id, title: 'Dep 2', description: 'Test' });
|
|
366
|
-
const mainStory = createStory(db, {
|
|
421
|
+
const dep1 = await createStory(db, { teamId: team.id, title: 'Dep 1', description: 'Test' });
|
|
422
|
+
const dep2 = await createStory(db, { teamId: team.id, title: 'Dep 2', description: 'Test' });
|
|
423
|
+
const mainStory = await createStory(db, {
|
|
367
424
|
teamId: team.id,
|
|
368
425
|
title: 'Main Story',
|
|
369
426
|
description: 'Test',
|
|
370
427
|
});
|
|
371
428
|
|
|
372
|
-
addStoryDependency(db, mainStory.id, dep1.id);
|
|
373
|
-
addStoryDependency(db, mainStory.id, dep2.id);
|
|
429
|
+
await addStoryDependency(db, mainStory.id, dep1.id);
|
|
430
|
+
await addStoryDependency(db, mainStory.id, dep2.id);
|
|
374
431
|
|
|
375
432
|
// Mark only first dependency as merged
|
|
376
|
-
updateStory(db, dep1.id, { status: 'merged' });
|
|
377
|
-
let isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
433
|
+
await updateStory(db, dep1.id, { status: 'merged' });
|
|
434
|
+
let isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
378
435
|
expect(isSatisfied).toBe(false);
|
|
379
436
|
|
|
380
437
|
// Mark second dependency as merged too
|
|
381
|
-
updateStory(db, dep2.id, { status: 'merged' });
|
|
382
|
-
isSatisfied = areDependenciesSatisfied(db, mainStory.id);
|
|
438
|
+
await updateStory(db, dep2.id, { status: 'merged' });
|
|
439
|
+
isSatisfied = await areDependenciesSatisfied(db, mainStory.id);
|
|
383
440
|
expect(isSatisfied).toBe(true);
|
|
384
441
|
});
|
|
385
442
|
});
|
|
386
443
|
|
|
387
444
|
describe('Scheduler Build Dependency Graph', () => {
|
|
388
|
-
it('should correctly build a dependency graph', () => {
|
|
389
|
-
const team = createTeam(db, {
|
|
445
|
+
it('should correctly build a dependency graph', async () => {
|
|
446
|
+
const team = await createTeam(db, {
|
|
390
447
|
name: 'Test Team',
|
|
391
448
|
repoUrl: 'https://github.com/test/repo',
|
|
392
449
|
repoPath: 'test',
|
|
393
450
|
});
|
|
394
|
-
const storyA = createStory(db, {
|
|
395
|
-
|
|
396
|
-
|
|
451
|
+
const storyA = await createStory(db, {
|
|
452
|
+
teamId: team.id,
|
|
453
|
+
title: 'Story A',
|
|
454
|
+
description: 'Test',
|
|
455
|
+
});
|
|
456
|
+
const storyB = await createStory(db, {
|
|
457
|
+
teamId: team.id,
|
|
458
|
+
title: 'Story B',
|
|
459
|
+
description: 'Test',
|
|
460
|
+
});
|
|
461
|
+
const storyC = await createStory(db, {
|
|
462
|
+
teamId: team.id,
|
|
463
|
+
title: 'Story C',
|
|
464
|
+
description: 'Test',
|
|
465
|
+
});
|
|
397
466
|
|
|
398
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
399
|
-
addStoryDependency(db, storyC.id, storyA.id);
|
|
467
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
468
|
+
await addStoryDependency(db, storyC.id, storyA.id);
|
|
400
469
|
|
|
401
|
-
const graph = buildDependencyGraph(db, [storyA, storyB, storyC]);
|
|
470
|
+
const graph = await buildDependencyGraph(db, [storyA, storyB, storyC]);
|
|
402
471
|
|
|
403
472
|
expect(graph.has(storyA.id)).toBe(true);
|
|
404
473
|
expect(graph.has(storyB.id)).toBe(true);
|
|
@@ -409,21 +478,33 @@ describe('Scheduler Build Dependency Graph', () => {
|
|
|
409
478
|
expect(graph.get(storyC.id)).toEqual(new Set([storyA.id]));
|
|
410
479
|
});
|
|
411
480
|
|
|
412
|
-
it('should include only stories in the input list', () => {
|
|
413
|
-
const team = createTeam(db, {
|
|
481
|
+
it('should include only stories in the input list', async () => {
|
|
482
|
+
const team = await createTeam(db, {
|
|
414
483
|
name: 'Test Team',
|
|
415
484
|
repoUrl: 'https://github.com/test/repo',
|
|
416
485
|
repoPath: 'test',
|
|
417
486
|
});
|
|
418
|
-
const storyA = createStory(db, {
|
|
419
|
-
|
|
420
|
-
|
|
487
|
+
const storyA = await createStory(db, {
|
|
488
|
+
teamId: team.id,
|
|
489
|
+
title: 'Story A',
|
|
490
|
+
description: 'Test',
|
|
491
|
+
});
|
|
492
|
+
const storyB = await createStory(db, {
|
|
493
|
+
teamId: team.id,
|
|
494
|
+
title: 'Story B',
|
|
495
|
+
description: 'Test',
|
|
496
|
+
});
|
|
497
|
+
const storyC = await createStory(db, {
|
|
498
|
+
teamId: team.id,
|
|
499
|
+
title: 'Story C',
|
|
500
|
+
description: 'Test',
|
|
501
|
+
});
|
|
421
502
|
|
|
422
503
|
// B depends on A (A is not in the filter list)
|
|
423
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
504
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
424
505
|
|
|
425
506
|
// Only include B and C in the graph
|
|
426
|
-
const graph = buildDependencyGraph(db, [storyB, storyC]);
|
|
507
|
+
const graph = await buildDependencyGraph(db, [storyB, storyC]);
|
|
427
508
|
|
|
428
509
|
expect(graph.has(storyB.id)).toBe(true);
|
|
429
510
|
expect(graph.has(storyC.id)).toBe(true);
|
|
@@ -432,7 +513,7 @@ describe('Scheduler Build Dependency Graph', () => {
|
|
|
432
513
|
});
|
|
433
514
|
|
|
434
515
|
describe('Scheduler Worktree Removal', () => {
|
|
435
|
-
it('should remove worktrees with a short cleanup timeout', () => {
|
|
516
|
+
it('should remove worktrees with a short cleanup timeout', async () => {
|
|
436
517
|
const removeSpy = vi.spyOn(worktreeModule, 'removeWorktree').mockReturnValue({
|
|
437
518
|
success: true,
|
|
438
519
|
fullWorktreePath: '/tmp/repos/test-agent-1',
|
|
@@ -446,7 +527,7 @@ describe('Scheduler Worktree Removal', () => {
|
|
|
446
527
|
vi.restoreAllMocks();
|
|
447
528
|
});
|
|
448
529
|
|
|
449
|
-
it('should log worktree removal failures to the database', () => {
|
|
530
|
+
it('should log worktree removal failures to the database', async () => {
|
|
450
531
|
// Mock the shared removeWorktree to simulate failure
|
|
451
532
|
vi.spyOn(worktreeModule, 'removeWorktree').mockReturnValue({
|
|
452
533
|
success: false,
|
|
@@ -455,10 +536,10 @@ describe('Scheduler Worktree Removal', () => {
|
|
|
455
536
|
});
|
|
456
537
|
|
|
457
538
|
const removeMethod = (scheduler as any).removeAgentWorktree;
|
|
458
|
-
removeMethod.call(scheduler, 'repos/test-agent-1', 'agent-test-1');
|
|
539
|
+
await removeMethod.call(scheduler, 'repos/test-agent-1', 'agent-test-1');
|
|
459
540
|
|
|
460
541
|
// Check that the failure was logged
|
|
461
|
-
const logs = getLogsByEventType(db, 'WORKTREE_REMOVAL_FAILED');
|
|
542
|
+
const logs = await getLogsByEventType(db, 'WORKTREE_REMOVAL_FAILED');
|
|
462
543
|
expect(logs).toHaveLength(1);
|
|
463
544
|
expect(logs[0].agent_id).toBe('agent-test-1');
|
|
464
545
|
expect(logs[0].event_type).toBe('WORKTREE_REMOVAL_FAILED');
|
|
@@ -468,14 +549,14 @@ describe('Scheduler Worktree Removal', () => {
|
|
|
468
549
|
vi.restoreAllMocks();
|
|
469
550
|
});
|
|
470
551
|
|
|
471
|
-
it('should handle empty worktree paths gracefully', () => {
|
|
552
|
+
it('should handle empty worktree paths gracefully', async () => {
|
|
472
553
|
const removeMethod = (scheduler as any).removeAgentWorktree;
|
|
473
554
|
|
|
474
555
|
// Should return without error for empty path
|
|
475
556
|
removeMethod.call(scheduler, '', 'agent-test-1');
|
|
476
557
|
|
|
477
558
|
// Should not log anything
|
|
478
|
-
const logs = getLogsByEventType(db, 'WORKTREE_REMOVAL_FAILED');
|
|
559
|
+
const logs = await getLogsByEventType(db, 'WORKTREE_REMOVAL_FAILED');
|
|
479
560
|
expect(logs).toHaveLength(0);
|
|
480
561
|
});
|
|
481
562
|
});
|
|
@@ -483,7 +564,7 @@ describe('Scheduler Worktree Removal', () => {
|
|
|
483
564
|
describe('Scheduler Orphaned Story Recovery', () => {
|
|
484
565
|
it('should recover orphaned stories assigned to terminated agents', async () => {
|
|
485
566
|
// Setup: Create team, agents, and a story
|
|
486
|
-
const team = createTeam(db, {
|
|
567
|
+
const team = await createTeam(db, {
|
|
487
568
|
name: 'Test Team',
|
|
488
569
|
repoUrl: 'https://github.com/test/repo',
|
|
489
570
|
repoPath: 'test',
|
|
@@ -491,32 +572,32 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
491
572
|
|
|
492
573
|
// Create a terminated agent in the database
|
|
493
574
|
const terminatedAgentId = 'agent-terminated-1';
|
|
494
|
-
db.run(
|
|
575
|
+
db.db.run(
|
|
495
576
|
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
496
577
|
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
497
578
|
[terminatedAgentId, 'intermediate', team.id, 'terminated']
|
|
498
579
|
);
|
|
499
580
|
|
|
500
581
|
// Create a story assigned to the terminated agent
|
|
501
|
-
const story = createStory(db, {
|
|
582
|
+
const story = await createStory(db, {
|
|
502
583
|
teamId: team.id,
|
|
503
584
|
title: 'Orphaned Story',
|
|
504
585
|
description: 'Test',
|
|
505
586
|
});
|
|
506
|
-
updateStory(db, story.id, {
|
|
587
|
+
await updateStory(db, story.id, {
|
|
507
588
|
assignedAgentId: terminatedAgentId,
|
|
508
589
|
status: 'in_progress',
|
|
509
590
|
});
|
|
510
591
|
|
|
511
592
|
// Get the recovery method
|
|
512
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
593
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
513
594
|
|
|
514
595
|
// Verify the story was recovered
|
|
515
596
|
expect(recovered).toContain(story.id);
|
|
516
597
|
expect(recovered.length).toBe(1);
|
|
517
598
|
|
|
518
599
|
// Verify the story's assignment was cleared and status changed
|
|
519
|
-
const recoveredStory = db.exec(
|
|
600
|
+
const recoveredStory = db.db.exec(
|
|
520
601
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${story.id}'`
|
|
521
602
|
)[0]?.values[0];
|
|
522
603
|
|
|
@@ -525,7 +606,7 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
525
606
|
});
|
|
526
607
|
|
|
527
608
|
it('should not affect stories assigned to active agents', async () => {
|
|
528
|
-
const team = createTeam(db, {
|
|
609
|
+
const team = await createTeam(db, {
|
|
529
610
|
name: 'Test Team',
|
|
530
611
|
repoUrl: 'https://github.com/test/repo',
|
|
531
612
|
repoPath: 'test',
|
|
@@ -533,28 +614,32 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
533
614
|
|
|
534
615
|
// Create an active (non-terminated) agent
|
|
535
616
|
const activeAgentId = 'agent-active-1';
|
|
536
|
-
db.run(
|
|
617
|
+
db.db.run(
|
|
537
618
|
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
538
619
|
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
539
620
|
[activeAgentId, 'intermediate', team.id, 'working']
|
|
540
621
|
);
|
|
541
622
|
|
|
542
623
|
// Create a story assigned to the active agent
|
|
543
|
-
const story = createStory(db, {
|
|
544
|
-
|
|
624
|
+
const story = await createStory(db, {
|
|
625
|
+
teamId: team.id,
|
|
626
|
+
title: 'Active Story',
|
|
627
|
+
description: 'Test',
|
|
628
|
+
});
|
|
629
|
+
await updateStory(db, story.id, {
|
|
545
630
|
assignedAgentId: activeAgentId,
|
|
546
631
|
status: 'in_progress',
|
|
547
632
|
});
|
|
548
|
-
db.run(`UPDATE agents SET current_story_id = ? WHERE id = ?`, [story.id, activeAgentId]);
|
|
633
|
+
db.db.run(`UPDATE agents SET current_story_id = ? WHERE id = ?`, [story.id, activeAgentId]);
|
|
549
634
|
|
|
550
635
|
// Get the recovery method
|
|
551
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
636
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
552
637
|
|
|
553
638
|
// Verify no stories were recovered
|
|
554
639
|
expect(recovered.length).toBe(0);
|
|
555
640
|
|
|
556
641
|
// Verify the story's assignment was NOT changed
|
|
557
|
-
const unchangedStory = db.exec(
|
|
642
|
+
const unchangedStory = db.db.exec(
|
|
558
643
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${story.id}'`
|
|
559
644
|
)[0]?.values[0];
|
|
560
645
|
|
|
@@ -563,34 +648,34 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
563
648
|
});
|
|
564
649
|
|
|
565
650
|
it('should recover in_progress stories assigned to idle agents with no current story', async () => {
|
|
566
|
-
const team = createTeam(db, {
|
|
651
|
+
const team = await createTeam(db, {
|
|
567
652
|
name: 'Inconsistent Team',
|
|
568
653
|
repoUrl: 'https://github.com/test/repo',
|
|
569
654
|
repoPath: 'test',
|
|
570
655
|
});
|
|
571
656
|
|
|
572
657
|
const idleAgentId = 'agent-idle-1';
|
|
573
|
-
db.run(
|
|
658
|
+
db.db.run(
|
|
574
659
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
575
660
|
VALUES (?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
576
661
|
[idleAgentId, 'intermediate', team.id, 'idle']
|
|
577
662
|
);
|
|
578
663
|
|
|
579
|
-
const story = createStory(db, {
|
|
664
|
+
const story = await createStory(db, {
|
|
580
665
|
teamId: team.id,
|
|
581
666
|
title: 'Inconsistent Assignment Story',
|
|
582
667
|
description: 'Assigned to idle agent',
|
|
583
668
|
});
|
|
584
|
-
updateStory(db, story.id, {
|
|
669
|
+
await updateStory(db, story.id, {
|
|
585
670
|
assignedAgentId: idleAgentId,
|
|
586
671
|
status: 'in_progress',
|
|
587
672
|
});
|
|
588
673
|
|
|
589
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
674
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
590
675
|
|
|
591
676
|
expect(recovered).toContain(story.id);
|
|
592
677
|
|
|
593
|
-
const recoveredStory = db.exec(
|
|
678
|
+
const recoveredStory = db.db.exec(
|
|
594
679
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${story.id}'`
|
|
595
680
|
)[0]?.values[0];
|
|
596
681
|
|
|
@@ -599,34 +684,34 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
599
684
|
});
|
|
600
685
|
|
|
601
686
|
it('should recover in_progress stories when agent current_story_id points to a different story', async () => {
|
|
602
|
-
const team = createTeam(db, {
|
|
687
|
+
const team = await createTeam(db, {
|
|
603
688
|
name: 'Mismatched Team',
|
|
604
689
|
repoUrl: 'https://github.com/test/repo',
|
|
605
690
|
repoPath: 'test',
|
|
606
691
|
});
|
|
607
692
|
|
|
608
693
|
const workingAgentId = 'agent-working-mismatch-1';
|
|
609
|
-
db.run(
|
|
694
|
+
db.db.run(
|
|
610
695
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
611
696
|
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
612
697
|
[workingAgentId, 'intermediate', team.id, 'working', 'STORY-OTHER']
|
|
613
698
|
);
|
|
614
699
|
|
|
615
|
-
const story = createStory(db, {
|
|
700
|
+
const story = await createStory(db, {
|
|
616
701
|
teamId: team.id,
|
|
617
702
|
title: 'Mismatched Assignment Story',
|
|
618
703
|
description: 'Assigned story does not match agent current story',
|
|
619
704
|
});
|
|
620
|
-
updateStory(db, story.id, {
|
|
705
|
+
await updateStory(db, story.id, {
|
|
621
706
|
assignedAgentId: workingAgentId,
|
|
622
707
|
status: 'in_progress',
|
|
623
708
|
});
|
|
624
709
|
|
|
625
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
710
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
626
711
|
|
|
627
712
|
expect(recovered).toContain(story.id);
|
|
628
713
|
|
|
629
|
-
const recoveredStory = db.exec(
|
|
714
|
+
const recoveredStory = db.db.exec(
|
|
630
715
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${story.id}'`
|
|
631
716
|
)[0]?.values[0];
|
|
632
717
|
|
|
@@ -635,33 +720,33 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
635
720
|
});
|
|
636
721
|
|
|
637
722
|
it('should recover in_progress stories assigned to blocked agents even with matching current story', async () => {
|
|
638
|
-
const team = createTeam(db, {
|
|
723
|
+
const team = await createTeam(db, {
|
|
639
724
|
name: 'Blocked Team',
|
|
640
725
|
repoUrl: 'https://github.com/test/repo',
|
|
641
726
|
repoPath: 'test',
|
|
642
727
|
});
|
|
643
728
|
|
|
644
729
|
const blockedAgentId = 'agent-blocked-1';
|
|
645
|
-
const story = createStory(db, {
|
|
730
|
+
const story = await createStory(db, {
|
|
646
731
|
teamId: team.id,
|
|
647
732
|
title: 'Blocked Assignment Story',
|
|
648
733
|
description: 'Assigned to blocked agent',
|
|
649
734
|
});
|
|
650
|
-
db.run(
|
|
735
|
+
db.db.run(
|
|
651
736
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
652
737
|
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
653
738
|
[blockedAgentId, 'intermediate', team.id, 'blocked', story.id]
|
|
654
739
|
);
|
|
655
|
-
updateStory(db, story.id, {
|
|
740
|
+
await updateStory(db, story.id, {
|
|
656
741
|
assignedAgentId: blockedAgentId,
|
|
657
742
|
status: 'in_progress',
|
|
658
743
|
});
|
|
659
744
|
|
|
660
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
745
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
661
746
|
|
|
662
747
|
expect(recovered).toContain(story.id);
|
|
663
748
|
|
|
664
|
-
const recoveredStory = db.exec(
|
|
749
|
+
const recoveredStory = db.db.exec(
|
|
665
750
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${story.id}'`
|
|
666
751
|
)[0]?.values[0];
|
|
667
752
|
|
|
@@ -670,34 +755,34 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
670
755
|
});
|
|
671
756
|
|
|
672
757
|
it('should not recover non-in_progress stories with inconsistent assignment', async () => {
|
|
673
|
-
const team = createTeam(db, {
|
|
758
|
+
const team = await createTeam(db, {
|
|
674
759
|
name: 'Review Team',
|
|
675
760
|
repoUrl: 'https://github.com/test/repo',
|
|
676
761
|
repoPath: 'test',
|
|
677
762
|
});
|
|
678
763
|
|
|
679
764
|
const idleAgentId = 'agent-idle-review-1';
|
|
680
|
-
db.run(
|
|
765
|
+
db.db.run(
|
|
681
766
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
682
767
|
VALUES (?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
683
768
|
[idleAgentId, 'intermediate', team.id, 'idle']
|
|
684
769
|
);
|
|
685
770
|
|
|
686
|
-
const reviewStory = createStory(db, {
|
|
771
|
+
const reviewStory = await createStory(db, {
|
|
687
772
|
teamId: team.id,
|
|
688
773
|
title: 'Review Story',
|
|
689
774
|
description: 'Should not be recovered by in_progress consistency check',
|
|
690
775
|
});
|
|
691
|
-
updateStory(db, reviewStory.id, {
|
|
776
|
+
await updateStory(db, reviewStory.id, {
|
|
692
777
|
assignedAgentId: idleAgentId,
|
|
693
778
|
status: 'review',
|
|
694
779
|
});
|
|
695
780
|
|
|
696
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
781
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
697
782
|
|
|
698
783
|
expect(recovered).not.toContain(reviewStory.id);
|
|
699
784
|
|
|
700
|
-
const unchangedStory = db.exec(
|
|
785
|
+
const unchangedStory = db.db.exec(
|
|
701
786
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${reviewStory.id}'`
|
|
702
787
|
)[0]?.values[0];
|
|
703
788
|
|
|
@@ -706,27 +791,27 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
706
791
|
});
|
|
707
792
|
|
|
708
793
|
it('should recover stale in_progress stories without assigned agents', async () => {
|
|
709
|
-
const team = createTeam(db, {
|
|
794
|
+
const team = await createTeam(db, {
|
|
710
795
|
name: 'Stale Team',
|
|
711
796
|
repoUrl: 'https://github.com/test/repo',
|
|
712
797
|
repoPath: 'test',
|
|
713
798
|
});
|
|
714
799
|
|
|
715
|
-
const staleStory = createStory(db, {
|
|
800
|
+
const staleStory = await createStory(db, {
|
|
716
801
|
teamId: team.id,
|
|
717
802
|
title: 'Stale In Progress Story',
|
|
718
803
|
description: 'Lost assignment',
|
|
719
804
|
});
|
|
720
|
-
updateStory(db, staleStory.id, {
|
|
805
|
+
await updateStory(db, staleStory.id, {
|
|
721
806
|
status: 'in_progress',
|
|
722
807
|
assignedAgentId: null,
|
|
723
808
|
});
|
|
724
809
|
|
|
725
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
810
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
726
811
|
|
|
727
812
|
expect(recovered).toContain(staleStory.id);
|
|
728
813
|
|
|
729
|
-
const recoveredStory = db.exec(
|
|
814
|
+
const recoveredStory = db.db.exec(
|
|
730
815
|
`SELECT assigned_agent_id, status FROM stories WHERE id = '${staleStory.id}'`
|
|
731
816
|
)[0]?.values[0];
|
|
732
817
|
|
|
@@ -735,29 +820,29 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
735
820
|
});
|
|
736
821
|
|
|
737
822
|
it('should not recover planned stories that are unassigned', async () => {
|
|
738
|
-
const team = createTeam(db, {
|
|
823
|
+
const team = await createTeam(db, {
|
|
739
824
|
name: 'Planned Team',
|
|
740
825
|
repoUrl: 'https://github.com/test/repo',
|
|
741
826
|
repoPath: 'test',
|
|
742
827
|
});
|
|
743
828
|
|
|
744
|
-
const plannedStory = createStory(db, {
|
|
829
|
+
const plannedStory = await createStory(db, {
|
|
745
830
|
teamId: team.id,
|
|
746
831
|
title: 'Already Planned',
|
|
747
832
|
description: 'Should stay planned',
|
|
748
833
|
});
|
|
749
|
-
updateStory(db, plannedStory.id, {
|
|
834
|
+
await updateStory(db, plannedStory.id, {
|
|
750
835
|
status: 'planned',
|
|
751
836
|
assignedAgentId: null,
|
|
752
837
|
});
|
|
753
838
|
|
|
754
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
839
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
755
840
|
|
|
756
841
|
expect(recovered).not.toContain(plannedStory.id);
|
|
757
842
|
});
|
|
758
843
|
|
|
759
844
|
it('should recover multiple orphaned stories', async () => {
|
|
760
|
-
const team = createTeam(db, {
|
|
845
|
+
const team = await createTeam(db, {
|
|
761
846
|
name: 'Test Team',
|
|
762
847
|
repoUrl: 'https://github.com/test/repo',
|
|
763
848
|
repoPath: 'test',
|
|
@@ -765,35 +850,35 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
765
850
|
|
|
766
851
|
// Create a terminated agent
|
|
767
852
|
const terminatedAgentId = 'agent-terminated-2';
|
|
768
|
-
db.run(
|
|
853
|
+
db.db.run(
|
|
769
854
|
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
770
855
|
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
771
856
|
[terminatedAgentId, 'intermediate', team.id, 'terminated']
|
|
772
857
|
);
|
|
773
858
|
|
|
774
859
|
// Create multiple stories assigned to the terminated agent
|
|
775
|
-
const story1 = createStory(db, {
|
|
860
|
+
const story1 = await createStory(db, {
|
|
776
861
|
teamId: team.id,
|
|
777
862
|
title: 'Orphaned Story 1',
|
|
778
863
|
description: 'Test',
|
|
779
864
|
});
|
|
780
|
-
const story2 = createStory(db, {
|
|
865
|
+
const story2 = await createStory(db, {
|
|
781
866
|
teamId: team.id,
|
|
782
867
|
title: 'Orphaned Story 2',
|
|
783
868
|
description: 'Test',
|
|
784
869
|
});
|
|
785
870
|
|
|
786
|
-
updateStory(db, story1.id, {
|
|
871
|
+
await updateStory(db, story1.id, {
|
|
787
872
|
assignedAgentId: terminatedAgentId,
|
|
788
873
|
status: 'in_progress',
|
|
789
874
|
});
|
|
790
|
-
updateStory(db, story2.id, {
|
|
875
|
+
await updateStory(db, story2.id, {
|
|
791
876
|
assignedAgentId: terminatedAgentId,
|
|
792
877
|
status: 'review',
|
|
793
878
|
});
|
|
794
879
|
|
|
795
880
|
// Get the recovery method
|
|
796
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp');
|
|
881
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp');
|
|
797
882
|
|
|
798
883
|
// Verify both stories were recovered
|
|
799
884
|
expect(recovered.length).toBe(2);
|
|
@@ -801,12 +886,12 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
801
886
|
expect(recovered).toContain(story2.id);
|
|
802
887
|
});
|
|
803
888
|
|
|
804
|
-
it('should write markdown files when storiesDir is provided during orphan recovery', () => {
|
|
889
|
+
it('should write markdown files when storiesDir is provided during orphan recovery', async () => {
|
|
805
890
|
const storiesDir = join(tmpdir(), `hive-test-stories-${Date.now()}`);
|
|
806
891
|
mkdirSync(storiesDir, { recursive: true });
|
|
807
892
|
|
|
808
893
|
try {
|
|
809
|
-
const team = createTeam(db, {
|
|
894
|
+
const team = await createTeam(db, {
|
|
810
895
|
name: 'MD Test Team',
|
|
811
896
|
repoUrl: 'https://github.com/test/repo',
|
|
812
897
|
repoPath: 'test',
|
|
@@ -819,17 +904,17 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
819
904
|
[terminatedAgentId, 'intermediate', team.id, 'terminated']
|
|
820
905
|
);
|
|
821
906
|
|
|
822
|
-
const story = createStory(db, {
|
|
907
|
+
const story = await createStory(db, {
|
|
823
908
|
teamId: team.id,
|
|
824
909
|
title: 'Story with Markdown',
|
|
825
910
|
description: 'Should get a markdown file on recovery',
|
|
826
911
|
});
|
|
827
|
-
updateStory(db, story.id, {
|
|
912
|
+
await updateStory(db, story.id, {
|
|
828
913
|
assignedAgentId: terminatedAgentId,
|
|
829
914
|
status: 'in_progress',
|
|
830
915
|
});
|
|
831
916
|
|
|
832
|
-
const recovered = detectAndRecoverOrphanedStories(db, '/tmp', storiesDir);
|
|
917
|
+
const recovered = await detectAndRecoverOrphanedStories(db, '/tmp', storiesDir);
|
|
833
918
|
|
|
834
919
|
expect(recovered).toContain(story.id);
|
|
835
920
|
|
|
@@ -838,7 +923,7 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
838
923
|
expect(existsSync(mdPath)).toBe(true);
|
|
839
924
|
|
|
840
925
|
// Verify markdown_path was set in DB
|
|
841
|
-
const updatedStory = getStoryById(db, story.id);
|
|
926
|
+
const updatedStory = await getStoryById(db, story.id);
|
|
842
927
|
expect(updatedStory?.markdown_path).toBe(mdPath);
|
|
843
928
|
} finally {
|
|
844
929
|
rmSync(storiesDir, { recursive: true, force: true });
|
|
@@ -858,33 +943,33 @@ describe('Scheduler Refactor Capacity Policy', () => {
|
|
|
858
943
|
} as any;
|
|
859
944
|
}
|
|
860
945
|
|
|
861
|
-
it('should enforce refactor budget based on feature workload', () => {
|
|
862
|
-
const team = createTeam(db, {
|
|
946
|
+
it('should enforce refactor budget based on feature workload', async () => {
|
|
947
|
+
const team = await createTeam(db, {
|
|
863
948
|
name: 'Refactor Team',
|
|
864
949
|
repoUrl: 'https://github.com/test/repo',
|
|
865
950
|
repoPath: 'test',
|
|
866
951
|
});
|
|
867
952
|
|
|
868
|
-
const feature = createStory(db, {
|
|
953
|
+
const feature = await createStory(db, {
|
|
869
954
|
teamId: team.id,
|
|
870
955
|
title: 'Add endpoint',
|
|
871
956
|
description: 'Feature story',
|
|
872
957
|
});
|
|
873
|
-
updateStory(db, feature.id, { status: 'planned', storyPoints: 10, complexityScore: 10 });
|
|
958
|
+
await updateStory(db, feature.id, { status: 'planned', storyPoints: 10, complexityScore: 10 });
|
|
874
959
|
|
|
875
|
-
const refactorA = createStory(db, {
|
|
960
|
+
const refactorA = await createStory(db, {
|
|
876
961
|
teamId: team.id,
|
|
877
962
|
title: 'Refactor: clean parser',
|
|
878
963
|
description: 'Refactor A',
|
|
879
964
|
});
|
|
880
|
-
updateStory(db, refactorA.id, { status: 'planned', storyPoints: 1, complexityScore: 1 });
|
|
965
|
+
await updateStory(db, refactorA.id, { status: 'planned', storyPoints: 1, complexityScore: 1 });
|
|
881
966
|
|
|
882
|
-
const refactorB = createStory(db, {
|
|
967
|
+
const refactorB = await createStory(db, {
|
|
883
968
|
teamId: team.id,
|
|
884
969
|
title: 'Refactor: simplify auth flow',
|
|
885
970
|
description: 'Refactor B',
|
|
886
971
|
});
|
|
887
|
-
updateStory(db, refactorB.id, { status: 'planned', storyPoints: 2, complexityScore: 2 });
|
|
972
|
+
await updateStory(db, refactorB.id, { status: 'planned', storyPoints: 2, complexityScore: 2 });
|
|
888
973
|
|
|
889
974
|
const scalingConfig = createRefactorScalingConfig({
|
|
890
975
|
enabled: true,
|
|
@@ -892,32 +977,32 @@ describe('Scheduler Refactor Capacity Policy', () => {
|
|
|
892
977
|
allow_without_feature_work: true,
|
|
893
978
|
});
|
|
894
979
|
|
|
895
|
-
const selected = selectStoriesForCapacity(
|
|
980
|
+
const selected = (await selectStoriesForCapacity(
|
|
896
981
|
[
|
|
897
|
-
getStoryById(db, feature.id)!,
|
|
898
|
-
getStoryById(db, refactorA.id)!,
|
|
899
|
-
getStoryById(db, refactorB.id)!,
|
|
982
|
+
(await getStoryById(db, feature.id))!,
|
|
983
|
+
(await getStoryById(db, refactorA.id))!,
|
|
984
|
+
(await getStoryById(db, refactorB.id))!,
|
|
900
985
|
],
|
|
901
986
|
scalingConfig
|
|
902
|
-
) as StoryRow[];
|
|
987
|
+
)) as StoryRow[];
|
|
903
988
|
|
|
904
989
|
expect(selected.map(s => s.id)).toContain(feature.id);
|
|
905
990
|
expect(selected.map(s => s.id)).toContain(refactorA.id);
|
|
906
991
|
expect(selected.map(s => s.id)).not.toContain(refactorB.id);
|
|
907
992
|
});
|
|
908
993
|
|
|
909
|
-
it('should allow refactor-only queues when policy permits', () => {
|
|
910
|
-
const team = createTeam(db, {
|
|
994
|
+
it('should allow refactor-only queues when policy permits', async () => {
|
|
995
|
+
const team = await createTeam(db, {
|
|
911
996
|
name: 'Maintenance Team',
|
|
912
997
|
repoUrl: 'https://github.com/test/repo',
|
|
913
998
|
repoPath: 'test',
|
|
914
999
|
});
|
|
915
|
-
const refactor = createStory(db, {
|
|
1000
|
+
const refactor = await createStory(db, {
|
|
916
1001
|
teamId: team.id,
|
|
917
1002
|
title: 'Refactor: remove dead code',
|
|
918
1003
|
description: 'Maintenance',
|
|
919
1004
|
});
|
|
920
|
-
updateStory(db, refactor.id, { status: 'planned', storyPoints: 3, complexityScore: 3 });
|
|
1005
|
+
await updateStory(db, refactor.id, { status: 'planned', storyPoints: 3, complexityScore: 3 });
|
|
921
1006
|
|
|
922
1007
|
const scalingConfig = createRefactorScalingConfig({
|
|
923
1008
|
enabled: true,
|
|
@@ -925,27 +1010,27 @@ describe('Scheduler Refactor Capacity Policy', () => {
|
|
|
925
1010
|
allow_without_feature_work: true,
|
|
926
1011
|
});
|
|
927
1012
|
|
|
928
|
-
const selected = selectStoriesForCapacity(
|
|
929
|
-
[getStoryById(db, refactor.id)!],
|
|
1013
|
+
const selected = (await selectStoriesForCapacity(
|
|
1014
|
+
[(await getStoryById(db, refactor.id))!],
|
|
930
1015
|
scalingConfig
|
|
931
|
-
) as StoryRow[];
|
|
1016
|
+
)) as StoryRow[];
|
|
932
1017
|
|
|
933
1018
|
expect(selected).toHaveLength(1);
|
|
934
1019
|
expect(selected[0].id).toBe(refactor.id);
|
|
935
1020
|
});
|
|
936
1021
|
|
|
937
|
-
it('should block refactor-only queues when policy disallows it', () => {
|
|
938
|
-
const team = createTeam(db, {
|
|
1022
|
+
it('should block refactor-only queues when policy disallows it', async () => {
|
|
1023
|
+
const team = await createTeam(db, {
|
|
939
1024
|
name: 'Strict Team',
|
|
940
1025
|
repoUrl: 'https://github.com/test/repo',
|
|
941
1026
|
repoPath: 'test',
|
|
942
1027
|
});
|
|
943
|
-
const refactor = createStory(db, {
|
|
1028
|
+
const refactor = await createStory(db, {
|
|
944
1029
|
teamId: team.id,
|
|
945
1030
|
title: 'Refactor: rename internals',
|
|
946
1031
|
description: 'Maintenance',
|
|
947
1032
|
});
|
|
948
|
-
updateStory(db, refactor.id, { status: 'planned', storyPoints: 2, complexityScore: 2 });
|
|
1033
|
+
await updateStory(db, refactor.id, { status: 'planned', storyPoints: 2, complexityScore: 2 });
|
|
949
1034
|
|
|
950
1035
|
const scalingConfig = createRefactorScalingConfig({
|
|
951
1036
|
enabled: true,
|
|
@@ -953,10 +1038,10 @@ describe('Scheduler Refactor Capacity Policy', () => {
|
|
|
953
1038
|
allow_without_feature_work: false,
|
|
954
1039
|
});
|
|
955
1040
|
|
|
956
|
-
const selected = selectStoriesForCapacity(
|
|
957
|
-
[getStoryById(db, refactor.id)!],
|
|
1041
|
+
const selected = (await selectStoriesForCapacity(
|
|
1042
|
+
[(await getStoryById(db, refactor.id))!],
|
|
958
1043
|
scalingConfig
|
|
959
|
-
) as StoryRow[];
|
|
1044
|
+
)) as StoryRow[];
|
|
960
1045
|
|
|
961
1046
|
expect(selected).toHaveLength(0);
|
|
962
1047
|
});
|
|
@@ -1044,36 +1129,36 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1044
1129
|
});
|
|
1045
1130
|
|
|
1046
1131
|
// 5 tests: capacity point calculation
|
|
1047
|
-
it('should use story_points when both story_points and complexity_score exist', () => {
|
|
1132
|
+
it('should use story_points when both story_points and complexity_score exist', async () => {
|
|
1048
1133
|
expect(getCapacityPoints(mkStory('Feature', 8, 3))).toBe(8);
|
|
1049
1134
|
});
|
|
1050
1135
|
|
|
1051
|
-
it('should use complexity_score when story_points is null', () => {
|
|
1136
|
+
it('should use complexity_score when story_points is null', async () => {
|
|
1052
1137
|
expect(getCapacityPoints(mkStory('Feature', null, 5))).toBe(5);
|
|
1053
1138
|
});
|
|
1054
1139
|
|
|
1055
|
-
it('should default to 1 when both story_points and complexity_score are null', () => {
|
|
1140
|
+
it('should default to 1 when both story_points and complexity_score are null', async () => {
|
|
1056
1141
|
expect(getCapacityPoints(mkStory('Feature', null, null))).toBe(1);
|
|
1057
1142
|
});
|
|
1058
1143
|
|
|
1059
|
-
it('should treat story_points 0 as missing and fall back to complexity_score', () => {
|
|
1144
|
+
it('should treat story_points 0 as missing and fall back to complexity_score', async () => {
|
|
1060
1145
|
expect(getCapacityPoints(mkStory('Feature', 0, 4))).toBe(4);
|
|
1061
1146
|
});
|
|
1062
1147
|
|
|
1063
|
-
it('should treat 0/0 points as minimum 1 capacity unit', () => {
|
|
1148
|
+
it('should treat 0/0 points as minimum 1 capacity unit', async () => {
|
|
1064
1149
|
expect(getCapacityPoints(mkStory('Feature', 0, 0))).toBe(1);
|
|
1065
1150
|
});
|
|
1066
1151
|
|
|
1067
|
-
it('should use story_points when complexity_score is null', () => {
|
|
1152
|
+
it('should use story_points when complexity_score is null', async () => {
|
|
1068
1153
|
expect(getCapacityPoints(mkStory('Feature', 6, null))).toBe(6);
|
|
1069
1154
|
});
|
|
1070
1155
|
|
|
1071
|
-
it('should pass through non-integer capacity points as provided', () => {
|
|
1156
|
+
it('should pass through non-integer capacity points as provided', async () => {
|
|
1072
1157
|
expect(getCapacityPoints(mkStory('Feature', 2.5, null))).toBe(2.5);
|
|
1073
1158
|
});
|
|
1074
1159
|
|
|
1075
1160
|
// 12 tests: capacity selection behavior
|
|
1076
|
-
it('should filter out refactor stories when refactor policy is disabled', () => {
|
|
1161
|
+
it('should filter out refactor stories when refactor policy is disabled', async () => {
|
|
1077
1162
|
const scalingConfig = mkScalingConfig({
|
|
1078
1163
|
enabled: false,
|
|
1079
1164
|
capacity_percent: 100,
|
|
@@ -1082,12 +1167,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1082
1167
|
|
|
1083
1168
|
const feature = mkStory('Feature: add endpoint', 8, 8);
|
|
1084
1169
|
const refactor = mkStory('Refactor: split parser', 2, 2);
|
|
1085
|
-
const selected = selectStoriesForCapacity(
|
|
1170
|
+
const selected = (await selectStoriesForCapacity(
|
|
1171
|
+
[feature, refactor],
|
|
1172
|
+
scalingConfig
|
|
1173
|
+
)) as StoryRow[];
|
|
1086
1174
|
|
|
1087
1175
|
expect(selected.map(s => s.id)).toEqual([feature.id]);
|
|
1088
1176
|
});
|
|
1089
1177
|
|
|
1090
|
-
it('should include all refactor stories when capacity percent is 100', () => {
|
|
1178
|
+
it('should include all refactor stories when capacity percent is 100', async () => {
|
|
1091
1179
|
const scalingConfig = mkScalingConfig({
|
|
1092
1180
|
enabled: true,
|
|
1093
1181
|
capacity_percent: 100,
|
|
@@ -1097,15 +1185,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1097
1185
|
const feature = mkStory('Feature: add endpoint', 10, 10);
|
|
1098
1186
|
const refactorA = mkStory('Refactor: split parser', 3, 3);
|
|
1099
1187
|
const refactorB = mkStory('Refactor: normalize naming', 4, 4);
|
|
1100
|
-
const selected = selectStoriesForCapacity(
|
|
1188
|
+
const selected = (await selectStoriesForCapacity(
|
|
1101
1189
|
[feature, refactorA, refactorB],
|
|
1102
1190
|
scalingConfig
|
|
1103
|
-
) as StoryRow[];
|
|
1191
|
+
)) as StoryRow[];
|
|
1104
1192
|
|
|
1105
1193
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactorA.id, refactorB.id]);
|
|
1106
1194
|
});
|
|
1107
1195
|
|
|
1108
|
-
it('should include no refactor stories when capacity percent is 0 and feature work exists', () => {
|
|
1196
|
+
it('should include no refactor stories when capacity percent is 0 and feature work exists', async () => {
|
|
1109
1197
|
const scalingConfig = mkScalingConfig({
|
|
1110
1198
|
enabled: true,
|
|
1111
1199
|
capacity_percent: 0,
|
|
@@ -1114,12 +1202,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1114
1202
|
|
|
1115
1203
|
const feature = mkStory('Feature: add endpoint', 10, 10);
|
|
1116
1204
|
const refactor = mkStory('Refactor: split parser', 1, 1);
|
|
1117
|
-
const selected = selectStoriesForCapacity(
|
|
1205
|
+
const selected = (await selectStoriesForCapacity(
|
|
1206
|
+
[feature, refactor],
|
|
1207
|
+
scalingConfig
|
|
1208
|
+
)) as StoryRow[];
|
|
1118
1209
|
|
|
1119
1210
|
expect(selected.map(s => s.id)).toEqual([feature.id]);
|
|
1120
1211
|
});
|
|
1121
1212
|
|
|
1122
|
-
it('should allow at least one refactor point when percent is positive but rounded budget is zero', () => {
|
|
1213
|
+
it('should allow at least one refactor point when percent is positive but rounded budget is zero', async () => {
|
|
1123
1214
|
const scalingConfig = mkScalingConfig({
|
|
1124
1215
|
enabled: true,
|
|
1125
1216
|
capacity_percent: 10,
|
|
@@ -1128,12 +1219,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1128
1219
|
|
|
1129
1220
|
const feature = mkStory('Feature: tiny patch', 5, 5); // floor(5 * 0.1) = 0 -> min 1
|
|
1130
1221
|
const refactor = mkStory('Refactor: tighten types', 1, 1);
|
|
1131
|
-
const selected = selectStoriesForCapacity(
|
|
1222
|
+
const selected = (await selectStoriesForCapacity(
|
|
1223
|
+
[feature, refactor],
|
|
1224
|
+
scalingConfig
|
|
1225
|
+
)) as StoryRow[];
|
|
1132
1226
|
|
|
1133
1227
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactor.id]);
|
|
1134
1228
|
});
|
|
1135
1229
|
|
|
1136
|
-
it('should compute budget from total feature story points across multiple stories', () => {
|
|
1230
|
+
it('should compute budget from total feature story points across multiple stories', async () => {
|
|
1137
1231
|
const scalingConfig = mkScalingConfig({
|
|
1138
1232
|
enabled: true,
|
|
1139
1233
|
capacity_percent: 20,
|
|
@@ -1145,15 +1239,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1145
1239
|
const refactorA = mkStory('Refactor: A', 1, 1);
|
|
1146
1240
|
const refactorB = mkStory('Refactor: B', 1, 1);
|
|
1147
1241
|
const refactorC = mkStory('Refactor: C', 1, 1);
|
|
1148
|
-
const selected = selectStoriesForCapacity(
|
|
1242
|
+
const selected = (await selectStoriesForCapacity(
|
|
1149
1243
|
[featureA, featureB, refactorA, refactorB, refactorC],
|
|
1150
1244
|
scalingConfig
|
|
1151
|
-
) as StoryRow[];
|
|
1245
|
+
)) as StoryRow[];
|
|
1152
1246
|
|
|
1153
1247
|
expect(selected.map(s => s.id)).toEqual([featureA.id, featureB.id, refactorA.id, refactorB.id]);
|
|
1154
1248
|
});
|
|
1155
1249
|
|
|
1156
|
-
it('should skip a refactor story that exceeds remaining budget', () => {
|
|
1250
|
+
it('should skip a refactor story that exceeds remaining budget', async () => {
|
|
1157
1251
|
const scalingConfig = mkScalingConfig({
|
|
1158
1252
|
enabled: true,
|
|
1159
1253
|
capacity_percent: 20,
|
|
@@ -1162,15 +1256,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1162
1256
|
|
|
1163
1257
|
const feature = mkStory('Feature: A', 10, 10); // budget = 2
|
|
1164
1258
|
const refactorLarge = mkStory('Refactor: big cleanup', 3, 3);
|
|
1165
|
-
const selected = selectStoriesForCapacity(
|
|
1259
|
+
const selected = (await selectStoriesForCapacity(
|
|
1166
1260
|
[feature, refactorLarge],
|
|
1167
1261
|
scalingConfig
|
|
1168
|
-
) as StoryRow[];
|
|
1262
|
+
)) as StoryRow[];
|
|
1169
1263
|
|
|
1170
1264
|
expect(selected.map(s => s.id)).toEqual([feature.id]);
|
|
1171
1265
|
});
|
|
1172
1266
|
|
|
1173
|
-
it('should select a later smaller refactor story if an earlier one exceeds budget', () => {
|
|
1267
|
+
it('should select a later smaller refactor story if an earlier one exceeds budget', async () => {
|
|
1174
1268
|
const scalingConfig = mkScalingConfig({
|
|
1175
1269
|
enabled: true,
|
|
1176
1270
|
capacity_percent: 20,
|
|
@@ -1180,15 +1274,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1180
1274
|
const feature = mkStory('Feature: A', 10, 10); // budget = 2
|
|
1181
1275
|
const refactorLarge = mkStory('Refactor: big cleanup', 3, 3); // skipped
|
|
1182
1276
|
const refactorSmall = mkStory('Refactor: tiny cleanup', 2, 2); // fits
|
|
1183
|
-
const selected = selectStoriesForCapacity(
|
|
1277
|
+
const selected = (await selectStoriesForCapacity(
|
|
1184
1278
|
[feature, refactorLarge, refactorSmall],
|
|
1185
1279
|
scalingConfig
|
|
1186
|
-
) as StoryRow[];
|
|
1280
|
+
)) as StoryRow[];
|
|
1187
1281
|
|
|
1188
1282
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactorSmall.id]);
|
|
1189
1283
|
});
|
|
1190
1284
|
|
|
1191
|
-
it('should allow refactor-only queues when configured to allow without feature work', () => {
|
|
1285
|
+
it('should allow refactor-only queues when configured to allow without feature work', async () => {
|
|
1192
1286
|
const scalingConfig = mkScalingConfig({
|
|
1193
1287
|
enabled: true,
|
|
1194
1288
|
capacity_percent: 10,
|
|
@@ -1197,12 +1291,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1197
1291
|
|
|
1198
1292
|
const refactorA = mkStory('Refactor: A', 3, 3);
|
|
1199
1293
|
const refactorB = mkStory('Refactor: B', 5, 5);
|
|
1200
|
-
const selected = selectStoriesForCapacity(
|
|
1294
|
+
const selected = (await selectStoriesForCapacity(
|
|
1295
|
+
[refactorA, refactorB],
|
|
1296
|
+
scalingConfig
|
|
1297
|
+
)) as StoryRow[];
|
|
1201
1298
|
|
|
1202
1299
|
expect(selected.map(s => s.id)).toEqual([refactorA.id, refactorB.id]);
|
|
1203
1300
|
});
|
|
1204
1301
|
|
|
1205
|
-
it('should block refactor-only queues when allow_without_feature_work is false', () => {
|
|
1302
|
+
it('should block refactor-only queues when allow_without_feature_work is false', async () => {
|
|
1206
1303
|
const scalingConfig = mkScalingConfig({
|
|
1207
1304
|
enabled: true,
|
|
1208
1305
|
capacity_percent: 10,
|
|
@@ -1211,12 +1308,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1211
1308
|
|
|
1212
1309
|
const refactorA = mkStory('Refactor: A', 3, 3);
|
|
1213
1310
|
const refactorB = mkStory('Refactor: B', 5, 5);
|
|
1214
|
-
const selected = selectStoriesForCapacity(
|
|
1311
|
+
const selected = (await selectStoriesForCapacity(
|
|
1312
|
+
[refactorA, refactorB],
|
|
1313
|
+
scalingConfig
|
|
1314
|
+
)) as StoryRow[];
|
|
1215
1315
|
|
|
1216
1316
|
expect(selected).toHaveLength(0);
|
|
1217
1317
|
});
|
|
1218
1318
|
|
|
1219
|
-
it('should preserve order of selected stories', () => {
|
|
1319
|
+
it('should preserve order of selected stories', async () => {
|
|
1220
1320
|
const scalingConfig = mkScalingConfig({
|
|
1221
1321
|
enabled: true,
|
|
1222
1322
|
capacity_percent: 20,
|
|
@@ -1227,36 +1327,39 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1227
1327
|
const refactorA = mkStory('Refactor: A', 1, 1);
|
|
1228
1328
|
const featureB = mkStory('Feature: B', 5, 5);
|
|
1229
1329
|
const refactorB = mkStory('Refactor: B', 1, 1);
|
|
1230
|
-
const selected = selectStoriesForCapacity(
|
|
1330
|
+
const selected = (await selectStoriesForCapacity(
|
|
1231
1331
|
[featureA, refactorA, featureB, refactorB],
|
|
1232
1332
|
scalingConfig
|
|
1233
|
-
) as StoryRow[];
|
|
1333
|
+
)) as StoryRow[];
|
|
1234
1334
|
|
|
1235
1335
|
expect(selected.map(s => s.id)).toEqual([featureA.id, refactorA.id, featureB.id, refactorB.id]);
|
|
1236
1336
|
});
|
|
1237
1337
|
|
|
1238
|
-
it('should default to disabled behavior when refactor config is missing', () => {
|
|
1338
|
+
it('should default to disabled behavior when refactor config is missing', async () => {
|
|
1239
1339
|
const scalingConfig = mkScalingConfig();
|
|
1240
1340
|
|
|
1241
1341
|
const feature = mkStory('Feature: A', 5, 5);
|
|
1242
1342
|
const refactor = mkStory('Refactor: A', 1, 1);
|
|
1243
|
-
const selected = selectStoriesForCapacity(
|
|
1343
|
+
const selected = (await selectStoriesForCapacity(
|
|
1344
|
+
[feature, refactor],
|
|
1345
|
+
scalingConfig
|
|
1346
|
+
)) as StoryRow[];
|
|
1244
1347
|
|
|
1245
1348
|
expect(selected.map(s => s.id)).toEqual([feature.id]);
|
|
1246
1349
|
});
|
|
1247
1350
|
|
|
1248
|
-
it('should return an empty array when no stories are provided', () => {
|
|
1351
|
+
it('should return an empty array when no stories are provided', async () => {
|
|
1249
1352
|
const scalingConfig = mkScalingConfig({
|
|
1250
1353
|
enabled: true,
|
|
1251
1354
|
capacity_percent: 50,
|
|
1252
1355
|
allow_without_feature_work: true,
|
|
1253
1356
|
});
|
|
1254
1357
|
|
|
1255
|
-
const selected = selectStoriesForCapacity([], scalingConfig) as StoryRow[];
|
|
1358
|
+
const selected = (await selectStoriesForCapacity([], scalingConfig)) as StoryRow[];
|
|
1256
1359
|
expect(selected).toEqual([]);
|
|
1257
1360
|
});
|
|
1258
1361
|
|
|
1259
|
-
it('should include refactor stories when cumulative points exactly match budget', () => {
|
|
1362
|
+
it('should include refactor stories when cumulative points exactly match budget', async () => {
|
|
1260
1363
|
const scalingConfig = mkScalingConfig({
|
|
1261
1364
|
enabled: true,
|
|
1262
1365
|
capacity_percent: 30,
|
|
@@ -1266,15 +1369,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1266
1369
|
const feature = mkStory('Feature: A', 10, 10); // budget = 3
|
|
1267
1370
|
const refactorA = mkStory('Refactor: A', 1, 1);
|
|
1268
1371
|
const refactorB = mkStory('Refactor: B', 2, 2);
|
|
1269
|
-
const selected = selectStoriesForCapacity(
|
|
1372
|
+
const selected = (await selectStoriesForCapacity(
|
|
1270
1373
|
[feature, refactorA, refactorB],
|
|
1271
1374
|
scalingConfig
|
|
1272
|
-
) as StoryRow[];
|
|
1375
|
+
)) as StoryRow[];
|
|
1273
1376
|
|
|
1274
1377
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactorA.id, refactorB.id]);
|
|
1275
1378
|
});
|
|
1276
1379
|
|
|
1277
|
-
it('should continue selecting later refactors after partially consuming budget and skipping a too-large one', () => {
|
|
1380
|
+
it('should continue selecting later refactors after partially consuming budget and skipping a too-large one', async () => {
|
|
1278
1381
|
const scalingConfig = mkScalingConfig({
|
|
1279
1382
|
enabled: true,
|
|
1280
1383
|
capacity_percent: 50,
|
|
@@ -1285,15 +1388,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1285
1388
|
const refactorA = mkStory('Refactor: A', 2, 2); // used = 2
|
|
1286
1389
|
const refactorLarge = mkStory('Refactor: Large', 4, 4); // skipped (2 + 4 > 5)
|
|
1287
1390
|
const refactorB = mkStory('Refactor: B', 3, 3); // used = 5
|
|
1288
|
-
const selected = selectStoriesForCapacity(
|
|
1391
|
+
const selected = (await selectStoriesForCapacity(
|
|
1289
1392
|
[feature, refactorA, refactorLarge, refactorB],
|
|
1290
1393
|
scalingConfig
|
|
1291
|
-
) as StoryRow[];
|
|
1394
|
+
)) as StoryRow[];
|
|
1292
1395
|
|
|
1293
1396
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactorA.id, refactorB.id]);
|
|
1294
1397
|
});
|
|
1295
1398
|
|
|
1296
|
-
it('should derive feature budget from complexity when story_points are not set', () => {
|
|
1399
|
+
it('should derive feature budget from complexity when story_points are not set', async () => {
|
|
1297
1400
|
const scalingConfig = mkScalingConfig({
|
|
1298
1401
|
enabled: true,
|
|
1299
1402
|
capacity_percent: 20,
|
|
@@ -1304,15 +1407,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1304
1407
|
const featureB = mkStory('Feature: B', null, 4); // feature total = 10, budget = 2
|
|
1305
1408
|
const refactorA = mkStory('Refactor: A', 1, 1);
|
|
1306
1409
|
const refactorB = mkStory('Refactor: B', 2, 2);
|
|
1307
|
-
const selected = selectStoriesForCapacity(
|
|
1410
|
+
const selected = (await selectStoriesForCapacity(
|
|
1308
1411
|
[featureA, featureB, refactorA, refactorB],
|
|
1309
1412
|
scalingConfig
|
|
1310
|
-
) as StoryRow[];
|
|
1413
|
+
)) as StoryRow[];
|
|
1311
1414
|
|
|
1312
1415
|
expect(selected.map(s => s.id)).toEqual([featureA.id, featureB.id, refactorA.id]);
|
|
1313
1416
|
});
|
|
1314
1417
|
|
|
1315
|
-
it('should allow one point of refactor work when feature stories have no explicit points', () => {
|
|
1418
|
+
it('should allow one point of refactor work when feature stories have no explicit points', async () => {
|
|
1316
1419
|
const scalingConfig = mkScalingConfig({
|
|
1317
1420
|
enabled: true,
|
|
1318
1421
|
capacity_percent: 10,
|
|
@@ -1322,15 +1425,15 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1322
1425
|
const feature = mkStory('Feature: A', null, null); // defaults to 1, floor(1 * 0.1)=0 -> min 1
|
|
1323
1426
|
const refactorA = mkStory('Refactor: A', 1, 1);
|
|
1324
1427
|
const refactorB = mkStory('Refactor: B', 1, 1);
|
|
1325
|
-
const selected = selectStoriesForCapacity(
|
|
1428
|
+
const selected = (await selectStoriesForCapacity(
|
|
1326
1429
|
[feature, refactorA, refactorB],
|
|
1327
1430
|
scalingConfig
|
|
1328
|
-
) as StoryRow[];
|
|
1431
|
+
)) as StoryRow[];
|
|
1329
1432
|
|
|
1330
1433
|
expect(selected.map(s => s.id)).toEqual([feature.id, refactorA.id]);
|
|
1331
1434
|
});
|
|
1332
1435
|
|
|
1333
|
-
it('should ignore capacity_percent for refactor-only queues when allow_without_feature_work is true', () => {
|
|
1436
|
+
it('should ignore capacity_percent for refactor-only queues when allow_without_feature_work is true', async () => {
|
|
1334
1437
|
const scalingConfig = mkScalingConfig({
|
|
1335
1438
|
enabled: true,
|
|
1336
1439
|
capacity_percent: 0,
|
|
@@ -1339,38 +1442,53 @@ describe('Scheduler Refactor Policy Test Matrix', () => {
|
|
|
1339
1442
|
|
|
1340
1443
|
const refactorA = mkStory('Refactor: A', 2, 2);
|
|
1341
1444
|
const refactorB = mkStory('Refactor: B', 4, 4);
|
|
1342
|
-
const selected = selectStoriesForCapacity(
|
|
1445
|
+
const selected = (await selectStoriesForCapacity(
|
|
1446
|
+
[refactorA, refactorB],
|
|
1447
|
+
scalingConfig
|
|
1448
|
+
)) as StoryRow[];
|
|
1343
1449
|
|
|
1344
1450
|
expect(selected.map(s => s.id)).toEqual([refactorA.id, refactorB.id]);
|
|
1345
1451
|
});
|
|
1346
1452
|
});
|
|
1347
1453
|
|
|
1348
1454
|
describe('Scheduler Agent Selection', () => {
|
|
1349
|
-
it('should select agent with least workload from multiple agents', () => {
|
|
1350
|
-
const team = createTeam(db, {
|
|
1455
|
+
it('should select agent with least workload from multiple agents', async () => {
|
|
1456
|
+
const team = await createTeam(db, {
|
|
1351
1457
|
name: 'Test Team',
|
|
1352
1458
|
repoUrl: 'https://github.com/test/repo',
|
|
1353
1459
|
repoPath: 'test',
|
|
1354
1460
|
});
|
|
1355
1461
|
|
|
1356
1462
|
// Create three junior agents with different workloads
|
|
1357
|
-
db.run(
|
|
1463
|
+
db.db.run(
|
|
1358
1464
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('junior-1', 'junior', '${team.id}', 'idle')`
|
|
1359
1465
|
);
|
|
1360
|
-
db.run(
|
|
1466
|
+
db.db.run(
|
|
1361
1467
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('junior-2', 'junior', '${team.id}', 'idle')`
|
|
1362
1468
|
);
|
|
1363
|
-
db.run(
|
|
1469
|
+
db.db.run(
|
|
1364
1470
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('junior-3', 'junior', '${team.id}', 'idle')`
|
|
1365
1471
|
);
|
|
1366
1472
|
|
|
1367
1473
|
// Give junior-1 two stories, junior-2 one story, junior-3 zero stories
|
|
1368
|
-
const story1 = createStory(db, {
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1474
|
+
const story1 = await createStory(db, {
|
|
1475
|
+
teamId: team.id,
|
|
1476
|
+
title: 'Story 1',
|
|
1477
|
+
description: 'Test',
|
|
1478
|
+
});
|
|
1479
|
+
const story2 = await createStory(db, {
|
|
1480
|
+
teamId: team.id,
|
|
1481
|
+
title: 'Story 2',
|
|
1482
|
+
description: 'Test',
|
|
1483
|
+
});
|
|
1484
|
+
const story3 = await createStory(db, {
|
|
1485
|
+
teamId: team.id,
|
|
1486
|
+
title: 'Story 3',
|
|
1487
|
+
description: 'Test',
|
|
1488
|
+
});
|
|
1489
|
+
await updateStory(db, story1.id, { assignedAgentId: 'junior-1', status: 'in_progress' });
|
|
1490
|
+
await updateStory(db, story2.id, { assignedAgentId: 'junior-1', status: 'in_progress' });
|
|
1491
|
+
await updateStory(db, story3.id, { assignedAgentId: 'junior-2', status: 'in_progress' });
|
|
1374
1492
|
|
|
1375
1493
|
const agents = [
|
|
1376
1494
|
{
|
|
@@ -1420,14 +1538,14 @@ describe('Scheduler Agent Selection', () => {
|
|
|
1420
1538
|
},
|
|
1421
1539
|
];
|
|
1422
1540
|
|
|
1423
|
-
const selected = selectAgentWithLeastWorkload(db, agents);
|
|
1541
|
+
const selected = await selectAgentWithLeastWorkload(db, agents);
|
|
1424
1542
|
|
|
1425
1543
|
// Should select junior-3 who has zero stories
|
|
1426
1544
|
expect(selected.id).toBe('junior-3');
|
|
1427
1545
|
});
|
|
1428
1546
|
|
|
1429
|
-
it('should select first agent when all have equal workload', () => {
|
|
1430
|
-
const team = createTeam(db, {
|
|
1547
|
+
it('should select first agent when all have equal workload', async () => {
|
|
1548
|
+
const team = await createTeam(db, {
|
|
1431
1549
|
name: 'Test Team',
|
|
1432
1550
|
repoUrl: 'https://github.com/test/repo',
|
|
1433
1551
|
repoPath: 'test',
|
|
@@ -1466,86 +1584,94 @@ describe('Scheduler Agent Selection', () => {
|
|
|
1466
1584
|
},
|
|
1467
1585
|
];
|
|
1468
1586
|
|
|
1469
|
-
const selected = selectAgentWithLeastWorkload(db, agents);
|
|
1587
|
+
const selected = await selectAgentWithLeastWorkload(db, agents);
|
|
1470
1588
|
|
|
1471
1589
|
expect(selected.id).toBe('agent-1');
|
|
1472
1590
|
});
|
|
1473
1591
|
|
|
1474
|
-
it('should calculate agent workload correctly', () => {
|
|
1475
|
-
const team = createTeam(db, {
|
|
1592
|
+
it('should calculate agent workload correctly', async () => {
|
|
1593
|
+
const team = await createTeam(db, {
|
|
1476
1594
|
name: 'Test Team',
|
|
1477
1595
|
repoUrl: 'https://github.com/test/repo',
|
|
1478
1596
|
repoPath: 'test',
|
|
1479
1597
|
});
|
|
1480
|
-
db.run(
|
|
1598
|
+
db.db.run(
|
|
1481
1599
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('agent-1', 'junior', '${team.id}', 'idle')`
|
|
1482
1600
|
);
|
|
1483
1601
|
|
|
1484
|
-
const story1 = createStory(db, {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1602
|
+
const story1 = await createStory(db, {
|
|
1603
|
+
teamId: team.id,
|
|
1604
|
+
title: 'Story 1',
|
|
1605
|
+
description: 'Test',
|
|
1606
|
+
});
|
|
1607
|
+
const story2 = await createStory(db, {
|
|
1608
|
+
teamId: team.id,
|
|
1609
|
+
title: 'Story 2',
|
|
1610
|
+
description: 'Test',
|
|
1611
|
+
});
|
|
1612
|
+
await updateStory(db, story1.id, { assignedAgentId: 'agent-1', status: 'in_progress' });
|
|
1613
|
+
await updateStory(db, story2.id, { assignedAgentId: 'agent-1', status: 'in_progress' });
|
|
1488
1614
|
|
|
1489
|
-
const workload = getAgentWorkload(db, 'agent-1');
|
|
1615
|
+
const workload = await getAgentWorkload(db, 'agent-1');
|
|
1490
1616
|
|
|
1491
1617
|
expect(workload).toBe(2);
|
|
1492
1618
|
});
|
|
1493
1619
|
|
|
1494
|
-
it('should return zero workload for agent with no stories', () => {
|
|
1495
|
-
const team = createTeam(db, {
|
|
1620
|
+
it('should return zero workload for agent with no stories', async () => {
|
|
1621
|
+
const team = await createTeam(db, {
|
|
1496
1622
|
name: 'Test Team',
|
|
1497
1623
|
repoUrl: 'https://github.com/test/repo',
|
|
1498
1624
|
repoPath: 'test',
|
|
1499
1625
|
});
|
|
1500
|
-
db.run(
|
|
1626
|
+
db.db.run(
|
|
1501
1627
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('agent-1', 'junior', '${team.id}', 'idle')`
|
|
1502
1628
|
);
|
|
1503
1629
|
|
|
1504
|
-
const workload = getAgentWorkload(db, 'agent-1');
|
|
1630
|
+
const workload = await getAgentWorkload(db, 'agent-1');
|
|
1505
1631
|
|
|
1506
1632
|
expect(workload).toBe(0);
|
|
1507
1633
|
});
|
|
1508
1634
|
});
|
|
1509
1635
|
|
|
1510
1636
|
describe('Scheduler Complexity Routing', () => {
|
|
1511
|
-
it('should route low complexity stories to junior agents', () => {
|
|
1637
|
+
it('should route low complexity stories to junior agents', async () => {
|
|
1512
1638
|
// Test the routing logic: complexity <= junior_max_complexity goes to junior
|
|
1513
1639
|
const complexity = 2;
|
|
1514
1640
|
expect(complexity).toBeLessThanOrEqual(mockConfig.scaling.junior_max_complexity);
|
|
1515
1641
|
});
|
|
1516
1642
|
|
|
1517
|
-
it('should route medium complexity stories to intermediate agents', () => {
|
|
1643
|
+
it('should route medium complexity stories to intermediate agents', async () => {
|
|
1518
1644
|
// Test the routing logic: complexity between junior and intermediate thresholds
|
|
1519
1645
|
const complexity = 4;
|
|
1520
1646
|
expect(complexity).toBeGreaterThan(mockConfig.scaling.junior_max_complexity);
|
|
1521
1647
|
expect(complexity).toBeLessThanOrEqual(mockConfig.scaling.intermediate_max_complexity);
|
|
1522
1648
|
});
|
|
1523
1649
|
|
|
1524
|
-
it('should route high complexity stories to senior agents', () => {
|
|
1650
|
+
it('should route high complexity stories to senior agents', async () => {
|
|
1525
1651
|
// Test the routing logic: complexity > intermediate_max_complexity goes to senior
|
|
1526
1652
|
const complexity = 8;
|
|
1527
1653
|
expect(complexity).toBeGreaterThan(mockConfig.scaling.intermediate_max_complexity);
|
|
1528
1654
|
});
|
|
1529
1655
|
|
|
1530
|
-
it('should handle edge case at junior boundary', () => {
|
|
1656
|
+
it('should handle edge case at junior boundary', async () => {
|
|
1531
1657
|
// Complexity exactly at junior_max_complexity should still go to junior
|
|
1532
1658
|
const complexity = 3;
|
|
1533
1659
|
expect(complexity).toBeLessThanOrEqual(mockConfig.scaling.junior_max_complexity);
|
|
1534
1660
|
});
|
|
1535
1661
|
|
|
1536
|
-
it('should handle edge case at intermediate boundary', () => {
|
|
1662
|
+
it('should handle edge case at intermediate boundary', async () => {
|
|
1537
1663
|
// Complexity exactly at intermediate_max_complexity should still go to intermediate
|
|
1538
1664
|
const complexity = 5;
|
|
1539
1665
|
expect(complexity).toBeLessThanOrEqual(mockConfig.scaling.intermediate_max_complexity);
|
|
1540
1666
|
});
|
|
1541
1667
|
|
|
1542
|
-
it('should use config values for routing thresholds', () => {
|
|
1668
|
+
it('should use config values for routing thresholds', async () => {
|
|
1543
1669
|
// Verify config values are set correctly for routing logic
|
|
1544
1670
|
expect(mockConfig.scaling.junior_max_complexity).toBe(3);
|
|
1545
1671
|
expect(mockConfig.scaling.intermediate_max_complexity).toBe(5);
|
|
1546
1672
|
});
|
|
1547
1673
|
|
|
1548
|
-
it('should default to complexity 5 when not specified', () => {
|
|
1674
|
+
it('should default to complexity 5 when not specified', async () => {
|
|
1549
1675
|
// Test default complexity value used in assignStories
|
|
1550
1676
|
const complexity = null;
|
|
1551
1677
|
const defaultComplexity = complexity || 5;
|
|
@@ -1554,115 +1680,131 @@ describe('Scheduler Complexity Routing', () => {
|
|
|
1554
1680
|
});
|
|
1555
1681
|
|
|
1556
1682
|
describe('Scheduler Story Assignment Prevention', () => {
|
|
1557
|
-
it('should prevent duplicate story assignments', () => {
|
|
1558
|
-
const team = createTeam(db, {
|
|
1683
|
+
it('should prevent duplicate story assignments', async () => {
|
|
1684
|
+
const team = await createTeam(db, {
|
|
1559
1685
|
name: 'Test Team',
|
|
1560
1686
|
repoUrl: 'https://github.com/test/repo',
|
|
1561
1687
|
repoPath: 'test',
|
|
1562
1688
|
});
|
|
1563
|
-
db.run(
|
|
1689
|
+
db.db.run(
|
|
1564
1690
|
`INSERT INTO agents (id, type, team_id, status) VALUES ('agent-1', 'junior', '${team.id}', 'idle')`
|
|
1565
1691
|
);
|
|
1566
1692
|
|
|
1567
1693
|
// Create a story and assign it
|
|
1568
|
-
const story = createStory(db, {
|
|
1694
|
+
const story = await createStory(db, {
|
|
1569
1695
|
teamId: team.id,
|
|
1570
1696
|
title: 'Story',
|
|
1571
1697
|
description: 'Test',
|
|
1572
1698
|
});
|
|
1573
|
-
updateStory(db, story.id, { complexityScore: 2, status: 'planned' });
|
|
1699
|
+
await updateStory(db, story.id, { complexityScore: 2, status: 'planned' });
|
|
1574
1700
|
|
|
1575
1701
|
// First assignment
|
|
1576
|
-
updateStory(db, story.id, { assignedAgentId: 'agent-1', status: 'in_progress' });
|
|
1702
|
+
await updateStory(db, story.id, { assignedAgentId: 'agent-1', status: 'in_progress' });
|
|
1577
1703
|
|
|
1578
1704
|
// Verify the story is now assigned
|
|
1579
|
-
const result = db.exec(`SELECT assigned_agent_id FROM stories WHERE id = '${story.id}'`);
|
|
1705
|
+
const result = db.db.exec(`SELECT assigned_agent_id FROM stories WHERE id = '${story.id}'`);
|
|
1580
1706
|
expect(result[0].values[0][0]).toBe('agent-1');
|
|
1581
1707
|
});
|
|
1582
1708
|
|
|
1583
|
-
it('should verify story assignment changes status', () => {
|
|
1584
|
-
const team = createTeam(db, {
|
|
1709
|
+
it('should verify story assignment changes status', async () => {
|
|
1710
|
+
const team = await createTeam(db, {
|
|
1585
1711
|
name: 'Test Team',
|
|
1586
1712
|
repoUrl: 'https://github.com/test/repo',
|
|
1587
1713
|
repoPath: 'test',
|
|
1588
1714
|
});
|
|
1589
1715
|
|
|
1590
|
-
const story = createStory(db, {
|
|
1716
|
+
const story = await createStory(db, {
|
|
1591
1717
|
teamId: team.id,
|
|
1592
1718
|
title: 'Story',
|
|
1593
1719
|
description: 'Test',
|
|
1594
1720
|
});
|
|
1595
|
-
updateStory(db, story.id, { status: 'planned' });
|
|
1721
|
+
await updateStory(db, story.id, { status: 'planned' });
|
|
1596
1722
|
|
|
1597
1723
|
// Change status to in_progress
|
|
1598
|
-
updateStory(db, story.id, { status: 'in_progress' });
|
|
1724
|
+
await updateStory(db, story.id, { status: 'in_progress' });
|
|
1599
1725
|
|
|
1600
1726
|
// Verify status changed
|
|
1601
|
-
const result = db.exec(`SELECT status FROM stories WHERE id = '${story.id}'`);
|
|
1727
|
+
const result = db.db.exec(`SELECT status FROM stories WHERE id = '${story.id}'`);
|
|
1602
1728
|
expect(result[0].values[0][0]).toBe('in_progress');
|
|
1603
1729
|
});
|
|
1604
1730
|
|
|
1605
|
-
it('should skip stories with unsatisfied dependencies', () => {
|
|
1606
|
-
const team = createTeam(db, {
|
|
1731
|
+
it('should skip stories with unsatisfied dependencies', async () => {
|
|
1732
|
+
const team = await createTeam(db, {
|
|
1607
1733
|
name: 'Test Team',
|
|
1608
1734
|
repoUrl: 'https://github.com/test/repo',
|
|
1609
1735
|
repoPath: 'test',
|
|
1610
1736
|
});
|
|
1611
|
-
const storyA = createStory(db, {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1737
|
+
const storyA = await createStory(db, {
|
|
1738
|
+
teamId: team.id,
|
|
1739
|
+
title: 'Story A',
|
|
1740
|
+
description: 'Test',
|
|
1741
|
+
});
|
|
1742
|
+
await updateStory(db, storyA.id, { status: 'planned' });
|
|
1743
|
+
const storyB = await createStory(db, {
|
|
1744
|
+
teamId: team.id,
|
|
1745
|
+
title: 'Story B',
|
|
1746
|
+
description: 'Test',
|
|
1747
|
+
});
|
|
1748
|
+
await updateStory(db, storyB.id, { status: 'planned' });
|
|
1615
1749
|
|
|
1616
1750
|
// B depends on A, but A is still planned
|
|
1617
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
1751
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
1618
1752
|
|
|
1619
1753
|
// B should not be ready for assignment because A is not merged yet
|
|
1620
|
-
const satisfied = areDependenciesSatisfied(db, storyB.id);
|
|
1754
|
+
const satisfied = await areDependenciesSatisfied(db, storyB.id);
|
|
1621
1755
|
expect(satisfied).toBe(false);
|
|
1622
1756
|
});
|
|
1623
1757
|
|
|
1624
|
-
it('should allow stories when dependencies are in terminal states', () => {
|
|
1625
|
-
const team = createTeam(db, {
|
|
1758
|
+
it('should allow stories when dependencies are in terminal states', async () => {
|
|
1759
|
+
const team = await createTeam(db, {
|
|
1626
1760
|
name: 'Test Team',
|
|
1627
1761
|
repoUrl: 'https://github.com/test/repo',
|
|
1628
1762
|
repoPath: 'test',
|
|
1629
1763
|
});
|
|
1630
1764
|
|
|
1631
1765
|
// Test with merged status (terminal state)
|
|
1632
|
-
const storyA = createStory(db, {
|
|
1633
|
-
|
|
1766
|
+
const storyA = await createStory(db, {
|
|
1767
|
+
teamId: team.id,
|
|
1768
|
+
title: 'Story A',
|
|
1769
|
+
description: 'Test',
|
|
1770
|
+
});
|
|
1771
|
+
const storyB = await createStory(db, {
|
|
1772
|
+
teamId: team.id,
|
|
1773
|
+
title: 'Story B',
|
|
1774
|
+
description: 'Test',
|
|
1775
|
+
});
|
|
1634
1776
|
|
|
1635
1777
|
// Update A to merged status
|
|
1636
|
-
updateStory(db, storyA.id, { status: 'merged' });
|
|
1778
|
+
await updateStory(db, storyA.id, { status: 'merged' });
|
|
1637
1779
|
|
|
1638
1780
|
// B depends on A, and A is merged
|
|
1639
|
-
addStoryDependency(db, storyB.id, storyA.id);
|
|
1781
|
+
await addStoryDependency(db, storyB.id, storyA.id);
|
|
1640
1782
|
|
|
1641
1783
|
// B should be ready for assignment
|
|
1642
|
-
const satisfied = areDependenciesSatisfied(db, storyB.id);
|
|
1784
|
+
const satisfied = await areDependenciesSatisfied(db, storyB.id);
|
|
1643
1785
|
expect(satisfied).toBe(true);
|
|
1644
1786
|
});
|
|
1645
1787
|
|
|
1646
|
-
it('should map claude model IDs to claude runtime shorthands', () => {
|
|
1788
|
+
it('should map claude model IDs to claude runtime shorthands', async () => {
|
|
1647
1789
|
const runtimeModel = (scheduler as any).getRuntimeModel('claude-sonnet-4-5-20250929', 'claude');
|
|
1648
1790
|
expect(runtimeModel).toBe('sonnet');
|
|
1649
1791
|
});
|
|
1650
1792
|
|
|
1651
|
-
it('should remap unsupported codex mini model and preserve gemini runtime model', () => {
|
|
1793
|
+
it('should remap unsupported codex mini model and preserve gemini runtime model', async () => {
|
|
1652
1794
|
const codexModel = (scheduler as any).getRuntimeModel('gpt-4o-mini', 'codex');
|
|
1653
1795
|
const geminiModel = (scheduler as any).getRuntimeModel('gemini-2.5-pro', 'gemini');
|
|
1654
1796
|
expect(codexModel).toBe('gpt-5.2-codex');
|
|
1655
1797
|
expect(geminiModel).toBe('gemini-2.5-pro');
|
|
1656
1798
|
});
|
|
1657
1799
|
|
|
1658
|
-
it('should not fallback unknown claude models to haiku', () => {
|
|
1800
|
+
it('should not fallback unknown claude models to haiku', async () => {
|
|
1659
1801
|
const runtimeModel = (scheduler as any).getRuntimeModel('claude-custom-model', 'claude');
|
|
1660
1802
|
expect(runtimeModel).toBe('claude-custom-model');
|
|
1661
1803
|
});
|
|
1662
1804
|
|
|
1663
|
-
it('should detect godmode is active when an active requirement has godmode enabled', () => {
|
|
1805
|
+
it('should detect godmode is active when an active requirement has godmode enabled', async () => {
|
|
1664
1806
|
// Create a requirement with godmode and set it to planning status
|
|
1665
|
-
const req = createRequirement(db, {
|
|
1807
|
+
const req = await createRequirement(db, {
|
|
1666
1808
|
title: 'Godmode Requirement',
|
|
1667
1809
|
description: 'Test requirement with godmode',
|
|
1668
1810
|
godmode: true,
|
|
@@ -1670,19 +1812,19 @@ describe('Scheduler Story Assignment Prevention', () => {
|
|
|
1670
1812
|
db.run(`UPDATE requirements SET status = 'planning' WHERE id = ?`, [req.id]);
|
|
1671
1813
|
|
|
1672
1814
|
// Godmode should be detected as active
|
|
1673
|
-
const isGodmodeActive = (scheduler as any).isGodmodeActive();
|
|
1815
|
+
const isGodmodeActive = await (scheduler as any).isGodmodeActive();
|
|
1674
1816
|
expect(isGodmodeActive).toBe(true);
|
|
1675
1817
|
});
|
|
1676
1818
|
|
|
1677
|
-
it('should detect godmode even when all stories have moved to in_progress', () => {
|
|
1678
|
-
const team = createTeam(db, {
|
|
1819
|
+
it('should detect godmode even when all stories have moved to in_progress', async () => {
|
|
1820
|
+
const team = await createTeam(db, {
|
|
1679
1821
|
name: 'Test Team',
|
|
1680
1822
|
repoUrl: 'https://github.com/test/repo',
|
|
1681
1823
|
repoPath: 'test',
|
|
1682
1824
|
});
|
|
1683
1825
|
|
|
1684
1826
|
// Create a godmode requirement in in_progress status
|
|
1685
|
-
const req = createRequirement(db, {
|
|
1827
|
+
const req = await createRequirement(db, {
|
|
1686
1828
|
title: 'Godmode Requirement',
|
|
1687
1829
|
description: 'Test requirement with godmode',
|
|
1688
1830
|
godmode: true,
|
|
@@ -1690,22 +1832,22 @@ describe('Scheduler Story Assignment Prevention', () => {
|
|
|
1690
1832
|
db.run(`UPDATE requirements SET status = 'in_progress' WHERE id = ?`, [req.id]);
|
|
1691
1833
|
|
|
1692
1834
|
// Create a story that has moved to in_progress (no longer planned)
|
|
1693
|
-
const story = createStory(db, {
|
|
1835
|
+
const story = await createStory(db, {
|
|
1694
1836
|
requirementId: req.id,
|
|
1695
1837
|
teamId: team.id,
|
|
1696
1838
|
title: 'Godmode Story',
|
|
1697
1839
|
description: 'Test',
|
|
1698
1840
|
});
|
|
1699
|
-
updateStory(db, story.id, { status: 'in_progress' });
|
|
1841
|
+
await updateStory(db, story.id, { status: 'in_progress' });
|
|
1700
1842
|
|
|
1701
1843
|
// Godmode should still be active even though no stories are planned
|
|
1702
|
-
const isGodmodeActive = (scheduler as any).isGodmodeActive();
|
|
1844
|
+
const isGodmodeActive = await (scheduler as any).isGodmodeActive();
|
|
1703
1845
|
expect(isGodmodeActive).toBe(true);
|
|
1704
1846
|
});
|
|
1705
1847
|
|
|
1706
|
-
it('should not detect godmode when no requirements have godmode enabled', () => {
|
|
1848
|
+
it('should not detect godmode when no requirements have godmode enabled', async () => {
|
|
1707
1849
|
// Create a normal requirement (without godmode) in planning status
|
|
1708
|
-
const req = createRequirement(db, {
|
|
1850
|
+
const req = await createRequirement(db, {
|
|
1709
1851
|
title: 'Normal Requirement',
|
|
1710
1852
|
description: 'Test requirement without godmode',
|
|
1711
1853
|
godmode: false,
|
|
@@ -1713,13 +1855,13 @@ describe('Scheduler Story Assignment Prevention', () => {
|
|
|
1713
1855
|
db.run(`UPDATE requirements SET status = 'planning' WHERE id = ?`, [req.id]);
|
|
1714
1856
|
|
|
1715
1857
|
// Godmode should not be detected as active
|
|
1716
|
-
const isGodmodeActive = (scheduler as any).isGodmodeActive();
|
|
1858
|
+
const isGodmodeActive = await (scheduler as any).isGodmodeActive();
|
|
1717
1859
|
expect(isGodmodeActive).toBe(false);
|
|
1718
1860
|
});
|
|
1719
1861
|
|
|
1720
|
-
it('should not detect godmode when godmode requirement is completed', () => {
|
|
1862
|
+
it('should not detect godmode when godmode requirement is completed', async () => {
|
|
1721
1863
|
// Create a godmode requirement that is already completed
|
|
1722
|
-
const req = createRequirement(db, {
|
|
1864
|
+
const req = await createRequirement(db, {
|
|
1723
1865
|
title: 'Godmode Requirement',
|
|
1724
1866
|
description: 'Test requirement with godmode',
|
|
1725
1867
|
godmode: true,
|
|
@@ -1727,34 +1869,34 @@ describe('Scheduler Story Assignment Prevention', () => {
|
|
|
1727
1869
|
db.run(`UPDATE requirements SET status = 'completed' WHERE id = ?`, [req.id]);
|
|
1728
1870
|
|
|
1729
1871
|
// Godmode should not be active for completed requirements
|
|
1730
|
-
const isGodmodeActive = (scheduler as any).isGodmodeActive();
|
|
1872
|
+
const isGodmodeActive = await (scheduler as any).isGodmodeActive();
|
|
1731
1873
|
expect(isGodmodeActive).toBe(false);
|
|
1732
1874
|
});
|
|
1733
1875
|
|
|
1734
|
-
it('should not detect godmode when no requirements exist', () => {
|
|
1876
|
+
it('should not detect godmode when no requirements exist', async () => {
|
|
1735
1877
|
// No requirements created, so godmode cannot be active
|
|
1736
|
-
const isGodmodeActive = (scheduler as any).isGodmodeActive();
|
|
1878
|
+
const isGodmodeActive = await (scheduler as any).isGodmodeActive();
|
|
1737
1879
|
expect(isGodmodeActive).toBe(false);
|
|
1738
1880
|
});
|
|
1739
1881
|
});
|
|
1740
1882
|
|
|
1741
1883
|
describe('Scheduler Agent Reassignment for Working Agents with NULL currentStoryId', () => {
|
|
1742
|
-
it('should consider working agents with null current_story_id as available for assignment', () => {
|
|
1743
|
-
const team = createTeam(db, {
|
|
1884
|
+
it('should consider working agents with null current_story_id as available for assignment', async () => {
|
|
1885
|
+
const team = await createTeam(db, {
|
|
1744
1886
|
name: 'Test Team',
|
|
1745
1887
|
repoUrl: 'https://github.com/test/repo',
|
|
1746
1888
|
repoPath: 'test',
|
|
1747
1889
|
});
|
|
1748
1890
|
|
|
1749
1891
|
// Create a working agent with no current story (effectively idle)
|
|
1750
|
-
db.run(
|
|
1892
|
+
db.db.run(
|
|
1751
1893
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
1752
1894
|
VALUES (?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
1753
1895
|
['senior-orphan-1', 'senior', team.id, 'working']
|
|
1754
1896
|
);
|
|
1755
1897
|
|
|
1756
1898
|
// Query agents using the same filter logic from assignStories
|
|
1757
|
-
const result = db.exec(
|
|
1899
|
+
const result = db.db.exec(
|
|
1758
1900
|
`SELECT id, type, status, current_story_id FROM agents
|
|
1759
1901
|
WHERE team_id = '${team.id}' AND type != 'qa'
|
|
1760
1902
|
AND (status = 'idle' OR (status = 'working' AND current_story_id IS NULL))`
|
|
@@ -1764,24 +1906,24 @@ describe('Scheduler Agent Reassignment for Working Agents with NULL currentStory
|
|
|
1764
1906
|
expect(result[0].values[0][0]).toBe('senior-orphan-1');
|
|
1765
1907
|
});
|
|
1766
1908
|
|
|
1767
|
-
it('should not consider working agents with a current story as available', () => {
|
|
1768
|
-
const team = createTeam(db, {
|
|
1909
|
+
it('should not consider working agents with a current story as available', async () => {
|
|
1910
|
+
const team = await createTeam(db, {
|
|
1769
1911
|
name: 'Test Team',
|
|
1770
1912
|
repoUrl: 'https://github.com/test/repo',
|
|
1771
1913
|
repoPath: 'test',
|
|
1772
1914
|
});
|
|
1773
1915
|
|
|
1774
|
-
const story = createStory(db, { teamId: team.id, title: 'Active', description: 'Test' });
|
|
1916
|
+
const story = await createStory(db, { teamId: team.id, title: 'Active', description: 'Test' });
|
|
1775
1917
|
|
|
1776
1918
|
// Create a working agent with a current story
|
|
1777
|
-
db.run(
|
|
1919
|
+
db.db.run(
|
|
1778
1920
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
1779
1921
|
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
1780
1922
|
['senior-busy-1', 'senior', team.id, 'working', story.id]
|
|
1781
1923
|
);
|
|
1782
1924
|
|
|
1783
1925
|
// Query agents using the same filter logic from assignStories
|
|
1784
|
-
const result = db.exec(
|
|
1926
|
+
const result = db.db.exec(
|
|
1785
1927
|
`SELECT id, type, status, current_story_id FROM agents
|
|
1786
1928
|
WHERE team_id = '${team.id}' AND type != 'qa'
|
|
1787
1929
|
AND (status = 'idle' OR (status = 'working' AND current_story_id IS NULL))`
|
|
@@ -1818,19 +1960,19 @@ describe('Scheduler checkMergeQueue', () => {
|
|
|
1818
1960
|
it('should spawn QA when a queued PR exists for an in_progress story', async () => {
|
|
1819
1961
|
ensurePullRequestsTable();
|
|
1820
1962
|
|
|
1821
|
-
const team = createTeam(db, {
|
|
1963
|
+
const team = await createTeam(db, {
|
|
1822
1964
|
name: 'Test Team',
|
|
1823
1965
|
repoUrl: 'https://github.com/test/repo',
|
|
1824
1966
|
repoPath: 'test',
|
|
1825
1967
|
});
|
|
1826
|
-
const story = createStory(db, {
|
|
1968
|
+
const story = await createStory(db, {
|
|
1827
1969
|
teamId: team.id,
|
|
1828
1970
|
title: 'Queued PR Story',
|
|
1829
1971
|
description: 'Story has queued PR but stale in_progress status',
|
|
1830
1972
|
});
|
|
1831
|
-
updateStory(db, story.id, { status: 'in_progress' });
|
|
1973
|
+
await updateStory(db, story.id, { status: 'in_progress' });
|
|
1832
1974
|
|
|
1833
|
-
createPullRequest(db, {
|
|
1975
|
+
await createPullRequest(db, {
|
|
1834
1976
|
storyId: story.id,
|
|
1835
1977
|
teamId: team.id,
|
|
1836
1978
|
branchName: 'feature/queued-pr-story',
|
|
@@ -1856,19 +1998,19 @@ describe('Scheduler checkMergeQueue', () => {
|
|
|
1856
1998
|
it('should not spawn QA for merged stories even if PR row is still queued', async () => {
|
|
1857
1999
|
ensurePullRequestsTable();
|
|
1858
2000
|
|
|
1859
|
-
const team = createTeam(db, {
|
|
2001
|
+
const team = await createTeam(db, {
|
|
1860
2002
|
name: 'Test Team',
|
|
1861
2003
|
repoUrl: 'https://github.com/test/repo',
|
|
1862
2004
|
repoPath: 'test',
|
|
1863
2005
|
});
|
|
1864
|
-
const story = createStory(db, {
|
|
2006
|
+
const story = await createStory(db, {
|
|
1865
2007
|
teamId: team.id,
|
|
1866
2008
|
title: 'Merged Story',
|
|
1867
2009
|
description: 'Merged stories should not drive QA scaling',
|
|
1868
2010
|
});
|
|
1869
|
-
updateStory(db, story.id, { status: 'merged' });
|
|
2011
|
+
await updateStory(db, story.id, { status: 'merged' });
|
|
1870
2012
|
|
|
1871
|
-
createPullRequest(db, {
|
|
2013
|
+
await createPullRequest(db, {
|
|
1872
2014
|
storyId: story.id,
|
|
1873
2015
|
teamId: team.id,
|
|
1874
2016
|
branchName: 'feature/merged-story',
|
|
@@ -1893,24 +2035,24 @@ describe('Scheduler checkMergeQueue', () => {
|
|
|
1893
2035
|
|
|
1894
2036
|
describe('Scheduler checkScaling', () => {
|
|
1895
2037
|
it('should spawn a new indexed senior when the base senior is busy', async () => {
|
|
1896
|
-
const team = createTeam(db, {
|
|
2038
|
+
const team = await createTeam(db, {
|
|
1897
2039
|
name: 'Busy Senior Team',
|
|
1898
2040
|
repoUrl: 'https://github.com/test/repo',
|
|
1899
2041
|
repoPath: 'test',
|
|
1900
2042
|
});
|
|
1901
2043
|
|
|
1902
|
-
db.run(
|
|
2044
|
+
db.db.run(
|
|
1903
2045
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
1904
2046
|
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
1905
2047
|
['senior-busy-1', 'senior', team.id, 'working', 'STORY-OLD']
|
|
1906
2048
|
);
|
|
1907
2049
|
|
|
1908
|
-
const story = createStory(db, {
|
|
2050
|
+
const story = await createStory(db, {
|
|
1909
2051
|
teamId: team.id,
|
|
1910
2052
|
title: 'Needs Senior',
|
|
1911
2053
|
description: 'High complexity story',
|
|
1912
2054
|
});
|
|
1913
|
-
updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2055
|
+
await updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
1914
2056
|
|
|
1915
2057
|
const spawnSeniorSpy = vi
|
|
1916
2058
|
.spyOn(scheduler as any, 'spawnSenior')
|
|
@@ -1936,11 +2078,11 @@ describe('Scheduler checkScaling', () => {
|
|
|
1936
2078
|
expect(result.assigned).toBe(1);
|
|
1937
2079
|
expect(spawnSeniorSpy).toHaveBeenCalledTimes(1);
|
|
1938
2080
|
|
|
1939
|
-
const updatedStory = getStoryById(db, story.id)!;
|
|
2081
|
+
const updatedStory = (await getStoryById(db, story.id))!;
|
|
1940
2082
|
expect(updatedStory.status).toBe('in_progress');
|
|
1941
2083
|
expect(updatedStory.assigned_agent_id).toBe('senior-spawned-2');
|
|
1942
2084
|
|
|
1943
|
-
const busySeniorRow = db.exec(
|
|
2085
|
+
const busySeniorRow = db.db.exec(
|
|
1944
2086
|
`SELECT status, current_story_id FROM agents WHERE id = 'senior-busy-1'`
|
|
1945
2087
|
)[0]?.values[0];
|
|
1946
2088
|
expect(busySeniorRow?.[0]).toBe('working');
|
|
@@ -1950,7 +2092,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
1950
2092
|
});
|
|
1951
2093
|
|
|
1952
2094
|
it('should choose next senior index from max active index when index 1 is absent', async () => {
|
|
1953
|
-
const team = createTeam(db, {
|
|
2095
|
+
const team = await createTeam(db, {
|
|
1954
2096
|
name: 'Gap Index Team',
|
|
1955
2097
|
repoUrl: 'https://github.com/test/repo',
|
|
1956
2098
|
repoPath: 'test',
|
|
@@ -1974,12 +2116,12 @@ describe('Scheduler checkScaling', () => {
|
|
|
1974
2116
|
);
|
|
1975
2117
|
}
|
|
1976
2118
|
|
|
1977
|
-
const story = createStory(db, {
|
|
2119
|
+
const story = await createStory(db, {
|
|
1978
2120
|
teamId: team.id,
|
|
1979
2121
|
title: 'Gap Index Story',
|
|
1980
2122
|
description: 'Requires spawning the next indexed senior',
|
|
1981
2123
|
});
|
|
1982
|
-
updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2124
|
+
await updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
1983
2125
|
|
|
1984
2126
|
const spawnSeniorSpy = vi
|
|
1985
2127
|
.spyOn(scheduler as any, 'spawnSenior')
|
|
@@ -2005,37 +2147,37 @@ describe('Scheduler checkScaling', () => {
|
|
|
2005
2147
|
|
|
2006
2148
|
expect(result.assigned).toBe(1);
|
|
2007
2149
|
expect(spawnSeniorSpy).toHaveBeenCalledTimes(1);
|
|
2008
|
-
expect(getStoryById(db, story.id)?.assigned_agent_id).toBe('senior-gap-6');
|
|
2150
|
+
expect((await getStoryById(db, story.id))?.assigned_agent_id).toBe('senior-gap-6');
|
|
2009
2151
|
|
|
2010
2152
|
spawnSeniorSpy.mockRestore();
|
|
2011
2153
|
});
|
|
2012
2154
|
|
|
2013
2155
|
it('should not assign multiple stories to the same senior in one cycle', async () => {
|
|
2014
|
-
const team = createTeam(db, {
|
|
2156
|
+
const team = await createTeam(db, {
|
|
2015
2157
|
name: 'Single Senior Team',
|
|
2016
2158
|
repoUrl: 'https://github.com/test/repo',
|
|
2017
2159
|
repoPath: 'test',
|
|
2018
2160
|
});
|
|
2019
2161
|
|
|
2020
|
-
db.run(
|
|
2162
|
+
db.db.run(
|
|
2021
2163
|
`INSERT INTO agents (id, type, team_id, status, current_story_id, created_at, updated_at)
|
|
2022
2164
|
VALUES (?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
2023
2165
|
['senior-single-1', 'senior', team.id, 'idle']
|
|
2024
2166
|
);
|
|
2025
2167
|
|
|
2026
|
-
const story1 = createStory(db, {
|
|
2168
|
+
const story1 = await createStory(db, {
|
|
2027
2169
|
teamId: team.id,
|
|
2028
2170
|
title: 'High Complexity 1',
|
|
2029
2171
|
description: 'Needs senior',
|
|
2030
2172
|
});
|
|
2031
|
-
updateStory(db, story1.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2173
|
+
await updateStory(db, story1.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2032
2174
|
|
|
2033
|
-
const story2 = createStory(db, {
|
|
2175
|
+
const story2 = await createStory(db, {
|
|
2034
2176
|
teamId: team.id,
|
|
2035
2177
|
title: 'High Complexity 2',
|
|
2036
2178
|
description: 'Needs senior too',
|
|
2037
2179
|
});
|
|
2038
|
-
updateStory(db, story2.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2180
|
+
await updateStory(db, story2.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2039
2181
|
|
|
2040
2182
|
const spawnSeniorSpy = vi
|
|
2041
2183
|
.spyOn(scheduler as any, 'spawnSenior')
|
|
@@ -2047,45 +2189,45 @@ describe('Scheduler checkScaling', () => {
|
|
|
2047
2189
|
expect(result.errors.some(e => e.includes('Failed to spawn Senior'))).toBe(true);
|
|
2048
2190
|
expect(spawnSeniorSpy).toHaveBeenCalledTimes(1);
|
|
2049
2191
|
|
|
2050
|
-
const updatedStory1 = getStoryById(db, story1.id)!;
|
|
2051
|
-
const updatedStory2 = getStoryById(db, story2.id)!;
|
|
2052
|
-
const inProgress = [updatedStory1, updatedStory2].filter(s => s
|
|
2053
|
-
const planned = [updatedStory1, updatedStory2].filter(s => s
|
|
2192
|
+
const updatedStory1 = (await getStoryById(db, story1.id))!;
|
|
2193
|
+
const updatedStory2 = (await getStoryById(db, story2.id))!;
|
|
2194
|
+
const inProgress = [updatedStory1, updatedStory2].filter(s => s!.status === 'in_progress');
|
|
2195
|
+
const planned = [updatedStory1, updatedStory2].filter(s => s!.status === 'planned');
|
|
2054
2196
|
|
|
2055
2197
|
expect(inProgress).toHaveLength(1);
|
|
2056
2198
|
expect(planned).toHaveLength(1);
|
|
2057
|
-
expect(inProgress[0]
|
|
2058
|
-
expect(planned[0]
|
|
2199
|
+
expect(inProgress[0]!.assigned_agent_id).toBe('senior-single-1');
|
|
2200
|
+
expect(planned[0]!.assigned_agent_id).toBeNull();
|
|
2059
2201
|
|
|
2060
|
-
const seniorRow = db.exec(
|
|
2202
|
+
const seniorRow = db.db.exec(
|
|
2061
2203
|
`SELECT status, current_story_id FROM agents WHERE id = 'senior-single-1'`
|
|
2062
2204
|
)[0]?.values[0];
|
|
2063
2205
|
expect(seniorRow?.[0]).toBe('working');
|
|
2064
|
-
expect(seniorRow?.[1]).toBe(inProgress[0]
|
|
2206
|
+
expect(seniorRow?.[1]).toBe(inProgress[0]!.id);
|
|
2065
2207
|
|
|
2066
2208
|
spawnSeniorSpy.mockRestore();
|
|
2067
2209
|
});
|
|
2068
2210
|
|
|
2069
2211
|
it('should send an explicit assignment handoff to the assigned tmux session', async () => {
|
|
2070
|
-
const team = createTeam(db, {
|
|
2212
|
+
const team = await createTeam(db, {
|
|
2071
2213
|
name: 'Handoff Team',
|
|
2072
2214
|
repoUrl: 'https://github.com/test/repo',
|
|
2073
2215
|
repoPath: 'test',
|
|
2074
2216
|
});
|
|
2075
2217
|
|
|
2076
2218
|
const sessionName = 'hive-senior-handoff-team';
|
|
2077
|
-
db.run(
|
|
2219
|
+
db.db.run(
|
|
2078
2220
|
`INSERT INTO agents (id, type, team_id, tmux_session, status, current_story_id, created_at, updated_at)
|
|
2079
2221
|
VALUES (?, ?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
2080
2222
|
['senior-handoff-1', 'senior', team.id, sessionName, 'idle']
|
|
2081
2223
|
);
|
|
2082
2224
|
|
|
2083
|
-
const story = createStory(db, {
|
|
2225
|
+
const story = await createStory(db, {
|
|
2084
2226
|
teamId: team.id,
|
|
2085
2227
|
title: 'Needs Context Reset',
|
|
2086
2228
|
description: 'Verify assignment handoff message is sent',
|
|
2087
2229
|
});
|
|
2088
|
-
updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2230
|
+
await updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2089
2231
|
|
|
2090
2232
|
const isRunningSpy = vi.spyOn(tmuxModule, 'isTmuxSessionRunning').mockResolvedValue(true);
|
|
2091
2233
|
const sendSpy = vi.spyOn(tmuxModule, 'sendToTmuxSession').mockResolvedValue();
|
|
@@ -2104,7 +2246,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
2104
2246
|
});
|
|
2105
2247
|
|
|
2106
2248
|
it('should reject spawning a senior on a busy existing session', async () => {
|
|
2107
|
-
const team = createTeam(db, {
|
|
2249
|
+
const team = await createTeam(db, {
|
|
2108
2250
|
name: 'Spawn Guard Team',
|
|
2109
2251
|
repoUrl: 'https://github.com/test/repo',
|
|
2110
2252
|
repoPath: 'test',
|
|
@@ -2113,7 +2255,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
2113
2255
|
const hiveDir = join(mockConfig.rootDir, '.hive');
|
|
2114
2256
|
const expectedSession = generateSessionName('senior', team.name, undefined, hiveDir);
|
|
2115
2257
|
|
|
2116
|
-
db.run(
|
|
2258
|
+
db.db.run(
|
|
2117
2259
|
`INSERT INTO agents (id, type, team_id, tmux_session, status, current_story_id, created_at, updated_at)
|
|
2118
2260
|
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
2119
2261
|
['senior-guard-1', 'senior', team.id, expectedSession, 'working', 'STORY-ACTIVE']
|
|
@@ -2129,38 +2271,38 @@ describe('Scheduler checkScaling', () => {
|
|
|
2129
2271
|
});
|
|
2130
2272
|
|
|
2131
2273
|
it('should only spawn agents for assignable stories (unblocked dependencies)', async () => {
|
|
2132
|
-
const team = createTeam(db, {
|
|
2274
|
+
const team = await createTeam(db, {
|
|
2133
2275
|
name: 'Test Team',
|
|
2134
2276
|
repoUrl: 'https://github.com/test/repo',
|
|
2135
2277
|
repoPath: 'test',
|
|
2136
2278
|
});
|
|
2137
2279
|
|
|
2138
2280
|
// Create a blocker story that is not yet merged
|
|
2139
|
-
const blockerStory = createStory(db, {
|
|
2281
|
+
const blockerStory = await createStory(db, {
|
|
2140
2282
|
teamId: team.id,
|
|
2141
2283
|
title: 'Blocker Story',
|
|
2142
2284
|
description: 'Must be completed first',
|
|
2143
2285
|
});
|
|
2144
|
-
updateStory(db, blockerStory.id, { status: 'planned', storyPoints: 10 });
|
|
2286
|
+
await updateStory(db, blockerStory.id, { status: 'planned', storyPoints: 10 });
|
|
2145
2287
|
|
|
2146
2288
|
// Create 4 stories that depend on the blocker (cannot be assigned yet)
|
|
2147
2289
|
for (let i = 1; i <= 4; i++) {
|
|
2148
|
-
const story = createStory(db, {
|
|
2290
|
+
const story = await createStory(db, {
|
|
2149
2291
|
teamId: team.id,
|
|
2150
2292
|
title: `Blocked Story ${i}`,
|
|
2151
2293
|
description: 'Depends on blocker',
|
|
2152
2294
|
});
|
|
2153
|
-
updateStory(db, story.id, { status: 'planned', storyPoints: 10 });
|
|
2154
|
-
addStoryDependency(db, story.id, blockerStory.id);
|
|
2295
|
+
await updateStory(db, story.id, { status: 'planned', storyPoints: 10 });
|
|
2296
|
+
await addStoryDependency(db, story.id, blockerStory.id);
|
|
2155
2297
|
}
|
|
2156
2298
|
|
|
2157
2299
|
// Create 1 story with no dependencies (can be assigned)
|
|
2158
|
-
const unblockedStory = createStory(db, {
|
|
2300
|
+
const unblockedStory = await createStory(db, {
|
|
2159
2301
|
teamId: team.id,
|
|
2160
2302
|
title: 'Unblocked Story',
|
|
2161
2303
|
description: 'No dependencies',
|
|
2162
2304
|
});
|
|
2163
|
-
updateStory(db, unblockedStory.id, { status: 'planned', storyPoints: 10 });
|
|
2305
|
+
await updateStory(db, unblockedStory.id, { status: 'planned', storyPoints: 10 });
|
|
2164
2306
|
|
|
2165
2307
|
// Total: 50 story points, but only 10 are assignable
|
|
2166
2308
|
// With senior_capacity: 50, this should spawn 1 senior (10/50 = 0.2, ceil = 1)
|
|
@@ -2184,29 +2326,29 @@ describe('Scheduler checkScaling', () => {
|
|
|
2184
2326
|
});
|
|
2185
2327
|
|
|
2186
2328
|
it('should not spawn agents when all stories are blocked', async () => {
|
|
2187
|
-
const team = createTeam(db, {
|
|
2329
|
+
const team = await createTeam(db, {
|
|
2188
2330
|
name: 'Test Team',
|
|
2189
2331
|
repoUrl: 'https://github.com/test/repo',
|
|
2190
2332
|
repoPath: 'test',
|
|
2191
2333
|
});
|
|
2192
2334
|
|
|
2193
2335
|
// Create a blocker story that is not yet merged
|
|
2194
|
-
const blockerStory = createStory(db, {
|
|
2336
|
+
const blockerStory = await createStory(db, {
|
|
2195
2337
|
teamId: team.id,
|
|
2196
2338
|
title: 'Blocker Story',
|
|
2197
2339
|
description: 'Must be completed first',
|
|
2198
2340
|
});
|
|
2199
|
-
updateStory(db, blockerStory.id, { status: 'planned', storyPoints: 10 });
|
|
2341
|
+
await updateStory(db, blockerStory.id, { status: 'planned', storyPoints: 10 });
|
|
2200
2342
|
|
|
2201
2343
|
// Create stories that all depend on the blocker
|
|
2202
2344
|
for (let i = 1; i <= 5; i++) {
|
|
2203
|
-
const story = createStory(db, {
|
|
2345
|
+
const story = await createStory(db, {
|
|
2204
2346
|
teamId: team.id,
|
|
2205
2347
|
title: `Blocked Story ${i}`,
|
|
2206
2348
|
description: 'Depends on blocker',
|
|
2207
2349
|
});
|
|
2208
|
-
updateStory(db, story.id, { status: 'planned', storyPoints: 10 });
|
|
2209
|
-
addStoryDependency(db, story.id, blockerStory.id);
|
|
2350
|
+
await updateStory(db, story.id, { status: 'planned', storyPoints: 10 });
|
|
2351
|
+
await addStoryDependency(db, story.id, blockerStory.id);
|
|
2210
2352
|
}
|
|
2211
2353
|
|
|
2212
2354
|
// Mock spawnSenior to track calls
|
|
@@ -2244,25 +2386,25 @@ describe('Scheduler Markdown File Writing', () => {
|
|
|
2244
2386
|
});
|
|
2245
2387
|
|
|
2246
2388
|
it('should write markdown files when assigning stories via scheduler', async () => {
|
|
2247
|
-
const team = createTeam(db, {
|
|
2389
|
+
const team = await createTeam(db, {
|
|
2248
2390
|
name: 'MD Write Team',
|
|
2249
2391
|
repoUrl: 'https://github.com/test/repo',
|
|
2250
2392
|
repoPath: 'test',
|
|
2251
2393
|
});
|
|
2252
2394
|
|
|
2253
2395
|
// Create an idle senior agent
|
|
2254
|
-
db.run(
|
|
2396
|
+
db.db.run(
|
|
2255
2397
|
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
2256
2398
|
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
2257
2399
|
['senior-md-1', 'senior', team.id, 'idle']
|
|
2258
2400
|
);
|
|
2259
2401
|
|
|
2260
|
-
const story = createStory(db, {
|
|
2402
|
+
const story = await createStory(db, {
|
|
2261
2403
|
teamId: team.id,
|
|
2262
2404
|
title: 'Markdown Test Story',
|
|
2263
2405
|
description: 'Should get a markdown file on assignment',
|
|
2264
2406
|
});
|
|
2265
|
-
updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2407
|
+
await updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2266
2408
|
|
|
2267
2409
|
// Create scheduler with rootDir pointing to a temp dir that has .hive/stories/
|
|
2268
2410
|
const hiveRoot = join(tmpdir(), `hive-md-root-${Date.now()}`);
|
|
@@ -2286,7 +2428,7 @@ describe('Scheduler Markdown File Writing', () => {
|
|
|
2286
2428
|
expect(existsSync(mdPath)).toBe(true);
|
|
2287
2429
|
|
|
2288
2430
|
// Verify markdown_path was set in DB
|
|
2289
|
-
const updatedStory = getStoryById(db, story.id);
|
|
2431
|
+
const updatedStory = await getStoryById(db, story.id);
|
|
2290
2432
|
expect(updatedStory?.markdown_path).toBe(mdPath);
|
|
2291
2433
|
expect(updatedStory?.status).toBe('in_progress');
|
|
2292
2434
|
|
|
@@ -2295,22 +2437,22 @@ describe('Scheduler Markdown File Writing', () => {
|
|
|
2295
2437
|
});
|
|
2296
2438
|
|
|
2297
2439
|
describe('Scheduler Target Branch Propagation', () => {
|
|
2298
|
-
it('should retrieve target_branch from requirement when creating story', () => {
|
|
2299
|
-
const team = createTeam(db, {
|
|
2440
|
+
it('should retrieve target_branch from requirement when creating story', async () => {
|
|
2441
|
+
const team = await createTeam(db, {
|
|
2300
2442
|
name: 'Test Team',
|
|
2301
2443
|
repoUrl: 'https://github.com/test/repo',
|
|
2302
2444
|
repoPath: 'test',
|
|
2303
2445
|
});
|
|
2304
2446
|
|
|
2305
2447
|
// Create a requirement with custom target_branch
|
|
2306
|
-
const requirement = createRequirement(db, {
|
|
2448
|
+
const requirement = await createRequirement(db, {
|
|
2307
2449
|
title: 'Feature for Release Branch',
|
|
2308
2450
|
description: 'Test feature',
|
|
2309
2451
|
targetBranch: 'release/v2.0',
|
|
2310
2452
|
});
|
|
2311
2453
|
|
|
2312
2454
|
// Create a story linked to this requirement
|
|
2313
|
-
const story = createStory(db, {
|
|
2455
|
+
const story = await createStory(db, {
|
|
2314
2456
|
teamId: team.id,
|
|
2315
2457
|
requirementId: requirement.id,
|
|
2316
2458
|
title: 'Story for Release',
|
|
@@ -2322,55 +2464,55 @@ describe('Scheduler Target Branch Propagation', () => {
|
|
|
2322
2464
|
expect(story.team_id).toBe(team.id);
|
|
2323
2465
|
|
|
2324
2466
|
// Verify we can retrieve the requirement and its target_branch
|
|
2325
|
-
const retrievedReq = db.exec(
|
|
2467
|
+
const retrievedReq = db.db.exec(
|
|
2326
2468
|
`SELECT target_branch FROM requirements WHERE id = '${requirement.id}'`
|
|
2327
2469
|
)[0]?.values[0];
|
|
2328
2470
|
expect(retrievedReq?.[0]).toBe('release/v2.0');
|
|
2329
2471
|
});
|
|
2330
2472
|
|
|
2331
|
-
it('should use default target_branch (main) when requirement has no custom branch', () => {
|
|
2473
|
+
it('should use default target_branch (main) when requirement has no custom branch', async () => {
|
|
2332
2474
|
// Create a requirement without specifying target_branch
|
|
2333
|
-
const requirement = createRequirement(db, {
|
|
2475
|
+
const requirement = await createRequirement(db, {
|
|
2334
2476
|
title: 'Feature for Main Branch',
|
|
2335
2477
|
description: 'Test feature',
|
|
2336
2478
|
});
|
|
2337
2479
|
|
|
2338
2480
|
// Verify the requirement defaults to main branch
|
|
2339
|
-
const retrievedReq = db.exec(
|
|
2481
|
+
const retrievedReq = db.db.exec(
|
|
2340
2482
|
`SELECT target_branch FROM requirements WHERE id = '${requirement.id}'`
|
|
2341
2483
|
)[0]?.values[0];
|
|
2342
2484
|
expect(retrievedReq?.[0]).toBe('main');
|
|
2343
2485
|
});
|
|
2344
2486
|
|
|
2345
|
-
it('should handle stories with different target branches from different requirements', () => {
|
|
2346
|
-
const team = createTeam(db, {
|
|
2487
|
+
it('should handle stories with different target branches from different requirements', async () => {
|
|
2488
|
+
const team = await createTeam(db, {
|
|
2347
2489
|
name: 'Test Team',
|
|
2348
2490
|
repoUrl: 'https://github.com/test/repo',
|
|
2349
2491
|
repoPath: 'test',
|
|
2350
2492
|
});
|
|
2351
2493
|
|
|
2352
2494
|
// Create two requirements with different target branches
|
|
2353
|
-
const req1 = createRequirement(db, {
|
|
2495
|
+
const req1 = await createRequirement(db, {
|
|
2354
2496
|
title: 'Main Feature',
|
|
2355
2497
|
description: 'Goes to main',
|
|
2356
2498
|
targetBranch: 'main',
|
|
2357
2499
|
});
|
|
2358
2500
|
|
|
2359
|
-
const req2 = createRequirement(db, {
|
|
2501
|
+
const req2 = await createRequirement(db, {
|
|
2360
2502
|
title: 'Staging Feature',
|
|
2361
2503
|
description: 'Goes to staging',
|
|
2362
2504
|
targetBranch: 'staging',
|
|
2363
2505
|
});
|
|
2364
2506
|
|
|
2365
2507
|
// Create stories for each requirement
|
|
2366
|
-
const story1 = createStory(db, {
|
|
2508
|
+
const story1 = await createStory(db, {
|
|
2367
2509
|
teamId: team.id,
|
|
2368
2510
|
requirementId: req1.id,
|
|
2369
2511
|
title: 'Story 1',
|
|
2370
2512
|
description: 'Test',
|
|
2371
2513
|
});
|
|
2372
2514
|
|
|
2373
|
-
const story2 = createStory(db, {
|
|
2515
|
+
const story2 = await createStory(db, {
|
|
2374
2516
|
teamId: team.id,
|
|
2375
2517
|
requirementId: req2.id,
|
|
2376
2518
|
title: 'Story 2',
|
|
@@ -2378,7 +2520,7 @@ describe('Scheduler Target Branch Propagation', () => {
|
|
|
2378
2520
|
});
|
|
2379
2521
|
|
|
2380
2522
|
// Verify each story can access its requirement's target_branch via JOIN
|
|
2381
|
-
const result = db.exec(
|
|
2523
|
+
const result = db.db.exec(
|
|
2382
2524
|
`SELECT s.id, r.target_branch
|
|
2383
2525
|
FROM stories s
|
|
2384
2526
|
LEFT JOIN requirements r ON s.requirement_id = r.id
|
|
@@ -2392,15 +2534,15 @@ describe('Scheduler Target Branch Propagation', () => {
|
|
|
2392
2534
|
expect(branches).toContain('staging');
|
|
2393
2535
|
});
|
|
2394
2536
|
|
|
2395
|
-
it('should handle stories without a linked requirement (null requirement_id)', () => {
|
|
2396
|
-
const team = createTeam(db, {
|
|
2537
|
+
it('should handle stories without a linked requirement (null requirement_id)', async () => {
|
|
2538
|
+
const team = await createTeam(db, {
|
|
2397
2539
|
name: 'Test Team',
|
|
2398
2540
|
repoUrl: 'https://github.com/test/repo',
|
|
2399
2541
|
repoPath: 'test',
|
|
2400
2542
|
});
|
|
2401
2543
|
|
|
2402
2544
|
// Create a story without a requirement
|
|
2403
|
-
const story = createStory(db, {
|
|
2545
|
+
const story = await createStory(db, {
|
|
2404
2546
|
teamId: team.id,
|
|
2405
2547
|
title: 'Standalone Story',
|
|
2406
2548
|
description: 'No requirement',
|
|
@@ -2409,7 +2551,7 @@ describe('Scheduler Target Branch Propagation', () => {
|
|
|
2409
2551
|
expect(story.requirement_id).toBeNull();
|
|
2410
2552
|
|
|
2411
2553
|
// When joining with requirements, should get null for target_branch
|
|
2412
|
-
const result = db.exec(
|
|
2554
|
+
const result = db.db.exec(
|
|
2413
2555
|
`SELECT s.id, r.target_branch
|
|
2414
2556
|
FROM stories s
|
|
2415
2557
|
LEFT JOIN requirements r ON s.requirement_id = r.id
|