@coralai/sps-cli 0.49.0 → 0.49.2

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.
Files changed (164) hide show
  1. package/dist/commands/projectInit.d.ts.map +1 -1
  2. package/dist/commands/projectInit.js +13 -7
  3. package/dist/commands/projectInit.js.map +1 -1
  4. package/dist/console-assets/assets/index-9IBifmI1.js +486 -0
  5. package/dist/console-assets/assets/index-DRtlGwvq.css +10 -0
  6. package/dist/console-assets/index.html +2 -2
  7. package/dist/console-server/routes/projects.d.ts.map +1 -1
  8. package/dist/console-server/routes/projects.js +162 -1
  9. package/dist/console-server/routes/projects.js.map +1 -1
  10. package/package.json +1 -1
  11. package/dist/commands/cardMarkComplete.test.d.ts +0 -2
  12. package/dist/commands/cardMarkComplete.test.d.ts.map +0 -1
  13. package/dist/commands/cardMarkComplete.test.js +0 -142
  14. package/dist/commands/cardMarkComplete.test.js.map +0 -1
  15. package/dist/commands/cardMarkStarted.test.d.ts +0 -2
  16. package/dist/commands/cardMarkStarted.test.d.ts.map +0 -1
  17. package/dist/commands/cardMarkStarted.test.js +0 -98
  18. package/dist/commands/cardMarkStarted.test.js.map +0 -1
  19. package/dist/commands/projectInit.test.d.ts +0 -2
  20. package/dist/commands/projectInit.test.d.ts.map +0 -1
  21. package/dist/commands/projectInit.test.js +0 -126
  22. package/dist/commands/projectInit.test.js.map +0 -1
  23. package/dist/commands/tick-heartbeat.test.d.ts +0 -2
  24. package/dist/commands/tick-heartbeat.test.d.ts.map +0 -1
  25. package/dist/commands/tick-heartbeat.test.js +0 -89
  26. package/dist/commands/tick-heartbeat.test.js.map +0 -1
  27. package/dist/console-assets/assets/index-C7skV1yt.js +0 -338
  28. package/dist/console-assets/assets/index-DNKOkgDj.css +0 -10
  29. package/dist/console-server/routes/chat.test.d.ts +0 -2
  30. package/dist/console-server/routes/chat.test.d.ts.map +0 -1
  31. package/dist/console-server/routes/chat.test.js +0 -131
  32. package/dist/console-server/routes/chat.test.js.map +0 -1
  33. package/dist/console-server/routes/projects.test.d.ts +0 -2
  34. package/dist/console-server/routes/projects.test.d.ts.map +0 -1
  35. package/dist/console-server/routes/projects.test.js +0 -150
  36. package/dist/console-server/routes/projects.test.js.map +0 -1
  37. package/dist/console-server/routes/system.test.d.ts +0 -2
  38. package/dist/console-server/routes/system.test.d.ts.map +0 -1
  39. package/dist/console-server/routes/system.test.js +0 -107
  40. package/dist/console-server/routes/system.test.js.map +0 -1
  41. package/dist/core/checklist.test.d.ts +0 -2
  42. package/dist/core/checklist.test.d.ts.map +0 -1
  43. package/dist/core/checklist.test.js +0 -74
  44. package/dist/core/checklist.test.js.map +0 -1
  45. package/dist/core/config.test.d.ts +0 -2
  46. package/dist/core/config.test.d.ts.map +0 -1
  47. package/dist/core/config.test.js +0 -352
  48. package/dist/core/config.test.js.map +0 -1
  49. package/dist/core/lock.test.d.ts +0 -2
  50. package/dist/core/lock.test.d.ts.map +0 -1
  51. package/dist/core/lock.test.js +0 -118
  52. package/dist/core/lock.test.js.map +0 -1
  53. package/dist/core/markerFile.test.d.ts +0 -2
  54. package/dist/core/markerFile.test.d.ts.map +0 -1
  55. package/dist/core/markerFile.test.js +0 -111
  56. package/dist/core/markerFile.test.js.map +0 -1
  57. package/dist/core/queue.test.d.ts +0 -2
  58. package/dist/core/queue.test.d.ts.map +0 -1
  59. package/dist/core/queue.test.js +0 -114
  60. package/dist/core/queue.test.js.map +0 -1
  61. package/dist/core/sessionCleanup.test.d.ts +0 -2
  62. package/dist/core/sessionCleanup.test.d.ts.map +0 -1
  63. package/dist/core/sessionCleanup.test.js +0 -158
  64. package/dist/core/sessionCleanup.test.js.map +0 -1
  65. package/dist/core/shellEnv.test.d.ts +0 -2
  66. package/dist/core/shellEnv.test.d.ts.map +0 -1
  67. package/dist/core/shellEnv.test.js +0 -116
  68. package/dist/core/shellEnv.test.js.map +0 -1
  69. package/dist/core/skillStore.test.d.ts +0 -2
  70. package/dist/core/skillStore.test.d.ts.map +0 -1
  71. package/dist/core/skillStore.test.js +0 -203
  72. package/dist/core/skillStore.test.js.map +0 -1
  73. package/dist/core/state.test.d.ts +0 -2
  74. package/dist/core/state.test.d.ts.map +0 -1
  75. package/dist/core/state.test.js +0 -336
  76. package/dist/core/state.test.js.map +0 -1
  77. package/dist/engines/CloseoutEngine.d.ts +0 -72
  78. package/dist/engines/CloseoutEngine.d.ts.map +0 -1
  79. package/dist/engines/CloseoutEngine.js +0 -648
  80. package/dist/engines/CloseoutEngine.js.map +0 -1
  81. package/dist/engines/EventHandler.test.d.ts +0 -2
  82. package/dist/engines/EventHandler.test.d.ts.map +0 -1
  83. package/dist/engines/EventHandler.test.js +0 -169
  84. package/dist/engines/EventHandler.test.js.map +0 -1
  85. package/dist/engines/ExecutionEngine.d.ts +0 -125
  86. package/dist/engines/ExecutionEngine.d.ts.map +0 -1
  87. package/dist/engines/ExecutionEngine.js +0 -766
  88. package/dist/engines/ExecutionEngine.js.map +0 -1
  89. package/dist/engines/MonitorEngine.test.d.ts +0 -2
  90. package/dist/engines/MonitorEngine.test.d.ts.map +0 -1
  91. package/dist/engines/MonitorEngine.test.js +0 -355
  92. package/dist/engines/MonitorEngine.test.js.map +0 -1
  93. package/dist/engines/engine-pipeline-adapter.test.d.ts +0 -17
  94. package/dist/engines/engine-pipeline-adapter.test.d.ts.map +0 -1
  95. package/dist/engines/engine-pipeline-adapter.test.js +0 -707
  96. package/dist/engines/engine-pipeline-adapter.test.js.map +0 -1
  97. package/dist/interfaces/HookProvider.d.ts +0 -9
  98. package/dist/interfaces/HookProvider.d.ts.map +0 -1
  99. package/dist/interfaces/HookProvider.js +0 -2
  100. package/dist/interfaces/HookProvider.js.map +0 -1
  101. package/dist/manager/completion-judge.test.d.ts +0 -17
  102. package/dist/manager/completion-judge.test.d.ts.map +0 -1
  103. package/dist/manager/completion-judge.test.js +0 -233
  104. package/dist/manager/completion-judge.test.js.map +0 -1
  105. package/dist/manager/integration-queue.d.ts +0 -71
  106. package/dist/manager/integration-queue.d.ts.map +0 -1
  107. package/dist/manager/integration-queue.js +0 -137
  108. package/dist/manager/integration-queue.js.map +0 -1
  109. package/dist/manager/integration-queue.test.d.ts +0 -17
  110. package/dist/manager/integration-queue.test.d.ts.map +0 -1
  111. package/dist/manager/integration-queue.test.js +0 -210
  112. package/dist/manager/integration-queue.test.js.map +0 -1
  113. package/dist/manager/pm-client.d.ts +0 -10
  114. package/dist/manager/pm-client.d.ts.map +0 -1
  115. package/dist/manager/pm-client.js +0 -260
  116. package/dist/manager/pm-client.js.map +0 -1
  117. package/dist/manager/resource-limiter.d.ts +0 -56
  118. package/dist/manager/resource-limiter.d.ts.map +0 -1
  119. package/dist/manager/resource-limiter.js +0 -116
  120. package/dist/manager/resource-limiter.js.map +0 -1
  121. package/dist/manager/resource-limiter.test.d.ts +0 -17
  122. package/dist/manager/resource-limiter.test.d.ts.map +0 -1
  123. package/dist/manager/resource-limiter.test.js +0 -118
  124. package/dist/manager/resource-limiter.test.js.map +0 -1
  125. package/dist/manager/supervisor.test.d.ts +0 -17
  126. package/dist/manager/supervisor.test.d.ts.map +0 -1
  127. package/dist/manager/supervisor.test.js +0 -216
  128. package/dist/manager/supervisor.test.js.map +0 -1
  129. package/dist/manager/worker-manager-impl.test.d.ts +0 -17
  130. package/dist/manager/worker-manager-impl.test.d.ts.map +0 -1
  131. package/dist/manager/worker-manager-impl.test.js +0 -446
  132. package/dist/manager/worker-manager-impl.test.js.map +0 -1
  133. package/dist/providers/PlaneTaskBackend.d.ts +0 -83
  134. package/dist/providers/PlaneTaskBackend.d.ts.map +0 -1
  135. package/dist/providers/PlaneTaskBackend.js +0 -461
  136. package/dist/providers/PlaneTaskBackend.js.map +0 -1
  137. package/dist/providers/TrelloTaskBackend.d.ts +0 -64
  138. package/dist/providers/TrelloTaskBackend.d.ts.map +0 -1
  139. package/dist/providers/TrelloTaskBackend.js +0 -298
  140. package/dist/providers/TrelloTaskBackend.js.map +0 -1
  141. package/dist/providers/adapters/acp-fs-handlers.test.d.ts +0 -2
  142. package/dist/providers/adapters/acp-fs-handlers.test.d.ts.map +0 -1
  143. package/dist/providers/adapters/acp-fs-handlers.test.js +0 -80
  144. package/dist/providers/adapters/acp-fs-handlers.test.js.map +0 -1
  145. package/dist/providers/adapters/acp-permissions.test.d.ts +0 -2
  146. package/dist/providers/adapters/acp-permissions.test.d.ts.map +0 -1
  147. package/dist/providers/adapters/acp-permissions.test.js +0 -103
  148. package/dist/providers/adapters/acp-permissions.test.js.map +0 -1
  149. package/dist/providers/adapters/acp-session-accumulator.test.d.ts +0 -2
  150. package/dist/providers/adapters/acp-session-accumulator.test.d.ts.map +0 -1
  151. package/dist/providers/adapters/acp-session-accumulator.test.js +0 -88
  152. package/dist/providers/adapters/acp-session-accumulator.test.js.map +0 -1
  153. package/dist/providers/adapters/acp-terminal-manager.test.d.ts +0 -2
  154. package/dist/providers/adapters/acp-terminal-manager.test.d.ts.map +0 -1
  155. package/dist/providers/adapters/acp-terminal-manager.test.js +0 -86
  156. package/dist/providers/adapters/acp-terminal-manager.test.js.map +0 -1
  157. package/dist/providers/outputParser.test.d.ts +0 -2
  158. package/dist/providers/outputParser.test.d.ts.map +0 -1
  159. package/dist/providers/outputParser.test.js +0 -185
  160. package/dist/providers/outputParser.test.js.map +0 -1
  161. package/dist/test-setup.d.ts +0 -2
  162. package/dist/test-setup.d.ts.map +0 -1
  163. package/dist/test-setup.js +0 -27
  164. 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