gdharness 1.3.3 → 1.3.5

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 (52) hide show
  1. package/build/cli.js +532 -456
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +181 -4
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
  6. package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
  7. package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
  8. package/build/godot/addons/gdharness_editor/property_values.gd +192 -231
  9. package/build/godot/addons/gdharness_editor/reading.gd +15 -0
  10. package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
  11. package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
  12. package/build/godot/addons/gdharness_editor/scene_file.gd +97 -14
  13. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
  14. package/build/godot/addons/gdharness_editor/tool_executor.gd +9 -3
  15. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +38 -35
  16. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +20 -12
  17. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
  18. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
  19. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
  20. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +68 -2
  21. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +94 -333
  22. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
  23. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_runtime/declared_values.gd +293 -0
  25. package/build/godot/addons/gdharness_runtime/declared_values.gd.uid +1 -0
  26. package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
  27. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -0
  28. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -1
  29. package/build/godot/addons/gdharness_runtime/runtime_input.gd +26 -39
  30. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +18 -0
  31. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
  32. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
  33. package/build/godot/addons/gdharness_runtime/runtime_values.gd +62 -5
  34. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +48 -15
  35. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
  36. package/build/godot/operations/animation_reads.gd +3 -2
  37. package/build/godot/operations/classdb_queries.gd +112 -46
  38. package/build/godot/operations/declared_values.gd +181 -4
  39. package/build/godot/operations/dependencies.gd +14 -39
  40. package/build/godot/operations/editor_tools.gd +4 -3
  41. package/build/godot/operations/file_reads.gd +15 -3
  42. package/build/godot/operations/gdscript_analysis.gd +229 -75
  43. package/build/godot/operations/gdscript_authoring.gd +157 -36
  44. package/build/godot/operations/import_pipeline.gd +17 -6
  45. package/build/godot/operations/logger.gd +7 -0
  46. package/build/godot/operations/property_paths.gd +225 -0
  47. package/build/godot/operations/property_values.gd +192 -231
  48. package/build/godot/operations/reading.gd +15 -0
  49. package/build/godot/operations/same_values.gd +177 -0
  50. package/build/godot/operations/serialisation.gd +75 -3
  51. package/build/keeper.js +83 -69
  52. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -7836,16 +7836,25 @@ var require_public_api = __commonJS(function(exports) {
7836
7836
 
7837
7837
  // src/paths.ts
7838
7838
  import { realpathSync, statSync as statSync2 } from "node:fs";
7839
- import { isAbsolute, relative, resolve, sep } from "node:path";
7840
- function realPathOr(path) {
7841
- try {
7842
- return realpathSync.native(path);
7843
- } catch {
7844
- return path;
7839
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
7840
+ function realAsFarAsItExists(path) {
7841
+ const missing = [];
7842
+ let at = path;
7843
+ for (;; ) {
7844
+ try {
7845
+ return join3(realpathSync.native(at), ...missing);
7846
+ } catch {
7847
+ const parent = dirname2(at);
7848
+ if (parent === at) {
7849
+ return path;
7850
+ }
7851
+ missing.unshift(basename2(at));
7852
+ at = parent;
7853
+ }
7845
7854
  }
7846
7855
  }
7847
7856
  function isSameDirectory(onePath, otherPath) {
7848
- return relative(realPathOr(resolve(onePath)), realPathOr(resolve(otherPath))) === "";
7857
+ return relative(realAsFarAsItExists(resolve(onePath)), realAsFarAsItExists(resolve(otherPath))) === "";
7849
7858
  }
7850
7859
  function isWithinRoot(rootPath, candidatePath) {
7851
7860
  const step = relative(resolve(rootPath), resolve(candidatePath));
@@ -7880,9 +7889,11 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
7880
7889
  };
7881
7890
  }
7882
7891
  if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
7892
+ const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
7893
+ const example = tail.slice(-2).join("/") || "scenes/main.tscn";
7883
7894
  return {
7884
7895
  ok: false,
7885
- reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as 'scenes/main.tscn'.`
7896
+ reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
7886
7897
  };
7887
7898
  }
7888
7899
  const kept = keptInside(trimmed);
@@ -7922,7 +7933,7 @@ function exportedBuild(projectPath, candidatePath) {
7922
7933
  reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
7923
7934
  };
7924
7935
  }
7925
- if (!isWithinRoot(realPathOr(projectPath), real)) {
7936
+ if (!isWithinRoot(realAsFarAsItExists(projectPath), real)) {
7926
7937
  return {
7927
7938
  ok: false,
7928
7939
  reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
@@ -7941,7 +7952,7 @@ var init_paths = __esm(() => {
7941
7952
 
7942
7953
  // src/runner.ts
7943
7954
  import { existsSync as existsSync3 } from "node:fs";
7944
- import { dirname as dirname2, join as join3 } from "node:path";
7955
+ import { dirname as dirname3, join as join4 } from "node:path";
7945
7956
  import process3 from "node:process";
7946
7957
  function currentRunner() {
7947
7958
  const versions = process3.versions;
@@ -7962,7 +7973,7 @@ function spawnFor(runner, version, execPath = process3.execPath) {
7962
7973
  return { command: alongside(execPath, "npx") ?? "npx", args: ["-y", spec] };
7963
7974
  }
7964
7975
  function alongside(execPath, name) {
7965
- const runner = join3(dirname2(execPath), process3.platform === "win32" ? `${name}.cmd` : name);
7976
+ const runner = join4(dirname3(execPath), process3.platform === "win32" ? `${name}.cmd` : name);
7966
7977
  return existsSync3(runner) ? runner : undefined;
7967
7978
  }
7968
7979
  var init_runner = () => {};
@@ -8303,25 +8314,25 @@ var init_engine_run = __esm(() => {
8303
8314
  // src/doc-cache.ts
8304
8315
  import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
8305
8316
  import { homedir as homedir3 } from "node:os";
8306
- import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
8317
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute2, join as join6 } from "node:path";
8307
8318
  import process5 from "node:process";
8308
8319
  function editorCacheDirectory(godotPath, platform = process5.platform, env = process5.env, home = homedir3()) {
8309
- const beside = dirname4(godotPath);
8310
- const bundle = platform === "darwin" && basename3(beside) === "MacOS" && basename3(dirname4(beside)) === "Contents" ? dirname4(dirname4(dirname4(beside))) : null;
8320
+ const beside = dirname5(godotPath);
8321
+ const bundle = platform === "darwin" && basename4(beside) === "MacOS" && basename4(dirname5(beside)) === "Contents" ? dirname5(dirname5(dirname5(beside))) : null;
8311
8322
  for (const folder of bundle === null ? [beside] : [beside, bundle]) {
8312
- if (existsSync5(join5(folder, "._sc_")) || existsSync5(join5(folder, "_sc_"))) {
8313
- return join5(folder, "editor_data", "cache");
8323
+ if (existsSync5(join6(folder, "._sc_")) || existsSync5(join6(folder, "_sc_"))) {
8324
+ return join6(folder, "editor_data", "cache");
8314
8325
  }
8315
8326
  }
8316
8327
  if (platform === "win32") {
8317
8328
  const set = (name) => env[name] === "" ? undefined : env[name];
8318
- return join5(set("LOCALAPPDATA") ?? set("TEMP") ?? join5(home, "AppData", "Local"), "Godot");
8329
+ return join6(set("LOCALAPPDATA") ?? set("TEMP") ?? join6(home, "AppData", "Local"), "Godot");
8319
8330
  }
8320
8331
  if (platform === "darwin") {
8321
- return join5(home, "Library", "Caches", "Godot");
8332
+ return join6(home, "Library", "Caches", "Godot");
8322
8333
  }
8323
8334
  const xdg = env["XDG_CACHE_HOME"];
8324
- return join5(xdg !== undefined && isAbsolute2(xdg) ? xdg : join5(home, ".cache"), "godot");
8335
+ return join6(xdg !== undefined && isAbsolute2(xdg) ? xdg : join6(home, ".cache"), "godot");
8325
8336
  }
8326
8337
  function docCachesWritten(directory) {
8327
8338
  const written = new Map;
@@ -8333,7 +8344,7 @@ function docCachesWritten(directory) {
8333
8344
  }
8334
8345
  for (const name of names.filter((one) => CACHE_FILE.test(one))) {
8335
8346
  try {
8336
- written.set(join5(directory, name), statSync4(join5(directory, name)).mtimeMs);
8347
+ written.set(join6(directory, name), statSync4(join6(directory, name)).mtimeMs);
8337
8348
  } catch {}
8338
8349
  }
8339
8350
  return written;
@@ -8358,7 +8369,7 @@ var init_doc_cache = __esm(() => {
8358
8369
 
8359
8370
  // src/library-copies.ts
8360
8371
  import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8361
- import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute3, join as join6 } from "node:path";
8372
+ import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute3, join as join7 } from "node:path";
8362
8373
  function freeLibraryCopies(projectPath, platform = process.platform) {
8363
8374
  const setAside = [];
8364
8375
  const swept = [];
@@ -8366,12 +8377,12 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8366
8377
  return { setAside, swept };
8367
8378
  }
8368
8379
  for (const library of windowsLibraries(projectPath)) {
8369
- const folder = dirname5(library);
8370
- const copy = `~${basename4(library)}`;
8371
- if (existsSync6(join6(folder, copy))) {
8372
- const aside = join6(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8380
+ const folder = dirname6(library);
8381
+ const copy = `~${basename5(library)}`;
8382
+ if (existsSync6(join7(folder, copy))) {
8383
+ const aside = join7(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8373
8384
  try {
8374
- renameSync(join6(folder, copy), aside);
8385
+ renameSync(join7(folder, copy), aside);
8375
8386
  setAside.push(aside);
8376
8387
  } catch {}
8377
8388
  }
@@ -8384,8 +8395,8 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8384
8395
  for (const name of names) {
8385
8396
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
8386
8397
  try {
8387
- unlinkSync(join6(folder, name));
8388
- swept.push(join6(folder, name));
8398
+ unlinkSync(join7(folder, name));
8399
+ swept.push(join7(folder, name));
8389
8400
  } catch {}
8390
8401
  }
8391
8402
  }
@@ -8393,7 +8404,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8393
8404
  return { setAside, swept };
8394
8405
  }
8395
8406
  function windowsLibraries(projectPath) {
8396
- const listed = readOrNull(join6(projectPath, ".godot", "extension_list.cfg"));
8407
+ const listed = readOrNull(join7(projectPath, ".godot", "extension_list.cfg"));
8397
8408
  if (listed === null) {
8398
8409
  return [];
8399
8410
  }
@@ -8412,7 +8423,7 @@ function windowsLibraries(projectPath) {
8412
8423
  continue;
8413
8424
  }
8414
8425
  const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
8415
- const library = named === undefined ? null : resolveResource(projectPath, dirname5(manifest), named);
8426
+ const library = named === undefined ? null : resolveResource(projectPath, dirname6(manifest), named);
8416
8427
  if (library !== null) {
8417
8428
  libraries.add(library);
8418
8429
  }
@@ -8425,9 +8436,9 @@ function resolveResource(projectPath, relativeTo, path) {
8425
8436
  return null;
8426
8437
  }
8427
8438
  if (path.startsWith("res://")) {
8428
- return join6(projectPath, path.slice("res://".length));
8439
+ return join7(projectPath, path.slice("res://".length));
8429
8440
  }
8430
- return isAbsolute3(path) ? path : join6(relativeTo, path);
8441
+ return isAbsolute3(path) ? path : join7(relativeTo, path);
8431
8442
  }
8432
8443
  function readOrNull(path) {
8433
8444
  try {
@@ -8441,10 +8452,10 @@ var init_library_copies = () => {};
8441
8452
  // src/reimport.ts
8442
8453
  import { createHash } from "node:crypto";
8443
8454
  import { existsSync as existsSync7, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync5, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8444
- import { basename as basename5, join as join7 } from "node:path";
8455
+ import { basename as basename6, join as join8 } from "node:path";
8445
8456
  function forceNextImport(projectPath, resourcePath) {
8446
8457
  const relative = resourcePath.replace(/^res:\/\//, "");
8447
- const sidecar = join7(projectPath, `${relative}.import`);
8458
+ const sidecar = join8(projectPath, `${relative}.import`);
8448
8459
  if (!existsSync7(sidecar)) {
8449
8460
  return;
8450
8461
  }
@@ -8455,7 +8466,7 @@ function forceNextImport(projectPath, resourcePath) {
8455
8466
  }
8456
8467
  function importRecord(projectPath, resourcePath) {
8457
8468
  const digest = createHash("md5").update(resourcePath).digest("hex");
8458
- return join7(projectPath, ".godot", "imported", `${basename5(resourcePath)}-${digest}.md5`);
8469
+ return join8(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
8459
8470
  }
8460
8471
  var init_reimport = () => {};
8461
8472
 
@@ -8477,10 +8488,10 @@ var init_alive = () => {};
8477
8488
  // src/scratch.ts
8478
8489
  import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "node:fs";
8479
8490
  import { tmpdir as tmpdir2 } from "node:os";
8480
- import { join as join8 } from "node:path";
8491
+ import { join as join9 } from "node:path";
8481
8492
  import process7 from "node:process";
8482
8493
  function scratchDirectory(kind) {
8483
- return mkdtempSync(join8(tmpdir2(), `gdharness-${kind}-${process7.pid}-`));
8494
+ return mkdtempSync(join9(tmpdir2(), `gdharness-${kind}-${process7.pid}-`));
8484
8495
  }
8485
8496
  function sweepAbandonedScratch(directory = tmpdir2()) {
8486
8497
  let entries;
@@ -8496,7 +8507,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
8496
8507
  continue;
8497
8508
  }
8498
8509
  try {
8499
- remove(join8(directory, entry));
8510
+ remove(join9(directory, entry));
8500
8511
  } catch {}
8501
8512
  }
8502
8513
  }
@@ -9081,7 +9092,7 @@ var init_junit = __esm(() => {
9081
9092
  });
9082
9093
 
9083
9094
  // src/script-origins.ts
9084
- import { join as join9 } from "node:path";
9095
+ import { join as join10 } from "node:path";
9085
9096
  function unresolvedClassIn(message) {
9086
9097
  return UNRESOLVED.exec(message)?.[1] ?? null;
9087
9098
  }
@@ -9143,7 +9154,7 @@ function checkWithEngine(godotPath, projectPath) {
9143
9154
  const ran = await runEngine(godotPath, [
9144
9155
  "--headless",
9145
9156
  "--log-file",
9146
- join9(logDir, "engine.log"),
9157
+ join10(logDir, "engine.log"),
9147
9158
  "--path",
9148
9159
  projectPath,
9149
9160
  "--check-only",
@@ -9189,8 +9200,8 @@ var init_script_origins = __esm(() => {
9189
9200
  });
9190
9201
 
9191
9202
  // src/headless.ts
9192
- import { existsSync as existsSync9, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
9193
- import { dirname as dirname6, join as join10 } from "node:path";
9203
+ import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
9204
+ import { dirname as dirname7, join as join11 } from "node:path";
9194
9205
  function snakeCased(params) {
9195
9206
  const result = emptyRecord();
9196
9207
  for (const [key, value] of Object.entries(params)) {
@@ -9222,6 +9233,19 @@ function withNotCompiled(note, entries) {
9222
9233
  const path = entries.map(notCompiledIn).find((found) => found !== undefined);
9223
9234
  return path === undefined ? note : `${note} ${path} did not compile, so every run of this project starts its autoloads without it until it is fixed; its errors are among these.`;
9224
9235
  }
9236
+ function withoutAsides(entries) {
9237
+ const kept = [];
9238
+ let aside = false;
9239
+ for (const entry of entries) {
9240
+ const mark = entry.source === "stderr" ? entry.text : "";
9241
+ if (mark === ASIDE || mark === RESUMED) {
9242
+ aside = mark === ASIDE;
9243
+ } else if (!aside) {
9244
+ kept.push(entry);
9245
+ }
9246
+ }
9247
+ return kept;
9248
+ }
9225
9249
  function problemsIn(log) {
9226
9250
  return problemsAmong(log.everything());
9227
9251
  }
@@ -9258,19 +9282,12 @@ function refusalsIn(log) {
9258
9282
  }
9259
9283
  function scriptFault(problems, script) {
9260
9284
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
9261
- const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
9285
+ const directories = [...new Set([spelled(dirname7(script)), spelled(realAsFarAsItExists(dirname7(script)))])];
9262
9286
  return problems.find((entry) => {
9263
9287
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
9264
9288
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
9265
9289
  });
9266
9290
  }
9267
- function resolvedOr(path) {
9268
- try {
9269
- return realpathSync2.native(path);
9270
- } catch {
9271
- return path;
9272
- }
9273
- }
9274
9291
  function librariesNotCopied(messages) {
9275
9292
  const found = new Set;
9276
9293
  for (const entry of messages) {
@@ -9322,7 +9339,7 @@ async function importOnce(godotPath, projectPath) {
9322
9339
  ran = await runEngine(godotPath, [
9323
9340
  "--headless",
9324
9341
  "--log-file",
9325
- join10(logDir, "engine.log"),
9342
+ join11(logDir, "engine.log"),
9326
9343
  "--path",
9327
9344
  projectPath,
9328
9345
  "--import"
@@ -9344,13 +9361,13 @@ async function importOnce(godotPath, projectPath) {
9344
9361
  }
9345
9362
  async function runOperation(engine, operation, params, projectPath) {
9346
9363
  const paramsDir = scratchDirectory("params");
9347
- const paramsFile = join10(paramsDir, `${operation}.json`);
9348
- const answerFile = join10(paramsDir, "answer.json");
9364
+ const paramsFile = join11(paramsDir, `${operation}.json`);
9365
+ const answerFile = join11(paramsDir, "answer.json");
9349
9366
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
9350
9367
  const args = [
9351
9368
  "--headless",
9352
9369
  "--log-file",
9353
- join10(paramsDir, "engine.log"),
9370
+ join11(paramsDir, "engine.log"),
9354
9371
  "--path",
9355
9372
  projectPath,
9356
9373
  "--script",
@@ -9368,7 +9385,7 @@ async function runOperation(engine, operation, params, projectPath) {
9368
9385
  } finally {
9369
9386
  discard(paramsDir);
9370
9387
  }
9371
- const everything = ran.log.everything();
9388
+ const everything = withoutAsides(ran.log.everything());
9372
9389
  const marked = (text) => everything.findIndex((entry) => entry.source === "stderr" && entry.text === text);
9373
9390
  const answered = marked(ANSWERED);
9374
9391
  const waiting = marked(WAITING);
@@ -9452,11 +9469,12 @@ function answerIn(file) {
9452
9469
  };
9453
9470
  }
9454
9471
  }
9455
- var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
9472
+ var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", ASIDE = "[gdharness] aside", RESUMED = "[gdharness] resumed", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
9456
9473
  var init_headless = __esm(() => {
9457
9474
  init_doc_cache();
9458
9475
  init_engine_run();
9459
9476
  init_library_copies();
9477
+ init_paths();
9460
9478
  init_reimport();
9461
9479
  init_scratch();
9462
9480
  init_script_origins();
@@ -9465,7 +9483,7 @@ var init_headless = __esm(() => {
9465
9483
  // src/update-check.ts
9466
9484
  import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
9467
9485
  import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
9468
- import { join as join11 } from "node:path";
9486
+ import { join as join12 } from "node:path";
9469
9487
  function packageUrl(root) {
9470
9488
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
9471
9489
  }
@@ -9487,20 +9505,20 @@ function cacheDirectory(environment) {
9487
9505
  };
9488
9506
  const home = set("HOME") ?? homedir4();
9489
9507
  if (process.platform === "win32") {
9490
- return join11(set("LOCALAPPDATA") ?? home, "gdharness");
9508
+ return join12(set("LOCALAPPDATA") ?? home, "gdharness");
9491
9509
  }
9492
9510
  if (process.platform === "darwin") {
9493
- return join11(home, "Library", "Caches", "gdharness");
9511
+ return join12(home, "Library", "Caches", "gdharness");
9494
9512
  }
9495
- return join11(set("XDG_CACHE_HOME") ?? join11(home, ".cache"), "gdharness");
9513
+ return join12(set("XDG_CACHE_HOME") ?? join12(home, ".cache"), "gdharness");
9496
9514
  }
9497
9515
  function cacheFile(environment = process.env) {
9498
9516
  try {
9499
9517
  const directory = cacheDirectory(environment);
9500
9518
  mkdirSync3(directory, { recursive: true, mode: 448 });
9501
- return join11(directory, "update-check.json");
9519
+ return join12(directory, "update-check.json");
9502
9520
  } catch {
9503
- return join11(tmpdir3(), "gdharness-update-check.json");
9521
+ return join12(tmpdir3(), "gdharness-update-check.json");
9504
9522
  }
9505
9523
  }
9506
9524
  function readCache(path) {
@@ -10501,7 +10519,7 @@ var init_gdscript_source = __esm(() => {
10501
10519
 
10502
10520
  // src/class-cache.ts
10503
10521
  import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
10504
- import { join as join12, posix } from "node:path";
10522
+ import { join as join13, posix } from "node:path";
10505
10523
  function isToolScript(source) {
10506
10524
  return TOOL.test(codeOf(source));
10507
10525
  }
@@ -10517,14 +10535,14 @@ function declaredClasses(projectPath) {
10517
10535
  }
10518
10536
  function eachScript(projectPath, each) {
10519
10537
  const visit = (directory, prefix) => {
10520
- if (existsSync11(join12(directory, ".gdignore"))) {
10538
+ if (existsSync11(join13(directory, ".gdignore"))) {
10521
10539
  return;
10522
10540
  }
10523
10541
  for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10524
10542
  if (entry.name.startsWith(".")) {
10525
10543
  continue;
10526
10544
  }
10527
- const path = join12(directory, entry.name);
10545
+ const path = join13(directory, entry.name);
10528
10546
  if (entry.isDirectory()) {
10529
10547
  visit(path, `${prefix}${entry.name}/`);
10530
10548
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -10550,7 +10568,7 @@ function scriptsNaming(projectPath, classes) {
10550
10568
  return naming.sort((a, b) => a.script.localeCompare(b.script));
10551
10569
  }
10552
10570
  function cachedClasses(projectPath) {
10553
- const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
10571
+ const cache = join13(projectPath, ".godot", "global_script_class_cache.cfg");
10554
10572
  if (!existsSync11(cache)) {
10555
10573
  return null;
10556
10574
  }
@@ -10561,6 +10579,22 @@ function cachedClasses(projectPath) {
10561
10579
  }
10562
10580
  return listed;
10563
10581
  }
10582
+ function addonOf(path) {
10583
+ return /^res:\/\/addons\/([^/]+)\//.exec(path)?.[1];
10584
+ }
10585
+ function withAddonPathsCounted(paths) {
10586
+ const own = [];
10587
+ const inAddons = {};
10588
+ for (const path of paths) {
10589
+ const addon = addonOf(path);
10590
+ if (addon === undefined) {
10591
+ own.push(path);
10592
+ } else {
10593
+ inAddons[addon] = (inAddons[addon] ?? 0) + 1;
10594
+ }
10595
+ }
10596
+ return { own, inAddons };
10597
+ }
10564
10598
  function withAddonClassesCounted(rebuilt, listed) {
10565
10599
  const counted = { ...rebuilt };
10566
10600
  for (const key of ["added", "changed"]) {
@@ -10571,7 +10605,7 @@ function withAddonClassesCounted(rebuilt, listed) {
10571
10605
  const own = [];
10572
10606
  const perAddon = {};
10573
10607
  for (const name of names) {
10574
- const addon = /^res:\/\/addons\/([^/]+)\//.exec(listed.get(String(name)) ?? "")?.[1];
10608
+ const addon = addonOf(listed.get(String(name)) ?? "");
10575
10609
  if (addon === undefined) {
10576
10610
  own.push(name);
10577
10611
  } else {
@@ -10832,12 +10866,12 @@ function uncachedForTheEngineNote(projectPath, types) {
10832
10866
  }
10833
10867
  const one = types.length === 1;
10834
10868
  const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
10835
- const cacheExists = existsSync11(join12(projectPath, ".godot", "global_script_class_cache.cfg"));
10869
+ const cacheExists = existsSync11(join13(projectPath, ".godot", "global_script_class_cache.cfg"));
10836
10870
  const state = cacheExists ? `missing from .godot/global_script_class_cache.cfg, which is where the engine that compiled these scripts resolves a class name from, so the error is the cache being behind the files rather than the script.` : `the engine that compiled these scripts resolves a class name from .godot/global_script_class_cache.cfg, which has not been written yet, as in a project no editor has opened, so the error is the missing cache rather than the script.`;
10837
10871
  return `${named} ${one ? "is" : "are"} declared in this project${cacheExists ? " and" : ", but"} ${state} ` + "project_import refresh_classes writes the cache from the declarations on disk; editor_run start " + "writes it before a game, and an editor opening the project writes it when it scans.";
10838
10872
  }
10839
10873
  function cacheWrittenAt(projectPath) {
10840
- const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
10874
+ const cache = join13(projectPath, ".godot", "global_script_class_cache.cfg");
10841
10875
  try {
10842
10876
  return statSync6(cache).mtimeMs;
10843
10877
  } catch {
@@ -10851,14 +10885,14 @@ function declaredSince(projectPath, paths, names, since) {
10851
10885
  return false;
10852
10886
  }
10853
10887
  try {
10854
- return statSync6(join12(projectPath, path.slice("res://".length))).mtimeMs > since;
10888
+ return statSync6(join13(projectPath, path.slice("res://".length))).mtimeMs > since;
10855
10889
  } catch {
10856
10890
  return false;
10857
10891
  }
10858
10892
  });
10859
10893
  }
10860
10894
  function cachedAtMissingPaths(cached, projectPath) {
10861
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join12(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10895
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join13(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10862
10896
  }
10863
10897
  function staleAgainst(cached, projectPath) {
10864
10898
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -10884,7 +10918,7 @@ var init_class_cache = __esm(() => {
10884
10918
 
10885
10919
  // src/project-scan.ts
10886
10920
  import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
10887
- import { extname, join as join13 } from "node:path";
10921
+ import { extname, join as join14 } from "node:path";
10888
10922
  function skipped(name) {
10889
10923
  return name.startsWith(".") || SKIPPED.has(name);
10890
10924
  }
@@ -10892,10 +10926,10 @@ function steppedOver(directory) {
10892
10926
  return gdignored(directory) || installedByUs(directory);
10893
10927
  }
10894
10928
  function gdignored(directory) {
10895
- return existsSync12(join13(directory, ".gdignore"));
10929
+ return existsSync12(join14(directory, ".gdignore"));
10896
10930
  }
10897
10931
  function installedByUs(directory) {
10898
- return existsSync12(join13(directory, VERSION_MARKER));
10932
+ return existsSync12(join14(directory, VERSION_MARKER));
10899
10933
  }
10900
10934
  function projectStructure(projectPath) {
10901
10935
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10908,7 +10942,7 @@ function projectStructure(projectPath) {
10908
10942
  continue;
10909
10943
  }
10910
10944
  if (entry.isDirectory()) {
10911
- visit(join13(directory, entry.name));
10945
+ visit(join14(directory, entry.name));
10912
10946
  } else if (entry.isFile()) {
10913
10947
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10914
10948
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10938,7 +10972,7 @@ function projectFiles(projectPath, extensions) {
10938
10972
  }
10939
10973
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10940
10974
  if (entry.isDirectory()) {
10941
- visit(join13(directory, entry.name), spelled);
10975
+ visit(join14(directory, entry.name), spelled);
10942
10976
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10943
10977
  found.push(spelled);
10944
10978
  }
@@ -10959,14 +10993,14 @@ function scriptsWithoutUid(projectPath) {
10959
10993
  }
10960
10994
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10961
10995
  if (entry.isDirectory()) {
10962
- visit(join13(directory, entry.name), spelled);
10996
+ visit(join14(directory, entry.name), spelled);
10963
10997
  continue;
10964
10998
  }
10965
10999
  if (!entry.isFile()) {
10966
11000
  continue;
10967
11001
  }
10968
11002
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10969
- if (CARRIES_A_UID.has(extension) && !existsSync12(join13(directory, `${entry.name}.uid`))) {
11003
+ if (CARRIES_A_UID.has(extension) && !existsSync12(join14(directory, `${entry.name}.uid`))) {
10970
11004
  found.push(spelled);
10971
11005
  }
10972
11006
  }
@@ -10977,7 +11011,7 @@ function scriptsWithoutUid(projectPath) {
10977
11011
  function pathsInUidCache(projectPath) {
10978
11012
  let data;
10979
11013
  try {
10980
- data = readFileSync9(join13(projectPath, ".godot", "uid_cache.bin"));
11014
+ data = readFileSync9(join14(projectPath, ".godot", "uid_cache.bin"));
10981
11015
  } catch {
10982
11016
  return null;
10983
11017
  }
@@ -11015,7 +11049,7 @@ function projectTexts(projectPath) {
11015
11049
  if (skipped(entry.name)) {
11016
11050
  continue;
11017
11051
  }
11018
- const absolute = join13(directory, entry.name);
11052
+ const absolute = join14(directory, entry.name);
11019
11053
  const spelled = `${prefix}${entry.name}`;
11020
11054
  if (entry.isDirectory()) {
11021
11055
  visit(absolute, `${spelled}/`);
@@ -11045,7 +11079,7 @@ function ignoredTexts(projectPath) {
11045
11079
  if (skipped(entry.name)) {
11046
11080
  continue;
11047
11081
  }
11048
- const absolute = join13(directory, entry.name);
11082
+ const absolute = join14(directory, entry.name);
11049
11083
  const spelled = `${prefix}${entry.name}`;
11050
11084
  if (entry.isDirectory()) {
11051
11085
  visit(absolute, `${spelled}/`, under);
@@ -11075,6 +11109,14 @@ function plainMatch(line, needle, caseSensitive) {
11075
11109
  }
11076
11110
  return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
11077
11111
  }
11112
+ function compiledQuery(query, caseSensitive) {
11113
+ try {
11114
+ return new RegExp(query, caseSensitive ? "" : "i");
11115
+ } catch (error) {
11116
+ const why = errorMessage(error).split(": ").at(-1) ?? "";
11117
+ throw new Error(`'${query}' is not a regular expression: ${why.charAt(0).toLowerCase()}${why.slice(1)}. With regex true, a character such as ( ) [ ] { } . * + ? | ^ $ or \\ stands for itself only after a backslash, as in \\(; or leave regex out to search for the text as written.`);
11118
+ }
11119
+ }
11078
11120
  function searchProject(projectPath, options) {
11079
11121
  const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
11080
11122
  const result = {
@@ -11082,7 +11124,7 @@ function searchProject(projectPath, options) {
11082
11124
  results: [],
11083
11125
  summary: { files_searched: 0, files_with_matches: 0, total_matches: 0, truncated: false }
11084
11126
  };
11085
- const regex = options.regex ? new RegExp(options.query, options.caseSensitive ? "" : "i") : null;
11127
+ const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
11086
11128
  const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
11087
11129
  const full = () => result.summary.truncated;
11088
11130
  const visit = (directory) => {
@@ -11096,7 +11138,7 @@ function searchProject(projectPath, options) {
11096
11138
  if (skipped(entry.name)) {
11097
11139
  continue;
11098
11140
  }
11099
- const entryPath = join13(directory, entry.name);
11141
+ const entryPath = join14(directory, entry.name);
11100
11142
  if (entry.isDirectory()) {
11101
11143
  visit(entryPath);
11102
11144
  continue;
@@ -11135,6 +11177,7 @@ function searchProject(projectPath, options) {
11135
11177
  }
11136
11178
  var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
11137
11179
  var init_project_scan = __esm(() => {
11180
+ init_errors();
11138
11181
  SKIPPED = new Set(["node_modules"]);
11139
11182
  SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
11140
11183
  ASSET_EXTENSIONS = new Set([
@@ -11192,7 +11235,7 @@ var init_answer_json = __esm(() => {
11192
11235
  });
11193
11236
 
11194
11237
  // src/resources.ts
11195
- import { readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
11238
+ import { readFileSync as readFileSync10, realpathSync as realpathSync2 } from "node:fs";
11196
11239
  import { extname as extname2, resolve as resolve3 } from "node:path";
11197
11240
  function ensureProjectPath(getProjectPath) {
11198
11241
  const projectPath = getProjectPath();
@@ -11215,13 +11258,13 @@ function resolveProjectFile(projectPath, resourcePath) {
11215
11258
  }
11216
11259
  let root;
11217
11260
  try {
11218
- root = realpathSync3.native(projectPath);
11261
+ root = realpathSync2.native(projectPath);
11219
11262
  } catch {
11220
11263
  throw new Refusal(`The project directory ${projectPath} is not there.`);
11221
11264
  }
11222
11265
  let real;
11223
11266
  try {
11224
- real = realpathSync3.native(contained.absolutePath);
11267
+ real = realpathSync2.native(contained.absolutePath);
11225
11268
  } catch {
11226
11269
  throw new Refusal(`No file at ${resourcePath} in the project.`);
11227
11270
  }
@@ -11550,7 +11593,7 @@ import {
11550
11593
  statSync as statSync7,
11551
11594
  writeFileSync as writeFileSync6
11552
11595
  } from "node:fs";
11553
- import { dirname as dirname7, join as join14 } from "node:path";
11596
+ import { dirname as dirname8, join as join15 } from "node:path";
11554
11597
  import { fileURLToPath } from "node:url";
11555
11598
  function editorAddonDigest(from = shippedAddonsDirectory()) {
11556
11599
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -11565,7 +11608,7 @@ function addonsDigest(names, from) {
11565
11608
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
11566
11609
  continue;
11567
11610
  }
11568
- const path = join14(directory, name);
11611
+ const path = join15(directory, name);
11569
11612
  const inside = `${relative}/${name}`;
11570
11613
  if (statSync7(path).isDirectory()) {
11571
11614
  walk(path, inside);
@@ -11579,7 +11622,7 @@ function addonsDigest(names, from) {
11579
11622
  }
11580
11623
  };
11581
11624
  for (const name of names) {
11582
- walk(join14(from, name), name);
11625
+ walk(join15(from, name), name);
11583
11626
  }
11584
11627
  return hash.digest("hex");
11585
11628
  }
@@ -11598,7 +11641,7 @@ function installedRuntimeDigest(projectPath) {
11598
11641
  return digestMarker(projectPath, RUNTIME_ADDON);
11599
11642
  }
11600
11643
  function digestMarker(projectPath, addon) {
11601
- const marker = join14(projectPath, "addons", addon, DIGEST_MARKER);
11644
+ const marker = join15(projectPath, "addons", addon, DIGEST_MARKER);
11602
11645
  if (!existsSync13(marker)) {
11603
11646
  return null;
11604
11647
  }
@@ -11606,35 +11649,35 @@ function digestMarker(projectPath, addon) {
11606
11649
  return said === "" ? null : said;
11607
11650
  }
11608
11651
  function shippedAddonsDirectory() {
11609
- return join14(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
11652
+ return join15(dirname8(fileURLToPath(import.meta.url)), "godot", "addons");
11610
11653
  }
11611
11654
  function shippedOperationsScript() {
11612
- return join14(dirname7(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
11655
+ return join15(dirname8(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
11613
11656
  }
11614
11657
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
11615
11658
  const installed = [];
11616
11659
  for (const name of ADDONS) {
11617
- const source = join14(from, name);
11618
- if (!existsSync13(join14(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
11660
+ const source = join15(from, name);
11661
+ if (!existsSync13(join15(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
11619
11662
  throw new Error(`The package holds no ${name} addon at ${source}.`);
11620
11663
  }
11621
- const target = join14(projectPath, "addons", name);
11664
+ const target = join15(projectPath, "addons", name);
11622
11665
  const replaced = existsSync13(target);
11623
11666
  rmSync5(target, { recursive: true, force: true });
11624
- mkdirSync4(dirname7(target), { recursive: true });
11667
+ mkdirSync4(dirname8(target), { recursive: true });
11625
11668
  cpSync(source, target, { recursive: true });
11626
- writeFileSync6(join14(target, VERSION_MARKER), `${SERVER_VERSION}
11669
+ writeFileSync6(join15(target, VERSION_MARKER), `${SERVER_VERSION}
11627
11670
  `);
11628
11671
  installed.push({ name, path: target, replaced });
11629
11672
  }
11630
- writeFileSync6(join14(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
11673
+ writeFileSync6(join15(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
11631
11674
  `);
11632
- writeFileSync6(join14(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
11675
+ writeFileSync6(join15(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
11633
11676
  `);
11634
11677
  return installed;
11635
11678
  }
11636
11679
  function installedAddonVersion(projectPath) {
11637
- const marker = join14(projectPath, "addons", ADDONS[0], VERSION_MARKER);
11680
+ const marker = join15(projectPath, "addons", ADDONS[0], VERSION_MARKER);
11638
11681
  if (!existsSync13(marker)) {
11639
11682
  return null;
11640
11683
  }
@@ -11644,13 +11687,13 @@ function installedAddonVersion(projectPath) {
11644
11687
  function removeAddons(projectPath) {
11645
11688
  const removed = [];
11646
11689
  for (const name of ADDONS) {
11647
- const target = join14(projectPath, "addons", name);
11690
+ const target = join15(projectPath, "addons", name);
11648
11691
  if (existsSync13(target)) {
11649
11692
  rmSync5(target, { recursive: true, force: true });
11650
11693
  removed.push(target);
11651
11694
  }
11652
11695
  }
11653
- const addons = join14(projectPath, "addons");
11696
+ const addons = join15(projectPath, "addons");
11654
11697
  if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
11655
11698
  rmdirSync(addons);
11656
11699
  removed.push(addons);
@@ -11685,7 +11728,7 @@ function loaderAmong(projectPath, named) {
11685
11728
  }
11686
11729
  let source = null;
11687
11730
  try {
11688
- source = readFileSync11(join14(projectPath, path), "utf8");
11731
+ source = readFileSync11(join15(projectPath, path), "utf8");
11689
11732
  } catch {}
11690
11733
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
11691
11734
  return { name: entry, path, exists: true };
@@ -11727,8 +11770,8 @@ function enabledPlugins(settings) {
11727
11770
  function inspectProject(projectPath) {
11728
11771
  const problems = [];
11729
11772
  const addons = ADDONS.map((name) => {
11730
- const directory = join14(projectPath, "addons", name);
11731
- const marker = join14(directory, VERSION_MARKER);
11773
+ const directory = join15(projectPath, "addons", name);
11774
+ const marker = join15(directory, VERSION_MARKER);
11732
11775
  const installed = existsSync13(directory);
11733
11776
  const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
11734
11777
  const current = version === SERVER_VERSION;
@@ -11739,7 +11782,7 @@ function inspectProject(projectPath) {
11739
11782
  }
11740
11783
  return { name, installed, version, current };
11741
11784
  });
11742
- const settings = parseProjectGodot(readFileSync11(join14(projectPath, "project.godot"), "utf8"));
11785
+ const settings = parseProjectGodot(readFileSync11(join15(projectPath, "project.godot"), "utf8"));
11743
11786
  const pluginsEnabled = enabledPlugins(settings);
11744
11787
  for (const name of EDITOR_PLUGINS) {
11745
11788
  if (!pluginsEnabled.includes(name)) {
@@ -11755,7 +11798,7 @@ function inspectProject(projectPath) {
11755
11798
  const tracked = trackedByGit(projectPath, [...named.values()]);
11756
11799
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
11757
11800
  for (const [name, path] of named) {
11758
- if (!existsSync13(join14(projectPath, path))) {
11801
+ if (!existsSync13(join15(projectPath, path))) {
11759
11802
  if (!addonMissing(path)) {
11760
11803
  problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
11761
11804
  }
@@ -11903,7 +11946,7 @@ function annotationsOf(spec) {
11903
11946
  openWorldHint: false
11904
11947
  };
11905
11948
  }
11906
- var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. An editor of the project that is running and has not reached this server is waited for rather than written around.", IMPORTED_FIRST = "importedFirst names the files whose import was behind them on disk, such as a picture changed since it was imported or one never imported, which were imported before the run through project_import reimport, so the run reads them as they are now; it is left out when every import was current. notImportedFirst lists, as project_import reimport does, any it could not import, and importNote says that the run reads those as they were. The import gives every script without a .uid sidecar one, as the editor's scan does, and uidsWrittenFirst names those scripts. importTookMs is how long the import took; it is outside the run's own time limit, so a call that imported can take that much longer than the limit it was given.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
11949
+ var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. An editor of the project that is running and has not reached this server is waited for rather than written around.", IMPORTED_FIRST = "importedFirst names the files whose import was behind them on disk, such as a picture changed since it was imported or one never imported, which were imported before the run through project_import reimport, so the run reads them as they are now; it is left out when every import was current. notImportedFirst lists, as project_import reimport does, any it could not import, and importNote says that the run reads those as they were. The import gives every script without a .uid sidecar one, as the editor's scan does, writing it beside the script in the project, and uidsWrittenFirst names those scripts. The files inside an addon are counted per addon rather than named, under importedInAddons and uidsWrittenInAddons. importTookMs is how long the import took; it is outside the run's own time limit, so a call that imported can take that much longer than the limit it was given.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
11907
11950
  var init_tool_definitions = __esm(() => {
11908
11951
  init_launch();
11909
11952
  PROJECT_INFO_SECTIONS = [
@@ -11942,7 +11985,7 @@ var init_tool_definitions = __esm(() => {
11942
11985
  };
11943
11986
  PROPERTIES = {
11944
11987
  type: "object",
11945
- description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. An enum property takes the name of a value, as the script spells it, or its number; a value past an export range, or a number no value of an enum has, is written as given and named under outsideDeclared. ${WHOLE_NUMBERS}`,
11988
+ description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". A property holding a Resource takes the res:// path of a file, which a read answers with its class and, when its script declares one, scriptClass, or a resource built into the scene as {"_type": "CircleShape2D", "radius": 8}, its class (an engine class or a script's class_name) beside its properties. A typed list or map of resources or nodes, such as Array[Item], takes each in the same forms, a node by its path; a read answers a resource built into the scene in that form, with what it holds away from the defaults, so it too can be written to another node. script takes the res:// path of a script to attach, one with no class_name included, and is attached before the rest are set, so the properties it declares can be given beside it. Each value is read back once set, and one the engine changed or refused is refused with what it holds. An enum property takes its number or the name of a value: as the script spells it, as the inspector shows it, or for an engine enum as the class's constant, so Add or BLEND_MODE_ADD; a value past an export range, or a number no value of an enum has, is written as given and named under outsideDeclared. A key may reach into a resource built into this one: the property holding it, a slash, then one of its own, so a TileSet's first atlas source takes "sources/0/0:0/0/probability". A resource kept in a file of its own, or built into the scene an instance comes from, is written in that file, or replaced here by one of this file's own. The next element of a list is made by writing to it, as the inspector's Add Element makes it: physics_layer_0/collision_layer on a TileSet with no physics layer makes its first, and "sources/0/0:0/0/physics_layer_0/polygon_0/points" a tile's first collision polygon on that layer. ${WHOLE_NUMBERS}`,
11946
11989
  additionalProperties: true
11947
11990
  };
11948
11991
  XY = {
@@ -12252,7 +12295,7 @@ var init_tool_definitions = __esm(() => {
12252
12295
  direction: {
12253
12296
  type: "string",
12254
12297
  enum: ["forward", "reverse"],
12255
- description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name. summary.total_dependencies counts each file once however many places load it, total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well, answered with exists: false, a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name. Each reference carries a kind: ext_resource, extends, preload, load or path for a reference by path; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about. Two kinds are mentions rather than uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs, and comment is one in ordinary prose, which it would only make stale. summary.in_code is the count with both of those left out, which is the number to read when the question is whether anything still uses this. Default forward."
12298
+ description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name. Each reference carries a kind: ext_resource, extends, preload, load or path for a reference by path; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about. Two kinds are mentions rather than uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs, and comment is one in ordinary prose, which it would only make stale. summary.in_code is the count with both of those left out, which is the number to read when the question is whether anything still uses this. Default forward."
12256
12299
  },
12257
12300
  depth: { type: "number", description: "forward: how many levels to follow. Default unlimited." },
12258
12301
  includeBuiltin: {
@@ -12282,7 +12325,7 @@ var init_tool_definitions = __esm(() => {
12282
12325
  options: {
12283
12326
  type: "object",
12284
12327
  ops: ["set_options"],
12285
- description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads.`,
12328
+ description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads. One already holding the value given is answered under unchanged_options rather than updated_options, and when none changed nothing is written or reimported.`,
12286
12329
  additionalProperties: true
12287
12330
  },
12288
12331
  reimport: {
@@ -12407,7 +12450,7 @@ var init_tool_definitions = __esm(() => {
12407
12450
  timeoutMs: {
12408
12451
  type: "integer",
12409
12452
  minimum: 1,
12410
- description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path, unless it ended repeating one script error, which the verdict names instead. A run whose autoload did not compile is stopped as soon as the engine says so, and names the script that did not under scriptErrors."
12453
+ description: "How long the run may take before it is killed, counted from the engine starting it: the class rebuild and any import before it are outside the limit, and tookMs counts them. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path, unless it ended repeating one script error, which the verdict names instead. A run whose autoload did not compile is stopped as soon as the engine says so, and names the script that did not under scriptErrors."
12411
12454
  },
12412
12455
  printed: {
12413
12456
  type: "string",
@@ -12457,7 +12500,7 @@ var init_tool_definitions = __esm(() => {
12457
12500
  name: "scene_tree",
12458
12501
  title: "Scene tree",
12459
12502
  effect: "read",
12460
- description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
12503
+ description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
12461
12504
  parameters: {
12462
12505
  projectPath: PROJECT_PATH,
12463
12506
  scenePath: SCENE_PATH,
@@ -12472,7 +12515,8 @@ var init_tool_definitions = __esm(() => {
12472
12515
  {
12473
12516
  name: "scene_node",
12474
12517
  title: "Scene nodes",
12475
- description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own. Each rewrite is listed under retargeted ({scenePath, nodePath, property or animation and track, from, to}), and under notRetargeted, with a reason, each left: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A reparent also drops the node ids saved along each rewritten path, which ran through the old parent; the next save writes them again. The scenes rewritten are named under followed, and under notFollowed any left as they were, with why. A delete rewrites no path and lists under stillNaming each node path and track, here and in the scenes instancing or inheriting this one, that named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. Other properties the engine changed in step, as a Control's offsets with its size, come back under alsoChanged as {was, now}.A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
12518
+ description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
12519
+ answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others. Each of these lists is answered only when it names anything. alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}.",
12476
12520
  parameters: {
12477
12521
  projectPath: PROJECT_PATH,
12478
12522
  scenePath: SCENE_PATH,
@@ -12499,17 +12543,34 @@ var init_tool_definitions = __esm(() => {
12499
12543
  ops: ["set"],
12500
12544
  description: "set: on an instance of another scene, whether its children are editable here, as the editor's Editable Children makes them. True lets scene_node set give the nodes inside that instance values of this scene's own, which no other instance shares. An instance nested inside another takes it once the one around it has editable children. False drops every value this scene keeps for them, and every node it added below one of them. The answer names the nodes whose values went under droppedValues (they stay, with the instance's own values) and the added nodes that went under droppedNodes; a node added straight under the instance stays. The answer says editableChildren as the file now holds it."
12501
12545
  },
12546
+ addGroups: {
12547
+ type: "array",
12548
+ items: { type: "string" },
12549
+ ops: ["add", "set"],
12550
+ description: "add and set: groups to put the node in, saved in this scene's file as the editor's Groups dock saves them. One it is in already is kept as it is."
12551
+ },
12552
+ removeGroups: {
12553
+ type: "array",
12554
+ items: { type: "string" },
12555
+ ops: ["set"],
12556
+ description: "set: groups to take the node out of. One it is not in is refused, and so is one another scene puts it in (the scene that makes it, instances it or is inherited here), which only that scene can take it out of."
12557
+ },
12502
12558
  newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
12503
12559
  newParentPath: {
12504
12560
  type: "string",
12505
12561
  ops: ["reparent"],
12506
- description: "reparent: the new parent, which may be the one it has, to move it among its siblings."
12562
+ description: "reparent: the new parent, which may be the one it has, to move it among its siblings. In the scenes the move is followed into, the parent_id_path, from_uid_path and to_uid_path saved beside each rewritten path are dropped, since their ids ran through the old parent; the node's own unique_id is kept, and the next save writes the dropped ones again."
12563
+ },
12564
+ keepGlobalTransform: {
12565
+ type: "boolean",
12566
+ ops: ["reparent"],
12567
+ description: "reparent: whether the node stays where it is drawn, its transform worked out again under the new parent, as the editor's reparent and Node.reparent keep it. A Control keeps its position only. Default true; false keeps its own transform, so it moves with the new parent."
12507
12568
  },
12508
12569
  index: {
12509
12570
  type: "integer",
12510
12571
  minimum: 0,
12511
- ops: ["add", "reparent"],
12512
- description: "add and reparent: where the node goes among the parent's children, 0 for first. Default last. Child order is draw order in a CanvasItem and layout order in a Container."
12572
+ ops: ["add", "duplicate", "reparent"],
12573
+ description: "add, duplicate and reparent: where the node goes among the parent's children, 0 for first. Default last, except for a copy under the original's own parent, which goes right after it. Child order is draw order in a CanvasItem and layout order in a Container."
12513
12574
  },
12514
12575
  includeDefaults: {
12515
12576
  type: "boolean",
@@ -12532,22 +12593,22 @@ var init_tool_definitions = __esm(() => {
12532
12593
  },
12533
12594
  get: {
12534
12595
  effect: "read",
12535
- summary: "read a node's properties, and a TileMapLayer's or TileMap's cells as set_tilemap_cells takes them, the first 1000 with cellCount for all. An instance of another scene also answers instanceOf and editableChildren, as scene_tree does",
12596
+ summary: "read a node's class, path, properties and where its values come from, and a TileMapLayer's or TileMap's cells as set_tilemap_cells takes them, the first 1000 with cellCount for all. An instance of another scene also answers instanceOf and editableChildren, as scene_tree does",
12536
12597
  requires: ["nodePath"]
12537
12598
  },
12538
12599
  set: {
12539
12600
  effect: "destructive",
12540
- summary: "set properties on a node, or make the children of an instance editable; give either or both",
12601
+ summary: "set properties on a node, make the children of an instance editable, or put it in groups or take it out of them; give any of them together",
12541
12602
  requires: ["nodePath"]
12542
12603
  },
12543
12604
  duplicate: {
12544
12605
  effect: "additive",
12545
- summary: "copy a node and its children, beside it or under parentNodePath, answering the copy's newNodePath, parentNodePath and index",
12606
+ summary: "copy a node and its children, right after it or under parentNodePath, answering the copy's newNodePath, parentNodePath and index",
12546
12607
  requires: ["nodePath", "newName"]
12547
12608
  },
12548
12609
  reparent: {
12549
12610
  effect: "destructive",
12550
- summary: "move a node under another parent, or to another place among its siblings",
12611
+ summary: "move a node under another parent, keeping where it is drawn, or to another place among its siblings",
12551
12612
  requires: ["nodePath", "newParentPath"]
12552
12613
  },
12553
12614
  delete: { effect: "destructive", summary: "remove a node and its children", requires: ["nodePath"] },
@@ -12561,7 +12622,7 @@ var init_tool_definitions = __esm(() => {
12561
12622
  {
12562
12623
  name: "scene_signal",
12563
12624
  title: "Scene signals",
12564
- description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. list marks editable false on a connection an instanced scene makes inside itself, which only that scene can change. ${WITHOUT_AN_EDITOR}`,
12625
+ description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection an instanced scene makes inside itself, which only that scene can change. ${WITHOUT_AN_EDITOR}`,
12565
12626
  parameters: {
12566
12627
  projectPath: PROJECT_PATH,
12567
12628
  scenePath: SCENE_PATH,
@@ -12618,7 +12679,7 @@ var init_tool_definitions = __esm(() => {
12618
12679
  animationName: {
12619
12680
  type: "string",
12620
12681
  ops: ["read", "create", "add_track", "set", "rename", "remove", "add_state"],
12621
- description: "The animation in the player. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. add_state: the one that state plays."
12682
+ description: "The animation in the player. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. add_state: the one that state plays. One the player does not have yet is written all the same, answering animationFound false and why under animationMissing."
12622
12683
  },
12623
12684
  newName: {
12624
12685
  type: "string",
@@ -12743,7 +12804,7 @@ var init_tool_definitions = __esm(() => {
12743
12804
  extends: {
12744
12805
  type: "string",
12745
12806
  ops: ["create"],
12746
- description: "create: the base class. Default Node. Not with content."
12807
+ description: "create: the base class. Default the class the template is written for, or Node with no template. Not with content."
12747
12808
  },
12748
12809
  content: {
12749
12810
  type: "string",
@@ -12754,13 +12815,13 @@ var init_tool_definitions = __esm(() => {
12754
12815
  type: "string",
12755
12816
  enum: ["singleton", "state_machine", "component", "resource"],
12756
12817
  ops: ["create"],
12757
- description: "create: a starting shape."
12818
+ description: "create: a starting shape. resource is written for a Resource and the others for a Node; an extends outside that class is refused."
12758
12819
  },
12759
12820
  modifications: { ...SCRIPT_MODIFICATIONS, ops: ["modify"] },
12760
12821
  symbol: {
12761
12822
  type: "string",
12762
12823
  ops: ["rename"],
12763
- description: "rename: what to rename, declared by the script at scriptPath: its class_name, or a method, variable, constant, signal, enum, enum value or inner class at its top level. A class is renamed from the files and needs no editor: every bare use in code, the declaration, doc links such as [Told] and [method Told.say], and the script_class a .tres records. A script that declares something of its own by the old name, and a use after a dot, are left alone, since which of those mean the class cannot be told from the text. A member is renamed from what the editor's language server resolves to it, so it needs the editor open on this project; a method is renamed together with every override of it in the classes inheriting it, and from the topmost class declaring it when scriptPath's is itself an override. Doc links naming the member through any class inheriting it are renamed too, and so are a scene's or resource's saved value of a variable on a node or resource whose script inherits it, and a connection naming the signal on its emitter or the method on its receiver. A name the engine itself declares on the native base, such as _ready, is refused, as is a new name already taken: by a class, an autoload, an identifier in code, a native class or built-in type, or a member anywhere in the class's chain."
12824
+ description: "rename: what to rename, declared by the script at scriptPath: its class_name, or a method, variable, constant, signal, enum, enum value or inner class at its top level. A class_name is renamed from the files and needs no editor: every bare use in code, the declaration, doc links such as [Told] and [method Told.say], and the script_class a .tres records. A script that declares something of its own by the old name, and a use after a dot, are left alone, since which of those mean the class cannot be told from the text. Everything else, an inner class and an enum value included, is renamed from what the editor's language server resolves to it, so it needs the editor open on this project; a method is renamed together with every override of it in the classes inheriting it, and from the topmost class declaring it when scriptPath's is itself an override. Doc links naming the member through any class inheriting it are renamed too, and so are a scene's or resource's saved value of a variable on a node or resource whose script inherits it, and a connection naming the signal on its emitter or the method on its receiver. A name the engine itself declares on the native base, such as _ready, is refused, as is a new name already taken: by a class, an autoload, an identifier in code, a native class or built-in type, or a member anywhere in the class's chain."
12764
12825
  },
12765
12826
  newName: { type: "string", ops: ["rename"], description: "rename: the new name." },
12766
12827
  newScriptPath: {
@@ -12814,7 +12875,7 @@ var init_tool_definitions = __esm(() => {
12814
12875
  operations: {
12815
12876
  structure: {
12816
12877
  effect: "read",
12817
- summary: "functions, variables, signals, constants, enums, inner_classes, class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
12878
+ summary: "functions, variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
12818
12879
  requires: []
12819
12880
  },
12820
12881
  symbols: {
@@ -12835,7 +12896,7 @@ var init_tool_definitions = __esm(() => {
12835
12896
  name: "script_diagnostics",
12836
12897
  title: "Script diagnostics",
12837
12898
  effect: "read",
12838
- description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine: every error that stops a script compiling, with its line and no column, dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and warnings null, since the engine reports warnings only to an editor. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
12899
+ description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine: every error that stops a script compiling, with its line and no column (a call to a method another class lacks is not one, since GDScript checks those only on self: the object may be a subclass that has it, so it fails at run time instead), dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and warnings null, since the engine reports warnings only to an editor. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
12839
12900
  parameters: {
12840
12901
  projectPath: PROJECT_PATH,
12841
12902
  scriptPath: {
@@ -12928,7 +12989,7 @@ var init_tool_definitions = __esm(() => {
12928
12989
  create_shader: { effect: "destructive", summary: "a .gdshader file", requires: ["shaderType"] },
12929
12990
  create_tileset: {
12930
12991
  effect: "destructive",
12931
- summary: "a TileSet from texture atlases",
12992
+ summary: "a TileSet from texture atlases; its physics, terrain and custom data layers, and each tile's collision and other values, are written with modify after it",
12932
12993
  requires: ["sources"]
12933
12994
  },
12934
12995
  set_theme_color: {
@@ -12951,7 +13012,7 @@ var init_tool_definitions = __esm(() => {
12951
13012
  {
12952
13013
  name: "editor_launch",
12953
13014
  title: "Open, restart or close the editor",
12954
- description: "Opens the Godot editor on a project, or restarts or closes the one already connected. close saves open scenes, quits the editor and waits for its process to go, which is the only way to end a hidden one short of killing it; settingsDropped in its answer says what that save took out of project.godot. An editor this server opened that has not connected yet has nothing of this server's to save, so close stops its process instead and answers beforeConnecting. open answers once the process has started, and the editor dials in after its import; a call needing the editor made before then waits for the one this server opened, for as long as a restart waits, and is refused naming it if it has not arrived. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it headless instead and, on Windows, on a desktop of its own, where the games it plays go too; a hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one; it saves open scenes on the way out and answers with the version that came back. An editor a server opened is started again by the server, visible unless hidden: true is passed again, once the old one has gone: it waits 90 seconds for that, or GDHARNESS_EDITOR_RESTART_TIMEOUT_MS, and an old editor still running after it is not replaced, since the new one would find its ports held. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart. An open is refused when the editor it would start could never reach this server: a project other than the one this server serves, whose editor dials that project's own server; a project without the gdharness editor addon installed and enabled; and a project whose editor a restart is already bringing back.",
13015
+ description: "Opens the Godot editor on a project, or restarts or closes the one already connected. close saves open scenes, quits the editor and waits for its process to go, which is the only way to end a hidden one short of killing it; settingsDropped in its answer names what that save took out of project.godot, and is left out when it took nothing; settingsNote says so when the file could not be read to tell. An editor this server opened that has not connected yet has nothing of this server's to save, so close stops its process instead and answers beforeConnecting. open answers once the process has started, and the editor dials in after its import; a call needing the editor made before then waits for the one this server opened, for as long as a restart waits, and is refused naming it if it has not arrived. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it headless instead and, on Windows, on a desktop of its own, where the games it plays go too; a hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one; it saves open scenes on the way out and answers with the version that came back. An editor a server opened is started again by the server, visible unless hidden: true is passed again, once the old one has gone: it waits 90 seconds for that, or GDHARNESS_EDITOR_RESTART_TIMEOUT_MS, and an old editor still running after it is not replaced, since the new one would find its ports held. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart. An open is refused when the editor it would start could never reach this server: a project other than the one this server serves, whose editor dials that project's own server; a project without the gdharness editor addon installed and enabled; and a project whose editor a restart is already bringing back.",
12955
13016
  parameters: {
12956
13017
  projectPath: { ...PROJECT_PATH, ops: ["open"] },
12957
13018
  hidden: {
@@ -13188,7 +13249,7 @@ var init_tool_definitions = __esm(() => {
13188
13249
  title: "Editor status",
13189
13250
  effect: "read",
13190
13251
  description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on a connected editor and on each running game (absent with neither), says it runs addon code other than what this server ships, and staleNote says which half moves: the project, by a gdharness upgrade it spells out to run in a shell, the editor by a restart, or this server by a reconnect. A false editor.lspListening or editor.dapListening comes with serversNote, and editor_launch restart binds the port again. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
13191
- answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, and syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
13252
+ answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, and syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
13192
13253
  parameters: {},
13193
13254
  requires: []
13194
13255
  },
@@ -13210,7 +13271,7 @@ var init_tool_definitions = __esm(() => {
13210
13271
  },
13211
13272
  listMembers: {
13212
13273
  type: "boolean",
13213
- description: "With reloadScript: also answer the whole member lists, heldBeforeReload and reloadedMethods for the methods, heldConstantsBeforeReload and reloadedConstants for the constants. Default false: a script holding hundreds of constants answers hundreds of lines twice to say what one reload added."
13274
+ description: "With reloadScript: also answer the whole member lists, heldBeforeReload and reloadedMethods for the methods, heldConstantsBeforeReload and reloadedConstants for the constants, each with what the script inherits from the scripts it extends. Default false: a script holding hundreds of constants answers hundreds of lines twice to say what one reload added."
13214
13275
  }
13215
13276
  },
13216
13277
  requires: ["projectPath"]
@@ -13254,7 +13315,7 @@ var init_tool_definitions = __esm(() => {
13254
13315
  includeInherited: {
13255
13316
  type: "boolean",
13256
13317
  ops: ["info"],
13257
- description: "info: include inherited members. Default false."
13318
+ description: "info: also name every member the class inherits, under inherited: one entry per ancestor, nearest first, with the names of the methods, properties, signals and enums it declares. The class's own members stay in full; member reads any inherited one in full. Default false."
13258
13319
  },
13259
13320
  member: {
13260
13321
  type: "string",
@@ -13392,7 +13453,7 @@ var init_tool_definitions = __esm(() => {
13392
13453
  {
13393
13454
  name: "runtime_invoke",
13394
13455
  title: "Set or call in the running game",
13395
- description: "Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors the engine reported while a called method ran come back under errors and warnings under warnings, the first 20 of each, with errors_not_shown and warnings_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
13456
+ description: "Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. A method that awaits is answered with the value it returns once it ends, and awaited true; elapsed_usec then runs to its end, and one that outlasts timeout is collected with op result. Errors the engine reported while a called method ran come back under errors and warnings under warnings, the first 20 of each, with errors_not_shown and warnings_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
13396
13457
  parameters: {
13397
13458
  projectPath: RUNNING_PROJECT_PATH,
13398
13459
  pid: RUNNING_PID,
@@ -13409,7 +13470,7 @@ var init_tool_definitions = __esm(() => {
13409
13470
  value: {
13410
13471
  ops: ["set"],
13411
13472
  blank: true,
13412
- description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with, and one carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
13473
+ description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with, and one carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". Text is written as sent: the JSON string "" writes an empty string, and quote marks inside the string are written into the text. An enum property takes the name of a value as well as its number, as the script or the inspector spells it, and a name none of its values has is refused listing them. A number past the range a property is exported with, or one none of its enum values has, is written, as the engine writes it, and the answer says so under outside_declared. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
13413
13474
  },
13414
13475
  method: {
13415
13476
  type: "string",
@@ -13513,7 +13574,7 @@ var init_tool_definitions = __esm(() => {
13513
13574
  name: "runtime_input",
13514
13575
  title: "Send input to the game",
13515
13576
  description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button, mouse motion or drag. All of it works headless, where the viewport is the project's window size and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position. A native window no real pointer has entered gets no hover from Godot, so the runtime supplies it there and the answers say hover_supplied.",
13516
- answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again, for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer.`,
13577
+ answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer. A mouse_click, mouse_motion or mouse_drag with a point outside the window's viewport says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
13517
13578
  parameters: {
13518
13579
  projectPath: RUNNING_PROJECT_PATH,
13519
13580
  pid: RUNNING_PID,
@@ -13542,7 +13603,7 @@ var init_tool_definitions = __esm(() => {
13542
13603
  minimum: 1,
13543
13604
  maximum: 600,
13544
13605
  ops: ["action", "key", "mouse_click", "joypad_button"],
13545
- description: "action, key, mouse_click, joypad_button: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames. Not with pressed, and not on a wheel step. Default 1."
13606
+ description: "action, key, mouse_click, joypad_button: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames, and under held_for the seconds the hold lasted and the physics_frames that ran in it: a process frame is no set time, and a headless game, drawing nothing, runs them far faster than the sixty physics ticks a second that _physics_process sees. Not with pressed, and not on a wheel step. Default 1."
13546
13607
  },
13547
13608
  strength: {
13548
13609
  type: "number",
@@ -13726,7 +13787,7 @@ var init_tool_definitions = __esm(() => {
13726
13787
  },
13727
13788
  value: {
13728
13789
  ops: ["until"],
13729
- description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
13790
+ description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. An enum property is waited for by the name of a value as well as its number, as set takes it. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
13730
13791
  },
13731
13792
  compare: {
13732
13793
  type: "string",
@@ -13775,16 +13836,21 @@ var init_tool_definitions = __esm(() => {
13775
13836
  {
13776
13837
  name: "debug_breakpoint",
13777
13838
  title: "Breakpoints",
13778
- description: "Sets or removes a breakpoint. A set on a script that is not there, or past its last line, is refused, as nothing would ever stop there; a remove takes any. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
13839
+ description: "Sets, removes or lists breakpoints. A set on a script that is not there, or past its last line, is refused, as nothing would ever stop there; a remove takes any line, and with no editor connected answers removed false, saying why, when none was held there. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
13779
13840
  parameters: {
13780
13841
  projectPath: PROJECT_PATH,
13781
- scriptPath: SCRIPT_PATH,
13782
- line: { type: "integer", minimum: 1, description: "One-based line." }
13842
+ scriptPath: { ...SCRIPT_PATH, ops: ["set", "remove"] },
13843
+ line: { type: "integer", minimum: 1, ops: ["set", "remove"], description: "One-based line." }
13783
13844
  },
13784
- requires: ["projectPath", "scriptPath", "line"],
13845
+ requires: ["projectPath"],
13785
13846
  operations: {
13786
- set: { effect: "additive", summary: "set a breakpoint", requires: [] },
13787
- remove: { effect: "destructive", summary: "remove a breakpoint", requires: [] }
13847
+ set: { effect: "additive", summary: "set a breakpoint", requires: ["scriptPath", "line"] },
13848
+ remove: { effect: "destructive", summary: "remove a breakpoint", requires: ["scriptPath", "line"] },
13849
+ list: {
13850
+ effect: "read",
13851
+ summary: "the breakpoints held here under held, and with the editor connected the ones set in its gutter under setInEditor",
13852
+ requires: []
13853
+ }
13788
13854
  }
13789
13855
  },
13790
13856
  {
@@ -35605,12 +35671,12 @@ var init_stdio = __esm(() => {
35605
35671
 
35606
35672
  // src/autoload-failure.ts
35607
35673
  import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
35608
- import { join as join17 } from "node:path";
35674
+ import { join as join18 } from "node:path";
35609
35675
  function scriptsAutoloaded(projectPath) {
35610
35676
  const scripts = new Map;
35611
35677
  let settings;
35612
35678
  try {
35613
- settings = parseProjectGodot(readFileSync13(join17(projectPath, "project.godot"), "utf8"));
35679
+ settings = parseProjectGodot(readFileSync13(join18(projectPath, "project.godot"), "utf8"));
35614
35680
  } catch {
35615
35681
  return scripts;
35616
35682
  }
@@ -35619,7 +35685,7 @@ function scriptsAutoloaded(projectPath) {
35619
35685
  scripts.set(`res://${path}`, name);
35620
35686
  continue;
35621
35687
  }
35622
- const scene = join17(projectPath, path);
35688
+ const scene = join18(projectPath, path);
35623
35689
  if (!existsSync15(scene)) {
35624
35690
  continue;
35625
35691
  }
@@ -35646,7 +35712,7 @@ var init_autoload_failure = __esm(() => {
35646
35712
  import { existsSync as existsSync16, readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
35647
35713
  import { createConnection } from "node:net";
35648
35714
  import { tmpdir as tmpdir4 } from "node:os";
35649
- import { dirname as dirname10, join as join18, resolve as resolve4 } from "node:path";
35715
+ import { dirname as dirname11, join as join19, resolve as resolve4 } from "node:path";
35650
35716
  import { setTimeout as delay } from "node:timers/promises";
35651
35717
  function runtimeDirectory(variables = process.env) {
35652
35718
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -35654,19 +35720,19 @@ function runtimeDirectory(variables = process.env) {
35654
35720
  return explicit;
35655
35721
  }
35656
35722
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
35657
- return join18(perUser ?? tmpdir4(), "gdharness");
35723
+ return join19(perUser ?? tmpdir4(), "gdharness");
35658
35724
  }
35659
35725
  function runtimeDirectories(variables = process.env) {
35660
35726
  const candidates = [runtimeDirectory(variables)];
35661
35727
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
35662
35728
  for (const base of fallbacks) {
35663
- candidates.push(join18(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35729
+ candidates.push(join19(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35664
35730
  }
35665
35731
  return [...new Set(candidates.map((path) => resolve4(path)))];
35666
35732
  }
35667
35733
  function errorReportOf(pid, directories = runtimeDirectories()) {
35668
35734
  for (const directory of directories) {
35669
- const path = join18(directory, `runtime-${pid}.log`);
35735
+ const path = join19(directory, `runtime-${pid}.log`);
35670
35736
  if (existsSync16(path)) {
35671
35737
  return path;
35672
35738
  }
@@ -35820,14 +35886,14 @@ function announcedIn(directory) {
35820
35886
  for (const entry of readdirSync10(directory)) {
35821
35887
  const report = ERROR_REPORT_PATTERN.exec(entry);
35822
35888
  if (report) {
35823
- sweepErrorReport(join18(directory, entry), Number.parseInt(report[1] ?? "", 10));
35889
+ sweepErrorReport(join19(directory, entry), Number.parseInt(report[1] ?? "", 10));
35824
35890
  continue;
35825
35891
  }
35826
35892
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
35827
35893
  if (!match) {
35828
35894
  continue;
35829
35895
  }
35830
- const file = join18(directory, entry);
35896
+ const file = join19(directory, entry);
35831
35897
  const pid = Number.parseInt(match[1] ?? "", 10);
35832
35898
  const running = alive(pid);
35833
35899
  entries.push({ file, pid, running });
@@ -35855,16 +35921,16 @@ function announcedIn(directory) {
35855
35921
  found.push(announced);
35856
35922
  }
35857
35923
  const held = new Set(entries.map((one) => one.file));
35858
- const here = join18(directory, ".");
35924
+ const here = join19(directory, ".");
35859
35925
  for (const file of verdicts.keys()) {
35860
- if (dirname10(file) === here && !held.has(file)) {
35926
+ if (dirname11(file) === here && !held.has(file)) {
35861
35927
  verdicts.delete(file);
35862
35928
  }
35863
35929
  }
35864
35930
  return found;
35865
35931
  }
35866
35932
  async function announcementEnded(pid, directories = runtimeDirectories()) {
35867
- const files = directories.map((directory) => join18(directory, `runtime-${pid}.json`)).filter((file) => existsSync16(file));
35933
+ const files = directories.map((directory) => join19(directory, `runtime-${pid}.json`)).filter((file) => existsSync16(file));
35868
35934
  const first = files[0];
35869
35935
  if (first === undefined) {
35870
35936
  return;
@@ -35920,7 +35986,7 @@ function theOneProject(endpoints) {
35920
35986
  if (endpoints.some((endpoint) => endpoint.project.path === "")) {
35921
35987
  return;
35922
35988
  }
35923
- const paths = new Set(endpoints.map((endpoint) => realPathOr(resolve4(endpoint.project.path))));
35989
+ const paths = new Set(endpoints.map((endpoint) => realAsFarAsItExists(resolve4(endpoint.project.path))));
35924
35990
  const only = [...paths][0];
35925
35991
  return paths.size === 1 ? only : undefined;
35926
35992
  }
@@ -36284,9 +36350,9 @@ var init_runtime_client = __esm(() => {
36284
36350
 
36285
36351
  // src/boot-note.ts
36286
36352
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
36287
- import { dirname as dirname11, join as join19 } from "node:path";
36353
+ import { dirname as dirname12, join as join20 } from "node:path";
36288
36354
  function bootNotePath(projectPath) {
36289
- return join19(projectPath, ".godot", "gdharness-boot.json");
36355
+ return join20(projectPath, ".godot", "gdharness-boot.json");
36290
36356
  }
36291
36357
  function readScenes(projectPath) {
36292
36358
  try {
@@ -36306,7 +36372,7 @@ function readBootNote(projectPath, scene) {
36306
36372
  function writeBootNote(projectPath, scene, announcedAfterMs) {
36307
36373
  try {
36308
36374
  const path = bootNotePath(projectPath);
36309
- mkdirSync6(dirname11(path), { recursive: true });
36375
+ mkdirSync6(dirname12(path), { recursive: true });
36310
36376
  const scenes = {
36311
36377
  ...readScenes(projectPath),
36312
36378
  [scene]: {
@@ -36333,14 +36399,14 @@ var init_boot_note = __esm(() => {
36333
36399
 
36334
36400
  // src/breakpoint-note.ts
36335
36401
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
36336
- import { dirname as dirname12, isAbsolute as isAbsolute4, join as join20, relative as relative3 } from "node:path";
36402
+ import { dirname as dirname13, isAbsolute as isAbsolute4, join as join21, relative as relative3 } from "node:path";
36337
36403
  function breakpointNotePath(projectPath) {
36338
- return join20(projectPath, ".godot", "gdharness-breakpoints.json");
36404
+ return join21(projectPath, ".godot", "gdharness-breakpoints.json");
36339
36405
  }
36340
36406
  function writeBreakpointNote(projectPath, held) {
36341
36407
  const path = breakpointNotePath(projectPath);
36342
- const root = realPathOr(projectPath);
36343
- const inside = held.map((one) => ({ scriptPath: realPathOr(one.scriptPath), lines: one.lines })).filter((one) => one.lines.length > 0 && withinProject(root, one.scriptPath));
36408
+ const root = realAsFarAsItExists(projectPath);
36409
+ const inside = held.map((one) => ({ scriptPath: realAsFarAsItExists(one.scriptPath), lines: one.lines })).filter((one) => one.lines.length > 0 && withinProject(root, one.scriptPath));
36344
36410
  try {
36345
36411
  if (inside.length === 0) {
36346
36412
  rmSync7(path, { force: true });
@@ -36352,7 +36418,7 @@ function writeBreakpointNote(projectPath, held) {
36352
36418
  lines: [...one.lines]
36353
36419
  }))
36354
36420
  };
36355
- mkdirSync7(dirname12(path), { recursive: true });
36421
+ mkdirSync7(dirname13(path), { recursive: true });
36356
36422
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
36357
36423
  `, "utf8");
36358
36424
  } catch (error) {
@@ -36376,7 +36442,7 @@ function readBreakpointNote(projectPath) {
36376
36442
  }
36377
36443
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
36378
36444
  if (lines.length > 0) {
36379
- held.push({ scriptPath: realPathOr(join20(projectPath, script)), lines });
36445
+ held.push({ scriptPath: realAsFarAsItExists(join21(projectPath, script)), lines });
36380
36446
  }
36381
36447
  }
36382
36448
  return held;
@@ -36426,9 +36492,9 @@ var init_whole_file = __esm(() => {
36426
36492
 
36427
36493
  // src/class-note.ts
36428
36494
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync17 } from "node:fs";
36429
- import { dirname as dirname13, join as join21 } from "node:path";
36495
+ import { dirname as dirname14, join as join22 } from "node:path";
36430
36496
  function classNotePath(projectPath) {
36431
- return join21(projectPath, ".godot", "gdharness-classes.json");
36497
+ return join22(projectPath, ".godot", "gdharness-classes.json");
36432
36498
  }
36433
36499
  function readClassNote(projectPath) {
36434
36500
  try {
@@ -36462,7 +36528,7 @@ function unfinishedScanIn(value) {
36462
36528
  function writeClassNote(projectPath, note) {
36463
36529
  const path = classNotePath(projectPath);
36464
36530
  try {
36465
- mkdirSync8(dirname13(path), { recursive: true });
36531
+ mkdirSync8(dirname14(path), { recursive: true });
36466
36532
  } catch {
36467
36533
  return;
36468
36534
  }
@@ -36475,9 +36541,9 @@ var init_class_note = __esm(() => {
36475
36541
 
36476
36542
  // src/config-pin.ts
36477
36543
  import { existsSync as existsSync17, readFileSync as readFileSync18 } from "node:fs";
36478
- import { join as join22 } from "node:path";
36544
+ import { join as join23 } from "node:path";
36479
36545
  function versionsNamedInConfig(projectPath) {
36480
- const config = join22(projectPath, ".mcp.json");
36546
+ const config = join23(projectPath, ".mcp.json");
36481
36547
  if (!existsSync17(config)) {
36482
36548
  return [];
36483
36549
  }
@@ -36505,14 +36571,14 @@ var init_config_pin = __esm(() => {
36505
36571
 
36506
36572
  // src/cut-test-run.ts
36507
36573
  import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
36508
- import { dirname as dirname14, join as join23 } from "node:path";
36574
+ import { dirname as dirname15, join as join24 } from "node:path";
36509
36575
  function keptAt(projectPath) {
36510
- return join23(projectPath, ".godot", "gdharness-reports", "cut-run.json");
36576
+ return join24(projectPath, ".godot", "gdharness-reports", "cut-run.json");
36511
36577
  }
36512
36578
  function keepCutTestRun(projectPath, run) {
36513
36579
  const path = keptAt(projectPath);
36514
36580
  try {
36515
- mkdirSync9(dirname14(path), { recursive: true });
36581
+ mkdirSync9(dirname15(path), { recursive: true });
36516
36582
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
36517
36583
  `, "utf8");
36518
36584
  } catch {}
@@ -37103,13 +37169,14 @@ class GodotDAPClient {
37103
37169
  const key = fileKey(scriptPath);
37104
37170
  const lines = this.breakpoints.get(key);
37105
37171
  if (lines === undefined) {
37106
- return;
37172
+ return false;
37107
37173
  }
37108
- lines.delete(line);
37174
+ const held = lines.delete(line);
37109
37175
  if (lines.size === 0) {
37110
37176
  this.breakpoints.delete(key);
37111
37177
  this.spelled.delete(key);
37112
37178
  }
37179
+ return held;
37113
37180
  }
37114
37181
  holdBreakpoints(held) {
37115
37182
  for (const { scriptPath, lines } of held) {
@@ -37346,10 +37413,10 @@ import {
37346
37413
  writeSync
37347
37414
  } from "node:fs";
37348
37415
  import { tmpdir as tmpdir5 } from "node:os";
37349
- import { join as join24 } from "node:path";
37416
+ import { join as join25 } from "node:path";
37350
37417
  import { setTimeout as delay3 } from "node:timers/promises";
37351
37418
  function helperAssembly(directory = tmpdir5()) {
37352
- return join24(directory, `gdharness-desktop-${DIGEST}.dll`);
37419
+ return join25(directory, `gdharness-desktop-${DIGEST}.dll`);
37353
37420
  }
37354
37421
  async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
37355
37422
  const assembly = helperAssembly(directory);
@@ -37389,8 +37456,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
37389
37456
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
37390
37457
  }
37391
37458
  async function compileHelper(directory, assembly, budgetMs) {
37392
- const scratch = mkdtempSync2(join24(directory, "gdharness-desktop-source-"));
37393
- const source = join24(scratch, "helper.cs");
37459
+ const scratch = mkdtempSync2(join25(directory, "gdharness-desktop-source-"));
37460
+ const source = join25(scratch, "helper.cs");
37394
37461
  writeFileSync12(source, SOURCE, "utf8");
37395
37462
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
37396
37463
  const script = [
@@ -37604,12 +37671,12 @@ import {
37604
37671
  statSync as statSync10,
37605
37672
  writeFileSync as writeFileSync13
37606
37673
  } from "node:fs";
37607
- import { dirname as dirname15, join as join25 } from "node:path";
37674
+ import { dirname as dirname16, join as join26 } from "node:path";
37608
37675
  function editorLogPath(projectPath) {
37609
- return join25(projectPath, ".godot", "gdharness-editor.log");
37676
+ return join26(projectPath, ".godot", "gdharness-editor.log");
37610
37677
  }
37611
37678
  function editorLogNotePath(projectPath) {
37612
- return join25(projectPath, ".godot", "gdharness-editor.json");
37679
+ return join26(projectPath, ".godot", "gdharness-editor.json");
37613
37680
  }
37614
37681
  function readEditorLogNote(projectPath) {
37615
37682
  try {
@@ -37624,7 +37691,7 @@ function readEditorLogNote(projectPath) {
37624
37691
  function clearEditorLog(projectPath) {
37625
37692
  try {
37626
37693
  const path = editorLogPath(projectPath);
37627
- mkdirSync10(dirname15(path), { recursive: true });
37694
+ mkdirSync10(dirname16(path), { recursive: true });
37628
37695
  rmSync11(path, { force: true });
37629
37696
  rmSync11(editorLogNotePath(projectPath), { force: true });
37630
37697
  } catch {}
@@ -37632,7 +37699,7 @@ function clearEditorLog(projectPath) {
37632
37699
  function writeEditorLogNote(projectPath, pid) {
37633
37700
  try {
37634
37701
  const path = editorLogNotePath(projectPath);
37635
- mkdirSync10(dirname15(path), { recursive: true });
37702
+ mkdirSync10(dirname16(path), { recursive: true });
37636
37703
  writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
37637
37704
  } catch {}
37638
37705
  }
@@ -37874,7 +37941,7 @@ var init_editor_log = __esm(() => {
37874
37941
 
37875
37942
  // src/rename.ts
37876
37943
  import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync22, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
37877
- import { dirname as dirname16, posix as posix2 } from "node:path";
37944
+ import { dirname as dirname17, posix as posix2 } from "node:path";
37878
37945
 
37879
37946
  class Script {
37880
37947
  path;
@@ -38589,7 +38656,7 @@ function applyRewrites(rewrites) {
38589
38656
  const done = [];
38590
38657
  try {
38591
38658
  for (const rewrite of rewrites) {
38592
- mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
38659
+ mkdirSync11(dirname17(rewrite.writeTo), { recursive: true });
38593
38660
  const staging = `${rewrite.writeTo}.gdharness-rename`;
38594
38661
  writeFileSync14(staging, rewrite.after, "utf8");
38595
38662
  renameSync3(staging, rewrite.writeTo);
@@ -38642,10 +38709,10 @@ var init_rename = __esm(() => {
38642
38709
  });
38643
38710
 
38644
38711
  // src/lsp_client.ts
38645
- import { existsSync as existsSync22, realpathSync as realpathSync4 } from "node:fs";
38712
+ import { existsSync as existsSync22, realpathSync as realpathSync3 } from "node:fs";
38646
38713
  import { readFile, realpath } from "node:fs/promises";
38647
38714
  import { createConnection as createConnection3 } from "node:net";
38648
- import { dirname as dirname17, join as join26, resolve as resolve5 } from "node:path";
38715
+ import { dirname as dirname18, join as join27, resolve as resolve5 } from "node:path";
38649
38716
  import { setTimeout as delay4 } from "node:timers/promises";
38650
38717
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
38651
38718
  function initializeTimeoutMs() {
@@ -38663,7 +38730,7 @@ function diagnosticsKey(uri) {
38663
38730
  return decoded;
38664
38731
  }
38665
38732
  try {
38666
- return realpathSync4.native(fileURLToPath3(decoded)).toLowerCase();
38733
+ return realpathSync3.native(fileURLToPath3(decoded)).toLowerCase();
38667
38734
  } catch {
38668
38735
  return decoded.toLowerCase();
38669
38736
  }
@@ -38807,7 +38874,7 @@ class GodotLSPClient {
38807
38874
  if (this.initialized) {
38808
38875
  return;
38809
38876
  }
38810
- const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
38877
+ const rootPath = this.rootPath ?? dirname18(resolve5(filePath));
38811
38878
  await this.initialize(rootPath);
38812
38879
  }
38813
38880
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -39323,7 +39390,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
39323
39390
  } catch {
39324
39391
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
39325
39392
  }
39326
- if (!existsSync22(join26(projectPath, "project.godot"))) {
39393
+ if (!existsSync22(join27(projectPath, "project.godot"))) {
39327
39394
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
39328
39395
  }
39329
39396
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -39483,8 +39550,8 @@ var init_lsp_client = __esm(() => {
39483
39550
  });
39484
39551
 
39485
39552
  // src/rename-symbol.ts
39486
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
39487
- import { basename as basename7, dirname as dirname18, join as join27, relative as relative4, resolve as resolve6 } from "node:path";
39553
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync4, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
39554
+ import { basename as basename8, dirname as dirname19, join as join28, relative as relative4, resolve as resolve6 } from "node:path";
39488
39555
  function resourcePathOf(projectPath, absolute) {
39489
39556
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
39490
39557
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -39497,13 +39564,13 @@ function onDiskSpelling(path) {
39497
39564
  let at = resolve6(path);
39498
39565
  for (;; ) {
39499
39566
  try {
39500
- return join27(realpathSync5.native(at), ...missing.reverse());
39567
+ return join28(realpathSync4.native(at), ...missing.reverse());
39501
39568
  } catch {
39502
- const parent = dirname18(at);
39569
+ const parent = dirname19(at);
39503
39570
  if (parent === at) {
39504
39571
  return resolve6(path);
39505
39572
  }
39506
- missing.push(basename7(at));
39573
+ missing.push(basename8(at));
39507
39574
  at = parent;
39508
39575
  }
39509
39576
  }
@@ -39535,7 +39602,8 @@ async function renameSymbol(request, services) {
39535
39602
  };
39536
39603
  }
39537
39604
  if (newName === symbol) {
39538
- return { ok: false, reason: `${symbol} is already called ${newName}; nothing to rename.` };
39605
+ const moving = request.newScriptPath === null ? "" : " resource_edit move moves the script alone, with every path in the project naming it.";
39606
+ return { ok: false, reason: `${symbol} is already called ${newName}; nothing to rename.${moving}` };
39539
39607
  }
39540
39608
  const declaringPath = resourcePathOf(projectPath, request.scriptPath);
39541
39609
  if (declaringPath === null) {
@@ -39925,15 +39993,15 @@ ${mention.kind}`;
39925
39993
  };
39926
39994
  }
39927
39995
  function writeLeftAloneDetail(projectPath, symbol, detail) {
39928
- const directory = join27(projectPath, ".godot", "gdharness-renames");
39996
+ const directory = join28(projectPath, ".godot", "gdharness-renames");
39929
39997
  mkdirSync12(directory, { recursive: true });
39930
39998
  detailWrites += 1;
39931
- const path = join27(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
39999
+ const path = join28(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
39932
40000
  writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
39933
40001
  `, "utf8");
39934
40002
  const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
39935
40003
  for (const old of kept.slice(10)) {
39936
- rmSync12(join27(directory, old), { force: true });
40004
+ rmSync12(join28(directory, old), { force: true });
39937
40005
  }
39938
40006
  return path;
39939
40007
  }
@@ -39982,10 +40050,10 @@ import {
39982
40050
  statSync as statSync11,
39983
40051
  writeFileSync as writeFileSync16
39984
40052
  } from "node:fs";
39985
- import { basename as basename8, dirname as dirname19, extname as extname3, join as join28, relative as relative5 } from "node:path";
40053
+ import { basename as basename9, dirname as dirname20, extname as extname3, join as join29, relative as relative5 } from "node:path";
39986
40054
  function filesUnder(directory) {
39987
40055
  return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
39988
- const path = join28(directory, entry.name);
40056
+ const path = join29(directory, entry.name);
39989
40057
  return entry.isDirectory() ? filesUnder(path) : [path];
39990
40058
  });
39991
40059
  }
@@ -40006,8 +40074,8 @@ function unseenByTheEngine(projectPath, path) {
40006
40074
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
40007
40075
  }
40008
40076
  if (at < steps.length - 1) {
40009
- directory = join28(directory, step);
40010
- if (existsSync24(join28(directory, ".gdignore"))) {
40077
+ directory = join29(directory, step);
40078
+ if (existsSync24(join29(directory, ".gdignore"))) {
40011
40079
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
40012
40080
  }
40013
40081
  }
@@ -40019,12 +40087,12 @@ function installedAddonOf(projectPath, path) {
40019
40087
  if (steps[0] !== "addons" || steps[1] === undefined) {
40020
40088
  return null;
40021
40089
  }
40022
- return existsSync24(join28(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
40090
+ return existsSync24(join29(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
40023
40091
  }
40024
40092
  function binaryResources(projectPath) {
40025
40093
  const found = [];
40026
40094
  const visit = (directory, prefix) => {
40027
- if (existsSync24(join28(directory, ".gdignore"))) {
40095
+ if (existsSync24(join29(directory, ".gdignore"))) {
40028
40096
  return;
40029
40097
  }
40030
40098
  for (const entry of readdirSync12(directory, { withFileTypes: true })) {
@@ -40033,7 +40101,7 @@ function binaryResources(projectPath) {
40033
40101
  }
40034
40102
  const path = `${prefix}${entry.name}`;
40035
40103
  if (entry.isDirectory()) {
40036
- visit(join28(directory, entry.name), `${path}/`);
40104
+ visit(join29(directory, entry.name), `${path}/`);
40037
40105
  } else if (/\.(scn|res)$/i.test(entry.name)) {
40038
40106
  found.push(path);
40039
40107
  }
@@ -40043,7 +40111,7 @@ function binaryResources(projectPath) {
40043
40111
  return found;
40044
40112
  }
40045
40113
  function targetPathOf(projectPath, absolute) {
40046
- const inside = relative5(onDiskSpelling(projectPath), join28(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
40114
+ const inside = relative5(onDiskSpelling(projectPath), join29(onDiskSpelling(dirname20(absolute)), basename9(absolute)));
40047
40115
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
40048
40116
  return null;
40049
40117
  }
@@ -40098,7 +40166,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40098
40166
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
40099
40167
  }
40100
40168
  if (!isDirectory) {
40101
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync24(join28(projectPath, path.slice("res://".length))));
40169
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync24(join29(projectPath, path.slice("res://".length))));
40102
40170
  if (owner !== undefined && owner !== null) {
40103
40171
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
40104
40172
  }
@@ -40132,12 +40200,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40132
40200
  scripts,
40133
40201
  moves,
40134
40202
  named: [from],
40135
- exists: (path) => existsSync24(join28(projectPath, path.slice("res://".length)))
40203
+ exists: (path) => existsSync24(join29(projectPath, path.slice("res://".length)))
40136
40204
  }, { edits: [], mentions: [] });
40137
40205
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
40138
40206
  const binaries = [];
40139
40207
  for (const path of binaryResources(projectPath)) {
40140
- const bytes = readFileSync23(join28(projectPath, path.slice("res://".length)));
40208
+ const bytes = readFileSync23(join29(projectPath, path.slice("res://".length)));
40141
40209
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
40142
40210
  if (names !== undefined) {
40143
40211
  binaries.push({
@@ -40156,7 +40224,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40156
40224
  }));
40157
40225
  const left = leftAloneReport(mentions);
40158
40226
  const rewrites = rewritesOf(plan, texts, null);
40159
- const absoluteOf = (path) => join28(projectPath, path.slice("res://".length));
40227
+ const absoluteOf = (path) => join29(projectPath, path.slice("res://".length));
40160
40228
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
40161
40229
  const moved = [...moves].map(([source, target]) => {
40162
40230
  const rewrite = rewritten.get(source);
@@ -40215,7 +40283,7 @@ function applyFileMove(outcome) {
40215
40283
  }
40216
40284
  const undo = [];
40217
40285
  const rename = (source, target) => {
40218
- mkdirSync13(dirname19(target), { recursive: true });
40286
+ mkdirSync13(dirname20(target), { recursive: true });
40219
40287
  renameSync4(source, target);
40220
40288
  undo.push(() => {
40221
40289
  renameSync4(target, source);
@@ -43224,10 +43292,7 @@ function mayYetConnect(listeningSince, now = Date.now()) {
43224
43292
  return listeningSince === undefined || now - listeningSince.getTime() < CONNECT_WINDOW_MS;
43225
43293
  }
43226
43294
  function theEditorHasComeBack(status, startedAt) {
43227
- return hasSaidWhoItIs(status) && (status.connectedAt?.getTime() ?? 0) > startedAt;
43228
- }
43229
- function hasSaidWhoItIs(status) {
43230
- return status.connected && status.addonVersion !== undefined;
43295
+ return status.connected && (status.connectedAt?.getTime() ?? 0) > startedAt;
43231
43296
  }
43232
43297
  function withoutTrailingSeparator(path) {
43233
43298
  if (path.length <= 1 || !/[\\/]$/.test(path) || /^[A-Za-z]:[\\/]$/.test(path)) {
@@ -43417,7 +43482,8 @@ var init_godot_bridge = __esm(() => {
43417
43482
  this.log("info", "WebSocket bridge stopped");
43418
43483
  }
43419
43484
  isConnected() {
43420
- return this.socket?.readyState === import_websocket.default.OPEN && this.connectionInfo?.identifying !== true;
43485
+ const info = this.connectionInfo;
43486
+ return this.socket?.readyState === import_websocket.default.OPEN && info?.greeted === true && info.identifying !== true;
43421
43487
  }
43422
43488
  isArriving() {
43423
43489
  const info = this.connectionInfo;
@@ -43427,7 +43493,7 @@ var init_godot_bridge = __esm(() => {
43427
43493
  return [...this.pendingRequests.values()].filter((pending) => pending.counted).map((pending) => ({ tool: pending.toolName, startedAt: pending.startedAt })).sort((a, b) => a.startedAt - b.startedAt);
43428
43494
  }
43429
43495
  getStatus() {
43430
- const identifying = this.connectionInfo?.identifying === true;
43496
+ const identifying = this.isArriving();
43431
43497
  const editor = identifying ? null : this.connectionInfo;
43432
43498
  return {
43433
43499
  host: this.host,
@@ -43699,7 +43765,7 @@ var init_godot_bridge = __esm(() => {
43699
43765
  startKeepalive() {
43700
43766
  this.stopKeepalive();
43701
43767
  this.pingInterval = setInterval(() => {
43702
- if (!this.isConnected()) {
43768
+ if (this.socket?.readyState !== import_websocket.default.OPEN) {
43703
43769
  return;
43704
43770
  }
43705
43771
  try {
@@ -43709,6 +43775,7 @@ var init_godot_bridge = __esm(() => {
43709
43775
  this.log("warn", `Failed to send ping: ${errorMessage(error)}`);
43710
43776
  }
43711
43777
  }, KEEPALIVE_INTERVAL_MS);
43778
+ this.pingInterval.unref();
43712
43779
  }
43713
43780
  stopKeepalive() {
43714
43781
  if (!this.pingInterval) {
@@ -43880,7 +43947,7 @@ var init_godot_bridge = __esm(() => {
43880
43947
  // src/import-freshness.ts
43881
43948
  import { createHash as createHash5 } from "node:crypto";
43882
43949
  import { existsSync as existsSync25, readdirSync as readdirSync13, readFileSync as readFileSync24, statSync as statSync12 } from "node:fs";
43883
- import { join as join29 } from "node:path";
43950
+ import { join as join30 } from "node:path";
43884
43951
  function md5Of(file) {
43885
43952
  let stats;
43886
43953
  try {
@@ -43924,7 +43991,7 @@ function listed2(value) {
43924
43991
  return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
43925
43992
  }
43926
43993
  function onDisk(projectPath, resPath) {
43927
- return join29(projectPath, resPath.slice("res://".length));
43994
+ return join30(projectPath, resPath.slice("res://".length));
43928
43995
  }
43929
43996
  function behind(projectPath, resPath, sidecar) {
43930
43997
  const remap = sectionOf(sidecar, "remap");
@@ -43937,7 +44004,7 @@ function behind(projectPath, resPath, sidecar) {
43937
44004
  return "its imported output is not on disk";
43938
44005
  }
43939
44006
  const name = resPath.slice(resPath.lastIndexOf("/") + 1);
43940
- const record = join29(projectPath, ".godot", "imported", `${name}-${createHash5("md5").update(resPath, "utf8").digest("hex")}.md5`);
44007
+ const record = join30(projectPath, ".godot", "imported", `${name}-${createHash5("md5").update(resPath, "utf8").digest("hex")}.md5`);
43941
44008
  let recorded = "";
43942
44009
  try {
43943
44010
  recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync24(record, "utf8"))?.[1] ?? "";
@@ -43953,7 +44020,7 @@ function behind(projectPath, resPath, sidecar) {
43953
44020
  function staleImports(projectPath) {
43954
44021
  const stale = [];
43955
44022
  const visit = (directory, resDirectory) => {
43956
- if (existsSync25(join29(directory, ".gdignore")) || existsSync25(join29(directory, VERSION_MARKER))) {
44023
+ if (existsSync25(join30(directory, ".gdignore")) || existsSync25(join30(directory, VERSION_MARKER))) {
43957
44024
  return;
43958
44025
  }
43959
44026
  let entries;
@@ -43969,7 +44036,7 @@ function staleImports(projectPath) {
43969
44036
  }
43970
44037
  const resPath = `${resDirectory}${entry.name}`;
43971
44038
  if (entry.isDirectory()) {
43972
- visit(join29(directory, entry.name), `${resPath}/`);
44039
+ visit(join30(directory, entry.name), `${resPath}/`);
43973
44040
  continue;
43974
44041
  }
43975
44042
  if (!entry.isFile()) {
@@ -43982,7 +44049,7 @@ function staleImports(projectPath) {
43982
44049
  }
43983
44050
  let sidecar;
43984
44051
  try {
43985
- sidecar = readFileSync24(join29(directory, entry.name), "utf8");
44052
+ sidecar = readFileSync24(join30(directory, entry.name), "utf8");
43986
44053
  } catch {
43987
44054
  continue;
43988
44055
  }
@@ -44103,15 +44170,15 @@ var init_keeper_channel = () => {};
44103
44170
 
44104
44171
  // src/lifecycle-log.ts
44105
44172
  import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync13 } from "node:fs";
44106
- import { dirname as dirname20, join as join30 } from "node:path";
44173
+ import { dirname as dirname21, join as join31 } from "node:path";
44107
44174
  import process11 from "node:process";
44108
44175
  function lifecycleLogPath() {
44109
- return join30(runtimeDirectory(), "servers.log");
44176
+ return join31(runtimeDirectory(), "servers.log");
44110
44177
  }
44111
44178
  function writeLifecycle(event, fields = {}) {
44112
44179
  const path = lifecycleLogPath();
44113
44180
  try {
44114
- mkdirSync14(dirname20(path), { recursive: true });
44181
+ mkdirSync14(dirname21(path), { recursive: true });
44115
44182
  if ((statSync13(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
44116
44183
  try {
44117
44184
  renameSync5(path, `${path}.1`);
@@ -44467,14 +44534,14 @@ var init_prompts = __esm(() => {
44467
44534
 
44468
44535
  // src/restart-note.ts
44469
44536
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
44470
- import { dirname as dirname21, join as join31 } from "node:path";
44537
+ import { dirname as dirname22, join as join32 } from "node:path";
44471
44538
  function restartNotePath(projectPath) {
44472
- return join31(projectPath, ".godot", "gdharness-restart.json");
44539
+ return join32(projectPath, ".godot", "gdharness-restart.json");
44473
44540
  }
44474
44541
  function noteRestartBegun(note) {
44475
44542
  const path = restartNotePath(note.projectPath);
44476
44543
  try {
44477
- mkdirSync15(dirname21(path), { recursive: true });
44544
+ mkdirSync15(dirname22(path), { recursive: true });
44478
44545
  writeFileSync17(path, `${JSON.stringify(note, null, 2)}
44479
44546
  `, "utf8");
44480
44547
  } catch (error) {
@@ -44509,26 +44576,10 @@ var init_restart_note = __esm(() => {
44509
44576
 
44510
44577
  // src/run-record.ts
44511
44578
  import { createHash as createHash6 } from "node:crypto";
44512
- import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync14, readFileSync as readFileSync26, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync14 } from "node:fs";
44513
- import { basename as basename9, dirname as dirname22, join as join32, resolve as resolve7 } from "node:path";
44579
+ import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync14, readFileSync as readFileSync26, rmSync as rmSync14, statSync as statSync14 } from "node:fs";
44580
+ import { basename as basename10, join as join33, resolve as resolve7 } from "node:path";
44514
44581
  function runsDirectory() {
44515
- return join32(runtimeDirectory(), "runs");
44516
- }
44517
- function realAsFarAsItExists(path) {
44518
- const missing = [];
44519
- let at = path;
44520
- for (;; ) {
44521
- try {
44522
- return join32(realpathSync6.native(at), ...missing);
44523
- } catch {
44524
- const parent = dirname22(at);
44525
- if (parent === at) {
44526
- return path;
44527
- }
44528
- missing.unshift(basename9(at));
44529
- at = parent;
44530
- }
44531
- }
44582
+ return join33(runtimeDirectory(), "runs");
44532
44583
  }
44533
44584
  function projectKey(projectPath) {
44534
44585
  const real = realAsFarAsItExists(resolve7(projectPath)).replace(/[\\/]+$/, "");
@@ -44536,10 +44587,10 @@ function projectKey(projectPath) {
44536
44587
  return createHash6("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
44537
44588
  }
44538
44589
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
44539
- return join32(directory, "runs", `note-${projectKey(projectPath)}.json`);
44590
+ return join33(directory, "runs", `note-${projectKey(projectPath)}.json`);
44540
44591
  }
44541
44592
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
44542
- return join32(directory, "runs", `latest-${projectKey(servedBy)}.json`);
44593
+ return join33(directory, "runs", `latest-${projectKey(servedBy)}.json`);
44543
44594
  }
44544
44595
  function writeLatestRun(servedBy, latest) {
44545
44596
  try {
@@ -44564,20 +44615,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
44564
44615
  }
44565
44616
  function openTranscript(startedAt) {
44566
44617
  mkdirSync16(runsDirectory(), { recursive: true });
44567
- const path = join32(runsDirectory(), `run-${startedAt}.log`);
44618
+ const path = join33(runsDirectory(), `run-${startedAt}.log`);
44568
44619
  return { path, fd: openSync3(path, "a") };
44569
44620
  }
44570
44621
  function profilePath(startedAt) {
44571
- return join32(runsDirectory(), `run-${startedAt}.profile.json`);
44622
+ return join33(runsDirectory(), `run-${startedAt}.profile.json`);
44572
44623
  }
44573
44624
  function profileMarkPath(startedAt) {
44574
- return join32(runsDirectory(), `run-${startedAt}.profile-mark.json`);
44625
+ return join33(runsDirectory(), `run-${startedAt}.profile-mark.json`);
44575
44626
  }
44576
44627
  function noteIsAbout(note, playing) {
44577
44628
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
44578
44629
  }
44579
44630
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
44580
- return join32(directory, "runs", `editor-${projectKey(projectPath)}.json`);
44631
+ return join33(directory, "runs", `editor-${projectKey(projectPath)}.json`);
44581
44632
  }
44582
44633
  function writeEditorRunNote(note) {
44583
44634
  mkdirSync16(runsDirectory(), { recursive: true });
@@ -44630,13 +44681,13 @@ function everyRunRecord() {
44630
44681
  for (const directory of runtimeDirectories()) {
44631
44682
  let entries;
44632
44683
  try {
44633
- entries = readdirSync14(join32(directory, "runs"));
44684
+ entries = readdirSync14(join33(directory, "runs"));
44634
44685
  } catch {
44635
44686
  continue;
44636
44687
  }
44637
44688
  for (const entry of entries) {
44638
44689
  if (entry.startsWith("note-") && entry.endsWith(".json")) {
44639
- const record = recordAt(join32(directory, "runs", entry));
44690
+ const record = recordAt(join33(directory, "runs", entry));
44640
44691
  if (record !== null) {
44641
44692
  found.push(record);
44642
44693
  }
@@ -44811,7 +44862,7 @@ function whyNotTheRun(record, running, needed) {
44811
44862
  if (after > SAME_RUN_WINDOW_MS) {
44812
44863
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
44813
44864
  }
44814
- const engine = basename9(record.command);
44865
+ const engine = basename10(record.command);
44815
44866
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
44816
44867
  const wanted = process.platform === "win32" ? engine.toLowerCase() : engine;
44817
44868
  if (!said.includes(wanted)) {
@@ -44874,7 +44925,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
44874
44925
  if (!transcript && !entry.endsWith(".partial")) {
44875
44926
  continue;
44876
44927
  }
44877
- const path = join32(directory, entry);
44928
+ const path = join33(directory, entry);
44878
44929
  if (kept.has(path) || ageMs(path, now) < keepMs) {
44879
44930
  continue;
44880
44931
  }
@@ -45123,15 +45174,15 @@ var init_server_instructions = __esm(() => {
45123
45174
 
45124
45175
  // src/test-selection.ts
45125
45176
  import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
45126
- import { basename as basename10, join as join33 } from "node:path";
45177
+ import { basename as basename11, join as join34 } from "node:path";
45127
45178
  function casesIn(projectPath, suite) {
45128
- const text = readFileSync28(join33(projectPath, suite.slice("res://".length)), "utf8");
45179
+ const text = readFileSync28(join34(projectPath, suite.slice("res://".length)), "utf8");
45129
45180
  return [...text.matchAll(/^func\s+(test_\w*)\s*\(/gm)].map((match) => match[1] ?? "");
45130
45181
  }
45131
45182
  function suitesUnder(projectPath, asked) {
45132
45183
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
45133
45184
  return [
45134
- ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync26(join33(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
45185
+ ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync26(join34(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
45135
45186
  ];
45136
45187
  }
45137
45188
  function selectTests(projectPath, asked, given) {
@@ -45143,7 +45194,7 @@ function selectTests(projectPath, asked, given) {
45143
45194
  return { ok: false, reason: "tests is an empty list: name a case, or leave tests out to run every one." };
45144
45195
  }
45145
45196
  const declared = suitesUnder(projectPath, asked).flatMap((suite) => casesIn(projectPath, suite).map((test) => ({ suite, test })));
45146
- const nameOf = (suite) => basename10(suite, ".gd");
45197
+ const nameOf = (suite) => basename11(suite, ".gd");
45147
45198
  const selected = [];
45148
45199
  for (const [index, entry] of entries.entries()) {
45149
45200
  if (typeof entry !== "string" || entry.trim() === "") {
@@ -45203,7 +45254,7 @@ import {
45203
45254
  statSync as statSync15,
45204
45255
  writeFileSync as writeFileSync18
45205
45256
  } from "node:fs";
45206
- import { basename as basename11, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join34, normalize as normalize2, relative as relative6 } from "node:path";
45257
+ import { basename as basename12, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join35, normalize as normalize2, relative as relative6 } from "node:path";
45207
45258
  import { setTimeout as delay5 } from "node:timers/promises";
45208
45259
  import { fileURLToPath as fileURLToPath5 } from "node:url";
45209
45260
  function editorRestartTimeoutMs() {
@@ -45239,12 +45290,12 @@ function writtenFileRefusal(path, rule) {
45239
45290
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
45240
45291
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
45241
45292
  }
45242
- const directory = realPathOr(dirname23(path));
45293
+ const directory = realAsFarAsItExists(dirname23(path));
45243
45294
  if (!existsSync27(directory)) {
45244
45295
  return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
45245
45296
  }
45246
- const where = join34(directory, basename11(path));
45247
- const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
45297
+ const where = join35(directory, basename12(path));
45298
+ const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realAsFarAsItExists(project), where));
45248
45299
  if (inside !== undefined) {
45249
45300
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
45250
45301
  }
@@ -45334,7 +45385,7 @@ function keptGameRequest(tool, op, args) {
45334
45385
  }
45335
45386
  }
45336
45387
  function projectSpelling(projectPath, absolutePath) {
45337
- return `res://${relative6(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
45388
+ return `res://${relative6(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
45338
45389
  }
45339
45390
  function whichGame(args) {
45340
45391
  return { projectPath: args["projectPath"], pid: args["pid"] };
@@ -45709,19 +45760,37 @@ function describeValue(value) {
45709
45760
  return VALUE_NAMES[typeof value] ?? typeof value;
45710
45761
  }
45711
45762
  function wrongTypes(spec, args) {
45712
- const complaints = [];
45713
- for (const [name, value] of Object.entries(args)) {
45714
- if (value === undefined || value === null) {
45715
- continue;
45763
+ return Object.entries(args).flatMap(([name, value]) => misshapen(spec.parameters[name], value, name));
45764
+ }
45765
+ function misshapen(schema, value, path) {
45766
+ if (!isPlainObject3(schema) || value === undefined || value === null) {
45767
+ return [];
45768
+ }
45769
+ const declared = schema["type"];
45770
+ const wanted = typeof declared === "string" ? [declared] : Array.isArray(declared) ? declared.filter((kind) => typeof kind === "string") : [];
45771
+ if (wanted.length > 0 && !wanted.some((kind) => isOfType(value, kind))) {
45772
+ return [`${path} as ${wanted.join(" or ")}, not ${describeValue(value)}`];
45773
+ }
45774
+ if (Array.isArray(value)) {
45775
+ return value.flatMap((element, index) => misshapen(schema["items"], element, `${path}[${index}]`));
45776
+ }
45777
+ const properties = schema["properties"];
45778
+ if (!isPlainObject3(properties) || !isPlainObject3(value)) {
45779
+ return [];
45780
+ }
45781
+ return Object.entries(value).flatMap(([field, held]) => {
45782
+ if (Object.hasOwn(properties, field)) {
45783
+ return misshapen(properties[field], held, `${path}.${field}`);
45716
45784
  }
45717
- const declared = spec.parameters[name]?.["type"];
45718
- const wanted = typeof declared === "string" ? [declared] : Array.isArray(declared) ? declared.filter((kind) => typeof kind === "string") : [];
45719
- if (wanted.length === 0 || wanted.some((kind) => isOfType(value, kind))) {
45720
- continue;
45785
+ if (schema["additionalProperties"] !== false) {
45786
+ return [];
45721
45787
  }
45722
- complaints.push(`${name} as ${wanted.join(" or ")}, not ${describeValue(value)}`);
45723
- }
45724
- return complaints;
45788
+ const fields = Object.keys(properties);
45789
+ const close = nearest(field, fields);
45790
+ return [
45791
+ `${path} with only the fields ${fields.join(", ")}, and ${field} is none of them${close.length === 0 ? "" : ` (the nearest is ${close[0]})`}`
45792
+ ];
45793
+ });
45725
45794
  }
45726
45795
  function unlistedValues(schema, value, path) {
45727
45796
  if (typeof schema !== "object" || schema === null) {
@@ -45775,10 +45844,14 @@ function importedFirstAnswer(importedFirst) {
45775
45844
  if (importedFirst === undefined) {
45776
45845
  return {};
45777
45846
  }
45847
+ const imported = withAddonPathsCounted(importedFirst.imported);
45848
+ const uids = withAddonPathsCounted(importedFirst.uidsWritten);
45778
45849
  return {
45779
- ...importedFirst.imported.length === 0 ? {} : { importedFirst: importedFirst.imported },
45850
+ ...imported.own.length === 0 ? {} : { importedFirst: imported.own },
45851
+ ...Object.keys(imported.inAddons).length === 0 ? {} : { importedInAddons: imported.inAddons },
45780
45852
  ...importedFirst.notImported.length === 0 ? {} : { notImportedFirst: importedFirst.notImported },
45781
- ...importedFirst.uidsWritten.length === 0 ? {} : { uidsWrittenFirst: importedFirst.uidsWritten },
45853
+ ...uids.own.length === 0 ? {} : { uidsWrittenFirst: uids.own },
45854
+ ...Object.keys(uids.inAddons).length === 0 ? {} : { uidsWrittenInAddons: uids.inAddons },
45782
45855
  importTookMs: importedFirst.tookMs,
45783
45856
  ...importedFirst.note === undefined ? {} : { importNote: importedFirst.note }
45784
45857
  };
@@ -45811,7 +45884,7 @@ function notRunNote(notRun, failFast) {
45811
45884
  function exportPresetRefusal(projectPath, preset) {
45812
45885
  let text;
45813
45886
  try {
45814
- text = readFileSync29(join34(projectPath, "export_presets.cfg"), "utf8");
45887
+ text = readFileSync29(join35(projectPath, "export_presets.cfg"), "utf8");
45815
45888
  } catch {
45816
45889
  return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
45817
45890
  }
@@ -45877,7 +45950,7 @@ function filesIn(folder) {
45877
45950
  try {
45878
45951
  for (const entry of readdirSync15(folder, { withFileTypes: true })) {
45879
45952
  if (entry.isFile()) {
45880
- found.set(entry.name, statSync15(join34(folder, entry.name)).mtimeMs);
45953
+ found.set(entry.name, statSync15(join35(folder, entry.name)).mtimeMs);
45881
45954
  }
45882
45955
  }
45883
45956
  } catch {}
@@ -45909,7 +45982,7 @@ function unsavedInEditorNote(scripts) {
45909
45982
  return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
45910
45983
  }
45911
45984
  function editorAddonMissing(projectPath) {
45912
- if (!existsSync27(join34(projectPath, "project.godot"))) {
45985
+ if (!existsSync27(join35(projectPath, "project.godot"))) {
45913
45986
  return null;
45914
45987
  }
45915
45988
  const report = inspectProject(projectPath);
@@ -46174,7 +46247,7 @@ function profileFrameFunctions(projectPath) {
46174
46247
  let projectGodot = null;
46175
46248
  if (projectPath !== null) {
46176
46249
  try {
46177
- projectGodot = readFileSync29(join34(projectPath, "project.godot"), "utf8");
46250
+ projectGodot = readFileSync29(join35(projectPath, "project.godot"), "utf8");
46178
46251
  } catch {}
46179
46252
  }
46180
46253
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -46353,7 +46426,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
46353
46426
  }
46354
46427
  function gdUnitFailsOnScriptErrors(projectPath) {
46355
46428
  try {
46356
- return settingKeys(readFileSync29(join34(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
46429
+ return settingKeys(readFileSync29(join35(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
46357
46430
  } catch {
46358
46431
  return true;
46359
46432
  }
@@ -46368,7 +46441,7 @@ function uncaughtNote(uncaught) {
46368
46441
  }
46369
46442
  function elsewhereIn(projectPath, asked) {
46370
46443
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
46371
- if (wanted === "" || existsSync27(join34(projectPath, wanted))) {
46444
+ if (wanted === "" || existsSync27(join35(projectPath, wanted))) {
46372
46445
  return "";
46373
46446
  }
46374
46447
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -46471,7 +46544,7 @@ function isDirectory(path) {
46471
46544
  }
46472
46545
  }
46473
46546
  function scriptsUnder(directory, prefix) {
46474
- if (existsSync27(join34(directory, ".gdignore"))) {
46547
+ if (existsSync27(join35(directory, ".gdignore"))) {
46475
46548
  return [];
46476
46549
  }
46477
46550
  const found = [];
@@ -46481,7 +46554,7 @@ function scriptsUnder(directory, prefix) {
46481
46554
  }
46482
46555
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
46483
46556
  if (entry.isDirectory()) {
46484
- found.push(...scriptsUnder(join34(directory, entry.name), path));
46557
+ found.push(...scriptsUnder(join35(directory, entry.name), path));
46485
46558
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
46486
46559
  found.push(path);
46487
46560
  }
@@ -46511,7 +46584,7 @@ function needsItsOwnProcess(asked) {
46511
46584
  class GodotServer {
46512
46585
  stdio = null;
46513
46586
  locator = new GodotLocator;
46514
- operationsScript = join34(__dirname2, "godot", "operations", "godot_operations.gd");
46587
+ operationsScript = join35(__dirname2, "godot", "operations", "godot_operations.gd");
46515
46588
  godotBridge;
46516
46589
  tools = buildToolDefinitions();
46517
46590
  activeProcess = null;
@@ -46773,7 +46846,7 @@ class GodotServer {
46773
46846
  }
46774
46847
  noteTheStart() {
46775
46848
  const cwd = process.cwd();
46776
- const mcpJson = join34(cwd, ".mcp.json");
46849
+ const mcpJson = join35(cwd, ".mcp.json");
46777
46850
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
46778
46851
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
46779
46852
  writeLifecycle("started", {
@@ -46921,7 +46994,7 @@ class GodotServer {
46921
46994
  return checked.response;
46922
46995
  }
46923
46996
  const named = args["projectPath"];
46924
- if (typeof named === "string" && existsSync27(join34(named, "project.godot"))) {
46997
+ if (typeof named === "string" && existsSync27(join35(named, "project.godot"))) {
46925
46998
  this.lastProjectPath = named;
46926
46999
  }
46927
47000
  this.updates.refresh();
@@ -46957,7 +47030,7 @@ class GodotServer {
46957
47030
  let notSaved;
46958
47031
  try {
46959
47032
  savedAt = answer.content.map((item, index) => {
46960
- const file = join34(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
47033
+ const file = join35(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46961
47034
  writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
46962
47035
  return file;
46963
47036
  });
@@ -47236,6 +47309,14 @@ class GodotServer {
47236
47309
  if (headless === "modify_script") {
47237
47310
  return await this.handleModifyScript(answerable);
47238
47311
  }
47312
+ if (tool === "editor_classes") {
47313
+ const project = this.project(answerable);
47314
+ if (!project.ok) {
47315
+ return project.response;
47316
+ }
47317
+ const projectClasses = Object.fromEntries(declaredClasses(project.value.path));
47318
+ return await this.headless(headless, { ...answerable, projectClasses });
47319
+ }
47239
47320
  if (headless !== "refresh_class_cache") {
47240
47321
  return await this.headless(headless, answerable);
47241
47322
  }
@@ -47491,12 +47572,23 @@ class GodotServer {
47491
47572
  if (!project.ok) {
47492
47573
  return project.response;
47493
47574
  }
47575
+ if (op === "list") {
47576
+ const session = this.dapHoldingBreakpointsOf(project.value.path);
47577
+ const editor = this.godotBridge.isConnected();
47578
+ return this.jsonTextResponse({
47579
+ held: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsHeld()),
47580
+ ...editor ? {
47581
+ setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor()),
47582
+ ...this.breakpointsAtRisk()
47583
+ } : {}
47584
+ });
47585
+ }
47494
47586
  const located = resolveWithinProject(project.value.path, readString(args, "scriptPath") ?? "");
47495
47587
  if (!located.ok) {
47496
47588
  return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
47497
47589
  }
47498
47590
  const session = this.dapHoldingBreakpointsOf(project.value.path);
47499
- const scriptPath = realPathOr(located.absolutePath);
47591
+ const scriptPath = realAsFarAsItExists(located.absolutePath);
47500
47592
  const line = readPositiveNumber(args, "line");
47501
47593
  if (line === undefined) {
47502
47594
  return this.createErrorResponse("line is required: the one-based line to stop on.");
@@ -47524,10 +47616,13 @@ class GodotServer {
47524
47616
  } else {
47525
47617
  if (op === "set") {
47526
47618
  session.holdBreakpoints([{ scriptPath, lines: [line] }]);
47619
+ answer = { set: true };
47527
47620
  } else {
47528
- session.forgetBreakpoint(scriptPath, line);
47621
+ answer = session.forgetBreakpoint(scriptPath, line) ? { removed: true } : {
47622
+ removed: false,
47623
+ why: `No breakpoint was held on line ${line} of res://${located.relativePath}, so there was nothing to remove; held lists the ones that are.`
47624
+ };
47529
47625
  }
47530
- answer = { [op === "set" ? "set" : "removed"]: true };
47531
47626
  }
47532
47627
  const held = session.breakpointsHeld();
47533
47628
  writeBreakpointNote(project.value.path, held);
@@ -47549,7 +47644,7 @@ class GodotServer {
47549
47644
  } : {
47550
47645
  runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
47551
47646
  },
47552
- note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the set under held is kept here: every game this server starts is handed it as it starts, an editor that connects is sent it before its first play, and it is kept in the project for the server after a reconnect.",
47647
+ note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the breakpoints under held are kept here: every game this server starts is handed them as it starts, an editor that connects is sent them before its first play, and they are kept in the project for the server after a reconnect.",
47553
47648
  ...editor ? this.breakpointsAtRisk() : {}
47554
47649
  });
47555
47650
  }
@@ -47592,7 +47687,7 @@ class GodotServer {
47592
47687
  if (path === undefined) {
47593
47688
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
47594
47689
  }
47595
- const file = join34(path, "project.godot");
47690
+ const file = join35(path, "project.godot");
47596
47691
  if (!existsSync27(file)) {
47597
47692
  return {
47598
47693
  ok: false,
@@ -47603,34 +47698,8 @@ class GodotServer {
47603
47698
  }
47604
47699
  return { ok: true, value: { path, file } };
47605
47700
  }
47606
- gameArguments(args) {
47607
- if (args["args"] === undefined) {
47608
- return { ok: true, value: [] };
47609
- }
47610
- const given = readStringArray(args, "args");
47611
- if (given === undefined) {
47612
- return {
47613
- ok: false,
47614
- response: this.createErrorResponse("args must be a list of strings.", [
47615
- `Pass the game's own flags, such as ["--level=2"]`
47616
- ])
47617
- };
47618
- }
47619
- return { ok: true, value: given };
47620
- }
47621
47701
  engineArguments(args) {
47622
- if (args["engineArgs"] === undefined) {
47623
- return { ok: true, value: [] };
47624
- }
47625
- const given = readStringArray(args, "engineArgs");
47626
- if (given === undefined) {
47627
- return {
47628
- ok: false,
47629
- response: this.createErrorResponse("engineArgs must be a list of strings.", [
47630
- 'Pass engine flags, such as ["--verbose"] or ["--fixed-fps", "60"]'
47631
- ])
47632
- };
47633
- }
47702
+ const given = readStringArray(args, "engineArgs") ?? [];
47634
47703
  const checked = engineFlags(given);
47635
47704
  if (!checked.ok) {
47636
47705
  return { ok: false, response: this.createErrorResponse(checked.reason) };
@@ -48032,6 +48101,16 @@ class GodotServer {
48032
48101
  ...params
48033
48102
  } = contained.value;
48034
48103
  const written = await this.operation("set_import_options", params, project.value.path);
48104
+ const changed = written.ok ? written.payload["updated_options"] : null;
48105
+ if (written.ok && Array.isArray(changed) && changed.length === 0) {
48106
+ return this.answer({
48107
+ ...written,
48108
+ payload: {
48109
+ ...written.payload,
48110
+ note: "Not reimported: every option given already held that value, so the import file was not written."
48111
+ }
48112
+ });
48113
+ }
48035
48114
  if (!written.ok || args["reimport"] === false) {
48036
48115
  return this.answer(written.ok ? {
48037
48116
  ...written,
@@ -48314,7 +48393,7 @@ class GodotServer {
48314
48393
  const name = application["config/name"];
48315
48394
  const mainScene = application["run/main_scene"];
48316
48395
  const info = {
48317
- name: typeof name === "string" && name !== "" ? name : basename11(project.value.path),
48396
+ name: typeof name === "string" && name !== "" ? name : basename12(project.value.path),
48318
48397
  path: project.value.path,
48319
48398
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
48320
48399
  godotVersion: await this.godotVersion(engine.value),
@@ -48432,7 +48511,7 @@ class GodotServer {
48432
48511
  const exportArgs = [
48433
48512
  "--headless",
48434
48513
  "--log-file",
48435
- join34(exportLogs, "engine.log"),
48514
+ join35(exportLogs, "engine.log"),
48436
48515
  "--path",
48437
48516
  project.value.path,
48438
48517
  debug ? "--export-debug" : "--export-release",
@@ -48469,7 +48548,7 @@ class GodotServer {
48469
48548
  if (ending.exitSignal === null && ending.exitCode === null) {
48470
48549
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
48471
48550
  }
48472
- const beside = writtenBeside(listedBefore, filesIn(folder), basename11(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
48551
+ const beside = writtenBeside(listedBefore, filesIn(folder), basename12(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
48473
48552
  return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
48474
48553
  }
48475
48554
  reportTestProgress(log) {
@@ -48523,10 +48602,10 @@ class GodotServer {
48523
48602
  if (movie !== null) {
48524
48603
  return this.createErrorResponse(movie);
48525
48604
  }
48526
- const missing = paths.value.filter((path) => !existsSync27(join34(project.value.path, path.slice("res://".length))));
48605
+ const missing = paths.value.filter((path) => !existsSync27(join35(project.value.path, path.slice("res://".length))));
48527
48606
  const asked = paths.value.filter((path) => !missing.includes(path));
48528
48607
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
48529
- const noRunner = !existsSync27(join34(project.value.path, runner));
48608
+ const noRunner = !existsSync27(join35(project.value.path, runner));
48530
48609
  if (asked.length === 0) {
48531
48610
  const verdict = `nothing at ${missing.join(" and ")}`;
48532
48611
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -48646,14 +48725,14 @@ class GodotServer {
48646
48725
  await run.settled;
48647
48726
  const tookMs = Date.now() - began;
48648
48727
  const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
48649
- const reportsDir = join34(project.value.path, ".godot", ...ours.split("/"));
48728
+ const reportsDir = join35(project.value.path, ".godot", ...ours.split("/"));
48650
48729
  let report = null;
48651
48730
  let reportProblem = null;
48652
48731
  try {
48653
48732
  const written = existsSync27(reportsDir) && notCompiled === null ? readdirSync15(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
48654
48733
  const newest = written.at(-1);
48655
48734
  if (newest !== undefined) {
48656
- report = parseJUnit(readFileSync29(join34(reportsDir, newest, "results.xml"), "utf8"));
48735
+ report = parseJUnit(readFileSync29(join35(reportsDir, newest, "results.xml"), "utf8"));
48657
48736
  }
48658
48737
  } catch (error) {
48659
48738
  reportProblem = errorMessage(error);
@@ -48781,7 +48860,7 @@ class GodotServer {
48781
48860
  const listedAsUncaught = new Set(uncaughtFolded.map((entry) => entry.index));
48782
48861
  const notApplied = ignoredYetRan(report, ignored);
48783
48862
  const testsNotRun = [
48784
- ...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename11(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename11(one.suite, ".gd")}:${one.test}`))
48863
+ ...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename12(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename12(one.suite, ".gd")}:${one.test}`))
48785
48864
  ];
48786
48865
  return this.jsonTextResponse({
48787
48866
  passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && missing.length === 0 && testsNotRun.length === 0,
@@ -49296,7 +49375,7 @@ class GodotServer {
49296
49375
  ...Array.isArray(stack) ? {
49297
49376
  stack: stack.map((frame) => ({
49298
49377
  ...frame,
49299
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join34(projectPath, frame["script"].slice("res://".length)) } : {}
49378
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join35(projectPath, frame["script"].slice("res://".length)) } : {}
49300
49379
  }))
49301
49380
  } : {}
49302
49381
  });
@@ -49359,7 +49438,7 @@ class GodotServer {
49359
49438
  const launchedIsUp = !status.connected && launchedWasUp;
49360
49439
  const arriving = status.connected ? [] : arrivingFound;
49361
49440
  const isPortConflict = this.bridgeStartupError?.includes("EADDRINUSE") ?? false;
49362
- const greeted = hasSaidWhoItIs(status);
49441
+ const greeted = status.connected;
49363
49442
  const staleNote = this.godotBridge.anotherRelease();
49364
49443
  const unfinished = this.restartLeftUnfinished(status.connected);
49365
49444
  const cannotConnect = status.connected || this.ownProject === null ? null : editorAddonMissing(this.ownProject);
@@ -49367,7 +49446,7 @@ class GodotServer {
49367
49446
  ...status,
49368
49447
  serverVersion: SERVER_VERSION,
49369
49448
  addonIsStale: greeted ? staleNote !== undefined : undefined,
49370
- greeting: status.connected && !greeted ? "The editor has connected and has not yet said who it is: its project, pid, addon version and ports arrive with its greeting a moment after the socket opens, later while it imports. Ask again for them." : undefined,
49449
+ greeting: status.identifying === true ? "An editor has opened the bridge and has not yet said who it is: its project, pid, addon version and ports arrive with its greeting a moment after the socket opens, later while it imports, and one nobody's server opened is served once its command line has been read. Ask again for them." : undefined,
49371
49450
  bridgeAvailable: this.bridgeStartupError === null && status.port !== null,
49372
49451
  mayYetConnect: status.connected ? undefined : cannotConnect !== null ? false : arriving.length > 0 ? true : unfinished !== null ? unfinished.underway : this.closedHere !== null ? false : anEditorIsStillComing(status.listeningSince, launchedIsUp),
49373
49452
  editorsNotYetConnected: arriving.length > 0 ? arriving : undefined,
@@ -49575,21 +49654,21 @@ class GodotServer {
49575
49654
  return null;
49576
49655
  }
49577
49656
  try {
49578
- return settingKeys(readFileSync29(join34(projectPath, "project.godot"), "utf8"));
49657
+ return settingKeys(readFileSync29(join35(projectPath, "project.godot"), "utf8"));
49579
49658
  } catch {
49580
49659
  return null;
49581
49660
  }
49582
49661
  }
49583
49662
  async handleRestartEditor(args) {
49584
49663
  const before = this.godotBridge.getStatus();
49585
- if (!before.connected) {
49664
+ if (!before.connected && before.identifying !== true) {
49586
49665
  return this.createErrorResponse("No editor is connected, so there is nothing to restart.", [
49587
49666
  "editor_launch opens one on a project",
49588
49667
  "editor_status says whether the bridge is up and what has reached it"
49589
49668
  ]);
49590
49669
  }
49591
- if (!hasSaidWhoItIs(before)) {
49592
- return this.createErrorResponse(`The editor has connected and has not said who it is within ${greetingWaitMs() / 1000}s, so whether this server or the editor itself restarts it cannot be told.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the restart again"]);
49670
+ if (!before.connected) {
49671
+ return this.createErrorResponse(`An editor has opened the bridge and has not said who it is within ${greetingWaitMs() / 1000}s, so whether this server or the editor itself restarts it cannot be told.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the restart again"]);
49593
49672
  }
49594
49673
  const mine = before.openedByAServer === true && before.projectPath !== undefined;
49595
49674
  if (!mine && args["hidden"] === true) {
@@ -49631,7 +49710,7 @@ class GodotServer {
49631
49710
  }
49632
49711
  async handleCloseEditor() {
49633
49712
  const before = this.godotBridge.getStatus();
49634
- if (!before.connected) {
49713
+ if (!before.connected && before.identifying !== true) {
49635
49714
  const launched = this.launchedEditor;
49636
49715
  if (launched !== null && await this.launchedEditorIsUp() && !this.godotBridge.isConnected()) {
49637
49716
  return await this.stopTheLaunchedEditor(launched);
@@ -49640,8 +49719,8 @@ class GodotServer {
49640
49719
  "editor_status says whether the bridge is up and what has reached it"
49641
49720
  ]);
49642
49721
  }
49643
- if (!hasSaidWhoItIs(before) || before.editorPid === undefined) {
49644
- return this.createErrorResponse(`The editor has connected and has not said who it is within ${greetingWaitMs() / 1000}s, so there is no process to wait on once it is asked to go.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the close again"]);
49722
+ if (!before.connected || before.editorPid === undefined) {
49723
+ return this.createErrorResponse(`An editor has opened the bridge and has not said who it is within ${greetingWaitMs() / 1000}s, so there is no process to wait on once it is asked to go.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the close again"]);
49645
49724
  }
49646
49725
  const pid = before.editorPid;
49647
49726
  const settingsBefore = this.settingKeysOf(before.projectPath);
@@ -49697,7 +49776,7 @@ class GodotServer {
49697
49776
  settleTheLaunchReading() {
49698
49777
  const watched = this.launchedFrom;
49699
49778
  const status = this.godotBridge.getStatus();
49700
- if (watched === null || !hasSaidWhoItIs(status)) {
49779
+ if (watched === null || !status.connected) {
49701
49780
  return;
49702
49781
  }
49703
49782
  this.launchedFrom = null;
@@ -49906,10 +49985,7 @@ class GodotServer {
49906
49985
  if (exported !== null && readBoolean(args, "profile") === true) {
49907
49986
  return this.createErrorResponse("A profile is read through the debugger, and an exported build is started without one, since a release build quits when given --remote-debug. Profile the project without executable.");
49908
49987
  }
49909
- const given = this.gameArguments(args);
49910
- if (!given.ok) {
49911
- return given.response;
49912
- }
49988
+ const given = readStringArray(args, "args") ?? [];
49913
49989
  const flags = this.engineArguments(args);
49914
49990
  if (!flags.ok) {
49915
49991
  return flags.response;
@@ -49941,7 +50017,7 @@ class GodotServer {
49941
50017
  const scanned = await this.afterTheImport(project.value.path, waited, imported);
49942
50018
  const sceneArgument = sceneToRun?.ok ? sceneToRun.relativePath : null;
49943
50019
  if (op === "check") {
49944
- return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value, scanned, asked.value, flags.value);
50020
+ return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given, scanned, asked.value, flags.value);
49945
50021
  }
49946
50022
  await this.pickUpWhatTheEditorIsPlaying();
49947
50023
  let ended = null;
@@ -49970,14 +50046,14 @@ class GodotServer {
49970
50046
  const silent = resolveSilent(args["silent"]) && !headless;
49971
50047
  const silentOnlyHere = silent && !editorPlaysHeadless(project.value.file);
49972
50048
  const profiled = readBoolean(args, "profile") === true;
49973
- if (exported === null && this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silentOnlyHere) {
50049
+ if (exported === null && this.godotBridge.isConnected() && editorWouldPlay && given.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silentOnlyHere) {
49974
50050
  return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended, profiled);
49975
50051
  }
49976
50052
  const cmdArgs = runArguments({
49977
50053
  projectPath: exported === null ? project.value.path : null,
49978
50054
  headless,
49979
50055
  scene: sceneArgument,
49980
- userArgs: given.value,
50056
+ userArgs: given,
49981
50057
  engineArgs: flags.value,
49982
50058
  silent
49983
50059
  });
@@ -49991,7 +50067,7 @@ class GodotServer {
49991
50067
  started.announcedBefore = alreadyPlaying;
49992
50068
  started.bootScene = runtimeWaitMs.scene;
49993
50069
  this.activeProcess = started;
49994
- const spawnedInstead = exported !== null ? " An exported build is started without a debugger, since a release build quits when given one, so breakpoints and debug_state do not reach it." : this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silentOnlyHere ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here, silent as every run is unless asked otherwise, and silent: false plays it through the editor, with sound.${OWN_DEBUGGER}` : "";
50070
+ const spawnedInstead = exported !== null ? " An exported build is started without a debugger, since a release build quits when given one, so breakpoints and debug_state do not reach it." : this.godotBridge.isConnected() && editorWouldPlay && (given.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silentOnlyHere ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here, silent as every run is unless asked otherwise, and silent: false plays it through the editor, with sound.${OWN_DEBUGGER}` : "";
49995
50071
  const endedForThis = endedToStartThis(ended);
49996
50072
  const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
49997
50073
  const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
@@ -50004,7 +50080,7 @@ class GodotServer {
50004
50080
  executable: exported?.ok === true ? exported.relativePath : undefined,
50005
50081
  scene: exported !== null ? undefined : sceneArgument === null ? mainSceneOf(project.value.file) : `res://${sceneArgument}`,
50006
50082
  arguments: cmdArgs,
50007
- argsNote: engineFlagsNote(given.value),
50083
+ argsNote: engineFlagsNote(given),
50008
50084
  transcript: started.transcript,
50009
50085
  breakpoints: started.keeper === undefined || handed.length === 0 ? undefined : this.breakpointsAsProjectSpells(project.value.path, handed),
50010
50086
  profile: profileOnStart(profiled, "gdharness", runtimeAddonRegistered(project.value.path)),
@@ -50014,13 +50090,13 @@ class GodotServer {
50014
50090
  endedPreviousRun: endedPreviousRun(ended),
50015
50091
  previousRunLeft: previousRunLeft(ended),
50016
50092
  ...previousRunHow(ended),
50017
- runtime: exported !== null ? undefined : await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
50093
+ runtime: exported !== null ? undefined : await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.length > 0),
50018
50094
  message: `Use editor_output for what it prints and editor_run stop to end it.${started.keeper !== undefined && handed.length > 0 ? " It stops at the lines under breakpoints, and debug_state stack reads where it is held." : ""}${spawnedInstead}${endedForThis}${alsoRunning}`
50019
50095
  });
50020
50096
  }
50021
50097
  async runtimeUp(projectPath, before, budget, withArgs) {
50022
50098
  const budgetMs = budget.ms;
50023
- if (!existsSync27(join34(projectPath, RUNTIME_AUTOLOAD.path))) {
50099
+ if (!existsSync27(join35(projectPath, RUNTIME_AUTOLOAD.path))) {
50024
50100
  return runtimeVerdict(null, {
50025
50101
  addon: false,
50026
50102
  budgetMs,
@@ -50997,7 +51073,7 @@ class GodotServer {
50997
51073
  };
50998
51074
  const going = await this.runStillGoing(run);
50999
51075
  const projectPath = run.projectPath;
51000
- const projectGone = projectPath !== null && !existsSync27(join34(projectPath, "project.godot"));
51076
+ const projectGone = projectPath !== null && !existsSync27(join35(projectPath, "project.godot"));
51001
51077
  if (projectGone && callersName !== undefined) {
51002
51078
  return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
51003
51079
  }
@@ -51222,10 +51298,10 @@ class GodotServer {
51222
51298
  }
51223
51299
  async handleEditorConsole(args) {
51224
51300
  const status = this.godotBridge.getStatus();
51225
- const greeted = hasSaidWhoItIs(status);
51301
+ const greeted = status.connected;
51226
51302
  const launched = greeted ? null : this.launchedEditor?.pid ?? null;
51227
- if (!greeted && launched === null && status.connected) {
51228
- return this.createErrorResponse("The editor has connected and has not yet said who it is, so whose console to read cannot be told yet.", ["Ask again in a moment; editor_status shows greeting until it has"]);
51303
+ if (launched === null && status.identifying === true) {
51304
+ return this.createErrorResponse("An editor has opened the bridge and has not yet said who it is, so whose console to read cannot be told yet.", ["Ask again in a moment; editor_status shows greeting until it has"]);
51229
51305
  }
51230
51306
  const projectPath = status.projectPath ?? this.launchedEditor?.projectPath ?? this.ownProject ?? undefined;
51231
51307
  const elsewhere = this.anotherProjectNamed(args, projectPath);
@@ -51344,7 +51420,7 @@ class GodotServer {
51344
51420
  notes.push(endedWithoutACode({
51345
51421
  throughEditor: run.throughEditor,
51346
51422
  announced: announced !== undefined || run.announcedPid !== undefined,
51347
- couldAnnounce: run.projectPath !== null && existsSync27(join34(run.projectPath, RUNTIME_AUTOLOAD.path)),
51423
+ couldAnnounce: run.projectPath !== null && existsSync27(join35(run.projectPath, RUNTIME_AUTOLOAD.path)),
51348
51424
  endedHere: typeof run.endedHere === "string",
51349
51425
  pickedUp: run.pickedUp === true
51350
51426
  }));
@@ -52434,7 +52510,7 @@ class GodotServer {
52434
52510
  const launched = this.launchedEditor;
52435
52511
  if (launched !== null && await this.launchedEditorIsUp()) {
52436
52512
  const began = Date.now();
52437
- await this.waitForBridge(() => hasSaidWhoItIs(this.godotBridge.getStatus()) || !alive(launched.pid), began + editorRestartTimeoutMs());
52513
+ await this.waitForBridge(() => this.godotBridge.isConnected() || !alive(launched.pid), began + editorRestartTimeoutMs());
52438
52514
  if (this.godotBridge.isConnected()) {
52439
52515
  return null;
52440
52516
  }
@@ -52553,7 +52629,7 @@ class GodotServer {
52553
52629
  async ownRunOnItsWay(projectPath, budgetMs) {
52554
52630
  const run = await this.currentRun();
52555
52631
  const project = projectPath ?? run?.projectPath ?? null;
52556
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync27(join34(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
52632
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync27(join35(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
52557
52633
  return null;
52558
52634
  }
52559
52635
  const already = runtimesAnnounced().running;
@@ -52647,7 +52723,7 @@ class GodotServer {
52647
52723
  return this.createErrorResponse(staleNote);
52648
52724
  }
52649
52725
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
52650
- const screenshotPath = screenshotDir ? join34(screenshotDir, "capture.png") : null;
52726
+ const screenshotPath = screenshotDir ? join35(screenshotDir, "capture.png") : null;
52651
52727
  const refused = (message) => this.createErrorResponse(message);
52652
52728
  const onGame = {
52653
52729
  pid: choice.endpoint.pid,
@@ -52950,12 +53026,12 @@ init_bridge_announce();
52950
53026
  init_errors();
52951
53027
  init_godot_path();
52952
53028
  import { existsSync as existsSync28 } from "node:fs";
52953
- import { join as join35, resolve as resolve8 } from "node:path";
53029
+ import { join as join36, resolve as resolve8 } from "node:path";
52954
53030
 
52955
53031
  // src/harnesses.ts
52956
53032
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
52957
53033
  import { homedir as homedir2 } from "node:os";
52958
- import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
53034
+ import { basename as basename3, dirname as dirname4, join as join5, relative as relative2, resolve as resolve2 } from "node:path";
52959
53035
  import process4 from "node:process";
52960
53036
 
52961
53037
  // node_modules/yaml/dist/index.js
@@ -53129,7 +53205,7 @@ var HARNESSES = [
53129
53205
  shape: "plain",
53130
53206
  home: ".claude",
53131
53207
  marker: ".claude",
53132
- skills: { dir: join4(".claude", "skills"), shared: false }
53208
+ skills: { dir: join5(".claude", "skills"), shared: false }
53133
53209
  },
53134
53210
  {
53135
53211
  id: "copilot-cli",
@@ -53139,7 +53215,7 @@ var HARNESSES = [
53139
53215
  container: "mcpServers",
53140
53216
  shape: "plain",
53141
53217
  home: ".copilot",
53142
- skills: { dir: join4(".github", "skills"), shared: true },
53218
+ skills: { dir: join5(".github", "skills"), shared: true },
53143
53219
  manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
53144
53220
  },
53145
53221
  {
@@ -53165,22 +53241,22 @@ var HARNESSES = [
53165
53241
  id: "cursor",
53166
53242
  name: "Cursor",
53167
53243
  scope: "project",
53168
- file: join4(".cursor", "mcp.json"),
53244
+ file: join5(".cursor", "mcp.json"),
53169
53245
  container: "mcpServers",
53170
53246
  shape: "plain",
53171
53247
  home: ".cursor",
53172
- skills: { dir: join4(".cursor", "skills"), shared: true },
53248
+ skills: { dir: join5(".cursor", "skills"), shared: true },
53173
53249
  manual: "Cursor asks before each tool call by default, and the server can be toggled off."
53174
53250
  },
53175
53251
  {
53176
53252
  id: "vscode",
53177
53253
  name: "VS Code",
53178
53254
  scope: "project",
53179
- file: join4(".vscode", "mcp.json"),
53255
+ file: join5(".vscode", "mcp.json"),
53180
53256
  container: "servers",
53181
53257
  shape: "typed",
53182
53258
  home: ".vscode",
53183
- skills: { dir: join4(".github", "skills"), shared: true },
53259
+ skills: { dir: join5(".github", "skills"), shared: true },
53184
53260
  manual: "VS Code asks you to trust a server the first time it starts one."
53185
53261
  },
53186
53262
  {
@@ -53190,45 +53266,45 @@ var HARNESSES = [
53190
53266
  file: "opencode.json",
53191
53267
  container: "mcp",
53192
53268
  shape: "opencode",
53193
- home: join4(".config", "opencode"),
53269
+ home: join5(".config", "opencode"),
53194
53270
  marker: ".opencode",
53195
- skills: { dir: join4(".opencode", "skills"), shared: true }
53271
+ skills: { dir: join5(".opencode", "skills"), shared: true }
53196
53272
  },
53197
53273
  {
53198
53274
  id: "junie",
53199
53275
  name: "Junie",
53200
53276
  scope: "project",
53201
- file: join4(".junie", "mcp", "mcp.json"),
53277
+ file: join5(".junie", "mcp", "mcp.json"),
53202
53278
  container: "mcpServers",
53203
53279
  shape: "plain",
53204
53280
  home: ".junie",
53205
- skills: { dir: join4(".junie", "skills"), shared: true }
53281
+ skills: { dir: join5(".junie", "skills"), shared: true }
53206
53282
  },
53207
53283
  {
53208
53284
  id: "kiro",
53209
53285
  name: "Kiro",
53210
53286
  scope: "project",
53211
- file: join4(".kiro", "settings", "mcp.json"),
53287
+ file: join5(".kiro", "settings", "mcp.json"),
53212
53288
  container: "mcpServers",
53213
53289
  shape: "plain",
53214
53290
  home: ".kiro",
53215
- skills: { dir: join4(".kiro", "skills"), shared: false }
53291
+ skills: { dir: join5(".kiro", "skills"), shared: false }
53216
53292
  },
53217
53293
  {
53218
53294
  id: "gemini",
53219
53295
  name: "Gemini CLI",
53220
53296
  scope: "project",
53221
- file: join4(".gemini", "settings.json"),
53297
+ file: join5(".gemini", "settings.json"),
53222
53298
  container: "mcpServers",
53223
53299
  shape: "plain",
53224
53300
  home: ".gemini",
53225
- skills: { dir: join4(".gemini", "skills"), shared: true }
53301
+ skills: { dir: join5(".gemini", "skills"), shared: true }
53226
53302
  },
53227
53303
  {
53228
53304
  id: "roo",
53229
53305
  name: "Roo Code",
53230
53306
  scope: "project",
53231
- file: join4(".roo", "mcp.json"),
53307
+ file: join5(".roo", "mcp.json"),
53232
53308
  container: "mcpServers",
53233
53309
  shape: "plain"
53234
53310
  },
@@ -53236,7 +53312,7 @@ var HARNESSES = [
53236
53312
  id: "kilo",
53237
53313
  name: "Kilo Code",
53238
53314
  scope: "project",
53239
- file: join4(".kilocode", "mcp.json"),
53315
+ file: join5(".kilocode", "mcp.json"),
53240
53316
  container: "mcpServers",
53241
53317
  shape: "plain"
53242
53318
  },
@@ -53244,16 +53320,16 @@ var HARNESSES = [
53244
53320
  id: "amazon-q",
53245
53321
  name: "Amazon Q Developer CLI",
53246
53322
  scope: "project",
53247
- file: join4(".amazonq", "mcp.json"),
53323
+ file: join5(".amazonq", "mcp.json"),
53248
53324
  container: "mcpServers",
53249
53325
  shape: "plain",
53250
- home: join4(".aws", "amazonq")
53326
+ home: join5(".aws", "amazonq")
53251
53327
  },
53252
53328
  {
53253
53329
  id: "zed",
53254
53330
  name: "Zed",
53255
53331
  scope: "project",
53256
- file: join4(".zed", "settings.json"),
53332
+ file: join5(".zed", "settings.json"),
53257
53333
  container: "context_servers",
53258
53334
  shape: "plain"
53259
53335
  },
@@ -53261,16 +53337,16 @@ var HARNESSES = [
53261
53337
  id: "amp",
53262
53338
  name: "Amp",
53263
53339
  scope: "project",
53264
- file: join4(".amp", "settings.json"),
53340
+ file: join5(".amp", "settings.json"),
53265
53341
  container: "amp.mcpServers",
53266
53342
  shape: "plain",
53267
- home: join4(".config", "amp")
53343
+ home: join5(".config", "amp")
53268
53344
  },
53269
53345
  {
53270
53346
  id: "warp",
53271
53347
  name: "Warp",
53272
53348
  scope: "project",
53273
- file: join4(".warp", ".mcp.json"),
53349
+ file: join5(".warp", ".mcp.json"),
53274
53350
  container: "mcpServers",
53275
53351
  shape: "plain",
53276
53352
  home: ".warp",
@@ -53280,7 +53356,7 @@ var HARNESSES = [
53280
53356
  id: "trae",
53281
53357
  name: "Trae",
53282
53358
  scope: "project",
53283
- file: join4(".trae", "mcp.json"),
53359
+ file: join5(".trae", "mcp.json"),
53284
53360
  container: "mcpServers",
53285
53361
  shape: "plain",
53286
53362
  home: ".trae"
@@ -53289,7 +53365,7 @@ var HARNESSES = [
53289
53365
  id: "factory",
53290
53366
  name: "Factory Droid",
53291
53367
  scope: "project",
53292
- file: join4(".factory", "mcp.json"),
53368
+ file: join5(".factory", "mcp.json"),
53293
53369
  container: "mcpServers",
53294
53370
  shape: "typed",
53295
53371
  home: ".factory"
@@ -53298,7 +53374,7 @@ var HARNESSES = [
53298
53374
  id: "tabnine",
53299
53375
  name: "Tabnine",
53300
53376
  scope: "project",
53301
- file: join4(".tabnine", "mcp_servers.json"),
53377
+ file: join5(".tabnine", "mcp_servers.json"),
53302
53378
  container: "mcpServers",
53303
53379
  shape: "plain",
53304
53380
  home: ".tabnine"
@@ -53315,7 +53391,7 @@ var HARNESSES = [
53315
53391
  id: "pi",
53316
53392
  name: "pi",
53317
53393
  scope: "project",
53318
- file: join4(".pi", "mcp.json"),
53394
+ file: join5(".pi", "mcp.json"),
53319
53395
  container: "mcpServers",
53320
53396
  shape: "plain",
53321
53397
  home: ".pi"
@@ -53324,7 +53400,7 @@ var HARNESSES = [
53324
53400
  id: "mistral-vibe",
53325
53401
  name: "Mistral Vibe",
53326
53402
  scope: "project",
53327
- file: join4(".vibe", "config.toml"),
53403
+ file: join5(".vibe", "config.toml"),
53328
53404
  container: "mcp_servers",
53329
53405
  shape: "plain",
53330
53406
  toml: {
@@ -53380,7 +53456,7 @@ var HARNESSES = [
53380
53456
  id: "codex",
53381
53457
  name: "Codex CLI",
53382
53458
  scope: "home",
53383
- file: join4(".codex", "config.toml"),
53459
+ file: join5(".codex", "config.toml"),
53384
53460
  container: "mcp_servers",
53385
53461
  shape: "plain",
53386
53462
  manual: "Codex reads its config at startup, so restart it.",
@@ -53399,20 +53475,20 @@ var HARNESSES = [
53399
53475
  id: "cline",
53400
53476
  name: "Cline",
53401
53477
  scope: "home",
53402
- file: join4(".cline", "data", "settings", "cline_mcp_settings.json"),
53478
+ file: join5(".cline", "data", "settings", "cline_mcp_settings.json"),
53403
53479
  container: "mcpServers",
53404
53480
  shape: "plain",
53405
53481
  marker: ".cline",
53406
- skills: { dir: join4(".cline", "skills"), shared: false }
53482
+ skills: { dir: join5(".cline", "skills"), shared: false }
53407
53483
  },
53408
53484
  {
53409
53485
  id: "goose",
53410
53486
  name: "Goose",
53411
53487
  scope: "home",
53412
53488
  file: {
53413
- win32: { appData: join4("Block", "goose", "config", "config.yaml") },
53414
- darwin: join4(".config", "goose", "config.yaml"),
53415
- linux: join4(".config", "goose", "config.yaml")
53489
+ win32: { appData: join5("Block", "goose", "config", "config.yaml") },
53490
+ darwin: join5(".config", "goose", "config.yaml"),
53491
+ linux: join5(".config", "goose", "config.yaml")
53416
53492
  },
53417
53493
  container: "extensions",
53418
53494
  shape: "plain",
@@ -53433,19 +53509,19 @@ var HARNESSES = [
53433
53509
  id: "windsurf",
53434
53510
  name: "Windsurf",
53435
53511
  scope: "home",
53436
- file: join4(".codeium", "windsurf", "mcp_config.json"),
53512
+ file: join5(".codeium", "windsurf", "mcp_config.json"),
53437
53513
  container: "mcpServers",
53438
53514
  shape: "plain",
53439
- skills: { dir: join4(".windsurf", "skills"), shared: true }
53515
+ skills: { dir: join5(".windsurf", "skills"), shared: true }
53440
53516
  },
53441
53517
  {
53442
53518
  id: "hermes",
53443
53519
  name: "Hermes",
53444
53520
  scope: "home",
53445
- file: join4(".hermes", "config.yaml"),
53521
+ file: join5(".hermes", "config.yaml"),
53446
53522
  container: "mcp_servers",
53447
53523
  shape: "plain",
53448
- skills: { dir: join4(".hermes", "skills"), shared: true },
53524
+ skills: { dir: join5(".hermes", "skills"), shared: true },
53449
53525
  yaml: {
53450
53526
  path: ["mcp_servers", SERVER_KEY],
53451
53527
  entry: (launch) => ({
@@ -53460,7 +53536,7 @@ var HARNESSES = [
53460
53536
  id: "openclaw",
53461
53537
  name: "OpenClaw",
53462
53538
  scope: "home",
53463
- file: join4(".openclaw", "openclaw.json"),
53539
+ file: join5(".openclaw", "openclaw.json"),
53464
53540
  container: "mcpServers",
53465
53541
  shape: "plain"
53466
53542
  },
@@ -53469,9 +53545,9 @@ var HARNESSES = [
53469
53545
  name: "Claude Desktop",
53470
53546
  scope: "home",
53471
53547
  file: {
53472
- win32: { appData: join4("Claude", "claude_desktop_config.json") },
53473
- darwin: join4("Library", "Application Support", "Claude", "claude_desktop_config.json"),
53474
- linux: join4(".config", "Claude", "claude_desktop_config.json")
53548
+ win32: { appData: join5("Claude", "claude_desktop_config.json") },
53549
+ darwin: join5("Library", "Application Support", "Claude", "claude_desktop_config.json"),
53550
+ linux: join5(".config", "Claude", "claude_desktop_config.json")
53475
53551
  },
53476
53552
  container: "mcpServers",
53477
53553
  shape: "plain"
@@ -53480,7 +53556,7 @@ var HARNESSES = [
53480
53556
  id: "zeroclaw",
53481
53557
  name: "ZeroClaw",
53482
53558
  scope: "home",
53483
- file: join4(".zeroclaw", "config.toml"),
53559
+ file: join5(".zeroclaw", "config.toml"),
53484
53560
  container: "mcp.servers",
53485
53561
  shape: "plain",
53486
53562
  toml: {
@@ -53501,7 +53577,7 @@ var HARNESSES = [
53501
53577
  id: "deepcode",
53502
53578
  name: "Deep Code",
53503
53579
  scope: "home",
53504
- file: join4(".deepcode", "settings.json"),
53580
+ file: join5(".deepcode", "settings.json"),
53505
53581
  container: "mcpServers",
53506
53582
  shape: "plain"
53507
53583
  },
@@ -53509,7 +53585,7 @@ var HARNESSES = [
53509
53585
  id: "nanobot",
53510
53586
  name: "nanobot",
53511
53587
  scope: "home",
53512
- file: join4(".nanobot", "config.json"),
53588
+ file: join5(".nanobot", "config.json"),
53513
53589
  container: "mcpServers",
53514
53590
  shape: "plain",
53515
53591
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -53518,17 +53594,17 @@ var HARNESSES = [
53518
53594
  id: "autohand",
53519
53595
  name: "Autohand",
53520
53596
  scope: "home",
53521
- file: join4(".autohand", "config.json"),
53597
+ file: join5(".autohand", "config.json"),
53522
53598
  container: "mcpServers",
53523
53599
  shape: "plain",
53524
53600
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
53525
53601
  }
53526
53602
  ];
53527
53603
  function appDataRoot() {
53528
- return process4.env["APPDATA"] ?? join4(homedir2(), "AppData", "Roaming");
53604
+ return process4.env["APPDATA"] ?? join5(homedir2(), "AppData", "Roaming");
53529
53605
  }
53530
53606
  function homePath(path) {
53531
- return typeof path === "string" ? join4(homedir2(), path) : join4(appDataRoot(), path.appData);
53607
+ return typeof path === "string" ? join5(homedir2(), path) : join5(appDataRoot(), path.appData);
53532
53608
  }
53533
53609
  function fileFor(harness, platform = process4.platform) {
53534
53610
  if (typeof harness.file === "string") {
@@ -53539,7 +53615,7 @@ function fileFor(harness, platform = process4.platform) {
53539
53615
  function configPath(harness, projectPath) {
53540
53616
  const file = fileFor(harness);
53541
53617
  if (harness.scope === "project") {
53542
- return join4(projectPath, file);
53618
+ return join5(projectPath, file);
53543
53619
  }
53544
53620
  return homePath(file);
53545
53621
  }
@@ -53598,11 +53674,11 @@ function present(harness, projectPath) {
53598
53674
  return true;
53599
53675
  }
53600
53676
  const root = harness.scope === "project" ? projectPath : homedir2();
53601
- const own = dirname3(path);
53677
+ const own = dirname4(path);
53602
53678
  if (own !== root && existsSync4(own)) {
53603
53679
  return true;
53604
53680
  }
53605
- return harness.marker !== undefined && existsSync4(join4(root, harness.marker));
53681
+ return harness.marker !== undefined && existsSync4(join5(root, harness.marker));
53606
53682
  }
53607
53683
  function candidates(projectPath) {
53608
53684
  const found = [];
@@ -53615,7 +53691,7 @@ function candidates(projectPath) {
53615
53691
  }
53616
53692
  if (present(harness, projectPath)) {
53617
53693
  found.push({ harness, reason: "configured" });
53618
- } else if (harness.home !== undefined && existsSync4(join4(homedir2(), harness.home))) {
53694
+ } else if (harness.home !== undefined && existsSync4(join5(homedir2(), harness.home))) {
53619
53695
  found.push({ harness, reason: "installed" });
53620
53696
  }
53621
53697
  }
@@ -53673,14 +53749,14 @@ function recordedEnginePath(projectPath) {
53673
53749
  }
53674
53750
  function mainCheckoutOf(projectPath) {
53675
53751
  let top = resolve2(projectPath);
53676
- while (!existsSync4(join4(top, ".git"))) {
53677
- const up = dirname3(top);
53752
+ while (!existsSync4(join5(top, ".git"))) {
53753
+ const up = dirname4(top);
53678
53754
  if (up === top) {
53679
53755
  return null;
53680
53756
  }
53681
53757
  top = up;
53682
53758
  }
53683
- const marker = join4(top, ".git");
53759
+ const marker = join5(top, ".git");
53684
53760
  if (!statSync3(marker).isFile()) {
53685
53761
  return null;
53686
53762
  }
@@ -53689,39 +53765,39 @@ function mainCheckoutOf(projectPath) {
53689
53765
  return null;
53690
53766
  }
53691
53767
  const entry = resolve2(top, pointer);
53692
- const commonFile = join4(entry, "commondir");
53768
+ const commonFile = join5(entry, "commondir");
53693
53769
  if (!existsSync4(commonFile)) {
53694
53770
  return null;
53695
53771
  }
53696
53772
  const common = resolve2(entry, readFileSync2(commonFile, "utf8").trim());
53697
- if (basename2(common) !== ".git") {
53773
+ if (basename3(common) !== ".git") {
53698
53774
  return null;
53699
53775
  }
53700
- const main = join4(dirname3(common), relative2(top, resolve2(projectPath)));
53701
- return isSameDirectory(main, projectPath) || !existsSync4(join4(main, "project.godot")) ? null : main;
53776
+ const main = join5(dirname4(common), relative2(top, resolve2(projectPath)));
53777
+ return isSameDirectory(main, projectPath) || !existsSync4(join5(main, "project.godot")) ? null : main;
53702
53778
  }
53703
53779
  function worktreeCheckoutsOf(projectPath) {
53704
53780
  let top = resolve2(projectPath);
53705
- while (!existsSync4(join4(top, ".git"))) {
53706
- const up = dirname3(top);
53781
+ while (!existsSync4(join5(top, ".git"))) {
53782
+ const up = dirname4(top);
53707
53783
  if (up === top) {
53708
53784
  return [];
53709
53785
  }
53710
53786
  top = up;
53711
53787
  }
53712
- const listed = join4(top, ".git", "worktrees");
53713
- if (!statSync3(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
53788
+ const listed = join5(top, ".git", "worktrees");
53789
+ if (!statSync3(join5(top, ".git")).isDirectory() || !existsSync4(listed)) {
53714
53790
  return [];
53715
53791
  }
53716
53792
  const below = relative2(top, resolve2(projectPath));
53717
53793
  return readdirSync2(listed).flatMap((name) => {
53718
- const pointer = join4(listed, name, "gitdir");
53794
+ const pointer = join5(listed, name, "gitdir");
53719
53795
  if (!existsSync4(pointer)) {
53720
53796
  return [];
53721
53797
  }
53722
- const checkout = dirname3(resolve2(listed, name, readFileSync2(pointer, "utf8").trim()));
53723
- const project = join4(checkout, below);
53724
- return existsSync4(join4(project, "project.godot")) ? [project] : [];
53798
+ const checkout = dirname4(resolve2(listed, name, readFileSync2(pointer, "utf8").trim()));
53799
+ const project = join5(checkout, below);
53800
+ return existsSync4(join5(project, "project.godot")) ? [project] : [];
53725
53801
  });
53726
53802
  }
53727
53803
  function entryEnvironment(harness, projectPath) {
@@ -53863,7 +53939,7 @@ function connect(harness, projectPath, launch) {
53863
53939
  return was === undefined || was === now ? written : { ...written, wasLaunchedBy: was };
53864
53940
  }
53865
53941
  function put(harness, path, contents, already) {
53866
- mkdirSync2(dirname3(path), { recursive: true });
53942
+ mkdirSync2(dirname4(path), { recursive: true });
53867
53943
  writeFileSync2(path, contents, "utf8");
53868
53944
  return { harness, path, action: already ? "replaced" : "written" };
53869
53945
  }
@@ -53916,9 +53992,9 @@ init_setup();
53916
53992
  init_tool_definitions();
53917
53993
  init_tool_reference();
53918
53994
  import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
53919
- import { basename as basename6, dirname as dirname8, join as join15 } from "node:path";
53995
+ import { basename as basename7, dirname as dirname9, join as join16 } from "node:path";
53920
53996
  var SKILL_NAME = "gdharness";
53921
- var SHARED_SKILLS = join15(".agents", "skills");
53997
+ var SHARED_SKILLS = join16(".agents", "skills");
53922
53998
  function skillMarkdown(version) {
53923
53999
  return `---
53924
54000
  name: ${SKILL_NAME}
@@ -54150,27 +54226,27 @@ what they report while starting comes back under \`engine_messages_before_operat
54150
54226
  function skillFiles(version) {
54151
54227
  return new Map([
54152
54228
  ["SKILL.md", skillMarkdown(version)],
54153
- [join15("references", "tools.md"), renderToolsMarkdown()]
54229
+ [join16("references", "tools.md"), renderToolsMarkdown()]
54154
54230
  ]);
54155
54231
  }
54156
54232
  function skillDirectories(harnesses, projectPath, exists) {
54157
- const directories = [join15(projectPath, SHARED_SKILLS, SKILL_NAME)];
54233
+ const directories = [join16(projectPath, SHARED_SKILLS, SKILL_NAME)];
54158
54234
  for (const harness of harnesses) {
54159
54235
  if (harness.skills === undefined) {
54160
54236
  continue;
54161
54237
  }
54162
- const own = join15(projectPath, harness.skills.dir);
54163
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join15(own, SKILL_NAME))) {
54164
- directories.push(join15(own, SKILL_NAME));
54238
+ const own = join16(projectPath, harness.skills.dir);
54239
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join16(own, SKILL_NAME))) {
54240
+ directories.push(join16(own, SKILL_NAME));
54165
54241
  }
54166
54242
  }
54167
54243
  return directories;
54168
54244
  }
54169
54245
  function everySkillDirectory(projectPath, harnesses) {
54170
- const directories = new Set([join15(projectPath, SHARED_SKILLS, SKILL_NAME)]);
54246
+ const directories = new Set([join16(projectPath, SHARED_SKILLS, SKILL_NAME)]);
54171
54247
  for (const harness of harnesses) {
54172
54248
  if (harness.skills !== undefined) {
54173
- directories.add(join15(projectPath, harness.skills.dir, SKILL_NAME));
54249
+ directories.add(join16(projectPath, harness.skills.dir, SKILL_NAME));
54174
54250
  }
54175
54251
  }
54176
54252
  return [...directories];
@@ -54180,21 +54256,21 @@ function removeSkill(directories, projectPath) {
54180
54256
  for (const directory of directories) {
54181
54257
  if (existsSync14(directory)) {
54182
54258
  rmSync6(directory, { recursive: true, force: true });
54183
- pruneEmpty(dirname8(directory), projectPath);
54259
+ pruneEmpty(dirname9(directory), projectPath);
54184
54260
  removed.push(directory);
54185
54261
  }
54186
54262
  }
54187
54263
  return removed;
54188
54264
  }
54189
54265
  function pruneEmpty(directory, projectPath) {
54190
- const shared = join15(projectPath, ".agents");
54266
+ const shared = join16(projectPath, ".agents");
54191
54267
  let path = directory;
54192
- while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
54268
+ while (path !== projectPath && (basename7(path) === "skills" || path === shared)) {
54193
54269
  if (readdirSync9(path).length > 0) {
54194
54270
  return;
54195
54271
  }
54196
54272
  rmSync6(path, { recursive: true, force: true });
54197
- path = dirname8(path);
54273
+ path = dirname9(path);
54198
54274
  }
54199
54275
  }
54200
54276
  function writeSkill(directories, version) {
@@ -54203,8 +54279,8 @@ function writeSkill(directories, version) {
54203
54279
  const replaced = existsSync14(directory);
54204
54280
  rmSync6(directory, { recursive: true, force: true });
54205
54281
  for (const [name, contents] of files) {
54206
- const path = join15(directory, name);
54207
- mkdirSync5(dirname8(path), { recursive: true });
54282
+ const path = join16(directory, name);
54283
+ mkdirSync5(dirname9(path), { recursive: true });
54208
54284
  writeFileSync7(path, contents, "utf8");
54209
54285
  }
54210
54286
  return { path: directory, replaced };
@@ -54213,13 +54289,13 @@ function writeSkill(directories, version) {
54213
54289
 
54214
54290
  // src/version.ts
54215
54291
  import { readFileSync as readFileSync12 } from "node:fs";
54216
- import { dirname as dirname9, join as join16 } from "node:path";
54292
+ import { dirname as dirname10, join as join17 } from "node:path";
54217
54293
  import { fileURLToPath as fileURLToPath2 } from "node:url";
54218
54294
  function getLocalVersion() {
54219
- const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
54295
+ const currentDirectory = dirname10(fileURLToPath2(import.meta.url));
54220
54296
  const packageCandidates = [
54221
- join16(currentDirectory, "..", "package.json"),
54222
- join16(currentDirectory, "..", "..", "package.json")
54297
+ join17(currentDirectory, "..", "package.json"),
54298
+ join17(currentDirectory, "..", "..", "package.json")
54223
54299
  ];
54224
54300
  for (const packagePath of packageCandidates) {
54225
54301
  try {
@@ -54241,7 +54317,7 @@ class UsageError extends Refusal {
54241
54317
  function projectArgument(at) {
54242
54318
  const named = args.slice(at).find((value) => !value.startsWith("--"));
54243
54319
  const projectPath = resolve8(named ?? ".");
54244
- if (!existsSync28(join35(projectPath, "project.godot"))) {
54320
+ if (!existsSync28(join36(projectPath, "project.godot"))) {
54245
54321
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
54246
54322
  }
54247
54323
  return projectPath;
@@ -54286,7 +54362,7 @@ async function registerRuntime(godot, projectPath) {
54286
54362
  return;
54287
54363
  }
54288
54364
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
54289
- if (!existsSync28(join35(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
54365
+ if (!existsSync28(join36(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
54290
54366
  console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
54291
54367
  }
54292
54368
  }