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