@scenetest/scenes 0.1.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/devices.test.d.ts +2 -0
  3. package/dist/__tests__/devices.test.d.ts.map +1 -0
  4. package/dist/__tests__/devices.test.js +117 -0
  5. package/dist/__tests__/devices.test.js.map +1 -0
  6. package/dist/__tests__/dsl.test.d.ts +2 -0
  7. package/dist/__tests__/dsl.test.d.ts.map +1 -0
  8. package/dist/__tests__/dsl.test.js +385 -0
  9. package/dist/__tests__/dsl.test.js.map +1 -0
  10. package/dist/__tests__/markdown-scene.test.d.ts +2 -0
  11. package/dist/__tests__/markdown-scene.test.d.ts.map +1 -0
  12. package/dist/__tests__/markdown-scene.test.js +508 -0
  13. package/dist/__tests__/markdown-scene.test.js.map +1 -0
  14. package/dist/__tests__/reactive.test.d.ts +2 -0
  15. package/dist/__tests__/reactive.test.d.ts.map +1 -0
  16. package/dist/__tests__/reactive.test.js +383 -0
  17. package/dist/__tests__/reactive.test.js.map +1 -0
  18. package/dist/__tests__/swarm.test.d.ts +2 -0
  19. package/dist/__tests__/swarm.test.d.ts.map +1 -0
  20. package/dist/__tests__/swarm.test.js +214 -0
  21. package/dist/__tests__/swarm.test.js.map +1 -0
  22. package/dist/actor.d.ts +104 -0
  23. package/dist/actor.d.ts.map +1 -0
  24. package/dist/actor.js +527 -0
  25. package/dist/actor.js.map +1 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +273 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/config.d.ts +21 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +120 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/devices.d.ts +55 -0
  35. package/dist/devices.d.ts.map +1 -0
  36. package/dist/devices.js +167 -0
  37. package/dist/devices.js.map +1 -0
  38. package/dist/dsl.d.ts +99 -0
  39. package/dist/dsl.d.ts.map +1 -0
  40. package/dist/dsl.js +247 -0
  41. package/dist/dsl.js.map +1 -0
  42. package/dist/index.d.ts +13 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +16 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/init.d.ts +9 -0
  47. package/dist/init.d.ts.map +1 -0
  48. package/dist/init.js +27 -0
  49. package/dist/init.js.map +1 -0
  50. package/dist/loader.d.ts +2 -0
  51. package/dist/loader.d.ts.map +1 -0
  52. package/dist/loader.js +10 -0
  53. package/dist/loader.js.map +1 -0
  54. package/dist/markdown-scene.d.ts +120 -0
  55. package/dist/markdown-scene.d.ts.map +1 -0
  56. package/dist/markdown-scene.js +452 -0
  57. package/dist/markdown-scene.js.map +1 -0
  58. package/dist/message-bus.d.ts +31 -0
  59. package/dist/message-bus.d.ts.map +1 -0
  60. package/dist/message-bus.js +74 -0
  61. package/dist/message-bus.js.map +1 -0
  62. package/dist/reactive.d.ts +267 -0
  63. package/dist/reactive.d.ts.map +1 -0
  64. package/dist/reactive.js +779 -0
  65. package/dist/reactive.js.map +1 -0
  66. package/dist/runner.d.ts +51 -0
  67. package/dist/runner.d.ts.map +1 -0
  68. package/dist/runner.js +306 -0
  69. package/dist/runner.js.map +1 -0
  70. package/dist/scene.d.ts +40 -0
  71. package/dist/scene.d.ts.map +1 -0
  72. package/dist/scene.js +110 -0
  73. package/dist/scene.js.map +1 -0
  74. package/dist/selectors.d.ts +57 -0
  75. package/dist/selectors.d.ts.map +1 -0
  76. package/dist/selectors.js +193 -0
  77. package/dist/selectors.js.map +1 -0
  78. package/dist/swarm.d.ts +64 -0
  79. package/dist/swarm.d.ts.map +1 -0
  80. package/dist/swarm.js +306 -0
  81. package/dist/swarm.js.map +1 -0
  82. package/dist/team-manager.d.ts +120 -0
  83. package/dist/team-manager.d.ts.map +1 -0
  84. package/dist/team-manager.js +267 -0
  85. package/dist/team-manager.js.map +1 -0
  86. package/dist/types.d.ts +653 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +2 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +61 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Markdown scene parser and loader.
