@sharpee/ext-testing 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 (121) hide show
  1. package/LICENSE +21 -0
  2. package/dist/annotations/context.d.ts +16 -0
  3. package/dist/annotations/context.d.ts.map +1 -0
  4. package/dist/annotations/context.js +42 -0
  5. package/dist/annotations/context.js.map +1 -0
  6. package/dist/annotations/index.d.ts +6 -0
  7. package/dist/annotations/index.d.ts.map +1 -0
  8. package/dist/annotations/index.js +12 -0
  9. package/dist/annotations/index.js.map +1 -0
  10. package/dist/annotations/store.d.ts +11 -0
  11. package/dist/annotations/store.d.ts.map +1 -0
  12. package/dist/annotations/store.js +191 -0
  13. package/dist/annotations/store.js.map +1 -0
  14. package/dist/checkpoints/index.d.ts +3 -0
  15. package/dist/checkpoints/index.d.ts.map +1 -0
  16. package/dist/checkpoints/index.js +12 -0
  17. package/dist/checkpoints/index.js.map +1 -0
  18. package/dist/checkpoints/serializer.d.ts +21 -0
  19. package/dist/checkpoints/serializer.d.ts.map +1 -0
  20. package/dist/checkpoints/serializer.js +95 -0
  21. package/dist/checkpoints/serializer.js.map +1 -0
  22. package/dist/checkpoints/store.d.ts +20 -0
  23. package/dist/checkpoints/store.d.ts.map +1 -0
  24. package/dist/checkpoints/store.js +193 -0
  25. package/dist/checkpoints/store.js.map +1 -0
  26. package/dist/commands/index.d.ts +2 -0
  27. package/dist/commands/index.d.ts.map +1 -0
  28. package/dist/commands/index.js +8 -0
  29. package/dist/commands/index.js.map +1 -0
  30. package/dist/commands/registry.d.ts +28 -0
  31. package/dist/commands/registry.d.ts.map +1 -0
  32. package/dist/commands/registry.js +70 -0
  33. package/dist/commands/registry.js.map +1 -0
  34. package/dist/context/debug-context.d.ts +21 -0
  35. package/dist/context/debug-context.d.ts.map +1 -0
  36. package/dist/context/debug-context.js +172 -0
  37. package/dist/context/debug-context.js.map +1 -0
  38. package/dist/context/index.d.ts +2 -0
  39. package/dist/context/index.d.ts.map +1 -0
  40. package/dist/context/index.js +8 -0
  41. package/dist/context/index.js.map +1 -0
  42. package/dist/extension.d.ts +78 -0
  43. package/dist/extension.d.ts.map +1 -0
  44. package/dist/extension.js +938 -0
  45. package/dist/extension.js.map +1 -0
  46. package/dist/index.d.ts +40 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +63 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/types.d.ts +375 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +8 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist-npm/annotations/context.d.ts +16 -0
  55. package/dist-npm/annotations/context.d.ts.map +1 -0
  56. package/dist-npm/annotations/context.js +42 -0
  57. package/dist-npm/annotations/context.js.map +1 -0
  58. package/dist-npm/annotations/index.d.ts +6 -0
  59. package/dist-npm/annotations/index.d.ts.map +1 -0
  60. package/dist-npm/annotations/index.js +12 -0
  61. package/dist-npm/annotations/index.js.map +1 -0
  62. package/dist-npm/annotations/store.d.ts +11 -0
  63. package/dist-npm/annotations/store.d.ts.map +1 -0
  64. package/dist-npm/annotations/store.js +191 -0
  65. package/dist-npm/annotations/store.js.map +1 -0
  66. package/dist-npm/checkpoints/index.d.ts +3 -0
  67. package/dist-npm/checkpoints/index.d.ts.map +1 -0
  68. package/dist-npm/checkpoints/index.js +12 -0
  69. package/dist-npm/checkpoints/index.js.map +1 -0
  70. package/dist-npm/checkpoints/serializer.d.ts +21 -0
  71. package/dist-npm/checkpoints/serializer.d.ts.map +1 -0
  72. package/dist-npm/checkpoints/serializer.js +95 -0
  73. package/dist-npm/checkpoints/serializer.js.map +1 -0
  74. package/dist-npm/checkpoints/store.d.ts +20 -0
  75. package/dist-npm/checkpoints/store.d.ts.map +1 -0
  76. package/dist-npm/checkpoints/store.js +193 -0
  77. package/dist-npm/checkpoints/store.js.map +1 -0
  78. package/dist-npm/commands/index.d.ts +2 -0
  79. package/dist-npm/commands/index.d.ts.map +1 -0
  80. package/dist-npm/commands/index.js +8 -0
  81. package/dist-npm/commands/index.js.map +1 -0
  82. package/dist-npm/commands/registry.d.ts +28 -0
  83. package/dist-npm/commands/registry.d.ts.map +1 -0
  84. package/dist-npm/commands/registry.js +70 -0
  85. package/dist-npm/commands/registry.js.map +1 -0
  86. package/dist-npm/context/debug-context.d.ts +21 -0
  87. package/dist-npm/context/debug-context.d.ts.map +1 -0
  88. package/dist-npm/context/debug-context.js +172 -0
  89. package/dist-npm/context/debug-context.js.map +1 -0
  90. package/dist-npm/context/index.d.ts +2 -0
  91. package/dist-npm/context/index.d.ts.map +1 -0
  92. package/dist-npm/context/index.js +8 -0
  93. package/dist-npm/context/index.js.map +1 -0
  94. package/dist-npm/extension.d.ts +78 -0
  95. package/dist-npm/extension.d.ts.map +1 -0
  96. package/dist-npm/extension.js +938 -0
  97. package/dist-npm/extension.js.map +1 -0
  98. package/dist-npm/index.d.ts +40 -0
  99. package/dist-npm/index.d.ts.map +1 -0
  100. package/dist-npm/index.js +63 -0
  101. package/dist-npm/index.js.map +1 -0
  102. package/dist-npm/types.d.ts +375 -0
  103. package/dist-npm/types.d.ts.map +1 -0
  104. package/dist-npm/types.js +8 -0
  105. package/dist-npm/types.js.map +1 -0
  106. package/package.json +42 -0
  107. package/src/annotations/context.ts +47 -0
  108. package/src/annotations/index.ts +6 -0
  109. package/src/annotations/store.ts +219 -0
  110. package/src/checkpoints/index.ts +2 -0
  111. package/src/checkpoints/serializer.ts +121 -0
  112. package/src/checkpoints/store.ts +188 -0
  113. package/src/commands/index.ts +1 -0
  114. package/src/commands/registry.ts +81 -0
  115. package/src/context/debug-context.ts +209 -0
  116. package/src/context/index.ts +1 -0
  117. package/src/extension.ts +1089 -0
  118. package/src/index.ts +69 -0
  119. package/src/types.ts +469 -0
  120. package/tsconfig.json +16 -0
  121. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,938 @@
