@sharpee/transcript-tester 0.9.61-beta

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 (137) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +21 -0
  3. package/dist/cli.d.ts +11 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +367 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/condition-evaluator.d.ts +30 -0
  8. package/dist/condition-evaluator.d.ts.map +1 -0
  9. package/dist/condition-evaluator.js +314 -0
  10. package/dist/condition-evaluator.js.map +1 -0
  11. package/dist/fast-cli.d.ts +13 -0
  12. package/dist/fast-cli.d.ts.map +1 -0
  13. package/dist/fast-cli.js +363 -0
  14. package/dist/fast-cli.js.map +1 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +48 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/navigator.d.ts +27 -0
  20. package/dist/navigator.d.ts.map +1 -0
  21. package/dist/navigator.js +303 -0
  22. package/dist/navigator.js.map +1 -0
  23. package/dist/parser.d.ts +19 -0
  24. package/dist/parser.d.ts.map +1 -0
  25. package/dist/parser.js +453 -0
  26. package/dist/parser.js.map +1 -0
  27. package/dist/reporter.d.ts +41 -0
  28. package/dist/reporter.d.ts.map +1 -0
  29. package/dist/reporter.js +386 -0
  30. package/dist/reporter.js.map +1 -0
  31. package/dist/runner.d.ts +44 -0
  32. package/dist/runner.d.ts.map +1 -0
  33. package/dist/runner.js +977 -0
  34. package/dist/runner.js.map +1 -0
  35. package/dist/story-loader.d.ts +31 -0
  36. package/dist/story-loader.d.ts.map +1 -0
  37. package/dist/story-loader.js +169 -0
  38. package/dist/story-loader.js.map +1 -0
  39. package/dist/types.d.ts +204 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +8 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist-esm/cli.d.ts +11 -0
  44. package/dist-esm/cli.d.ts.map +1 -0
  45. package/dist-esm/cli.js +332 -0
  46. package/dist-esm/cli.js.map +1 -0
  47. package/dist-esm/condition-evaluator.d.ts +30 -0
  48. package/dist-esm/condition-evaluator.d.ts.map +1 -0
  49. package/dist-esm/condition-evaluator.js +311 -0
  50. package/dist-esm/condition-evaluator.js.map +1 -0
  51. package/dist-esm/fast-cli.d.ts +13 -0
  52. package/dist-esm/fast-cli.d.ts.map +1 -0
  53. package/dist-esm/fast-cli.js +328 -0
  54. package/dist-esm/fast-cli.js.map +1 -0
  55. package/dist-esm/index.d.ts +17 -0
  56. package/dist-esm/index.d.ts.map +1 -0
  57. package/dist-esm/index.js +21 -0
  58. package/dist-esm/index.js.map +1 -0
  59. package/dist-esm/navigator.d.ts +27 -0
  60. package/dist-esm/navigator.d.ts.map +1 -0
  61. package/dist-esm/navigator.js +300 -0
  62. package/dist-esm/navigator.js.map +1 -0
  63. package/dist-esm/parser.d.ts +19 -0
  64. package/dist-esm/parser.d.ts.map +1 -0
  65. package/dist-esm/parser.js +415 -0
  66. package/dist-esm/parser.js.map +1 -0
  67. package/dist-esm/reporter.d.ts +41 -0
  68. package/dist-esm/reporter.d.ts.map +1 -0
  69. package/dist-esm/reporter.js +342 -0
  70. package/dist-esm/reporter.js.map +1 -0
  71. package/dist-esm/runner.d.ts +44 -0
  72. package/dist-esm/runner.d.ts.map +1 -0
  73. package/dist-esm/runner.js +941 -0
  74. package/dist-esm/runner.js.map +1 -0
  75. package/dist-esm/story-loader.d.ts +31 -0
  76. package/dist-esm/story-loader.d.ts.map +1 -0
  77. package/dist-esm/story-loader.js +131 -0
  78. package/dist-esm/story-loader.js.map +1 -0
  79. package/dist-esm/types.d.ts +204 -0
  80. package/dist-esm/types.d.ts.map +1 -0
  81. package/dist-esm/types.js +7 -0
  82. package/dist-esm/types.js.map +1 -0
  83. package/dist-npm/cli.d.ts +11 -0
  84. package/dist-npm/cli.d.ts.map +1 -0
  85. package/dist-npm/cli.js +367 -0
  86. package/dist-npm/cli.js.map +1 -0
  87. package/dist-npm/condition-evaluator.d.ts +30 -0
  88. package/dist-npm/condition-evaluator.d.ts.map +1 -0
  89. package/dist-npm/condition-evaluator.js +314 -0
  90. package/dist-npm/condition-evaluator.js.map +1 -0
  91. package/dist-npm/fast-cli.d.ts +13 -0
  92. package/dist-npm/fast-cli.d.ts.map +1 -0
  93. package/dist-npm/fast-cli.js +363 -0
  94. package/dist-npm/fast-cli.js.map +1 -0
  95. package/dist-npm/index.d.ts +17 -0
  96. package/dist-npm/index.d.ts.map +1 -0
  97. package/dist-npm/index.js +48 -0
  98. package/dist-npm/index.js.map +1 -0
  99. package/dist-npm/navigator.d.ts +27 -0
  100. package/dist-npm/navigator.d.ts.map +1 -0
  101. package/dist-npm/navigator.js +303 -0
  102. package/dist-npm/navigator.js.map +1 -0
  103. package/dist-npm/parser.d.ts +19 -0
  104. package/dist-npm/parser.d.ts.map +1 -0
  105. package/dist-npm/parser.js +453 -0
  106. package/dist-npm/parser.js.map +1 -0
  107. package/dist-npm/reporter.d.ts +41 -0
  108. package/dist-npm/reporter.d.ts.map +1 -0
  109. package/dist-npm/reporter.js +386 -0
  110. package/dist-npm/reporter.js.map +1 -0
  111. package/dist-npm/runner.d.ts +44 -0
  112. package/dist-npm/runner.d.ts.map +1 -0
  113. package/dist-npm/runner.js +977 -0
  114. package/dist-npm/runner.js.map +1 -0
  115. package/dist-npm/story-loader.d.ts +31 -0
  116. package/dist-npm/story-loader.d.ts.map +1 -0
  117. package/dist-npm/story-loader.js +169 -0
  118. package/dist-npm/story-loader.js.map +1 -0
  119. package/dist-npm/types.d.ts +204 -0
  120. package/dist-npm/types.d.ts.map +1 -0
  121. package/dist-npm/types.js +8 -0
  122. package/dist-npm/types.js.map +1 -0
  123. package/package.json +49 -0
  124. package/src/cli.ts +385 -0
  125. package/src/condition-evaluator.ts +382 -0
  126. package/src/fast-cli.ts +403 -0
  127. package/src/index.ts +26 -0
  128. package/src/navigator.ts +365 -0
  129. package/src/parser.ts +488 -0
  130. package/src/reporter.ts +409 -0
  131. package/src/runner.ts +1152 -0
  132. package/src/story-loader.ts +168 -0
  133. package/src/types.ts +244 -0
  134. package/tsconfig.esm.json +11 -0
  135. package/tsconfig.esm.tsbuildinfo +1 -0
  136. package/tsconfig.json +22 -0
  137. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Condition Evaluator for Smart Transcript Directives (ADR-092)