3
+ *
4
+ * Parses `.spec.md` files into reactive flow registrations. The format is
5
+ * natural markdown — human-readable, GitHub-renderable, and executable:
6
+ *
7
+ * ```markdown
8
+ * # User friend requests
9
+ * ## new user signs up and gets a friend request
10
+ * new-user:
11
+ * - openTo /
12
+ * - see welcome-box
13
+ * - click continue-button
14
+ *
15
+ * primary-user:
16
+ * - openTo /friends
17
+ * - click main-navbar search
18
+ * - typeInto search-input [new-user.username]
19
+ * - see search-results-section
20
+ * - click friend-request-button
21
+ *
22
+ * new-user:
23
+ * - seeToast friend-request
24
+ * - see navbar notifications-badge
25
+ * - click
26
+ * - see notifications-menu-expanded new-friend-request
27
+ * - click
28
+ *
29
+ * ## old user re-activates account
30
+ * returning-user:
31
+ * - openTo /login
32
+ * - see login-form
33
+ * - typeInto email [self.email]
34
+ * - click submit
35
+ * ```
36
+ *
37
+ * ## Format rules
38
+ *
39
+ * - `#` headings are **group names** (optional hierarchy/context)
40
+ * - `##` headings are **scene names** (each becomes a `flow()` registration)
41
+ * - If no `##` headings exist, `#` headings are promoted to scene names
42
+ * - `role-name:` switches the active actor for subsequent lines (like a screenplay cue)
43
+ * - `role-name: action args` is inline shorthand for a one-line actor block
44
+ * - Action lines map to the standard text DSL (see `dsl.ts`)
45
+ * - Lines may start with `- ` or `1. ` (markdown lists) for readability (stripped)
46
+ * - `// comment` lines become `console.log` during execution
47
+ * - Blank lines are ignored
48
+ * - `[namespace.field]` interpolation:
49
+ * - `[self.field]` — current actor's own fields
50
+ * - `[role-name.field]` — another actor's fields
51
+ * - `[team.field]` — team metadata
52
+ * - `[alias.field]` — aliased role (from macro args)
53
+ * - `if <selector>` followed by indented lines creates a conditional monitor
54
+ * - `macro-name` or `macro-name alias=role` invokes a registered macro
55
+ * - `waitFor <message>` blocks the actor until a bus message arrives
56
+ * - Bare `click` (no selector) clicks the current scope
57
+ */
58
+ export interface MarkdownScene {
59
+ name: string;
60
+ group?: string;
61
+ blocks: ActorBlock[];
62
+ }
63
+ export interface ActorBlock {
64
+ role: string;
65
+ alias?: string;
66
+ actions: SceneAction[];
67
+ }
68
+ /**
69
+ * Macro argument types.
70
+ *
71
+ * - `mapping`: alias=role mapping, e.g., `target=new-user` makes `[target.field]` resolve to new-user's fields
72
+ * - `literal`: plain string value for backward compatibility
73
+ */
74
+ export type MacroArg = {
75
+ type: 'mapping';
76
+ alias: string;
77
+ role: string;
78
+ } | {
79
+ type: 'literal';
80
+ value: string;
81
+ };
82
+ export type SceneAction = {
83
+ type: 'action';
84
+ line: string;
85
+ } | {
86
+ type: 'comment';
87
+ text: string;
88
+ } | {
89
+ type: 'if';
90
+ selector: string;
91
+ actions: string[];
92
+ } | {
93
+ type: 'macro';
94
+ name: string;
95
+ args: MacroArg[];
96
+ };
97
+ /**
98
+ * Parse a `.spec.md` file into scene definitions.
99
+ *
100
+ * Handles both formats:
101
+ * - `#` groups + `##` scenes (recommended for multi-scene files)
102
+ * - `#` scenes only (for single-scene files or flat lists)
103
+ */
104
+ export declare function parseMarkdownScenes(content: string, filePath: string): MarkdownScene[];
105
+ /**
106
+ * Register parsed markdown scenes as reactive flows.
107
+ *
108
+ * Each scene becomes a `flow()` call. Actors are created upfront so
109
+ * `[actor.field]` interpolation can reference any actor regardless of
110
+ * declaration order.
111
+ */
112
+ export declare function registerMarkdownScenes(scenes: MarkdownScene[], filePath: string): void;
113
+ /**
114
+ * Load a `.spec.md` file: parse it and register all scenes as flows.
115
+ *
116
+ * Called by the runner when it discovers `.spec.md` files alongside
117
+ * `.spec.ts` files.
118
+ */
119
+ export declare function loadMarkdownScene(filePath: string): Promise<void>;
120
+ //# sourceMappingURL=markdown-scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-scene.d.ts","sourceRoot":"","sources":["../src/markdown-scene.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAYH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;AAMrD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,aAAa,EAAE,CAgJjB;AAmLD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,EAAE,EACvB,QAAQ,EAAE,MAAM,GACf,IAAI,CA0GN;AAMD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOvE"}
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Markdown scene parser and loader.
3
+ *
4
+ * Parses `.spec.md` files into reactive flow registrations. The format is
5
+ * natural markdown — human-readable, GitHub-renderable, and executable:
6
+ *
7
+ * ```markdown
8
+ * # User friend requests
9
+ * ## new user signs up and gets a friend request
10
+ * new-user:
11
+ * - openTo /
12
+ * - see welcome-box
13
+ * - click continue-button
14
+ *
15
+ * primary-user:
16
+ * - openTo /friends
17
+ * - click main-navbar search
18
+ * - typeInto search-input [new-user.username]
19
+ * - see search-results-section
20
+ * - click friend-request-button
21
+ *
22
+ * new-user:
23
+ * - seeToast friend-request
24
+ * - see navbar notifications-badge
25
+ * - click
26
+ * - see notifications-menu-expanded new-friend-request
27
+ * - click
28
+ *
29
+ * ## old user re-activates account
30
+ * returning-user:
31
+ * - openTo /login
32
+ * - see login-form
33
+ * - typeInto email [self.email]
34
+ * - click submit
35
+ * ```
36
+ *
37
+ * ## Format rules
38
+ *
39
+ * - `#` headings are **group names** (optional hierarchy/context)
40
+ * - `##` headings are **scene names** (each becomes a `flow()` registration)
41
+ * - If no `##` headings exist, `#` headings are promoted to scene names
42
+ * - `role-name:` switches the active actor for subsequent lines (like a screenplay cue)
43
+ * - `role-name: action args` is inline shorthand for a one-line actor block
44
+ * - Action lines map to the standard text DSL (see `dsl.ts`)
45
+ * - Lines may start with `- ` or `1. ` (markdown lists) for readability (stripped)
46
+ * - `// comment` lines become `console.log` during execution
47
+ * - Blank lines are ignored
48
+ * - `[namespace.field]` interpolation:
49
+ * - `[self.field]` — current actor's own fields
50
+ * - `[role-name.field]` — another actor's fields
51
+ * - `[team.field]` — team metadata
52
+ * - `[alias.field]` — aliased role (from macro args)
53
+ * - `if <selector>` followed by indented lines creates a conditional monitor
54
+ * - `macro-name` or `macro-name alias=role` invokes a registered macro
55
+ * - `waitFor <message>` blocks the actor until a bus message arrives
56
+ * - Bare `click` (no selector) clicks the current scope
57
+ */
58
+ import path from 'path';
59
+ import { parseAction, applyDslAction, getMacro } from './dsl.js';
60
+ import { flow } from './reactive.js';
61
+ import { setCurrentFile } from './scene.js';
62
+ // ---------------------------------------------------------------------------
63
+ // Parser
64
+ // ---------------------------------------------------------------------------
65
+ /**
66
+ * Parse a `.spec.md` file into scene definitions.
67
+ *
68
+ * Handles both formats:
69
+ * - `#` groups + `##` scenes (recommended for multi-scene files)
70
+ * - `#` scenes only (for single-scene files or flat lists)
71
+ */
72
+ export function parseMarkdownScenes(content, filePath) {
73
+ const lines = content.split('\n');
74
+ // First pass: determine heading strategy.
75
+ // If any ## heading exists, use # = group, ## = scene.
76
+ // Otherwise, # = scene (no groups).
77
+ const hasH2 = lines.some((l) => /^##\s/.test(l.trim()));
78
+ const scenes = [];
79
+ let currentGroup;
80
+ let currentScene = null;
81
+ let currentBlock = null;
82
+ for (let i = 0; i < lines.length; i++) {
83
+ const raw = lines[i];
84
+ const trimmed = raw.trim();
85
+ // Skip blank lines
86
+ if (!trimmed)
87
+ continue;
88
+ // ── Heading handling ──────────────────────────────────────────────
89
+ if (hasH2) {
90
+ // # = group, ## = scene
91
+ if (/^## /.test(trimmed)) {
92
+ const name = trimmed.slice(3).trim();
93
+ currentScene = { name, group: currentGroup, blocks: [] };
94
+ scenes.push(currentScene);
95
+ currentBlock = null;
96
+ continue;
97
+ }
98
+ if (/^# /.test(trimmed)) {
99
+ currentGroup = trimmed.slice(2).trim();
100
+ continue;
101
+ }
102
+ }
103
+ else {
104
+ // No ## headings — # = scene
105
+ if (/^# /.test(trimmed)) {
106
+ const name = trimmed.slice(2).trim();
107
+ currentScene = { name, group: undefined, blocks: [] };
108
+ scenes.push(currentScene);
109
+ currentBlock = null;
110
+ continue;
111
+ }
112
+ }
113
+ // ── Content lines (need an active scene) ──────────────────────────
114
+ // Auto-create scene if content appears before any heading
115
+ const earlyDecl = parseActorDeclaration(trimmed);
116
+ if (!currentScene && (earlyDecl || isActionLine(trimmed))) {
117
+ currentScene = {
118
+ name: path.basename(filePath, '.spec.md'),
119
+ group: undefined,
120
+ blocks: [],
121
+ };
122
+ scenes.push(currentScene);
123
+ // Fall through to process this line
124
+ }
125
+ if (!currentScene)
126
+ continue;
127
+ // ── Actor declaration ─────────────────────────────────────────────
128
+ // Format: `role-name:` or `role-name: action args`
129
+ // The first token before `:` is the role name (must not be a known action).
130
+ const actorDecl = earlyDecl ?? parseActorDeclaration(trimmed);
131
+ if (actorDecl) {
132
+ currentBlock = { role: actorDecl.role, actions: [] };
133
+ currentScene.blocks.push(currentBlock);
134
+ // If there's inline content after the colon, parse it as an action
135
+ if (actorDecl.rest) {
136
+ const actionLine = stripListPrefix(actorDecl.rest);
137
+ currentBlock.actions.push({ type: 'action', line: actionLine });
138
+ }
139
+ continue;
140
+ }
141
+ // Must have an actor block to add actions
142
+ if (!currentBlock)
143
+ continue;
144
+ // ── Comment ───────────────────────────────────────────────────────
145
+ if (trimmed.startsWith('//')) {
146
+ const text = trimmed.slice(2).trim();
147
+ if (text) {
148
+ currentBlock.actions.push({ type: 'comment', text });
149
+ }
150
+ continue;
151
+ }
152
+ // ── Conditional monitor (if <selector> + indented block) ──────────
153
+ if (trimmed.startsWith('if ') && !trimmed.startsWith('if(')) {
154
+ const selector = trimmed.slice(3).trim();
155
+ const subActions = [];
156
+ // Collect indented sub-actions
157
+ while (i + 1 < lines.length) {
158
+ const nextRaw = lines[i + 1];
159
+ const nextTrimmed = nextRaw.trim();
160
+ // Blank lines inside if block — skip but continue collecting
161
+ if (!nextTrimmed) {
162
+ i++;
163
+ continue;
164
+ }
165
+ // Indented line (2+ spaces from start of raw line) = sub-action
166
+ if (/^\s{2,}/.test(nextRaw) && nextTrimmed) {
167
+ const actionLine = stripListPrefix(nextTrimmed);
168
+ subActions.push(actionLine);
169
+ i++;
170
+ }
171
+ else {
172
+ break;
173
+ }
174
+ }
175
+ currentBlock.actions.push({ type: 'if', selector, actions: subActions });
176
+ continue;
177
+ }
178
+ // ── Macro invocation ────────────────────────────────────────────────
179
+ // A macro is any word that isn't a known DSL action, followed by
180
+ // optional `role <name>` or `team <name>` arguments:
181
+ // send-friend-request role best-friend-1
182
+ // signup-to-language team language-focus
183
+ const actionLine = stripListPrefix(trimmed);
184
+ const words = actionLine.split(/\s+/);
185
+ const firstWord = words[0];
186
+ if (firstWord && !KNOWN_ACTIONS.includes(firstWord)) {
187
+ const macroArgs = parseMacroArgs(words.slice(1));
188
+ currentBlock.actions.push({ type: 'macro', name: firstWord, args: macroArgs });
189
+ continue;
190
+ }
191
+ // ── Regular action line ───────────────────────────────────────────
192
+ currentBlock.actions.push({ type: 'action', line: actionLine });
193
+ }
194
+ return scenes;
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // Helpers
198
+ // ---------------------------------------------------------------------------
199
+ /**
200
+ * Strip optional markdown list prefix from an action line.
201
+ * Supports unordered (`- `), ordered (`1. `, `2. `, etc.), and plain lines.
202
+ */
203
+ function stripListPrefix(line) {
204
+ // Unordered: - action
205
+ if (line.startsWith('- '))
206
+ return line.slice(2);
207
+ // Ordered: 1. action, 2. action, 10. action, etc.
208
+ const orderedMatch = line.match(/^\d+\.\s+/);
209
+ if (orderedMatch)
210
+ return line.slice(orderedMatch[0].length);
211
+ return line;
212
+ }
213
+ /** Known DSL action verbs — used to distinguish actions from actor declarations */
214
+ const KNOWN_ACTIONS = [
215
+ 'openTo', 'see', 'seeInView', 'notSee', 'seeText', 'seeToast', 'click',
216
+ 'typeInto', 'check', 'select', 'wait', 'emit', 'waitFor',
217
+ 'warnIf', 'up', 'prev', 'scrollToBottom', 'if',
218
+ ];
219
+ /** Check if a line looks like a DSL action or macro invocation */
220
+ function isActionLine(line) {
221
+ const stripped = stripListPrefix(line);
222
+ const first = stripped.split(/\s/)[0];
223
+ // Any word could be either a known action or a macro name
224
+ return first !== undefined && first.length > 0;
225
+ }
226
+ /**
227
+ * Parse a line as an actor declaration.
228
+ *
229
+ * Actor declarations look like screenplay cues:
230
+ * - `role-name:` — block declaration (actions follow on subsequent lines)
231
+ * - `role-name: see foo` — inline declaration with first action on same line
232
+ *
233
+ * Returns null if the line is not an actor declaration (e.g. it's an action
234
+ * line or doesn't contain a colon in the right position).
235
+ */
236
+ function parseActorDeclaration(line) {
237
+ // Match: word-chars (including hyphens) followed by a colon
238
+ const match = line.match(/^([\w][\w-]*):\s*(.*)$/);
239
+ if (!match)
240
+ return null;
241
+ const [, word, rest] = match;
242
+ // Don't treat known action verbs as actor declarations
243
+ if (KNOWN_ACTIONS.includes(word))
244
+ return null;
245
+ return { role: word, rest: rest.trim() };
246
+ }
247
+ /**
248
+ * Parse macro arguments into typed MacroArg objects.
249
+ *
250
+ * Supports `alias=role` mappings that make `[alias.field]` resolve to the role's fields.
251
+ * Plain values without `=` are treated as literals for backward compatibility.
252
+ *
253
+ * @example
254
+ * parseMacroArgs(['target=new-user'])
255
+ * // => [{ type: 'mapping', alias: 'target', role: 'new-user' }]
256
+ *
257
+ * parseMacroArgs(['target=new-user', 'other=alice'])
258
+ * // => [{ type: 'mapping', alias: 'target', role: 'new-user' }, { type: 'mapping', alias: 'other', role: 'alice' }]
259
+ */
260
+ function parseMacroArgs(words) {
261
+ const args = [];
262
+ for (const word of words) {
263
+ if (word.includes('=')) {
264
+ const eqIndex = word.indexOf('=');
265
+ const alias = word.slice(0, eqIndex);
266
+ const role = word.slice(eqIndex + 1);
267
+ if (alias && role) {
268
+ args.push({ type: 'mapping', alias, role });
269
+ }
270
+ else {
271
+ // Malformed mapping — treat as literal
272
+ args.push({ type: 'literal', value: word });
273
+ }
274
+ }
275
+ else {
276
+ args.push({ type: 'literal', value: word });
277
+ }
278
+ }
279
+ return args;
280
+ }
281
+ /**
282
+ * Interpolate `[namespace.field]` references in an action line.
283
+ *
284
+ * Supported namespaces:
285
+ * - `[self.field]` — current actor's own fields
286
+ * - `[team.field]` — team metadata
287
+ * - `[role-name.field]` — another actor's fields
288
+ * - `[alias.field]` — aliased role (from macro args)
289
+ *
290
+ * @example
291
+ * // In an action line:
292
+ * interpolate('see user-card-[target.id]', { actors, self, aliases: new Map([['target', 'new-user']]) })
293
+ * // => 'see user-card-12345'
294
+ */
295
+ function interpolate(line, ctx) {
296
+ return line.replace(/\[([\w][\w-]*)\.([\w]+)\]/g, (match, namespace, field) => {
297
+ // [self.field] — current actor's fields
298
+ if (namespace === 'self') {
299
+ if (!ctx.self) {
300
+ throw new Error(`Cannot use [self.${field}] — no current actor context`);
301
+ }
302
+ const value = ctx.self[field];
303
+ if (value === undefined) {
304
+ throw new Error(`[self.${field}] — actor has no field "${field}"`);
305
+ }
306
+ return String(value);
307
+ }
308
+ // [team.field] — team metadata
309
+ if (namespace === 'team') {
310
+ if (!ctx.team) {
311
+ throw new Error(`Cannot use [team.${field}] — no team context`);
312
+ }
313
+ const value = ctx.team[field];
314
+ if (value === undefined) {
315
+ throw new Error(`[team.${field}] — team has no field "${field}"`);
316
+ }
317
+ return String(value);
318
+ }
319
+ // Check for alias mapping first
320
+ const resolvedRole = ctx.aliases?.get(namespace) ?? namespace;
321
+ // Look up the actor
322
+ const actor = ctx.actors.get(resolvedRole);
323
+ if (!actor) {
324
+ const available = [...ctx.actors.keys()].join(', ');
325
+ const aliasNote = ctx.aliases?.has(namespace)
326
+ ? ` (alias "${namespace}" -> "${resolvedRole}")`
327
+ : '';
328
+ throw new Error(`Unknown actor "${namespace}"${aliasNote} in [${namespace}.${field}] — available: ${available}`);
329
+ }
330
+ const value = actor[field];
331
+ if (value === undefined) {
332
+ throw new Error(`Actor "${resolvedRole}" has no field "${field}" (available: id, username, email, password, ...)`);
333
+ }
334
+ return String(value);
335
+ });
336
+ }
337
+ // ---------------------------------------------------------------------------
338
+ // Registration — convert parsed scenes into flow() registrations
339
+ // ---------------------------------------------------------------------------
340
+ /**
341
+ * Register parsed markdown scenes as reactive flows.
342
+ *
343
+ * Each scene becomes a `flow()` call. Actors are created upfront so
344
+ * `[actor.field]` interpolation can reference any actor regardless of
345
+ * declaration order.
346
+ */
347
+ export function registerMarkdownScenes(scenes, filePath) {
348
+ for (const scene of scenes) {
349
+ flow(scene.name, ({ actor }) => {
350
+ // ── Phase 1: collect all unique roles and create actors ──────────
351
+ const actors = new Map();
352
+ for (const block of scene.blocks) {
353
+ if (!actors.has(block.role)) {
354
+ const a = actor(block.role);
355
+ actors.set(block.role, a);
356
+ }
357
+ if (block.alias && !actors.has(block.alias)) {
358
+ actors.set(block.alias, actors.get(block.role));
359
+ }
360
+ }
361
+ // ── Phase 2: apply actions to each actor block ──────────────────
362
+ for (const block of scene.blocks) {
363
+ const a = actors.get(block.alias || block.role);
364
+ // Base interpolation context for this actor block
365
+ const baseCtx = {
366
+ actors,
367
+ self: a,
368
+ // TODO: wire up team metadata when TeamManager provides it
369
+ team: undefined,
370
+ };
371
+ for (const action of block.actions) {
372
+ switch (action.type) {
373
+ case 'comment':
374
+ // Queue a console.log that executes during drain
375
+ a.do(async () => {
376
+ console.log(` // ${action.text}`);
377
+ });
378
+ break;
379
+ case 'action': {
380
+ const interpolated = interpolate(action.line, baseCtx);
381
+ const parsed = parseAction(interpolated);
382
+ applyDslAction(a, parsed);
383
+ break;
384
+ }
385
+ case 'if': {
386
+ const interpolatedSelector = interpolate(action.selector, baseCtx);
387
+ a.if(interpolatedSelector, (actor) => {
388
+ for (const subLine of action.actions) {
389
+ const interpolated = interpolate(subLine, {
390
+ ...baseCtx,
391
+ self: actor,
392
+ });
393
+ const parsed = parseAction(interpolated);
394
+ applyDslAction(actor, parsed);
395
+ }
396
+ });
397
+ break;
398
+ }
399
+ case 'macro': {
400
+ const macro = getMacro(action.name);
401
+ if (!macro) {
402
+ throw new Error(`Macro not found: ${action.name} — define it with defineMacro('${action.name}', [...]) in a .ts file`);
403
+ }
404
+ // Build alias mappings from macro args
405
+ const aliases = new Map();
406
+ for (const arg of action.args) {
407
+ if (arg.type === 'mapping') {
408
+ // Verify the role exists
409
+ if (!actors.has(arg.role)) {
410
+ throw new Error(`Macro ${action.name}: unknown role "${arg.role}" in mapping "${arg.alias}=${arg.role}" — actors in this scene: ${[...actors.keys()].join(', ')}`);
411
+ }
412
+ aliases.set(arg.alias, arg.role);
413
+ }
414
+ // Literals are ignored for now — macros use [alias.field] syntax
415
+ }
416
+ // Context for macro interpolation includes aliases
417
+ const macroCtx = {
418
+ ...baseCtx,
419
+ aliases,
420
+ };
421
+ // Apply macro actions inline (no await — flow model)
422
+ // Uses unified [namespace.field] syntax — no {{var}} substitution
423
+ for (const actionLine of macro) {
424
+ const interpolated = interpolate(actionLine, macroCtx);
425
+ const parsed = parseAction(interpolated);
426
+ applyDslAction(a, parsed);
427
+ }
428
+ break;
429
+ }
430
+ }
431
+ }
432
+ }
433
+ });
434
+ }
435
+ }
436
+ // ---------------------------------------------------------------------------
437
+ // File loader — top-level entry point
438
+ // ---------------------------------------------------------------------------
439
+ /**
440
+ * Load a `.spec.md` file: parse it and register all scenes as flows.
441
+ *
442
+ * Called by the runner when it discovers `.spec.md` files alongside
443
+ * `.spec.ts` files.
444
+ */
445
+ export async function loadMarkdownScene(filePath) {
446
+ const fs = await import('fs/promises');
447
+ const content = await fs.readFile(filePath, 'utf-8');
448
+ setCurrentFile(filePath);
449
+ const scenes = parseMarkdownScenes(content, filePath);
450
+ registerMarkdownScenes(scenes, filePath);
451
+ }
452
+ //# sourceMappingURL=markdown-scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-scene.js","sourceRoot":"","sources":["../src/markdown-scene.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Message bus for inter-actor coordination.
3
+ *
4
+ * Key behavior: Messages are "sticky" - they persist on the bus.
5
+ * If you listen AFTER a message was emitted, you still receive it (once).
6
+ * This prevents race conditions when declaring causality early in scenes.
7
+ */
8
+ export declare class MessageBus {
9
+ private emittedMessages;
10
+ private listeners;
11
+ /**
12
+ * Emit a message to the bus.
13
+ * All current and future listeners for this message will be triggered.
14
+ */
15
+ emit(message: string): void;
16
+ /**
17
+ * Wait for a message to be emitted.
18
+ * If the message was already emitted, resolves immediately.
19
+ */
20
+ waitFor(message: string, timeout?: number): Promise<void>;
21
+ /**
22
+ * Check if a message has been emitted.
23
+ */
24
+ hasEmitted(message: string): boolean;
25
+ /**
26
+ * Clear all messages and listeners.
27
+ * Call this between scene runs.
28
+ */
29
+ clear(): void;
30
+ }
31
+ //# sourceMappingURL=message-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.d.ts","sourceRoot":"","sources":["../src/message-bus.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,SAAS,CAAuC;IAExD;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAa3B;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCxD;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIpC;;;OAGG;IACH,KAAK,IAAI,IAAI;CAId"}