agileflow 2.90.7 → 2.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +818 -0
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate-names.js +3 -3
- package/lib/validate.js +116 -52
- package/package.json +4 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +118 -1048
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +36 -11
- package/scripts/query-codebase.js +538 -0
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +180 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +101 -0
- package/scripts/tui/blessed/panels/sessions.js +150 -0
- package/scripts/tui/blessed/panels/trace.js +97 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +47 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +239 -0
- package/scripts/validators/json-schema-validator.js +186 -0
- package/scripts/validators/markdown-validator.js +152 -0
- package/scripts/validators/migration-validator.js +129 -0
- package/scripts/validators/security-validator.js +380 -0
- package/scripts/validators/story-format-validator.js +197 -0
- package/scripts/validators/test-result-validator.js +114 -0
- package/scripts/validators/workflow-validator.js +247 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +261 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +205 -1
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +76 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +200 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- package/tools/cli/lib/ui.js +14 -25
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session State Machine
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe state transitions for AgileFlow sessions.
|
|
5
|
+
* Enforces valid state changes and emits events on transitions.
|
|
6
|
+
*
|
|
7
|
+
* States:
|
|
8
|
+
* - idle: Session created but not started
|
|
9
|
+
* - active: Session is running
|
|
10
|
+
* - paused: Session temporarily suspended
|
|
11
|
+
* - terminated: Session has ended
|
|
12
|
+
*
|
|
13
|
+
* Valid Transitions:
|
|
14
|
+
* - idle → active (start)
|
|
15
|
+
* - active → paused (pause)
|
|
16
|
+
* - active → terminated (stop)
|
|
17
|
+
* - paused → active (resume)
|
|
18
|
+
* - paused → terminated (stop)
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* const { SessionStateMachine, SessionState } = require('./session-state-machine');
|
|
22
|
+
*
|
|
23
|
+
* const sm = new SessionStateMachine('idle');
|
|
24
|
+
* sm.on('transition', ({ from, to, action }) => {
|
|
25
|
+
* console.log(`Transition: ${from} -[${action}]-> ${to}`);
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* sm.canTransition('start'); // true
|
|
29
|
+
* sm.transition('start'); // State is now 'active'
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const EventEmitter = require('events');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Valid session states
|
|
38
|
+
* @enum {string}
|
|
39
|
+
*/
|
|
40
|
+
const SessionState = Object.freeze({
|
|
41
|
+
IDLE: 'idle',
|
|
42
|
+
ACTIVE: 'active',
|
|
43
|
+
PAUSED: 'paused',
|
|
44
|
+
TERMINATED: 'terminated',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Valid state transition actions
|
|
49
|
+
* @enum {string}
|
|
50
|
+
*/
|
|
51
|
+
const SessionAction = Object.freeze({
|
|
52
|
+
START: 'start',
|
|
53
|
+
PAUSE: 'pause',
|
|
54
|
+
RESUME: 'resume',
|
|
55
|
+
STOP: 'stop',
|
|
56
|
+
RESTART: 'restart',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* State transition table
|
|
61
|
+
* Maps current state → action → next state
|
|
62
|
+
*/
|
|
63
|
+
const TRANSITIONS = Object.freeze({
|
|
64
|
+
[SessionState.IDLE]: {
|
|
65
|
+
[SessionAction.START]: SessionState.ACTIVE,
|
|
66
|
+
},
|
|
67
|
+
[SessionState.ACTIVE]: {
|
|
68
|
+
[SessionAction.PAUSE]: SessionState.PAUSED,
|
|
69
|
+
[SessionAction.STOP]: SessionState.TERMINATED,
|
|
70
|
+
[SessionAction.RESTART]: SessionState.ACTIVE, // Self-transition for restart
|
|
71
|
+
},
|
|
72
|
+
[SessionState.PAUSED]: {
|
|
73
|
+
[SessionAction.RESUME]: SessionState.ACTIVE,
|
|
74
|
+
[SessionAction.STOP]: SessionState.TERMINATED,
|
|
75
|
+
},
|
|
76
|
+
[SessionState.TERMINATED]: {
|
|
77
|
+
// No transitions out of terminated (final state)
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all valid states
|
|
83
|
+
* @returns {string[]}
|
|
84
|
+
*/
|
|
85
|
+
function getValidStates() {
|
|
86
|
+
return Object.values(SessionState);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get all valid actions
|
|
91
|
+
* @returns {string[]}
|
|
92
|
+
*/
|
|
93
|
+
function getValidActions() {
|
|
94
|
+
return Object.values(SessionAction);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a state is valid
|
|
99
|
+
* @param {string} state
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
function isValidState(state) {
|
|
103
|
+
return getValidStates().includes(state);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if an action is valid
|
|
108
|
+
* @param {string} action
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
function isValidAction(action) {
|
|
112
|
+
return getValidActions().includes(action);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get valid transitions from a state
|
|
117
|
+
* @param {string} state - Current state
|
|
118
|
+
* @returns {Object} Map of action → next state
|
|
119
|
+
*/
|
|
120
|
+
function getTransitionsFromState(state) {
|
|
121
|
+
return TRANSITIONS[state] || {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get available actions from a state
|
|
126
|
+
* @param {string} state - Current state
|
|
127
|
+
* @returns {string[]} Available actions
|
|
128
|
+
*/
|
|
129
|
+
function getAvailableActions(state) {
|
|
130
|
+
return Object.keys(getTransitionsFromState(state));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if a transition is valid
|
|
135
|
+
* @param {string} currentState - Current state
|
|
136
|
+
* @param {string} action - Action to perform
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
function canTransition(currentState, action) {
|
|
140
|
+
const transitions = TRANSITIONS[currentState];
|
|
141
|
+
return !!(transitions && transitions[action]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the next state for a transition
|
|
146
|
+
* @param {string} currentState - Current state
|
|
147
|
+
* @param {string} action - Action to perform
|
|
148
|
+
* @returns {string|null} Next state or null if invalid
|
|
149
|
+
*/
|
|
150
|
+
function getNextState(currentState, action) {
|
|
151
|
+
const transitions = TRANSITIONS[currentState];
|
|
152
|
+
return (transitions && transitions[action]) || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Session state machine error
|
|
157
|
+
*/
|
|
158
|
+
class StateMachineError extends Error {
|
|
159
|
+
constructor(message, details = {}) {
|
|
160
|
+
super(message);
|
|
161
|
+
this.name = 'StateMachineError';
|
|
162
|
+
this.currentState = details.currentState;
|
|
163
|
+
this.action = details.action;
|
|
164
|
+
this.code = details.code || 'INVALID_TRANSITION';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Session State Machine
|
|
170
|
+
* @extends EventEmitter
|
|
171
|
+
*
|
|
172
|
+
* Events:
|
|
173
|
+
* - 'transition': { from, to, action, timestamp }
|
|
174
|
+
* - 'invalidTransition': { currentState, action, error }
|
|
175
|
+
* - 'stateChange': { state }
|
|
176
|
+
*/
|
|
177
|
+
class SessionStateMachine extends EventEmitter {
|
|
178
|
+
/**
|
|
179
|
+
* @param {string} [initialState='idle'] - Initial state
|
|
180
|
+
* @param {Object} [options={}] - Options
|
|
181
|
+
* @param {boolean} [options.strict=true] - Throw on invalid transitions
|
|
182
|
+
* @param {boolean} [options.historyEnabled=false] - Track transition history
|
|
183
|
+
*/
|
|
184
|
+
constructor(initialState = SessionState.IDLE, options = {}) {
|
|
185
|
+
super();
|
|
186
|
+
|
|
187
|
+
// Validate initial state
|
|
188
|
+
if (!isValidState(initialState)) {
|
|
189
|
+
throw new StateMachineError(`Invalid initial state: ${initialState}`, {
|
|
190
|
+
code: 'INVALID_STATE',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this._state = initialState;
|
|
195
|
+
this._strict = options.strict !== false;
|
|
196
|
+
this._historyEnabled = options.historyEnabled || false;
|
|
197
|
+
this._history = this._historyEnabled ? [{ state: initialState, timestamp: new Date() }] : [];
|
|
198
|
+
this._metadata = {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get current state
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
get state() {
|
|
206
|
+
return this._state;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get transition history (if enabled)
|
|
211
|
+
* @returns {Array<{state: string, action?: string, timestamp: Date}>}
|
|
212
|
+
*/
|
|
213
|
+
get history() {
|
|
214
|
+
return [...this._history];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get metadata
|
|
219
|
+
* @returns {Object}
|
|
220
|
+
*/
|
|
221
|
+
get metadata() {
|
|
222
|
+
return { ...this._metadata };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Set metadata
|
|
227
|
+
* @param {Object} meta - Metadata to merge
|
|
228
|
+
*/
|
|
229
|
+
setMetadata(meta) {
|
|
230
|
+
this._metadata = { ...this._metadata, ...meta };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if state machine is in a final state
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
isFinal() {
|
|
238
|
+
return this._state === SessionState.TERMINATED;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if a transition can be performed
|
|
243
|
+
* @param {string} action - Action to check
|
|
244
|
+
* @returns {boolean}
|
|
245
|
+
*/
|
|
246
|
+
canTransition(action) {
|
|
247
|
+
return canTransition(this._state, action);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get available actions from current state
|
|
252
|
+
* @returns {string[]}
|
|
253
|
+
*/
|
|
254
|
+
getAvailableActions() {
|
|
255
|
+
return getAvailableActions(this._state);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Perform a transition
|
|
260
|
+
* @param {string} action - Action to perform
|
|
261
|
+
* @returns {{ok: boolean, from?: string, to?: string, error?: Error}}
|
|
262
|
+
*/
|
|
263
|
+
transition(action) {
|
|
264
|
+
const from = this._state;
|
|
265
|
+
|
|
266
|
+
// Check if action is valid
|
|
267
|
+
if (!isValidAction(action)) {
|
|
268
|
+
const error = new StateMachineError(`Invalid action: ${action}`, {
|
|
269
|
+
currentState: from,
|
|
270
|
+
action,
|
|
271
|
+
code: 'INVALID_ACTION',
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
this.emit('invalidTransition', { currentState: from, action, error });
|
|
275
|
+
|
|
276
|
+
if (this._strict) {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
return { ok: false, error };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check if transition is valid
|
|
283
|
+
const to = getNextState(from, action);
|
|
284
|
+
|
|
285
|
+
if (!to) {
|
|
286
|
+
const error = new StateMachineError(
|
|
287
|
+
`Cannot perform '${action}' from state '${from}'. Available actions: ${this.getAvailableActions().join(', ') || 'none'}`,
|
|
288
|
+
{
|
|
289
|
+
currentState: from,
|
|
290
|
+
action,
|
|
291
|
+
code: 'INVALID_TRANSITION',
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
this.emit('invalidTransition', { currentState: from, action, error });
|
|
296
|
+
|
|
297
|
+
if (this._strict) {
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
return { ok: false, error };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Perform transition
|
|
304
|
+
this._state = to;
|
|
305
|
+
|
|
306
|
+
const timestamp = new Date();
|
|
307
|
+
|
|
308
|
+
if (this._historyEnabled) {
|
|
309
|
+
this._history.push({ state: to, action, from, timestamp });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Emit events
|
|
313
|
+
this.emit('transition', { from, to, action, timestamp });
|
|
314
|
+
this.emit('stateChange', { state: to });
|
|
315
|
+
|
|
316
|
+
return { ok: true, from, to };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Attempt transition without throwing on failure
|
|
321
|
+
* @param {string} action - Action to perform
|
|
322
|
+
* @returns {boolean} True if transition succeeded
|
|
323
|
+
*/
|
|
324
|
+
tryTransition(action) {
|
|
325
|
+
try {
|
|
326
|
+
const result = this.transition(action);
|
|
327
|
+
return result.ok;
|
|
328
|
+
} catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Reset to initial state
|
|
335
|
+
* @param {string} [state='idle'] - State to reset to
|
|
336
|
+
*/
|
|
337
|
+
reset(state = SessionState.IDLE) {
|
|
338
|
+
if (!isValidState(state)) {
|
|
339
|
+
throw new StateMachineError(`Invalid state: ${state}`, {
|
|
340
|
+
code: 'INVALID_STATE',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const from = this._state;
|
|
345
|
+
this._state = state;
|
|
346
|
+
|
|
347
|
+
if (this._historyEnabled) {
|
|
348
|
+
this._history.push({ state, action: 'reset', from, timestamp: new Date() });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.emit('transition', { from, to: state, action: 'reset', timestamp: new Date() });
|
|
352
|
+
this.emit('stateChange', { state });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Check if state matches
|
|
357
|
+
* @param {string} state - State to check
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
is(state) {
|
|
361
|
+
return this._state === state;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Check if state is one of given states
|
|
366
|
+
* @param {...string} states - States to check
|
|
367
|
+
* @returns {boolean}
|
|
368
|
+
*/
|
|
369
|
+
isOneOf(...states) {
|
|
370
|
+
return states.includes(this._state);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Serialize state machine
|
|
375
|
+
* @returns {Object}
|
|
376
|
+
*/
|
|
377
|
+
serialize() {
|
|
378
|
+
return {
|
|
379
|
+
state: this._state,
|
|
380
|
+
history: this._historyEnabled ? this._history : undefined,
|
|
381
|
+
metadata: Object.keys(this._metadata).length > 0 ? this._metadata : undefined,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Deserialize state machine
|
|
387
|
+
* @param {Object} data - Serialized data
|
|
388
|
+
* @param {Object} [options={}] - Options
|
|
389
|
+
* @returns {SessionStateMachine}
|
|
390
|
+
*/
|
|
391
|
+
static deserialize(data, options = {}) {
|
|
392
|
+
const sm = new SessionStateMachine(data.state, {
|
|
393
|
+
...options,
|
|
394
|
+
historyEnabled: !!data.history,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (data.history) {
|
|
398
|
+
sm._history = data.history.map(h => ({
|
|
399
|
+
...h,
|
|
400
|
+
timestamp: new Date(h.timestamp),
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (data.metadata) {
|
|
405
|
+
sm._metadata = data.metadata;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return sm;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Create state machine diagram (Mermaid format)
|
|
413
|
+
* @returns {string}
|
|
414
|
+
*/
|
|
415
|
+
static toMermaid() {
|
|
416
|
+
const lines = ['stateDiagram-v2'];
|
|
417
|
+
|
|
418
|
+
for (const [state, transitions] of Object.entries(TRANSITIONS)) {
|
|
419
|
+
for (const [action, nextState] of Object.entries(transitions)) {
|
|
420
|
+
lines.push(` ${state} --> ${nextState}: ${action}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Mark initial and final states
|
|
425
|
+
lines.push(' [*] --> idle');
|
|
426
|
+
lines.push(' terminated --> [*]');
|
|
427
|
+
|
|
428
|
+
return lines.join('\n');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Create a pre-configured state machine for sessions
|
|
434
|
+
* @param {Object} [options={}] - Options
|
|
435
|
+
* @returns {SessionStateMachine}
|
|
436
|
+
*/
|
|
437
|
+
function createSessionStateMachine(options = {}) {
|
|
438
|
+
return new SessionStateMachine(SessionState.IDLE, options);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = {
|
|
442
|
+
// Enums
|
|
443
|
+
SessionState,
|
|
444
|
+
SessionAction,
|
|
445
|
+
|
|
446
|
+
// Constants
|
|
447
|
+
TRANSITIONS,
|
|
448
|
+
|
|
449
|
+
// Utility functions
|
|
450
|
+
getValidStates,
|
|
451
|
+
getValidActions,
|
|
452
|
+
isValidState,
|
|
453
|
+
isValidAction,
|
|
454
|
+
getTransitionsFromState,
|
|
455
|
+
getAvailableActions,
|
|
456
|
+
canTransition,
|
|
457
|
+
getNextState,
|
|
458
|
+
|
|
459
|
+
// Classes
|
|
460
|
+
StateMachineError,
|
|
461
|
+
SessionStateMachine,
|
|
462
|
+
|
|
463
|
+
// Factory
|
|
464
|
+
createSessionStateMachine,
|
|
465
|
+
};
|