agileflow 2.91.0 → 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.
Files changed (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +31 -23
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +435 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +43 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +122 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +220 -42
  48. package/scripts/spawn-parallel.js +651 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +113 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +86 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/lib/config-manager.js +17 -2
  94. package/tools/cli/lib/content-transformer.js +271 -0
  95. package/tools/cli/lib/error-handler.js +14 -22
  96. package/tools/cli/lib/ide-error-factory.js +421 -0
  97. package/tools/cli/lib/ide-health-monitor.js +364 -0
  98. package/tools/cli/lib/ide-registry.js +114 -1
  99. package/tools/cli/lib/ui.js +14 -25
package/lib/result.js ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * result.js - Unified Result Schema for AgileFlow
3
+ *
4
+ * Provides consistent result objects across modules with type helpers.
5
+ *
6
+ * Standard Result Schema:
7
+ * { ok: boolean, data?: any, error?: string|Error }
8
+ *
9
+ * Extended fields (context-specific):
10
+ * - found: boolean (for lookup operations)
11
+ * - applied: number (for batch operations)
12
+ * - cleaned: number (for cleanup operations)
13
+ * - path: string (for file operations)
14
+ * - status: string (for state operations)
15
+ *
16
+ * Usage:
17
+ * const { ok, err, Result } = require('./result');
18
+ *
19
+ * // Success
20
+ * return ok({ path: '/saved/file.json' });
21
+ *
22
+ * // Failure
23
+ * return err('File not found');
24
+ *
25
+ * // With data
26
+ * return ok({ data: parsedConfig });
27
+ *
28
+ * // Type checking
29
+ * if (Result.isOk(result)) { ... }
30
+ */
31
+
32
+ /**
33
+ * Create a success result
34
+ *
35
+ * @param {Object} [extras={}] - Additional fields to include
36
+ * @returns {{ ok: true } & Object} Success result
37
+ *
38
+ * @example
39
+ * ok() // { ok: true }
40
+ * ok({ data: config }) // { ok: true, data: config }
41
+ * ok({ path: '/file.json', created: true }) // { ok: true, path: '/file.json', created: true }
42
+ */
43
+ function ok(extras = {}) {
44
+ return { ok: true, ...extras };
45
+ }
46
+
47
+ /**
48
+ * Create a failure result
49
+ *
50
+ * @param {string|Error} error - Error message or Error object
51
+ * @param {Object} [extras={}] - Additional fields to include
52
+ * @returns {{ ok: false, error: string } & Object} Failure result
53
+ *
54
+ * @example
55
+ * err('Not found') // { ok: false, error: 'Not found' }
56
+ * err(new Error('Failed')) // { ok: false, error: 'Failed' }
57
+ * err('Invalid', { code: 'EINVAL' }) // { ok: false, error: 'Invalid', code: 'EINVAL' }
58
+ */
59
+ function err(error, extras = {}) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ return { ok: false, error: message, ...extras };
62
+ }
63
+
64
+ /**
65
+ * Result utilities for type checking and manipulation
66
+ */
67
+ const Result = {
68
+ /**
69
+ * Check if result is success
70
+ * @param {Object} result - Result to check
71
+ * @returns {boolean}
72
+ */
73
+ isOk(result) {
74
+ return Boolean(result && result.ok === true);
75
+ },
76
+
77
+ /**
78
+ * Check if result is failure
79
+ * @param {Object} result - Result to check
80
+ * @returns {boolean}
81
+ */
82
+ isErr(result) {
83
+ return Boolean(result && result.ok === false);
84
+ },
85
+
86
+ /**
87
+ * Unwrap result data or throw on error
88
+ * @param {Object} result - Result to unwrap
89
+ * @param {string} [context] - Context for error message
90
+ * @returns {any} The data field or the result without ok field
91
+ * @throws {Error} If result is not ok
92
+ */
93
+ unwrap(result, context = '') {
94
+ if (!Result.isOk(result)) {
95
+ const prefix = context ? `${context}: ` : '';
96
+ throw new Error(`${prefix}${result.error || 'Unknown error'}`);
97
+ }
98
+ return result.data !== undefined ? result.data : result;
99
+ },
100
+
101
+ /**
102
+ * Unwrap result data or return default value
103
+ * @param {Object} result - Result to unwrap
104
+ * @param {any} defaultValue - Value to return on error
105
+ * @returns {any}
106
+ */
107
+ unwrapOr(result, defaultValue) {
108
+ if (!Result.isOk(result)) {
109
+ return defaultValue;
110
+ }
111
+ return result.data !== undefined ? result.data : result;
112
+ },
113
+
114
+ /**
115
+ * Map over successful result
116
+ * @param {Object} result - Result to map
117
+ * @param {Function} fn - Function to apply to data
118
+ * @returns {Object} Mapped result or original error
119
+ */
120
+ map(result, fn) {
121
+ if (!Result.isOk(result)) {
122
+ return result;
123
+ }
124
+ try {
125
+ const data = result.data !== undefined ? result.data : result;
126
+ const mapped = fn(data);
127
+ return ok({ data: mapped });
128
+ } catch (e) {
129
+ return err(e);
130
+ }
131
+ },
132
+
133
+ /**
134
+ * Convert legacy { success: true/false } to standard { ok: true/false }
135
+ * @param {Object} legacyResult - Legacy result object
136
+ * @returns {Object} Standardized result
137
+ */
138
+ fromLegacy(legacyResult) {
139
+ if (legacyResult.success !== undefined) {
140
+ const { success, ...rest } = legacyResult;
141
+ return { ok: success, ...rest };
142
+ }
143
+ return legacyResult;
144
+ },
145
+
146
+ /**
147
+ * Convert standard result to legacy format for backwards compatibility
148
+ * @param {Object} result - Standard result object
149
+ * @returns {Object} Legacy result with success field
150
+ */
151
+ toLegacy(result) {
152
+ if (result.ok !== undefined) {
153
+ const { ok: isOk, ...rest } = result;
154
+ return { success: isOk, ...rest };
155
+ }
156
+ return result;
157
+ },
158
+ };
159
+
160
+ /**
161
+ * Async result helpers
162
+ */
163
+ const AsyncResult = {
164
+ /**
165
+ * Wrap an async function to return Result
166
+ * @param {Function} fn - Async function to wrap
167
+ * @returns {Function} Wrapped function returning Result
168
+ *
169
+ * @example
170
+ * const safeRead = AsyncResult.wrap(fs.promises.readFile);
171
+ * const result = await safeRead('file.txt');
172
+ * if (Result.isOk(result)) { console.log(result.data); }
173
+ */
174
+ wrap(fn) {
175
+ return async (...args) => {
176
+ try {
177
+ const data = await fn(...args);
178
+ return ok({ data });
179
+ } catch (e) {
180
+ return err(e);
181
+ }
182
+ };
183
+ },
184
+
185
+ /**
186
+ * Execute multiple async operations and collect results
187
+ * @param {Array<Promise>} promises - Promises to execute
188
+ * @returns {Promise<Object>} Result with all settled results
189
+ */
190
+ async all(promises) {
191
+ try {
192
+ const results = await Promise.all(promises);
193
+ const allOk = results.every(r => Result.isOk(r));
194
+ if (allOk) {
195
+ return ok({ data: results });
196
+ }
197
+ const errors = results.filter(r => Result.isErr(r)).map(r => r.error);
198
+ return err(errors.join('; '), { partial: results });
199
+ } catch (e) {
200
+ return err(e);
201
+ }
202
+ },
203
+ };
204
+
205
+ module.exports = {
206
+ ok,
207
+ err,
208
+ Result,
209
+ AsyncResult,
210
+ };
@@ -155,6 +155,11 @@ class SessionRegistry extends EventEmitter {
155
155
  * @returns {Object} Registry data
156
156
  */
157
157
  loadSync() {
158
+ // Return cached data if within TTL (performance optimization)
159
+ if (this._cache && Date.now() - this._cacheTime < this.cacheTTL) {
160
+ return this._cache;
161
+ }
162
+
158
163
  this._ensureDir();
159
164
  const result = this._jsonFile.readSync();
160
165
 
@@ -167,6 +172,14 @@ class SessionRegistry extends EventEmitter {
167
172
  return this._createDefaultRegistry();
168
173
  }
169
174
 
175
+ /**
176
+ * Invalidate cache (call after external modifications)
177
+ */
178
+ invalidateCache() {
179
+ this._cache = null;
180
+ this._cacheTime = 0;
181
+ }
182
+
170
183
  /**
171
184
  * Save registry
172
185
  * @param {Object} registry - Registry data to save
@@ -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
+ };