ccjk 2.2.5 → 2.3.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/chunks/codex.mjs +4 -84
- package/dist/chunks/index.mjs +698 -1
- package/dist/chunks/index2.mjs +1 -0
- package/dist/chunks/mcp-performance.mjs +2 -5
- package/dist/chunks/menu.mjs +46 -40
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/workflows.mjs +617 -84
- package/dist/cli.mjs +2 -2
- package/dist/i18n/locales/en/cloud-sync.json +147 -0
- package/dist/i18n/locales/en/marketplace.json +32 -1
- package/dist/i18n/locales/zh-CN/cloud-sync.json +147 -0
- package/dist/i18n/locales/zh-CN/marketplace.json +32 -1
- package/package.json +1 -1
|
@@ -1,100 +1,633 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
2
1
|
import ansis from 'ansis';
|
|
3
2
|
import 'inquirer';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import 'node:os';
|
|
7
|
-
import '
|
|
8
|
-
import 'node:process';
|
|
9
|
-
import 'node:url';
|
|
10
|
-
import 'i18next';
|
|
11
|
-
import 'i18next-fs-backend';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join, dirname } from 'pathe';
|
|
12
7
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
const PHASE_CONFIGS = {
|
|
9
|
+
brainstorming: {
|
|
10
|
+
phase: "brainstorming",
|
|
11
|
+
name: {
|
|
12
|
+
"en": "Brainstorming",
|
|
13
|
+
"zh-CN": "\u5934\u8111\u98CE\u66B4",
|
|
14
|
+
"ja-JP": "\u30D6\u30EC\u30A4\u30F3\u30B9\u30C8\u30FC\u30DF\u30F3\u30B0",
|
|
15
|
+
"ko-KR": "\uBE0C\uB808\uC778\uC2A4\uD1A0\uBC0D"
|
|
16
|
+
},
|
|
17
|
+
description: {
|
|
18
|
+
"en": "Explore ideas and gather requirements",
|
|
19
|
+
"zh-CN": "\u63A2\u7D22\u60F3\u6CD5\uFF0C\u6536\u96C6\u9700\u6C42",
|
|
20
|
+
"ja-JP": "\u30A2\u30A4\u30C7\u30A2\u3092\u63A2\u6C42\u3057\u3001\u8981\u4EF6\u3092\u53CE\u96C6\u3059\u308B",
|
|
21
|
+
"ko-KR": "\uC544\uC774\uB514\uC5B4 \uD0D0\uC0C9 \uBC0F \uC694\uAD6C\uC0AC\uD56D \uC218\uC9D1"
|
|
22
|
+
},
|
|
23
|
+
autoActivateSkills: ["brainstorming", "requirements"],
|
|
24
|
+
allowedTransitions: ["planning"],
|
|
25
|
+
requiresConfirmation: true,
|
|
26
|
+
maxDuration: 30
|
|
27
|
+
},
|
|
28
|
+
planning: {
|
|
29
|
+
phase: "planning",
|
|
30
|
+
name: {
|
|
31
|
+
"en": "Planning",
|
|
32
|
+
"zh-CN": "\u89C4\u5212",
|
|
33
|
+
"ja-JP": "\u8A08\u753B",
|
|
34
|
+
"ko-KR": "\uACC4\uD68D"
|
|
35
|
+
},
|
|
36
|
+
description: {
|
|
37
|
+
"en": "Create detailed implementation plan with bite-sized tasks",
|
|
38
|
+
"zh-CN": "\u521B\u5EFA\u8BE6\u7EC6\u7684\u5B9E\u65BD\u8BA1\u5212\uFF0C\u5206\u89E3\u4E3A\u5C0F\u4EFB\u52A1",
|
|
39
|
+
"ja-JP": "\u8A73\u7D30\u306A\u5B9F\u88C5\u8A08\u753B\u3092\u4F5C\u6210\u3057\u3001\u5C0F\u3055\u306A\u30BF\u30B9\u30AF\u306B\u5206\u89E3\u3059\u308B",
|
|
40
|
+
"ko-KR": "\uC138\uBD80 \uAD6C\uD604 \uACC4\uD68D \uC791\uC131 \uBC0F \uC791\uC740 \uC791\uC5C5\uC73C\uB85C \uBD84\uD574"
|
|
41
|
+
},
|
|
42
|
+
autoActivateSkills: ["planning", "task-breakdown"],
|
|
43
|
+
allowedTransitions: ["implementation", "brainstorming"],
|
|
44
|
+
requiresConfirmation: true,
|
|
45
|
+
maxDuration: 60
|
|
46
|
+
},
|
|
47
|
+
implementation: {
|
|
48
|
+
phase: "implementation",
|
|
49
|
+
name: {
|
|
50
|
+
"en": "Implementation",
|
|
51
|
+
"zh-CN": "\u5B9E\u73B0",
|
|
52
|
+
"ja-JP": "\u5B9F\u88C5",
|
|
53
|
+
"ko-KR": "\uAD6C\uD604"
|
|
54
|
+
},
|
|
55
|
+
description: {
|
|
56
|
+
"en": "Execute tasks via subagents with TDD approach",
|
|
57
|
+
"zh-CN": "\u901A\u8FC7\u5B50\u4EE3\u7406\u6267\u884C\u4EFB\u52A1\uFF0C\u91C7\u7528 TDD \u65B9\u6CD5",
|
|
58
|
+
"ja-JP": "TDD\u30A2\u30D7\u30ED\u30FC\u30C1\u3067\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u4ECB\u3057\u3066\u30BF\u30B9\u30AF\u3092\u5B9F\u884C",
|
|
59
|
+
"ko-KR": "TDD \uC811\uADFC \uBC29\uC2DD\uC73C\uB85C \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uB97C \uD1B5\uD574 \uC791\uC5C5 \uC2E4\uD589"
|
|
60
|
+
},
|
|
61
|
+
autoActivateSkills: ["implementation", "tdd", "coding"],
|
|
62
|
+
allowedTransitions: ["review", "planning"],
|
|
63
|
+
requiresConfirmation: false,
|
|
64
|
+
maxDuration: 0
|
|
65
|
+
// Unlimited
|
|
66
|
+
},
|
|
67
|
+
review: {
|
|
68
|
+
phase: "review",
|
|
69
|
+
name: {
|
|
70
|
+
"en": "Code Review",
|
|
71
|
+
"zh-CN": "\u4EE3\u7801\u5BA1\u67E5",
|
|
72
|
+
"ja-JP": "\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30FC",
|
|
73
|
+
"ko-KR": "\uCF54\uB4DC \uB9AC\uBDF0"
|
|
74
|
+
},
|
|
75
|
+
description: {
|
|
76
|
+
"en": "Two-stage review: spec compliance + code quality",
|
|
77
|
+
"zh-CN": "\u4E24\u9636\u6BB5\u5BA1\u67E5\uFF1A\u89C4\u683C\u7B26\u5408\u6027 + \u4EE3\u7801\u8D28\u91CF",
|
|
78
|
+
"ja-JP": "2\u6BB5\u968E\u30EC\u30D3\u30E5\u30FC\uFF1A\u4ED5\u69D8\u6E96\u62E0 + \u30B3\u30FC\u30C9\u54C1\u8CEA",
|
|
79
|
+
"ko-KR": "2\uB2E8\uACC4 \uAC80\uD1A0: \uC0AC\uC591 \uC900\uC218 + \uCF54\uB4DC \uD488\uC9C8"
|
|
80
|
+
},
|
|
81
|
+
autoActivateSkills: ["code-review", "quality-check"],
|
|
82
|
+
allowedTransitions: ["finishing", "implementation"],
|
|
83
|
+
requiresConfirmation: true,
|
|
84
|
+
maxDuration: 30
|
|
85
|
+
},
|
|
86
|
+
finishing: {
|
|
87
|
+
phase: "finishing",
|
|
88
|
+
name: {
|
|
89
|
+
"en": "Finishing",
|
|
90
|
+
"zh-CN": "\u6536\u5C3E",
|
|
91
|
+
"ja-JP": "\u4ED5\u4E0A\u3052",
|
|
92
|
+
"ko-KR": "\uB9C8\uBB34\uB9AC"
|
|
93
|
+
},
|
|
94
|
+
description: {
|
|
95
|
+
"en": "Final cleanup, documentation, and merge",
|
|
96
|
+
"zh-CN": "\u6700\u7EC8\u6E05\u7406\u3001\u6587\u6863\u548C\u5408\u5E76",
|
|
97
|
+
"ja-JP": "\u6700\u7D42\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u3001\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3001\u30DE\u30FC\u30B8",
|
|
98
|
+
"ko-KR": "\uCD5C\uC885 \uC815\uB9AC, \uBB38\uC11C\uD654 \uBC0F \uBCD1\uD569"
|
|
99
|
+
},
|
|
100
|
+
autoActivateSkills: ["finishing", "documentation"],
|
|
101
|
+
allowedTransitions: [],
|
|
102
|
+
requiresConfirmation: true,
|
|
103
|
+
maxDuration: 15
|
|
104
|
+
}
|
|
22
105
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
106
|
+
const WORKFLOW_PERSISTENCE_VERSION = 1;
|
|
107
|
+
|
|
108
|
+
function generateId() {
|
|
109
|
+
return `wf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
110
|
+
}
|
|
111
|
+
const DEFAULT_OPTIONS = {
|
|
112
|
+
persistPath: join(homedir(), ".ccjk", "workflow-state.json"),
|
|
113
|
+
autoSave: true,
|
|
114
|
+
verbose: false
|
|
115
|
+
};
|
|
116
|
+
class WorkflowStateMachine extends EventEmitter {
|
|
117
|
+
sessions = /* @__PURE__ */ new Map();
|
|
118
|
+
options;
|
|
119
|
+
constructor(options = {}) {
|
|
120
|
+
super();
|
|
121
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
122
|
+
this.loadState();
|
|
123
|
+
}
|
|
124
|
+
// ==========================================================================
|
|
125
|
+
// Session Management
|
|
126
|
+
// ==========================================================================
|
|
127
|
+
/**
|
|
128
|
+
* Create a new workflow session
|
|
129
|
+
*
|
|
130
|
+
* @param params - Session parameters
|
|
131
|
+
* @param params.name - Session name
|
|
132
|
+
* @param params.description - Optional session description
|
|
133
|
+
* @param params.initialPhase - Optional initial workflow phase
|
|
134
|
+
* @param params.branch - Optional git branch name
|
|
135
|
+
* @param params.skills - Optional list of skill IDs to use
|
|
136
|
+
* @param params.metadata - Optional additional metadata
|
|
137
|
+
* @returns Created session
|
|
138
|
+
*/
|
|
139
|
+
createSession(params) {
|
|
140
|
+
const session = {
|
|
141
|
+
id: generateId(),
|
|
142
|
+
name: params.name,
|
|
143
|
+
description: params.description || "",
|
|
144
|
+
currentPhase: params.initialPhase || "brainstorming",
|
|
145
|
+
status: "active",
|
|
146
|
+
tasks: [],
|
|
147
|
+
phaseHistory: [{
|
|
148
|
+
from: null,
|
|
149
|
+
to: params.initialPhase || "brainstorming",
|
|
150
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
151
|
+
reason: "Session created",
|
|
152
|
+
triggeredBy: "system"
|
|
153
|
+
}],
|
|
154
|
+
branch: params.branch,
|
|
155
|
+
skills: params.skills || [],
|
|
156
|
+
metadata: params.metadata || {},
|
|
157
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
158
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
159
|
+
};
|
|
160
|
+
this.sessions.set(session.id, session);
|
|
161
|
+
this.emit("session:created", session);
|
|
162
|
+
this.log(`Created session: ${session.id} - ${session.name}`);
|
|
163
|
+
this.saveState();
|
|
164
|
+
return session;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get a session by ID
|
|
168
|
+
*/
|
|
169
|
+
getSession(id) {
|
|
170
|
+
return this.sessions.get(id) || null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get all sessions
|
|
174
|
+
*/
|
|
175
|
+
getAllSessions() {
|
|
176
|
+
return Array.from(this.sessions.values());
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get active sessions
|
|
180
|
+
*/
|
|
181
|
+
getActiveSessions() {
|
|
182
|
+
return this.getAllSessions().filter((s) => s.status === "active" || s.status === "paused");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Update session metadata
|
|
186
|
+
*/
|
|
187
|
+
updateSession(id, updates) {
|
|
188
|
+
const session = this.getSessionOrThrow(id);
|
|
189
|
+
Object.assign(session, updates, { updatedAt: /* @__PURE__ */ new Date() });
|
|
190
|
+
this.saveState();
|
|
191
|
+
return session;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Delete a session
|
|
195
|
+
*/
|
|
196
|
+
deleteSession(id) {
|
|
197
|
+
const session = this.sessions.get(id);
|
|
198
|
+
if (!session) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
this.sessions.delete(id);
|
|
202
|
+
this.log(`Deleted session: ${id}`);
|
|
203
|
+
this.saveState();
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
// ==========================================================================
|
|
207
|
+
// Phase Transitions
|
|
208
|
+
// ==========================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Transition to a new phase
|
|
211
|
+
*
|
|
212
|
+
* @param sessionId - Session ID
|
|
213
|
+
* @param targetPhase - Target phase
|
|
214
|
+
* @param reason - Reason for transition
|
|
215
|
+
* @returns Updated session
|
|
216
|
+
* @throws Error if transition is not allowed
|
|
217
|
+
*/
|
|
218
|
+
transitionTo(sessionId, targetPhase, reason) {
|
|
219
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
220
|
+
this.validateTransition(session, targetPhase);
|
|
221
|
+
const oldPhase = session.currentPhase;
|
|
222
|
+
const transition = {
|
|
223
|
+
from: oldPhase,
|
|
224
|
+
to: targetPhase,
|
|
225
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
226
|
+
reason: reason || `Transition from ${oldPhase} to ${targetPhase}`,
|
|
227
|
+
triggeredBy: "user"
|
|
228
|
+
};
|
|
229
|
+
session.currentPhase = targetPhase;
|
|
230
|
+
session.phaseHistory.push(transition);
|
|
231
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
232
|
+
this.emit("phase:changed", session, transition);
|
|
233
|
+
this.log(`Phase transition: ${oldPhase} -> ${targetPhase} (${session.name})`);
|
|
234
|
+
this.saveState();
|
|
235
|
+
return session;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Check if a transition is allowed
|
|
239
|
+
*/
|
|
240
|
+
canTransitionTo(sessionId, targetPhase) {
|
|
241
|
+
const session = this.sessions.get(sessionId);
|
|
242
|
+
if (!session) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
const currentConfig = PHASE_CONFIGS[session.currentPhase];
|
|
246
|
+
return currentConfig.allowedTransitions.includes(targetPhase);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get allowed transitions for a session
|
|
250
|
+
*/
|
|
251
|
+
getAllowedTransitions(sessionId) {
|
|
252
|
+
const session = this.sessions.get(sessionId);
|
|
253
|
+
if (!session) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
return PHASE_CONFIGS[session.currentPhase].allowedTransitions;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Auto-advance to next phase if conditions are met
|
|
260
|
+
*/
|
|
261
|
+
autoAdvance(sessionId) {
|
|
262
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
263
|
+
const currentConfig = PHASE_CONFIGS[session.currentPhase];
|
|
264
|
+
if (currentConfig.requiresConfirmation) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const phaseTasks = this.getTasksForPhase(session, session.currentPhase);
|
|
268
|
+
const allCompleted = phaseTasks.every((t) => t.status === "completed");
|
|
269
|
+
if (!allCompleted || phaseTasks.length === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const nextPhase = currentConfig.allowedTransitions[0];
|
|
273
|
+
if (!nextPhase) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return this.transitionTo(sessionId, nextPhase, "Auto-advanced after task completion");
|
|
277
|
+
}
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
// Session Status Management
|
|
280
|
+
// ==========================================================================
|
|
281
|
+
/**
|
|
282
|
+
* Pause a session
|
|
283
|
+
*/
|
|
284
|
+
pauseSession(sessionId) {
|
|
285
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
286
|
+
if (session.status !== "active") {
|
|
287
|
+
throw new Error(`Cannot pause session with status: ${session.status}`);
|
|
288
|
+
}
|
|
289
|
+
return this.changeSessionStatus(session, "paused");
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Resume a paused session
|
|
293
|
+
*/
|
|
294
|
+
resumeSession(sessionId) {
|
|
295
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
296
|
+
if (session.status !== "paused") {
|
|
297
|
+
throw new Error(`Cannot resume session with status: ${session.status}`);
|
|
298
|
+
}
|
|
299
|
+
return this.changeSessionStatus(session, "active");
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Complete a session
|
|
303
|
+
*/
|
|
304
|
+
completeSession(sessionId) {
|
|
305
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
306
|
+
if (session.status !== "active") {
|
|
307
|
+
throw new Error(`Cannot complete session with status: ${session.status}`);
|
|
308
|
+
}
|
|
309
|
+
session.completedAt = /* @__PURE__ */ new Date();
|
|
310
|
+
const updated = this.changeSessionStatus(session, "completed");
|
|
311
|
+
this.emit("workflow:completed", updated);
|
|
312
|
+
return updated;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Fail a session
|
|
316
|
+
*/
|
|
317
|
+
failSession(sessionId, error) {
|
|
318
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
319
|
+
session.error = error;
|
|
320
|
+
const updated = this.changeSessionStatus(session, "failed");
|
|
321
|
+
this.emit("workflow:failed", updated, error);
|
|
322
|
+
return updated;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Cancel a session
|
|
326
|
+
*/
|
|
327
|
+
cancelSession(sessionId) {
|
|
328
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
329
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
330
|
+
throw new Error(`Cannot cancel session with status: ${session.status}`);
|
|
331
|
+
}
|
|
332
|
+
return this.changeSessionStatus(session, "cancelled");
|
|
333
|
+
}
|
|
334
|
+
// ==========================================================================
|
|
335
|
+
// Task Management
|
|
336
|
+
// ==========================================================================
|
|
337
|
+
/**
|
|
338
|
+
* Add a task to a session
|
|
339
|
+
*/
|
|
340
|
+
addTask(sessionId, task) {
|
|
341
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
342
|
+
const newTask = {
|
|
343
|
+
...task,
|
|
344
|
+
id: `task-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
|
345
|
+
status: "pending",
|
|
346
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
347
|
+
};
|
|
348
|
+
session.tasks.push(newTask);
|
|
349
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
350
|
+
this.emit("task:created", session, newTask);
|
|
351
|
+
this.saveState();
|
|
352
|
+
return newTask;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Update task status
|
|
356
|
+
*/
|
|
357
|
+
updateTaskStatus(sessionId, taskId, status) {
|
|
358
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
359
|
+
const task = session.tasks.find((t) => t.id === taskId);
|
|
360
|
+
if (!task) {
|
|
361
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
362
|
+
}
|
|
363
|
+
const oldStatus = task.status;
|
|
364
|
+
task.status = status;
|
|
365
|
+
if (status === "running" && !task.startedAt) {
|
|
366
|
+
task.startedAt = /* @__PURE__ */ new Date();
|
|
367
|
+
}
|
|
368
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
369
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
370
|
+
if (task.startedAt) {
|
|
371
|
+
task.actualMinutes = Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / 6e4);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
375
|
+
this.emit("task:status", session, task, oldStatus, status);
|
|
376
|
+
if (status === "completed") {
|
|
377
|
+
this.emit("task:completed", session, task);
|
|
378
|
+
} else if (status === "failed") {
|
|
379
|
+
this.emit("task:failed", session, task, task.error || "Unknown error");
|
|
380
|
+
}
|
|
381
|
+
this.saveState();
|
|
382
|
+
return task;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get tasks for a specific phase
|
|
386
|
+
*/
|
|
387
|
+
getTasksForPhase(session, phase) {
|
|
388
|
+
return session.tasks.filter((t) => t.metadata?.phase === phase);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get pending tasks
|
|
392
|
+
*/
|
|
393
|
+
getPendingTasks(sessionId) {
|
|
394
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
395
|
+
return session.tasks.filter((t) => t.status === "pending" || t.status === "queued");
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get running tasks
|
|
399
|
+
*/
|
|
400
|
+
getRunningTasks(sessionId) {
|
|
401
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
402
|
+
return session.tasks.filter((t) => t.status === "running");
|
|
28
403
|
}
|
|
29
|
-
|
|
404
|
+
// ==========================================================================
|
|
405
|
+
// Persistence
|
|
406
|
+
// ==========================================================================
|
|
407
|
+
/**
|
|
408
|
+
* Save state to disk
|
|
409
|
+
*/
|
|
410
|
+
saveState() {
|
|
411
|
+
if (!this.options.autoSave) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const state = {
|
|
415
|
+
version: WORKFLOW_PERSISTENCE_VERSION,
|
|
416
|
+
sessions: Array.from(this.sessions.values()),
|
|
417
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
418
|
+
};
|
|
419
|
+
try {
|
|
420
|
+
const dir = dirname(this.options.persistPath);
|
|
421
|
+
if (!existsSync(dir)) {
|
|
422
|
+
mkdirSync(dir, { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
writeFileSync(
|
|
425
|
+
this.options.persistPath,
|
|
426
|
+
JSON.stringify(state, null, 2),
|
|
427
|
+
"utf-8"
|
|
428
|
+
);
|
|
429
|
+
this.log(`State saved to ${this.options.persistPath}`);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error("Failed to save workflow state:", error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Load state from disk
|
|
436
|
+
*/
|
|
437
|
+
loadState() {
|
|
30
438
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
439
|
+
if (!existsSync(this.options.persistPath)) {
|
|
440
|
+
this.log("No existing state file found");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const content = readFileSync(this.options.persistPath, "utf-8");
|
|
444
|
+
const state = JSON.parse(content);
|
|
445
|
+
if (state.version !== WORKFLOW_PERSISTENCE_VERSION) {
|
|
446
|
+
this.log(`Migrating state from version ${state.version} to ${WORKFLOW_PERSISTENCE_VERSION}`);
|
|
447
|
+
}
|
|
448
|
+
this.sessions.clear();
|
|
449
|
+
for (const session of state.sessions) {
|
|
450
|
+
session.createdAt = new Date(session.createdAt);
|
|
451
|
+
session.updatedAt = new Date(session.updatedAt);
|
|
452
|
+
if (session.completedAt) {
|
|
453
|
+
session.completedAt = new Date(session.completedAt);
|
|
454
|
+
}
|
|
455
|
+
for (const task of session.tasks) {
|
|
456
|
+
task.createdAt = new Date(task.createdAt);
|
|
457
|
+
if (task.startedAt)
|
|
458
|
+
task.startedAt = new Date(task.startedAt);
|
|
459
|
+
if (task.completedAt)
|
|
460
|
+
task.completedAt = new Date(task.completedAt);
|
|
46
461
|
}
|
|
462
|
+
for (const transition of session.phaseHistory) {
|
|
463
|
+
transition.timestamp = new Date(transition.timestamp);
|
|
464
|
+
}
|
|
465
|
+
this.sessions.set(session.id, session);
|
|
47
466
|
}
|
|
48
|
-
|
|
467
|
+
this.log(`Loaded ${this.sessions.size} sessions from state file`);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error("Failed to load workflow state:", error);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Clear all state
|
|
474
|
+
*/
|
|
475
|
+
clearState() {
|
|
476
|
+
this.sessions.clear();
|
|
477
|
+
this.saveState();
|
|
478
|
+
this.log("State cleared");
|
|
479
|
+
}
|
|
480
|
+
// ==========================================================================
|
|
481
|
+
// Statistics
|
|
482
|
+
// ==========================================================================
|
|
483
|
+
/**
|
|
484
|
+
* Get workflow statistics
|
|
485
|
+
*/
|
|
486
|
+
getStats() {
|
|
487
|
+
const sessions = this.getAllSessions();
|
|
488
|
+
const allTasks = sessions.flatMap((s) => s.tasks);
|
|
489
|
+
const completedTasks = allTasks.filter((t) => t.status === "completed" && t.actualMinutes);
|
|
490
|
+
const avgDuration = completedTasks.length > 0 ? completedTasks.reduce((sum, t) => sum + (t.actualMinutes || 0), 0) / completedTasks.length : 0;
|
|
491
|
+
return {
|
|
492
|
+
totalSessions: sessions.length,
|
|
493
|
+
activeSessions: sessions.filter((s) => s.status === "active").length,
|
|
494
|
+
completedSessions: sessions.filter((s) => s.status === "completed").length,
|
|
495
|
+
failedSessions: sessions.filter((s) => s.status === "failed").length,
|
|
496
|
+
totalTasks: allTasks.length,
|
|
497
|
+
completedTasks: completedTasks.length,
|
|
498
|
+
averageTaskDuration: Math.round(avgDuration * 10) / 10
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
// ==========================================================================
|
|
502
|
+
// Private Helpers
|
|
503
|
+
// ==========================================================================
|
|
504
|
+
getSessionOrThrow(id) {
|
|
505
|
+
const session = this.sessions.get(id);
|
|
506
|
+
if (!session) {
|
|
507
|
+
throw new Error(`Session not found: ${id}`);
|
|
508
|
+
}
|
|
509
|
+
return session;
|
|
510
|
+
}
|
|
511
|
+
validateTransition(session, targetPhase) {
|
|
512
|
+
if (session.status !== "active") {
|
|
513
|
+
throw new Error(`Cannot transition session with status: ${session.status}`);
|
|
49
514
|
}
|
|
515
|
+
const currentConfig = PHASE_CONFIGS[session.currentPhase];
|
|
516
|
+
if (!currentConfig.allowedTransitions.includes(targetPhase)) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Invalid transition: ${session.currentPhase} -> ${targetPhase}. Allowed: ${currentConfig.allowedTransitions.join(", ")}`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
changeSessionStatus(session, newStatus) {
|
|
523
|
+
const oldStatus = session.status;
|
|
524
|
+
session.status = newStatus;
|
|
525
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
526
|
+
this.emit("session:status", session, oldStatus, newStatus);
|
|
527
|
+
this.log(`Session status: ${oldStatus} -> ${newStatus} (${session.name})`);
|
|
528
|
+
this.saveState();
|
|
529
|
+
return session;
|
|
530
|
+
}
|
|
531
|
+
log(message) {
|
|
532
|
+
if (this.options.verbose) {
|
|
533
|
+
console.log(`[WorkflowStateMachine] ${message}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// ==========================================================================
|
|
537
|
+
// Type-safe Event Emitter
|
|
538
|
+
// ==========================================================================
|
|
539
|
+
on(event, listener) {
|
|
540
|
+
return super.on(event, listener);
|
|
541
|
+
}
|
|
542
|
+
emit(event, ...args) {
|
|
543
|
+
return super.emit(event, ...args);
|
|
544
|
+
}
|
|
545
|
+
once(event, listener) {
|
|
546
|
+
return super.once(event, listener);
|
|
547
|
+
}
|
|
548
|
+
off(event, listener) {
|
|
549
|
+
return super.off(event, listener);
|
|
50
550
|
}
|
|
51
|
-
scanDir(commandsDir);
|
|
52
|
-
return workflows;
|
|
53
551
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return ansis.bgGreen.black(` ${tag} `);
|
|
61
|
-
case "\u70ED\u95E8":
|
|
62
|
-
return ansis.bgYellow.black(` ${tag} `);
|
|
63
|
-
case "\u6838\u5FC3":
|
|
64
|
-
return ansis.bgBlue.white(` ${tag} `);
|
|
65
|
-
case "Git":
|
|
66
|
-
return ansis.bgMagenta.white(` ${tag} `);
|
|
67
|
-
default:
|
|
68
|
-
return ansis.bgGray.white(` ${tag} `);
|
|
69
|
-
}
|
|
70
|
-
}).join(" ");
|
|
552
|
+
let instance = null;
|
|
553
|
+
function getWorkflowStateMachine(options) {
|
|
554
|
+
if (!instance) {
|
|
555
|
+
instance = new WorkflowStateMachine(options);
|
|
556
|
+
}
|
|
557
|
+
return instance;
|
|
71
558
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
559
|
+
|
|
560
|
+
function listWorkflows() {
|
|
561
|
+
const machine = getWorkflowStateMachine();
|
|
562
|
+
return machine.getAllSessions();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const PHASE_ICONS = {
|
|
566
|
+
brainstorming: "\u{1F4A1}",
|
|
567
|
+
planning: "\u{1F4CB}",
|
|
568
|
+
implementation: "\u{1F528}",
|
|
569
|
+
review: "\u{1F50D}",
|
|
570
|
+
finishing: "\u2705"
|
|
571
|
+
};
|
|
572
|
+
const PHASE_COLORS = {
|
|
573
|
+
brainstorming: ansis.magenta,
|
|
574
|
+
planning: ansis.blue,
|
|
575
|
+
implementation: ansis.yellow,
|
|
576
|
+
review: ansis.cyan,
|
|
577
|
+
finishing: ansis.green
|
|
578
|
+
};
|
|
579
|
+
const STATUS_ICONS = {
|
|
580
|
+
active: "\u{1F504}",
|
|
581
|
+
paused: "\u23F8\uFE0F",
|
|
582
|
+
completed: "\u2705",
|
|
583
|
+
failed: "\u274C",
|
|
584
|
+
cancelled: "\u{1F6AB}"
|
|
585
|
+
};
|
|
586
|
+
function formatPhase(phase) {
|
|
587
|
+
const icon = PHASE_ICONS[phase];
|
|
588
|
+
const color = PHASE_COLORS[phase];
|
|
589
|
+
return `${icon} ${color(phase.charAt(0).toUpperCase() + phase.slice(1))}`;
|
|
590
|
+
}
|
|
591
|
+
function getTaskCounts(session) {
|
|
592
|
+
const tasks = session.tasks || [];
|
|
593
|
+
return {
|
|
594
|
+
completed: tasks.filter((t) => t.status === "completed").length,
|
|
595
|
+
failed: tasks.filter((t) => t.status === "failed").length,
|
|
596
|
+
total: tasks.length
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async function listAllWorkflows(options = {}) {
|
|
600
|
+
const workflows = listWorkflows();
|
|
601
|
+
if (options.format === "json") {
|
|
602
|
+
console.log(JSON.stringify(workflows, null, 2));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
78
605
|
if (workflows.length === 0) {
|
|
79
|
-
console.log(ansis.
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
console.log(ansis.bold.cyan("\u2502") + ansis.bold.green(` \u2705 \u5DF2\u5B89\u88C5\u5DE5\u4F5C\u6D41 (${workflows.length})`) + " ".repeat(42 - String(workflows.length).length) + ansis.bold.cyan("\u2502"));
|
|
83
|
-
console.log(`${ansis.bold.cyan("\u2502")} ${ansis.bold.cyan("\u2502")}`);
|
|
84
|
-
for (const wf of workflows) {
|
|
85
|
-
const tags = formatTags(wf.tags);
|
|
86
|
-
const desc = wf.description || "";
|
|
87
|
-
const nameCol = ansis.green(wf.name.padEnd(20));
|
|
88
|
-
const descCol = ansis.dim(desc.padEnd(25));
|
|
89
|
-
console.log(ansis.bold.cyan("\u2502") + ` ${nameCol} ${descCol} ${tags}`.padEnd(60) + ansis.bold.cyan("\u2502"));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
console.log(`${ansis.bold.cyan("\u2502")} ${ansis.bold.cyan("\u2502")}`);
|
|
93
|
-
console.log(ansis.bold.cyan("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
94
|
-
console.log(ansis.bold.cyan("\u2502") + ansis.dim(" \u{1F4A1} \u5728 Claude Code \u4E2D\u8F93\u5165\u5DE5\u4F5C\u6D41\u540D\u79F0\u5373\u53EF\u4F7F\u7528 ") + ansis.bold.cyan("\u2502"));
|
|
95
|
-
console.log(ansis.bold.cyan("\u2502") + ansis.dim(" \u4F8B\u5982: /ccjk:workflow \u5B9E\u73B0\u7528\u6237\u767B\u5F55\u529F\u80FD ") + ansis.bold.cyan("\u2502"));
|
|
96
|
-
console.log(ansis.bold.cyan("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
606
|
+
console.log(ansis.yellow("\n \u26A0\uFE0F No workflows found\n"));
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
97
609
|
console.log("");
|
|
610
|
+
console.log(ansis.bold.cyan("\u2501".repeat(80)));
|
|
611
|
+
console.log(ansis.bold(` ${"ID".padEnd(10)} ${"Name".padEnd(20)} ${"Phase".padEnd(15)} ${"Status".padEnd(10)} ${"Progress".padEnd(15)}`));
|
|
612
|
+
console.log(ansis.bold.cyan("\u2501".repeat(80)));
|
|
613
|
+
for (const wf of workflows) {
|
|
614
|
+
const counts = getTaskCounts(wf);
|
|
615
|
+
const progress = `${counts.completed}/${counts.total}`;
|
|
616
|
+
console.log(
|
|
617
|
+
` ${ansis.dim(wf.id.slice(0, 8).padEnd(10))} ${wf.name.slice(0, 18).padEnd(20)} ${formatPhase(wf.currentPhase).padEnd(25)} ${STATUS_ICONS[wf.status]} ${wf.status.padEnd(8)} ${progress}`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
console.log(ansis.bold.cyan("\u2501".repeat(80)));
|
|
621
|
+
console.log(ansis.dim(` Total: ${workflows.length} workflows`));
|
|
622
|
+
console.log("");
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function listWorkflowsQuick(options = {}) {
|
|
626
|
+
const opts = {
|
|
627
|
+
lang: options.lang,
|
|
628
|
+
format: options.format
|
|
629
|
+
};
|
|
630
|
+
await listAllWorkflows(opts);
|
|
98
631
|
}
|
|
99
632
|
|
|
100
|
-
export {
|
|
633
|
+
export { listWorkflowsQuick };
|