@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,403 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Fast Transcript Tester CLI
5
+ *
6
+ * Uses pre-bundled platform (dist/sharpee.js) and story for instant loading.
7
+ * Supports --chain flag for walkthrough testing.
8
+ *
9
+ * Usage:
10
+ * node packages/transcript-tester/dist/fast-cli.js [transcript-files...] [options]
11
+ * node packages/transcript-tester/dist/fast-cli.js --chain wt-*.transcript
12
+ */
13
+
14
+ import * as path from 'path';
15
+ import * as fs from 'fs';
16
+ import * as readline from 'readline';
17
+ import { parseTranscriptFile, validateTranscript } from './parser';
18
+ import { runTranscript } from './runner';
19
+ import {
20
+ reportTranscript,
21
+ reportTestRun,
22
+ getExitCode,
23
+ } from './reporter';
24
+ import { TranscriptResult, TestRunResult } from './types';
25
+
26
+ // Load the pre-bundled platform
27
+ // __dirname is packages/transcript-tester/dist, so go up 3 levels to repo root
28
+ const bundlePath = path.resolve(__dirname, '..', '..', '..', 'dist', 'sharpee.js');
29
+ const platform = require(bundlePath);
30
+
31
+ // Extract what we need from the bundle
32
+ const { GameEngine, WorldModel, EntityType, Parser, LanguageProvider, PerceptionService, TestingExtension } = platform;
33
+
34
+ interface CliOptions {
35
+ transcriptPaths: string[];
36
+ verbose: boolean;
37
+ stopOnFailure: boolean;
38
+ chain: boolean;
39
+ play: boolean;
40
+ storyPath: string;
41
+ }
42
+
43
+ interface TestableGame {
44
+ engine: any;
45
+ world: any;
46
+ testingExtension: any; // TestingExtension instance
47
+ lastOutput: string;
48
+ lastEvents: any[];
49
+ lastTurnResult: any;
50
+ executeCommand(input: string): Promise<string>;
51
+ }
52
+
53
+ /**
54
+ * Parse command line arguments
55
+ */
56
+ function parseArgs(args: string[]): CliOptions {
57
+ const options: CliOptions = {
58
+ transcriptPaths: [],
59
+ verbose: false,
60
+ stopOnFailure: false,
61
+ chain: false,
62
+ play: false,
63
+ storyPath: 'stories/dungeo'
64
+ };
65
+
66
+ let i = 0;
67
+ while (i < args.length) {
68
+ const arg = args[i];
69
+
70
+ if (arg === '--verbose' || arg === '-v') {
71
+ options.verbose = true;
72
+ } else if (arg === '--stop-on-failure' || arg === '-s') {
73
+ options.stopOnFailure = true;
74
+ } else if (arg === '--chain' || arg === '-c') {
75
+ options.chain = true;
76
+ } else if (arg === '--play' || arg === '-p') {
77
+ options.play = true;
78
+ } else if (arg === '--story') {
79
+ i++;
80
+ if (i < args.length) {
81
+ options.storyPath = args[i];
82
+ }
83
+ } else if (arg === '--help' || arg === '-h') {
84
+ printHelp();
85
+ process.exit(0);
86
+ } else if (!arg.startsWith('-')) {
87
+ options.transcriptPaths.push(arg);
88
+ }
89
+
90
+ i++;
91
+ }
92
+
93
+ return options;
94
+ }
95
+
96
+ /**
97
+ * Print help message
98
+ */
99
+ function printHelp(): void {
100
+ console.log(`
101
+ Fast Transcript Tester - Uses pre-bundled platform for instant loading
102
+
103
+ Usage:
104
+ fast-transcript-test [transcript-files...] [options]
105
+
106
+ Arguments:
107
+ transcript-files One or more .transcript files to run
108
+
109
+ Options:
110
+ -c, --chain Chain transcripts (don't reset game state between them)
111
+ -v, --verbose Show detailed output for each command
112
+ -s, --stop-on-failure Stop on first failure
113
+ -p, --play Interactive play mode (REPL)
114
+ --story <path> Story path (default: stories/dungeo)
115
+ -h, --help Show this help message
116
+
117
+ Examples:
118
+ fast-transcript-test stories/dungeo/walkthroughs/wt-01-get-torch-early.transcript
119
+ fast-transcript-test --chain stories/dungeo/walkthroughs/wt-*.transcript
120
+ fast-transcript-test --play
121
+ `);
122
+ }
123
+
124
+ /**
125
+ * Load the story and create a testable game
126
+ */
127
+ function loadStoryAndCreateGame(storyPath: string): TestableGame {
128
+ // Resolve story path
129
+ const resolvedPath = path.isAbsolute(storyPath)
130
+ ? storyPath
131
+ : path.resolve(process.cwd(), storyPath);
132
+
133
+ // Load the story module
134
+ const distPath = path.join(resolvedPath, 'dist', 'index.js');
135
+ const storyModule = require(distPath);
136
+ const story = storyModule.story || storyModule.default;
137
+
138
+ if (!story) {
139
+ throw new Error(`Story module at ${storyPath} does not export 'story' or 'default'`);
140
+ }
141
+
142
+ // Create world and player
143
+ const world = new WorldModel();
144
+ const player = world.createEntity('player', EntityType.ACTOR);
145
+ world.setPlayer(player.id);
146
+
147
+ // Create parser and language
148
+ const language = new LanguageProvider();
149
+ const parser = new Parser(language);
150
+
151
+ // Extend parser and language with story-specific vocabulary
152
+ if (story.extendParser) {
153
+ story.extendParser(parser);
154
+ }
155
+ if (story.extendLanguage) {
156
+ story.extendLanguage(language);
157
+ }
158
+
159
+ // Create perception service
160
+ const perceptionService = new PerceptionService();
161
+
162
+ // Create engine
163
+ const engine = new GameEngine({
164
+ world,
165
+ player,
166
+ parser,
167
+ language,
168
+ perceptionService,
169
+ });
170
+
171
+ // Set the story and start
172
+ engine.setStory(story);
173
+ engine.start();
174
+
175
+ // Create testing extension
176
+ const testingExtension = TestingExtension ? new TestingExtension() : null;
177
+
178
+ // Capture text output and events
179
+ let lastOutput = '';
180
+ let outputBuffer: string[] = [];
181
+ let lastEvents: any[] = [];
182
+ let lastTurnResult: any = null;
183
+
184
+ engine.on('text:output', (text: string) => {
185
+ outputBuffer.push(text);
186
+ });
187
+
188
+ let eventBuffer: any[] = [];
189
+ engine.on('event', (event: any) => {
190
+ eventBuffer.push(event);
191
+ });
192
+
193
+ const testableGame: TestableGame = {
194
+ engine,
195
+ world,
196
+ testingExtension,
197
+ lastOutput: '',
198
+ lastEvents: [],
199
+ lastTurnResult: null,
200
+
201
+ async executeCommand(input: string): Promise<string> {
202
+ outputBuffer = [];
203
+ eventBuffer = [];
204
+ lastEvents = [];
205
+ lastTurnResult = null;
206
+
207
+ try {
208
+ const result = await engine.executeTurn(input);
209
+ if (result) {
210
+ lastTurnResult = result;
211
+ lastEvents = eventBuffer;
212
+ }
213
+ } catch (error) {
214
+ const errorMessage = error instanceof Error ? error.message : String(error);
215
+ outputBuffer.push(`Error: ${errorMessage}`);
216
+ }
217
+
218
+ lastOutput = outputBuffer.join('\n');
219
+ testableGame.lastOutput = lastOutput;
220
+ testableGame.lastEvents = lastEvents;
221
+ testableGame.lastTurnResult = lastTurnResult;
222
+ return lastOutput;
223
+ },
224
+ };
225
+
226
+ return testableGame;
227
+ }
228
+
229
+ /**
230
+ * Run interactive play mode (REPL)
231
+ */
232
+ async function runInteractiveMode(game: TestableGame): Promise<void> {
233
+ const rl = readline.createInterface({
234
+ input: process.stdin,
235
+ output: process.stdout
236
+ });
237
+
238
+ let debugMode = false;
239
+
240
+ console.log('\n--- Interactive Mode (Fast) ---');
241
+ console.log('Type commands to play. Special commands:');
242
+ console.log(' /quit, /q - Exit the game');
243
+ console.log(' /debug - Toggle debug mode (show events)');
244
+ console.log(' /look, /l - Shortcut for "look"');
245
+ console.log(' /inv, /i - Shortcut for "inventory"');
246
+ console.log('');
247
+
248
+ const initialOutput = await game.executeCommand('look');
249
+ console.log(initialOutput);
250
+
251
+ const prompt = (): void => {
252
+ rl.question('\n> ', async (input) => {
253
+ const trimmed = input.trim();
254
+
255
+ if (!trimmed) {
256
+ prompt();
257
+ return;
258
+ }
259
+
260
+ if (trimmed === '/quit' || trimmed === '/q') {
261
+ console.log('Goodbye!');
262
+ rl.close();
263
+ process.exit(0);
264
+ return;
265
+ }
266
+
267
+ if (trimmed === '/debug') {
268
+ debugMode = !debugMode;
269
+ console.log(`Debug mode: ${debugMode ? 'ON' : 'OFF'}`);
270
+ prompt();
271
+ return;
272
+ }
273
+
274
+ let command = trimmed;
275
+ if (trimmed === '/look' || trimmed === '/l') {
276
+ command = 'look';
277
+ } else if (trimmed === '/inv' || trimmed === '/i') {
278
+ command = 'inventory';
279
+ }
280
+
281
+ try {
282
+ const output = await game.executeCommand(command);
283
+ console.log(output);
284
+
285
+ if (debugMode && game.lastEvents && game.lastEvents.length > 0) {
286
+ console.log('\n[Events]');
287
+ for (const event of game.lastEvents) {
288
+ console.log(` ${event.type}`);
289
+ }
290
+ }
291
+ } catch (error) {
292
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
293
+ }
294
+
295
+ prompt();
296
+ });
297
+ };
298
+
299
+ prompt();
300
+ }
301
+
302
+ /**
303
+ * Main entry point
304
+ */
305
+ async function main(): Promise<void> {
306
+ const args = process.argv.slice(2);
307
+
308
+ if (args.length === 0) {
309
+ printHelp();
310
+ process.exit(1);
311
+ }
312
+
313
+ const options = parseArgs(args);
314
+
315
+ // Interactive play mode
316
+ if (options.play) {
317
+ console.log(`Loading story from: ${options.storyPath} (using bundle)`);
318
+ const game = loadStoryAndCreateGame(options.storyPath);
319
+ await runInteractiveMode(game);
320
+ return;
321
+ }
322
+
323
+ if (options.transcriptPaths.length === 0) {
324
+ console.error('Error: No transcript files specified');
325
+ printHelp();
326
+ process.exit(1);
327
+ }
328
+
329
+ console.log(`Loading story from: ${options.storyPath} (using bundle)`);
330
+ console.log(`Found ${options.transcriptPaths.length} transcript(s) to run`);
331
+ if (options.chain) {
332
+ console.log(`Chain mode: Game state will persist between transcripts`);
333
+ }
334
+
335
+ // Load the game once (will reload for each transcript unless chaining)
336
+ let game = loadStoryAndCreateGame(options.storyPath);
337
+
338
+ // Run all transcripts
339
+ const results: TranscriptResult[] = [];
340
+
341
+ for (const transcriptPath of options.transcriptPaths) {
342
+ // Parse the transcript
343
+ const transcript = parseTranscriptFile(transcriptPath);
344
+
345
+ // Validate
346
+ const errors = validateTranscript(transcript);
347
+ if (errors.length > 0) {
348
+ console.error(`\nErrors in ${transcriptPath}:`);
349
+ for (const err of errors) {
350
+ console.error(` - ${err}`);
351
+ }
352
+ continue;
353
+ }
354
+
355
+ // Reload story for each transcript to reset state (unless chaining)
356
+ if (!options.chain) {
357
+ game = loadStoryAndCreateGame(options.storyPath);
358
+ }
359
+
360
+ // Run the transcript with saves directory based on story path
361
+ const savesDirectory = path.join(options.storyPath, 'saves');
362
+ const result = await runTranscript(transcript, game, {
363
+ verbose: options.verbose,
364
+ stopOnFailure: options.stopOnFailure,
365
+ savesDirectory,
366
+ testingExtension: game.testingExtension
367
+ });
368
+
369
+ results.push(result);
370
+
371
+ // Report individual transcript results
372
+ reportTranscript(result, { verbose: options.verbose });
373
+
374
+ // Stop if requested and there was a failure
375
+ if (options.stopOnFailure && result.failed > 0) {
376
+ break;
377
+ }
378
+ }
379
+
380
+ // Aggregate results
381
+ const runResult: TestRunResult = {
382
+ transcripts: results,
383
+ totalPassed: results.reduce((sum, r) => sum + r.passed, 0),
384
+ totalFailed: results.reduce((sum, r) => sum + r.failed, 0),
385
+ totalExpectedFailures: results.reduce((sum, r) => sum + r.expectedFailures, 0),
386
+ totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
387
+ totalDuration: results.reduce((sum, r) => sum + r.duration, 0)
388
+ };
389
+
390
+ // Final report if multiple transcripts
391
+ if (results.length > 1) {
392
+ reportTestRun(runResult, { verbose: options.verbose });
393
+ }
394
+
395
+ // Exit with appropriate code
396
+ process.exit(getExitCode(runResult));
397
+ }
398
+
399
+ // Run
400
+ main().catch(error => {
401
+ console.error('Fatal error:', error);
402
+ process.exit(1);
403
+ });
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @sharpee/transcript-tester
3
+ *
4
+ * Transcript-based testing for Sharpee interactive fiction stories.
5
+ *
6
+ * Usage:
7
+ * npx transcript-test <story-path> [transcripts...]
8
+ *
9
+ * See ADR-073 for format specification.
10
+ */
11
+
12
+ // Types
13
+ export * from './types';
14
+
15
+ // Parser
16
+ export { parseTranscript, parseTranscriptFile, validateTranscript } from './parser';
17
+
18
+ // Runner
19
+ export { runTranscript } from './runner';
20
+
21
+ // Reporter
22
+ export { reportTranscript, reportTestRun, getExitCode } from './reporter';
23
+
24
+ // Story Loader
25
+ export { loadStory, createTestableGame, findTranscripts } from './story-loader';
26
+ export type { TestableGame } from './story-loader';