macro-agent 0.1.0 → 0.1.1
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/.claude/settings.local.json +3 -1
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +16 -14
- package/README.md +11 -29
- package/dist/acp/macro-agent.d.ts +15 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +131 -35
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +32 -1
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +65 -1
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +464 -183
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +37 -6
- package/dist/api/server.js.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token.d.ts +41 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +73 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli/acp.d.ts +2 -23
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +127 -61
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +147 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +268 -181
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/parse-args.d.ts +20 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli/parse-args.js.map +1 -0
- package/dist/cli/stable-instance-id.d.ts +8 -0
- package/dist/cli/stable-instance-id.d.ts.map +1 -0
- package/dist/cli/stable-instance-id.js +14 -0
- package/dist/cli/stable-instance-id.js.map +1 -0
- package/dist/config/project-config.d.ts +74 -7
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +123 -20
- package/dist/config/project-config.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts +17 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
- package/dist/map/adapter/acp-over-map.js +384 -23
- package/dist/map/adapter/acp-over-map.js.map +1 -1
- package/dist/map/adapter/connection-manager.d.ts.map +1 -1
- package/dist/map/adapter/connection-manager.js +3 -0
- package/dist/map/adapter/connection-manager.js.map +1 -1
- package/dist/map/adapter/event-log.d.ts +87 -0
- package/dist/map/adapter/event-log.d.ts.map +1 -0
- package/dist/map/adapter/event-log.js +122 -0
- package/dist/map/adapter/event-log.js.map +1 -0
- package/dist/map/adapter/event-translator.js +6 -6
- package/dist/map/adapter/event-translator.js.map +1 -1
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
- package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
- package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
- package/dist/map/adapter/extensions/index.d.ts +10 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +34 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
- package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
- package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
- package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
- package/dist/map/adapter/extensions/rename.d.ts +29 -0
- package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
- package/dist/map/adapter/extensions/rename.js +49 -0
- package/dist/map/adapter/extensions/rename.js.map +1 -0
- package/dist/map/adapter/extensions/task.d.ts.map +1 -1
- package/dist/map/adapter/extensions/task.js +10 -0
- package/dist/map/adapter/extensions/task.js.map +1 -1
- package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
- package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
- package/dist/map/adapter/extensions/update-metadata.js +67 -0
- package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
- package/dist/map/adapter/index.d.ts +2 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +8 -2
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/interface.d.ts +2 -0
- package/dist/map/adapter/interface.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.d.ts +3 -0
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +258 -35
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
- package/dist/map/adapter/subscription-manager.js +5 -1
- package/dist/map/adapter/subscription-manager.js.map +1 -1
- package/dist/map/adapter/types.d.ts +2 -0
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/map-client.d.ts +39 -0
- package/dist/mcp/map-client.d.ts.map +1 -0
- package/dist/mcp/map-client.js +129 -0
- package/dist/mcp/map-client.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +14 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +113 -85
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/types.d.ts +9 -1
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/metrics/metrics.js +1 -1
- package/dist/metrics/metrics.js.map +1 -1
- package/dist/roles/capabilities.d.ts +3 -1
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +17 -7
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +6 -6
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +6 -6
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/registry.d.ts +2 -2
- package/dist/roles/registry.js +2 -2
- package/dist/server/combined-server.d.ts +20 -0
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +107 -8
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts +2 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +69 -20
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/types/agents.d.ts +18 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/store/types/events.d.ts +1 -1
- package/dist/store/types/events.d.ts.map +1 -1
- package/dist/task/backend/index.d.ts +47 -29
- package/dist/task/backend/index.d.ts.map +1 -1
- package/dist/task/backend/index.js +109 -71
- package/dist/task/backend/index.js.map +1 -1
- package/dist/task/backend/memory.d.ts +1 -0
- package/dist/task/backend/memory.d.ts.map +1 -1
- package/dist/task/backend/memory.js +3 -0
- package/dist/task/backend/memory.js.map +1 -1
- package/dist/task/backend/opentasks/backend.d.ts +140 -0
- package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
- package/dist/task/backend/opentasks/backend.js +1023 -0
- package/dist/task/backend/opentasks/backend.js.map +1 -0
- package/dist/task/backend/opentasks/client.d.ts +337 -0
- package/dist/task/backend/opentasks/client.d.ts.map +1 -0
- package/dist/task/backend/opentasks/client.js +225 -0
- package/dist/task/backend/opentasks/client.js.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
- package/dist/task/backend/opentasks/daemon-manager.js +195 -0
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
- package/dist/task/backend/opentasks/index.d.ts +21 -0
- package/dist/task/backend/opentasks/index.d.ts.map +1 -0
- package/dist/task/backend/opentasks/index.js +21 -0
- package/dist/task/backend/opentasks/index.js.map +1 -0
- package/dist/task/backend/opentasks/mapping.d.ts +48 -0
- package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
- package/dist/task/backend/opentasks/mapping.js +77 -0
- package/dist/task/backend/opentasks/mapping.js.map +1 -0
- package/dist/task/backend/types.d.ts +33 -53
- package/dist/task/backend/types.d.ts.map +1 -1
- package/dist/task/backend/types.js +7 -11
- package/dist/task/backend/types.js.map +1 -1
- package/dist/task/backend/unified-tool-provider.d.ts +57 -0
- package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
- package/dist/task/backend/unified-tool-provider.js +623 -0
- package/dist/task/backend/unified-tool-provider.js.map +1 -0
- package/dist/teams/team-loader.d.ts +2 -2
- package/dist/teams/team-loader.js +3 -3
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +2 -0
- package/dist/teams/team-runtime.js.map +1 -1
- package/docs/architecture.md +7 -6
- package/docs/configuration.md +26 -62
- package/docs/implementation-details.md +5 -5
- package/docs/implementation-summary.md +17 -17
- package/docs/plan-self-driving-support.md +4 -4
- package/docs/spec-self-driving-support.md +10 -10
- package/docs/team-templates.md +2 -2
- package/docs/teams.md +3 -3
- package/docs/troubleshooting.md +10 -11
- package/package.json +6 -4
- package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
- package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
- package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
- package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
- package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
- package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
- package/src/acp/__tests__/integration.test.ts +56 -31
- package/src/acp/__tests__/macro-agent.test.ts +16 -7
- package/src/acp/macro-agent.ts +170 -36
- package/src/acp/types.ts +46 -1
- package/src/agent/__tests__/agent-manager.test.ts +228 -2
- package/src/agent/agent-manager.ts +714 -261
- package/src/agent/types.ts +3 -1
- package/src/api/server.ts +41 -7
- package/src/auth/__tests__/token.test.ts +100 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/token.ts +82 -0
- package/src/cli/__tests__/acp.test.ts +1 -1
- package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
- package/src/cli/acp.ts +130 -72
- package/src/cli/index.ts +120 -14
- package/src/cli/mcp.ts +311 -207
- package/src/cli/parse-args.ts +54 -0
- package/src/cli/stable-instance-id.ts +14 -0
- package/src/config/project-config.ts +190 -27
- package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
- package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
- package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
- package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
- package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
- package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
- package/src/map/adapter/__tests__/event-log.test.ts +527 -0
- package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
- package/src/map/adapter/__tests__/extensions.test.ts +408 -0
- package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
- package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
- package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
- package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
- package/src/map/adapter/acp-over-map.ts +678 -66
- package/src/map/adapter/connection-manager.ts +3 -0
- package/src/map/adapter/event-log.ts +208 -0
- package/src/map/adapter/event-translator.ts +6 -6
- package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
- package/src/map/adapter/extensions/index.ts +60 -0
- package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
- package/src/map/adapter/extensions/task.ts +11 -0
- package/src/map/adapter/extensions/update-metadata.ts +126 -0
- package/src/map/adapter/index.ts +28 -0
- package/src/map/adapter/interface.ts +2 -0
- package/src/map/adapter/map-adapter.ts +312 -47
- package/src/map/adapter/subscription-manager.ts +5 -1
- package/src/map/adapter/types.ts +2 -0
- package/src/mcp/__tests__/map-client.test.ts +386 -0
- package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
- package/src/mcp/__tests__/mcp-server.test.ts +100 -1
- package/src/mcp/map-client.ts +177 -0
- package/src/mcp/mcp-server.ts +191 -100
- package/src/mcp/types.ts +6 -1
- package/src/metrics/metrics.ts +1 -1
- package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
- package/src/roles/__tests__/config-loader.test.ts +7 -7
- package/src/roles/capabilities.ts +17 -7
- package/src/roles/config-loader.ts +6 -6
- package/src/roles/registry.ts +2 -2
- package/src/server/__tests__/combined-server.test.ts +94 -21
- package/src/server/combined-server.ts +189 -33
- package/src/steering/__tests__/steering-integration.test.ts +1 -1
- package/src/store/__tests__/event-store.test.ts +196 -1
- package/src/store/__tests__/instance.test.ts +3 -3
- package/src/store/event-store.ts +80 -21
- package/src/store/types/agents.ts +15 -0
- package/src/store/types/events.ts +1 -1
- package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
- package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
- package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
- package/src/task/backend/index.ts +156 -106
- package/src/task/backend/memory.ts +4 -0
- package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
- package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
- package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
- package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
- package/src/task/backend/opentasks/backend.ts +1323 -0
- package/src/task/backend/opentasks/client.ts +652 -0
- package/src/task/backend/opentasks/daemon-manager.ts +253 -0
- package/src/task/backend/opentasks/index.ts +69 -0
- package/src/task/backend/opentasks/mapping.ts +94 -0
- package/src/task/backend/types.ts +42 -66
- package/src/task/backend/unified-tool-provider.ts +779 -0
- package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
- package/src/teams/team-loader.ts +3 -3
- package/src/teams/team-runtime.ts +2 -0
- package/test_fixtures/README.md +2 -3
- package/test_fixtures/fixtures/index.ts +0 -3
- package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
- package/test_fixtures/fixtures/repos/index.ts +1 -3
- package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
- package/test_fixtures/fixtures/repos/types.ts +0 -11
- package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
- package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
- package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
- package/vitest.config.ts +1 -1
- package/vitest.e2e.config.ts +1 -1
- package/vitest.setup.ts +1 -30
- package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
- package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
- package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
- package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
- package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
- package/.macro-agent/teams/self-driving/team.yaml +0 -103
- package/.macro-agent/teams/structured/prompts/developer.md +0 -26
- package/.macro-agent/teams/structured/prompts/lead.md +0 -25
- package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
- package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
- package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
- package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
- package/.macro-agent/teams/structured/team.yaml +0 -89
- package/docs/sudocode-integration.md +0 -383
- package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
- package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
- package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
- package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
- package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
- package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
- package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
- package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
- package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
- package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
- package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
- package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
- package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
- package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
- package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
- package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
- package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
- package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
- package/src/task/backend/sudocode/backend.ts +0 -1237
- package/src/task/backend/sudocode/client.ts +0 -515
- package/src/task/backend/sudocode/index.ts +0 -120
- package/src/task/backend/sudocode/mapping.ts +0 -93
- package/src/task/backend/sudocode/server-client.ts +0 -522
- package/src/task/backend/sudocode/standalone-client.ts +0 -623
- package/src/task/backend/sudocode/sync-policy.ts +0 -387
- package/src/task/backend/sudocode/tools.ts +0 -896
- package/src/task/backend/tool-provider.ts +0 -506
- package/test_fixtures/fixtures/sudocode/index.ts +0 -29
- package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
- package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
|
@@ -1,666 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-End Workflow Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for complete task lifecycle with sudocode integration.
|
|
5
|
-
* These tests verify the interaction between all components.
|
|
6
|
-
*
|
|
7
|
-
* @module task/backend/sudocode/__tests__/e2e-workflow.test
|
|
8
|
-
* @see s-8472 Pluggable Task Backend Integration
|
|
9
|
-
* @see s-1zcx Multi-Agent Orchestration Testing Strategy
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
13
|
-
import { createEventStore, type EventStore } from "../../../../store/event-store.js";
|
|
14
|
-
import {
|
|
15
|
-
SudocodeTaskBackend,
|
|
16
|
-
createSudocodeTaskBackend,
|
|
17
|
-
} from "../backend.js";
|
|
18
|
-
import type { SudocodeClient, Issue, IssueChangeCallback } from "../client.js";
|
|
19
|
-
import type { TaskChangeEvent, SyncEvent } from "../sync-policy.js";
|
|
20
|
-
|
|
21
|
-
// Create a realistic mock client
|
|
22
|
-
function createRealisticMockClient(): SudocodeClient & {
|
|
23
|
-
_issues: Map<string, Issue>;
|
|
24
|
-
_blockers: Map<string, Issue[]>;
|
|
25
|
-
_blocking: Map<string, Issue[]>;
|
|
26
|
-
_callbacks: IssueChangeCallback[];
|
|
27
|
-
_triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
|
|
28
|
-
_createIssue: (issue: Issue) => void;
|
|
29
|
-
} {
|
|
30
|
-
const issues = new Map<string, Issue>();
|
|
31
|
-
const blockers = new Map<string, Issue[]>();
|
|
32
|
-
const blocking = new Map<string, Issue[]>();
|
|
33
|
-
const callbacks: IssueChangeCallback[] = [];
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
_issues: issues,
|
|
37
|
-
_blockers: blockers,
|
|
38
|
-
_blocking: blocking,
|
|
39
|
-
_callbacks: callbacks,
|
|
40
|
-
_triggerIssueChange: (event) => {
|
|
41
|
-
for (const cb of callbacks) {
|
|
42
|
-
cb(event);
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
_createIssue: (issue) => {
|
|
46
|
-
issues.set(issue.id, issue);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
getIssue: vi.fn(async (id: string) => issues.get(id) ?? null),
|
|
50
|
-
listIssues: vi.fn(async () => Array.from(issues.values())),
|
|
51
|
-
createIssue: vi.fn(async (data: Partial<Issue>) => {
|
|
52
|
-
const id = data.id ?? `i-${Date.now()}`;
|
|
53
|
-
const issue: Issue = {
|
|
54
|
-
id,
|
|
55
|
-
uuid: `uuid-${id}`,
|
|
56
|
-
title: data.title ?? "New Issue",
|
|
57
|
-
status: data.status ?? "open",
|
|
58
|
-
priority: data.priority ?? 2,
|
|
59
|
-
created_at: new Date().toISOString(),
|
|
60
|
-
updated_at: new Date().toISOString(),
|
|
61
|
-
...data,
|
|
62
|
-
} as Issue;
|
|
63
|
-
issues.set(id, issue);
|
|
64
|
-
return issue;
|
|
65
|
-
}),
|
|
66
|
-
updateIssue: vi.fn(async (id: string, updates: Partial<Issue>) => {
|
|
67
|
-
const issue = issues.get(id);
|
|
68
|
-
if (!issue) throw new Error(`Issue not found: ${id}`);
|
|
69
|
-
const previousStatus = issue.status;
|
|
70
|
-
Object.assign(issue, updates);
|
|
71
|
-
issue.updated_at = new Date().toISOString();
|
|
72
|
-
|
|
73
|
-
// Trigger status_changed if status changed
|
|
74
|
-
if (updates.status && updates.status !== previousStatus) {
|
|
75
|
-
for (const cb of callbacks) {
|
|
76
|
-
cb({
|
|
77
|
-
type: "status_changed",
|
|
78
|
-
issueId: id,
|
|
79
|
-
issue,
|
|
80
|
-
previousIssue: { ...issue, status: previousStatus },
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return issue;
|
|
86
|
-
}),
|
|
87
|
-
getReadyIssues: vi.fn(async () => {
|
|
88
|
-
return Array.from(issues.values()).filter((i) => {
|
|
89
|
-
const issueBlockers = blockers.get(i.id) ?? [];
|
|
90
|
-
return (
|
|
91
|
-
issueBlockers.every((b) => b.status === "closed") &&
|
|
92
|
-
i.status !== "closed"
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
}),
|
|
96
|
-
getBlockers: vi.fn(async (id: string) => blockers.get(id) ?? []),
|
|
97
|
-
getBlocking: vi.fn(async (id: string) => blocking.get(id) ?? []),
|
|
98
|
-
createLink: vi.fn(async (from: string, to: string, type: string) => {
|
|
99
|
-
if (type === "blocks") {
|
|
100
|
-
const fromIssue = issues.get(from);
|
|
101
|
-
if (!fromIssue) return;
|
|
102
|
-
const existing = blockers.get(to) ?? [];
|
|
103
|
-
if (!existing.some((b) => b.id === from)) {
|
|
104
|
-
blockers.set(to, [...existing, fromIssue]);
|
|
105
|
-
}
|
|
106
|
-
const blockingList = blocking.get(from) ?? [];
|
|
107
|
-
const toIssue = issues.get(to);
|
|
108
|
-
if (toIssue && !blockingList.some((b) => b.id === to)) {
|
|
109
|
-
blocking.set(from, [...blockingList, toIssue]);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}),
|
|
113
|
-
removeLink: vi.fn(async (from: string, to: string, type: string) => {
|
|
114
|
-
if (type === "blocks") {
|
|
115
|
-
const existing = blockers.get(to) ?? [];
|
|
116
|
-
blockers.set(to, existing.filter((b) => b.id !== from));
|
|
117
|
-
const blockingList = blocking.get(from) ?? [];
|
|
118
|
-
blocking.set(from, blockingList.filter((b) => b.id !== to));
|
|
119
|
-
}
|
|
120
|
-
}),
|
|
121
|
-
getSpec: vi.fn(async () => null),
|
|
122
|
-
listSpecs: vi.fn(async () => []),
|
|
123
|
-
addFeedback: vi.fn(async () => {}),
|
|
124
|
-
onIssueChange: vi.fn((callback: IssueChangeCallback) => {
|
|
125
|
-
callbacks.push(callback);
|
|
126
|
-
return () => {
|
|
127
|
-
const idx = callbacks.indexOf(callback);
|
|
128
|
-
if (idx >= 0) callbacks.splice(idx, 1);
|
|
129
|
-
};
|
|
130
|
-
}),
|
|
131
|
-
close: vi.fn(),
|
|
132
|
-
isReady: vi.fn(() => true),
|
|
133
|
-
} as unknown as SudocodeClient & {
|
|
134
|
-
_issues: Map<string, Issue>;
|
|
135
|
-
_blockers: Map<string, Issue[]>;
|
|
136
|
-
_blocking: Map<string, Issue[]>;
|
|
137
|
-
_callbacks: IssueChangeCallback[];
|
|
138
|
-
_triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
|
|
139
|
-
_createIssue: (issue: Issue) => void;
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
describe("E2E Workflow", () => {
|
|
144
|
-
let eventStore: EventStore;
|
|
145
|
-
let backend: SudocodeTaskBackend;
|
|
146
|
-
let mockClient: ReturnType<typeof createRealisticMockClient>;
|
|
147
|
-
const testAgentId = "agent_test";
|
|
148
|
-
|
|
149
|
-
beforeEach(async () => {
|
|
150
|
-
eventStore = await createEventStore({ inMemory: true });
|
|
151
|
-
mockClient = createRealisticMockClient();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
afterEach(async () => {
|
|
155
|
-
backend?.close();
|
|
156
|
-
await eventStore.close();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe("Task creation and issue binding", () => {
|
|
160
|
-
beforeEach(() => {
|
|
161
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
162
|
-
syncStatus: true,
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("should create a task bound to an existing issue", async () => {
|
|
167
|
-
// Create an issue first
|
|
168
|
-
mockClient._createIssue({
|
|
169
|
-
id: "i-feature1",
|
|
170
|
-
uuid: "uuid-1",
|
|
171
|
-
title: "Implement feature 1",
|
|
172
|
-
status: "open",
|
|
173
|
-
priority: 1,
|
|
174
|
-
created_at: new Date().toISOString(),
|
|
175
|
-
updated_at: new Date().toISOString(),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Create task bound to the issue
|
|
179
|
-
const task = await backend.create({
|
|
180
|
-
description: "Implement feature 1",
|
|
181
|
-
created_by: testAgentId,
|
|
182
|
-
external_id: "i-feature1",
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
expect(task.id).toBeDefined();
|
|
186
|
-
expect(task.external_id).toBe("i-feature1");
|
|
187
|
-
expect(backend.getIssueForTask(task.id)).toBe("i-feature1");
|
|
188
|
-
expect(backend.getTasksByIssue("i-feature1")).toContain(task.id);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("should fail to create task bound to non-existent issue", async () => {
|
|
192
|
-
await expect(
|
|
193
|
-
backend.create({
|
|
194
|
-
description: "Test",
|
|
195
|
-
created_by: testAgentId,
|
|
196
|
-
external_id: "i-nonexistent",
|
|
197
|
-
})
|
|
198
|
-
).rejects.toThrow("Issue not found");
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("Complete workflow: create -> assign -> start -> complete", () => {
|
|
203
|
-
beforeEach(() => {
|
|
204
|
-
mockClient._createIssue({
|
|
205
|
-
id: "i-workflow",
|
|
206
|
-
uuid: "uuid-workflow",
|
|
207
|
-
title: "Workflow test issue",
|
|
208
|
-
status: "open",
|
|
209
|
-
priority: 2,
|
|
210
|
-
created_at: new Date().toISOString(),
|
|
211
|
-
updated_at: new Date().toISOString(),
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
215
|
-
syncStatus: true,
|
|
216
|
-
autoCloseIssues: true,
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("should complete full task lifecycle", async () => {
|
|
221
|
-
// Create task
|
|
222
|
-
const task = await backend.create({
|
|
223
|
-
description: "Complete workflow test",
|
|
224
|
-
created_by: testAgentId,
|
|
225
|
-
external_id: "i-workflow",
|
|
226
|
-
});
|
|
227
|
-
expect(task.status).toBe("pending");
|
|
228
|
-
|
|
229
|
-
// Assign task
|
|
230
|
-
await backend.assign(task.id, "worker-1");
|
|
231
|
-
const afterAssign = await backend.get(task.id);
|
|
232
|
-
expect(afterAssign?.status).toBe("assigned");
|
|
233
|
-
expect(afterAssign?.assigned_agent).toBe("worker-1");
|
|
234
|
-
|
|
235
|
-
// Start task
|
|
236
|
-
await backend.start(task.id);
|
|
237
|
-
const afterStart = await backend.get(task.id);
|
|
238
|
-
expect(afterStart?.status).toBe("in_progress");
|
|
239
|
-
|
|
240
|
-
// Complete task
|
|
241
|
-
await backend.complete(task.id, {
|
|
242
|
-
summary: "Work completed successfully",
|
|
243
|
-
data: { result: "success" },
|
|
244
|
-
});
|
|
245
|
-
const afterComplete = await backend.get(task.id);
|
|
246
|
-
expect(afterComplete?.status).toBe("completed");
|
|
247
|
-
|
|
248
|
-
// Issue should have been closed (autoCloseIssues: true)
|
|
249
|
-
expect(mockClient.updateIssue).toHaveBeenCalledWith("i-workflow", {
|
|
250
|
-
status: "closed",
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("should track status changes correctly", async () => {
|
|
255
|
-
const statusHistory: string[] = [];
|
|
256
|
-
backend.onTaskChange((event) => {
|
|
257
|
-
if (event.task.status) {
|
|
258
|
-
statusHistory.push(event.task.status);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
const task = await backend.create({
|
|
263
|
-
description: "Track status",
|
|
264
|
-
created_by: testAgentId,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
await backend.assign(task.id, "worker-1");
|
|
268
|
-
await backend.start(task.id);
|
|
269
|
-
await backend.complete(task.id);
|
|
270
|
-
|
|
271
|
-
// Should have recorded: pending -> assigned -> in_progress -> completed
|
|
272
|
-
expect(statusHistory).toContain("pending");
|
|
273
|
-
expect(statusHistory).toContain("assigned");
|
|
274
|
-
expect(statusHistory).toContain("in_progress");
|
|
275
|
-
expect(statusHistory).toContain("completed");
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe("Blocker workflow", () => {
|
|
280
|
-
beforeEach(() => {
|
|
281
|
-
mockClient._createIssue({
|
|
282
|
-
id: "i-blocker",
|
|
283
|
-
uuid: "uuid-blocker",
|
|
284
|
-
title: "Blocker issue",
|
|
285
|
-
status: "open",
|
|
286
|
-
priority: 1,
|
|
287
|
-
created_at: new Date().toISOString(),
|
|
288
|
-
updated_at: new Date().toISOString(),
|
|
289
|
-
});
|
|
290
|
-
mockClient._createIssue({
|
|
291
|
-
id: "i-blocked",
|
|
292
|
-
uuid: "uuid-blocked",
|
|
293
|
-
title: "Blocked issue",
|
|
294
|
-
status: "open",
|
|
295
|
-
priority: 2,
|
|
296
|
-
created_at: new Date().toISOString(),
|
|
297
|
-
updated_at: new Date().toISOString(),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
301
|
-
syncStatus: true,
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it("should handle task blocked by another task", async () => {
|
|
306
|
-
const blocker = await backend.create({
|
|
307
|
-
description: "Blocker task",
|
|
308
|
-
created_by: testAgentId,
|
|
309
|
-
external_id: "i-blocker",
|
|
310
|
-
});
|
|
311
|
-
const blocked = await backend.create({
|
|
312
|
-
description: "Blocked task",
|
|
313
|
-
created_by: testAgentId,
|
|
314
|
-
external_id: "i-blocked",
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// Add blocker
|
|
318
|
-
await backend.addBlocker(blocked.id, blocker.id);
|
|
319
|
-
|
|
320
|
-
// Blocked task should be blocked
|
|
321
|
-
const blockedTask = await backend.get(blocked.id);
|
|
322
|
-
expect(blockedTask?.isBlocked).toBe(true);
|
|
323
|
-
|
|
324
|
-
// Should not appear in ready list
|
|
325
|
-
const ready = await backend.listReady();
|
|
326
|
-
expect(ready.find((t) => t.id === blocked.id)).toBeUndefined();
|
|
327
|
-
expect(ready.find((t) => t.id === blocker.id)).toBeDefined();
|
|
328
|
-
|
|
329
|
-
// Complete the blocker
|
|
330
|
-
await backend.start(blocker.id);
|
|
331
|
-
await backend.complete(blocker.id);
|
|
332
|
-
|
|
333
|
-
// Blocked task should now be unblocked
|
|
334
|
-
const afterComplete = await backend.get(blocked.id);
|
|
335
|
-
expect(afterComplete?.isBlocked).toBe(false);
|
|
336
|
-
|
|
337
|
-
// Should now appear in ready list
|
|
338
|
-
const readyAfter = await backend.listReady();
|
|
339
|
-
expect(readyAfter.find((t) => t.id === blocked.id)).toBeDefined();
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("should sync blocker relationships to sudocode", async () => {
|
|
343
|
-
const blocker = await backend.create({
|
|
344
|
-
description: "Blocker",
|
|
345
|
-
created_by: testAgentId,
|
|
346
|
-
external_id: "i-blocker",
|
|
347
|
-
});
|
|
348
|
-
const blocked = await backend.create({
|
|
349
|
-
description: "Blocked",
|
|
350
|
-
created_by: testAgentId,
|
|
351
|
-
external_id: "i-blocked",
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
await backend.addBlocker(blocked.id, blocker.id);
|
|
355
|
-
|
|
356
|
-
// Should have created link in sudocode
|
|
357
|
-
expect(mockClient.createLink).toHaveBeenCalledWith(
|
|
358
|
-
"i-blocker",
|
|
359
|
-
"i-blocked",
|
|
360
|
-
"blocks"
|
|
361
|
-
);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe("Subtask workflow", () => {
|
|
366
|
-
beforeEach(() => {
|
|
367
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
368
|
-
syncStatus: false,
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it("should create and track subtasks", async () => {
|
|
373
|
-
const parent = await backend.create({
|
|
374
|
-
description: "Parent task",
|
|
375
|
-
created_by: testAgentId,
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const child1 = await backend.createSubtask(parent.id, {
|
|
379
|
-
description: "Child 1",
|
|
380
|
-
created_by: testAgentId,
|
|
381
|
-
});
|
|
382
|
-
const child2 = await backend.createSubtask(parent.id, {
|
|
383
|
-
description: "Child 2",
|
|
384
|
-
created_by: testAgentId,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
expect(child1.parent_task).toBe(parent.id);
|
|
388
|
-
expect(child2.parent_task).toBe(parent.id);
|
|
389
|
-
|
|
390
|
-
// Get children
|
|
391
|
-
const children = await backend.getChildren(parent.id);
|
|
392
|
-
expect(children).toHaveLength(2);
|
|
393
|
-
|
|
394
|
-
// Get subtask status
|
|
395
|
-
const status = await backend.getSubtaskStatus(parent.id);
|
|
396
|
-
expect(status.total).toBe(2);
|
|
397
|
-
expect(status.pending).toBe(2);
|
|
398
|
-
expect(status.completed).toBe(0);
|
|
399
|
-
expect(status.allCompleted).toBe(false);
|
|
400
|
-
|
|
401
|
-
// Complete one child
|
|
402
|
-
await backend.start(child1.id);
|
|
403
|
-
await backend.complete(child1.id);
|
|
404
|
-
|
|
405
|
-
const afterOne = await backend.getSubtaskStatus(parent.id);
|
|
406
|
-
expect(afterOne.completed).toBe(1);
|
|
407
|
-
expect(afterOne.pending).toBe(1);
|
|
408
|
-
expect(afterOne.allCompleted).toBe(false);
|
|
409
|
-
|
|
410
|
-
// Complete second child
|
|
411
|
-
await backend.start(child2.id);
|
|
412
|
-
await backend.complete(child2.id);
|
|
413
|
-
|
|
414
|
-
const afterAll = await backend.getSubtaskStatus(parent.id);
|
|
415
|
-
expect(afterAll.completed).toBe(2);
|
|
416
|
-
expect(afterAll.allCompleted).toBe(true);
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
describe("Issue change propagation", () => {
|
|
421
|
-
beforeEach(() => {
|
|
422
|
-
mockClient._createIssue({
|
|
423
|
-
id: "i-sync",
|
|
424
|
-
uuid: "uuid-sync",
|
|
425
|
-
title: "Sync test issue",
|
|
426
|
-
status: "open",
|
|
427
|
-
priority: 2,
|
|
428
|
-
created_at: new Date().toISOString(),
|
|
429
|
-
updated_at: new Date().toISOString(),
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
433
|
-
syncStatus: true,
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it("should update task when issue status changes", async () => {
|
|
438
|
-
const task = await backend.create({
|
|
439
|
-
description: "Sync test",
|
|
440
|
-
created_by: testAgentId,
|
|
441
|
-
external_id: "i-sync",
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// Initially pending
|
|
445
|
-
expect((await backend.get(task.id))?.status).toBe("pending");
|
|
446
|
-
|
|
447
|
-
// Simulate issue status change to in_progress
|
|
448
|
-
mockClient._issues.get("i-sync")!.status = "in_progress";
|
|
449
|
-
mockClient._triggerIssueChange({
|
|
450
|
-
type: "status_changed",
|
|
451
|
-
issueId: "i-sync",
|
|
452
|
-
issue: mockClient._issues.get("i-sync")!,
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Wait for async handler
|
|
456
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
457
|
-
|
|
458
|
-
// Task should be in_progress
|
|
459
|
-
expect((await backend.get(task.id))?.status).toBe("in_progress");
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it("should not overwrite completed task status", async () => {
|
|
463
|
-
const task = await backend.create({
|
|
464
|
-
description: "Completed task",
|
|
465
|
-
created_by: testAgentId,
|
|
466
|
-
external_id: "i-sync",
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
await backend.start(task.id);
|
|
470
|
-
await backend.complete(task.id);
|
|
471
|
-
expect((await backend.get(task.id))?.status).toBe("completed");
|
|
472
|
-
|
|
473
|
-
// Simulate issue reopened
|
|
474
|
-
mockClient._issues.get("i-sync")!.status = "open";
|
|
475
|
-
mockClient._triggerIssueChange({
|
|
476
|
-
type: "status_changed",
|
|
477
|
-
issueId: "i-sync",
|
|
478
|
-
issue: mockClient._issues.get("i-sync")!,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
482
|
-
|
|
483
|
-
// Task should remain completed
|
|
484
|
-
expect((await backend.get(task.id))?.status).toBe("completed");
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
describe("Error handling in workflows", () => {
|
|
489
|
-
beforeEach(() => {
|
|
490
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
491
|
-
syncStatus: false,
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it("should handle task failure correctly", async () => {
|
|
496
|
-
const task = await backend.create({
|
|
497
|
-
description: "Will fail",
|
|
498
|
-
created_by: testAgentId,
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
await backend.start(task.id);
|
|
502
|
-
await backend.fail(task.id, {
|
|
503
|
-
code: "TEST_ERROR",
|
|
504
|
-
message: "Test failure",
|
|
505
|
-
details: { reason: "testing" },
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
const failed = await backend.get(task.id);
|
|
509
|
-
expect(failed?.status).toBe("failed");
|
|
510
|
-
expect((failed?.outputs as any)?.error?.code).toBe("TEST_ERROR");
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
it("should reject invalid status transitions", async () => {
|
|
514
|
-
const task = await backend.create({
|
|
515
|
-
description: "Test",
|
|
516
|
-
created_by: testAgentId,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// Can't complete pending task directly
|
|
520
|
-
await expect(backend.complete(task.id)).rejects.toThrow(
|
|
521
|
-
"Invalid status transition"
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
// Start first
|
|
525
|
-
await backend.start(task.id);
|
|
526
|
-
await backend.complete(task.id);
|
|
527
|
-
|
|
528
|
-
// Can't start completed task
|
|
529
|
-
await expect(backend.start(task.id)).rejects.toThrow(
|
|
530
|
-
"Invalid status transition"
|
|
531
|
-
);
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
describe("Multiple tasks per issue", () => {
|
|
536
|
-
beforeEach(() => {
|
|
537
|
-
mockClient._createIssue({
|
|
538
|
-
id: "i-multi",
|
|
539
|
-
uuid: "uuid-multi",
|
|
540
|
-
title: "Multi-task issue",
|
|
541
|
-
status: "open",
|
|
542
|
-
priority: 2,
|
|
543
|
-
created_at: new Date().toISOString(),
|
|
544
|
-
updated_at: new Date().toISOString(),
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
548
|
-
syncStatus: true,
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
it("should handle multiple tasks bound to same issue", async () => {
|
|
553
|
-
const task1 = await backend.create({
|
|
554
|
-
description: "Task 1",
|
|
555
|
-
created_by: testAgentId,
|
|
556
|
-
external_id: "i-multi",
|
|
557
|
-
});
|
|
558
|
-
const task2 = await backend.create({
|
|
559
|
-
description: "Task 2",
|
|
560
|
-
created_by: testAgentId,
|
|
561
|
-
external_id: "i-multi",
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const tasksByIssue = backend.getTasksByIssue("i-multi");
|
|
565
|
-
expect(tasksByIssue).toHaveLength(2);
|
|
566
|
-
expect(tasksByIssue).toContain(task1.id);
|
|
567
|
-
expect(tasksByIssue).toContain(task2.id);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it("should update all tasks when issue status changes", async () => {
|
|
571
|
-
const task1 = await backend.create({
|
|
572
|
-
description: "Task 1",
|
|
573
|
-
created_by: testAgentId,
|
|
574
|
-
external_id: "i-multi",
|
|
575
|
-
});
|
|
576
|
-
const task2 = await backend.create({
|
|
577
|
-
description: "Task 2",
|
|
578
|
-
created_by: testAgentId,
|
|
579
|
-
external_id: "i-multi",
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
// Simulate issue status change
|
|
583
|
-
mockClient._issues.get("i-multi")!.status = "in_progress";
|
|
584
|
-
mockClient._triggerIssueChange({
|
|
585
|
-
type: "status_changed",
|
|
586
|
-
issueId: "i-multi",
|
|
587
|
-
issue: mockClient._issues.get("i-multi")!,
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
591
|
-
|
|
592
|
-
expect((await backend.get(task1.id))?.status).toBe("in_progress");
|
|
593
|
-
expect((await backend.get(task2.id))?.status).toBe("in_progress");
|
|
594
|
-
});
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
describe("listReady with mixed tasks", () => {
|
|
598
|
-
beforeEach(() => {
|
|
599
|
-
mockClient._createIssue({
|
|
600
|
-
id: "i-ready",
|
|
601
|
-
uuid: "uuid-ready",
|
|
602
|
-
title: "Ready issue",
|
|
603
|
-
status: "open",
|
|
604
|
-
priority: 2,
|
|
605
|
-
created_at: new Date().toISOString(),
|
|
606
|
-
updated_at: new Date().toISOString(),
|
|
607
|
-
});
|
|
608
|
-
mockClient._createIssue({
|
|
609
|
-
id: "i-blocked-ext",
|
|
610
|
-
uuid: "uuid-blocked-ext",
|
|
611
|
-
title: "Externally blocked issue",
|
|
612
|
-
status: "blocked",
|
|
613
|
-
priority: 2,
|
|
614
|
-
created_at: new Date().toISOString(),
|
|
615
|
-
updated_at: new Date().toISOString(),
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
backend = createSudocodeTaskBackend(eventStore, mockClient, {
|
|
619
|
-
syncStatus: false,
|
|
620
|
-
});
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
it("should correctly filter ready tasks", async () => {
|
|
624
|
-
// Unbound task (always ready if not blocked locally)
|
|
625
|
-
const unboundReady = await backend.create({
|
|
626
|
-
description: "Unbound ready",
|
|
627
|
-
created_by: testAgentId,
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
// Bound to ready issue
|
|
631
|
-
const boundReady = await backend.create({
|
|
632
|
-
description: "Bound to ready issue",
|
|
633
|
-
created_by: testAgentId,
|
|
634
|
-
external_id: "i-ready",
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
// Bound to blocked issue
|
|
638
|
-
const boundBlocked = await backend.create({
|
|
639
|
-
description: "Bound to blocked issue",
|
|
640
|
-
created_by: testAgentId,
|
|
641
|
-
external_id: "i-blocked-ext",
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
// Locally blocked (by another task)
|
|
645
|
-
const locallyBlocked = await backend.create({
|
|
646
|
-
description: "Locally blocked",
|
|
647
|
-
created_by: testAgentId,
|
|
648
|
-
});
|
|
649
|
-
const localBlocker = await backend.create({
|
|
650
|
-
description: "Local blocker",
|
|
651
|
-
created_by: testAgentId,
|
|
652
|
-
});
|
|
653
|
-
await backend.addBlocker(locallyBlocked.id, localBlocker.id);
|
|
654
|
-
|
|
655
|
-
const ready = await backend.listReady();
|
|
656
|
-
|
|
657
|
-
// Should include: unboundReady, boundReady, localBlocker
|
|
658
|
-
// Should exclude: boundBlocked (blocked issue), locallyBlocked (blocked by task)
|
|
659
|
-
expect(ready.find((t) => t.id === unboundReady.id)).toBeDefined();
|
|
660
|
-
expect(ready.find((t) => t.id === boundReady.id)).toBeDefined();
|
|
661
|
-
expect(ready.find((t) => t.id === localBlocker.id)).toBeDefined();
|
|
662
|
-
expect(ready.find((t) => t.id === boundBlocked.id)).toBeUndefined();
|
|
663
|
-
expect(ready.find((t) => t.id === locallyBlocked.id)).toBeUndefined();
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
});
|