3
+ *
4
+ * Evaluates condition expressions against game state.
5
+ *
6
+ * Supported expressions:
7
+ * - location = "Room Name" - Player is in room with that name
8
+ * - room contains "entity" - Entity with that name is in current room
9
+ * - inventory contains "item" - Player has item with that name
10
+ * - not inventory contains "item" - Player does NOT have item
11
+ * - entity "X" exists - Entity with name X exists anywhere
12
+ * - entity "X" alive - NPC entity is not dead
13
+ * - entity "X" in "Room" - Entity X is in specified room
14
+ */
15
+ /**
16
+ * Find an entity by name (searches identity.name and aliases)
17
+ * Prioritizes exact matches and actors/NPCs over rooms
18
+ */
19
+ function findEntityByName(world, name) {
20
+ const nameLower = name.toLowerCase();
21
+ const allMatches = world.findWhere((entity) => {
22
+ const identity = entity.get?.('identity') || entity.traits?.get?.('identity');
23
+ if (!identity)
24
+ return false;
25
+ const entityName = (identity.name || '').toLowerCase();
26
+ const aliases = (identity.aliases || []).map((a) => a.toLowerCase());
27
+ return entityName === nameLower ||
28
+ entityName.includes(nameLower) ||
29
+ aliases.some((a) => a === nameLower || a.includes(nameLower));
30
+ });
31
+ if (allMatches.length === 0)
32
+ return null;
33
+ // Prioritize: 1) exact name match, 2) exact alias match, 3) actors/NPCs, 4) first match
34
+ const exactNameMatch = allMatches.find((e) => {
35
+ const identity = e.get?.('identity') || e.traits?.get?.('identity');
36
+ return (identity?.name || '').toLowerCase() === nameLower;
37
+ });
38
+ if (exactNameMatch)
39
+ return exactNameMatch;
40
+ const exactAliasMatch = allMatches.find((e) => {
41
+ const identity = e.get?.('identity') || e.traits?.get?.('identity');
42
+ const aliases = (identity?.aliases || []).map((a) => a.toLowerCase());
43
+ return aliases.includes(nameLower);
44
+ });
45
+ if (exactAliasMatch)
46
+ return exactAliasMatch;
47
+ // Prefer actors/NPCs/combatants over rooms
48
+ const actorMatch = allMatches.find((e) => e.get?.('actor') || e.get?.('npc') || e.get?.('combatant') ||
49
+ e.traits?.get?.('actor') || e.traits?.get?.('npc') || e.traits?.get?.('combatant'));
50
+ if (actorMatch)
51
+ return actorMatch;
52
+ return allMatches[0];
53
+ }
54
+ /**
55
+ * Find a room by name
56
+ */
57
+ function findRoomByName(world, name) {
58
+ const nameLower = name.toLowerCase();
59
+ const rooms = world.findWhere((entity) => {
60
+ // Check if it's a room
61
+ const roomTrait = entity.get?.('room') || entity.traits?.get?.('room');
62
+ if (!roomTrait)
63
+ return false;
64
+ const identity = entity.get?.('identity') || entity.traits?.get?.('identity');
65
+ if (!identity)
66
+ return false;
67
+ const entityName = (identity.name || '').toLowerCase();
68
+ return entityName === nameLower || entityName.includes(nameLower);
69
+ });
70
+ return rooms[0] || null;
71
+ }
72
+ /**
73
+ * Get player entity
74
+ */
75
+ function getPlayer(world) {
76
+ const players = world.findWhere((entity) => {
77
+ const identity = entity.get?.('identity') || entity.traits?.get?.('identity');
78
+ return identity?.name === 'player' || identity?.name === 'yourself';
79
+ });
80
+ return players[0] || null;
81
+ }
82
+ /**
83
+ * Get the name of a room by ID
84
+ */
85
+ function getRoomName(world, roomId) {
86
+ const room = world.getEntity(roomId);
87
+ if (!room)
88
+ return roomId;
89
+ const identity = room.get?.('identity') || room.traits?.get?.('identity');
90
+ return identity?.name || roomId;
91
+ }
92
+ /**
93
+ * Check if an entity is "alive" (for NPCs/Combatants)
94
+ */
95
+ function isEntityAlive(entity) {
96
+ // Check for CombatantTrait (used by troll, thief, etc.)
97
+ const combatantTrait = entity.get?.('combatant') || entity.traits?.get?.('combatant');
98
+ if (combatantTrait) {
99
+ // CombatantTrait uses isAlive (not isDead)
100
+ if (combatantTrait.isAlive === false)
101
+ return false;
102
+ // Check health
103
+ if (combatantTrait.health !== undefined && combatantTrait.health <= 0)
104
+ return false;
105
+ }
106
+ // Check for NPC trait with health/alive status
107
+ const npcTrait = entity.get?.('npc') || entity.traits?.get?.('npc');
108
+ if (npcTrait) {
109
+ // Check isDead flag
110
+ if (entity.isDead === true)
111
+ return false;
112
+ if (npcTrait.isDead === true)
113
+ return false;
114
+ if (npcTrait.isAlive === false)
115
+ return false;
116
+ // Check health
117
+ if (npcTrait.health !== undefined && npcTrait.health <= 0)
118
+ return false;
119
+ }
120
+ // Default to alive if no NPC/combatant trait or death indicators
121
+ return true;
122
+ }
123
+ /**
124
+ * Parse and evaluate a condition expression
125
+ */
126
+ export function evaluateCondition(condition, world, playerId) {
127
+ const trimmed = condition.trim();
128
+ // Handle negation prefix
129
+ const isNegated = trimmed.toLowerCase().startsWith('not ');
130
+ const expr = isNegated ? trimmed.slice(4).trim() : trimmed;
131
+ // Try each pattern
132
+ let result = tryLocationEquals(expr, world, playerId);
133
+ if (result)
134
+ return applyNegation(result, isNegated);
135
+ result = tryRoomContains(expr, world, playerId);
136
+ if (result)
137
+ return applyNegation(result, isNegated);
138
+ result = tryInventoryContains(expr, world, playerId);
139
+ if (result)
140
+ return applyNegation(result, isNegated);
141
+ result = tryEntityExists(expr, world);
142
+ if (result)
143
+ return applyNegation(result, isNegated);
144
+ result = tryEntityAlive(expr, world);
145
+ if (result)
146
+ return applyNegation(result, isNegated);
147
+ result = tryEntityInRoom(expr, world);
148
+ if (result)
149
+ return applyNegation(result, isNegated);
150
+ // Unknown condition format
151
+ return {
152
+ met: false,
153
+ reason: `Unknown condition format: "${condition}"`
154
+ };
155
+ }
156
+ /**
157
+ * Apply negation to a result
158
+ */
159
+ function applyNegation(result, negate) {
160
+ if (!negate)
161
+ return result;
162
+ return {
163
+ met: !result.met,
164
+ reason: `NOT (${result.reason})`
165
+ };
166
+ }
167
+ /**
168
+ * Pattern: location = "Room Name"
169
+ */
170
+ function tryLocationEquals(expr, world, playerId) {
171
+ const match = expr.match(/^location\s*=\s*"([^"]+)"$/i);
172
+ if (!match)
173
+ return null;
174
+ const targetRoomName = match[1];
175
+ const currentRoomId = world.getLocation(playerId);
176
+ if (!currentRoomId) {
177
+ return { met: false, reason: `Player location unknown` };
178
+ }
179
+ const currentRoomName = getRoomName(world, currentRoomId);
180
+ const met = currentRoomName.toLowerCase() === targetRoomName.toLowerCase() ||
181
+ currentRoomName.toLowerCase().includes(targetRoomName.toLowerCase());
182
+ return {
183
+ met,
184
+ reason: `Player is in "${currentRoomName}" (expected "${targetRoomName}")`
185
+ };
186
+ }
187
+ /**
188
+ * Pattern: room contains "entity"
189
+ */
190
+ function tryRoomContains(expr, world, playerId) {
191
+ const match = expr.match(/^room\s+contains\s+"([^"]+)"$/i);
192
+ if (!match)
193
+ return null;
194
+ const entityName = match[1];
195
+ const currentRoomId = world.getLocation(playerId);
196
+ if (!currentRoomId) {
197
+ return { met: false, reason: `Player location unknown` };
198
+ }
199
+ // Get room contents
200
+ const contents = world.getContents(currentRoomId);
201
+ const entityNameLower = entityName.toLowerCase();
202
+ const found = contents.some((item) => {
203
+ const identity = item.get?.('identity') || item.traits?.get?.('identity');
204
+ if (!identity)
205
+ return false;
206
+ const itemName = (identity.name || '').toLowerCase();
207
+ const aliases = (identity.aliases || []).map((a) => a.toLowerCase());
208
+ return itemName === entityNameLower ||
209
+ itemName.includes(entityNameLower) ||
210
+ aliases.some((a) => a === entityNameLower || a.includes(entityNameLower));
211
+ });
212
+ const roomName = getRoomName(world, currentRoomId);
213
+ return {
214
+ met: found,
215
+ reason: found
216
+ ? `"${entityName}" found in ${roomName}`
217
+ : `"${entityName}" not found in ${roomName}`
218
+ };
219
+ }
220
+ /**
221
+ * Pattern: inventory contains "item"
222
+ */
223
+ function tryInventoryContains(expr, world, playerId) {
224
+ const match = expr.match(/^inventory\s+contains\s+"([^"]+)"$/i);
225
+ if (!match)
226
+ return null;
227
+ const itemName = match[1];
228
+ // Get player inventory
229
+ const inventory = world.getContents(playerId, { includeWorn: true });
230
+ const itemNameLower = itemName.toLowerCase();
231
+ const found = inventory.some((item) => {
232
+ const identity = item.get?.('identity') || item.traits?.get?.('identity');
233
+ if (!identity)
234
+ return false;
235
+ const name = (identity.name || '').toLowerCase();
236
+ const aliases = (identity.aliases || []).map((a) => a.toLowerCase());
237
+ return name === itemNameLower ||
238
+ name.includes(itemNameLower) ||
239
+ aliases.some((a) => a === itemNameLower || a.includes(itemNameLower));
240
+ });
241
+ return {
242
+ met: found,
243
+ reason: found
244
+ ? `"${itemName}" found in inventory`
245
+ : `"${itemName}" not in inventory`
246
+ };
247
+ }
248
+ /**
249
+ * Pattern: entity "X" exists
250
+ */
251
+ function tryEntityExists(expr, world) {
252
+ const match = expr.match(/^entity\s+"([^"]+)"\s+exists$/i);
253
+ if (!match)
254
+ return null;
255
+ const entityName = match[1];
256
+ const entity = findEntityByName(world, entityName);
257
+ return {
258
+ met: entity !== null,
259
+ reason: entity
260
+ ? `Entity "${entityName}" exists`
261
+ : `Entity "${entityName}" not found`
262
+ };
263
+ }
264
+ /**
265
+ * Pattern: entity "X" alive
266
+ */
267
+ function tryEntityAlive(expr, world) {
268
+ const match = expr.match(/^entity\s+"([^"]+)"\s+alive$/i);
269
+ if (!match)
270
+ return null;
271
+ const entityName = match[1];
272
+ const entity = findEntityByName(world, entityName);
273
+ if (!entity) {
274
+ return { met: false, reason: `Entity "${entityName}" not found` };
275
+ }
276
+ const alive = isEntityAlive(entity);
277
+ return {
278
+ met: alive,
279
+ reason: alive
280
+ ? `Entity "${entityName}" is alive`
281
+ : `Entity "${entityName}" is dead`
282
+ };
283
+ }
284
+ /**
285
+ * Pattern: entity "X" in "Room"
286
+ */
287
+ function tryEntityInRoom(expr, world) {
288
+ const match = expr.match(/^entity\s+"([^"]+)"\s+in\s+"([^"]+)"$/i);
289
+ if (!match)
290
+ return null;
291
+ const entityName = match[1];
292
+ const roomName = match[2];
293
+ const entity = findEntityByName(world, entityName);
294
+ if (!entity) {
295
+ return { met: false, reason: `Entity "${entityName}" not found` };
296
+ }
297
+ const targetRoom = findRoomByName(world, roomName);
298
+ if (!targetRoom) {
299
+ return { met: false, reason: `Room "${roomName}" not found` };
300
+ }
301
+ const entityLocation = world.getLocation(entity.id);
302
+ const met = entityLocation === targetRoom.id;
303
+ const actualRoomName = entityLocation ? getRoomName(world, entityLocation) : 'unknown';
304
+ return {
305
+ met,
306
+ reason: met
307
+ ? `"${entityName}" is in "${roomName}"`
308
+ : `"${entityName}" is in "${actualRoomName}", not "${roomName}"`
309
+ };
310
+ }
311
+ //# sourceMappingURL=condition-evaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition-evaluator.js","sourceRoot":"","sources":["../src/condition-evaluator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fast Transcript Tester CLI
4
+ *
5
+ * Uses pre-bundled platform (dist/sharpee.js) and story for instant loading.
6
+ * Supports --chain flag for walkthrough testing.
7
+ *
8
+ * Usage:
9
+ * node packages/transcript-tester/dist/fast-cli.js [transcript-files...] [options]
10
+ * node packages/transcript-tester/dist/fast-cli.js --chain wt-*.transcript
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=fast-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fast-cli.d.ts","sourceRoot":"","sources":["../src/fast-cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;GASG"}
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fast Transcript Tester CLI
4
+ *
5
+ * Uses pre-bundled platform (dist/sharpee.js) and story for instant loading.
6
+ * Supports --chain flag for walkthrough testing.
7
+ *
8
+ * Usage:
9
+ * node packages/transcript-tester/dist/fast-cli.js [transcript-files...] [options]
10
+ * node packages/transcript-tester/dist/fast-cli.js --chain wt-*.transcript
11
+ */
12
+ import * as path from 'path';
13
+ import * as readline from 'readline';
14
+ import { parseTranscriptFile, validateTranscript } from './parser';
15
+ import { runTranscript } from './runner';
16
+ import { reportTranscript, reportTestRun, getExitCode, } from './reporter';
17
+ // Load the pre-bundled platform
18
+ // __dirname is packages/transcript-tester/dist, so go up 3 levels to repo root
19
+ const bundlePath = path.resolve(__dirname, '..', '..', '..', 'dist', 'sharpee.js');
20
+ const platform = require(bundlePath);
21
+ // Extract what we need from the bundle
22
+ const { GameEngine, WorldModel, EntityType, Parser, LanguageProvider, PerceptionService, TestingExtension } = platform;
23
+ /**
24
+ * Parse command line arguments
25
+ */
26
+ function parseArgs(args) {
27
+ const options = {
28
+ transcriptPaths: [],
29
+ verbose: false,
30
+ stopOnFailure: false,
31
+ chain: false,
32
+ play: false,
33
+ storyPath: 'stories/dungeo'
34
+ };
35
+ let i = 0;
36
+ while (i < args.length) {
37
+ const arg = args[i];
38
+ if (arg === '--verbose' || arg === '-v') {
39
+ options.verbose = true;
40
+ }
41
+ else if (arg === '--stop-on-failure' || arg === '-s') {
42
+ options.stopOnFailure = true;
43
+ }
44
+ else if (arg === '--chain' || arg === '-c') {
45
+ options.chain = true;
46
+ }
47
+ else if (arg === '--play' || arg === '-p') {
48
+ options.play = true;
49
+ }
50
+ else if (arg === '--story') {
51
+ i++;
52
+ if (i < args.length) {
53
+ options.storyPath = args[i];
54
+ }
55
+ }
56
+ else if (arg === '--help' || arg === '-h') {
57
+ printHelp();
58
+ process.exit(0);
59
+ }
60
+ else if (!arg.startsWith('-')) {
61
+ options.transcriptPaths.push(arg);
62
+ }
63
+ i++;
64
+ }
65
+ return options;
66
+ }
67
+ /**
68
+ * Print help message
69
+ */
70
+ function printHelp() {
71
+ console.log(`
72
+ Fast Transcript Tester - Uses pre-bundled platform for instant loading
73
+
74
+ Usage:
75
+ fast-transcript-test [transcript-files...] [options]
76
+
77
+ Arguments:
78
+ transcript-files One or more .transcript files to run
79
+
80
+ Options:
81
+ -c, --chain Chain transcripts (don't reset game state between them)
82
+ -v, --verbose Show detailed output for each command
83
+ -s, --stop-on-failure Stop on first failure
84
+ -p, --play Interactive play mode (REPL)
85
+ --story <path> Story path (default: stories/dungeo)
86
+ -h, --help Show this help message
87
+
88
+ Examples:
89
+ fast-transcript-test stories/dungeo/walkthroughs/wt-01-get-torch-early.transcript
90
+ fast-transcript-test --chain stories/dungeo/walkthroughs/wt-*.transcript
91
+ fast-transcript-test --play
92
+ `);
93
+ }
94
+ /**
95
+ * Load the story and create a testable game
96
+ */
97
+ function loadStoryAndCreateGame(storyPath) {
98
+ // Resolve story path
99
+ const resolvedPath = path.isAbsolute(storyPath)
100
+ ? storyPath
101
+ : path.resolve(process.cwd(), storyPath);
102
+ // Load the story module
103
+ const distPath = path.join(resolvedPath, 'dist', 'index.js');
104
+ const storyModule = require(distPath);
105
+ const story = storyModule.story || storyModule.default;
106
+ if (!story) {
107
+ throw new Error(`Story module at ${storyPath} does not export 'story' or 'default'`);
108
+ }
109
+ // Create world and player
110
+ const world = new WorldModel();
111
+ const player = world.createEntity('player', EntityType.ACTOR);
112
+ world.setPlayer(player.id);
113
+ // Create parser and language
114
+ const language = new LanguageProvider();
115
+ const parser = new Parser(language);
116
+ // Extend parser and language with story-specific vocabulary
117
+ if (story.extendParser) {
118
+ story.extendParser(parser);
119
+ }
120
+ if (story.extendLanguage) {
121
+ story.extendLanguage(language);
122
+ }
123
+ // Create perception service
124
+ const perceptionService = new PerceptionService();
125
+ // Create engine
126
+ const engine = new GameEngine({
127
+ world,
128
+ player,
129
+ parser,
130
+ language,
131
+ perceptionService,
132
+ });
133
+ // Set the story and start
134
+ engine.setStory(story);
135
+ engine.start();
136
+ // Create testing extension
137
+ const testingExtension = TestingExtension ? new TestingExtension() : null;
138
+ // Capture text output and events
139
+ let lastOutput = '';
140
+ let outputBuffer = [];
141
+ let lastEvents = [];
142
+ let lastTurnResult = null;
143
+ engine.on('text:output', (text) => {
144
+ outputBuffer.push(text);
145
+ });
146
+ let eventBuffer = [];
147
+ engine.on('event', (event) => {
148
+ eventBuffer.push(event);
149
+ });
150
+ const testableGame = {
151
+ engine,
152
+ world,
153
+ testingExtension,
154
+ lastOutput: '',
155
+ lastEvents: [],
156
+ lastTurnResult: null,
157
+ async executeCommand(input) {
158
+ outputBuffer = [];
159
+ eventBuffer = [];
160
+ lastEvents = [];
161
+ lastTurnResult = null;
162
+ try {
163
+ const result = await engine.executeTurn(input);
164
+ if (result) {
165
+ lastTurnResult = result;
166
+ lastEvents = eventBuffer;
167
+ }
168
+ }
169
+ catch (error) {
170
+ const errorMessage = error instanceof Error ? error.message : String(error);
171
+ outputBuffer.push(`Error: ${errorMessage}`);
172
+ }
173
+ lastOutput = outputBuffer.join('\n');
174
+ testableGame.lastOutput = lastOutput;
175
+ testableGame.lastEvents = lastEvents;
176
+ testableGame.lastTurnResult = lastTurnResult;
177
+ return lastOutput;
178
+ },
179
+ };
180
+ return testableGame;
181
+ }
182
+ /**
183
+ * Run interactive play mode (REPL)
184
+ */
185
+ async function runInteractiveMode(game) {
186
+ const rl = readline.createInterface({
187
+ input: process.stdin,
188
+ output: process.stdout
189
+ });
190
+ let debugMode = false;
191
+ console.log('\n--- Interactive Mode (Fast) ---');
192
+ console.log('Type commands to play. Special commands:');
193
+ console.log(' /quit, /q - Exit the game');
194
+ console.log(' /debug - Toggle debug mode (show events)');
195
+ console.log(' /look, /l - Shortcut for "look"');
196
+ console.log(' /inv, /i - Shortcut for "inventory"');
197
+ console.log('');
198
+ const initialOutput = await game.executeCommand('look');
199
+ console.log(initialOutput);
200
+ const prompt = () => {
201
+ rl.question('\n> ', async (input) => {
202
+ const trimmed = input.trim();
203
+ if (!trimmed) {
204
+ prompt();
205
+ return;
206
+ }
207
+ if (trimmed === '/quit' || trimmed === '/q') {
208
+ console.log('Goodbye!');
209
+ rl.close();
210
+ process.exit(0);
211
+ return;
212
+ }
213
+ if (trimmed === '/debug') {
214
+ debugMode = !debugMode;
215
+ console.log(`Debug mode: ${debugMode ? 'ON' : 'OFF'}`);
216
+ prompt();
217
+ return;
218
+ }
219
+ let command = trimmed;
220
+ if (trimmed === '/look' || trimmed === '/l') {
221
+ command = 'look';
222
+ }
223
+ else if (trimmed === '/inv' || trimmed === '/i') {
224
+ command = 'inventory';
225
+ }
226
+ try {
227
+ const output = await game.executeCommand(command);
228
+ console.log(output);
229
+ if (debugMode && game.lastEvents && game.lastEvents.length > 0) {
230
+ console.log('\n[Events]');
231
+ for (const event of game.lastEvents) {
232
+ console.log(` ${event.type}`);
233
+ }
234
+ }
235
+ }
236
+ catch (error) {
237
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
238
+ }
239
+ prompt();
240
+ });
241
+ };
242
+ prompt();
243
+ }
244
+ /**
245
+ * Main entry point
246
+ */
247
+ async function main() {
248
+ const args = process.argv.slice(2);
249
+ if (args.length === 0) {
250
+ printHelp();
251
+ process.exit(1);
252
+ }
253
+ const options = parseArgs(args);
254
+ // Interactive play mode
255
+ if (options.play) {
256
+ console.log(`Loading story from: ${options.storyPath} (using bundle)`);
257
+ const game = loadStoryAndCreateGame(options.storyPath);
258
+ await runInteractiveMode(game);
259
+ return;
260
+ }
261
+ if (options.transcriptPaths.length === 0) {
262
+ console.error('Error: No transcript files specified');
263
+ printHelp();
264
+ process.exit(1);
265
+ }
266
+ console.log(`Loading story from: ${options.storyPath} (using bundle)`);
267
+ console.log(`Found ${options.transcriptPaths.length} transcript(s) to run`);
268
+ if (options.chain) {
269
+ console.log(`Chain mode: Game state will persist between transcripts`);
270
+ }
271
+ // Load the game once (will reload for each transcript unless chaining)
272
+ let game = loadStoryAndCreateGame(options.storyPath);
273
+ // Run all transcripts
274
+ const results = [];
275
+ for (const transcriptPath of options.transcriptPaths) {
276
+ // Parse the transcript
277
+ const transcript = parseTranscriptFile(transcriptPath);
278
+ // Validate
279
+ const errors = validateTranscript(transcript);
280
+ if (errors.length > 0) {
281
+ console.error(`\nErrors in ${transcriptPath}:`);
282
+ for (const err of errors) {
283
+ console.error(` - ${err}`);
284
+ }
285
+ continue;
286
+ }
287
+ // Reload story for each transcript to reset state (unless chaining)
288
+ if (!options.chain) {
289
+ game = loadStoryAndCreateGame(options.storyPath);
290
+ }
291
+ // Run the transcript with saves directory based on story path
292
+ const savesDirectory = path.join(options.storyPath, 'saves');
293
+ const result = await runTranscript(transcript, game, {
294
+ verbose: options.verbose,
295
+ stopOnFailure: options.stopOnFailure,
296
+ savesDirectory,
297
+ testingExtension: game.testingExtension
298
+ });
299
+ results.push(result);
300
+ // Report individual transcript results
301
+ reportTranscript(result, { verbose: options.verbose });
302
+ // Stop if requested and there was a failure
303
+ if (options.stopOnFailure && result.failed > 0) {
304
+ break;
305
+ }
306
+ }
307
+ // Aggregate results
308
+ const runResult = {
309
+ transcripts: results,
310
+ totalPassed: results.reduce((sum, r) => sum + r.passed, 0),
311
+ totalFailed: results.reduce((sum, r) => sum + r.failed, 0),
312
+ totalExpectedFailures: results.reduce((sum, r) => sum + r.expectedFailures, 0),
313
+ totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
314
+ totalDuration: results.reduce((sum, r) => sum + r.duration, 0)
315
+ };
316
+ // Final report if multiple transcripts
317
+ if (results.length > 1) {
318
+ reportTestRun(runResult, { verbose: options.verbose });
319
+ }
320
+ // Exit with appropriate code
321
+ process.exit(getExitCode(runResult));
322
+ }
323
+ // Run
324
+ main().catch(error => {
325
+ console.error('Fatal error:', error);
326
+ process.exit(1);
327
+ });
328
+ //# sourceMappingURL=fast-cli.js.map