@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.
- package/LICENSE +21 -0
- package/dist/annotations/context.d.ts +16 -0
- package/dist/annotations/context.d.ts.map +1 -0
- package/dist/annotations/context.js +42 -0
- package/dist/annotations/context.js.map +1 -0
- package/dist/annotations/index.d.ts +6 -0
- package/dist/annotations/index.d.ts.map +1 -0
- package/dist/annotations/index.js +12 -0
- package/dist/annotations/index.js.map +1 -0
- package/dist/annotations/store.d.ts +11 -0
- package/dist/annotations/store.d.ts.map +1 -0
- package/dist/annotations/store.js +191 -0
- package/dist/annotations/store.js.map +1 -0
- package/dist/checkpoints/index.d.ts +3 -0
- package/dist/checkpoints/index.d.ts.map +1 -0
- package/dist/checkpoints/index.js +12 -0
- package/dist/checkpoints/index.js.map +1 -0
- package/dist/checkpoints/serializer.d.ts +21 -0
- package/dist/checkpoints/serializer.d.ts.map +1 -0
- package/dist/checkpoints/serializer.js +95 -0
- package/dist/checkpoints/serializer.js.map +1 -0
- package/dist/checkpoints/store.d.ts +20 -0
- package/dist/checkpoints/store.d.ts.map +1 -0
- package/dist/checkpoints/store.js +193 -0
- package/dist/checkpoints/store.js.map +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/registry.d.ts +28 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +70 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/context/debug-context.d.ts +21 -0
- package/dist/context/debug-context.d.ts.map +1 -0
- package/dist/context/debug-context.js +172 -0
- package/dist/context/debug-context.js.map +1 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +8 -0
- package/dist/context/index.js.map +1 -0
- package/dist/extension.d.ts +78 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +938 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +375 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist-npm/annotations/context.d.ts +16 -0
- package/dist-npm/annotations/context.d.ts.map +1 -0
- package/dist-npm/annotations/context.js +42 -0
- package/dist-npm/annotations/context.js.map +1 -0
- package/dist-npm/annotations/index.d.ts +6 -0
- package/dist-npm/annotations/index.d.ts.map +1 -0
- package/dist-npm/annotations/index.js +12 -0
- package/dist-npm/annotations/index.js.map +1 -0
- package/dist-npm/annotations/store.d.ts +11 -0
- package/dist-npm/annotations/store.d.ts.map +1 -0
- package/dist-npm/annotations/store.js +191 -0
- package/dist-npm/annotations/store.js.map +1 -0
- package/dist-npm/checkpoints/index.d.ts +3 -0
- package/dist-npm/checkpoints/index.d.ts.map +1 -0
- package/dist-npm/checkpoints/index.js +12 -0
- package/dist-npm/checkpoints/index.js.map +1 -0
- package/dist-npm/checkpoints/serializer.d.ts +21 -0
- package/dist-npm/checkpoints/serializer.d.ts.map +1 -0
- package/dist-npm/checkpoints/serializer.js +95 -0
- package/dist-npm/checkpoints/serializer.js.map +1 -0
- package/dist-npm/checkpoints/store.d.ts +20 -0
- package/dist-npm/checkpoints/store.d.ts.map +1 -0
- package/dist-npm/checkpoints/store.js +193 -0
- package/dist-npm/checkpoints/store.js.map +1 -0
- package/dist-npm/commands/index.d.ts +2 -0
- package/dist-npm/commands/index.d.ts.map +1 -0
- package/dist-npm/commands/index.js +8 -0
- package/dist-npm/commands/index.js.map +1 -0
- package/dist-npm/commands/registry.d.ts +28 -0
- package/dist-npm/commands/registry.d.ts.map +1 -0
- package/dist-npm/commands/registry.js +70 -0
- package/dist-npm/commands/registry.js.map +1 -0
- package/dist-npm/context/debug-context.d.ts +21 -0
- package/dist-npm/context/debug-context.d.ts.map +1 -0
- package/dist-npm/context/debug-context.js +172 -0
- package/dist-npm/context/debug-context.js.map +1 -0
- package/dist-npm/context/index.d.ts +2 -0
- package/dist-npm/context/index.d.ts.map +1 -0
- package/dist-npm/context/index.js +8 -0
- package/dist-npm/context/index.js.map +1 -0
- package/dist-npm/extension.d.ts +78 -0
- package/dist-npm/extension.d.ts.map +1 -0
- package/dist-npm/extension.js +938 -0
- package/dist-npm/extension.js.map +1 -0
- package/dist-npm/index.d.ts +40 -0
- package/dist-npm/index.d.ts.map +1 -0
- package/dist-npm/index.js +63 -0
- package/dist-npm/index.js.map +1 -0
- package/dist-npm/types.d.ts +375 -0
- package/dist-npm/types.d.ts.map +1 -0
- package/dist-npm/types.js +8 -0
- package/dist-npm/types.js.map +1 -0
- package/package.json +42 -0
- package/src/annotations/context.ts +47 -0
- package/src/annotations/index.ts +6 -0
- package/src/annotations/store.ts +219 -0
- package/src/checkpoints/index.ts +2 -0
- package/src/checkpoints/serializer.ts +121 -0
- package/src/checkpoints/store.ts +188 -0
- package/src/commands/index.ts +1 -0
- package/src/commands/registry.ts +81 -0
- package/src/context/debug-context.ts +209 -0
- package/src/context/index.ts +1 -0
- package/src/extension.ts +1089 -0
- package/src/index.ts +69 -0
- package/src/types.ts +469 -0
- package/tsconfig.json +16 -0
- 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
|