agent-dbg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.bin/ndbg +0 -0
  2. package/.claude/settings.local.json +21 -0
  3. package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
  4. package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
  5. package/CLAUDE.md +43 -0
  6. package/PROGRESS.md +261 -0
  7. package/README.md +67 -0
  8. package/biome.json +41 -0
  9. package/ndbg-spec.md +958 -0
  10. package/package.json +30 -0
  11. package/src/cdp/client.ts +198 -0
  12. package/src/cdp/types.ts +16 -0
  13. package/src/cli/parser.ts +287 -0
  14. package/src/cli/registry.ts +7 -0
  15. package/src/cli/types.ts +24 -0
  16. package/src/commands/attach.ts +47 -0
  17. package/src/commands/blackbox-ls.ts +38 -0
  18. package/src/commands/blackbox-rm.ts +57 -0
  19. package/src/commands/blackbox.ts +48 -0
  20. package/src/commands/break-ls.ts +57 -0
  21. package/src/commands/break-rm.ts +40 -0
  22. package/src/commands/break-toggle.ts +42 -0
  23. package/src/commands/break.ts +145 -0
  24. package/src/commands/breakable.ts +69 -0
  25. package/src/commands/catch.ts +38 -0
  26. package/src/commands/console.ts +61 -0
  27. package/src/commands/continue.ts +46 -0
  28. package/src/commands/eval.ts +70 -0
  29. package/src/commands/exceptions.ts +61 -0
  30. package/src/commands/hotpatch.ts +67 -0
  31. package/src/commands/launch.ts +69 -0
  32. package/src/commands/logpoint.ts +78 -0
  33. package/src/commands/pause.ts +46 -0
  34. package/src/commands/props.ts +77 -0
  35. package/src/commands/restart-frame.ts +36 -0
  36. package/src/commands/run-to.ts +70 -0
  37. package/src/commands/scripts.ts +57 -0
  38. package/src/commands/search.ts +73 -0
  39. package/src/commands/sessions.ts +71 -0
  40. package/src/commands/set-return.ts +49 -0
  41. package/src/commands/set.ts +61 -0
  42. package/src/commands/source.ts +59 -0
  43. package/src/commands/sourcemap.ts +66 -0
  44. package/src/commands/stack.ts +64 -0
  45. package/src/commands/state.ts +124 -0
  46. package/src/commands/status.ts +57 -0
  47. package/src/commands/step.ts +50 -0
  48. package/src/commands/stop.ts +27 -0
  49. package/src/commands/vars.ts +71 -0
  50. package/src/daemon/client.ts +147 -0
  51. package/src/daemon/entry.ts +242 -0
  52. package/src/daemon/paths.ts +26 -0
  53. package/src/daemon/server.ts +185 -0
  54. package/src/daemon/session-blackbox.ts +41 -0
  55. package/src/daemon/session-breakpoints.ts +492 -0
  56. package/src/daemon/session-execution.ts +121 -0
  57. package/src/daemon/session-inspection.ts +701 -0
  58. package/src/daemon/session-mutation.ts +197 -0
  59. package/src/daemon/session-state.ts +258 -0
  60. package/src/daemon/session.ts +938 -0
  61. package/src/daemon/spawn.ts +53 -0
  62. package/src/formatter/errors.ts +15 -0
  63. package/src/formatter/source.ts +74 -0
  64. package/src/formatter/stack.ts +70 -0
  65. package/src/formatter/values.ts +269 -0
  66. package/src/formatter/variables.ts +20 -0
  67. package/src/main.ts +45 -0
  68. package/src/protocol/messages.ts +316 -0
  69. package/src/refs/ref-table.ts +120 -0
  70. package/src/refs/resolver.ts +24 -0
  71. package/src/sourcemap/resolver.ts +318 -0
  72. package/tests/fixtures/async-app.js +34 -0
  73. package/tests/fixtures/console-app.js +12 -0
  74. package/tests/fixtures/error-app.js +28 -0
  75. package/tests/fixtures/exception-app.js +6 -0
  76. package/tests/fixtures/inspect-app.js +10 -0
  77. package/tests/fixtures/mutation-app.js +9 -0
  78. package/tests/fixtures/simple-app.js +50 -0
  79. package/tests/fixtures/step-app.js +13 -0
  80. package/tests/fixtures/ts-app/src/app.ts +21 -0
  81. package/tests/fixtures/ts-app/tsconfig.json +14 -0
  82. package/tests/integration/blackbox.test.ts +135 -0
  83. package/tests/integration/break-extras.test.ts +241 -0
  84. package/tests/integration/breakpoint.test.ts +217 -0
  85. package/tests/integration/console.test.ts +275 -0
  86. package/tests/integration/execution.test.ts +247 -0
  87. package/tests/integration/inspection.test.ts +311 -0
  88. package/tests/integration/mutation.test.ts +178 -0
  89. package/tests/integration/session.test.ts +223 -0
  90. package/tests/integration/source.test.ts +209 -0
  91. package/tests/integration/sourcemap.test.ts +214 -0
  92. package/tests/integration/state.test.ts +208 -0
  93. package/tests/unit/cdp-client.test.ts +422 -0
  94. package/tests/unit/daemon.test.ts +286 -0
  95. package/tests/unit/formatter.test.ts +716 -0
  96. package/tests/unit/parser.test.ts +105 -0
  97. package/tests/unit/refs.test.ts +383 -0
  98. package/tests/unit/sourcemap.test.ts +236 -0
  99. package/tsconfig.json +32 -0
