@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,168 @@
1
+ /**
2
+ * Story Loader
3
+ *
4
+ * Dynamically loads and initializes a story for testing.
5
+ */
6
+
7
+ import * as path from 'path';
8
+ import { GameEngine, TurnResult, SequencedEvent } from '@sharpee/engine';
9
+ import { WorldModel, EntityType } from '@sharpee/world-model';
10
+ import { Parser } from '@sharpee/parser-en-us';
11
+ import { PerceptionService } from '@sharpee/stdlib';
12
+ // @ts-ignore
13
+ import { LanguageProvider } from '@sharpee/lang-en-us';
14
+
15
+ /**
16
+ * Interface for a story module
17
+ */
18
+ interface StoryModule {
19
+ story?: any;
20
+ default?: any;
21
+ }
22
+
23
+ /**
24
+ * A testable game instance
25
+ */
26
+ export interface TestableGame {
27
+ engine: GameEngine;
28
+ world: WorldModel;
29
+ lastOutput: string;
30
+ lastEvents: SequencedEvent[];
31
+ lastTurnResult: TurnResult | null;
32
+ executeCommand(input: string): Promise<string>;
33
+ }
34
+
35
+ /**
36
+ * Load a story from a path and create a testable game instance
37
+ */
38
+ export async function loadStory(storyPath: string): Promise<TestableGame> {
39
+ // Resolve the story path
40
+ const resolvedPath = path.isAbsolute(storyPath)
41
+ ? storyPath
42
+ : path.resolve(process.cwd(), storyPath);
43
+
44
+ // Try to load the story module
45
+ let storyModule: StoryModule;
46
+ try {
47
+ // Try loading the compiled dist version first
48
+ const distPath = path.join(resolvedPath, 'dist', 'index.js');
49
+ storyModule = require(distPath);
50
+ } catch (e) {
51
+ try {
52
+ // Fall back to src (for ts-node environments)
53
+ const srcPath = path.join(resolvedPath, 'src', 'index.ts');
54
+ storyModule = require(srcPath);
55
+ } catch (e2) {
56
+ throw new Error(`Could not load story from ${storyPath}: ${e}`);
57
+ }
58
+ }
59
+
60
+ const story = storyModule.story || storyModule.default;
61
+ if (!story) {
62
+ throw new Error(`Story module at ${storyPath} does not export 'story' or 'default'`);
63
+ }
64
+
65
+ // Create the game instance
66
+ return createTestableGame(story);
67
+ }
68
+
69
+ /**
70
+ * Create a testable game from a story instance
71
+ */
72
+ export function createTestableGame(story: any): TestableGame {
73
+ // Create world and player
74
+ const world = new WorldModel();
75
+ const player = world.createEntity('player', EntityType.ACTOR);
76
+ world.setPlayer(player.id);
77
+
78
+ // Create parser and language
79
+ const language = new LanguageProvider();
80
+ const parser = new Parser(language);
81
+
82
+ // Extend parser and language with story-specific vocabulary
83
+ if (story.extendParser) {
84
+ story.extendParser(parser);
85
+ }
86
+ if (story.extendLanguage) {
87
+ story.extendLanguage(language);
88
+ }
89
+
90
+ // Create perception service
91
+ const perceptionService = new PerceptionService();
92
+
93
+ // Create engine (TextService created internally from language provider)
94
+ const engine = new GameEngine({
95
+ world,
96
+ player,
97
+ parser,
98
+ language,
99
+ perceptionService,
100
+ });
101
+
102
+ // Set the story and start
103
+ engine.setStory(story);
104
+ engine.start();
105
+
106
+ // Capture text output and events
107
+ let lastOutput = '';
108
+ let outputBuffer: string[] = [];
109
+ let lastEvents: SequencedEvent[] = [];
110
+ let lastTurnResult: TurnResult | null = null;
111
+
112
+ engine.on('text:output', (text: string) => {
113
+ outputBuffer.push(text);
114
+ });
115
+
116
+ // Capture ALL events through the event emitter (includes scheduler/NPC events)
117
+ let eventBuffer: SequencedEvent[] = [];
118
+ engine.on('event', (event: SequencedEvent) => {
119
+ eventBuffer.push(event);
120
+ });
121
+
122
+ // Create the testable game interface
123
+ const testableGame: TestableGame = {
124
+ engine,
125
+ world,
126
+ lastOutput: '',
127
+ lastEvents: [],
128
+ lastTurnResult: null,
129
+
130
+ async executeCommand(input: string): Promise<string> {
131
+ outputBuffer = [];
132
+ eventBuffer = []; // Reset event buffer for this command
133
+ lastEvents = [];
134
+ lastTurnResult = null;
135
+
136
+ try {
137
+ const result = await engine.executeTurn(input);
138
+ if (result) {
139
+ lastTurnResult = result;
140
+ // Use eventBuffer which captures ALL events (action + NPC + scheduler)
141
+ lastEvents = eventBuffer;
142
+ }
143
+ } catch (error) {
144
+ const errorMessage = error instanceof Error ? error.message : String(error);
145
+ outputBuffer.push(`Error: ${errorMessage}`);
146
+ }
147
+
148
+ lastOutput = outputBuffer.join('\n');
149
+ testableGame.lastOutput = lastOutput;
150
+ testableGame.lastEvents = lastEvents;
151
+ testableGame.lastTurnResult = lastTurnResult;
152
+ return lastOutput;
153
+ },
154
+ };
155
+
156
+ return testableGame;
157
+ }
158
+
159
+ /**
160
+ * Find all transcript files in a directory
161
+ */
162
+ export function findTranscripts(dir: string, pattern: string = '*.transcript'): string[] {
163
+ const glob = require('glob');
164
+ const resolvedDir = path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir);
165
+
166
+ const files = glob.sync(path.join(resolvedDir, '**', pattern));
167
+ return files;
168
+ }
package/src/types.ts ADDED
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Transcript Testing Types
3
+ *
4
+ * Defines the structure of parsed transcripts and test results.
5
+ */
6
+
7
+ // ============================================================================
8
+ // Directive Types (ADR-092: Smart Transcript Directives)
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Types of control flow directives
13
+ */
14
+ export type DirectiveType =
15
+ | 'goal' // [GOAL: name]
16
+ | 'end_goal' // [END GOAL]
17
+ | 'requires' // [REQUIRES: condition]
18
+ | 'ensures' // [ENSURES: condition]
19
+ | 'if' // [IF: condition]
20
+ | 'end_if' // [END IF]
21
+ | 'while' // [WHILE: condition]
22
+ | 'end_while' // [END WHILE]
23
+ | 'navigate' // [NAVIGATE TO: "Room Name"]
24
+ | 'save' // $save <name>
25
+ | 'restore' // $restore <name>
26
+ | 'test-command'; // $teleport, $take, $kill, etc. (ext-testing)
27
+
28
+ /**
29
+ * A control flow directive in the transcript
30
+ */
31
+ export interface Directive {
32
+ type: DirectiveType;
33
+ lineNumber: number;
34
+ condition?: string; // For IF/WHILE/REQUIRES/ENSURES: the condition expression
35
+ target?: string; // For NAVIGATE: the target room name
36
+ goalName?: string; // For GOAL: the goal name
37
+ saveName?: string; // For SAVE/RESTORE: the checkpoint name
38
+ testCommand?: string; // For test-command: the full $command input (e.g., "$teleport kitchen")
39
+ }
40
+
41
+ /**
42
+ * A goal segment with its preconditions, postconditions, and content
43
+ */
44
+ export interface GoalDefinition {
45
+ name: string;
46
+ lineNumber: number;
47
+ requires: string[]; // Precondition expressions
48
+ ensures: string[]; // Postcondition expressions
49
+ startIndex: number; // Index in items array where goal content starts
50
+ endIndex: number; // Index in items array where goal ends
51
+ }
52
+
53
+ /**
54
+ * Result of executing a goal
55
+ */
56
+ export interface GoalResult {
57
+ name: string;
58
+ success: boolean;
59
+ requiresResults: ConditionResult[];
60
+ ensuresResults: ConditionResult[];
61
+ commandsExecuted: number;
62
+ error?: string;
63
+ }
64
+
65
+ /**
66
+ * Result of evaluating a condition
67
+ */
68
+ export interface ConditionResult {
69
+ met: boolean;
70
+ reason: string; // Human-readable explanation
71
+ }
72
+
73
+ /**
74
+ * Result of executing a NAVIGATE directive
75
+ */
76
+ export interface NavigateResult {
77
+ success: boolean;
78
+ path: string[]; // Room names traversed
79
+ commands: string[]; // GO commands executed
80
+ error?: string;
81
+ }
82
+
83
+ /**
84
+ * A comment annotation from the transcript (# lines)
85
+ */
86
+ export interface TranscriptComment {
87
+ lineNumber: number;
88
+ text: string;
89
+ }
90
+
91
+ /**
92
+ * A transcript item - either a command, directive, or comment
93
+ */
94
+ export interface TranscriptItem {
95
+ type: 'command' | 'directive' | 'comment';
96
+ command?: TranscriptCommand;
97
+ directive?: Directive;
98
+ comment?: TranscriptComment;
99
+ }
100
+
101
+ // ============================================================================
102
+ // Original Types
103
+ // ============================================================================
104
+
105
+ /**
106
+ * Header metadata from a transcript file
107
+ */
108
+ export interface TranscriptHeader {
109
+ title?: string;
110
+ story?: string;
111
+ author?: string;
112
+ description?: string;
113
+ [key: string]: string | undefined;
114
+ }
115
+
116
+ /**
117
+ * A single assertion about command output, events, or state
118
+ */
119
+ export interface Assertion {
120
+ type: 'ok' | 'ok-contains' | 'ok-matches' | 'ok-not-contains' | 'fail' | 'skip' | 'todo'
121
+ | 'event-count' | 'event-assert' | 'state-assert';
122
+ value?: string; // For contains/matches
123
+ pattern?: RegExp; // For regex matches
124
+ reason?: string; // For fail/todo
125
+
126
+ // Event assertions
127
+ eventCount?: number; // For event-count assertion
128
+ assertTrue?: boolean; // For event-assert and state-assert: true = must exist, false = must not exist
129
+ eventPosition?: number; // For event-assert: optional 1-based position (omit for "any position")
130
+ eventType?: string; // For event-assert: the event type to match
131
+ eventData?: Record<string, any>; // For event-assert: data properties to match
132
+
133
+ // State assertions
134
+ stateExpression?: string; // For state-assert: the expression to evaluate (e.g., "egg.location = thief")
135
+ }
136
+
137
+ /**
138
+ * A single command with its expected output and assertions
139
+ */
140
+ export interface TranscriptCommand {
141
+ lineNumber: number;
142
+ input: string;
143
+ expectedOutput: string[];
144
+ assertions: Assertion[];
145
+ }
146
+
147
+ /**
148
+ * A fully parsed transcript file
149
+ */
150
+ export interface Transcript {
151
+ filePath: string;
152
+ header: TranscriptHeader;
153
+ commands: TranscriptCommand[]; // Legacy: just commands (for backwards compat)
154
+ items?: TranscriptItem[]; // New: commands + directives in order
155
+ goals?: GoalDefinition[]; // Parsed goal segments
156
+ comments: string[];
157
+ }
158
+
159
+ /**
160
+ * Simplified event info for test results
161
+ */
162
+ export interface TestEventInfo {
163
+ type: string;
164
+ data: Record<string, any>;
165
+ }
166
+
167
+ /**
168
+ * Result of running a single command
169
+ */
170
+ export interface CommandResult {
171
+ command: TranscriptCommand;
172
+ actualOutput: string;
173
+ actualEvents: TestEventInfo[];
174
+ passed: boolean;
175
+ expectedFailure: boolean; // Was marked [FAIL]
176
+ skipped: boolean; // Was marked [SKIP] or [TODO]
177
+ assertionResults: AssertionResult[];
178
+ error?: string;
179
+ }
180
+
181
+ /**
182
+ * Result of a single assertion check
183
+ */
184
+ export interface AssertionResult {
185
+ assertion: Assertion;
186
+ passed: boolean;
187
+ message?: string;
188
+ }
189
+
190
+ /**
191
+ * Result of running an entire transcript
192
+ */
193
+ export interface TranscriptResult {
194
+ transcript: Transcript;
195
+ commands: CommandResult[];
196
+ passed: number;
197
+ failed: number;
198
+ expectedFailures: number;
199
+ skipped: number;
200
+ duration: number; // milliseconds
201
+ }
202
+
203
+ /**
204
+ * Result of running multiple transcripts
205
+ */
206
+ export interface TestRunResult {
207
+ transcripts: TranscriptResult[];
208
+ totalPassed: number;
209
+ totalFailed: number;
210
+ totalExpectedFailures: number;
211
+ totalSkipped: number;
212
+ totalDuration: number;
213
+ }
214
+
215
+ /**
216
+ * Options for the test runner
217
+ */
218
+ /**
219
+ * Interface for ext-testing extension (optional)
220
+ */
221
+ export interface TestingExtensionInterface {
222
+ executeTestCommand(input: string, world: any): { success: boolean; output: string[]; error?: string };
223
+ /** Set context for annotation commands (called after each command execution) */
224
+ setCommandContext?(command: string, response: string): void;
225
+ /** Add an annotation directly (for # comments) */
226
+ addAnnotation?(type: string, text: string, world: any): any;
227
+ }
228
+
229
+ export interface RunnerOptions {
230
+ verbose?: boolean;
231
+ stopOnFailure?: boolean;
232
+ updateExpected?: boolean;
233
+ filter?: string; // Only run commands matching this pattern
234
+ savesDirectory?: string; // Directory for $save/$restore checkpoints
235
+ testingExtension?: TestingExtensionInterface; // Optional ext-testing integration
236
+ }
237
+
238
+ /**
239
+ * Story loader function type
240
+ */
241
+ export type StoryLoader = (storyPath: string) => Promise<{
242
+ engine: any; // GameEngine
243
+ story: any; // Story instance
244
+ }>;
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base-esm.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist-esm",
5
+ "rootDir": "./src",
6
+ "declaration": true,
7
+ "declarationMap": true
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist", "dist-esm", "**/*.test.ts", "**/*.spec.ts"]
11
+ }