@coralai/sps-cli 0.48.1 → 0.49.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/dist/console-assets/assets/{index-BR3iEolY.js → index-Co0H5e5_.js} +85 -85
- package/dist/console-assets/assets/{index-lsG7ZrzD.css → index-DNKOkgDj.css} +1 -1
- package/dist/console-assets/index.html +2 -2
- package/dist/console-server/routes/system.d.ts.map +1 -1
- package/dist/console-server/routes/system.js +30 -0
- package/dist/console-server/routes/system.js.map +1 -1
- package/package.json +4 -1
- package/dist/commands/cardMarkComplete.test.d.ts +0 -2
- package/dist/commands/cardMarkComplete.test.d.ts.map +0 -1
- package/dist/commands/cardMarkComplete.test.js +0 -142
- package/dist/commands/cardMarkComplete.test.js.map +0 -1
- package/dist/commands/cardMarkStarted.test.d.ts +0 -2
- package/dist/commands/cardMarkStarted.test.d.ts.map +0 -1
- package/dist/commands/cardMarkStarted.test.js +0 -98
- package/dist/commands/cardMarkStarted.test.js.map +0 -1
- package/dist/commands/projectInit.test.d.ts +0 -2
- package/dist/commands/projectInit.test.d.ts.map +0 -1
- package/dist/commands/projectInit.test.js +0 -126
- package/dist/commands/projectInit.test.js.map +0 -1
- package/dist/commands/tick-heartbeat.test.d.ts +0 -2
- package/dist/commands/tick-heartbeat.test.d.ts.map +0 -1
- package/dist/commands/tick-heartbeat.test.js +0 -89
- package/dist/commands/tick-heartbeat.test.js.map +0 -1
- package/dist/core/checklist.test.d.ts +0 -2
- package/dist/core/checklist.test.d.ts.map +0 -1
- package/dist/core/checklist.test.js +0 -74
- package/dist/core/checklist.test.js.map +0 -1
- package/dist/core/config.test.d.ts +0 -2
- package/dist/core/config.test.d.ts.map +0 -1
- package/dist/core/config.test.js +0 -352
- package/dist/core/config.test.js.map +0 -1
- package/dist/core/lock.test.d.ts +0 -2
- package/dist/core/lock.test.d.ts.map +0 -1
- package/dist/core/lock.test.js +0 -118
- package/dist/core/lock.test.js.map +0 -1
- package/dist/core/markerFile.test.d.ts +0 -2
- package/dist/core/markerFile.test.d.ts.map +0 -1
- package/dist/core/markerFile.test.js +0 -111
- package/dist/core/markerFile.test.js.map +0 -1
- package/dist/core/queue.test.d.ts +0 -2
- package/dist/core/queue.test.d.ts.map +0 -1
- package/dist/core/queue.test.js +0 -114
- package/dist/core/queue.test.js.map +0 -1
- package/dist/core/sessionCleanup.test.d.ts +0 -2
- package/dist/core/sessionCleanup.test.d.ts.map +0 -1
- package/dist/core/sessionCleanup.test.js +0 -158
- package/dist/core/sessionCleanup.test.js.map +0 -1
- package/dist/core/shellEnv.test.d.ts +0 -2
- package/dist/core/shellEnv.test.d.ts.map +0 -1
- package/dist/core/shellEnv.test.js +0 -116
- package/dist/core/shellEnv.test.js.map +0 -1
- package/dist/core/skillStore.test.d.ts +0 -2
- package/dist/core/skillStore.test.d.ts.map +0 -1
- package/dist/core/skillStore.test.js +0 -203
- package/dist/core/skillStore.test.js.map +0 -1
- package/dist/core/state.test.d.ts +0 -2
- package/dist/core/state.test.d.ts.map +0 -1
- package/dist/core/state.test.js +0 -336
- package/dist/core/state.test.js.map +0 -1
- package/dist/engines/CloseoutEngine.d.ts +0 -72
- package/dist/engines/CloseoutEngine.d.ts.map +0 -1
- package/dist/engines/CloseoutEngine.js +0 -648
- package/dist/engines/CloseoutEngine.js.map +0 -1
- package/dist/engines/EventHandler.test.d.ts +0 -2
- package/dist/engines/EventHandler.test.d.ts.map +0 -1
- package/dist/engines/EventHandler.test.js +0 -169
- package/dist/engines/EventHandler.test.js.map +0 -1
- package/dist/engines/ExecutionEngine.d.ts +0 -125
- package/dist/engines/ExecutionEngine.d.ts.map +0 -1
- package/dist/engines/ExecutionEngine.js +0 -766
- package/dist/engines/ExecutionEngine.js.map +0 -1
- package/dist/engines/MonitorEngine.test.d.ts +0 -2
- package/dist/engines/MonitorEngine.test.d.ts.map +0 -1
- package/dist/engines/MonitorEngine.test.js +0 -355
- package/dist/engines/MonitorEngine.test.js.map +0 -1
- package/dist/engines/engine-pipeline-adapter.test.d.ts +0 -17
- package/dist/engines/engine-pipeline-adapter.test.d.ts.map +0 -1
- package/dist/engines/engine-pipeline-adapter.test.js +0 -707
- package/dist/engines/engine-pipeline-adapter.test.js.map +0 -1
- package/dist/interfaces/HookProvider.d.ts +0 -9
- package/dist/interfaces/HookProvider.d.ts.map +0 -1
- package/dist/interfaces/HookProvider.js +0 -2
- package/dist/interfaces/HookProvider.js.map +0 -1
- package/dist/manager/completion-judge.test.d.ts +0 -17
- package/dist/manager/completion-judge.test.d.ts.map +0 -1
- package/dist/manager/completion-judge.test.js +0 -233
- package/dist/manager/completion-judge.test.js.map +0 -1
- package/dist/manager/integration-queue.d.ts +0 -71
- package/dist/manager/integration-queue.d.ts.map +0 -1
- package/dist/manager/integration-queue.js +0 -137
- package/dist/manager/integration-queue.js.map +0 -1
- package/dist/manager/integration-queue.test.d.ts +0 -17
- package/dist/manager/integration-queue.test.d.ts.map +0 -1
- package/dist/manager/integration-queue.test.js +0 -210
- package/dist/manager/integration-queue.test.js.map +0 -1
- package/dist/manager/pm-client.d.ts +0 -10
- package/dist/manager/pm-client.d.ts.map +0 -1
- package/dist/manager/pm-client.js +0 -260
- package/dist/manager/pm-client.js.map +0 -1
- package/dist/manager/resource-limiter.d.ts +0 -56
- package/dist/manager/resource-limiter.d.ts.map +0 -1
- package/dist/manager/resource-limiter.js +0 -116
- package/dist/manager/resource-limiter.js.map +0 -1
- package/dist/manager/resource-limiter.test.d.ts +0 -17
- package/dist/manager/resource-limiter.test.d.ts.map +0 -1
- package/dist/manager/resource-limiter.test.js +0 -118
- package/dist/manager/resource-limiter.test.js.map +0 -1
- package/dist/manager/supervisor.test.d.ts +0 -17
- package/dist/manager/supervisor.test.d.ts.map +0 -1
- package/dist/manager/supervisor.test.js +0 -216
- package/dist/manager/supervisor.test.js.map +0 -1
- package/dist/manager/worker-manager-impl.test.d.ts +0 -17
- package/dist/manager/worker-manager-impl.test.d.ts.map +0 -1
- package/dist/manager/worker-manager-impl.test.js +0 -446
- package/dist/manager/worker-manager-impl.test.js.map +0 -1
- package/dist/providers/PlaneTaskBackend.d.ts +0 -83
- package/dist/providers/PlaneTaskBackend.d.ts.map +0 -1
- package/dist/providers/PlaneTaskBackend.js +0 -461
- package/dist/providers/PlaneTaskBackend.js.map +0 -1
- package/dist/providers/TrelloTaskBackend.d.ts +0 -64
- package/dist/providers/TrelloTaskBackend.d.ts.map +0 -1
- package/dist/providers/TrelloTaskBackend.js +0 -298
- package/dist/providers/TrelloTaskBackend.js.map +0 -1
- package/dist/providers/adapters/acp-fs-handlers.test.d.ts +0 -2
- package/dist/providers/adapters/acp-fs-handlers.test.d.ts.map +0 -1
- package/dist/providers/adapters/acp-fs-handlers.test.js +0 -80
- package/dist/providers/adapters/acp-fs-handlers.test.js.map +0 -1
- package/dist/providers/adapters/acp-permissions.test.d.ts +0 -2
- package/dist/providers/adapters/acp-permissions.test.d.ts.map +0 -1
- package/dist/providers/adapters/acp-permissions.test.js +0 -103
- package/dist/providers/adapters/acp-permissions.test.js.map +0 -1
- package/dist/providers/adapters/acp-session-accumulator.test.d.ts +0 -2
- package/dist/providers/adapters/acp-session-accumulator.test.d.ts.map +0 -1
- package/dist/providers/adapters/acp-session-accumulator.test.js +0 -88
- package/dist/providers/adapters/acp-session-accumulator.test.js.map +0 -1
- package/dist/providers/adapters/acp-terminal-manager.test.d.ts +0 -2
- package/dist/providers/adapters/acp-terminal-manager.test.d.ts.map +0 -1
- package/dist/providers/adapters/acp-terminal-manager.test.js +0 -86
- package/dist/providers/adapters/acp-terminal-manager.test.js.map +0 -1
- package/dist/providers/outputParser.test.d.ts +0 -2
- package/dist/providers/outputParser.test.d.ts.map +0 -1
- package/dist/providers/outputParser.test.js +0 -185
- package/dist/providers/outputParser.test.js.map +0 -1
- package/dist/test-setup.d.ts +0 -2
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -27
- package/dist/test-setup.js.map +0 -1
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module engine-pipeline-adapter.test
|
|
3
|
-
* @description 集成测试:验证所有引擎通过 ProjectPipelineAdapter 使用可配置状态名
|
|
4
|
-
*
|
|
5
|
-
* @author eddy
|
|
6
|
-
* @organization wykj
|
|
7
|
-
* @ownership wykj/eddy
|
|
8
|
-
*
|
|
9
|
-
* @created 2026-04-02
|
|
10
|
-
* @updated 2026-04-03
|
|
11
|
-
*
|
|
12
|
-
* @role test
|
|
13
|
-
* @layer engine
|
|
14
|
-
* @boundedContext pipeline-configuration
|
|
15
|
-
*/
|
|
16
|
-
import { cpSync, mkdirSync, mkdtempSync, rmSync, } from 'node:fs';
|
|
17
|
-
import { tmpdir } from 'node:os';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
|
-
import { ProjectPipelineAdapter } from '../core/projectPipelineAdapter.js';
|
|
21
|
-
import { RuntimeStore } from '../core/runtimeStore.js';
|
|
22
|
-
import { createIdleWorkerSlot, writeState } from '../core/state.js';
|
|
23
|
-
import { SPSEventHandler } from './EventHandler.js';
|
|
24
|
-
import { MonitorEngine } from './MonitorEngine.js';
|
|
25
|
-
import { SchedulerEngine } from './SchedulerEngine.js';
|
|
26
|
-
import { StageEngine } from './StageEngine.js';
|
|
27
|
-
// ─── Custom state names (must match __fixtures__/custom-pipeline.yaml) ──
|
|
28
|
-
const CUSTOM = {
|
|
29
|
-
planning: 'Planned',
|
|
30
|
-
backlog: 'Queue',
|
|
31
|
-
ready: 'Ready',
|
|
32
|
-
active: 'Working',
|
|
33
|
-
review: 'Review',
|
|
34
|
-
done: 'Shipped',
|
|
35
|
-
};
|
|
36
|
-
// ─── Helpers ────────────────────────────────────────────────────────
|
|
37
|
-
function makeTempDir() {
|
|
38
|
-
return mkdtempSync(join(tmpdir(), 'sps-engine-adapter-test-'));
|
|
39
|
-
}
|
|
40
|
-
function makeConfig(overrides = {}) {
|
|
41
|
-
return {
|
|
42
|
-
PROJECT_NAME: 'test-project',
|
|
43
|
-
PROJECT_DIR: '/tmp/test-project',
|
|
44
|
-
GITLAB_PROJECT: 'test/project',
|
|
45
|
-
GITLAB_PROJECT_ID: '1',
|
|
46
|
-
GITLAB_MERGE_BRANCH: 'main',
|
|
47
|
-
PM_TOOL: 'markdown',
|
|
48
|
-
MR_MODE: 'create',
|
|
49
|
-
WORKER_TRANSPORT: 'acp-sdk',
|
|
50
|
-
MAX_CONCURRENT_WORKERS: 2,
|
|
51
|
-
WORKER_RESTART_LIMIT: 3,
|
|
52
|
-
MAX_ACTIONS_PER_TICK: 5,
|
|
53
|
-
INPROGRESS_TIMEOUT_HOURS: 4,
|
|
54
|
-
MONITOR_AUTO_QA: true,
|
|
55
|
-
CONFLICT_DEFAULT: 'parallel',
|
|
56
|
-
TICK_LOCK_TIMEOUT_MINUTES: 10,
|
|
57
|
-
WORKER_LAUNCH_TIMEOUT_S: 60,
|
|
58
|
-
WORKER_IDLE_TIMEOUT_M: 30,
|
|
59
|
-
WORKER_ACK_TIMEOUT_S: 60,
|
|
60
|
-
WORKER_ACK_MAX_RETRIES: 1,
|
|
61
|
-
raw: {},
|
|
62
|
-
...overrides,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function makeDefaultState(maxWorkers) {
|
|
66
|
-
const workers = {};
|
|
67
|
-
for (let i = 0; i < maxWorkers; i++) {
|
|
68
|
-
workers[`worker-${i}`] = createIdleWorkerSlot();
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
version: 1,
|
|
72
|
-
generation: 0,
|
|
73
|
-
updatedAt: new Date().toISOString(),
|
|
74
|
-
updatedBy: 'test',
|
|
75
|
-
workers,
|
|
76
|
-
activeCards: {},
|
|
77
|
-
leases: {},
|
|
78
|
-
worktreeEvidence: {},
|
|
79
|
-
worktreeCleanup: [],
|
|
80
|
-
sessions: {},
|
|
81
|
-
integrationQueues: {},
|
|
82
|
-
pendingPMActions: [],
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function makeCard(seq, state, overrides = {}) {
|
|
86
|
-
return {
|
|
87
|
-
id: `card-${seq}`,
|
|
88
|
-
seq,
|
|
89
|
-
title: `Test card ${seq}`,
|
|
90
|
-
desc: `Description for card ${seq}`,
|
|
91
|
-
state,
|
|
92
|
-
labels: [],
|
|
93
|
-
meta: {},
|
|
94
|
-
...overrides,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function makeCtx(tempDir, config) {
|
|
98
|
-
const stateFile = join(tempDir, 'state.json');
|
|
99
|
-
const logsDir = join(tempDir, 'logs');
|
|
100
|
-
mkdirSync(logsDir, { recursive: true });
|
|
101
|
-
return {
|
|
102
|
-
projectName: 'test-project',
|
|
103
|
-
config,
|
|
104
|
-
paths: {
|
|
105
|
-
repoDir: tempDir,
|
|
106
|
-
stateFile,
|
|
107
|
-
lockFile: join(tempDir, 'tick.lock'),
|
|
108
|
-
logsDir,
|
|
109
|
-
pipelineOrderFile: join(tempDir, 'pipeline_order.json'),
|
|
110
|
-
},
|
|
111
|
-
pmTool: config.PM_TOOL,
|
|
112
|
-
maxWorkers: config.MAX_CONCURRENT_WORKERS,
|
|
113
|
-
mrMode: config.MR_MODE,
|
|
114
|
-
mergeBranch: config.GITLAB_MERGE_BRANCH,
|
|
115
|
-
validate: () => ({ ok: true, errors: [] }),
|
|
116
|
-
reload: () => { },
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function makeTaskBackend() {
|
|
120
|
-
return {
|
|
121
|
-
listAll: vi.fn().mockResolvedValue([]),
|
|
122
|
-
listByState: vi.fn().mockResolvedValue([]),
|
|
123
|
-
getBySeq: vi.fn().mockResolvedValue(null),
|
|
124
|
-
move: vi.fn().mockResolvedValue(undefined),
|
|
125
|
-
addLabel: vi.fn().mockResolvedValue(undefined),
|
|
126
|
-
removeLabel: vi.fn().mockResolvedValue(undefined),
|
|
127
|
-
claim: vi.fn().mockResolvedValue(undefined),
|
|
128
|
-
releaseClaim: vi.fn().mockResolvedValue(undefined),
|
|
129
|
-
comment: vi.fn().mockResolvedValue(undefined),
|
|
130
|
-
create: vi.fn().mockResolvedValue(null),
|
|
131
|
-
checklistCreate: vi.fn().mockResolvedValue(undefined),
|
|
132
|
-
checklistList: vi.fn().mockResolvedValue([]),
|
|
133
|
-
metaRead: vi.fn().mockResolvedValue({}),
|
|
134
|
-
metaWrite: vi.fn().mockResolvedValue(undefined),
|
|
135
|
-
bootstrap: vi.fn().mockResolvedValue(undefined),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function makeRepoBackend() {
|
|
139
|
-
return {
|
|
140
|
-
ensureCleanBase: vi.fn().mockResolvedValue(undefined),
|
|
141
|
-
ensureBranch: vi.fn().mockResolvedValue(undefined),
|
|
142
|
-
ensureWorktree: vi.fn().mockResolvedValue(undefined),
|
|
143
|
-
commit: vi.fn().mockResolvedValue(undefined),
|
|
144
|
-
push: vi.fn().mockResolvedValue(undefined),
|
|
145
|
-
createOrUpdateMr: vi.fn().mockResolvedValue({ url: '', iid: 1 }),
|
|
146
|
-
getMrStatus: vi.fn().mockResolvedValue({ exists: false, state: 'none', merged: false }),
|
|
147
|
-
mergeMr: vi.fn().mockResolvedValue({ merged: true }),
|
|
148
|
-
detectMerged: vi.fn().mockResolvedValue(false),
|
|
149
|
-
rebase: vi.fn().mockResolvedValue({ success: true, conflictFiles: [] }),
|
|
150
|
-
removeWorktree: vi.fn().mockResolvedValue(undefined),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function makeWorkerManager() {
|
|
154
|
-
const response = {
|
|
155
|
-
accepted: true,
|
|
156
|
-
slot: 'worker-0',
|
|
157
|
-
workerId: 'test-project:worker-0:1',
|
|
158
|
-
pid: 99999,
|
|
159
|
-
sessionId: 'test-session',
|
|
160
|
-
};
|
|
161
|
-
return {
|
|
162
|
-
run: vi.fn().mockResolvedValue(response),
|
|
163
|
-
cancel: vi.fn().mockResolvedValue(undefined),
|
|
164
|
-
inspect: vi.fn().mockReturnValue([]),
|
|
165
|
-
onEvent: vi.fn(),
|
|
166
|
-
cleanup: vi.fn(),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function makeSupervisor() {
|
|
170
|
-
return {
|
|
171
|
-
spawn: vi.fn(),
|
|
172
|
-
kill: vi.fn().mockResolvedValue(undefined),
|
|
173
|
-
remove: vi.fn(),
|
|
174
|
-
get: vi.fn().mockReturnValue(null),
|
|
175
|
-
list: vi.fn().mockReturnValue([]),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function makeWorkerManagerStub() {
|
|
179
|
-
return {
|
|
180
|
-
run: vi.fn().mockResolvedValue({ accepted: true, slot: 'worker-1', workerId: 'w', pid: 1 }),
|
|
181
|
-
resume: vi.fn().mockResolvedValue({ accepted: true, slot: 'worker-1', workerId: 'w', pid: 1 }),
|
|
182
|
-
cancel: vi.fn().mockResolvedValue(undefined),
|
|
183
|
-
sendInput: vi.fn().mockResolvedValue(undefined),
|
|
184
|
-
confirm: vi.fn().mockResolvedValue(undefined),
|
|
185
|
-
inspect: vi.fn().mockReturnValue([]),
|
|
186
|
-
onEvent: vi.fn(),
|
|
187
|
-
recover: vi.fn().mockResolvedValue({ scanned: 0, alive: 0, completed: 0, failed: 0, released: 0, queueRebuilt: 0 }),
|
|
188
|
-
cleanup: vi.fn(),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
// ─── Setup pipeline adapter from YAML fixture ──────────────────────
|
|
192
|
-
function setupAdapterWithCustomYaml(tempDir, config) {
|
|
193
|
-
// Copy the custom YAML to the temp project dir
|
|
194
|
-
const pipelinesDir = join(tempDir, '.sps', 'pipelines');
|
|
195
|
-
mkdirSync(pipelinesDir, { recursive: true });
|
|
196
|
-
cpSync(join(__dirname, '__fixtures__', 'custom-pipeline.yaml'), join(pipelinesDir, 'custom-pipeline.yaml'));
|
|
197
|
-
return new ProjectPipelineAdapter(config, tempDir);
|
|
198
|
-
}
|
|
199
|
-
// ─── Tests ──────────────────────────────────────────────────────────
|
|
200
|
-
describe('ProjectPipelineAdapter YAML loading', () => {
|
|
201
|
-
let tempDir;
|
|
202
|
-
beforeEach(() => { tempDir = makeTempDir(); });
|
|
203
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
204
|
-
it('loads custom state names from YAML', () => {
|
|
205
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
206
|
-
const adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
207
|
-
expect(adapter.states.planning).toBe(CUSTOM.planning);
|
|
208
|
-
expect(adapter.states.backlog).toBe(CUSTOM.backlog);
|
|
209
|
-
expect(adapter.states.ready).toBe(CUSTOM.ready);
|
|
210
|
-
expect(adapter.states.active).toBe(CUSTOM.active);
|
|
211
|
-
expect(adapter.states.review).toBe(CUSTOM.review);
|
|
212
|
-
expect(adapter.states.done).toBe(CUSTOM.done);
|
|
213
|
-
});
|
|
214
|
-
it('loads custom stages from YAML', () => {
|
|
215
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
216
|
-
const adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
217
|
-
expect(adapter.stages).toHaveLength(2);
|
|
218
|
-
expect(adapter.stages[0].name).toBe('develop');
|
|
219
|
-
expect(adapter.stages[0].triggerState).toBe('Ready');
|
|
220
|
-
expect(adapter.stages[0].activeState).toBe('Working');
|
|
221
|
-
expect(adapter.stages[0].onCompleteState).toBe('Review');
|
|
222
|
-
expect(adapter.stages[1].name).toBe('integrate');
|
|
223
|
-
expect(adapter.stages[1].triggerState).toBe('Review');
|
|
224
|
-
expect(adapter.stages[1].onCompleteState).toBe('Shipped');
|
|
225
|
-
});
|
|
226
|
-
it('activeStates uses custom names', () => {
|
|
227
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
228
|
-
const adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
229
|
-
expect(adapter.activeStates).toContain('Planned');
|
|
230
|
-
expect(adapter.activeStates).toContain('Queue');
|
|
231
|
-
expect(adapter.activeStates).toContain('Ready');
|
|
232
|
-
expect(adapter.activeStates).toContain('Working');
|
|
233
|
-
expect(adapter.activeStates).toContain('Review');
|
|
234
|
-
expect(adapter.activeStates).not.toContain('Done');
|
|
235
|
-
expect(adapter.activeStates).not.toContain('Shipped');
|
|
236
|
-
});
|
|
237
|
-
it('derivePmState maps lease phases to custom states', () => {
|
|
238
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
239
|
-
const adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
240
|
-
expect(adapter.derivePmState('queued')).toBe('Ready');
|
|
241
|
-
expect(adapter.derivePmState('coding')).toBe('Working');
|
|
242
|
-
expect(adapter.derivePmState('merging')).toBe('Review');
|
|
243
|
-
expect(adapter.derivePmState('resolving_conflict')).toBe('Review');
|
|
244
|
-
expect(adapter.derivePmState('closing')).toBe('Review');
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
describe('SchedulerEngine uses adapter states', () => {
|
|
248
|
-
let tempDir;
|
|
249
|
-
let taskBackend;
|
|
250
|
-
let adapter;
|
|
251
|
-
beforeEach(() => {
|
|
252
|
-
tempDir = makeTempDir();
|
|
253
|
-
taskBackend = makeTaskBackend();
|
|
254
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
255
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
256
|
-
});
|
|
257
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
258
|
-
it('lists cards by custom planning state', async () => {
|
|
259
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
260
|
-
const ctx = makeCtx(tempDir, config);
|
|
261
|
-
const state = makeDefaultState(2);
|
|
262
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
263
|
-
// Return a card with AI-PIPELINE label
|
|
264
|
-
const card = makeCard('1', CUSTOM.planning, { labels: ['AI-PIPELINE'] });
|
|
265
|
-
taskBackend.listByState.mockResolvedValue([card]);
|
|
266
|
-
const engine = new SchedulerEngine(ctx, taskBackend, adapter);
|
|
267
|
-
await engine.tick({ dryRun: true });
|
|
268
|
-
// Should call listByState with custom 'Planned' state (not 'Planning')
|
|
269
|
-
expect(taskBackend.listByState).toHaveBeenCalledWith('Planned');
|
|
270
|
-
});
|
|
271
|
-
it('moves cards to custom backlog state', async () => {
|
|
272
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
273
|
-
const ctx = makeCtx(tempDir, config);
|
|
274
|
-
const state = makeDefaultState(2);
|
|
275
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
276
|
-
const card = makeCard('1', CUSTOM.planning, { labels: ['AI-PIPELINE'] });
|
|
277
|
-
taskBackend.listByState.mockResolvedValue([card]);
|
|
278
|
-
const engine = new SchedulerEngine(ctx, taskBackend, adapter);
|
|
279
|
-
const _result = await engine.tick();
|
|
280
|
-
// Should move to custom 'Queue' state (not 'Backlog')
|
|
281
|
-
expect(taskBackend.move).toHaveBeenCalledWith('1', 'Queue');
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
describe('StageEngine (first stage) uses adapter states', () => {
|
|
285
|
-
let tempDir;
|
|
286
|
-
let taskBackend;
|
|
287
|
-
let repoBackend;
|
|
288
|
-
let workerManager;
|
|
289
|
-
let adapter;
|
|
290
|
-
beforeEach(() => {
|
|
291
|
-
tempDir = makeTempDir();
|
|
292
|
-
taskBackend = makeTaskBackend();
|
|
293
|
-
repoBackend = makeRepoBackend();
|
|
294
|
-
workerManager = makeWorkerManager();
|
|
295
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
296
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
297
|
-
});
|
|
298
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
299
|
-
it('lists backlog cards by custom state', async () => {
|
|
300
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
301
|
-
const ctx = makeCtx(tempDir, config);
|
|
302
|
-
const state = makeDefaultState(2);
|
|
303
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
304
|
-
const firstStage = adapter.stages[0];
|
|
305
|
-
const engine = new StageEngine(ctx, firstStage, 0, adapter.stages.length, taskBackend, repoBackend, workerManager, adapter);
|
|
306
|
-
await engine.tick({ dryRun: true });
|
|
307
|
-
// Should query custom states: 'Working' (active), 'Queue' (backlog), 'Ready' (ready)
|
|
308
|
-
const calls = taskBackend.listByState.mock.calls.map(c => c[0]);
|
|
309
|
-
expect(calls).toContain('Working'); // listRuntimeAwareActiveCards
|
|
310
|
-
expect(calls).toContain('Queue'); // backlog cards
|
|
311
|
-
expect(calls).toContain('Ready'); // ready cards (at least 2 calls)
|
|
312
|
-
});
|
|
313
|
-
it('prepares card: moves to custom ready state', async () => {
|
|
314
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
315
|
-
const ctx = makeCtx(tempDir, config);
|
|
316
|
-
const state = makeDefaultState(2);
|
|
317
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
318
|
-
// Return a backlog card, empty for other states
|
|
319
|
-
const backlogCard = makeCard('1', CUSTOM.backlog);
|
|
320
|
-
taskBackend.listByState.mockImplementation((s) => {
|
|
321
|
-
if (s === CUSTOM.backlog)
|
|
322
|
-
return Promise.resolve([backlogCard]);
|
|
323
|
-
return Promise.resolve([]);
|
|
324
|
-
});
|
|
325
|
-
const firstStage = adapter.stages[0];
|
|
326
|
-
const engine = new StageEngine(ctx, firstStage, 0, adapter.stages.length, taskBackend, repoBackend, workerManager, adapter);
|
|
327
|
-
await engine.tick();
|
|
328
|
-
// prepare phase should move Backlog → Ready (custom states)
|
|
329
|
-
expect(taskBackend.move).toHaveBeenCalledWith('1', 'Ready');
|
|
330
|
-
});
|
|
331
|
-
it('launches card: moves to custom active state', async () => {
|
|
332
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
333
|
-
const ctx = makeCtx(tempDir, config);
|
|
334
|
-
const state = makeDefaultState(2);
|
|
335
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
336
|
-
// Return a Todo/Ready card ready to launch
|
|
337
|
-
const readyCard = makeCard('1', CUSTOM.ready);
|
|
338
|
-
taskBackend.listByState.mockImplementation((s) => {
|
|
339
|
-
if (s === CUSTOM.ready)
|
|
340
|
-
return Promise.resolve([readyCard]);
|
|
341
|
-
return Promise.resolve([]);
|
|
342
|
-
});
|
|
343
|
-
const firstStage = adapter.stages[0];
|
|
344
|
-
const engine = new StageEngine(ctx, firstStage, 0, adapter.stages.length, taskBackend, repoBackend, workerManager, adapter);
|
|
345
|
-
await engine.tick();
|
|
346
|
-
// launch phase should move Ready → Working (custom states)
|
|
347
|
-
expect(taskBackend.move).toHaveBeenCalledWith('1', 'Working');
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
describe('StageEngine (last stage) uses adapter states', () => {
|
|
351
|
-
let tempDir;
|
|
352
|
-
let taskBackend;
|
|
353
|
-
let repoBackend;
|
|
354
|
-
let workerManager;
|
|
355
|
-
let adapter;
|
|
356
|
-
beforeEach(() => {
|
|
357
|
-
tempDir = makeTempDir();
|
|
358
|
-
taskBackend = makeTaskBackend();
|
|
359
|
-
repoBackend = makeRepoBackend();
|
|
360
|
-
workerManager = makeWorkerManager();
|
|
361
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
362
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
363
|
-
});
|
|
364
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
365
|
-
it('lists cards by custom review state', async () => {
|
|
366
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
367
|
-
const ctx = makeCtx(tempDir, config);
|
|
368
|
-
const state = makeDefaultState(2);
|
|
369
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
370
|
-
const lastIdx = adapter.stages.length - 1;
|
|
371
|
-
const lastStage = adapter.stages[lastIdx];
|
|
372
|
-
const engine = new StageEngine(ctx, lastStage, lastIdx, adapter.stages.length, taskBackend, repoBackend, workerManager, adapter);
|
|
373
|
-
await engine.tick();
|
|
374
|
-
// Should call listByState with custom 'Review' (not 'QA')
|
|
375
|
-
expect(taskBackend.listByState).toHaveBeenCalledWith('Review');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
describe('MonitorEngine uses adapter states', () => {
|
|
379
|
-
let tempDir;
|
|
380
|
-
let taskBackend;
|
|
381
|
-
let repoBackend;
|
|
382
|
-
let supervisor;
|
|
383
|
-
let adapter;
|
|
384
|
-
beforeEach(() => {
|
|
385
|
-
tempDir = makeTempDir();
|
|
386
|
-
taskBackend = makeTaskBackend();
|
|
387
|
-
repoBackend = makeRepoBackend();
|
|
388
|
-
supervisor = makeSupervisor();
|
|
389
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
390
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
391
|
-
});
|
|
392
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
393
|
-
it('lists inprogress cards by custom active state', async () => {
|
|
394
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
395
|
-
const ctx = makeCtx(tempDir, config);
|
|
396
|
-
const state = makeDefaultState(2);
|
|
397
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
398
|
-
const engine = new MonitorEngine(ctx, taskBackend, repoBackend, undefined, supervisor, adapter, makeWorkerManagerStub());
|
|
399
|
-
await engine.tick();
|
|
400
|
-
// Should call listByState with custom 'Working' (not 'Inprogress')
|
|
401
|
-
const calls = taskBackend.listByState.mock.calls.map(c => c[0]);
|
|
402
|
-
expect(calls).toContain('Working');
|
|
403
|
-
});
|
|
404
|
-
it('checkBlockedCards iterates custom state names', async () => {
|
|
405
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
406
|
-
const ctx = makeCtx(tempDir, config);
|
|
407
|
-
const state = makeDefaultState(2);
|
|
408
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
409
|
-
const engine = new MonitorEngine(ctx, taskBackend, repoBackend, undefined, supervisor, adapter, makeWorkerManagerStub());
|
|
410
|
-
await engine.tick();
|
|
411
|
-
// checkBlockedCards should iterate Queue/Ready/Working/Review (not Backlog/Todo/Inprogress/QA)
|
|
412
|
-
const calls = taskBackend.listByState.mock.calls.map(c => c[0]);
|
|
413
|
-
expect(calls).toContain('Queue');
|
|
414
|
-
expect(calls).toContain('Ready');
|
|
415
|
-
expect(calls).toContain('Review');
|
|
416
|
-
});
|
|
417
|
-
it('auto-retry moves to custom ready state', async () => {
|
|
418
|
-
const config = makeConfig({ PROJECT_DIR: tempDir, MONITOR_AUTO_QA: true });
|
|
419
|
-
const ctx = makeCtx(tempDir, config);
|
|
420
|
-
// Set up state with an active card that has a stale slot
|
|
421
|
-
const state = makeDefaultState(2);
|
|
422
|
-
state.workers['worker-0'] = {
|
|
423
|
-
...createIdleWorkerSlot(),
|
|
424
|
-
status: 'active',
|
|
425
|
-
seq: 1,
|
|
426
|
-
branch: 'feature/1-test',
|
|
427
|
-
worktree: '/tmp/wt-1',
|
|
428
|
-
claimedAt: new Date(Date.now() - 600_000).toISOString(),
|
|
429
|
-
lastHeartbeat: null,
|
|
430
|
-
mode: 'acp-sdk',
|
|
431
|
-
transport: 'acp-sdk',
|
|
432
|
-
outputFile: '/tmp/non-existent-output.jsonl',
|
|
433
|
-
};
|
|
434
|
-
state.activeCards['1'] = {
|
|
435
|
-
seq: 1,
|
|
436
|
-
state: 'Working',
|
|
437
|
-
worker: 'worker-0',
|
|
438
|
-
mrUrl: null,
|
|
439
|
-
conflictDomains: [],
|
|
440
|
-
startedAt: new Date(Date.now() - 600_000).toISOString(),
|
|
441
|
-
retryCount: 0,
|
|
442
|
-
};
|
|
443
|
-
state.leases['1'] = {
|
|
444
|
-
seq: 1,
|
|
445
|
-
pmStateObserved: 'Working',
|
|
446
|
-
phase: 'coding',
|
|
447
|
-
slot: 'worker-0',
|
|
448
|
-
branch: 'feature/1-test',
|
|
449
|
-
worktree: '/tmp/wt-1',
|
|
450
|
-
sessionId: null,
|
|
451
|
-
runId: null,
|
|
452
|
-
claimedAt: new Date(Date.now() - 600_000).toISOString(),
|
|
453
|
-
retryCount: 0,
|
|
454
|
-
lastTransitionAt: new Date(Date.now() - 600_000).toISOString(),
|
|
455
|
-
};
|
|
456
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
457
|
-
// Return the card as being in 'Working' state
|
|
458
|
-
const card = makeCard('1', CUSTOM.active);
|
|
459
|
-
taskBackend.listByState.mockImplementation((s) => {
|
|
460
|
-
if (s === CUSTOM.active)
|
|
461
|
-
return Promise.resolve([card]);
|
|
462
|
-
return Promise.resolve([]);
|
|
463
|
-
});
|
|
464
|
-
// getMrStatus returns no MR (so it's a genuine stale runtime)
|
|
465
|
-
repoBackend.getMrStatus.mockResolvedValue({
|
|
466
|
-
exists: false,
|
|
467
|
-
state: 'none',
|
|
468
|
-
merged: false,
|
|
469
|
-
});
|
|
470
|
-
const engine = new MonitorEngine(ctx, taskBackend, repoBackend, undefined, supervisor, adapter, makeWorkerManagerStub());
|
|
471
|
-
await engine.tick();
|
|
472
|
-
// With MONITOR_AUTO_QA, stale runtime should be moved to Review (custom, not QA)
|
|
473
|
-
const moveCalls = taskBackend.move.mock.calls;
|
|
474
|
-
if (moveCalls.length > 0) {
|
|
475
|
-
// Should use 'Review' (custom) not 'QA' (default)
|
|
476
|
-
const targets = moveCalls.map(c => c[1]);
|
|
477
|
-
expect(targets.every(t => t !== 'QA')).toBe(true);
|
|
478
|
-
// Check for either Review (auto-qa) or Ready (auto-retry)
|
|
479
|
-
expect(targets.some(t => t === 'Review' || t === 'Ready')).toBe(true);
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
|
-
describe('SPSEventHandler uses adapter states', () => {
|
|
484
|
-
let tempDir;
|
|
485
|
-
let taskBackend;
|
|
486
|
-
let adapter;
|
|
487
|
-
beforeEach(() => {
|
|
488
|
-
tempDir = makeTempDir();
|
|
489
|
-
taskBackend = makeTaskBackend();
|
|
490
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
491
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
492
|
-
});
|
|
493
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
494
|
-
it('onCompleted moves to custom done state for integration', async () => {
|
|
495
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
496
|
-
const ctx = makeCtx(tempDir, config);
|
|
497
|
-
const state = makeDefaultState(2);
|
|
498
|
-
state.leases['1'] = {
|
|
499
|
-
seq: 1,
|
|
500
|
-
pmStateObserved: 'Review',
|
|
501
|
-
phase: 'merging',
|
|
502
|
-
slot: 'worker-0',
|
|
503
|
-
branch: 'feature/1-test',
|
|
504
|
-
worktree: '/tmp/wt-1',
|
|
505
|
-
sessionId: null,
|
|
506
|
-
runId: null,
|
|
507
|
-
claimedAt: new Date().toISOString(),
|
|
508
|
-
retryCount: 0,
|
|
509
|
-
lastTransitionAt: new Date().toISOString(),
|
|
510
|
-
};
|
|
511
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
512
|
-
// Mock card with COMPLETED-integrate label — signals Claude declared completion
|
|
513
|
-
taskBackend.getBySeq = vi.fn().mockResolvedValue(makeCard('1', 'Review', { labels: ['COMPLETED-integrate'] }));
|
|
514
|
-
const runtimeStore = new RuntimeStore({
|
|
515
|
-
paths: { stateFile: ctx.paths.stateFile },
|
|
516
|
-
maxWorkers: config.MAX_CONCURRENT_WORKERS,
|
|
517
|
-
});
|
|
518
|
-
const handler = new SPSEventHandler({
|
|
519
|
-
taskBackend,
|
|
520
|
-
runtimeStore,
|
|
521
|
-
project: 'test-project',
|
|
522
|
-
pipelineAdapter: adapter,
|
|
523
|
-
});
|
|
524
|
-
// Simulate a completed integration event
|
|
525
|
-
handler.handle({
|
|
526
|
-
type: 'run.completed',
|
|
527
|
-
taskId: '1',
|
|
528
|
-
cardId: '1',
|
|
529
|
-
workerId: 'test-project:worker-0:1',
|
|
530
|
-
timestamp: new Date().toISOString(),
|
|
531
|
-
phase: 'integration',
|
|
532
|
-
slot: 'worker-0',
|
|
533
|
-
project: 'test-project',
|
|
534
|
-
state: 'completed',
|
|
535
|
-
exitCode: 0,
|
|
536
|
-
completionResult: { status: 'completed', reason: 'already_merged' },
|
|
537
|
-
});
|
|
538
|
-
// Allow async handlers to complete
|
|
539
|
-
await new Promise(r => setTimeout(r, 100));
|
|
540
|
-
// Should move to 'Shipped' (custom done), not 'Done'
|
|
541
|
-
expect(taskBackend.move).toHaveBeenCalledWith('1', 'Shipped');
|
|
542
|
-
});
|
|
543
|
-
it('onCompleted moves to custom review state for development', async () => {
|
|
544
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
545
|
-
const ctx = makeCtx(tempDir, config);
|
|
546
|
-
const state = makeDefaultState(2);
|
|
547
|
-
state.leases['1'] = {
|
|
548
|
-
seq: 1,
|
|
549
|
-
pmStateObserved: 'Working',
|
|
550
|
-
phase: 'coding',
|
|
551
|
-
slot: 'worker-0',
|
|
552
|
-
branch: 'feature/1-test',
|
|
553
|
-
worktree: '/tmp/wt-1',
|
|
554
|
-
sessionId: null,
|
|
555
|
-
runId: null,
|
|
556
|
-
claimedAt: new Date().toISOString(),
|
|
557
|
-
retryCount: 0,
|
|
558
|
-
lastTransitionAt: new Date().toISOString(),
|
|
559
|
-
};
|
|
560
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
561
|
-
// Mock card with COMPLETED-develop label
|
|
562
|
-
taskBackend.getBySeq = vi.fn().mockResolvedValue(makeCard('1', 'Working', { labels: ['COMPLETED-develop'] }));
|
|
563
|
-
const runtimeStore = new RuntimeStore({
|
|
564
|
-
paths: { stateFile: ctx.paths.stateFile },
|
|
565
|
-
maxWorkers: config.MAX_CONCURRENT_WORKERS,
|
|
566
|
-
});
|
|
567
|
-
const handler = new SPSEventHandler({
|
|
568
|
-
taskBackend,
|
|
569
|
-
runtimeStore,
|
|
570
|
-
project: 'test-project',
|
|
571
|
-
pipelineAdapter: adapter,
|
|
572
|
-
});
|
|
573
|
-
// Simulate a completed development event
|
|
574
|
-
handler.handle({
|
|
575
|
-
type: 'run.completed',
|
|
576
|
-
taskId: '1',
|
|
577
|
-
cardId: '1',
|
|
578
|
-
workerId: 'test-project:worker-0:1',
|
|
579
|
-
timestamp: new Date().toISOString(),
|
|
580
|
-
phase: 'development',
|
|
581
|
-
slot: 'worker-0',
|
|
582
|
-
project: 'test-project',
|
|
583
|
-
state: 'completed',
|
|
584
|
-
exitCode: 0,
|
|
585
|
-
completionResult: { status: 'completed', reason: 'branch_pushed' },
|
|
586
|
-
});
|
|
587
|
-
// Allow async handlers to complete
|
|
588
|
-
await new Promise(r => setTimeout(r, 100));
|
|
589
|
-
// Should move to 'Review' (custom review), not 'QA'
|
|
590
|
-
expect(taskBackend.move).toHaveBeenCalledWith('1', 'Review');
|
|
591
|
-
});
|
|
592
|
-
it('releaseSlot sets custom review state in pmStateObserved', async () => {
|
|
593
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
594
|
-
const ctx = makeCtx(tempDir, config);
|
|
595
|
-
const state = makeDefaultState(2);
|
|
596
|
-
state.workers['worker-0'] = {
|
|
597
|
-
...createIdleWorkerSlot(),
|
|
598
|
-
status: 'active',
|
|
599
|
-
seq: 1,
|
|
600
|
-
branch: 'feature/1-test',
|
|
601
|
-
worktree: '/tmp/wt-1',
|
|
602
|
-
claimedAt: new Date().toISOString(),
|
|
603
|
-
lastHeartbeat: null,
|
|
604
|
-
};
|
|
605
|
-
state.activeCards['1'] = {
|
|
606
|
-
seq: 1,
|
|
607
|
-
state: 'Working',
|
|
608
|
-
worker: 'worker-0',
|
|
609
|
-
mrUrl: null,
|
|
610
|
-
conflictDomains: [],
|
|
611
|
-
startedAt: new Date().toISOString(),
|
|
612
|
-
};
|
|
613
|
-
state.leases['1'] = {
|
|
614
|
-
seq: 1,
|
|
615
|
-
pmStateObserved: 'Working',
|
|
616
|
-
phase: 'coding',
|
|
617
|
-
slot: 'worker-0',
|
|
618
|
-
branch: 'feature/1-test',
|
|
619
|
-
worktree: '/tmp/wt-1',
|
|
620
|
-
sessionId: null,
|
|
621
|
-
runId: null,
|
|
622
|
-
claimedAt: new Date().toISOString(),
|
|
623
|
-
retryCount: 0,
|
|
624
|
-
lastTransitionAt: new Date().toISOString(),
|
|
625
|
-
};
|
|
626
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
627
|
-
const runtimeStore = new RuntimeStore({
|
|
628
|
-
paths: { stateFile: ctx.paths.stateFile },
|
|
629
|
-
maxWorkers: config.MAX_CONCURRENT_WORKERS,
|
|
630
|
-
});
|
|
631
|
-
const handler = new SPSEventHandler({
|
|
632
|
-
taskBackend,
|
|
633
|
-
runtimeStore,
|
|
634
|
-
project: 'test-project',
|
|
635
|
-
pipelineAdapter: adapter,
|
|
636
|
-
});
|
|
637
|
-
// Simulate development completion
|
|
638
|
-
handler.handle({
|
|
639
|
-
type: 'run.completed',
|
|
640
|
-
taskId: '1',
|
|
641
|
-
cardId: '1',
|
|
642
|
-
workerId: 'test-project:worker-0:1',
|
|
643
|
-
timestamp: new Date().toISOString(),
|
|
644
|
-
phase: 'development',
|
|
645
|
-
slot: 'worker-0',
|
|
646
|
-
project: 'test-project',
|
|
647
|
-
state: 'completed',
|
|
648
|
-
exitCode: 0,
|
|
649
|
-
completionResult: { status: 'completed', reason: 'branch_pushed' },
|
|
650
|
-
});
|
|
651
|
-
await new Promise(r => setTimeout(r, 100));
|
|
652
|
-
// Verify runtime state has custom review state
|
|
653
|
-
const freshState = runtimeStore.readState();
|
|
654
|
-
if (freshState.leases['1']) {
|
|
655
|
-
expect(freshState.leases['1'].pmStateObserved).toBe('Review');
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
describe('Full pipeline flow with custom states (dry-run)', () => {
|
|
660
|
-
let tempDir;
|
|
661
|
-
let taskBackend;
|
|
662
|
-
let repoBackend;
|
|
663
|
-
let workerManager;
|
|
664
|
-
let adapter;
|
|
665
|
-
beforeEach(() => {
|
|
666
|
-
tempDir = makeTempDir();
|
|
667
|
-
taskBackend = makeTaskBackend();
|
|
668
|
-
repoBackend = makeRepoBackend();
|
|
669
|
-
workerManager = makeWorkerManager();
|
|
670
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
671
|
-
adapter = setupAdapterWithCustomYaml(tempDir, config);
|
|
672
|
-
});
|
|
673
|
-
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
674
|
-
it('never uses default state names when custom YAML is loaded', async () => {
|
|
675
|
-
const config = makeConfig({ PROJECT_DIR: tempDir });
|
|
676
|
-
const ctx = makeCtx(tempDir, config);
|
|
677
|
-
const state = makeDefaultState(2);
|
|
678
|
-
writeState(ctx.paths.stateFile, state, 'test');
|
|
679
|
-
const DEFAULTS = ['Planning', 'Backlog', 'Todo', 'Inprogress', 'QA', 'Done'];
|
|
680
|
-
// Run all engines in sequence
|
|
681
|
-
const scheduler = new SchedulerEngine(ctx, taskBackend, adapter);
|
|
682
|
-
await scheduler.tick();
|
|
683
|
-
// Run all stage engines
|
|
684
|
-
for (let i = 0; i < adapter.stages.length; i++) {
|
|
685
|
-
const stageEngine = new StageEngine(ctx, adapter.stages[i], i, adapter.stages.length, taskBackend, repoBackend, workerManager, adapter);
|
|
686
|
-
await stageEngine.tick();
|
|
687
|
-
}
|
|
688
|
-
const supervisor = makeSupervisor();
|
|
689
|
-
const monitor = new MonitorEngine(ctx, taskBackend, repoBackend, undefined, supervisor, adapter, makeWorkerManagerStub());
|
|
690
|
-
await monitor.tick();
|
|
691
|
-
// Collect all calls to listByState and move
|
|
692
|
-
const listCalls = taskBackend.listByState.mock.calls.map(c => c[0]);
|
|
693
|
-
const moveCalls = taskBackend.move.mock.calls.map(c => c[1]);
|
|
694
|
-
const allStateCalls = [...listCalls, ...moveCalls];
|
|
695
|
-
// No call should use any default state name
|
|
696
|
-
for (const call of allStateCalls) {
|
|
697
|
-
expect(DEFAULTS).not.toContain(call);
|
|
698
|
-
}
|
|
699
|
-
// Should use custom state names instead
|
|
700
|
-
expect(listCalls).toContain('Planned'); // Scheduler
|
|
701
|
-
expect(listCalls).toContain('Queue'); // StageEngine[0] (backlog)
|
|
702
|
-
expect(listCalls).toContain('Ready'); // StageEngine[0] (ready)
|
|
703
|
-
expect(listCalls).toContain('Working'); // StageEngine[0] (active) + Monitor
|
|
704
|
-
expect(listCalls).toContain('Review'); // StageEngine[1] (trigger) + Monitor
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
//# sourceMappingURL=engine-pipeline-adapter.test.js.map
|