@@ -0,0 +1,105 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { parseArgs } from "../../src/cli/parser.ts";
3
+
4
+ describe("parseArgs", () => {
5
+ test("parses command only", () => {
6
+ const args = parseArgs(["continue"]);
7
+ expect(args.command).toBe("continue");
8
+ expect(args.subcommand).toBeNull();
9
+ expect(args.positionals).toEqual([]);
10
+ });
11
+
12
+ test("parses command with subcommand", () => {
13
+ const args = parseArgs(["step", "into"]);
14
+ expect(args.command).toBe("step");
15
+ expect(args.subcommand).toBe("into");
16
+ });
17
+
18
+ test("parses command with positionals", () => {
19
+ const args = parseArgs(["break", "src/app.ts:42"]);
20
+ expect(args.command).toBe("break");
21
+ expect(args.subcommand).toBe("src/app.ts:42");
22
+ });
23
+
24
+ test("parses boolean flags", () => {
25
+ const args = parseArgs(["launch", "--brk", "node", "app.js"]);
26
+ expect(args.command).toBe("launch");
27
+ expect(args.flags.brk).toBe(true);
28
+ expect(args.positionals).toEqual(["node", "app.js"]);
29
+ });
30
+
31
+ test("parses value flags", () => {
32
+ const args = parseArgs(["break", "src/app.ts:42", "--condition", "x > 5"]);
33
+ expect(args.command).toBe("break");
34
+ expect(args.flags.condition).toBe("x > 5");
35
+ });
36
+
37
+ test("parses global flags", () => {
38
+ const args = parseArgs(["state", "--session", "mysession", "--json", "--color"]);
39
+ expect(args.global.session).toBe("mysession");
40
+ expect(args.global.json).toBe(true);
41
+ expect(args.global.color).toBe(true);
42
+ });
43
+
44
+ test("global flags not in flags map", () => {
45
+ const args = parseArgs(["state", "--json"]);
46
+ expect(args.flags.json).toBeUndefined();
47
+ expect(args.global.json).toBe(true);
48
+ });
49
+
50
+ test("default session is 'default'", () => {
51
+ const args = parseArgs(["state"]);
52
+ expect(args.global.session).toBe("default");
53
+ });
54
+
55
+ test("parses short flags", () => {
56
+ const args = parseArgs(["state", "-v", "-s"]);
57
+ expect(args.flags.vars).toBe(true);
58
+ expect(args.flags.stack).toBe(true);
59
+ });
60
+
61
+ test("handles -- separator", () => {
62
+ const args = parseArgs(["launch", "--brk", "--", "node", "--inspect", "app.js"]);
63
+ expect(args.flags.brk).toBe(true);
64
+ expect(args.positionals).toEqual(["node", "--inspect", "app.js"]);
65
+ });
66
+
67
+ test("parses --help-agent", () => {
68
+ const args = parseArgs(["--help-agent"]);
69
+ expect(args.global.helpAgent).toBe(true);
70
+ });
71
+
72
+ test("empty args", () => {
73
+ const args = parseArgs([]);
74
+ expect(args.command).toBe("");
75
+ expect(args.global.help).toBe(false);
76
+ });
77
+
78
+ test("parses eval with expression", () => {
79
+ const args = parseArgs(["eval", "@v1.retryCount"]);
80
+ expect(args.command).toBe("eval");
81
+ expect(args.subcommand).toBe("@v1.retryCount");
82
+ });
83
+
84
+ test("parses complex launch command", () => {
85
+ const args = parseArgs([
86
+ "launch",
87
+ "--brk",
88
+ "--session",
89
+ "test",
90
+ "--port",
91
+ "9229",
92
+ "--timeout",
93
+ "600",
94
+ "--",
95
+ "node",
96
+ "app.js",
97
+ ]);
98
+ expect(args.command).toBe("launch");
99
+ expect(args.flags.brk).toBe(true);
100
+ expect(args.global.session).toBe("test");
101
+ expect(args.flags.port).toBe("9229");
102
+ expect(args.flags.timeout).toBe("600");
103
+ expect(args.positionals).toEqual(["node", "app.js"]);
104
+ });
105
+ });
@@ -0,0 +1,383 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { RefTable } from "../../src/refs/ref-table.ts";
3
+ import { interpolateRefs, parseRef } from "../../src/refs/resolver.ts";
4
+
5
+ describe("RefTable", () => {
6
+ describe("addVar", () => {
7
+ test("returns @v refs starting from 1", () => {
8
+ const table = new RefTable();
9
+ expect(table.addVar("rid-1", "x")).toBe("@v1");
10
+ expect(table.addVar("rid-2", "y")).toBe("@v2");
11
+ expect(table.addVar("rid-3", "z")).toBe("@v3");
12
+ });
13
+
14
+ test("stores name and meta", () => {
15
+ const table = new RefTable();
16
+ table.addVar("rid-1", "count", { scope: "local" });
17
+ const entry = table.resolve("@v1");
18
+ expect(entry).toBeDefined();
19
+ expect(entry!.name).toBe("count");
20
+ expect(entry!.meta).toEqual({ scope: "local" });
21
+ expect(entry!.remoteId).toBe("rid-1");
22
+ expect(entry!.type).toBe("v");
23
+ });
24
+ });
25
+
26
+ describe("addFrame", () => {
27
+ test("returns @f refs starting from 0", () => {
28
+ const table = new RefTable();
29
+ expect(table.addFrame("cfid-0", "main")).toBe("@f0");
30
+ expect(table.addFrame("cfid-1", "handler")).toBe("@f1");
31
+ expect(table.addFrame("cfid-2", "callback")).toBe("@f2");
32
+ });
33
+ });
34
+
35
+ describe("addObject", () => {
36
+ test("returns @o refs starting from 1", () => {
37
+ const table = new RefTable();
38
+ expect(table.addObject("obj-1", "myArray")).toBe("@o1");
39
+ expect(table.addObject("obj-2", "myMap")).toBe("@o2");
40
+ });
41
+ });
42
+
43
+ describe("addBreakpoint", () => {
44
+ test("returns BP# refs starting from 1", () => {
45
+ const table = new RefTable();
46
+ expect(table.addBreakpoint("bp-id-1", { file: "app.ts", line: 10 })).toBe("BP#1");
47
+ expect(table.addBreakpoint("bp-id-2", { file: "app.ts", line: 20 })).toBe("BP#2");
48
+ });
49
+ });
50
+
51
+ describe("addLogpoint", () => {
52
+ test("returns LP# refs starting from 1", () => {
53
+ const table = new RefTable();
54
+ expect(table.addLogpoint("lp-id-1", { expression: "x" })).toBe("LP#1");
55
+ expect(table.addLogpoint("lp-id-2", { expression: "y" })).toBe("LP#2");
56
+ });
57
+ });
58
+
59
+ describe("addHeapSnapshot", () => {
60
+ test("returns HS# refs starting from 1", () => {
61
+ const table = new RefTable();
62
+ expect(table.addHeapSnapshot("hs-id-1")).toBe("HS#1");
63
+ expect(table.addHeapSnapshot("hs-id-2")).toBe("HS#2");
64
+ });
65
+ });
66
+
67
+ describe("resolve", () => {
68
+ test("returns entry for valid ref", () => {
69
+ const table = new RefTable();
70
+ table.addVar("rid-1", "x");
71
+ const entry = table.resolve("@v1");
72
+ expect(entry).toEqual({
73
+ ref: "@v1",
74
+ type: "v",
75
+ remoteId: "rid-1",
76
+ name: "x",
77
+ });
78
+ });
79
+
80
+ test("returns undefined for unknown ref", () => {
81
+ const table = new RefTable();
82
+ expect(table.resolve("@v99")).toBeUndefined();
83
+ });
84
+ });
85
+
86
+ describe("resolveId", () => {
87
+ test("returns remoteId for valid ref", () => {
88
+ const table = new RefTable();
89
+ table.addVar("rid-1", "x");
90
+ expect(table.resolveId("@v1")).toBe("rid-1");
91
+ });
92
+
93
+ test("returns undefined for unknown ref", () => {
94
+ const table = new RefTable();
95
+ expect(table.resolveId("@v99")).toBeUndefined();
96
+ });
97
+ });
98
+
99
+ describe("clearVolatile", () => {
100
+ test("clears @v and @f refs", () => {
101
+ const table = new RefTable();
102
+ table.addVar("rid-1", "x");
103
+ table.addFrame("cfid-0", "main");
104
+ table.clearVolatile();
105
+ expect(table.resolve("@v1")).toBeUndefined();
106
+ expect(table.resolve("@f0")).toBeUndefined();
107
+ });
108
+
109
+ test("does not clear @o, BP#, LP#, HS# refs", () => {
110
+ const table = new RefTable();
111
+ table.addVar("rid-1", "x");
112
+ table.addFrame("cfid-0", "main");
113
+ table.addObject("obj-1", "arr");
114
+ table.addBreakpoint("bp-1");
115
+ table.addLogpoint("lp-1");
116
+ table.addHeapSnapshot("hs-1");
117
+
118
+ table.clearVolatile();
119
+
120
+ expect(table.resolve("@o1")).toBeDefined();
121
+ expect(table.resolve("BP#1")).toBeDefined();
122
+ expect(table.resolve("LP#1")).toBeDefined();
123
+ expect(table.resolve("HS#1")).toBeDefined();
124
+ });
125
+
126
+ test("@v numbering restarts from 1 after clearVolatile", () => {
127
+ const table = new RefTable();
128
+ table.addVar("rid-1", "x");
129
+ table.addVar("rid-2", "y");
130
+ table.clearVolatile();
131
+ expect(table.addVar("rid-3", "z")).toBe("@v1");
132
+ });
133
+
134
+ test("@f numbering restarts from 0 after clearVolatile", () => {
135
+ const table = new RefTable();
136
+ table.addFrame("cfid-0", "main");
137
+ table.addFrame("cfid-1", "handler");
138
+ table.clearVolatile();
139
+ expect(table.addFrame("cfid-2", "newTop")).toBe("@f0");
140
+ });
141
+ });
142
+
143
+ describe("@o numbering persists across clearVolatile", () => {
144
+ test("@o counter continues after clearVolatile", () => {
145
+ const table = new RefTable();
146
+ table.addObject("obj-1", "first");
147
+ table.addObject("obj-2", "second");
148
+ table.clearVolatile();
149
+ expect(table.addObject("obj-3", "third")).toBe("@o3");
150
+ });
151
+ });
152
+
153
+ describe("clearObjects", () => {
154
+ test("clears @o refs only", () => {
155
+ const table = new RefTable();
156
+ table.addVar("rid-1", "x");
157
+ table.addObject("obj-1", "arr");
158
+ table.addBreakpoint("bp-1");
159
+
160
+ table.clearObjects();
161
+
162
+ expect(table.resolve("@o1")).toBeUndefined();
163
+ expect(table.resolve("@v1")).toBeDefined();
164
+ expect(table.resolve("BP#1")).toBeDefined();
165
+ });
166
+
167
+ test("@o numbering resets after clearObjects", () => {
168
+ const table = new RefTable();
169
+ table.addObject("obj-1", "first");
170
+ table.addObject("obj-2", "second");
171
+ table.clearObjects();
172
+ expect(table.addObject("obj-3", "restarted")).toBe("@o1");
173
+ });
174
+ });
175
+
176
+ describe("clearAll", () => {
177
+ test("clears all refs", () => {
178
+ const table = new RefTable();
179
+ table.addVar("rid-1", "x");
180
+ table.addFrame("cfid-0", "main");
181
+ table.addObject("obj-1", "arr");
182
+ table.addBreakpoint("bp-1");
183
+ table.addLogpoint("lp-1");
184
+ table.addHeapSnapshot("hs-1");
185
+
186
+ table.clearAll();
187
+
188
+ expect(table.resolve("@v1")).toBeUndefined();
189
+ expect(table.resolve("@f0")).toBeUndefined();
190
+ expect(table.resolve("@o1")).toBeUndefined();
191
+ expect(table.resolve("BP#1")).toBeUndefined();
192
+ expect(table.resolve("LP#1")).toBeUndefined();
193
+ expect(table.resolve("HS#1")).toBeUndefined();
194
+ });
195
+
196
+ test("resets all counters", () => {
197
+ const table = new RefTable();
198
+ table.addVar("rid-1");
199
+ table.addVar("rid-2");
200
+ table.addFrame("cfid-0");
201
+ table.addObject("obj-1");
202
+ table.addBreakpoint("bp-1");
203
+ table.addLogpoint("lp-1");
204
+ table.addHeapSnapshot("hs-1");
205
+
206
+ table.clearAll();
207
+
208
+ expect(table.addVar("new-1")).toBe("@v1");
209
+ expect(table.addFrame("new-2")).toBe("@f0");
210
+ expect(table.addObject("new-3")).toBe("@o1");
211
+ expect(table.addBreakpoint("new-4")).toBe("BP#1");
212
+ expect(table.addLogpoint("new-5")).toBe("LP#1");
213
+ expect(table.addHeapSnapshot("new-6")).toBe("HS#1");
214
+ });
215
+ });
216
+
217
+ describe("list", () => {
218
+ test("returns entries of the given type", () => {
219
+ const table = new RefTable();
220
+ table.addVar("rid-1", "x");
221
+ table.addVar("rid-2", "y");
222
+ table.addFrame("cfid-0", "main");
223
+ table.addObject("obj-1", "arr");
224
+
225
+ const vars = table.list("v");
226
+ expect(vars).toHaveLength(2);
227
+ expect(vars[0]!.ref).toBe("@v1");
228
+ expect(vars[1]!.ref).toBe("@v2");
229
+ });
230
+
231
+ test("returns empty array when no entries of type exist", () => {
232
+ const table = new RefTable();
233
+ expect(table.list("HS")).toEqual([]);
234
+ });
235
+ });
236
+
237
+ describe("remove", () => {
238
+ test("removes a breakpoint ref", () => {
239
+ const table = new RefTable();
240
+ table.addBreakpoint("bp-1");
241
+ expect(table.remove("BP#1")).toBe(true);
242
+ expect(table.resolve("BP#1")).toBeUndefined();
243
+ });
244
+
245
+ test("removes a logpoint ref", () => {
246
+ const table = new RefTable();
247
+ table.addLogpoint("lp-1");
248
+ expect(table.remove("LP#1")).toBe(true);
249
+ expect(table.resolve("LP#1")).toBeUndefined();
250
+ });
251
+
252
+ test("returns false for non-existent ref", () => {
253
+ const table = new RefTable();
254
+ expect(table.remove("BP#99")).toBe(false);
255
+ });
256
+ });
257
+
258
+ describe("name and meta are optional", () => {
259
+ test("entry without name or meta", () => {
260
+ const table = new RefTable();
261
+ table.addVar("rid-1");
262
+ const entry = table.resolve("@v1");
263
+ expect(entry).toBeDefined();
264
+ expect(entry!.name).toBeUndefined();
265
+ expect(entry!.meta).toBeUndefined();
266
+ });
267
+ });
268
+ });
269
+
270
+ describe("parseRef", () => {
271
+ test("parses @v refs", () => {
272
+ expect(parseRef("@v1")).toEqual({ type: "v", num: 1 });
273
+ expect(parseRef("@v42")).toEqual({ type: "v", num: 42 });
274
+ });
275
+
276
+ test("parses @f refs", () => {
277
+ expect(parseRef("@f0")).toEqual({ type: "f", num: 0 });
278
+ expect(parseRef("@f3")).toEqual({ type: "f", num: 3 });
279
+ });
280
+
281
+ test("parses @o refs", () => {
282
+ expect(parseRef("@o1")).toEqual({ type: "o", num: 1 });
283
+ expect(parseRef("@o10")).toEqual({ type: "o", num: 10 });
284
+ });
285
+
286
+ test("parses BP# refs", () => {
287
+ expect(parseRef("BP#1")).toEqual({ type: "BP", num: 1 });
288
+ expect(parseRef("BP#25")).toEqual({ type: "BP", num: 25 });
289
+ });
290
+
291
+ test("parses LP# refs", () => {
292
+ expect(parseRef("LP#1")).toEqual({ type: "LP", num: 1 });
293
+ expect(parseRef("LP#7")).toEqual({ type: "LP", num: 7 });
294
+ });
295
+
296
+ test("parses HS# refs", () => {
297
+ expect(parseRef("HS#1")).toEqual({ type: "HS", num: 1 });
298
+ expect(parseRef("HS#3")).toEqual({ type: "HS", num: 3 });
299
+ });
300
+
301
+ test("returns null for invalid refs", () => {
302
+ expect(parseRef("")).toBeNull();
303
+ expect(parseRef("v1")).toBeNull();
304
+ expect(parseRef("@x1")).toBeNull();
305
+ expect(parseRef("@v")).toBeNull();
306
+ expect(parseRef("BP1")).toBeNull();
307
+ expect(parseRef("#BP1")).toBeNull();
308
+ expect(parseRef("foo")).toBeNull();
309
+ expect(parseRef("@v1.name")).toBeNull();
310
+ });
311
+ });
312
+
313
+ describe("interpolateRefs", () => {
314
+ test("replaces @v ref in expression", () => {
315
+ const table = new RefTable();
316
+ table.addVar("remote-obj-1", "x");
317
+ const result = interpolateRefs("@v1.retryCount", table);
318
+ expect(result).toBe("remote-obj-1.retryCount");
319
+ });
320
+
321
+ test("replaces multiple refs in expression", () => {
322
+ const table = new RefTable();
323
+ table.addVar("rid-1", "a");
324
+ table.addVar("rid-2", "b");
325
+ const result = interpolateRefs("@v1 + @v2", table);
326
+ expect(result).toBe("rid-1 + rid-2");
327
+ });
328
+
329
+ test("replaces @f ref in expression", () => {
330
+ const table = new RefTable();
331
+ table.addFrame("cfid-0", "main");
332
+ const result = interpolateRefs("@f0", table);
333
+ expect(result).toBe("cfid-0");
334
+ });
335
+
336
+ test("replaces @o ref in expression", () => {
337
+ const table = new RefTable();
338
+ table.addObject("obj-id-1", "myObj");
339
+ const result = interpolateRefs("@o1.length", table);
340
+ expect(result).toBe("obj-id-1.length");
341
+ });
342
+
343
+ test("replaces BP# ref in expression", () => {
344
+ const table = new RefTable();
345
+ table.addBreakpoint("bp-id-1");
346
+ const result = interpolateRefs("BP#1", table);
347
+ expect(result).toBe("bp-id-1");
348
+ });
349
+
350
+ test("replaces LP# ref in expression", () => {
351
+ const table = new RefTable();
352
+ table.addLogpoint("lp-id-1");
353
+ const result = interpolateRefs("LP#1", table);
354
+ expect(result).toBe("lp-id-1");
355
+ });
356
+
357
+ test("replaces HS# ref in expression", () => {
358
+ const table = new RefTable();
359
+ table.addHeapSnapshot("hs-id-1");
360
+ const result = interpolateRefs("HS#1", table);
361
+ expect(result).toBe("hs-id-1");
362
+ });
363
+
364
+ test("leaves unresolvable refs unchanged", () => {
365
+ const table = new RefTable();
366
+ const result = interpolateRefs("@v99 + 1", table);
367
+ expect(result).toBe("@v99 + 1");
368
+ });
369
+
370
+ test("handles expression with no refs", () => {
371
+ const table = new RefTable();
372
+ const result = interpolateRefs("1 + 2", table);
373
+ expect(result).toBe("1 + 2");
374
+ });
375
+
376
+ test("handles mixed ref types in expression", () => {
377
+ const table = new RefTable();
378
+ table.addVar("rid-1", "x");
379
+ table.addObject("obj-1", "arr");
380
+ const result = interpolateRefs("@v1 === @o1[0]", table);
381
+ expect(result).toBe("rid-1 === obj-1[0]");
382
+ });
383
+ });