1
+ "use strict";
2
+ /**
3
+ * Testing Extension
4
+ *
5
+ * Main extension class that provides debug and testing capabilities.
6
+ * Implements the ITestingExtension interface.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.TestingExtension = void 0;
10
+ const debug_context_js_1 = require("./context/debug-context.js");
11
+ const registry_js_1 = require("./commands/registry.js");
12
+ const store_js_1 = require("./checkpoints/store.js");
13
+ const serializer_js_1 = require("./checkpoints/serializer.js");
14
+ const index_js_1 = require("./annotations/index.js");
15
+ /**
16
+ * Default configuration
17
+ */
18
+ const DEFAULT_CONFIG = {
19
+ debugMode: {
20
+ enabled: true,
21
+ prefix: 'gdt',
22
+ password: null,
23
+ },
24
+ testMode: {
25
+ enabled: true,
26
+ deterministicRandom: true,
27
+ assertions: true,
28
+ },
29
+ checkpoints: {
30
+ directory: './checkpoints',
31
+ },
32
+ commands: [],
33
+ };
34
+ /**
35
+ * Testing Extension implementation
36
+ */
37
+ class TestingExtension {
38
+ config;
39
+ commands;
40
+ checkpoints;
41
+ annotations;
42
+ isDebugModeActive = false;
43
+ // Context tracking for annotations
44
+ lastCommand = '';
45
+ lastResponse = '';
46
+ constructor(config = {}) {
47
+ // Merge config with defaults
48
+ this.config = {
49
+ debugMode: { ...DEFAULT_CONFIG.debugMode, ...config.debugMode },
50
+ testMode: { ...DEFAULT_CONFIG.testMode, ...config.testMode },
51
+ checkpoints: { ...DEFAULT_CONFIG.checkpoints, ...config.checkpoints },
52
+ commands: config.commands ?? [],
53
+ };
54
+ // Initialize command registry
55
+ this.commands = (0, registry_js_1.createCommandRegistry)();
56
+ // Register built-in commands
57
+ this.registerBuiltInCommands();
58
+ // Register custom commands
59
+ for (const command of this.config.commands ?? []) {
60
+ this.commands.register(command);
61
+ }
62
+ // Initialize checkpoint store
63
+ // Use memory store by default; file store requires explicit path
64
+ if (this.config.checkpoints?.directory) {
65
+ this.checkpoints = (0, store_js_1.createFileStore)(this.config.checkpoints.directory);
66
+ }
67
+ else {
68
+ this.checkpoints = (0, store_js_1.createMemoryStore)();
69
+ }
70
+ // Initialize annotation store
71
+ this.annotations = (0, index_js_1.createAnnotationStore)();
72
+ }
73
+ /**
74
+ * Register built-in debug commands
75
+ */
76
+ registerBuiltInCommands() {
77
+ // Help command
78
+ this.commands.register({
79
+ code: 'HE',
80
+ testSyntax: 'help',
81
+ name: 'Help',
82
+ description: 'Display available commands',
83
+ category: 'utility',
84
+ execute: (context, args) => this.cmdHelp(context, args),
85
+ });
86
+ // Teleport command
87
+ this.commands.register({
88
+ code: 'AH',
89
+ testSyntax: 'teleport',
90
+ name: 'Teleport',
91
+ description: 'Teleport player to a room',
92
+ category: 'alter',
93
+ usage: 'teleport <room-id>',
94
+ execute: (context, args) => this.cmdTeleport(context, args),
95
+ });
96
+ // Take command
97
+ this.commands.register({
98
+ code: 'TK',
99
+ testSyntax: 'take',
100
+ name: 'Take Item',
101
+ description: 'Give an item to the player',
102
+ category: 'alter',
103
+ usage: 'take <item-id>',
104
+ execute: (context, args) => this.cmdTake(context, args),
105
+ });
106
+ // Move command
107
+ this.commands.register({
108
+ code: 'AO',
109
+ testSyntax: 'move',
110
+ name: 'Move Object',
111
+ description: 'Move an object to a location',
112
+ category: 'alter',
113
+ usage: 'move <object-id> <location-id>',
114
+ execute: (context, args) => this.cmdMove(context, args),
115
+ });
116
+ // Remove command
117
+ this.commands.register({
118
+ code: 'RO',
119
+ testSyntax: 'remove',
120
+ name: 'Remove Object',
121
+ description: 'Remove an object from the game',
122
+ category: 'alter',
123
+ usage: 'remove <object-id>',
124
+ execute: (context, args) => this.cmdRemove(context, args),
125
+ });
126
+ // Display Player command
127
+ this.commands.register({
128
+ code: 'DA',
129
+ testSyntax: 'player',
130
+ name: 'Display Adventurer',
131
+ description: 'Show player state and inventory',
132
+ category: 'display',
133
+ execute: (context, args) => this.cmdDisplayPlayer(context, args),
134
+ });
135
+ // Display Room command
136
+ this.commands.register({
137
+ code: 'DR',
138
+ testSyntax: 'room',
139
+ name: 'Display Room',
140
+ description: 'Show current room details',
141
+ category: 'display',
142
+ execute: (context, args) => this.cmdDisplayRoom(context, args),
143
+ });
144
+ // Display Object command
145
+ this.commands.register({
146
+ code: 'DO',
147
+ testSyntax: 'object',
148
+ name: 'Display Object',
149
+ description: 'Show object details',
150
+ category: 'display',
151
+ usage: 'object <object-id>',
152
+ execute: (context, args) => this.cmdDisplayObject(context, args),
153
+ });
154
+ // Saves list command
155
+ this.commands.register({
156
+ code: 'SL',
157
+ testSyntax: 'saves',
158
+ name: 'List Saves',
159
+ description: 'List available checkpoints',
160
+ category: 'utility',
161
+ execute: (context, args) => this.cmdListSaves(context, args),
162
+ });
163
+ // Describe Entity command (detailed)
164
+ this.commands.register({
165
+ code: 'DE',
166
+ testSyntax: 'describe',
167
+ name: 'Describe Entity',
168
+ description: 'Full entity inspection with all traits',
169
+ category: 'display',
170
+ usage: 'describe <entity-id>',
171
+ execute: (context, args) => this.cmdDescribeEntity(context, args),
172
+ });
173
+ // Display State command
174
+ this.commands.register({
175
+ code: 'DS',
176
+ testSyntax: 'state',
177
+ name: 'Display State',
178
+ description: 'Show game state (turn, score, entity counts)',
179
+ category: 'display',
180
+ execute: (context, args) => this.cmdDisplayState(context, args),
181
+ });
182
+ // Display Exits command
183
+ this.commands.register({
184
+ code: 'DX',
185
+ testSyntax: 'exits',
186
+ name: 'Display Exits',
187
+ description: 'Show room exits in detail',
188
+ category: 'display',
189
+ usage: 'exits [room-id]',
190
+ execute: (context, args) => this.cmdDisplayExits(context, args),
191
+ });
192
+ // No Deaths (immortality on)
193
+ this.commands.register({
194
+ code: 'ND',
195
+ testSyntax: 'immortal',
196
+ name: 'No Deaths',
197
+ description: 'Enable immortality mode',
198
+ category: 'toggle',
199
+ execute: (context, args) => this.cmdNoDeaths(context, args),
200
+ });
201
+ // Restore Deaths (immortality off)
202
+ this.commands.register({
203
+ code: 'RD',
204
+ testSyntax: 'mortal',
205
+ name: 'Restore Deaths',
206
+ description: 'Disable immortality mode',
207
+ category: 'toggle',
208
+ execute: (context, args) => this.cmdRestoreDeaths(context, args),
209
+ });
210
+ // Kill Entity
211
+ this.commands.register({
212
+ code: 'KL',
213
+ testSyntax: 'kill',
214
+ name: 'Kill Entity',
215
+ description: 'Kill an NPC or entity',
216
+ category: 'alter',
217
+ usage: 'kill <entity-id>',
218
+ execute: (context, args) => this.cmdKillEntity(context, args),
219
+ });
220
+ // Exit GDT
221
+ this.commands.register({
222
+ code: 'EX',
223
+ testSyntax: 'exit',
224
+ name: 'Exit',
225
+ description: 'Exit debug mode',
226
+ category: 'utility',
227
+ execute: (context, args) => this.cmdExit(context, args),
228
+ });
229
+ // =========================================================================
230
+ // Annotation Commands (ADR-109)
231
+ // =========================================================================
232
+ // Bug annotation
233
+ this.commands.register({
234
+ code: 'BG',
235
+ testSyntax: 'bug',
236
+ name: 'Bug',
237
+ description: 'Flag a bug',
238
+ category: 'annotation',
239
+ usage: 'bug <description>',
240
+ execute: (context, args) => this.cmdBug(context, args),
241
+ });
242
+ // Note annotation
243
+ this.commands.register({
244
+ code: 'NT',
245
+ testSyntax: 'note',
246
+ name: 'Note',
247
+ description: 'Add a general note',
248
+ category: 'annotation',
249
+ usage: 'note <text>',
250
+ execute: (context, args) => this.cmdNote(context, args),
251
+ });
252
+ // Confusing annotation
253
+ this.commands.register({
254
+ code: 'CF',
255
+ testSyntax: 'confusing',
256
+ name: 'Confusing',
257
+ description: 'Mark last interaction as confusing',
258
+ category: 'annotation',
259
+ execute: (context, args) => this.cmdConfusing(context, args),
260
+ });
261
+ // Expected behavior annotation
262
+ this.commands.register({
263
+ code: 'EP',
264
+ testSyntax: 'expected',
265
+ name: 'Expected',
266
+ description: 'Document expected behavior',
267
+ category: 'annotation',
268
+ usage: 'expected <what was expected>',
269
+ execute: (context, args) => this.cmdExpected(context, args),
270
+ });
271
+ // Bookmark annotation
272
+ this.commands.register({
273
+ code: 'BM',
274
+ testSyntax: 'bookmark',
275
+ name: 'Bookmark',
276
+ description: 'Create a named save point',
277
+ category: 'annotation',
278
+ usage: 'bookmark <name>',
279
+ execute: (context, args) => this.cmdBookmark(context, args),
280
+ });
281
+ // Session management
282
+ this.commands.register({
283
+ code: 'SS',
284
+ testSyntax: 'session',
285
+ name: 'Session',
286
+ description: 'Start or end annotation session',
287
+ category: 'annotation',
288
+ usage: 'session start <name> | session end',
289
+ execute: (context, args) => this.cmdSession(context, args),
290
+ });
291
+ // Review annotations
292
+ this.commands.register({
293
+ code: 'RV',
294
+ testSyntax: 'review',
295
+ name: 'Review',
296
+ description: 'Show current session annotations',
297
+ category: 'annotation',
298
+ execute: (context, args) => this.cmdReview(context, args),
299
+ });
300
+ // Export annotations
301
+ this.commands.register({
302
+ code: 'XP',
303
+ testSyntax: 'export',
304
+ name: 'Export',
305
+ description: 'Export annotations as markdown',
306
+ category: 'annotation',
307
+ execute: (context, args) => this.cmdExport(context, args),
308
+ });
309
+ }
310
+ // =========================================================================
311
+ // Public API
312
+ // =========================================================================
313
+ /**
314
+ * Execute a GDT-style command
315
+ */
316
+ executeGdtCommand(input, world) {
317
+ if (!this.config.debugMode?.enabled) {
318
+ return { success: false, output: [], error: 'Debug mode is disabled' };
319
+ }
320
+ const { code, args } = (0, registry_js_1.parseGdtInput)(input);
321
+ if (!code) {
322
+ return { success: false, output: [], error: 'No command specified' };
323
+ }
324
+ const command = this.commands.getByCode(code);
325
+ if (!command) {
326
+ return { success: false, output: [], error: `Unknown command: ${code}` };
327
+ }
328
+ const context = this.createContext(world);
329
+ return command.execute(context, args);
330
+ }
331
+ /**
332
+ * Execute a test command ($command syntax)
333
+ */
334
+ executeTestCommand(input, world) {
335
+ if (!this.config.testMode?.enabled) {
336
+ return { success: false, output: [], error: 'Test mode is disabled' };
337
+ }
338
+ const parsed = (0, registry_js_1.parseTestInput)(input);
339
+ if (!parsed) {
340
+ return { success: false, output: [], error: 'Invalid test command format' };
341
+ }
342
+ const { syntax, args } = parsed;
343
+ const command = this.commands.getByTestSyntax(syntax);
344
+ if (!command) {
345
+ return { success: false, output: [], error: `Unknown test command: $${syntax}` };
346
+ }
347
+ const context = this.createContext(world);
348
+ return command.execute(context, args);
349
+ }
350
+ /**
351
+ * Create a debug context for the world
352
+ */
353
+ createContext(world) {
354
+ return (0, debug_context_js_1.createDebugContext)(world);
355
+ }
356
+ /**
357
+ * Save a checkpoint
358
+ */
359
+ async saveCheckpoint(name, world) {
360
+ const data = (0, serializer_js_1.serializeCheckpoint)(world, name);
361
+ await this.checkpoints.save(name, data);
362
+ }
363
+ /**
364
+ * Restore from a checkpoint
365
+ */
366
+ async restoreCheckpoint(name, world) {
367
+ const data = await this.checkpoints.load(name);
368
+ if (!data) {
369
+ return false;
370
+ }
371
+ (0, serializer_js_1.deserializeCheckpoint)(data, world);
372
+ return true;
373
+ }
374
+ /**
375
+ * Set context for annotation commands (called by transcript-tester after each command)
376
+ */
377
+ setCommandContext(command, response) {
378
+ this.lastCommand = command;
379
+ this.lastResponse = response;
380
+ }
381
+ /**
382
+ * Add an annotation directly (for # comments from transcript-tester)
383
+ */
384
+ addAnnotation(type, text, world) {
385
+ const context = (0, index_js_1.captureContext)(world, this.lastCommand, this.lastResponse);
386
+ return this.annotations.addAnnotation(type, text, context);
387
+ }
388
+ // =========================================================================
389
+ // Built-in Command Implementations
390
+ // =========================================================================
391
+ cmdHelp(_context, _args) {
392
+ const output = ['Available Commands:', ''];
393
+ const categories = ['display', 'alter', 'toggle', 'utility', 'test'];
394
+ for (const category of categories) {
395
+ const commands = this.commands.getByCategory(category);
396
+ if (commands.length === 0)
397
+ continue;
398
+ output.push(`${category.toUpperCase()}:`);
399
+ for (const cmd of commands) {
400
+ const testSyntax = cmd.testSyntax ? `$${cmd.testSyntax}` : '';
401
+ output.push(` ${cmd.code.padEnd(4)} ${testSyntax.padEnd(12)} - ${cmd.description}`);
402
+ }
403
+ output.push('');
404
+ }
405
+ return { success: true, output };
406
+ }
407
+ cmdTeleport(context, args) {
408
+ if (args.length === 0) {
409
+ return { success: false, output: [], error: 'Usage: teleport <room-name>' };
410
+ }
411
+ // Join args to support multi-word room names like "Reservoir North"
412
+ const roomId = args.join(' ');
413
+ const room = context.findRoom(roomId);
414
+ if (!room) {
415
+ return { success: false, output: [], error: `Room not found: ${roomId}` };
416
+ }
417
+ const success = context.teleportPlayer(room.id);
418
+ if (success) {
419
+ return {
420
+ success: true,
421
+ output: [`Teleported to: ${room.name || room.id}`],
422
+ };
423
+ }
424
+ else {
425
+ return { success: false, output: [], error: 'Failed to teleport' };
426
+ }
427
+ }
428
+ cmdTake(context, args) {
429
+ if (args.length === 0) {
430
+ return { success: false, output: [], error: 'Usage: take <item-name>' };
431
+ }
432
+ // Join args to support multi-word names like "brass lantern"
433
+ const itemId = args.join(' ');
434
+ const item = context.findEntity(itemId);
435
+ if (!item) {
436
+ return { success: false, output: [], error: `Item not found: ${itemId}` };
437
+ }
438
+ const success = context.takeObject(item.id);
439
+ if (success) {
440
+ return {
441
+ success: true,
442
+ output: [`Took: ${item.name || item.id}`],
443
+ };
444
+ }
445
+ else {
446
+ return { success: false, output: [], error: 'Failed to take item' };
447
+ }
448
+ }
449
+ cmdMove(context, args) {
450
+ if (args.length < 2) {
451
+ return { success: false, output: [], error: 'Usage: move <object-id> <location-id>' };
452
+ }
453
+ const [objectId, locationId] = args;
454
+ const object = context.findEntity(objectId);
455
+ const location = context.findEntity(locationId);
456
+ if (!object) {
457
+ return { success: false, output: [], error: `Object not found: ${objectId}` };
458
+ }
459
+ if (!location) {
460
+ return { success: false, output: [], error: `Location not found: ${locationId}` };
461
+ }
462
+ const success = context.moveObject(object.id, location.id);
463
+ if (success) {
464
+ return {
465
+ success: true,
466
+ output: [`Moved ${object.name || object.id} to ${location.name || location.id}`],
467
+ };
468
+ }
469
+ else {
470
+ return { success: false, output: [], error: 'Failed to move object' };
471
+ }
472
+ }
473
+ cmdRemove(context, args) {
474
+ if (args.length === 0) {
475
+ return { success: false, output: [], error: 'Usage: remove <object-name>' };
476
+ }
477
+ // Join args to support multi-word names
478
+ const objectId = args.join(' ');
479
+ const object = context.findEntity(objectId);
480
+ if (!object) {
481
+ return { success: false, output: [], error: `Object not found: ${objectId}` };
482
+ }
483
+ const success = context.removeObject(object.id);
484
+ if (success) {
485
+ return {
486
+ success: true,
487
+ output: [`Removed: ${object.name || object.id}`],
488
+ };
489
+ }
490
+ else {
491
+ return { success: false, output: [], error: 'Failed to remove object' };
492
+ }
493
+ }
494
+ cmdDisplayPlayer(context, _args) {
495
+ const player = context.player;
496
+ const location = context.getPlayerLocation();
497
+ const inventory = context.getInventory();
498
+ const output = [
499
+ 'PLAYER STATUS',
500
+ ` ID: ${player.id}`,
501
+ ` Location: ${location?.name || location?.id || 'unknown'}`,
502
+ ` Inventory (${inventory.length} items):`,
503
+ ];
504
+ for (const item of inventory) {
505
+ output.push(` - ${item.name || item.id}`);
506
+ }
507
+ return { success: true, output };
508
+ }
509
+ cmdDisplayRoom(context, _args) {
510
+ const room = context.getPlayerLocation();
511
+ if (!room) {
512
+ return { success: false, output: [], error: 'Player location unknown' };
513
+ }
514
+ const contents = context.getContents(room.id);
515
+ const objects = contents.filter((e) => e.id !== context.player.id);
516
+ const output = [
517
+ 'ROOM STATUS',
518
+ ` ID: ${room.id}`,
519
+ ` Name: ${room.name || 'unnamed'}`,
520
+ ` Contents (${objects.length} objects):`,
521
+ ];
522
+ for (const obj of objects) {
523
+ output.push(` - ${obj.name || obj.id}`);
524
+ }
525
+ // TODO: Show exits when that API is available
526
+ return { success: true, output };
527
+ }
528
+ cmdDisplayObject(context, args) {
529
+ if (args.length === 0) {
530
+ return { success: false, output: [], error: 'Usage: object <object-name>' };
531
+ }
532
+ // Join args to support multi-word names
533
+ const objectId = args.join(' ');
534
+ const object = context.findEntity(objectId);
535
+ if (!object) {
536
+ return { success: false, output: [], error: `Object not found: ${objectId}` };
537
+ }
538
+ const locationId = context.world.getLocation(object.id);
539
+ const location = locationId ? context.world.getEntity(locationId) : undefined;
540
+ const output = [
541
+ 'OBJECT STATUS',
542
+ ` ID: ${object.id}`,
543
+ ` Name: ${object.name || 'unnamed'}`,
544
+ ` Type: ${object.type}`,
545
+ ` Location: ${location?.name || location?.id || 'none'}`,
546
+ ` Traits:`,
547
+ ];
548
+ if (object.traits) {
549
+ for (const [, trait] of object.traits) {
550
+ output.push(` - ${trait.type}`);
551
+ }
552
+ }
553
+ return { success: true, output };
554
+ }
555
+ cmdListSaves(_context, _args) {
556
+ // Note: This is a synchronous stub. Full checkpoint listing
557
+ // requires async access - use $saves from transcript-tester instead.
558
+ return {
559
+ success: true,
560
+ output: ['Use $saves in transcript tests to list checkpoints.'],
561
+ };
562
+ }
563
+ cmdDescribeEntity(context, args) {
564
+ if (args.length === 0) {
565
+ return { success: false, output: [], error: 'Usage: describe <entity-id>' };
566
+ }
567
+ const entity = context.findEntity(args.join(' '));
568
+ if (!entity) {
569
+ return { success: false, output: [], error: `Entity not found: ${args.join(' ')}` };
570
+ }
571
+ const output = [];
572
+ // Header
573
+ output.push('╔══════════════════════════════════════════════════════════════╗');
574
+ output.push(`║ ENTITY: ${entity.id.substring(0, 53).padEnd(53)} ║`);
575
+ output.push('╚══════════════════════════════════════════════════════════════╝');
576
+ output.push('');
577
+ // Basic Info
578
+ output.push('┌─ BASIC INFO ─────────────────────────────────────────────────┐');
579
+ output.push(`│ ID: ${entity.id}`);
580
+ output.push(`│ Type: ${entity.type}`);
581
+ output.push(`│ Name: ${entity.name ?? '<unnamed>'}`);
582
+ output.push('└──────────────────────────────────────────────────────────────┘');
583
+ output.push('');
584
+ // Location
585
+ output.push('┌─ LOCATION ────────────────────────────────────────────────────┐');
586
+ const locationId = context.world.getLocation(entity.id);
587
+ if (locationId) {
588
+ const locationEntity = context.findEntity(locationId);
589
+ output.push(`│ In: ${locationEntity?.name ?? locationId} (${locationId})`);
590
+ }
591
+ else {
592
+ output.push('│ In: <nowhere>');
593
+ }
594
+ output.push('└──────────────────────────────────────────────────────────────┘');
595
+ output.push('');
596
+ // All Traits
597
+ output.push('┌─ TRAITS ──────────────────────────────────────────────────────┐');
598
+ if (!entity.traits || entity.traits.size === 0) {
599
+ output.push('│ <none>');
600
+ }
601
+ else {
602
+ for (const [traitType, trait] of entity.traits) {
603
+ output.push(`│`);
604
+ output.push(`│ ▸ ${traitType}`);
605
+ // Show trait properties
606
+ const props = Object.entries(trait).filter(([key]) => key !== 'type' && !key.startsWith('_'));
607
+ for (const [key, value] of props) {
608
+ output.push(`│ ${key}: ${formatValue(value)}`);
609
+ }
610
+ }
611
+ }
612
+ output.push('└──────────────────────────────────────────────────────────────┘');
613
+ output.push('');
614
+ // Contents (if applicable)
615
+ const contents = context.getContents(entity.id);
616
+ if (contents.length > 0) {
617
+ output.push('┌─ CONTENTS ─────────────────────────────────────────────────────┐');
618
+ for (const item of contents) {
619
+ output.push(`│ • ${item.name ?? item.id} (${item.id})`);
620
+ }
621
+ output.push('└──────────────────────────────────────────────────────────────┘');
622
+ }
623
+ return { success: true, output };
624
+ }
625
+ cmdDisplayState(context, _args) {
626
+ const { world } = context;
627
+ const output = [];
628
+ output.push('=== GAME STATE ===');
629
+ output.push('');
630
+ // Core stats
631
+ const moves = world.getStateValue('moves') ?? 0;
632
+ const turnCount = world.getStateValue('turnCount') ?? moves;
633
+ const score = world.getStateValue('score') ?? 0;
634
+ const maxScore = world.getStateValue('maxScore') ?? 0;
635
+ output.push(`Turn: ${turnCount}`);
636
+ output.push(`Moves: ${moves}`);
637
+ output.push(`Score: ${score}/${maxScore}`);
638
+ // Game phase
639
+ const gamePhase = world.getStateValue('gamePhase') ?? 'playing';
640
+ const gameOver = world.getStateValue('gameOver') ?? false;
641
+ output.push('');
642
+ output.push(`Phase: ${gamePhase}`);
643
+ output.push(`Game Over: ${gameOver ? 'YES' : 'no'}`);
644
+ // Entity counts
645
+ output.push('');
646
+ output.push('Entity Counts:');
647
+ const allEntities = world.getAllEntities();
648
+ const rooms = allEntities.filter((e) => e.type === 'room');
649
+ const objects = allEntities.filter((e) => e.type !== 'room' && e.type !== 'player');
650
+ output.push(` Rooms: ${rooms.length}`);
651
+ output.push(` Objects: ${objects.length}`);
652
+ output.push(` Total: ${allEntities.length}`);
653
+ // Debug flags
654
+ output.push('');
655
+ output.push('Debug Flags:');
656
+ output.push(` Immortal: ${context.getFlag('immortal') ? 'YES' : 'no'}`);
657
+ return { success: true, output };
658
+ }
659
+ cmdDisplayExits(context, args) {
660
+ // Find the room to display
661
+ let room;
662
+ if (args.length > 0) {
663
+ room = context.findRoom(args[0]);
664
+ if (!room) {
665
+ return { success: false, output: [], error: `Room not found: ${args[0]}` };
666
+ }
667
+ }
668
+ else {
669
+ room = context.getPlayerLocation();
670
+ if (!room) {
671
+ return { success: false, output: [], error: 'Player has no location' };
672
+ }
673
+ }
674
+ const output = [];
675
+ output.push('=== EXITS ===');
676
+ output.push('');
677
+ output.push(`Room: ${room.name ?? room.id} (${room.id})`);
678
+ output.push('');
679
+ // Get room trait for exits
680
+ const roomTrait = room.traits?.get('room');
681
+ const exits = roomTrait?.exits ?? {};
682
+ const allDirections = [
683
+ 'NORTH', 'SOUTH', 'EAST', 'WEST',
684
+ 'NORTHEAST', 'NORTHWEST', 'SOUTHEAST', 'SOUTHWEST',
685
+ 'UP', 'DOWN', 'IN', 'OUT',
686
+ ];
687
+ let exitCount = 0;
688
+ for (const dir of allDirections) {
689
+ const exit = exits[dir];
690
+ if (exit) {
691
+ exitCount++;
692
+ const destRoom = context.findRoom(exit.destination);
693
+ const destName = destRoom?.name ?? exit.destination;
694
+ let line = ` ${dir.padEnd(10)} -> ${destName}`;
695
+ if (exit.via) {
696
+ const viaEntity = context.findEntity(exit.via);
697
+ line += ` [via: ${viaEntity?.name ?? exit.via}]`;
698
+ }
699
+ output.push(line);
700
+ }
701
+ }
702
+ if (exitCount === 0) {
703
+ output.push(' <no exits>');
704
+ }
705
+ output.push('');
706
+ output.push(`Total exits: ${exitCount}`);
707
+ return { success: true, output };
708
+ }
709
+ cmdNoDeaths(context, _args) {
710
+ if (context.getFlag('immortal')) {
711
+ return { success: true, output: ['Immortality already enabled.'] };
712
+ }
713
+ context.setFlag('immortal', true);
714
+ return { success: true, output: ['Immortality ENABLED. You cannot die.'] };
715
+ }
716
+ cmdRestoreDeaths(context, _args) {
717
+ if (!context.getFlag('immortal')) {
718
+ return { success: true, output: ['Immortality already disabled.'] };
719
+ }
720
+ context.setFlag('immortal', false);
721
+ return { success: true, output: ['Immortality DISABLED. You can die again.'] };
722
+ }
723
+ cmdKillEntity(context, args) {
724
+ if (args.length === 0) {
725
+ return { success: false, output: [], error: 'Usage: kill <entity-id>' };
726
+ }
727
+ const entity = context.findEntity(args.join(' '));
728
+ if (!entity) {
729
+ return { success: false, output: [], error: `Entity not found: ${args.join(' ')}` };
730
+ }
731
+ // Mark entity as dead
732
+ entity.isDead = true;
733
+ entity.isAlive = false;
734
+ // Check for combatant trait
735
+ const combatant = entity.traits?.get('combatant');
736
+ if (combatant?.kill) {
737
+ combatant.kill();
738
+ }
739
+ return {
740
+ success: true,
741
+ output: [`Killed: ${entity.name ?? entity.id}`],
742
+ };
743
+ }
744
+ cmdExit(context, _args) {
745
+ context.setFlag('active', false);
746
+ return { success: true, output: ['Returning to game.'] };
747
+ }
748
+ // =========================================================================
749
+ // Annotation Command Implementations (ADR-109)
750
+ // =========================================================================
751
+ cmdBug(context, args) {
752
+ if (args.length === 0) {
753
+ return { success: false, output: [], error: 'Usage: bug <description>' };
754
+ }
755
+ const text = args.join(' ');
756
+ const annotationContext = (0, index_js_1.captureContext)(context.world, this.lastCommand, this.lastResponse);
757
+ const annotation = this.annotations.addAnnotation('bug', text, annotationContext);
758
+ return {
759
+ success: true,
760
+ output: [`Bug logged: "${text}" [Turn ${annotation.context.turn}, ${annotation.context.roomName}]`],
761
+ };
762
+ }
763
+ cmdNote(context, args) {
764
+ if (args.length === 0) {
765
+ return { success: false, output: [], error: 'Usage: note <text>' };
766
+ }
767
+ const text = args.join(' ');
768
+ const annotationContext = (0, index_js_1.captureContext)(context.world, this.lastCommand, this.lastResponse);
769
+ const annotation = this.annotations.addAnnotation('note', text, annotationContext);
770
+ return {
771
+ success: true,
772
+ output: [`Note added: "${text}" [Turn ${annotation.context.turn}]`],
773
+ };
774
+ }
775
+ cmdConfusing(context, _args) {
776
+ const annotationContext = (0, index_js_1.captureContext)(context.world, this.lastCommand, this.lastResponse);
777
+ const annotation = this.annotations.addAnnotation('confusing', '', annotationContext);
778
+ return {
779
+ success: true,
780
+ output: [`Marked as confusing: "${this.lastCommand}" [Turn ${annotation.context.turn}]`],
781
+ };
782
+ }
783
+ cmdExpected(context, args) {
784
+ if (args.length === 0) {
785
+ return { success: false, output: [], error: 'Usage: expected <what was expected>' };
786
+ }
787
+ const text = args.join(' ');
788
+ const annotationContext = (0, index_js_1.captureContext)(context.world, this.lastCommand, this.lastResponse);
789
+ const annotation = this.annotations.addAnnotation('expected', text, annotationContext);
790
+ return {
791
+ success: true,
792
+ output: [`Expected: "${text}" (Got: "${this.lastResponse.substring(0, 50)}...")`],
793
+ data: { annotation },
794
+ };
795
+ }
796
+ cmdBookmark(context, args) {
797
+ if (args.length === 0) {
798
+ return { success: false, output: [], error: 'Usage: bookmark <name>' };
799
+ }
800
+ const name = args.join(' ');
801
+ const annotationContext = (0, index_js_1.captureContext)(context.world, this.lastCommand, this.lastResponse);
802
+ const annotation = this.annotations.addAnnotation('bookmark', name, annotationContext);
803
+ // Also save a checkpoint with this name
804
+ const checkpointData = (0, serializer_js_1.serializeCheckpoint)(context.world, name);
805
+ this.checkpoints.save(name, checkpointData).catch(() => {
806
+ // Silently ignore checkpoint save errors
807
+ });
808
+ return {
809
+ success: true,
810
+ output: [`Bookmark "${name}" created at Turn ${annotation.context.turn}, ${annotation.context.roomName}`],
811
+ };
812
+ }
813
+ cmdSession(_context, args) {
814
+ if (args.length === 0) {
815
+ return { success: false, output: [], error: 'Usage: session start <name> | session end' };
816
+ }
817
+ const subcommand = args[0].toLowerCase();
818
+ if (subcommand === 'start') {
819
+ if (args.length < 2) {
820
+ return { success: false, output: [], error: 'Usage: session start <name>' };
821
+ }
822
+ const name = args.slice(1).join(' ');
823
+ const sessionId = this.annotations.startSession(name);
824
+ return {
825
+ success: true,
826
+ output: [`Session "${name}" started (ID: ${sessionId})`],
827
+ };
828
+ }
829
+ if (subcommand === 'end') {
830
+ const ended = this.annotations.endSession();
831
+ if (!ended) {
832
+ return { success: false, output: [], error: 'No active session to end' };
833
+ }
834
+ const duration = ended.endTime
835
+ ? Math.round((ended.endTime - ended.startTime) / 60000)
836
+ : 0;
837
+ return {
838
+ success: true,
839
+ output: [
840
+ `Session "${ended.name}" ended`,
841
+ `Duration: ${duration} minutes`,
842
+ `Annotations: ${ended.annotations.length}`,
843
+ ],
844
+ };
845
+ }
846
+ return { success: false, output: [], error: 'Usage: session start <name> | session end' };
847
+ }
848
+ cmdReview(_context, _args) {
849
+ const session = this.annotations.getCurrentSession();
850
+ const annotations = this.annotations.getAnnotations();
851
+ const output = [];
852
+ if (session) {
853
+ output.push(`=== Session: ${session.name} ===`);
854
+ output.push(`Started: ${new Date(session.startTime).toLocaleTimeString()}`);
855
+ }
856
+ else {
857
+ output.push('=== Annotations (no active session) ===');
858
+ }
859
+ output.push('');
860
+ if (annotations.length === 0) {
861
+ output.push('No annotations yet.');
862
+ }
863
+ else {
864
+ // Group by type
865
+ const bugs = annotations.filter((a) => a.type === 'bug');
866
+ const notes = annotations.filter((a) => a.type === 'note');
867
+ const confusing = annotations.filter((a) => a.type === 'confusing');
868
+ const expected = annotations.filter((a) => a.type === 'expected');
869
+ const bookmarks = annotations.filter((a) => a.type === 'bookmark');
870
+ const comments = annotations.filter((a) => a.type === 'comment');
871
+ if (bugs.length > 0) {
872
+ output.push(`Bugs: ${bugs.length}`);
873
+ bugs.forEach((b) => output.push(` - [T${b.context.turn}] ${b.text}`));
874
+ }
875
+ if (notes.length > 0) {
876
+ output.push(`Notes: ${notes.length}`);
877
+ notes.forEach((n) => output.push(` - [T${n.context.turn}] ${n.text}`));
878
+ }
879
+ if (confusing.length > 0) {
880
+ output.push(`Confusion points: ${confusing.length}`);
881
+ }
882
+ if (expected.length > 0) {
883
+ output.push(`Expected behavior: ${expected.length}`);
884
+ }
885
+ if (bookmarks.length > 0) {
886
+ output.push(`Bookmarks: ${bookmarks.length}`);
887
+ bookmarks.forEach((b) => output.push(` - "${b.text}" at T${b.context.turn}`));
888
+ }
889
+ if (comments.length > 0) {
890
+ output.push(`Comments: ${comments.length}`);
891
+ }
892
+ }
893
+ output.push('');
894
+ output.push(`Total: ${annotations.length} annotations`);
895
+ return { success: true, output };
896
+ }
897
+ cmdExport(_context, _args) {
898
+ const markdown = this.annotations.exportMarkdown();
899
+ return {
900
+ success: true,
901
+ output: markdown.split('\n'),
902
+ };
903
+ }
904
+ }
905
+ exports.TestingExtension = TestingExtension;
906
+ /**
907
+ * Format a value for display
908
+ */
909
+ function formatValue(value) {
910
+ if (value === null)
911
+ return 'null';
912
+ if (value === undefined)
913
+ return 'undefined';
914
+ if (typeof value === 'string')
915
+ return `"${value}"`;
916
+ if (typeof value === 'boolean')
917
+ return value ? 'true' : 'false';
918
+ if (typeof value === 'number')
919
+ return String(value);
920
+ if (Array.isArray(value)) {
921
+ if (value.length === 0)
922
+ return '[]';
923
+ if (value.length <= 3)
924
+ return `[${value.map((v) => formatValue(v)).join(', ')}]`;
925
+ return `[${value.slice(0, 2).map((v) => formatValue(v)).join(', ')}, ... (${value.length} items)]`;
926
+ }
927
+ if (typeof value === 'object') {
928
+ const keys = Object.keys(value);
929
+ if (keys.length === 0)
930
+ return '{}';
931
+ if (keys.length <= 2) {
932
+ return `{${keys.map((k) => `${k}: ${formatValue(value[k])}`).join(', ')}}`;
933
+ }
934
+ return `{... (${keys.length} keys)}`;
935
+ }
936
+ return String(value);
937
+ }
938
+ //# sourceMappingURL=extension.js.map