@stoneforge/smithy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +114 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +7 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/orchestrator-api.d.ts +153 -0
- package/dist/api/orchestrator-api.d.ts.map +1 -0
- package/dist/api/orchestrator-api.js +374 -0
- package/dist/api/orchestrator-api.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +10 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/agent.d.ts +20 -0
- package/dist/cli/commands/agent.d.ts.map +1 -0
- package/dist/cli/commands/agent.js +861 -0
- package/dist/cli/commands/agent.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +14 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +272 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/dispatch.d.ts +9 -0
- package/dist/cli/commands/dispatch.d.ts.map +1 -0
- package/dist/cli/commands/dispatch.js +128 -0
- package/dist/cli/commands/dispatch.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +11 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +246 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/pool.d.ts +21 -0
- package/dist/cli/commands/pool.d.ts.map +1 -0
- package/dist/cli/commands/pool.js +762 -0
- package/dist/cli/commands/pool.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +54 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +57 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/task.d.ts +36 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +889 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/test-orchestration.d.ts +32 -0
- package/dist/cli/commands/test-orchestration.d.ts.map +1 -0
- package/dist/cli/commands/test-orchestration.js +392 -0
- package/dist/cli/commands/test-orchestration.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/plugin.d.ts +23 -0
- package/dist/cli/plugin.d.ts.map +1 -0
- package/dist/cli/plugin.js +36 -0
- package/dist/cli/plugin.js.map +1 -0
- package/dist/git/index.d.ts +10 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +12 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/merge.d.ts +79 -0
- package/dist/git/merge.d.ts.map +1 -0
- package/dist/git/merge.js +254 -0
- package/dist/git/merge.js.map +1 -0
- package/dist/git/worktree-manager.d.ts +299 -0
- package/dist/git/worktree-manager.d.ts.map +1 -0
- package/dist/git/worktree-manager.js +744 -0
- package/dist/git/worktree-manager.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/director.md +272 -0
- package/dist/prompts/index.d.ts +100 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +294 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/message-triage.md +50 -0
- package/dist/prompts/persistent-worker.md +240 -0
- package/dist/prompts/steward-base.md +64 -0
- package/dist/prompts/steward-docs.md +118 -0
- package/dist/prompts/steward-health.md +39 -0
- package/dist/prompts/steward-merge.md +168 -0
- package/dist/prompts/steward-ops.md +28 -0
- package/dist/prompts/steward-reminder.md +26 -0
- package/dist/prompts/worker.md +282 -0
- package/dist/providers/claude/headless.d.ts +18 -0
- package/dist/providers/claude/headless.d.ts.map +1 -0
- package/dist/providers/claude/headless.js +307 -0
- package/dist/providers/claude/headless.js.map +1 -0
- package/dist/providers/claude/index.d.ts +24 -0
- package/dist/providers/claude/index.d.ts.map +1 -0
- package/dist/providers/claude/index.js +80 -0
- package/dist/providers/claude/index.js.map +1 -0
- package/dist/providers/claude/interactive.d.ts +21 -0
- package/dist/providers/claude/interactive.d.ts.map +1 -0
- package/dist/providers/claude/interactive.js +142 -0
- package/dist/providers/claude/interactive.js.map +1 -0
- package/dist/providers/codex/event-mapper.d.ts +91 -0
- package/dist/providers/codex/event-mapper.d.ts.map +1 -0
- package/dist/providers/codex/event-mapper.js +299 -0
- package/dist/providers/codex/event-mapper.js.map +1 -0
- package/dist/providers/codex/headless.d.ts +20 -0
- package/dist/providers/codex/headless.d.ts.map +1 -0
- package/dist/providers/codex/headless.js +174 -0
- package/dist/providers/codex/headless.js.map +1 -0
- package/dist/providers/codex/index.d.ts +30 -0
- package/dist/providers/codex/index.d.ts.map +1 -0
- package/dist/providers/codex/index.js +55 -0
- package/dist/providers/codex/index.js.map +1 -0
- package/dist/providers/codex/interactive.d.ts +21 -0
- package/dist/providers/codex/interactive.d.ts.map +1 -0
- package/dist/providers/codex/interactive.js +141 -0
- package/dist/providers/codex/interactive.js.map +1 -0
- package/dist/providers/codex/jsonrpc-client.d.ts +52 -0
- package/dist/providers/codex/jsonrpc-client.d.ts.map +1 -0
- package/dist/providers/codex/jsonrpc-client.js +141 -0
- package/dist/providers/codex/jsonrpc-client.js.map +1 -0
- package/dist/providers/codex/server-manager.d.ts +100 -0
- package/dist/providers/codex/server-manager.d.ts.map +1 -0
- package/dist/providers/codex/server-manager.js +153 -0
- package/dist/providers/codex/server-manager.js.map +1 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/opencode/async-queue.d.ts +21 -0
- package/dist/providers/opencode/async-queue.d.ts.map +1 -0
- package/dist/providers/opencode/async-queue.js +51 -0
- package/dist/providers/opencode/async-queue.js.map +1 -0
- package/dist/providers/opencode/event-mapper.d.ts +132 -0
- package/dist/providers/opencode/event-mapper.d.ts.map +1 -0
- package/dist/providers/opencode/event-mapper.js +204 -0
- package/dist/providers/opencode/event-mapper.js.map +1 -0
- package/dist/providers/opencode/headless.d.ts +25 -0
- package/dist/providers/opencode/headless.d.ts.map +1 -0
- package/dist/providers/opencode/headless.js +190 -0
- package/dist/providers/opencode/headless.js.map +1 -0
- package/dist/providers/opencode/index.d.ts +33 -0
- package/dist/providers/opencode/index.d.ts.map +1 -0
- package/dist/providers/opencode/index.js +42 -0
- package/dist/providers/opencode/index.js.map +1 -0
- package/dist/providers/opencode/interactive.d.ts +21 -0
- package/dist/providers/opencode/interactive.d.ts.map +1 -0
- package/dist/providers/opencode/interactive.js +135 -0
- package/dist/providers/opencode/interactive.js.map +1 -0
- package/dist/providers/opencode/server-manager.d.ts +145 -0
- package/dist/providers/opencode/server-manager.d.ts.map +1 -0
- package/dist/providers/opencode/server-manager.js +163 -0
- package/dist/providers/opencode/server-manager.js.map +1 -0
- package/dist/providers/registry.d.ts +38 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +82 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +144 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +25 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/runtime/event-utils.d.ts +8 -0
- package/dist/runtime/event-utils.d.ts.map +1 -0
- package/dist/runtime/event-utils.js +23 -0
- package/dist/runtime/event-utils.js.map +1 -0
- package/dist/runtime/handoff.d.ts +195 -0
- package/dist/runtime/handoff.d.ts.map +1 -0
- package/dist/runtime/handoff.js +332 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/index.d.ts +17 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +60 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/message-mapper.d.ts +99 -0
- package/dist/runtime/message-mapper.d.ts.map +1 -0
- package/dist/runtime/message-mapper.js +202 -0
- package/dist/runtime/message-mapper.js.map +1 -0
- package/dist/runtime/predecessor-query.d.ts +212 -0
- package/dist/runtime/predecessor-query.d.ts.map +1 -0
- package/dist/runtime/predecessor-query.js +283 -0
- package/dist/runtime/predecessor-query.js.map +1 -0
- package/dist/runtime/session-manager.d.ts +466 -0
- package/dist/runtime/session-manager.d.ts.map +1 -0
- package/dist/runtime/session-manager.js +986 -0
- package/dist/runtime/session-manager.js.map +1 -0
- package/dist/runtime/spawner.d.ts +407 -0
- package/dist/runtime/spawner.d.ts.map +1 -0
- package/dist/runtime/spawner.js +781 -0
- package/dist/runtime/spawner.js.map +1 -0
- package/dist/server/config.d.ts +22 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +59 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/daemon-state.d.ts +50 -0
- package/dist/server/daemon-state.d.ts.map +1 -0
- package/dist/server/daemon-state.js +100 -0
- package/dist/server/daemon-state.js.map +1 -0
- package/dist/server/events-websocket.d.ts +32 -0
- package/dist/server/events-websocket.d.ts.map +1 -0
- package/dist/server/events-websocket.js +96 -0
- package/dist/server/events-websocket.js.map +1 -0
- package/dist/server/formatters.d.ts +94 -0
- package/dist/server/formatters.d.ts.map +1 -0
- package/dist/server/formatters.js +142 -0
- package/dist/server/formatters.js.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +153 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lsp-websocket.d.ts +33 -0
- package/dist/server/lsp-websocket.d.ts.map +1 -0
- package/dist/server/lsp-websocket.js +161 -0
- package/dist/server/lsp-websocket.js.map +1 -0
- package/dist/server/routes/agents.d.ts +9 -0
- package/dist/server/routes/agents.d.ts.map +1 -0
- package/dist/server/routes/agents.js +369 -0
- package/dist/server/routes/agents.js.map +1 -0
- package/dist/server/routes/daemon.d.ts +13 -0
- package/dist/server/routes/daemon.d.ts.map +1 -0
- package/dist/server/routes/daemon.js +187 -0
- package/dist/server/routes/daemon.js.map +1 -0
- package/dist/server/routes/events.d.ts +23 -0
- package/dist/server/routes/events.d.ts.map +1 -0
- package/dist/server/routes/events.js +282 -0
- package/dist/server/routes/events.js.map +1 -0
- package/dist/server/routes/extensions.d.ts +9 -0
- package/dist/server/routes/extensions.d.ts.map +1 -0
- package/dist/server/routes/extensions.js +202 -0
- package/dist/server/routes/extensions.js.map +1 -0
- package/dist/server/routes/health.d.ts +7 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +33 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/index.d.ts +21 -0
- package/dist/server/routes/index.d.ts.map +1 -0
- package/dist/server/routes/index.js +21 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/lsp.d.ts +9 -0
- package/dist/server/routes/lsp.d.ts.map +1 -0
- package/dist/server/routes/lsp.js +50 -0
- package/dist/server/routes/lsp.js.map +1 -0
- package/dist/server/routes/plugins.d.ts +9 -0
- package/dist/server/routes/plugins.d.ts.map +1 -0
- package/dist/server/routes/plugins.js +109 -0
- package/dist/server/routes/plugins.js.map +1 -0
- package/dist/server/routes/pools.d.ts +9 -0
- package/dist/server/routes/pools.d.ts.map +1 -0
- package/dist/server/routes/pools.js +189 -0
- package/dist/server/routes/pools.js.map +1 -0
- package/dist/server/routes/scheduler.d.ts +9 -0
- package/dist/server/routes/scheduler.d.ts.map +1 -0
- package/dist/server/routes/scheduler.js +162 -0
- package/dist/server/routes/scheduler.js.map +1 -0
- package/dist/server/routes/sessions.d.ts +27 -0
- package/dist/server/routes/sessions.d.ts.map +1 -0
- package/dist/server/routes/sessions.js +773 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/tasks.d.ts +9 -0
- package/dist/server/routes/tasks.d.ts.map +1 -0
- package/dist/server/routes/tasks.js +954 -0
- package/dist/server/routes/tasks.js.map +1 -0
- package/dist/server/routes/upload.d.ts +8 -0
- package/dist/server/routes/upload.d.ts.map +1 -0
- package/dist/server/routes/upload.js +40 -0
- package/dist/server/routes/upload.js.map +1 -0
- package/dist/server/routes/workflows.d.ts +9 -0
- package/dist/server/routes/workflows.d.ts.map +1 -0
- package/dist/server/routes/workflows.js +532 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/routes/workspace-files.d.ts +12 -0
- package/dist/server/routes/workspace-files.d.ts.map +1 -0
- package/dist/server/routes/workspace-files.js +520 -0
- package/dist/server/routes/workspace-files.js.map +1 -0
- package/dist/server/routes/worktrees.d.ts +9 -0
- package/dist/server/routes/worktrees.d.ts.map +1 -0
- package/dist/server/routes/worktrees.js +94 -0
- package/dist/server/routes/worktrees.js.map +1 -0
- package/dist/server/server.d.ts +14 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +258 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/services/lsp-manager.d.ts +93 -0
- package/dist/server/services/lsp-manager.d.ts.map +1 -0
- package/dist/server/services/lsp-manager.js +291 -0
- package/dist/server/services/lsp-manager.js.map +1 -0
- package/dist/server/services/session-messages.d.ts +61 -0
- package/dist/server/services/session-messages.d.ts.map +1 -0
- package/dist/server/services/session-messages.js +101 -0
- package/dist/server/services/session-messages.js.map +1 -0
- package/dist/server/services.d.ts +35 -0
- package/dist/server/services.d.ts.map +1 -0
- package/dist/server/services.js +159 -0
- package/dist/server/services.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +7 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/websocket.d.ts +16 -0
- package/dist/server/websocket.d.ts.map +1 -0
- package/dist/server/websocket.js +143 -0
- package/dist/server/websocket.js.map +1 -0
- package/dist/services/agent-pool-service.d.ts +181 -0
- package/dist/services/agent-pool-service.d.ts.map +1 -0
- package/dist/services/agent-pool-service.js +590 -0
- package/dist/services/agent-pool-service.js.map +1 -0
- package/dist/services/agent-registry.d.ts +185 -0
- package/dist/services/agent-registry.d.ts.map +1 -0
- package/dist/services/agent-registry.js +432 -0
- package/dist/services/agent-registry.js.map +1 -0
- package/dist/services/dispatch-daemon.d.ts +429 -0
- package/dist/services/dispatch-daemon.d.ts.map +1 -0
- package/dist/services/dispatch-daemon.js +1833 -0
- package/dist/services/dispatch-daemon.js.map +1 -0
- package/dist/services/dispatch-service.d.ts +148 -0
- package/dist/services/dispatch-service.d.ts.map +1 -0
- package/dist/services/dispatch-service.js +170 -0
- package/dist/services/dispatch-service.js.map +1 -0
- package/dist/services/docs-steward-service.d.ts +199 -0
- package/dist/services/docs-steward-service.d.ts.map +1 -0
- package/dist/services/docs-steward-service.js +599 -0
- package/dist/services/docs-steward-service.js.map +1 -0
- package/dist/services/health-steward-service.d.ts +446 -0
- package/dist/services/health-steward-service.d.ts.map +1 -0
- package/dist/services/health-steward-service.js +866 -0
- package/dist/services/health-steward-service.js.map +1 -0
- package/dist/services/index.d.ts +26 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +111 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/merge-request-provider.d.ts +59 -0
- package/dist/services/merge-request-provider.d.ts.map +1 -0
- package/dist/services/merge-request-provider.js +89 -0
- package/dist/services/merge-request-provider.js.map +1 -0
- package/dist/services/merge-steward-service.d.ts +268 -0
- package/dist/services/merge-steward-service.d.ts.map +1 -0
- package/dist/services/merge-steward-service.js +568 -0
- package/dist/services/merge-steward-service.js.map +1 -0
- package/dist/services/plugin-executor.d.ts +247 -0
- package/dist/services/plugin-executor.d.ts.map +1 -0
- package/dist/services/plugin-executor.js +451 -0
- package/dist/services/plugin-executor.js.map +1 -0
- package/dist/services/role-definition-service.d.ts +117 -0
- package/dist/services/role-definition-service.d.ts.map +1 -0
- package/dist/services/role-definition-service.js +289 -0
- package/dist/services/role-definition-service.js.map +1 -0
- package/dist/services/steward-scheduler.d.ts +336 -0
- package/dist/services/steward-scheduler.d.ts.map +1 -0
- package/dist/services/steward-scheduler.js +732 -0
- package/dist/services/steward-scheduler.js.map +1 -0
- package/dist/services/task-assignment-service.d.ts +291 -0
- package/dist/services/task-assignment-service.d.ts.map +1 -0
- package/dist/services/task-assignment-service.js +454 -0
- package/dist/services/task-assignment-service.js.map +1 -0
- package/dist/services/worker-task-service.d.ts +202 -0
- package/dist/services/worker-task-service.d.ts.map +1 -0
- package/dist/services/worker-task-service.js +228 -0
- package/dist/services/worker-task-service.js.map +1 -0
- package/dist/testing/index.d.ts +13 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/orchestration-tests.d.ts +62 -0
- package/dist/testing/orchestration-tests.d.ts.map +1 -0
- package/dist/testing/orchestration-tests.js +1115 -0
- package/dist/testing/orchestration-tests.js.map +1 -0
- package/dist/testing/test-context.d.ts +171 -0
- package/dist/testing/test-context.d.ts.map +1 -0
- package/dist/testing/test-context.js +665 -0
- package/dist/testing/test-context.js.map +1 -0
- package/dist/testing/test-prompts.d.ts +46 -0
- package/dist/testing/test-prompts.d.ts.map +1 -0
- package/dist/testing/test-prompts.js +140 -0
- package/dist/testing/test-prompts.js.map +1 -0
- package/dist/testing/test-utils.d.ts +200 -0
- package/dist/testing/test-utils.d.ts.map +1 -0
- package/dist/testing/test-utils.js +378 -0
- package/dist/testing/test-utils.js.map +1 -0
- package/dist/types/agent-pool.d.ts +215 -0
- package/dist/types/agent-pool.d.ts.map +1 -0
- package/dist/types/agent-pool.js +143 -0
- package/dist/types/agent-pool.js.map +1 -0
- package/dist/types/agent.d.ts +265 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +127 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +40 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message-types.d.ts +294 -0
- package/dist/types/message-types.d.ts.map +1 -0
- package/dist/types/message-types.js +354 -0
- package/dist/types/message-types.js.map +1 -0
- package/dist/types/role-definition.d.ts +272 -0
- package/dist/types/role-definition.d.ts.map +1 -0
- package/dist/types/role-definition.js +144 -0
- package/dist/types/role-definition.js.map +1 -0
- package/dist/types/task-meta.d.ts +248 -0
- package/dist/types/task-meta.d.ts.map +1 -0
- package/dist/types/task-meta.js +213 -0
- package/dist/types/task-meta.js.map +1 -0
- package/package.json +120 -0
- package/web/assets/abap-BrgZPUOV.js +6 -0
- package/web/assets/apex-DyP6w7ZV.js +6 -0
- package/web/assets/azcli-BaLxmfj-.js +6 -0
- package/web/assets/bat-CFOPXBzS.js +6 -0
- package/web/assets/bicep-BfEKNvv3.js +7 -0
- package/web/assets/cameligo-BFG1Mk7z.js +6 -0
- package/web/assets/clojure-DTECt2xU.js +6 -0
- package/web/assets/codicon-DCmgc-ay.ttf +0 -0
- package/web/assets/coffee-CDGzqUPQ.js +6 -0
- package/web/assets/cpp-CLLBncYj.js +6 -0
- package/web/assets/csharp-dUCx_-0o.js +6 -0
- package/web/assets/csp-5Rap-vPy.js +6 -0
- package/web/assets/css-D3h14YRZ.js +8 -0
- package/web/assets/cssMode-DMo-5YLA.js +9 -0
- package/web/assets/cypher-DrQuvNYM.js +6 -0
- package/web/assets/dart-CFKIUWau.js +6 -0
- package/web/assets/dockerfile-Zznr-cwX.js +6 -0
- package/web/assets/ecl-Ce3n6wWz.js +6 -0
- package/web/assets/elixir-deUWdS0T.js +6 -0
- package/web/assets/flow9-i9-g7ZhI.js +6 -0
- package/web/assets/freemarker2-D4qgkQzN.js +8 -0
- package/web/assets/fsharp-CzKuDChf.js +6 -0
- package/web/assets/go-Cphgjts3.js +6 -0
- package/web/assets/graphql-Cg7bfA9N.js +6 -0
- package/web/assets/handlebars-CXFvNjQC.js +6 -0
- package/web/assets/hcl-0cvrggvQ.js +6 -0
- package/web/assets/html-oyuB_D-B.js +6 -0
- package/web/assets/htmlMode-iWuZ24-r.js +9 -0
- package/web/assets/index-DqP-_E4F.css +32 -0
- package/web/assets/index-R1cylSgw.js +1665 -0
- package/web/assets/ini-Drc7WvVn.js +6 -0
- package/web/assets/java-B_fMsGYe.js +6 -0
- package/web/assets/javascript-CRIkN2Pg.js +6 -0
- package/web/assets/jsonMode-DVDkDgex.js +15 -0
- package/web/assets/julia-Bqgm2twL.js +6 -0
- package/web/assets/kotlin-BSkB5QuD.js +6 -0
- package/web/assets/less-BsTHnhdd.js +7 -0
- package/web/assets/lexon-YWi4-JPR.js +6 -0
- package/web/assets/liquid-CSfldbB5.js +6 -0
- package/web/assets/lua-nf6ki56Z.js +6 -0
- package/web/assets/m3-Cpb6xl2v.js +6 -0
- package/web/assets/markdown-DSZPf7rp.js +6 -0
- package/web/assets/mdx-Dd58iymR.js +6 -0
- package/web/assets/mips-B_c3zf-v.js +6 -0
- package/web/assets/monaco-editor-B4lwqA13.js +751 -0
- package/web/assets/monaco-editor-CQpyCxOA.css +1 -0
- package/web/assets/msdax-rUNN04Wq.js +6 -0
- package/web/assets/mysql-DDwshQtU.js +6 -0
- package/web/assets/objective-c-B5zXfXm9.js +6 -0
- package/web/assets/pascal-CXOwvkN_.js +6 -0
- package/web/assets/pascaligo-Bc-ZgV77.js +6 -0
- package/web/assets/perl-CwNk8-XU.js +6 -0
- package/web/assets/pgsql-tGk8EFnU.js +6 -0
- package/web/assets/php-CpIb_Oan.js +6 -0
- package/web/assets/pla-B03wrqEc.js +6 -0
- package/web/assets/postiats-BKlk5iyT.js +6 -0
- package/web/assets/powerquery-Bhzvs7bI.js +6 -0
- package/web/assets/powershell-Dd3NCNK9.js +6 -0
- package/web/assets/protobuf-COyEY5Pt.js +7 -0
- package/web/assets/pug-BaJupSGV.js +6 -0
- package/web/assets/python-XWrMqdhO.js +6 -0
- package/web/assets/qsharp-DXyYeYxl.js +6 -0
- package/web/assets/r-CdQndTaG.js +6 -0
- package/web/assets/razor-DPlhCpIs.js +6 -0
- package/web/assets/redis-CVwtpugi.js +6 -0
- package/web/assets/redshift-25W9uPmb.js +6 -0
- package/web/assets/restructuredtext-DfzH4Xui.js +6 -0
- package/web/assets/router-vendor-DHlGizSU.js +41 -0
- package/web/assets/ruby-Cp1zYvxS.js +6 -0
- package/web/assets/rust-D5C2fndG.js +6 -0
- package/web/assets/sb-CDntyWJ8.js +6 -0
- package/web/assets/scala-BoFRg7Ot.js +6 -0
- package/web/assets/scheme-Bio4gycK.js +6 -0
- package/web/assets/scss-4Ik7cdeQ.js +8 -0
- package/web/assets/shell-CX-rkNHf.js +6 -0
- package/web/assets/solidity-Tw7wswEv.js +6 -0
- package/web/assets/sophia-C5WLch3f.js +6 -0
- package/web/assets/sparql-DHaeiCBh.js +6 -0
- package/web/assets/sql-CCSDG5nI.js +6 -0
- package/web/assets/st-pnP8ivHi.js +6 -0
- package/web/assets/swift-DwJ7jVG9.js +8 -0
- package/web/assets/systemverilog-B9Xyijhd.js +6 -0
- package/web/assets/tcl-DnHyzjbg.js +6 -0
- package/web/assets/tsMode-BbA1Jbf3.js +16 -0
- package/web/assets/twig-CPajHgWi.js +6 -0
- package/web/assets/typescript-DcLHYzvH.js +6 -0
- package/web/assets/typespec-D-MeaMDU.js +6 -0
- package/web/assets/ui-vendor-BSco96uv.js +51 -0
- package/web/assets/utils-vendor-DaJ2Dubl.js +911 -0
- package/web/assets/vb-DgyLZaXg.js +6 -0
- package/web/assets/wgsl-DYQUnd45.js +303 -0
- package/web/assets/xml-xKYS3dO6.js +6 -0
- package/web/assets/yaml-CNmlXqzH.js +6 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +22 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration E2E Test Definitions
|
|
3
|
+
*
|
|
4
|
+
* This module defines the orchestration tests that validate each behavior
|
|
5
|
+
* in the orchestration system. Tests run against an isolated test workspace.
|
|
6
|
+
*
|
|
7
|
+
* Each test supports dual-mode execution:
|
|
8
|
+
* - `mock` (default): Uses mock session manager, simulates agent behavior
|
|
9
|
+
* - `real`: Spawns actual Claude processes via SpawnerService
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { TaskStatus } from '@stoneforge/core';
|
|
14
|
+
import { waitFor, waitForTaskStatus, waitForTaskAssignment, waitForSessionStart, waitForSessionEnd, waitForTaskMeta, waitForGitCommit, pass, fail, sleep, uniqueId, } from './test-utils.js';
|
|
15
|
+
import { createTestWorker, createTestDirector, createTestSteward, createTestTask, } from './test-context.js';
|
|
16
|
+
import { buildTestDirectorPrompt, buildTestStewardPrompt, } from './test-prompts.js';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Test 1: Director Creates Tasks
|
|
19
|
+
// ============================================================================
|
|
20
|
+
export const directorCreatesTasksTest = {
|
|
21
|
+
id: 'director-creates-tasks',
|
|
22
|
+
name: 'Director creates tasks when prompted',
|
|
23
|
+
description: 'Send a feature request to director, verify task is created',
|
|
24
|
+
timeout: 120000,
|
|
25
|
+
tags: ['director', 'task'],
|
|
26
|
+
async run(ctx) {
|
|
27
|
+
if (ctx.mode === 'mock') {
|
|
28
|
+
return runDirectorCreatesTasksMock(ctx);
|
|
29
|
+
}
|
|
30
|
+
return runDirectorCreatesTasksReal(ctx);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
async function runDirectorCreatesTasksMock(ctx) {
|
|
34
|
+
// 1. Register a director agent
|
|
35
|
+
ctx.log('Registering director agent...');
|
|
36
|
+
const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
|
|
37
|
+
ctx.log(`Registered director: ${director.id}`);
|
|
38
|
+
// 2. Simulate director creating a task
|
|
39
|
+
ctx.log('Creating task (simulating director behavior)...');
|
|
40
|
+
const task = await createTestTask(ctx, 'Add /health endpoint that returns { status: "ok" }', {
|
|
41
|
+
priority: 5,
|
|
42
|
+
tags: ['test', 'feature', 'health'],
|
|
43
|
+
acceptanceCriteria: 'Endpoint returns { status: "ok" } on GET /health',
|
|
44
|
+
});
|
|
45
|
+
if (!task) {
|
|
46
|
+
return fail('Failed to create task');
|
|
47
|
+
}
|
|
48
|
+
// 3. Verify task was created correctly
|
|
49
|
+
ctx.log(`Task created: ${task.id}`);
|
|
50
|
+
const retrieved = await ctx.api.get(task.id);
|
|
51
|
+
if (!retrieved) {
|
|
52
|
+
return fail('Task not found after creation');
|
|
53
|
+
}
|
|
54
|
+
if (!retrieved.title.toLowerCase().includes('health')) {
|
|
55
|
+
return fail(`Task title doesn't contain 'health': ${retrieved.title}`);
|
|
56
|
+
}
|
|
57
|
+
return pass(`Director created task: "${retrieved.title}"`, {
|
|
58
|
+
taskId: task.id,
|
|
59
|
+
taskTitle: task.title,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function runDirectorCreatesTasksReal(ctx) {
|
|
63
|
+
// 1. Register a director agent
|
|
64
|
+
ctx.log('Registering director agent...');
|
|
65
|
+
const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
|
|
66
|
+
ctx.log(`Registered director: ${director.id}`);
|
|
67
|
+
// 2. Start director session with a prompt to create a task
|
|
68
|
+
ctx.log('Starting director session...');
|
|
69
|
+
const prompt = buildTestDirectorPrompt('Create a task titled "Add /health endpoint" with acceptance criteria "Endpoint returns { status: ok } on GET /health"');
|
|
70
|
+
const { session } = await ctx.sessionManager.startSession(director.id, {
|
|
71
|
+
workingDirectory: ctx.tempWorkspace,
|
|
72
|
+
initialPrompt: prompt,
|
|
73
|
+
interactive: false,
|
|
74
|
+
});
|
|
75
|
+
ctx.log(`Director session started: ${session.id}`);
|
|
76
|
+
// 3. Wait for a task with 'health' in the title to appear
|
|
77
|
+
ctx.log('Waiting for director to create task...');
|
|
78
|
+
const task = await waitFor(async () => {
|
|
79
|
+
// List all tasks and look for one containing 'health'
|
|
80
|
+
const tasks = await ctx.stoneforgeApi.list({ type: 'task' });
|
|
81
|
+
const healthTask = tasks.find(t => t.title.toLowerCase().includes('health'));
|
|
82
|
+
return healthTask ?? null;
|
|
83
|
+
}, { timeout: 180000, interval: 3000, description: 'director to create health task' });
|
|
84
|
+
// 4. Wait for session to end
|
|
85
|
+
await waitForSessionEnd(ctx.sessionManager, session.id, { timeout: 60000 }).catch(() => { });
|
|
86
|
+
return pass(`Director created task: "${task.title}"`, {
|
|
87
|
+
taskId: task.id,
|
|
88
|
+
taskTitle: task.title,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Test 2: Director Creates Plans
|
|
93
|
+
// ============================================================================
|
|
94
|
+
export const directorCreatesPlansTest = {
|
|
95
|
+
id: 'director-creates-plans',
|
|
96
|
+
name: 'Director creates plans for complex goals',
|
|
97
|
+
description: 'Send a complex goal to director, verify plan with tasks is created',
|
|
98
|
+
timeout: 120000,
|
|
99
|
+
tags: ['director', 'plan'],
|
|
100
|
+
async run(ctx) {
|
|
101
|
+
if (ctx.mode === 'mock') {
|
|
102
|
+
return runDirectorCreatesPlansMock(ctx);
|
|
103
|
+
}
|
|
104
|
+
return runDirectorCreatesPlansReal(ctx);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
async function runDirectorCreatesPlansMock(ctx) {
|
|
108
|
+
// 1. Register a director
|
|
109
|
+
const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
|
|
110
|
+
ctx.log(`Registered director: ${director.id}`);
|
|
111
|
+
// 2. Simulate creating a plan with multiple tasks
|
|
112
|
+
ctx.log('Creating plan (simulating director behavior)...');
|
|
113
|
+
const task1 = await createTestTask(ctx, 'Set up API framework', {
|
|
114
|
+
priority: 5,
|
|
115
|
+
tags: ['test', 'api', 'setup'],
|
|
116
|
+
});
|
|
117
|
+
const task2 = await createTestTask(ctx, 'Implement authentication', {
|
|
118
|
+
priority: 4,
|
|
119
|
+
tags: ['test', 'api', 'auth'],
|
|
120
|
+
});
|
|
121
|
+
const task3 = await createTestTask(ctx, 'Add rate limiting', {
|
|
122
|
+
priority: 3,
|
|
123
|
+
tags: ['test', 'api', 'security'],
|
|
124
|
+
});
|
|
125
|
+
const tasks = [task1, task2, task3];
|
|
126
|
+
if (tasks.length < 2) {
|
|
127
|
+
return fail('Failed to create plan with multiple tasks');
|
|
128
|
+
}
|
|
129
|
+
return pass(`Director created plan with ${tasks.length} tasks`, {
|
|
130
|
+
taskCount: tasks.length,
|
|
131
|
+
tasks: tasks.map(t => ({ id: t.id, title: t.title })),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function runDirectorCreatesPlansReal(ctx) {
|
|
135
|
+
// 1. Register a director
|
|
136
|
+
const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
|
|
137
|
+
ctx.log(`Registered director: ${director.id}`);
|
|
138
|
+
// 2. Start director session with a complex goal
|
|
139
|
+
ctx.log('Starting director session...');
|
|
140
|
+
const prompt = buildTestDirectorPrompt('Create a plan for "Build a REST API". Create at least 3 tasks: ' +
|
|
141
|
+
'"Set up API framework", "Implement authentication", and "Add rate limiting".');
|
|
142
|
+
const { session } = await ctx.sessionManager.startSession(director.id, {
|
|
143
|
+
workingDirectory: ctx.tempWorkspace,
|
|
144
|
+
initialPrompt: prompt,
|
|
145
|
+
interactive: false,
|
|
146
|
+
});
|
|
147
|
+
ctx.log(`Director session started: ${session.id}`);
|
|
148
|
+
// 3. Wait for 3+ tasks to exist
|
|
149
|
+
ctx.log('Waiting for director to create plan tasks...');
|
|
150
|
+
const tasks = await waitFor(async () => {
|
|
151
|
+
const allTasks = await ctx.stoneforgeApi.list({ type: 'task' });
|
|
152
|
+
return allTasks.length >= 3 ? allTasks : null;
|
|
153
|
+
}, { timeout: 180000, interval: 3000, description: 'director to create 3+ tasks' });
|
|
154
|
+
// 4. Wait for session to end
|
|
155
|
+
await waitForSessionEnd(ctx.sessionManager, session.id, { timeout: 60000 }).catch(() => { });
|
|
156
|
+
return pass(`Director created plan with ${tasks.length} tasks`, {
|
|
157
|
+
taskCount: tasks.length,
|
|
158
|
+
tasks: tasks.map(t => ({ id: t.id, title: t.title })),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Test 3: Daemon Dispatches to Worker
|
|
163
|
+
// ============================================================================
|
|
164
|
+
export const daemonDispatchesWorkerTest = {
|
|
165
|
+
id: 'daemon-dispatches-worker',
|
|
166
|
+
name: 'Daemon dispatches unassigned task to available worker',
|
|
167
|
+
description: 'Create unassigned task, verify daemon assigns to worker',
|
|
168
|
+
timeout: 60000,
|
|
169
|
+
tags: ['daemon', 'dispatch', 'worker'],
|
|
170
|
+
async run(ctx) {
|
|
171
|
+
if (ctx.mode === 'mock') {
|
|
172
|
+
return runDaemonDispatchesWorkerMock(ctx);
|
|
173
|
+
}
|
|
174
|
+
return runDaemonDispatchesWorkerReal(ctx);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
async function runDaemonDispatchesWorkerMock(ctx) {
|
|
178
|
+
// 1. Create an ephemeral worker
|
|
179
|
+
ctx.log('Creating worker agent...');
|
|
180
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
181
|
+
ctx.log(`Registered worker: ${worker.id}`);
|
|
182
|
+
// 2. Create an unassigned task
|
|
183
|
+
ctx.log('Creating unassigned task...');
|
|
184
|
+
const task = await createTestTask(ctx, 'Test task for dispatch', {
|
|
185
|
+
priority: 5,
|
|
186
|
+
tags: ['test', 'dispatch'],
|
|
187
|
+
});
|
|
188
|
+
ctx.log(`Created task: ${task.id}`);
|
|
189
|
+
// 3. Manually trigger daemon poll
|
|
190
|
+
ctx.log('Triggering daemon poll...');
|
|
191
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
192
|
+
// 4. Wait for task to be assigned
|
|
193
|
+
ctx.log('Waiting for task assignment...');
|
|
194
|
+
const assigned = await waitFor(async () => {
|
|
195
|
+
const updated = await ctx.api.get(task.id);
|
|
196
|
+
if (!updated)
|
|
197
|
+
return null;
|
|
198
|
+
return updated.assignee ? updated : null;
|
|
199
|
+
}, { timeout: 30000, interval: 1000, description: 'task assignment' }).catch(() => null);
|
|
200
|
+
if (!assigned) {
|
|
201
|
+
return fail('Daemon did not assign task to worker');
|
|
202
|
+
}
|
|
203
|
+
return pass(`Task assigned to worker`, {
|
|
204
|
+
taskId: task.id,
|
|
205
|
+
assignee: assigned.assignee,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async function runDaemonDispatchesWorkerReal(ctx) {
|
|
209
|
+
// 1. Create an ephemeral worker
|
|
210
|
+
ctx.log('Creating worker agent...');
|
|
211
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
212
|
+
ctx.log(`Registered worker: ${worker.id}`);
|
|
213
|
+
// 2. Create an unassigned task
|
|
214
|
+
ctx.log('Creating unassigned task...');
|
|
215
|
+
const task = await createTestTask(ctx, 'Test task for dispatch', {
|
|
216
|
+
priority: 5,
|
|
217
|
+
tags: ['test', 'dispatch'],
|
|
218
|
+
});
|
|
219
|
+
ctx.log(`Created task: ${task.id}`);
|
|
220
|
+
// 3. Trigger daemon poll to dispatch the task
|
|
221
|
+
ctx.log('Triggering daemon poll...');
|
|
222
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
223
|
+
// 4. Wait for task to be assigned
|
|
224
|
+
ctx.log('Waiting for task assignment...');
|
|
225
|
+
const assigned = await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
|
|
226
|
+
// 5. Wait for a session to start for this worker
|
|
227
|
+
ctx.log('Waiting for session start...');
|
|
228
|
+
const session = await waitForSessionStart(ctx.sessionManager, worker.id, {
|
|
229
|
+
timeout: 60000,
|
|
230
|
+
}).catch(() => null);
|
|
231
|
+
return pass('Task assigned and session started', {
|
|
232
|
+
taskId: task.id,
|
|
233
|
+
assignee: assigned.assignee,
|
|
234
|
+
sessionStarted: !!session,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Test 4: Daemon Respects Dependencies
|
|
239
|
+
// ============================================================================
|
|
240
|
+
export const daemonRespectsDependenciesTest = {
|
|
241
|
+
id: 'daemon-respects-dependencies',
|
|
242
|
+
name: 'Daemon respects task dependencies',
|
|
243
|
+
description: 'Blocked task waits until dependency resolves',
|
|
244
|
+
timeout: 60000,
|
|
245
|
+
tags: ['daemon', 'dependencies'],
|
|
246
|
+
async run(ctx) {
|
|
247
|
+
if (ctx.mode === 'mock') {
|
|
248
|
+
return runDaemonRespectsDependenciesMock(ctx);
|
|
249
|
+
}
|
|
250
|
+
return runDaemonRespectsDependenciesReal(ctx);
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
async function runDaemonRespectsDependenciesMock(ctx) {
|
|
254
|
+
// 1. Create worker
|
|
255
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
256
|
+
ctx.log(`Registered worker: ${worker.id}`);
|
|
257
|
+
// 2. Create two tasks where task2 depends on task1
|
|
258
|
+
const task1 = await createTestTask(ctx, 'First task', { priority: 5 });
|
|
259
|
+
const task2 = await createTestTask(ctx, 'Dependent task', { priority: 5 });
|
|
260
|
+
// 3. Add dependency: task2 is blocked by task1
|
|
261
|
+
await ctx.api.addDependency({
|
|
262
|
+
blockedId: task2.id,
|
|
263
|
+
blockerId: task1.id,
|
|
264
|
+
type: 'blocks',
|
|
265
|
+
actor: ctx.systemEntityId,
|
|
266
|
+
});
|
|
267
|
+
ctx.log(`Created dependency: ${task1.id} blocks ${task2.id}`);
|
|
268
|
+
// 4. Poll daemon - should only assign task1, not task2
|
|
269
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
270
|
+
await sleep(1000);
|
|
271
|
+
// 5. Check that task2 is still unassigned
|
|
272
|
+
const task2After = await ctx.api.get(task2.id);
|
|
273
|
+
if (task2After?.assignee) {
|
|
274
|
+
return fail('Daemon assigned blocked task before dependency resolved');
|
|
275
|
+
}
|
|
276
|
+
// 6. Complete task1 (simulate) and stop worker session so it becomes available
|
|
277
|
+
await ctx.api.update(task1.id, { status: TaskStatus.CLOSED });
|
|
278
|
+
const activeSession = ctx.sessionManager.getActiveSession(worker.id);
|
|
279
|
+
if (activeSession) {
|
|
280
|
+
await ctx.sessionManager.stopSession(activeSession.id, { graceful: false });
|
|
281
|
+
}
|
|
282
|
+
ctx.log('Completed task1 and freed worker');
|
|
283
|
+
// 7. Now poll again - task2 should be assignable
|
|
284
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
285
|
+
const task2Final = await waitFor(async () => {
|
|
286
|
+
const updated = await ctx.api.get(task2.id);
|
|
287
|
+
return updated?.assignee ? updated : null;
|
|
288
|
+
}, { timeout: 10000, interval: 1000, description: 'blocked task assignment' }).catch(() => null);
|
|
289
|
+
if (!task2Final?.assignee) {
|
|
290
|
+
return fail('Daemon did not assign task after dependency resolved');
|
|
291
|
+
}
|
|
292
|
+
return pass('Daemon correctly respected dependencies', {
|
|
293
|
+
task1Id: task1.id,
|
|
294
|
+
task2Id: task2.id,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async function runDaemonRespectsDependenciesReal(ctx) {
|
|
298
|
+
// 1. Create worker
|
|
299
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
300
|
+
ctx.log(`Registered worker: ${worker.id}`);
|
|
301
|
+
// 2. Create two tasks where task2 depends on task1
|
|
302
|
+
const task1 = await createTestTask(ctx, 'First task - create hello.txt', { priority: 5 });
|
|
303
|
+
const task2 = await createTestTask(ctx, 'Dependent task - create world.txt', { priority: 5 });
|
|
304
|
+
// 3. Add dependency: task2 is blocked by task1
|
|
305
|
+
await ctx.api.addDependency({
|
|
306
|
+
blockedId: task2.id,
|
|
307
|
+
blockerId: task1.id,
|
|
308
|
+
type: 'blocks',
|
|
309
|
+
actor: ctx.systemEntityId,
|
|
310
|
+
});
|
|
311
|
+
ctx.log(`Created dependency: ${task1.id} blocks ${task2.id}`);
|
|
312
|
+
// 4. Poll daemon — should only assign task1
|
|
313
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
314
|
+
await sleep(3000);
|
|
315
|
+
// 5. Verify task2 is still unassigned
|
|
316
|
+
const task2After = await ctx.api.get(task2.id);
|
|
317
|
+
if (task2After?.assignee) {
|
|
318
|
+
return fail('Daemon assigned blocked task before dependency resolved');
|
|
319
|
+
}
|
|
320
|
+
ctx.log('Verified: blocked task is not assigned');
|
|
321
|
+
// 6. Close task1 and free the worker session to unblock task2
|
|
322
|
+
await ctx.api.update(task1.id, { status: TaskStatus.CLOSED });
|
|
323
|
+
const activeSession = ctx.sessionManager.getActiveSession(worker.id);
|
|
324
|
+
if (activeSession) {
|
|
325
|
+
await ctx.sessionManager.stopSession(activeSession.id, { graceful: false });
|
|
326
|
+
}
|
|
327
|
+
ctx.log('Completed task1 and freed worker');
|
|
328
|
+
// 7. Poll again — task2 should now be assignable
|
|
329
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
330
|
+
const task2Final = await waitForTaskAssignment(ctx.api, task2.id, { timeout: 120000 })
|
|
331
|
+
.catch(() => null);
|
|
332
|
+
if (!task2Final?.assignee) {
|
|
333
|
+
return fail('Daemon did not assign task after dependency resolved');
|
|
334
|
+
}
|
|
335
|
+
return pass('Daemon correctly respected dependencies', {
|
|
336
|
+
task1Id: task1.id,
|
|
337
|
+
task2Id: task2.id,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Test 5: Worker Uses Worktree
|
|
342
|
+
// ============================================================================
|
|
343
|
+
export const workerUsesWorktreeTest = {
|
|
344
|
+
id: 'worker-uses-worktree',
|
|
345
|
+
name: 'Worker operates in isolated worktree',
|
|
346
|
+
description: 'Verify worker is spawned in a git worktree directory',
|
|
347
|
+
timeout: 60000,
|
|
348
|
+
tags: ['worker', 'worktree', 'git'],
|
|
349
|
+
async run(ctx) {
|
|
350
|
+
if (ctx.mode === 'mock') {
|
|
351
|
+
return runWorkerUsesWorktreeMock(ctx);
|
|
352
|
+
}
|
|
353
|
+
return runWorkerUsesWorktreeReal(ctx);
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
async function runWorkerUsesWorktreeMock(ctx) {
|
|
357
|
+
// 1. Create worker and task
|
|
358
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
359
|
+
const task = await createTestTask(ctx, 'Test worktree usage', { priority: 5 });
|
|
360
|
+
// 2. Create worktree for the task
|
|
361
|
+
ctx.log('Creating worktree...');
|
|
362
|
+
const worktreeResult = await ctx.worktreeManager.createWorktree({
|
|
363
|
+
agentName: worker.name,
|
|
364
|
+
taskId: task.id,
|
|
365
|
+
taskTitle: task.title,
|
|
366
|
+
});
|
|
367
|
+
ctx.log(`Worktree created at: ${worktreeResult.path}`);
|
|
368
|
+
// 3. Verify worktree exists
|
|
369
|
+
const exists = await ctx.worktreeManager.worktreeExists(worktreeResult.path);
|
|
370
|
+
if (!exists) {
|
|
371
|
+
return fail(`Worktree does not exist at: ${worktreeResult.path}`);
|
|
372
|
+
}
|
|
373
|
+
// 4. Verify it's a valid git worktree
|
|
374
|
+
const worktreeInfo = await ctx.worktreeManager.getWorktree(worktreeResult.path);
|
|
375
|
+
if (!worktreeInfo) {
|
|
376
|
+
return fail('Failed to get worktree info');
|
|
377
|
+
}
|
|
378
|
+
// 5. Verify branch naming convention
|
|
379
|
+
const expectedBranchPattern = /^agent\/.+\/.+/;
|
|
380
|
+
if (!expectedBranchPattern.test(worktreeResult.branch)) {
|
|
381
|
+
return fail(`Branch name doesn't match pattern: ${worktreeResult.branch}`);
|
|
382
|
+
}
|
|
383
|
+
// 6. Verify path is in worktrees directory
|
|
384
|
+
if (!worktreeResult.path.includes('.worktrees')) {
|
|
385
|
+
return fail(`Worktree path not in .worktrees directory: ${worktreeResult.path}`);
|
|
386
|
+
}
|
|
387
|
+
return pass(`Worker running in worktree: ${worktreeResult.path}`, {
|
|
388
|
+
worktreePath: worktreeResult.path,
|
|
389
|
+
branch: worktreeResult.branch,
|
|
390
|
+
isGitWorktree: true,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async function runWorkerUsesWorktreeReal(ctx) {
|
|
394
|
+
// 1. Create worker and task
|
|
395
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
396
|
+
const task = await createTestTask(ctx, 'Test worktree usage', { priority: 5 });
|
|
397
|
+
// 2. Let daemon dispatch the task (creates worktree automatically)
|
|
398
|
+
ctx.log('Triggering daemon poll for dispatch...');
|
|
399
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
400
|
+
// 3. Wait for task to be assigned
|
|
401
|
+
await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
|
|
402
|
+
// 4. Wait for session to start
|
|
403
|
+
const session = await waitForSessionStart(ctx.sessionManager, worker.id, { timeout: 60000 });
|
|
404
|
+
// 5. Verify session working directory is in .worktrees
|
|
405
|
+
if (!session.workingDirectory.includes('.worktrees')) {
|
|
406
|
+
return fail(`Session working directory not in .worktrees: ${session.workingDirectory}`);
|
|
407
|
+
}
|
|
408
|
+
// 6. Verify worktree branch pattern
|
|
409
|
+
if (session.worktree) {
|
|
410
|
+
const worktreeInfo = await ctx.worktreeManager.getWorktree(session.worktree);
|
|
411
|
+
if (worktreeInfo) {
|
|
412
|
+
const expectedBranchPattern = /^agent\/.+\/.+/;
|
|
413
|
+
if (!expectedBranchPattern.test(worktreeInfo.branch)) {
|
|
414
|
+
return fail(`Branch name doesn't match pattern: ${worktreeInfo.branch}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return pass(`Worker running in worktree: ${session.workingDirectory}`, {
|
|
419
|
+
worktreePath: session.workingDirectory,
|
|
420
|
+
worktree: session.worktree,
|
|
421
|
+
sessionId: session.id,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
// ============================================================================
|
|
425
|
+
// Test 6: Worker Commits Work
|
|
426
|
+
// ============================================================================
|
|
427
|
+
export const workerCommitsWorkTest = {
|
|
428
|
+
id: 'worker-commits-work',
|
|
429
|
+
name: 'Worker makes commits in worktree branch',
|
|
430
|
+
description: 'Verify worker can commit changes to its worktree branch',
|
|
431
|
+
timeout: 60000,
|
|
432
|
+
tags: ['worker', 'git', 'commit'],
|
|
433
|
+
async run(ctx) {
|
|
434
|
+
if (ctx.mode === 'mock') {
|
|
435
|
+
return runWorkerCommitsWorkMock(ctx);
|
|
436
|
+
}
|
|
437
|
+
return runWorkerCommitsWorkReal(ctx);
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
async function runWorkerCommitsWorkMock(ctx) {
|
|
441
|
+
const { execSync } = await import('node:child_process');
|
|
442
|
+
const { writeFile } = await import('node:fs/promises');
|
|
443
|
+
const { join } = await import('node:path');
|
|
444
|
+
// 1. Create worker and worktree
|
|
445
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
446
|
+
const task = await createTestTask(ctx, 'Test commits', { priority: 5 });
|
|
447
|
+
const worktreeResult = await ctx.worktreeManager.createWorktree({
|
|
448
|
+
agentName: worker.name,
|
|
449
|
+
taskId: task.id,
|
|
450
|
+
taskTitle: task.title,
|
|
451
|
+
});
|
|
452
|
+
ctx.log(`Working in worktree: ${worktreeResult.path}`);
|
|
453
|
+
// 2. Make a change in the worktree
|
|
454
|
+
const testFilePath = join(worktreeResult.path, 'test-file.txt');
|
|
455
|
+
await writeFile(testFilePath, 'Test content from worker\n');
|
|
456
|
+
ctx.log('Created test file');
|
|
457
|
+
// 3. Commit the change
|
|
458
|
+
try {
|
|
459
|
+
execSync('git add -A && git commit -m "Test commit from worker"', {
|
|
460
|
+
cwd: worktreeResult.path,
|
|
461
|
+
stdio: 'pipe',
|
|
462
|
+
});
|
|
463
|
+
ctx.log('Committed changes');
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
return fail(`Failed to commit: ${error instanceof Error ? error.message : String(error)}`);
|
|
467
|
+
}
|
|
468
|
+
// 4. Verify commit exists
|
|
469
|
+
try {
|
|
470
|
+
const log = execSync('git log --oneline -1', {
|
|
471
|
+
cwd: worktreeResult.path,
|
|
472
|
+
encoding: 'utf8',
|
|
473
|
+
});
|
|
474
|
+
if (!log.includes('Test commit from worker')) {
|
|
475
|
+
return fail(`Commit message not found in log: ${log}`);
|
|
476
|
+
}
|
|
477
|
+
ctx.log(`Commit verified: ${log.trim()}`);
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
return fail(`Failed to verify commit: ${error instanceof Error ? error.message : String(error)}`);
|
|
481
|
+
}
|
|
482
|
+
return pass('Worker successfully committed changes', {
|
|
483
|
+
worktreePath: worktreeResult.path,
|
|
484
|
+
branch: worktreeResult.branch,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
async function runWorkerCommitsWorkReal(ctx) {
|
|
488
|
+
// 1. Create worker and task that requires creating a file
|
|
489
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
490
|
+
const task = await createTestTask(ctx, 'Create file test-output.txt containing "hello world"', {
|
|
491
|
+
priority: 5,
|
|
492
|
+
tags: ['test', 'commit'],
|
|
493
|
+
acceptanceCriteria: 'File test-output.txt exists and contains "hello world"',
|
|
494
|
+
});
|
|
495
|
+
// 2. Let daemon dispatch
|
|
496
|
+
ctx.log('Triggering daemon poll for dispatch...');
|
|
497
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
498
|
+
// 3. Wait for assignment and session
|
|
499
|
+
await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
|
|
500
|
+
const session = await waitForSessionStart(ctx.sessionManager, worker.id, { timeout: 60000 });
|
|
501
|
+
const worktreePath = session.workingDirectory;
|
|
502
|
+
ctx.log(`Worker running in: ${worktreePath}`);
|
|
503
|
+
// 4. Wait for a git commit in the worktree
|
|
504
|
+
ctx.log('Waiting for worker to commit...');
|
|
505
|
+
const commitHash = await waitForGitCommit(worktreePath, { timeout: 240000 });
|
|
506
|
+
return pass(`Worker committed changes: ${commitHash}`, {
|
|
507
|
+
worktreePath,
|
|
508
|
+
commitHash,
|
|
509
|
+
sessionId: session.id,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
// ============================================================================
|
|
513
|
+
// Test 7: Worker Creates Merge Request
|
|
514
|
+
// ============================================================================
|
|
515
|
+
export const workerCreatesMergeRequestTest = {
|
|
516
|
+
id: 'worker-creates-merge-request',
|
|
517
|
+
name: 'Worker creates merge request on task completion',
|
|
518
|
+
description: 'Worker creates PR/MR when finishing a task',
|
|
519
|
+
timeout: 90000,
|
|
520
|
+
tags: ['worker', 'merge-request', 'git'],
|
|
521
|
+
async run(ctx) {
|
|
522
|
+
if (ctx.mode === 'mock') {
|
|
523
|
+
return runWorkerCreatesMergeRequestMock(ctx);
|
|
524
|
+
}
|
|
525
|
+
return runWorkerCreatesMergeRequestReal(ctx);
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
async function runWorkerCreatesMergeRequestMock(ctx) {
|
|
529
|
+
// 1. Create worker and task
|
|
530
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
531
|
+
const task = await createTestTask(ctx, 'Test merge request creation', { priority: 5 });
|
|
532
|
+
// 2. Create worktree and make changes
|
|
533
|
+
const worktreeResult = await ctx.worktreeManager.createWorktree({
|
|
534
|
+
agentName: worker.name,
|
|
535
|
+
taskId: task.id,
|
|
536
|
+
taskTitle: task.title,
|
|
537
|
+
});
|
|
538
|
+
// 3. Simulate worker completing task by updating task metadata with MR info
|
|
539
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
540
|
+
branch: worktreeResult.branch,
|
|
541
|
+
worktree: worktreeResult.path,
|
|
542
|
+
mergeRequestId: 1,
|
|
543
|
+
mergeRequestUrl: `https://github.com/test/repo/pull/1`,
|
|
544
|
+
mergeStatus: 'pending',
|
|
545
|
+
});
|
|
546
|
+
ctx.log('Updated task with MR info');
|
|
547
|
+
// 4. Verify MR info is stored
|
|
548
|
+
const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
|
|
549
|
+
if (!taskMeta?.mergeRequestId) {
|
|
550
|
+
return fail('PR number not set in task metadata');
|
|
551
|
+
}
|
|
552
|
+
if (!taskMeta?.mergeRequestUrl) {
|
|
553
|
+
return fail('PR URL not set in task metadata');
|
|
554
|
+
}
|
|
555
|
+
return pass(`Merge request created: PR #${taskMeta.mergeRequestId}`, {
|
|
556
|
+
mergeRequestId: taskMeta.mergeRequestId,
|
|
557
|
+
mergeRequestUrl: taskMeta.mergeRequestUrl,
|
|
558
|
+
branch: taskMeta.branch,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
async function runWorkerCreatesMergeRequestReal(ctx) {
|
|
562
|
+
const { execSync } = await import('node:child_process');
|
|
563
|
+
// 1. Create worker (needed for dispatch) and task
|
|
564
|
+
await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
565
|
+
const task = await createTestTask(ctx, 'Create a new file and push branch', {
|
|
566
|
+
priority: 5,
|
|
567
|
+
tags: ['test', 'merge-request'],
|
|
568
|
+
acceptanceCriteria: 'Branch pushed to remote with changes',
|
|
569
|
+
});
|
|
570
|
+
// 2. Let daemon dispatch
|
|
571
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
572
|
+
await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
|
|
573
|
+
// 3. Wait for task metadata to have a branch set
|
|
574
|
+
ctx.log('Waiting for worker to push branch...');
|
|
575
|
+
const meta = await waitForTaskMeta(ctx.api, task.id, (m) => !!m.branch, { timeout: 240000 });
|
|
576
|
+
// 4. Verify the branch was pushed to the bare remote
|
|
577
|
+
try {
|
|
578
|
+
const branches = execSync('git branch -r', {
|
|
579
|
+
cwd: ctx.tempWorkspace,
|
|
580
|
+
encoding: 'utf8',
|
|
581
|
+
});
|
|
582
|
+
const hasBranch = branches.includes(meta.branch);
|
|
583
|
+
ctx.log(`Remote branches include worker branch: ${hasBranch}`);
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
ctx.log('Could not check remote branches');
|
|
587
|
+
}
|
|
588
|
+
return pass(`Worker set branch metadata: ${meta.branch}`, {
|
|
589
|
+
branch: meta.branch,
|
|
590
|
+
taskId: task.id,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// ============================================================================
|
|
594
|
+
// Test 8: Worker Marks Task Complete
|
|
595
|
+
// ============================================================================
|
|
596
|
+
export const workerMarksTaskCompleteTest = {
|
|
597
|
+
id: 'worker-marks-task-complete',
|
|
598
|
+
name: 'Worker marks task as complete',
|
|
599
|
+
description: 'Task status changes to closed when worker finishes',
|
|
600
|
+
timeout: 30000,
|
|
601
|
+
tags: ['worker', 'task', 'completion'],
|
|
602
|
+
async run(ctx) {
|
|
603
|
+
if (ctx.mode === 'mock') {
|
|
604
|
+
return runWorkerMarksTaskCompleteMock(ctx);
|
|
605
|
+
}
|
|
606
|
+
return runWorkerMarksTaskCompleteReal(ctx);
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
async function runWorkerMarksTaskCompleteMock(ctx) {
|
|
610
|
+
// 1. Create worker and task
|
|
611
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
612
|
+
const task = await createTestTask(ctx, 'Test task completion', { priority: 5 });
|
|
613
|
+
// 2. Assign task to worker
|
|
614
|
+
await ctx.api.assignTaskToAgent(task.id, worker.id, {
|
|
615
|
+
markAsStarted: true,
|
|
616
|
+
});
|
|
617
|
+
ctx.log('Assigned task to worker');
|
|
618
|
+
// 3. Verify task is in progress
|
|
619
|
+
const taskInProgress = await ctx.api.get(task.id);
|
|
620
|
+
if (taskInProgress?.status !== TaskStatus.IN_PROGRESS) {
|
|
621
|
+
return fail(`Expected task in_progress, got: ${taskInProgress?.status}`);
|
|
622
|
+
}
|
|
623
|
+
// 4. Complete the task (simulating worker completion)
|
|
624
|
+
await ctx.taskAssignment.completeTask(task.id, {
|
|
625
|
+
summary: 'Task completed successfully',
|
|
626
|
+
});
|
|
627
|
+
ctx.log('Completed task');
|
|
628
|
+
// 5. Verify task is closed
|
|
629
|
+
const taskClosed = await ctx.api.get(task.id);
|
|
630
|
+
if (taskClosed?.status !== TaskStatus.CLOSED) {
|
|
631
|
+
return fail(`Expected task closed, got: ${taskClosed?.status}`);
|
|
632
|
+
}
|
|
633
|
+
return pass(`Task status is '${taskClosed.status}'`, {
|
|
634
|
+
taskId: task.id,
|
|
635
|
+
status: taskClosed.status,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async function runWorkerMarksTaskCompleteReal(ctx) {
|
|
639
|
+
// 1. Create worker (needed for dispatch) and a simple task
|
|
640
|
+
await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
641
|
+
const task = await createTestTask(ctx, 'Add a comment to src/index.ts', {
|
|
642
|
+
priority: 5,
|
|
643
|
+
tags: ['test', 'simple'],
|
|
644
|
+
acceptanceCriteria: 'A comment is added to src/index.ts',
|
|
645
|
+
});
|
|
646
|
+
// 2. Let daemon dispatch
|
|
647
|
+
ctx.log('Triggering daemon poll...');
|
|
648
|
+
await ctx.daemon.pollWorkerAvailability();
|
|
649
|
+
// 3. Wait for task to reach CLOSED status
|
|
650
|
+
ctx.log('Waiting for worker to complete task...');
|
|
651
|
+
const closedTask = await waitForTaskStatus(ctx.api, task.id, TaskStatus.CLOSED, { timeout: 240000 });
|
|
652
|
+
return pass(`Task status is '${closedTask.status}'`, {
|
|
653
|
+
taskId: task.id,
|
|
654
|
+
status: closedTask.status,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
// ============================================================================
|
|
658
|
+
// Test 9: Worker Handoff on Context Fill
|
|
659
|
+
// ============================================================================
|
|
660
|
+
export const workerHandoffOnContextFillTest = {
|
|
661
|
+
id: 'worker-handoff-context',
|
|
662
|
+
name: 'Worker triggers handoff before context exhaustion',
|
|
663
|
+
description: 'Worker creates handoff task when approaching context limits',
|
|
664
|
+
timeout: 60000,
|
|
665
|
+
tags: ['worker', 'handoff', 'context'],
|
|
666
|
+
async run(ctx) {
|
|
667
|
+
if (ctx.mode === 'mock') {
|
|
668
|
+
return runWorkerHandoffOnContextFillMock(ctx);
|
|
669
|
+
}
|
|
670
|
+
return runWorkerHandoffOnContextFillReal(ctx);
|
|
671
|
+
},
|
|
672
|
+
};
|
|
673
|
+
async function runWorkerHandoffOnContextFillMock(ctx) {
|
|
674
|
+
// 1. Create worker and task
|
|
675
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
676
|
+
const task = await createTestTask(ctx, 'Long running task', { priority: 5 });
|
|
677
|
+
// 2. Assign task and create worktree
|
|
678
|
+
const worktreeResult = await ctx.worktreeManager.createWorktree({
|
|
679
|
+
agentName: worker.name,
|
|
680
|
+
taskId: task.id,
|
|
681
|
+
taskTitle: task.title,
|
|
682
|
+
});
|
|
683
|
+
await ctx.api.assignTaskToAgent(task.id, worker.id, {
|
|
684
|
+
branch: worktreeResult.branch,
|
|
685
|
+
worktree: worktreeResult.path,
|
|
686
|
+
markAsStarted: true,
|
|
687
|
+
});
|
|
688
|
+
// 3. Simulate handoff by updating task metadata
|
|
689
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
690
|
+
handoffBranch: worktreeResult.branch,
|
|
691
|
+
handoffWorktree: worktreeResult.path,
|
|
692
|
+
handoffFrom: worker.id,
|
|
693
|
+
handoffAt: new Date().toISOString(),
|
|
694
|
+
handoffHistory: [{
|
|
695
|
+
sessionId: 'sim-session',
|
|
696
|
+
message: 'Context limit approaching, handing off to continue work',
|
|
697
|
+
branch: worktreeResult.branch,
|
|
698
|
+
worktree: worktreeResult.path,
|
|
699
|
+
handoffAt: new Date().toISOString(),
|
|
700
|
+
}],
|
|
701
|
+
});
|
|
702
|
+
// 4. Unassign from current worker (simulating handoff)
|
|
703
|
+
await ctx.api.update(task.id, {
|
|
704
|
+
assignee: undefined,
|
|
705
|
+
status: TaskStatus.OPEN,
|
|
706
|
+
});
|
|
707
|
+
ctx.log('Created handoff');
|
|
708
|
+
// 5. Verify handoff metadata exists
|
|
709
|
+
const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
|
|
710
|
+
if (!taskMeta?.handoffBranch) {
|
|
711
|
+
return fail('Handoff branch not set');
|
|
712
|
+
}
|
|
713
|
+
if (!taskMeta?.handoffHistory || taskMeta.handoffHistory.length === 0) {
|
|
714
|
+
return fail('Handoff history not set');
|
|
715
|
+
}
|
|
716
|
+
return pass('Worker created handoff successfully', {
|
|
717
|
+
handoffBranch: taskMeta.handoffBranch,
|
|
718
|
+
handoffFrom: taskMeta.handoffFrom,
|
|
719
|
+
handoffHistory: taskMeta.handoffHistory,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function runWorkerHandoffOnContextFillReal(ctx) {
|
|
723
|
+
// 1. Create worker and task
|
|
724
|
+
const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
725
|
+
const task = await createTestTask(ctx, 'Hand off this task', {
|
|
726
|
+
priority: 5,
|
|
727
|
+
tags: ['test', 'handoff'],
|
|
728
|
+
acceptanceCriteria: 'Task is handed off with a note',
|
|
729
|
+
});
|
|
730
|
+
// 2. Assign task to worker (needed so handoff command can find the assignment)
|
|
731
|
+
await ctx.api.assignTaskToAgent(task.id, worker.id, {
|
|
732
|
+
markAsStarted: true,
|
|
733
|
+
});
|
|
734
|
+
// 3. Start worker session with ultra-direct handoff prompt (like steward tests)
|
|
735
|
+
const dbFlag = ctx.dbPath ? ` --db "${ctx.dbPath}"` : '';
|
|
736
|
+
const command = `sf task handoff ${task.id} --message "Handing off to next worker"${dbFlag}`;
|
|
737
|
+
const prompt = `You are a test agent. Execute this one command immediately and stop.
|
|
738
|
+
|
|
739
|
+
COMMAND TO RUN:
|
|
740
|
+
\`\`\`
|
|
741
|
+
${command}
|
|
742
|
+
\`\`\`
|
|
743
|
+
|
|
744
|
+
RULES:
|
|
745
|
+
- Run ONLY the command above. Nothing else.
|
|
746
|
+
- The \`el\` command is already on PATH. Do not install or locate it.
|
|
747
|
+
- Do not explore the codebase, read files, or run other commands first.
|
|
748
|
+
- Do not ask questions. Just run the command.
|
|
749
|
+
- After running the command, stop immediately.`;
|
|
750
|
+
const { session } = await ctx.sessionManager.startSession(worker.id, {
|
|
751
|
+
workingDirectory: ctx.tempWorkspace,
|
|
752
|
+
initialPrompt: prompt,
|
|
753
|
+
});
|
|
754
|
+
ctx.log(`Worker session started: ${session.id}`);
|
|
755
|
+
// 4. Wait for handoff metadata to appear
|
|
756
|
+
ctx.log('Waiting for handoff...');
|
|
757
|
+
const meta = await waitForTaskMeta(ctx.api, task.id, (m) => Array.isArray(m.handoffHistory) && m.handoffHistory.length > 0, { timeout: 240000 });
|
|
758
|
+
// 5. Verify task was reopened
|
|
759
|
+
const reopened = await ctx.api.get(task.id);
|
|
760
|
+
if (reopened?.status !== TaskStatus.OPEN) {
|
|
761
|
+
ctx.log(`Task status after handoff: ${reopened?.status} (expected OPEN)`);
|
|
762
|
+
}
|
|
763
|
+
return pass('Worker created handoff successfully', {
|
|
764
|
+
handoffHistory: meta.handoffHistory,
|
|
765
|
+
handoffBranch: meta.handoffBranch,
|
|
766
|
+
taskStatus: reopened?.status,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
// ============================================================================
|
|
770
|
+
// Test 10: Daemon Spawns Steward for MR
|
|
771
|
+
// ============================================================================
|
|
772
|
+
export const daemonSpawnsStewardForMRTest = {
|
|
773
|
+
id: 'daemon-spawns-steward-mr',
|
|
774
|
+
name: 'Daemon spawns steward for merge request',
|
|
775
|
+
description: 'Merge request triggers steward spawn for review',
|
|
776
|
+
timeout: 60000,
|
|
777
|
+
tags: ['daemon', 'steward', 'merge-request'],
|
|
778
|
+
async run(ctx) {
|
|
779
|
+
if (ctx.mode === 'mock') {
|
|
780
|
+
return runDaemonSpawnsStewardForMRMock(ctx);
|
|
781
|
+
}
|
|
782
|
+
return runDaemonSpawnsStewardForMRReal(ctx);
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
async function runDaemonSpawnsStewardForMRMock(ctx) {
|
|
786
|
+
// 1. Create a merge steward
|
|
787
|
+
const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
|
|
788
|
+
focus: 'merge',
|
|
789
|
+
triggers: [{ type: 'event', event: 'merge_request_created' }],
|
|
790
|
+
});
|
|
791
|
+
ctx.log(`Registered merge steward: ${steward.id}`);
|
|
792
|
+
// 2. Register steward with scheduler
|
|
793
|
+
await ctx.stewardScheduler.registerSteward(steward.id);
|
|
794
|
+
// 3. Start the scheduler
|
|
795
|
+
await ctx.stewardScheduler.start();
|
|
796
|
+
// 4. Create a task with MR info
|
|
797
|
+
const task = await createTestTask(ctx, 'Task with merge request', { priority: 5 });
|
|
798
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
799
|
+
mergeRequestId: 42,
|
|
800
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/42',
|
|
801
|
+
mergeStatus: 'pending',
|
|
802
|
+
});
|
|
803
|
+
// 5. Publish merge request event
|
|
804
|
+
const triggered = await ctx.stewardScheduler.publishEvent('merge_request_created', {
|
|
805
|
+
taskId: task.id,
|
|
806
|
+
mergeRequestId: 42,
|
|
807
|
+
});
|
|
808
|
+
ctx.log(`Published MR event, triggered ${triggered} steward(s)`);
|
|
809
|
+
// 6. Verify steward was triggered
|
|
810
|
+
if (triggered === 0) {
|
|
811
|
+
return fail('No steward was triggered for merge request');
|
|
812
|
+
}
|
|
813
|
+
// 7. Check steward execution history
|
|
814
|
+
const history = ctx.stewardScheduler.getExecutionHistory({
|
|
815
|
+
stewardId: steward.id,
|
|
816
|
+
limit: 1,
|
|
817
|
+
});
|
|
818
|
+
if (history.length === 0) {
|
|
819
|
+
return fail('Steward execution not recorded');
|
|
820
|
+
}
|
|
821
|
+
return pass('Steward triggered for merge request', {
|
|
822
|
+
stewardId: steward.id,
|
|
823
|
+
triggerCount: triggered,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
async function runDaemonSpawnsStewardForMRReal(ctx) {
|
|
827
|
+
// 1. Create a merge steward
|
|
828
|
+
const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
|
|
829
|
+
focus: 'merge',
|
|
830
|
+
triggers: [{ type: 'event', event: 'merge_request_created' }],
|
|
831
|
+
});
|
|
832
|
+
ctx.log(`Registered merge steward: ${steward.id}`);
|
|
833
|
+
// 2. Register steward with scheduler
|
|
834
|
+
await ctx.stewardScheduler.registerSteward(steward.id);
|
|
835
|
+
await ctx.stewardScheduler.start();
|
|
836
|
+
// 3. Create a task with MR info
|
|
837
|
+
const task = await createTestTask(ctx, 'Task with merge request', { priority: 5 });
|
|
838
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
839
|
+
mergeRequestId: 42,
|
|
840
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/42',
|
|
841
|
+
mergeStatus: 'pending',
|
|
842
|
+
});
|
|
843
|
+
// 4. Publish merge request event
|
|
844
|
+
const triggered = await ctx.stewardScheduler.publishEvent('merge_request_created', {
|
|
845
|
+
taskId: task.id,
|
|
846
|
+
mergeRequestId: 42,
|
|
847
|
+
});
|
|
848
|
+
if (triggered === 0) {
|
|
849
|
+
return fail('No steward was triggered for merge request');
|
|
850
|
+
}
|
|
851
|
+
// 5. Wait for steward session to start
|
|
852
|
+
ctx.log('Waiting for steward session to start...');
|
|
853
|
+
const session = await waitForSessionStart(ctx.sessionManager, steward.id, { timeout: 120000 }).catch(() => null);
|
|
854
|
+
return pass('Steward triggered for merge request', {
|
|
855
|
+
stewardId: steward.id,
|
|
856
|
+
triggerCount: triggered,
|
|
857
|
+
sessionStarted: !!session,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
// ============================================================================
|
|
861
|
+
// Test 11: Steward Merges Passing MR
|
|
862
|
+
// ============================================================================
|
|
863
|
+
export const stewardMergesPassingMRTest = {
|
|
864
|
+
id: 'steward-merges-passing',
|
|
865
|
+
name: 'Steward reviews and merges passing PR',
|
|
866
|
+
description: 'Steward merges PR when tests pass',
|
|
867
|
+
timeout: 60000,
|
|
868
|
+
tags: ['steward', 'merge', 'tests'],
|
|
869
|
+
async run(ctx) {
|
|
870
|
+
if (ctx.mode === 'mock') {
|
|
871
|
+
return runStewardMergesPassingMRMock(ctx);
|
|
872
|
+
}
|
|
873
|
+
return runStewardMergesPassingMRReal(ctx);
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
async function runStewardMergesPassingMRMock(ctx) {
|
|
877
|
+
// 1. Create task with passing MR status
|
|
878
|
+
const task = await createTestTask(ctx, 'Task with passing tests', { priority: 5 });
|
|
879
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
880
|
+
mergeRequestId: 100,
|
|
881
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/100',
|
|
882
|
+
mergeStatus: 'testing',
|
|
883
|
+
testRunCount: 1,
|
|
884
|
+
lastTestResult: {
|
|
885
|
+
passed: true,
|
|
886
|
+
totalTests: 10,
|
|
887
|
+
passedTests: 10,
|
|
888
|
+
failedTests: 0,
|
|
889
|
+
durationMs: 5000,
|
|
890
|
+
completedAt: new Date().toISOString(),
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
ctx.log('Created task with passing test results');
|
|
894
|
+
// 2. Simulate steward merging the MR
|
|
895
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
896
|
+
mergeStatus: 'merged',
|
|
897
|
+
completedAt: new Date().toISOString(),
|
|
898
|
+
});
|
|
899
|
+
// 3. Close the task
|
|
900
|
+
await ctx.api.update(task.id, { status: TaskStatus.CLOSED });
|
|
901
|
+
// 4. Verify merge status
|
|
902
|
+
const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
|
|
903
|
+
if (taskMeta?.mergeStatus !== 'merged') {
|
|
904
|
+
return fail(`Expected merge status 'merged', got: ${taskMeta?.mergeStatus}`);
|
|
905
|
+
}
|
|
906
|
+
const updatedTask = await ctx.api.get(task.id);
|
|
907
|
+
if (updatedTask?.status !== TaskStatus.CLOSED) {
|
|
908
|
+
return fail(`Expected task closed, got: ${updatedTask?.status}`);
|
|
909
|
+
}
|
|
910
|
+
return pass('Steward merged passing PR', {
|
|
911
|
+
taskId: task.id,
|
|
912
|
+
mergeStatus: taskMeta.mergeStatus,
|
|
913
|
+
taskStatus: updatedTask.status,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
async function runStewardMergesPassingMRReal(ctx) {
|
|
917
|
+
// 1. Create steward
|
|
918
|
+
const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
|
|
919
|
+
focus: 'merge',
|
|
920
|
+
});
|
|
921
|
+
// 2. Create task with passing test metadata
|
|
922
|
+
const task = await createTestTask(ctx, 'Task with passing tests', { priority: 5 });
|
|
923
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
924
|
+
mergeRequestId: 100,
|
|
925
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/100',
|
|
926
|
+
mergeStatus: 'testing',
|
|
927
|
+
testRunCount: 1,
|
|
928
|
+
lastTestResult: {
|
|
929
|
+
passed: true,
|
|
930
|
+
totalTests: 10,
|
|
931
|
+
passedTests: 10,
|
|
932
|
+
failedTests: 0,
|
|
933
|
+
durationMs: 5000,
|
|
934
|
+
completedAt: new Date().toISOString(),
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
// 3. Start steward session with review prompt
|
|
938
|
+
const prompt = buildTestStewardPrompt('merge', task.id, { dbPath: ctx.dbPath });
|
|
939
|
+
const { session } = await ctx.sessionManager.startSession(steward.id, {
|
|
940
|
+
workingDirectory: ctx.tempWorkspace,
|
|
941
|
+
initialPrompt: prompt,
|
|
942
|
+
});
|
|
943
|
+
ctx.log(`Steward session started: ${session.id}`);
|
|
944
|
+
// 4. Wait for merge status to become 'merged'
|
|
945
|
+
ctx.log('Waiting for steward to merge...');
|
|
946
|
+
const meta = await waitForTaskMeta(ctx.api, task.id, (m) => m.mergeStatus === 'merged', { timeout: 240000 });
|
|
947
|
+
return pass('Steward merged passing PR', {
|
|
948
|
+
taskId: task.id,
|
|
949
|
+
mergeStatus: meta.mergeStatus,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
// ============================================================================
|
|
953
|
+
// Test 12: Steward Handoff Failing MR
|
|
954
|
+
// ============================================================================
|
|
955
|
+
export const stewardHandoffFailingMRTest = {
|
|
956
|
+
id: 'steward-handoff-failing',
|
|
957
|
+
name: 'Steward creates handoff for failing PR',
|
|
958
|
+
description: 'Steward hands off to worker when tests fail',
|
|
959
|
+
timeout: 60000,
|
|
960
|
+
tags: ['steward', 'handoff', 'tests'],
|
|
961
|
+
async run(ctx) {
|
|
962
|
+
if (ctx.mode === 'mock') {
|
|
963
|
+
return runStewardHandoffFailingMRMock(ctx);
|
|
964
|
+
}
|
|
965
|
+
return runStewardHandoffFailingMRReal(ctx);
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
async function runStewardHandoffFailingMRMock(ctx) {
|
|
969
|
+
// 1. Create worker for handoff (needed for reassignment)
|
|
970
|
+
await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
971
|
+
// 2. Create task with failing MR status
|
|
972
|
+
const task = await createTestTask(ctx, 'Task with failing tests', {
|
|
973
|
+
priority: 5,
|
|
974
|
+
tags: ['test', 'needs-fix'],
|
|
975
|
+
});
|
|
976
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
977
|
+
mergeRequestId: 101,
|
|
978
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/101',
|
|
979
|
+
mergeStatus: 'testing',
|
|
980
|
+
testRunCount: 1,
|
|
981
|
+
lastTestResult: {
|
|
982
|
+
passed: false,
|
|
983
|
+
totalTests: 10,
|
|
984
|
+
passedTests: 7,
|
|
985
|
+
failedTests: 3,
|
|
986
|
+
durationMs: 5000,
|
|
987
|
+
completedAt: new Date().toISOString(),
|
|
988
|
+
errorMessage: 'Test 1 failed, Test 2 failed, Test 3 failed',
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
ctx.log('Created task with failing test results');
|
|
992
|
+
// 3. Simulate steward marking as test_failed and creating handoff
|
|
993
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
994
|
+
mergeStatus: 'test_failed',
|
|
995
|
+
mergeFailureReason: '3 tests failed: Test 1, Test 2, Test 3',
|
|
996
|
+
handoffHistory: [{
|
|
997
|
+
sessionId: 'steward-session',
|
|
998
|
+
message: 'Tests failed, needs worker to fix issues',
|
|
999
|
+
handoffAt: new Date().toISOString(),
|
|
1000
|
+
}],
|
|
1001
|
+
});
|
|
1002
|
+
// 4. Reopen task for assignment
|
|
1003
|
+
await ctx.api.update(task.id, {
|
|
1004
|
+
status: TaskStatus.OPEN,
|
|
1005
|
+
assignee: undefined,
|
|
1006
|
+
});
|
|
1007
|
+
ctx.log('Created handoff for failing tests');
|
|
1008
|
+
// 5. Verify handoff was created
|
|
1009
|
+
const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
|
|
1010
|
+
if (taskMeta?.mergeStatus !== 'test_failed') {
|
|
1011
|
+
return fail(`Expected merge status 'test_failed', got: ${taskMeta?.mergeStatus}`);
|
|
1012
|
+
}
|
|
1013
|
+
if (!taskMeta?.handoffHistory || taskMeta.handoffHistory.length === 0) {
|
|
1014
|
+
return fail('Handoff history not set');
|
|
1015
|
+
}
|
|
1016
|
+
const updatedTask = await ctx.api.get(task.id);
|
|
1017
|
+
if (updatedTask?.status !== TaskStatus.OPEN) {
|
|
1018
|
+
return fail(`Expected task open for reassignment, got: ${updatedTask?.status}`);
|
|
1019
|
+
}
|
|
1020
|
+
return pass('Steward created handoff for failing PR', {
|
|
1021
|
+
taskId: task.id,
|
|
1022
|
+
mergeStatus: taskMeta.mergeStatus,
|
|
1023
|
+
handoffHistory: taskMeta.handoffHistory,
|
|
1024
|
+
taskStatus: updatedTask.status,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
async function runStewardHandoffFailingMRReal(ctx) {
|
|
1028
|
+
// 1. Create steward and worker (worker needed for reassignment)
|
|
1029
|
+
const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
|
|
1030
|
+
focus: 'merge',
|
|
1031
|
+
});
|
|
1032
|
+
await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
|
|
1033
|
+
// 2. Create task with failing test metadata
|
|
1034
|
+
const task = await createTestTask(ctx, 'Task with failing tests', {
|
|
1035
|
+
priority: 5,
|
|
1036
|
+
tags: ['test', 'needs-fix'],
|
|
1037
|
+
});
|
|
1038
|
+
await ctx.api.updateTaskOrchestratorMeta(task.id, {
|
|
1039
|
+
mergeRequestId: 101,
|
|
1040
|
+
mergeRequestUrl: 'https://github.com/test/repo/pull/101',
|
|
1041
|
+
mergeStatus: 'testing',
|
|
1042
|
+
testRunCount: 1,
|
|
1043
|
+
lastTestResult: {
|
|
1044
|
+
passed: false,
|
|
1045
|
+
totalTests: 10,
|
|
1046
|
+
passedTests: 7,
|
|
1047
|
+
failedTests: 3,
|
|
1048
|
+
durationMs: 5000,
|
|
1049
|
+
completedAt: new Date().toISOString(),
|
|
1050
|
+
errorMessage: 'Test 1 failed, Test 2 failed, Test 3 failed',
|
|
1051
|
+
},
|
|
1052
|
+
});
|
|
1053
|
+
// 3. Start steward session with reject/handoff prompt
|
|
1054
|
+
const prompt = buildTestStewardPrompt('reject', task.id, { dbPath: ctx.dbPath });
|
|
1055
|
+
const { session } = await ctx.sessionManager.startSession(steward.id, {
|
|
1056
|
+
workingDirectory: ctx.tempWorkspace,
|
|
1057
|
+
initialPrompt: prompt,
|
|
1058
|
+
});
|
|
1059
|
+
ctx.log(`Steward session started: ${session.id}`);
|
|
1060
|
+
// 4. Wait for merge status to become 'test_failed'
|
|
1061
|
+
ctx.log('Waiting for steward to reject...');
|
|
1062
|
+
const meta = await waitForTaskMeta(ctx.api, task.id, (m) => m.mergeStatus === 'test_failed', { timeout: 240000 });
|
|
1063
|
+
// 5. Verify task is reopened
|
|
1064
|
+
const reopened = await ctx.api.get(task.id);
|
|
1065
|
+
if (reopened?.status !== TaskStatus.OPEN) {
|
|
1066
|
+
ctx.log(`Task status after handoff: ${reopened?.status} (expected OPEN)`);
|
|
1067
|
+
}
|
|
1068
|
+
return pass('Steward created handoff for failing PR', {
|
|
1069
|
+
taskId: task.id,
|
|
1070
|
+
mergeStatus: meta.mergeStatus,
|
|
1071
|
+
handoffHistory: meta.handoffHistory,
|
|
1072
|
+
taskStatus: reopened?.status,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
// ============================================================================
|
|
1076
|
+
// All Tests Collection
|
|
1077
|
+
// ============================================================================
|
|
1078
|
+
/**
|
|
1079
|
+
* All orchestration tests in order of execution
|
|
1080
|
+
*/
|
|
1081
|
+
export const allTests = [
|
|
1082
|
+
directorCreatesTasksTest,
|
|
1083
|
+
directorCreatesPlansTest,
|
|
1084
|
+
daemonDispatchesWorkerTest,
|
|
1085
|
+
daemonRespectsDependenciesTest,
|
|
1086
|
+
workerUsesWorktreeTest,
|
|
1087
|
+
workerCommitsWorkTest,
|
|
1088
|
+
workerCreatesMergeRequestTest,
|
|
1089
|
+
workerMarksTaskCompleteTest,
|
|
1090
|
+
workerHandoffOnContextFillTest,
|
|
1091
|
+
daemonSpawnsStewardForMRTest,
|
|
1092
|
+
stewardMergesPassingMRTest,
|
|
1093
|
+
stewardHandoffFailingMRTest,
|
|
1094
|
+
];
|
|
1095
|
+
/**
|
|
1096
|
+
* Get tests by tag
|
|
1097
|
+
*/
|
|
1098
|
+
export function getTestsByTag(tag) {
|
|
1099
|
+
return allTests.filter(t => t.tags?.includes(tag));
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Get test by ID
|
|
1103
|
+
*/
|
|
1104
|
+
export function getTestById(id) {
|
|
1105
|
+
return allTests.find(t => t.id === id);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Get tests matching a filter string (matches id or name)
|
|
1109
|
+
*/
|
|
1110
|
+
export function filterTests(filter) {
|
|
1111
|
+
const lowerFilter = filter.toLowerCase();
|
|
1112
|
+
return allTests.filter(t => t.id.toLowerCase().includes(lowerFilter) ||
|
|
1113
|
+
t.name.toLowerCase().includes(lowerFilter));
|
|
1114
|
+
}
|
|
1115
|
+
//# sourceMappingURL=orchestration-tests.js.map
|