gdharness 1.3.4 → 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.
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)) {
@@ -9271,19 +9282,12 @@ function refusalsIn(log) {
9271
9282
  }
9272
9283
  function scriptFault(problems, script) {
9273
9284
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
9274
- const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
9285
+ const directories = [...new Set([spelled(dirname7(script)), spelled(realAsFarAsItExists(dirname7(script)))])];
9275
9286
  return problems.find((entry) => {
9276
9287
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
9277
9288
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
9278
9289
  });
9279
9290
  }
9280
- function resolvedOr(path) {
9281
- try {
9282
- return realpathSync2.native(path);
9283
- } catch {
9284
- return path;
9285
- }
9286
- }
9287
9291
  function librariesNotCopied(messages) {
9288
9292
  const found = new Set;
9289
9293
  for (const entry of messages) {
@@ -9335,7 +9339,7 @@ async function importOnce(godotPath, projectPath) {
9335
9339
  ran = await runEngine(godotPath, [
9336
9340
  "--headless",
9337
9341
  "--log-file",
9338
- join10(logDir, "engine.log"),
9342
+ join11(logDir, "engine.log"),
9339
9343
  "--path",
9340
9344
  projectPath,
9341
9345
  "--import"
@@ -9357,13 +9361,13 @@ async function importOnce(godotPath, projectPath) {
9357
9361
  }
9358
9362
  async function runOperation(engine, operation, params, projectPath) {
9359
9363
  const paramsDir = scratchDirectory("params");
9360
- const paramsFile = join10(paramsDir, `${operation}.json`);
9361
- const answerFile = join10(paramsDir, "answer.json");
9364
+ const paramsFile = join11(paramsDir, `${operation}.json`);
9365
+ const answerFile = join11(paramsDir, "answer.json");
9362
9366
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
9363
9367
  const args = [
9364
9368
  "--headless",
9365
9369
  "--log-file",
9366
- join10(paramsDir, "engine.log"),
9370
+ join11(paramsDir, "engine.log"),
9367
9371
  "--path",
9368
9372
  projectPath,
9369
9373
  "--script",
@@ -9470,6 +9474,7 @@ var init_headless = __esm(() => {
9470
9474
  init_doc_cache();
9471
9475
  init_engine_run();
9472
9476
  init_library_copies();
9477
+ init_paths();
9473
9478
  init_reimport();
9474
9479
  init_scratch();
9475
9480
  init_script_origins();
@@ -9478,7 +9483,7 @@ var init_headless = __esm(() => {
9478
9483
  // src/update-check.ts
9479
9484
  import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
9480
9485
  import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
9481
- import { join as join11 } from "node:path";
9486
+ import { join as join12 } from "node:path";
9482
9487
  function packageUrl(root) {
9483
9488
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
9484
9489
  }
@@ -9500,20 +9505,20 @@ function cacheDirectory(environment) {
9500
9505
  };
9501
9506
  const home = set("HOME") ?? homedir4();
9502
9507
  if (process.platform === "win32") {
9503
- return join11(set("LOCALAPPDATA") ?? home, "gdharness");
9508
+ return join12(set("LOCALAPPDATA") ?? home, "gdharness");
9504
9509
  }
9505
9510
  if (process.platform === "darwin") {
9506
- return join11(home, "Library", "Caches", "gdharness");
9511
+ return join12(home, "Library", "Caches", "gdharness");
9507
9512
  }
9508
- return join11(set("XDG_CACHE_HOME") ?? join11(home, ".cache"), "gdharness");
9513
+ return join12(set("XDG_CACHE_HOME") ?? join12(home, ".cache"), "gdharness");
9509
9514
  }
9510
9515
  function cacheFile(environment = process.env) {
9511
9516
  try {
9512
9517
  const directory = cacheDirectory(environment);
9513
9518
  mkdirSync3(directory, { recursive: true, mode: 448 });
9514
- return join11(directory, "update-check.json");
9519
+ return join12(directory, "update-check.json");
9515
9520
  } catch {
9516
- return join11(tmpdir3(), "gdharness-update-check.json");
9521
+ return join12(tmpdir3(), "gdharness-update-check.json");
9517
9522
  }
9518
9523
  }
9519
9524
  function readCache(path) {
@@ -10514,7 +10519,7 @@ var init_gdscript_source = __esm(() => {
10514
10519
 
10515
10520
  // src/class-cache.ts
10516
10521
  import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
10517
- import { join as join12, posix } from "node:path";
10522
+ import { join as join13, posix } from "node:path";
10518
10523
  function isToolScript(source) {
10519
10524
  return TOOL.test(codeOf(source));
10520
10525
  }
@@ -10530,14 +10535,14 @@ function declaredClasses(projectPath) {
10530
10535
  }
10531
10536
  function eachScript(projectPath, each) {
10532
10537
  const visit = (directory, prefix) => {
10533
- if (existsSync11(join12(directory, ".gdignore"))) {
10538
+ if (existsSync11(join13(directory, ".gdignore"))) {
10534
10539
  return;
10535
10540
  }
10536
10541
  for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10537
10542
  if (entry.name.startsWith(".")) {
10538
10543
  continue;
10539
10544
  }
10540
- const path = join12(directory, entry.name);
10545
+ const path = join13(directory, entry.name);
10541
10546
  if (entry.isDirectory()) {
10542
10547
  visit(path, `${prefix}${entry.name}/`);
10543
10548
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -10563,7 +10568,7 @@ function scriptsNaming(projectPath, classes) {
10563
10568
  return naming.sort((a, b) => a.script.localeCompare(b.script));
10564
10569
  }
10565
10570
  function cachedClasses(projectPath) {
10566
- const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
10571
+ const cache = join13(projectPath, ".godot", "global_script_class_cache.cfg");
10567
10572
  if (!existsSync11(cache)) {
10568
10573
  return null;
10569
10574
  }
@@ -10574,6 +10579,22 @@ function cachedClasses(projectPath) {
10574
10579
  }
10575
10580
  return listed;
10576
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
+ }
10577
10598
  function withAddonClassesCounted(rebuilt, listed) {
10578
10599
  const counted = { ...rebuilt };
10579
10600
  for (const key of ["added", "changed"]) {
@@ -10584,7 +10605,7 @@ function withAddonClassesCounted(rebuilt, listed) {
10584
10605
  const own = [];
10585
10606
  const perAddon = {};
10586
10607
  for (const name of names) {
10587
- const addon = /^res:\/\/addons\/([^/]+)\//.exec(listed.get(String(name)) ?? "")?.[1];
10608
+ const addon = addonOf(listed.get(String(name)) ?? "");
10588
10609
  if (addon === undefined) {
10589
10610
  own.push(name);
10590
10611
  } else {
@@ -10845,12 +10866,12 @@ function uncachedForTheEngineNote(projectPath, types) {
10845
10866
  }
10846
10867
  const one = types.length === 1;
10847
10868
  const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
10848
- const cacheExists = existsSync11(join12(projectPath, ".godot", "global_script_class_cache.cfg"));
10869
+ const cacheExists = existsSync11(join13(projectPath, ".godot", "global_script_class_cache.cfg"));
10849
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.`;
10850
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.";
10851
10872
  }
10852
10873
  function cacheWrittenAt(projectPath) {
10853
- const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
10874
+ const cache = join13(projectPath, ".godot", "global_script_class_cache.cfg");
10854
10875
  try {
10855
10876
  return statSync6(cache).mtimeMs;
10856
10877
  } catch {
@@ -10864,14 +10885,14 @@ function declaredSince(projectPath, paths, names, since) {
10864
10885
  return false;
10865
10886
  }
10866
10887
  try {
10867
- return statSync6(join12(projectPath, path.slice("res://".length))).mtimeMs > since;
10888
+ return statSync6(join13(projectPath, path.slice("res://".length))).mtimeMs > since;
10868
10889
  } catch {
10869
10890
  return false;
10870
10891
  }
10871
10892
  });
10872
10893
  }
10873
10894
  function cachedAtMissingPaths(cached, projectPath) {
10874
- 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();
10875
10896
  }
10876
10897
  function staleAgainst(cached, projectPath) {
10877
10898
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -10897,7 +10918,7 @@ var init_class_cache = __esm(() => {
10897
10918
 
10898
10919
  // src/project-scan.ts
10899
10920
  import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
10900
- import { extname, join as join13 } from "node:path";
10921
+ import { extname, join as join14 } from "node:path";
10901
10922
  function skipped(name) {
10902
10923
  return name.startsWith(".") || SKIPPED.has(name);
10903
10924
  }
@@ -10905,10 +10926,10 @@ function steppedOver(directory) {
10905
10926
  return gdignored(directory) || installedByUs(directory);
10906
10927
  }
10907
10928
  function gdignored(directory) {
10908
- return existsSync12(join13(directory, ".gdignore"));
10929
+ return existsSync12(join14(directory, ".gdignore"));
10909
10930
  }
10910
10931
  function installedByUs(directory) {
10911
- return existsSync12(join13(directory, VERSION_MARKER));
10932
+ return existsSync12(join14(directory, VERSION_MARKER));
10912
10933
  }
10913
10934
  function projectStructure(projectPath) {
10914
10935
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10921,7 +10942,7 @@ function projectStructure(projectPath) {
10921
10942
  continue;
10922
10943
  }
10923
10944
  if (entry.isDirectory()) {
10924
- visit(join13(directory, entry.name));
10945
+ visit(join14(directory, entry.name));
10925
10946
  } else if (entry.isFile()) {
10926
10947
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10927
10948
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10951,7 +10972,7 @@ function projectFiles(projectPath, extensions) {
10951
10972
  }
10952
10973
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10953
10974
  if (entry.isDirectory()) {
10954
- visit(join13(directory, entry.name), spelled);
10975
+ visit(join14(directory, entry.name), spelled);
10955
10976
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10956
10977
  found.push(spelled);
10957
10978
  }
@@ -10972,14 +10993,14 @@ function scriptsWithoutUid(projectPath) {
10972
10993
  }
10973
10994
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10974
10995
  if (entry.isDirectory()) {
10975
- visit(join13(directory, entry.name), spelled);
10996
+ visit(join14(directory, entry.name), spelled);
10976
10997
  continue;
10977
10998
  }
10978
10999
  if (!entry.isFile()) {
10979
11000
  continue;
10980
11001
  }
10981
11002
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10982
- 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`))) {
10983
11004
  found.push(spelled);
10984
11005
  }
10985
11006
  }
@@ -10990,7 +11011,7 @@ function scriptsWithoutUid(projectPath) {
10990
11011
  function pathsInUidCache(projectPath) {
10991
11012
  let data;
10992
11013
  try {
10993
- data = readFileSync9(join13(projectPath, ".godot", "uid_cache.bin"));
11014
+ data = readFileSync9(join14(projectPath, ".godot", "uid_cache.bin"));
10994
11015
  } catch {
10995
11016
  return null;
10996
11017
  }
@@ -11028,7 +11049,7 @@ function projectTexts(projectPath) {
11028
11049
  if (skipped(entry.name)) {
11029
11050
  continue;
11030
11051
  }
11031
- const absolute = join13(directory, entry.name);
11052
+ const absolute = join14(directory, entry.name);
11032
11053
  const spelled = `${prefix}${entry.name}`;
11033
11054
  if (entry.isDirectory()) {
11034
11055
  visit(absolute, `${spelled}/`);
@@ -11058,7 +11079,7 @@ function ignoredTexts(projectPath) {
11058
11079
  if (skipped(entry.name)) {
11059
11080
  continue;
11060
11081
  }
11061
- const absolute = join13(directory, entry.name);
11082
+ const absolute = join14(directory, entry.name);
11062
11083
  const spelled = `${prefix}${entry.name}`;
11063
11084
  if (entry.isDirectory()) {
11064
11085
  visit(absolute, `${spelled}/`, under);
@@ -11088,6 +11109,14 @@ function plainMatch(line, needle, caseSensitive) {
11088
11109
  }
11089
11110
  return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
11090
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
+ }
11091
11120
  function searchProject(projectPath, options) {
11092
11121
  const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
11093
11122
  const result = {
@@ -11095,7 +11124,7 @@ function searchProject(projectPath, options) {
11095
11124
  results: [],
11096
11125
  summary: { files_searched: 0, files_with_matches: 0, total_matches: 0, truncated: false }
11097
11126
  };
11098
- const regex = options.regex ? new RegExp(options.query, options.caseSensitive ? "" : "i") : null;
11127
+ const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
11099
11128
  const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
11100
11129
  const full = () => result.summary.truncated;
11101
11130
  const visit = (directory) => {
@@ -11109,7 +11138,7 @@ function searchProject(projectPath, options) {
11109
11138
  if (skipped(entry.name)) {
11110
11139
  continue;
11111
11140
  }
11112
- const entryPath = join13(directory, entry.name);
11141
+ const entryPath = join14(directory, entry.name);
11113
11142
  if (entry.isDirectory()) {
11114
11143
  visit(entryPath);
11115
11144
  continue;
@@ -11148,6 +11177,7 @@ function searchProject(projectPath, options) {
11148
11177
  }
11149
11178
  var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
11150
11179
  var init_project_scan = __esm(() => {
11180
+ init_errors();
11151
11181
  SKIPPED = new Set(["node_modules"]);
11152
11182
  SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
11153
11183
  ASSET_EXTENSIONS = new Set([
@@ -11205,7 +11235,7 @@ var init_answer_json = __esm(() => {
11205
11235
  });
11206
11236
 
11207
11237
  // src/resources.ts
11208
- import { readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
11238
+ import { readFileSync as readFileSync10, realpathSync as realpathSync2 } from "node:fs";
11209
11239
  import { extname as extname2, resolve as resolve3 } from "node:path";
11210
11240
  function ensureProjectPath(getProjectPath) {
11211
11241
  const projectPath = getProjectPath();
@@ -11228,13 +11258,13 @@ function resolveProjectFile(projectPath, resourcePath) {
11228
11258
  }
11229
11259
  let root;
11230
11260
  try {
11231
- root = realpathSync3.native(projectPath);
11261
+ root = realpathSync2.native(projectPath);
11232
11262
  } catch {
11233
11263
  throw new Refusal(`The project directory ${projectPath} is not there.`);
11234
11264
  }
11235
11265
  let real;
11236
11266
  try {
11237
- real = realpathSync3.native(contained.absolutePath);
11267
+ real = realpathSync2.native(contained.absolutePath);
11238
11268
  } catch {
11239
11269
  throw new Refusal(`No file at ${resourcePath} in the project.`);
11240
11270
  }
@@ -11563,7 +11593,7 @@ import {
11563
11593
  statSync as statSync7,
11564
11594
  writeFileSync as writeFileSync6
11565
11595
  } from "node:fs";
11566
- import { dirname as dirname7, join as join14 } from "node:path";
11596
+ import { dirname as dirname8, join as join15 } from "node:path";
11567
11597
  import { fileURLToPath } from "node:url";
11568
11598
  function editorAddonDigest(from = shippedAddonsDirectory()) {
11569
11599
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -11578,7 +11608,7 @@ function addonsDigest(names, from) {
11578
11608
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
11579
11609
  continue;
11580
11610
  }
11581
- const path = join14(directory, name);
11611
+ const path = join15(directory, name);
11582
11612
  const inside = `${relative}/${name}`;
11583
11613
  if (statSync7(path).isDirectory()) {
11584
11614
  walk(path, inside);
@@ -11592,7 +11622,7 @@ function addonsDigest(names, from) {
11592
11622
  }
11593
11623
  };
11594
11624
  for (const name of names) {
11595
- walk(join14(from, name), name);
11625
+ walk(join15(from, name), name);
11596
11626
  }
11597
11627
  return hash.digest("hex");
11598
11628
  }
@@ -11611,7 +11641,7 @@ function installedRuntimeDigest(projectPath) {
11611
11641
  return digestMarker(projectPath, RUNTIME_ADDON);
11612
11642
  }
11613
11643
  function digestMarker(projectPath, addon) {
11614
- const marker = join14(projectPath, "addons", addon, DIGEST_MARKER);
11644
+ const marker = join15(projectPath, "addons", addon, DIGEST_MARKER);
11615
11645
  if (!existsSync13(marker)) {
11616
11646
  return null;
11617
11647
  }
@@ -11619,35 +11649,35 @@ function digestMarker(projectPath, addon) {
11619
11649
  return said === "" ? null : said;
11620
11650
  }
11621
11651
  function shippedAddonsDirectory() {
11622
- return join14(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
11652
+ return join15(dirname8(fileURLToPath(import.meta.url)), "godot", "addons");
11623
11653
  }
11624
11654
  function shippedOperationsScript() {
11625
- 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");
11626
11656
  }
11627
11657
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
11628
11658
  const installed = [];
11629
11659
  for (const name of ADDONS) {
11630
- const source = join14(from, name);
11631
- 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"))) {
11632
11662
  throw new Error(`The package holds no ${name} addon at ${source}.`);
11633
11663
  }
11634
- const target = join14(projectPath, "addons", name);
11664
+ const target = join15(projectPath, "addons", name);
11635
11665
  const replaced = existsSync13(target);
11636
11666
  rmSync5(target, { recursive: true, force: true });
11637
- mkdirSync4(dirname7(target), { recursive: true });
11667
+ mkdirSync4(dirname8(target), { recursive: true });
11638
11668
  cpSync(source, target, { recursive: true });
11639
- writeFileSync6(join14(target, VERSION_MARKER), `${SERVER_VERSION}
11669
+ writeFileSync6(join15(target, VERSION_MARKER), `${SERVER_VERSION}
11640
11670
  `);
11641
11671
  installed.push({ name, path: target, replaced });
11642
11672
  }
11643
- writeFileSync6(join14(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
11673
+ writeFileSync6(join15(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
11644
11674
  `);
11645
- writeFileSync6(join14(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
11675
+ writeFileSync6(join15(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
11646
11676
  `);
11647
11677
  return installed;
11648
11678
  }
11649
11679
  function installedAddonVersion(projectPath) {
11650
- const marker = join14(projectPath, "addons", ADDONS[0], VERSION_MARKER);
11680
+ const marker = join15(projectPath, "addons", ADDONS[0], VERSION_MARKER);
11651
11681
  if (!existsSync13(marker)) {
11652
11682
  return null;
11653
11683
  }
@@ -11657,13 +11687,13 @@ function installedAddonVersion(projectPath) {
11657
11687
  function removeAddons(projectPath) {
11658
11688
  const removed = [];
11659
11689
  for (const name of ADDONS) {
11660
- const target = join14(projectPath, "addons", name);
11690
+ const target = join15(projectPath, "addons", name);
11661
11691
  if (existsSync13(target)) {
11662
11692
  rmSync5(target, { recursive: true, force: true });
11663
11693
  removed.push(target);
11664
11694
  }
11665
11695
  }
11666
- const addons = join14(projectPath, "addons");
11696
+ const addons = join15(projectPath, "addons");
11667
11697
  if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
11668
11698
  rmdirSync(addons);
11669
11699
  removed.push(addons);
@@ -11698,7 +11728,7 @@ function loaderAmong(projectPath, named) {
11698
11728
  }
11699
11729
  let source = null;
11700
11730
  try {
11701
- source = readFileSync11(join14(projectPath, path), "utf8");
11731
+ source = readFileSync11(join15(projectPath, path), "utf8");
11702
11732
  } catch {}
11703
11733
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
11704
11734
  return { name: entry, path, exists: true };
@@ -11740,8 +11770,8 @@ function enabledPlugins(settings) {
11740
11770
  function inspectProject(projectPath) {
11741
11771
  const problems = [];
11742
11772
  const addons = ADDONS.map((name) => {
11743
- const directory = join14(projectPath, "addons", name);
11744
- const marker = join14(directory, VERSION_MARKER);
11773
+ const directory = join15(projectPath, "addons", name);
11774
+ const marker = join15(directory, VERSION_MARKER);
11745
11775
  const installed = existsSync13(directory);
11746
11776
  const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
11747
11777
  const current = version === SERVER_VERSION;
@@ -11752,7 +11782,7 @@ function inspectProject(projectPath) {
11752
11782
  }
11753
11783
  return { name, installed, version, current };
11754
11784
  });
11755
- const settings = parseProjectGodot(readFileSync11(join14(projectPath, "project.godot"), "utf8"));
11785
+ const settings = parseProjectGodot(readFileSync11(join15(projectPath, "project.godot"), "utf8"));
11756
11786
  const pluginsEnabled = enabledPlugins(settings);
11757
11787
  for (const name of EDITOR_PLUGINS) {
11758
11788
  if (!pluginsEnabled.includes(name)) {
@@ -11768,7 +11798,7 @@ function inspectProject(projectPath) {
11768
11798
  const tracked = trackedByGit(projectPath, [...named.values()]);
11769
11799
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
11770
11800
  for (const [name, path] of named) {
11771
- if (!existsSync13(join14(projectPath, path))) {
11801
+ if (!existsSync13(join15(projectPath, path))) {
11772
11802
  if (!addonMissing(path)) {
11773
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`);
11774
11804
  }
@@ -11916,7 +11946,7 @@ function annotationsOf(spec) {
11916
11946
  openWorldHint: false
11917
11947
  };
11918
11948
  }
11919
- 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;
11920
11950
  var init_tool_definitions = __esm(() => {
11921
11951
  init_launch();
11922
11952
  PROJECT_INFO_SECTIONS = [
@@ -12420,7 +12450,7 @@ var init_tool_definitions = __esm(() => {
12420
12450
  timeoutMs: {
12421
12451
  type: "integer",
12422
12452
  minimum: 1,
12423
- 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."
12424
12454
  },
12425
12455
  printed: {
12426
12456
  type: "string",
@@ -13219,7 +13249,7 @@ var init_tool_definitions = __esm(() => {
13219
13249
  title: "Editor status",
13220
13250
  effect: "read",
13221
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.",
13222
- 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 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.",
13223
13253
  parameters: {},
13224
13254
  requires: []
13225
13255
  },
@@ -13241,7 +13271,7 @@ var init_tool_definitions = __esm(() => {
13241
13271
  },
13242
13272
  listMembers: {
13243
13273
  type: "boolean",
13244
- 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."
13245
13275
  }
13246
13276
  },
13247
13277
  requires: ["projectPath"]
@@ -13285,7 +13315,7 @@ var init_tool_definitions = __esm(() => {
13285
13315
  includeInherited: {
13286
13316
  type: "boolean",
13287
13317
  ops: ["info"],
13288
- 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."
13289
13319
  },
13290
13320
  member: {
13291
13321
  type: "string",
@@ -13423,7 +13453,7 @@ var init_tool_definitions = __esm(() => {
13423
13453
  {
13424
13454
  name: "runtime_invoke",
13425
13455
  title: "Set or call in the running game",
13426
- 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.",
13427
13457
  parameters: {
13428
13458
  projectPath: RUNNING_PROJECT_PATH,
13429
13459
  pid: RUNNING_PID,
@@ -13440,7 +13470,7 @@ var init_tool_definitions = __esm(() => {
13440
13470
  value: {
13441
13471
  ops: ["set"],
13442
13472
  blank: true,
13443
- 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.`
13444
13474
  },
13445
13475
  method: {
13446
13476
  type: "string",
@@ -13544,7 +13574,7 @@ var init_tool_definitions = __esm(() => {
13544
13574
  name: "runtime_input",
13545
13575
  title: "Send input to the game",
13546
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.",
13547
- 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.`,
13548
13578
  parameters: {
13549
13579
  projectPath: RUNNING_PROJECT_PATH,
13550
13580
  pid: RUNNING_PID,
@@ -13757,7 +13787,7 @@ var init_tool_definitions = __esm(() => {
13757
13787
  },
13758
13788
  value: {
13759
13789
  ops: ["until"],
13760
- 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."
13761
13791
  },
13762
13792
  compare: {
13763
13793
  type: "string",
@@ -13806,16 +13836,21 @@ var init_tool_definitions = __esm(() => {
13806
13836
  {
13807
13837
  name: "debug_breakpoint",
13808
13838
  title: "Breakpoints",
13809
- 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.",
13810
13840
  parameters: {
13811
13841
  projectPath: PROJECT_PATH,
13812
- scriptPath: SCRIPT_PATH,
13813
- 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." }
13814
13844
  },
13815
- requires: ["projectPath", "scriptPath", "line"],
13845
+ requires: ["projectPath"],
13816
13846
  operations: {
13817
- set: { effect: "additive", summary: "set a breakpoint", requires: [] },
13818
- 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
+ }
13819
13854
  }
13820
13855
  },
13821
13856
  {
@@ -35636,12 +35671,12 @@ var init_stdio = __esm(() => {
35636
35671
 
35637
35672
  // src/autoload-failure.ts
35638
35673
  import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
35639
- import { join as join17 } from "node:path";
35674
+ import { join as join18 } from "node:path";
35640
35675
  function scriptsAutoloaded(projectPath) {
35641
35676
  const scripts = new Map;
35642
35677
  let settings;
35643
35678
  try {
35644
- settings = parseProjectGodot(readFileSync13(join17(projectPath, "project.godot"), "utf8"));
35679
+ settings = parseProjectGodot(readFileSync13(join18(projectPath, "project.godot"), "utf8"));
35645
35680
  } catch {
35646
35681
  return scripts;
35647
35682
  }
@@ -35650,7 +35685,7 @@ function scriptsAutoloaded(projectPath) {
35650
35685
  scripts.set(`res://${path}`, name);
35651
35686
  continue;
35652
35687
  }
35653
- const scene = join17(projectPath, path);
35688
+ const scene = join18(projectPath, path);
35654
35689
  if (!existsSync15(scene)) {
35655
35690
  continue;
35656
35691
  }
@@ -35677,7 +35712,7 @@ var init_autoload_failure = __esm(() => {
35677
35712
  import { existsSync as existsSync16, readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
35678
35713
  import { createConnection } from "node:net";
35679
35714
  import { tmpdir as tmpdir4 } from "node:os";
35680
- 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";
35681
35716
  import { setTimeout as delay } from "node:timers/promises";
35682
35717
  function runtimeDirectory(variables = process.env) {
35683
35718
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -35685,19 +35720,19 @@ function runtimeDirectory(variables = process.env) {
35685
35720
  return explicit;
35686
35721
  }
35687
35722
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
35688
- return join18(perUser ?? tmpdir4(), "gdharness");
35723
+ return join19(perUser ?? tmpdir4(), "gdharness");
35689
35724
  }
35690
35725
  function runtimeDirectories(variables = process.env) {
35691
35726
  const candidates = [runtimeDirectory(variables)];
35692
35727
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
35693
35728
  for (const base of fallbacks) {
35694
- candidates.push(join18(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35729
+ candidates.push(join19(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
35695
35730
  }
35696
35731
  return [...new Set(candidates.map((path) => resolve4(path)))];
35697
35732
  }
35698
35733
  function errorReportOf(pid, directories = runtimeDirectories()) {
35699
35734
  for (const directory of directories) {
35700
- const path = join18(directory, `runtime-${pid}.log`);
35735
+ const path = join19(directory, `runtime-${pid}.log`);
35701
35736
  if (existsSync16(path)) {
35702
35737
  return path;
35703
35738
  }
@@ -35851,14 +35886,14 @@ function announcedIn(directory) {
35851
35886
  for (const entry of readdirSync10(directory)) {
35852
35887
  const report = ERROR_REPORT_PATTERN.exec(entry);
35853
35888
  if (report) {
35854
- sweepErrorReport(join18(directory, entry), Number.parseInt(report[1] ?? "", 10));
35889
+ sweepErrorReport(join19(directory, entry), Number.parseInt(report[1] ?? "", 10));
35855
35890
  continue;
35856
35891
  }
35857
35892
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
35858
35893
  if (!match) {
35859
35894
  continue;
35860
35895
  }
35861
- const file = join18(directory, entry);
35896
+ const file = join19(directory, entry);
35862
35897
  const pid = Number.parseInt(match[1] ?? "", 10);
35863
35898
  const running = alive(pid);
35864
35899
  entries.push({ file, pid, running });
@@ -35886,16 +35921,16 @@ function announcedIn(directory) {
35886
35921
  found.push(announced);
35887
35922
  }
35888
35923
  const held = new Set(entries.map((one) => one.file));
35889
- const here = join18(directory, ".");
35924
+ const here = join19(directory, ".");
35890
35925
  for (const file of verdicts.keys()) {
35891
- if (dirname10(file) === here && !held.has(file)) {
35926
+ if (dirname11(file) === here && !held.has(file)) {
35892
35927
  verdicts.delete(file);
35893
35928
  }
35894
35929
  }
35895
35930
  return found;
35896
35931
  }
35897
35932
  async function announcementEnded(pid, directories = runtimeDirectories()) {
35898
- 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));
35899
35934
  const first = files[0];
35900
35935
  if (first === undefined) {
35901
35936
  return;
@@ -35951,7 +35986,7 @@ function theOneProject(endpoints) {
35951
35986
  if (endpoints.some((endpoint) => endpoint.project.path === "")) {
35952
35987
  return;
35953
35988
  }
35954
- 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))));
35955
35990
  const only = [...paths][0];
35956
35991
  return paths.size === 1 ? only : undefined;
35957
35992
  }
@@ -36315,9 +36350,9 @@ var init_runtime_client = __esm(() => {
36315
36350
 
36316
36351
  // src/boot-note.ts
36317
36352
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
36318
- import { dirname as dirname11, join as join19 } from "node:path";
36353
+ import { dirname as dirname12, join as join20 } from "node:path";
36319
36354
  function bootNotePath(projectPath) {
36320
- return join19(projectPath, ".godot", "gdharness-boot.json");
36355
+ return join20(projectPath, ".godot", "gdharness-boot.json");
36321
36356
  }
36322
36357
  function readScenes(projectPath) {
36323
36358
  try {
@@ -36337,7 +36372,7 @@ function readBootNote(projectPath, scene) {
36337
36372
  function writeBootNote(projectPath, scene, announcedAfterMs) {
36338
36373
  try {
36339
36374
  const path = bootNotePath(projectPath);
36340
- mkdirSync6(dirname11(path), { recursive: true });
36375
+ mkdirSync6(dirname12(path), { recursive: true });
36341
36376
  const scenes = {
36342
36377
  ...readScenes(projectPath),
36343
36378
  [scene]: {
@@ -36364,14 +36399,14 @@ var init_boot_note = __esm(() => {
36364
36399
 
36365
36400
  // src/breakpoint-note.ts
36366
36401
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
36367
- 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";
36368
36403
  function breakpointNotePath(projectPath) {
36369
- return join20(projectPath, ".godot", "gdharness-breakpoints.json");
36404
+ return join21(projectPath, ".godot", "gdharness-breakpoints.json");
36370
36405
  }
36371
36406
  function writeBreakpointNote(projectPath, held) {
36372
36407
  const path = breakpointNotePath(projectPath);
36373
- const root = realPathOr(projectPath);
36374
- 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));
36375
36410
  try {
36376
36411
  if (inside.length === 0) {
36377
36412
  rmSync7(path, { force: true });
@@ -36383,7 +36418,7 @@ function writeBreakpointNote(projectPath, held) {
36383
36418
  lines: [...one.lines]
36384
36419
  }))
36385
36420
  };
36386
- mkdirSync7(dirname12(path), { recursive: true });
36421
+ mkdirSync7(dirname13(path), { recursive: true });
36387
36422
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
36388
36423
  `, "utf8");
36389
36424
  } catch (error) {
@@ -36407,7 +36442,7 @@ function readBreakpointNote(projectPath) {
36407
36442
  }
36408
36443
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
36409
36444
  if (lines.length > 0) {
36410
- held.push({ scriptPath: realPathOr(join20(projectPath, script)), lines });
36445
+ held.push({ scriptPath: realAsFarAsItExists(join21(projectPath, script)), lines });
36411
36446
  }
36412
36447
  }
36413
36448
  return held;
@@ -36457,9 +36492,9 @@ var init_whole_file = __esm(() => {
36457
36492
 
36458
36493
  // src/class-note.ts
36459
36494
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync17 } from "node:fs";
36460
- import { dirname as dirname13, join as join21 } from "node:path";
36495
+ import { dirname as dirname14, join as join22 } from "node:path";
36461
36496
  function classNotePath(projectPath) {
36462
- return join21(projectPath, ".godot", "gdharness-classes.json");
36497
+ return join22(projectPath, ".godot", "gdharness-classes.json");
36463
36498
  }
36464
36499
  function readClassNote(projectPath) {
36465
36500
  try {
@@ -36493,7 +36528,7 @@ function unfinishedScanIn(value) {
36493
36528
  function writeClassNote(projectPath, note) {
36494
36529
  const path = classNotePath(projectPath);
36495
36530
  try {
36496
- mkdirSync8(dirname13(path), { recursive: true });
36531
+ mkdirSync8(dirname14(path), { recursive: true });
36497
36532
  } catch {
36498
36533
  return;
36499
36534
  }
@@ -36506,9 +36541,9 @@ var init_class_note = __esm(() => {
36506
36541
 
36507
36542
  // src/config-pin.ts
36508
36543
  import { existsSync as existsSync17, readFileSync as readFileSync18 } from "node:fs";
36509
- import { join as join22 } from "node:path";
36544
+ import { join as join23 } from "node:path";
36510
36545
  function versionsNamedInConfig(projectPath) {
36511
- const config = join22(projectPath, ".mcp.json");
36546
+ const config = join23(projectPath, ".mcp.json");
36512
36547
  if (!existsSync17(config)) {
36513
36548
  return [];
36514
36549
  }
@@ -36536,14 +36571,14 @@ var init_config_pin = __esm(() => {
36536
36571
 
36537
36572
  // src/cut-test-run.ts
36538
36573
  import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
36539
- import { dirname as dirname14, join as join23 } from "node:path";
36574
+ import { dirname as dirname15, join as join24 } from "node:path";
36540
36575
  function keptAt(projectPath) {
36541
- return join23(projectPath, ".godot", "gdharness-reports", "cut-run.json");
36576
+ return join24(projectPath, ".godot", "gdharness-reports", "cut-run.json");
36542
36577
  }
36543
36578
  function keepCutTestRun(projectPath, run) {
36544
36579
  const path = keptAt(projectPath);
36545
36580
  try {
36546
- mkdirSync9(dirname14(path), { recursive: true });
36581
+ mkdirSync9(dirname15(path), { recursive: true });
36547
36582
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
36548
36583
  `, "utf8");
36549
36584
  } catch {}
@@ -37134,13 +37169,14 @@ class GodotDAPClient {
37134
37169
  const key = fileKey(scriptPath);
37135
37170
  const lines = this.breakpoints.get(key);
37136
37171
  if (lines === undefined) {
37137
- return;
37172
+ return false;
37138
37173
  }
37139
- lines.delete(line);
37174
+ const held = lines.delete(line);
37140
37175
  if (lines.size === 0) {
37141
37176
  this.breakpoints.delete(key);
37142
37177
  this.spelled.delete(key);
37143
37178
  }
37179
+ return held;
37144
37180
  }
37145
37181
  holdBreakpoints(held) {
37146
37182
  for (const { scriptPath, lines } of held) {
@@ -37377,10 +37413,10 @@ import {
37377
37413
  writeSync
37378
37414
  } from "node:fs";
37379
37415
  import { tmpdir as tmpdir5 } from "node:os";
37380
- import { join as join24 } from "node:path";
37416
+ import { join as join25 } from "node:path";
37381
37417
  import { setTimeout as delay3 } from "node:timers/promises";
37382
37418
  function helperAssembly(directory = tmpdir5()) {
37383
- return join24(directory, `gdharness-desktop-${DIGEST}.dll`);
37419
+ return join25(directory, `gdharness-desktop-${DIGEST}.dll`);
37384
37420
  }
37385
37421
  async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
37386
37422
  const assembly = helperAssembly(directory);
@@ -37420,8 +37456,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
37420
37456
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
37421
37457
  }
37422
37458
  async function compileHelper(directory, assembly, budgetMs) {
37423
- const scratch = mkdtempSync2(join24(directory, "gdharness-desktop-source-"));
37424
- const source = join24(scratch, "helper.cs");
37459
+ const scratch = mkdtempSync2(join25(directory, "gdharness-desktop-source-"));
37460
+ const source = join25(scratch, "helper.cs");
37425
37461
  writeFileSync12(source, SOURCE, "utf8");
37426
37462
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
37427
37463
  const script = [
@@ -37635,12 +37671,12 @@ import {
37635
37671
  statSync as statSync10,
37636
37672
  writeFileSync as writeFileSync13
37637
37673
  } from "node:fs";
37638
- import { dirname as dirname15, join as join25 } from "node:path";
37674
+ import { dirname as dirname16, join as join26 } from "node:path";
37639
37675
  function editorLogPath(projectPath) {
37640
- return join25(projectPath, ".godot", "gdharness-editor.log");
37676
+ return join26(projectPath, ".godot", "gdharness-editor.log");
37641
37677
  }
37642
37678
  function editorLogNotePath(projectPath) {
37643
- return join25(projectPath, ".godot", "gdharness-editor.json");
37679
+ return join26(projectPath, ".godot", "gdharness-editor.json");
37644
37680
  }
37645
37681
  function readEditorLogNote(projectPath) {
37646
37682
  try {
@@ -37655,7 +37691,7 @@ function readEditorLogNote(projectPath) {
37655
37691
  function clearEditorLog(projectPath) {
37656
37692
  try {
37657
37693
  const path = editorLogPath(projectPath);
37658
- mkdirSync10(dirname15(path), { recursive: true });
37694
+ mkdirSync10(dirname16(path), { recursive: true });
37659
37695
  rmSync11(path, { force: true });
37660
37696
  rmSync11(editorLogNotePath(projectPath), { force: true });
37661
37697
  } catch {}
@@ -37663,7 +37699,7 @@ function clearEditorLog(projectPath) {
37663
37699
  function writeEditorLogNote(projectPath, pid) {
37664
37700
  try {
37665
37701
  const path = editorLogNotePath(projectPath);
37666
- mkdirSync10(dirname15(path), { recursive: true });
37702
+ mkdirSync10(dirname16(path), { recursive: true });
37667
37703
  writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
37668
37704
  } catch {}
37669
37705
  }
@@ -37905,7 +37941,7 @@ var init_editor_log = __esm(() => {
37905
37941
 
37906
37942
  // src/rename.ts
37907
37943
  import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync22, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
37908
- import { dirname as dirname16, posix as posix2 } from "node:path";
37944
+ import { dirname as dirname17, posix as posix2 } from "node:path";
37909
37945
 
37910
37946
  class Script {
37911
37947
  path;
@@ -38620,7 +38656,7 @@ function applyRewrites(rewrites) {
38620
38656
  const done = [];
38621
38657
  try {
38622
38658
  for (const rewrite of rewrites) {
38623
- mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
38659
+ mkdirSync11(dirname17(rewrite.writeTo), { recursive: true });
38624
38660
  const staging = `${rewrite.writeTo}.gdharness-rename`;
38625
38661
  writeFileSync14(staging, rewrite.after, "utf8");
38626
38662
  renameSync3(staging, rewrite.writeTo);
@@ -38673,10 +38709,10 @@ var init_rename = __esm(() => {
38673
38709
  });
38674
38710
 
38675
38711
  // src/lsp_client.ts
38676
- import { existsSync as existsSync22, realpathSync as realpathSync4 } from "node:fs";
38712
+ import { existsSync as existsSync22, realpathSync as realpathSync3 } from "node:fs";
38677
38713
  import { readFile, realpath } from "node:fs/promises";
38678
38714
  import { createConnection as createConnection3 } from "node:net";
38679
- 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";
38680
38716
  import { setTimeout as delay4 } from "node:timers/promises";
38681
38717
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
38682
38718
  function initializeTimeoutMs() {
@@ -38694,7 +38730,7 @@ function diagnosticsKey(uri) {
38694
38730
  return decoded;
38695
38731
  }
38696
38732
  try {
38697
- return realpathSync4.native(fileURLToPath3(decoded)).toLowerCase();
38733
+ return realpathSync3.native(fileURLToPath3(decoded)).toLowerCase();
38698
38734
  } catch {
38699
38735
  return decoded.toLowerCase();
38700
38736
  }
@@ -38838,7 +38874,7 @@ class GodotLSPClient {
38838
38874
  if (this.initialized) {
38839
38875
  return;
38840
38876
  }
38841
- const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
38877
+ const rootPath = this.rootPath ?? dirname18(resolve5(filePath));
38842
38878
  await this.initialize(rootPath);
38843
38879
  }
38844
38880
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -39354,7 +39390,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
39354
39390
  } catch {
39355
39391
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
39356
39392
  }
39357
- if (!existsSync22(join26(projectPath, "project.godot"))) {
39393
+ if (!existsSync22(join27(projectPath, "project.godot"))) {
39358
39394
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
39359
39395
  }
39360
39396
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -39514,8 +39550,8 @@ var init_lsp_client = __esm(() => {
39514
39550
  });
39515
39551
 
39516
39552
  // src/rename-symbol.ts
39517
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
39518
- 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";
39519
39555
  function resourcePathOf(projectPath, absolute) {
39520
39556
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
39521
39557
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -39528,13 +39564,13 @@ function onDiskSpelling(path) {
39528
39564
  let at = resolve6(path);
39529
39565
  for (;; ) {
39530
39566
  try {
39531
- return join27(realpathSync5.native(at), ...missing.reverse());
39567
+ return join28(realpathSync4.native(at), ...missing.reverse());
39532
39568
  } catch {
39533
- const parent = dirname18(at);
39569
+ const parent = dirname19(at);
39534
39570
  if (parent === at) {
39535
39571
  return resolve6(path);
39536
39572
  }
39537
- missing.push(basename7(at));
39573
+ missing.push(basename8(at));
39538
39574
  at = parent;
39539
39575
  }
39540
39576
  }
@@ -39957,15 +39993,15 @@ ${mention.kind}`;
39957
39993
  };
39958
39994
  }
39959
39995
  function writeLeftAloneDetail(projectPath, symbol, detail) {
39960
- const directory = join27(projectPath, ".godot", "gdharness-renames");
39996
+ const directory = join28(projectPath, ".godot", "gdharness-renames");
39961
39997
  mkdirSync12(directory, { recursive: true });
39962
39998
  detailWrites += 1;
39963
- 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`);
39964
40000
  writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
39965
40001
  `, "utf8");
39966
40002
  const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
39967
40003
  for (const old of kept.slice(10)) {
39968
- rmSync12(join27(directory, old), { force: true });
40004
+ rmSync12(join28(directory, old), { force: true });
39969
40005
  }
39970
40006
  return path;
39971
40007
  }
@@ -40014,10 +40050,10 @@ import {
40014
40050
  statSync as statSync11,
40015
40051
  writeFileSync as writeFileSync16
40016
40052
  } from "node:fs";
40017
- 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";
40018
40054
  function filesUnder(directory) {
40019
40055
  return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
40020
- const path = join28(directory, entry.name);
40056
+ const path = join29(directory, entry.name);
40021
40057
  return entry.isDirectory() ? filesUnder(path) : [path];
40022
40058
  });
40023
40059
  }
@@ -40038,8 +40074,8 @@ function unseenByTheEngine(projectPath, path) {
40038
40074
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
40039
40075
  }
40040
40076
  if (at < steps.length - 1) {
40041
- directory = join28(directory, step);
40042
- if (existsSync24(join28(directory, ".gdignore"))) {
40077
+ directory = join29(directory, step);
40078
+ if (existsSync24(join29(directory, ".gdignore"))) {
40043
40079
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
40044
40080
  }
40045
40081
  }
@@ -40051,12 +40087,12 @@ function installedAddonOf(projectPath, path) {
40051
40087
  if (steps[0] !== "addons" || steps[1] === undefined) {
40052
40088
  return null;
40053
40089
  }
40054
- 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;
40055
40091
  }
40056
40092
  function binaryResources(projectPath) {
40057
40093
  const found = [];
40058
40094
  const visit = (directory, prefix) => {
40059
- if (existsSync24(join28(directory, ".gdignore"))) {
40095
+ if (existsSync24(join29(directory, ".gdignore"))) {
40060
40096
  return;
40061
40097
  }
40062
40098
  for (const entry of readdirSync12(directory, { withFileTypes: true })) {
@@ -40065,7 +40101,7 @@ function binaryResources(projectPath) {
40065
40101
  }
40066
40102
  const path = `${prefix}${entry.name}`;
40067
40103
  if (entry.isDirectory()) {
40068
- visit(join28(directory, entry.name), `${path}/`);
40104
+ visit(join29(directory, entry.name), `${path}/`);
40069
40105
  } else if (/\.(scn|res)$/i.test(entry.name)) {
40070
40106
  found.push(path);
40071
40107
  }
@@ -40075,7 +40111,7 @@ function binaryResources(projectPath) {
40075
40111
  return found;
40076
40112
  }
40077
40113
  function targetPathOf(projectPath, absolute) {
40078
- const inside = relative5(onDiskSpelling(projectPath), join28(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
40114
+ const inside = relative5(onDiskSpelling(projectPath), join29(onDiskSpelling(dirname20(absolute)), basename9(absolute)));
40079
40115
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
40080
40116
  return null;
40081
40117
  }
@@ -40130,7 +40166,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40130
40166
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
40131
40167
  }
40132
40168
  if (!isDirectory) {
40133
- 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))));
40134
40170
  if (owner !== undefined && owner !== null) {
40135
40171
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
40136
40172
  }
@@ -40164,12 +40200,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40164
40200
  scripts,
40165
40201
  moves,
40166
40202
  named: [from],
40167
- exists: (path) => existsSync24(join28(projectPath, path.slice("res://".length)))
40203
+ exists: (path) => existsSync24(join29(projectPath, path.slice("res://".length)))
40168
40204
  }, { edits: [], mentions: [] });
40169
40205
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
40170
40206
  const binaries = [];
40171
40207
  for (const path of binaryResources(projectPath)) {
40172
- const bytes = readFileSync23(join28(projectPath, path.slice("res://".length)));
40208
+ const bytes = readFileSync23(join29(projectPath, path.slice("res://".length)));
40173
40209
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
40174
40210
  if (names !== undefined) {
40175
40211
  binaries.push({
@@ -40188,7 +40224,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
40188
40224
  }));
40189
40225
  const left = leftAloneReport(mentions);
40190
40226
  const rewrites = rewritesOf(plan, texts, null);
40191
- const absoluteOf = (path) => join28(projectPath, path.slice("res://".length));
40227
+ const absoluteOf = (path) => join29(projectPath, path.slice("res://".length));
40192
40228
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
40193
40229
  const moved = [...moves].map(([source, target]) => {
40194
40230
  const rewrite = rewritten.get(source);
@@ -40247,7 +40283,7 @@ function applyFileMove(outcome) {
40247
40283
  }
40248
40284
  const undo = [];
40249
40285
  const rename = (source, target) => {
40250
- mkdirSync13(dirname19(target), { recursive: true });
40286
+ mkdirSync13(dirname20(target), { recursive: true });
40251
40287
  renameSync4(source, target);
40252
40288
  undo.push(() => {
40253
40289
  renameSync4(target, source);
@@ -43256,10 +43292,7 @@ function mayYetConnect(listeningSince, now = Date.now()) {
43256
43292
  return listeningSince === undefined || now - listeningSince.getTime() < CONNECT_WINDOW_MS;
43257
43293
  }
43258
43294
  function theEditorHasComeBack(status, startedAt) {
43259
- return hasSaidWhoItIs(status) && (status.connectedAt?.getTime() ?? 0) > startedAt;
43260
- }
43261
- function hasSaidWhoItIs(status) {
43262
- return status.connected && status.addonVersion !== undefined;
43295
+ return status.connected && (status.connectedAt?.getTime() ?? 0) > startedAt;
43263
43296
  }
43264
43297
  function withoutTrailingSeparator(path) {
43265
43298
  if (path.length <= 1 || !/[\\/]$/.test(path) || /^[A-Za-z]:[\\/]$/.test(path)) {
@@ -43449,7 +43482,8 @@ var init_godot_bridge = __esm(() => {
43449
43482
  this.log("info", "WebSocket bridge stopped");
43450
43483
  }
43451
43484
  isConnected() {
43452
- 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;
43453
43487
  }
43454
43488
  isArriving() {
43455
43489
  const info = this.connectionInfo;
@@ -43459,7 +43493,7 @@ var init_godot_bridge = __esm(() => {
43459
43493
  return [...this.pendingRequests.values()].filter((pending) => pending.counted).map((pending) => ({ tool: pending.toolName, startedAt: pending.startedAt })).sort((a, b) => a.startedAt - b.startedAt);
43460
43494
  }
43461
43495
  getStatus() {
43462
- const identifying = this.connectionInfo?.identifying === true;
43496
+ const identifying = this.isArriving();
43463
43497
  const editor = identifying ? null : this.connectionInfo;
43464
43498
  return {
43465
43499
  host: this.host,
@@ -43731,7 +43765,7 @@ var init_godot_bridge = __esm(() => {
43731
43765
  startKeepalive() {
43732
43766
  this.stopKeepalive();
43733
43767
  this.pingInterval = setInterval(() => {
43734
- if (!this.isConnected()) {
43768
+ if (this.socket?.readyState !== import_websocket.default.OPEN) {
43735
43769
  return;
43736
43770
  }
43737
43771
  try {
@@ -43741,6 +43775,7 @@ var init_godot_bridge = __esm(() => {
43741
43775
  this.log("warn", `Failed to send ping: ${errorMessage(error)}`);
43742
43776
  }
43743
43777
  }, KEEPALIVE_INTERVAL_MS);
43778
+ this.pingInterval.unref();
43744
43779
  }
43745
43780
  stopKeepalive() {
43746
43781
  if (!this.pingInterval) {
@@ -43912,7 +43947,7 @@ var init_godot_bridge = __esm(() => {
43912
43947
  // src/import-freshness.ts
43913
43948
  import { createHash as createHash5 } from "node:crypto";
43914
43949
  import { existsSync as existsSync25, readdirSync as readdirSync13, readFileSync as readFileSync24, statSync as statSync12 } from "node:fs";
43915
- import { join as join29 } from "node:path";
43950
+ import { join as join30 } from "node:path";
43916
43951
  function md5Of(file) {
43917
43952
  let stats;
43918
43953
  try {
@@ -43956,7 +43991,7 @@ function listed2(value) {
43956
43991
  return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
43957
43992
  }
43958
43993
  function onDisk(projectPath, resPath) {
43959
- return join29(projectPath, resPath.slice("res://".length));
43994
+ return join30(projectPath, resPath.slice("res://".length));
43960
43995
  }
43961
43996
  function behind(projectPath, resPath, sidecar) {
43962
43997
  const remap = sectionOf(sidecar, "remap");
@@ -43969,7 +44004,7 @@ function behind(projectPath, resPath, sidecar) {
43969
44004
  return "its imported output is not on disk";
43970
44005
  }
43971
44006
  const name = resPath.slice(resPath.lastIndexOf("/") + 1);
43972
- 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`);
43973
44008
  let recorded = "";
43974
44009
  try {
43975
44010
  recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync24(record, "utf8"))?.[1] ?? "";
@@ -43985,7 +44020,7 @@ function behind(projectPath, resPath, sidecar) {
43985
44020
  function staleImports(projectPath) {
43986
44021
  const stale = [];
43987
44022
  const visit = (directory, resDirectory) => {
43988
- if (existsSync25(join29(directory, ".gdignore")) || existsSync25(join29(directory, VERSION_MARKER))) {
44023
+ if (existsSync25(join30(directory, ".gdignore")) || existsSync25(join30(directory, VERSION_MARKER))) {
43989
44024
  return;
43990
44025
  }
43991
44026
  let entries;
@@ -44001,7 +44036,7 @@ function staleImports(projectPath) {
44001
44036
  }
44002
44037
  const resPath = `${resDirectory}${entry.name}`;
44003
44038
  if (entry.isDirectory()) {
44004
- visit(join29(directory, entry.name), `${resPath}/`);
44039
+ visit(join30(directory, entry.name), `${resPath}/`);
44005
44040
  continue;
44006
44041
  }
44007
44042
  if (!entry.isFile()) {
@@ -44014,7 +44049,7 @@ function staleImports(projectPath) {
44014
44049
  }
44015
44050
  let sidecar;
44016
44051
  try {
44017
- sidecar = readFileSync24(join29(directory, entry.name), "utf8");
44052
+ sidecar = readFileSync24(join30(directory, entry.name), "utf8");
44018
44053
  } catch {
44019
44054
  continue;
44020
44055
  }
@@ -44135,15 +44170,15 @@ var init_keeper_channel = () => {};
44135
44170
 
44136
44171
  // src/lifecycle-log.ts
44137
44172
  import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync13 } from "node:fs";
44138
- import { dirname as dirname20, join as join30 } from "node:path";
44173
+ import { dirname as dirname21, join as join31 } from "node:path";
44139
44174
  import process11 from "node:process";
44140
44175
  function lifecycleLogPath() {
44141
- return join30(runtimeDirectory(), "servers.log");
44176
+ return join31(runtimeDirectory(), "servers.log");
44142
44177
  }
44143
44178
  function writeLifecycle(event, fields = {}) {
44144
44179
  const path = lifecycleLogPath();
44145
44180
  try {
44146
- mkdirSync14(dirname20(path), { recursive: true });
44181
+ mkdirSync14(dirname21(path), { recursive: true });
44147
44182
  if ((statSync13(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
44148
44183
  try {
44149
44184
  renameSync5(path, `${path}.1`);
@@ -44499,14 +44534,14 @@ var init_prompts = __esm(() => {
44499
44534
 
44500
44535
  // src/restart-note.ts
44501
44536
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
44502
- import { dirname as dirname21, join as join31 } from "node:path";
44537
+ import { dirname as dirname22, join as join32 } from "node:path";
44503
44538
  function restartNotePath(projectPath) {
44504
- return join31(projectPath, ".godot", "gdharness-restart.json");
44539
+ return join32(projectPath, ".godot", "gdharness-restart.json");
44505
44540
  }
44506
44541
  function noteRestartBegun(note) {
44507
44542
  const path = restartNotePath(note.projectPath);
44508
44543
  try {
44509
- mkdirSync15(dirname21(path), { recursive: true });
44544
+ mkdirSync15(dirname22(path), { recursive: true });
44510
44545
  writeFileSync17(path, `${JSON.stringify(note, null, 2)}
44511
44546
  `, "utf8");
44512
44547
  } catch (error) {
@@ -44541,26 +44576,10 @@ var init_restart_note = __esm(() => {
44541
44576
 
44542
44577
  // src/run-record.ts
44543
44578
  import { createHash as createHash6 } from "node:crypto";
44544
- 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";
44545
- 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";
44546
44581
  function runsDirectory() {
44547
- return join32(runtimeDirectory(), "runs");
44548
- }
44549
- function realAsFarAsItExists(path) {
44550
- const missing = [];
44551
- let at = path;
44552
- for (;; ) {
44553
- try {
44554
- return join32(realpathSync6.native(at), ...missing);
44555
- } catch {
44556
- const parent = dirname22(at);
44557
- if (parent === at) {
44558
- return path;
44559
- }
44560
- missing.unshift(basename9(at));
44561
- at = parent;
44562
- }
44563
- }
44582
+ return join33(runtimeDirectory(), "runs");
44564
44583
  }
44565
44584
  function projectKey(projectPath) {
44566
44585
  const real = realAsFarAsItExists(resolve7(projectPath)).replace(/[\\/]+$/, "");
@@ -44568,10 +44587,10 @@ function projectKey(projectPath) {
44568
44587
  return createHash6("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
44569
44588
  }
44570
44589
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
44571
- return join32(directory, "runs", `note-${projectKey(projectPath)}.json`);
44590
+ return join33(directory, "runs", `note-${projectKey(projectPath)}.json`);
44572
44591
  }
44573
44592
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
44574
- return join32(directory, "runs", `latest-${projectKey(servedBy)}.json`);
44593
+ return join33(directory, "runs", `latest-${projectKey(servedBy)}.json`);
44575
44594
  }
44576
44595
  function writeLatestRun(servedBy, latest) {
44577
44596
  try {
@@ -44596,20 +44615,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
44596
44615
  }
44597
44616
  function openTranscript(startedAt) {
44598
44617
  mkdirSync16(runsDirectory(), { recursive: true });
44599
- const path = join32(runsDirectory(), `run-${startedAt}.log`);
44618
+ const path = join33(runsDirectory(), `run-${startedAt}.log`);
44600
44619
  return { path, fd: openSync3(path, "a") };
44601
44620
  }
44602
44621
  function profilePath(startedAt) {
44603
- return join32(runsDirectory(), `run-${startedAt}.profile.json`);
44622
+ return join33(runsDirectory(), `run-${startedAt}.profile.json`);
44604
44623
  }
44605
44624
  function profileMarkPath(startedAt) {
44606
- return join32(runsDirectory(), `run-${startedAt}.profile-mark.json`);
44625
+ return join33(runsDirectory(), `run-${startedAt}.profile-mark.json`);
44607
44626
  }
44608
44627
  function noteIsAbout(note, playing) {
44609
44628
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
44610
44629
  }
44611
44630
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
44612
- return join32(directory, "runs", `editor-${projectKey(projectPath)}.json`);
44631
+ return join33(directory, "runs", `editor-${projectKey(projectPath)}.json`);
44613
44632
  }
44614
44633
  function writeEditorRunNote(note) {
44615
44634
  mkdirSync16(runsDirectory(), { recursive: true });
@@ -44662,13 +44681,13 @@ function everyRunRecord() {
44662
44681
  for (const directory of runtimeDirectories()) {
44663
44682
  let entries;
44664
44683
  try {
44665
- entries = readdirSync14(join32(directory, "runs"));
44684
+ entries = readdirSync14(join33(directory, "runs"));
44666
44685
  } catch {
44667
44686
  continue;
44668
44687
  }
44669
44688
  for (const entry of entries) {
44670
44689
  if (entry.startsWith("note-") && entry.endsWith(".json")) {
44671
- const record = recordAt(join32(directory, "runs", entry));
44690
+ const record = recordAt(join33(directory, "runs", entry));
44672
44691
  if (record !== null) {
44673
44692
  found.push(record);
44674
44693
  }
@@ -44843,7 +44862,7 @@ function whyNotTheRun(record, running, needed) {
44843
44862
  if (after > SAME_RUN_WINDOW_MS) {
44844
44863
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
44845
44864
  }
44846
- const engine = basename9(record.command);
44865
+ const engine = basename10(record.command);
44847
44866
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
44848
44867
  const wanted = process.platform === "win32" ? engine.toLowerCase() : engine;
44849
44868
  if (!said.includes(wanted)) {
@@ -44906,7 +44925,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
44906
44925
  if (!transcript && !entry.endsWith(".partial")) {
44907
44926
  continue;
44908
44927
  }
44909
- const path = join32(directory, entry);
44928
+ const path = join33(directory, entry);
44910
44929
  if (kept.has(path) || ageMs(path, now) < keepMs) {
44911
44930
  continue;
44912
44931
  }
@@ -45155,15 +45174,15 @@ var init_server_instructions = __esm(() => {
45155
45174
 
45156
45175
  // src/test-selection.ts
45157
45176
  import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
45158
- import { basename as basename10, join as join33 } from "node:path";
45177
+ import { basename as basename11, join as join34 } from "node:path";
45159
45178
  function casesIn(projectPath, suite) {
45160
- const text = readFileSync28(join33(projectPath, suite.slice("res://".length)), "utf8");
45179
+ const text = readFileSync28(join34(projectPath, suite.slice("res://".length)), "utf8");
45161
45180
  return [...text.matchAll(/^func\s+(test_\w*)\s*\(/gm)].map((match) => match[1] ?? "");
45162
45181
  }
45163
45182
  function suitesUnder(projectPath, asked) {
45164
45183
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
45165
45184
  return [
45166
- ...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}/`))))
45167
45186
  ];
45168
45187
  }
45169
45188
  function selectTests(projectPath, asked, given) {
@@ -45175,7 +45194,7 @@ function selectTests(projectPath, asked, given) {
45175
45194
  return { ok: false, reason: "tests is an empty list: name a case, or leave tests out to run every one." };
45176
45195
  }
45177
45196
  const declared = suitesUnder(projectPath, asked).flatMap((suite) => casesIn(projectPath, suite).map((test) => ({ suite, test })));
45178
- const nameOf = (suite) => basename10(suite, ".gd");
45197
+ const nameOf = (suite) => basename11(suite, ".gd");
45179
45198
  const selected = [];
45180
45199
  for (const [index, entry] of entries.entries()) {
45181
45200
  if (typeof entry !== "string" || entry.trim() === "") {
@@ -45235,7 +45254,7 @@ import {
45235
45254
  statSync as statSync15,
45236
45255
  writeFileSync as writeFileSync18
45237
45256
  } from "node:fs";
45238
- 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";
45239
45258
  import { setTimeout as delay5 } from "node:timers/promises";
45240
45259
  import { fileURLToPath as fileURLToPath5 } from "node:url";
45241
45260
  function editorRestartTimeoutMs() {
@@ -45271,12 +45290,12 @@ function writtenFileRefusal(path, rule) {
45271
45290
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
45272
45291
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
45273
45292
  }
45274
- const directory = realPathOr(dirname23(path));
45293
+ const directory = realAsFarAsItExists(dirname23(path));
45275
45294
  if (!existsSync27(directory)) {
45276
45295
  return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
45277
45296
  }
45278
- const where = join34(directory, basename11(path));
45279
- 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));
45280
45299
  if (inside !== undefined) {
45281
45300
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
45282
45301
  }
@@ -45366,7 +45385,7 @@ function keptGameRequest(tool, op, args) {
45366
45385
  }
45367
45386
  }
45368
45387
  function projectSpelling(projectPath, absolutePath) {
45369
- return `res://${relative6(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
45388
+ return `res://${relative6(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
45370
45389
  }
45371
45390
  function whichGame(args) {
45372
45391
  return { projectPath: args["projectPath"], pid: args["pid"] };
@@ -45825,10 +45844,14 @@ function importedFirstAnswer(importedFirst) {
45825
45844
  if (importedFirst === undefined) {
45826
45845
  return {};
45827
45846
  }
45847
+ const imported = withAddonPathsCounted(importedFirst.imported);
45848
+ const uids = withAddonPathsCounted(importedFirst.uidsWritten);
45828
45849
  return {
45829
- ...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 },
45830
45852
  ...importedFirst.notImported.length === 0 ? {} : { notImportedFirst: importedFirst.notImported },
45831
- ...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 },
45832
45855
  importTookMs: importedFirst.tookMs,
45833
45856
  ...importedFirst.note === undefined ? {} : { importNote: importedFirst.note }
45834
45857
  };
@@ -45861,7 +45884,7 @@ function notRunNote(notRun, failFast) {
45861
45884
  function exportPresetRefusal(projectPath, preset) {
45862
45885
  let text;
45863
45886
  try {
45864
- text = readFileSync29(join34(projectPath, "export_presets.cfg"), "utf8");
45887
+ text = readFileSync29(join35(projectPath, "export_presets.cfg"), "utf8");
45865
45888
  } catch {
45866
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.`;
45867
45890
  }
@@ -45927,7 +45950,7 @@ function filesIn(folder) {
45927
45950
  try {
45928
45951
  for (const entry of readdirSync15(folder, { withFileTypes: true })) {
45929
45952
  if (entry.isFile()) {
45930
- found.set(entry.name, statSync15(join34(folder, entry.name)).mtimeMs);
45953
+ found.set(entry.name, statSync15(join35(folder, entry.name)).mtimeMs);
45931
45954
  }
45932
45955
  }
45933
45956
  } catch {}
@@ -45959,7 +45982,7 @@ function unsavedInEditorNote(scripts) {
45959
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.`;
45960
45983
  }
45961
45984
  function editorAddonMissing(projectPath) {
45962
- if (!existsSync27(join34(projectPath, "project.godot"))) {
45985
+ if (!existsSync27(join35(projectPath, "project.godot"))) {
45963
45986
  return null;
45964
45987
  }
45965
45988
  const report = inspectProject(projectPath);
@@ -46224,7 +46247,7 @@ function profileFrameFunctions(projectPath) {
46224
46247
  let projectGodot = null;
46225
46248
  if (projectPath !== null) {
46226
46249
  try {
46227
- projectGodot = readFileSync29(join34(projectPath, "project.godot"), "utf8");
46250
+ projectGodot = readFileSync29(join35(projectPath, "project.godot"), "utf8");
46228
46251
  } catch {}
46229
46252
  }
46230
46253
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -46403,7 +46426,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
46403
46426
  }
46404
46427
  function gdUnitFailsOnScriptErrors(projectPath) {
46405
46428
  try {
46406
- 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;
46407
46430
  } catch {
46408
46431
  return true;
46409
46432
  }
@@ -46418,7 +46441,7 @@ function uncaughtNote(uncaught) {
46418
46441
  }
46419
46442
  function elsewhereIn(projectPath, asked) {
46420
46443
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
46421
- if (wanted === "" || existsSync27(join34(projectPath, wanted))) {
46444
+ if (wanted === "" || existsSync27(join35(projectPath, wanted))) {
46422
46445
  return "";
46423
46446
  }
46424
46447
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -46521,7 +46544,7 @@ function isDirectory(path) {
46521
46544
  }
46522
46545
  }
46523
46546
  function scriptsUnder(directory, prefix) {
46524
- if (existsSync27(join34(directory, ".gdignore"))) {
46547
+ if (existsSync27(join35(directory, ".gdignore"))) {
46525
46548
  return [];
46526
46549
  }
46527
46550
  const found = [];
@@ -46531,7 +46554,7 @@ function scriptsUnder(directory, prefix) {
46531
46554
  }
46532
46555
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
46533
46556
  if (entry.isDirectory()) {
46534
- found.push(...scriptsUnder(join34(directory, entry.name), path));
46557
+ found.push(...scriptsUnder(join35(directory, entry.name), path));
46535
46558
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
46536
46559
  found.push(path);
46537
46560
  }
@@ -46561,7 +46584,7 @@ function needsItsOwnProcess(asked) {
46561
46584
  class GodotServer {
46562
46585
  stdio = null;
46563
46586
  locator = new GodotLocator;
46564
- operationsScript = join34(__dirname2, "godot", "operations", "godot_operations.gd");
46587
+ operationsScript = join35(__dirname2, "godot", "operations", "godot_operations.gd");
46565
46588
  godotBridge;
46566
46589
  tools = buildToolDefinitions();
46567
46590
  activeProcess = null;
@@ -46823,7 +46846,7 @@ class GodotServer {
46823
46846
  }
46824
46847
  noteTheStart() {
46825
46848
  const cwd = process.cwd();
46826
- const mcpJson = join34(cwd, ".mcp.json");
46849
+ const mcpJson = join35(cwd, ".mcp.json");
46827
46850
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
46828
46851
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
46829
46852
  writeLifecycle("started", {
@@ -46971,7 +46994,7 @@ class GodotServer {
46971
46994
  return checked.response;
46972
46995
  }
46973
46996
  const named = args["projectPath"];
46974
- if (typeof named === "string" && existsSync27(join34(named, "project.godot"))) {
46997
+ if (typeof named === "string" && existsSync27(join35(named, "project.godot"))) {
46975
46998
  this.lastProjectPath = named;
46976
46999
  }
46977
47000
  this.updates.refresh();
@@ -47007,7 +47030,7 @@ class GodotServer {
47007
47030
  let notSaved;
47008
47031
  try {
47009
47032
  savedAt = answer.content.map((item, index) => {
47010
- 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"}`);
47011
47034
  writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
47012
47035
  return file;
47013
47036
  });
@@ -47286,6 +47309,14 @@ class GodotServer {
47286
47309
  if (headless === "modify_script") {
47287
47310
  return await this.handleModifyScript(answerable);
47288
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
+ }
47289
47320
  if (headless !== "refresh_class_cache") {
47290
47321
  return await this.headless(headless, answerable);
47291
47322
  }
@@ -47541,12 +47572,23 @@ class GodotServer {
47541
47572
  if (!project.ok) {
47542
47573
  return project.response;
47543
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
+ }
47544
47586
  const located = resolveWithinProject(project.value.path, readString(args, "scriptPath") ?? "");
47545
47587
  if (!located.ok) {
47546
47588
  return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
47547
47589
  }
47548
47590
  const session = this.dapHoldingBreakpointsOf(project.value.path);
47549
- const scriptPath = realPathOr(located.absolutePath);
47591
+ const scriptPath = realAsFarAsItExists(located.absolutePath);
47550
47592
  const line = readPositiveNumber(args, "line");
47551
47593
  if (line === undefined) {
47552
47594
  return this.createErrorResponse("line is required: the one-based line to stop on.");
@@ -47574,10 +47616,13 @@ class GodotServer {
47574
47616
  } else {
47575
47617
  if (op === "set") {
47576
47618
  session.holdBreakpoints([{ scriptPath, lines: [line] }]);
47619
+ answer = { set: true };
47577
47620
  } else {
47578
- 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
+ };
47579
47625
  }
47580
- answer = { [op === "set" ? "set" : "removed"]: true };
47581
47626
  }
47582
47627
  const held = session.breakpointsHeld();
47583
47628
  writeBreakpointNote(project.value.path, held);
@@ -47599,7 +47644,7 @@ class GodotServer {
47599
47644
  } : {
47600
47645
  runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
47601
47646
  },
47602
- 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.",
47603
47648
  ...editor ? this.breakpointsAtRisk() : {}
47604
47649
  });
47605
47650
  }
@@ -47642,7 +47687,7 @@ class GodotServer {
47642
47687
  if (path === undefined) {
47643
47688
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
47644
47689
  }
47645
- const file = join34(path, "project.godot");
47690
+ const file = join35(path, "project.godot");
47646
47691
  if (!existsSync27(file)) {
47647
47692
  return {
47648
47693
  ok: false,
@@ -48348,7 +48393,7 @@ class GodotServer {
48348
48393
  const name = application["config/name"];
48349
48394
  const mainScene = application["run/main_scene"];
48350
48395
  const info = {
48351
- name: typeof name === "string" && name !== "" ? name : basename11(project.value.path),
48396
+ name: typeof name === "string" && name !== "" ? name : basename12(project.value.path),
48352
48397
  path: project.value.path,
48353
48398
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
48354
48399
  godotVersion: await this.godotVersion(engine.value),
@@ -48466,7 +48511,7 @@ class GodotServer {
48466
48511
  const exportArgs = [
48467
48512
  "--headless",
48468
48513
  "--log-file",
48469
- join34(exportLogs, "engine.log"),
48514
+ join35(exportLogs, "engine.log"),
48470
48515
  "--path",
48471
48516
  project.value.path,
48472
48517
  debug ? "--export-debug" : "--export-release",
@@ -48503,7 +48548,7 @@ class GodotServer {
48503
48548
  if (ending.exitSignal === null && ending.exitCode === null) {
48504
48549
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
48505
48550
  }
48506
- 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("\\", "/"));
48507
48552
  return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
48508
48553
  }
48509
48554
  reportTestProgress(log) {
@@ -48557,10 +48602,10 @@ class GodotServer {
48557
48602
  if (movie !== null) {
48558
48603
  return this.createErrorResponse(movie);
48559
48604
  }
48560
- 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))));
48561
48606
  const asked = paths.value.filter((path) => !missing.includes(path));
48562
48607
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
48563
- const noRunner = !existsSync27(join34(project.value.path, runner));
48608
+ const noRunner = !existsSync27(join35(project.value.path, runner));
48564
48609
  if (asked.length === 0) {
48565
48610
  const verdict = `nothing at ${missing.join(" and ")}`;
48566
48611
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -48680,14 +48725,14 @@ class GodotServer {
48680
48725
  await run.settled;
48681
48726
  const tookMs = Date.now() - began;
48682
48727
  const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
48683
- const reportsDir = join34(project.value.path, ".godot", ...ours.split("/"));
48728
+ const reportsDir = join35(project.value.path, ".godot", ...ours.split("/"));
48684
48729
  let report = null;
48685
48730
  let reportProblem = null;
48686
48731
  try {
48687
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))) : [];
48688
48733
  const newest = written.at(-1);
48689
48734
  if (newest !== undefined) {
48690
- report = parseJUnit(readFileSync29(join34(reportsDir, newest, "results.xml"), "utf8"));
48735
+ report = parseJUnit(readFileSync29(join35(reportsDir, newest, "results.xml"), "utf8"));
48691
48736
  }
48692
48737
  } catch (error) {
48693
48738
  reportProblem = errorMessage(error);
@@ -48815,7 +48860,7 @@ class GodotServer {
48815
48860
  const listedAsUncaught = new Set(uncaughtFolded.map((entry) => entry.index));
48816
48861
  const notApplied = ignoredYetRan(report, ignored);
48817
48862
  const testsNotRun = [
48818
- ...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}`))
48819
48864
  ];
48820
48865
  return this.jsonTextResponse({
48821
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,
@@ -49330,7 +49375,7 @@ class GodotServer {
49330
49375
  ...Array.isArray(stack) ? {
49331
49376
  stack: stack.map((frame) => ({
49332
49377
  ...frame,
49333
- ...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)) } : {}
49334
49379
  }))
49335
49380
  } : {}
49336
49381
  });
@@ -49393,7 +49438,7 @@ class GodotServer {
49393
49438
  const launchedIsUp = !status.connected && launchedWasUp;
49394
49439
  const arriving = status.connected ? [] : arrivingFound;
49395
49440
  const isPortConflict = this.bridgeStartupError?.includes("EADDRINUSE") ?? false;
49396
- const greeted = hasSaidWhoItIs(status);
49441
+ const greeted = status.connected;
49397
49442
  const staleNote = this.godotBridge.anotherRelease();
49398
49443
  const unfinished = this.restartLeftUnfinished(status.connected);
49399
49444
  const cannotConnect = status.connected || this.ownProject === null ? null : editorAddonMissing(this.ownProject);
@@ -49401,7 +49446,7 @@ class GodotServer {
49401
49446
  ...status,
49402
49447
  serverVersion: SERVER_VERSION,
49403
49448
  addonIsStale: greeted ? staleNote !== undefined : undefined,
49404
- 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,
49405
49450
  bridgeAvailable: this.bridgeStartupError === null && status.port !== null,
49406
49451
  mayYetConnect: status.connected ? undefined : cannotConnect !== null ? false : arriving.length > 0 ? true : unfinished !== null ? unfinished.underway : this.closedHere !== null ? false : anEditorIsStillComing(status.listeningSince, launchedIsUp),
49407
49452
  editorsNotYetConnected: arriving.length > 0 ? arriving : undefined,
@@ -49609,21 +49654,21 @@ class GodotServer {
49609
49654
  return null;
49610
49655
  }
49611
49656
  try {
49612
- return settingKeys(readFileSync29(join34(projectPath, "project.godot"), "utf8"));
49657
+ return settingKeys(readFileSync29(join35(projectPath, "project.godot"), "utf8"));
49613
49658
  } catch {
49614
49659
  return null;
49615
49660
  }
49616
49661
  }
49617
49662
  async handleRestartEditor(args) {
49618
49663
  const before = this.godotBridge.getStatus();
49619
- if (!before.connected) {
49664
+ if (!before.connected && before.identifying !== true) {
49620
49665
  return this.createErrorResponse("No editor is connected, so there is nothing to restart.", [
49621
49666
  "editor_launch opens one on a project",
49622
49667
  "editor_status says whether the bridge is up and what has reached it"
49623
49668
  ]);
49624
49669
  }
49625
- if (!hasSaidWhoItIs(before)) {
49626
- 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"]);
49627
49672
  }
49628
49673
  const mine = before.openedByAServer === true && before.projectPath !== undefined;
49629
49674
  if (!mine && args["hidden"] === true) {
@@ -49665,7 +49710,7 @@ class GodotServer {
49665
49710
  }
49666
49711
  async handleCloseEditor() {
49667
49712
  const before = this.godotBridge.getStatus();
49668
- if (!before.connected) {
49713
+ if (!before.connected && before.identifying !== true) {
49669
49714
  const launched = this.launchedEditor;
49670
49715
  if (launched !== null && await this.launchedEditorIsUp() && !this.godotBridge.isConnected()) {
49671
49716
  return await this.stopTheLaunchedEditor(launched);
@@ -49674,8 +49719,8 @@ class GodotServer {
49674
49719
  "editor_status says whether the bridge is up and what has reached it"
49675
49720
  ]);
49676
49721
  }
49677
- if (!hasSaidWhoItIs(before) || before.editorPid === undefined) {
49678
- 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"]);
49679
49724
  }
49680
49725
  const pid = before.editorPid;
49681
49726
  const settingsBefore = this.settingKeysOf(before.projectPath);
@@ -49731,7 +49776,7 @@ class GodotServer {
49731
49776
  settleTheLaunchReading() {
49732
49777
  const watched = this.launchedFrom;
49733
49778
  const status = this.godotBridge.getStatus();
49734
- if (watched === null || !hasSaidWhoItIs(status)) {
49779
+ if (watched === null || !status.connected) {
49735
49780
  return;
49736
49781
  }
49737
49782
  this.launchedFrom = null;
@@ -50051,7 +50096,7 @@ class GodotServer {
50051
50096
  }
50052
50097
  async runtimeUp(projectPath, before, budget, withArgs) {
50053
50098
  const budgetMs = budget.ms;
50054
- if (!existsSync27(join34(projectPath, RUNTIME_AUTOLOAD.path))) {
50099
+ if (!existsSync27(join35(projectPath, RUNTIME_AUTOLOAD.path))) {
50055
50100
  return runtimeVerdict(null, {
50056
50101
  addon: false,
50057
50102
  budgetMs,
@@ -51028,7 +51073,7 @@ class GodotServer {
51028
51073
  };
51029
51074
  const going = await this.runStillGoing(run);
51030
51075
  const projectPath = run.projectPath;
51031
- const projectGone = projectPath !== null && !existsSync27(join34(projectPath, "project.godot"));
51076
+ const projectGone = projectPath !== null && !existsSync27(join35(projectPath, "project.godot"));
51032
51077
  if (projectGone && callersName !== undefined) {
51033
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"]);
51034
51079
  }
@@ -51253,10 +51298,10 @@ class GodotServer {
51253
51298
  }
51254
51299
  async handleEditorConsole(args) {
51255
51300
  const status = this.godotBridge.getStatus();
51256
- const greeted = hasSaidWhoItIs(status);
51301
+ const greeted = status.connected;
51257
51302
  const launched = greeted ? null : this.launchedEditor?.pid ?? null;
51258
- if (!greeted && launched === null && status.connected) {
51259
- 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"]);
51260
51305
  }
51261
51306
  const projectPath = status.projectPath ?? this.launchedEditor?.projectPath ?? this.ownProject ?? undefined;
51262
51307
  const elsewhere = this.anotherProjectNamed(args, projectPath);
@@ -51375,7 +51420,7 @@ class GodotServer {
51375
51420
  notes.push(endedWithoutACode({
51376
51421
  throughEditor: run.throughEditor,
51377
51422
  announced: announced !== undefined || run.announcedPid !== undefined,
51378
- couldAnnounce: run.projectPath !== null && existsSync27(join34(run.projectPath, RUNTIME_AUTOLOAD.path)),
51423
+ couldAnnounce: run.projectPath !== null && existsSync27(join35(run.projectPath, RUNTIME_AUTOLOAD.path)),
51379
51424
  endedHere: typeof run.endedHere === "string",
51380
51425
  pickedUp: run.pickedUp === true
51381
51426
  }));
@@ -52465,7 +52510,7 @@ class GodotServer {
52465
52510
  const launched = this.launchedEditor;
52466
52511
  if (launched !== null && await this.launchedEditorIsUp()) {
52467
52512
  const began = Date.now();
52468
- await this.waitForBridge(() => hasSaidWhoItIs(this.godotBridge.getStatus()) || !alive(launched.pid), began + editorRestartTimeoutMs());
52513
+ await this.waitForBridge(() => this.godotBridge.isConnected() || !alive(launched.pid), began + editorRestartTimeoutMs());
52469
52514
  if (this.godotBridge.isConnected()) {
52470
52515
  return null;
52471
52516
  }
@@ -52584,7 +52629,7 @@ class GodotServer {
52584
52629
  async ownRunOnItsWay(projectPath, budgetMs) {
52585
52630
  const run = await this.currentRun();
52586
52631
  const project = projectPath ?? run?.projectPath ?? null;
52587
- 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)) {
52588
52633
  return null;
52589
52634
  }
52590
52635
  const already = runtimesAnnounced().running;
@@ -52678,7 +52723,7 @@ class GodotServer {
52678
52723
  return this.createErrorResponse(staleNote);
52679
52724
  }
52680
52725
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
52681
- const screenshotPath = screenshotDir ? join34(screenshotDir, "capture.png") : null;
52726
+ const screenshotPath = screenshotDir ? join35(screenshotDir, "capture.png") : null;
52682
52727
  const refused = (message) => this.createErrorResponse(message);
52683
52728
  const onGame = {
52684
52729
  pid: choice.endpoint.pid,
@@ -52981,12 +53026,12 @@ init_bridge_announce();
52981
53026
  init_errors();
52982
53027
  init_godot_path();
52983
53028
  import { existsSync as existsSync28 } from "node:fs";
52984
- import { join as join35, resolve as resolve8 } from "node:path";
53029
+ import { join as join36, resolve as resolve8 } from "node:path";
52985
53030
 
52986
53031
  // src/harnesses.ts
52987
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";
52988
53033
  import { homedir as homedir2 } from "node:os";
52989
- 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";
52990
53035
  import process4 from "node:process";
52991
53036
 
52992
53037
  // node_modules/yaml/dist/index.js
@@ -53160,7 +53205,7 @@ var HARNESSES = [
53160
53205
  shape: "plain",
53161
53206
  home: ".claude",
53162
53207
  marker: ".claude",
53163
- skills: { dir: join4(".claude", "skills"), shared: false }
53208
+ skills: { dir: join5(".claude", "skills"), shared: false }
53164
53209
  },
53165
53210
  {
53166
53211
  id: "copilot-cli",
@@ -53170,7 +53215,7 @@ var HARNESSES = [
53170
53215
  container: "mcpServers",
53171
53216
  shape: "plain",
53172
53217
  home: ".copilot",
53173
- skills: { dir: join4(".github", "skills"), shared: true },
53218
+ skills: { dir: join5(".github", "skills"), shared: true },
53174
53219
  manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
53175
53220
  },
53176
53221
  {
@@ -53196,22 +53241,22 @@ var HARNESSES = [
53196
53241
  id: "cursor",
53197
53242
  name: "Cursor",
53198
53243
  scope: "project",
53199
- file: join4(".cursor", "mcp.json"),
53244
+ file: join5(".cursor", "mcp.json"),
53200
53245
  container: "mcpServers",
53201
53246
  shape: "plain",
53202
53247
  home: ".cursor",
53203
- skills: { dir: join4(".cursor", "skills"), shared: true },
53248
+ skills: { dir: join5(".cursor", "skills"), shared: true },
53204
53249
  manual: "Cursor asks before each tool call by default, and the server can be toggled off."
53205
53250
  },
53206
53251
  {
53207
53252
  id: "vscode",
53208
53253
  name: "VS Code",
53209
53254
  scope: "project",
53210
- file: join4(".vscode", "mcp.json"),
53255
+ file: join5(".vscode", "mcp.json"),
53211
53256
  container: "servers",
53212
53257
  shape: "typed",
53213
53258
  home: ".vscode",
53214
- skills: { dir: join4(".github", "skills"), shared: true },
53259
+ skills: { dir: join5(".github", "skills"), shared: true },
53215
53260
  manual: "VS Code asks you to trust a server the first time it starts one."
53216
53261
  },
53217
53262
  {
@@ -53221,45 +53266,45 @@ var HARNESSES = [
53221
53266
  file: "opencode.json",
53222
53267
  container: "mcp",
53223
53268
  shape: "opencode",
53224
- home: join4(".config", "opencode"),
53269
+ home: join5(".config", "opencode"),
53225
53270
  marker: ".opencode",
53226
- skills: { dir: join4(".opencode", "skills"), shared: true }
53271
+ skills: { dir: join5(".opencode", "skills"), shared: true }
53227
53272
  },
53228
53273
  {
53229
53274
  id: "junie",
53230
53275
  name: "Junie",
53231
53276
  scope: "project",
53232
- file: join4(".junie", "mcp", "mcp.json"),
53277
+ file: join5(".junie", "mcp", "mcp.json"),
53233
53278
  container: "mcpServers",
53234
53279
  shape: "plain",
53235
53280
  home: ".junie",
53236
- skills: { dir: join4(".junie", "skills"), shared: true }
53281
+ skills: { dir: join5(".junie", "skills"), shared: true }
53237
53282
  },
53238
53283
  {
53239
53284
  id: "kiro",
53240
53285
  name: "Kiro",
53241
53286
  scope: "project",
53242
- file: join4(".kiro", "settings", "mcp.json"),
53287
+ file: join5(".kiro", "settings", "mcp.json"),
53243
53288
  container: "mcpServers",
53244
53289
  shape: "plain",
53245
53290
  home: ".kiro",
53246
- skills: { dir: join4(".kiro", "skills"), shared: false }
53291
+ skills: { dir: join5(".kiro", "skills"), shared: false }
53247
53292
  },
53248
53293
  {
53249
53294
  id: "gemini",
53250
53295
  name: "Gemini CLI",
53251
53296
  scope: "project",
53252
- file: join4(".gemini", "settings.json"),
53297
+ file: join5(".gemini", "settings.json"),
53253
53298
  container: "mcpServers",
53254
53299
  shape: "plain",
53255
53300
  home: ".gemini",
53256
- skills: { dir: join4(".gemini", "skills"), shared: true }
53301
+ skills: { dir: join5(".gemini", "skills"), shared: true }
53257
53302
  },
53258
53303
  {
53259
53304
  id: "roo",
53260
53305
  name: "Roo Code",
53261
53306
  scope: "project",
53262
- file: join4(".roo", "mcp.json"),
53307
+ file: join5(".roo", "mcp.json"),
53263
53308
  container: "mcpServers",
53264
53309
  shape: "plain"
53265
53310
  },
@@ -53267,7 +53312,7 @@ var HARNESSES = [
53267
53312
  id: "kilo",
53268
53313
  name: "Kilo Code",
53269
53314
  scope: "project",
53270
- file: join4(".kilocode", "mcp.json"),
53315
+ file: join5(".kilocode", "mcp.json"),
53271
53316
  container: "mcpServers",
53272
53317
  shape: "plain"
53273
53318
  },
@@ -53275,16 +53320,16 @@ var HARNESSES = [
53275
53320
  id: "amazon-q",
53276
53321
  name: "Amazon Q Developer CLI",
53277
53322
  scope: "project",
53278
- file: join4(".amazonq", "mcp.json"),
53323
+ file: join5(".amazonq", "mcp.json"),
53279
53324
  container: "mcpServers",
53280
53325
  shape: "plain",
53281
- home: join4(".aws", "amazonq")
53326
+ home: join5(".aws", "amazonq")
53282
53327
  },
53283
53328
  {
53284
53329
  id: "zed",
53285
53330
  name: "Zed",
53286
53331
  scope: "project",
53287
- file: join4(".zed", "settings.json"),
53332
+ file: join5(".zed", "settings.json"),
53288
53333
  container: "context_servers",
53289
53334
  shape: "plain"
53290
53335
  },
@@ -53292,16 +53337,16 @@ var HARNESSES = [
53292
53337
  id: "amp",
53293
53338
  name: "Amp",
53294
53339
  scope: "project",
53295
- file: join4(".amp", "settings.json"),
53340
+ file: join5(".amp", "settings.json"),
53296
53341
  container: "amp.mcpServers",
53297
53342
  shape: "plain",
53298
- home: join4(".config", "amp")
53343
+ home: join5(".config", "amp")
53299
53344
  },
53300
53345
  {
53301
53346
  id: "warp",
53302
53347
  name: "Warp",
53303
53348
  scope: "project",
53304
- file: join4(".warp", ".mcp.json"),
53349
+ file: join5(".warp", ".mcp.json"),
53305
53350
  container: "mcpServers",
53306
53351
  shape: "plain",
53307
53352
  home: ".warp",
@@ -53311,7 +53356,7 @@ var HARNESSES = [
53311
53356
  id: "trae",
53312
53357
  name: "Trae",
53313
53358
  scope: "project",
53314
- file: join4(".trae", "mcp.json"),
53359
+ file: join5(".trae", "mcp.json"),
53315
53360
  container: "mcpServers",
53316
53361
  shape: "plain",
53317
53362
  home: ".trae"
@@ -53320,7 +53365,7 @@ var HARNESSES = [
53320
53365
  id: "factory",
53321
53366
  name: "Factory Droid",
53322
53367
  scope: "project",
53323
- file: join4(".factory", "mcp.json"),
53368
+ file: join5(".factory", "mcp.json"),
53324
53369
  container: "mcpServers",
53325
53370
  shape: "typed",
53326
53371
  home: ".factory"
@@ -53329,7 +53374,7 @@ var HARNESSES = [
53329
53374
  id: "tabnine",
53330
53375
  name: "Tabnine",
53331
53376
  scope: "project",
53332
- file: join4(".tabnine", "mcp_servers.json"),
53377
+ file: join5(".tabnine", "mcp_servers.json"),
53333
53378
  container: "mcpServers",
53334
53379
  shape: "plain",
53335
53380
  home: ".tabnine"
@@ -53346,7 +53391,7 @@ var HARNESSES = [
53346
53391
  id: "pi",
53347
53392
  name: "pi",
53348
53393
  scope: "project",
53349
- file: join4(".pi", "mcp.json"),
53394
+ file: join5(".pi", "mcp.json"),
53350
53395
  container: "mcpServers",
53351
53396
  shape: "plain",
53352
53397
  home: ".pi"
@@ -53355,7 +53400,7 @@ var HARNESSES = [
53355
53400
  id: "mistral-vibe",
53356
53401
  name: "Mistral Vibe",
53357
53402
  scope: "project",
53358
- file: join4(".vibe", "config.toml"),
53403
+ file: join5(".vibe", "config.toml"),
53359
53404
  container: "mcp_servers",
53360
53405
  shape: "plain",
53361
53406
  toml: {
@@ -53411,7 +53456,7 @@ var HARNESSES = [
53411
53456
  id: "codex",
53412
53457
  name: "Codex CLI",
53413
53458
  scope: "home",
53414
- file: join4(".codex", "config.toml"),
53459
+ file: join5(".codex", "config.toml"),
53415
53460
  container: "mcp_servers",
53416
53461
  shape: "plain",
53417
53462
  manual: "Codex reads its config at startup, so restart it.",
@@ -53430,20 +53475,20 @@ var HARNESSES = [
53430
53475
  id: "cline",
53431
53476
  name: "Cline",
53432
53477
  scope: "home",
53433
- file: join4(".cline", "data", "settings", "cline_mcp_settings.json"),
53478
+ file: join5(".cline", "data", "settings", "cline_mcp_settings.json"),
53434
53479
  container: "mcpServers",
53435
53480
  shape: "plain",
53436
53481
  marker: ".cline",
53437
- skills: { dir: join4(".cline", "skills"), shared: false }
53482
+ skills: { dir: join5(".cline", "skills"), shared: false }
53438
53483
  },
53439
53484
  {
53440
53485
  id: "goose",
53441
53486
  name: "Goose",
53442
53487
  scope: "home",
53443
53488
  file: {
53444
- win32: { appData: join4("Block", "goose", "config", "config.yaml") },
53445
- darwin: join4(".config", "goose", "config.yaml"),
53446
- 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")
53447
53492
  },
53448
53493
  container: "extensions",
53449
53494
  shape: "plain",
@@ -53464,19 +53509,19 @@ var HARNESSES = [
53464
53509
  id: "windsurf",
53465
53510
  name: "Windsurf",
53466
53511
  scope: "home",
53467
- file: join4(".codeium", "windsurf", "mcp_config.json"),
53512
+ file: join5(".codeium", "windsurf", "mcp_config.json"),
53468
53513
  container: "mcpServers",
53469
53514
  shape: "plain",
53470
- skills: { dir: join4(".windsurf", "skills"), shared: true }
53515
+ skills: { dir: join5(".windsurf", "skills"), shared: true }
53471
53516
  },
53472
53517
  {
53473
53518
  id: "hermes",
53474
53519
  name: "Hermes",
53475
53520
  scope: "home",
53476
- file: join4(".hermes", "config.yaml"),
53521
+ file: join5(".hermes", "config.yaml"),
53477
53522
  container: "mcp_servers",
53478
53523
  shape: "plain",
53479
- skills: { dir: join4(".hermes", "skills"), shared: true },
53524
+ skills: { dir: join5(".hermes", "skills"), shared: true },
53480
53525
  yaml: {
53481
53526
  path: ["mcp_servers", SERVER_KEY],
53482
53527
  entry: (launch) => ({
@@ -53491,7 +53536,7 @@ var HARNESSES = [
53491
53536
  id: "openclaw",
53492
53537
  name: "OpenClaw",
53493
53538
  scope: "home",
53494
- file: join4(".openclaw", "openclaw.json"),
53539
+ file: join5(".openclaw", "openclaw.json"),
53495
53540
  container: "mcpServers",
53496
53541
  shape: "plain"
53497
53542
  },
@@ -53500,9 +53545,9 @@ var HARNESSES = [
53500
53545
  name: "Claude Desktop",
53501
53546
  scope: "home",
53502
53547
  file: {
53503
- win32: { appData: join4("Claude", "claude_desktop_config.json") },
53504
- darwin: join4("Library", "Application Support", "Claude", "claude_desktop_config.json"),
53505
- 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")
53506
53551
  },
53507
53552
  container: "mcpServers",
53508
53553
  shape: "plain"
@@ -53511,7 +53556,7 @@ var HARNESSES = [
53511
53556
  id: "zeroclaw",
53512
53557
  name: "ZeroClaw",
53513
53558
  scope: "home",
53514
- file: join4(".zeroclaw", "config.toml"),
53559
+ file: join5(".zeroclaw", "config.toml"),
53515
53560
  container: "mcp.servers",
53516
53561
  shape: "plain",
53517
53562
  toml: {
@@ -53532,7 +53577,7 @@ var HARNESSES = [
53532
53577
  id: "deepcode",
53533
53578
  name: "Deep Code",
53534
53579
  scope: "home",
53535
- file: join4(".deepcode", "settings.json"),
53580
+ file: join5(".deepcode", "settings.json"),
53536
53581
  container: "mcpServers",
53537
53582
  shape: "plain"
53538
53583
  },
@@ -53540,7 +53585,7 @@ var HARNESSES = [
53540
53585
  id: "nanobot",
53541
53586
  name: "nanobot",
53542
53587
  scope: "home",
53543
- file: join4(".nanobot", "config.json"),
53588
+ file: join5(".nanobot", "config.json"),
53544
53589
  container: "mcpServers",
53545
53590
  shape: "plain",
53546
53591
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -53549,17 +53594,17 @@ var HARNESSES = [
53549
53594
  id: "autohand",
53550
53595
  name: "Autohand",
53551
53596
  scope: "home",
53552
- file: join4(".autohand", "config.json"),
53597
+ file: join5(".autohand", "config.json"),
53553
53598
  container: "mcpServers",
53554
53599
  shape: "plain",
53555
53600
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
53556
53601
  }
53557
53602
  ];
53558
53603
  function appDataRoot() {
53559
- return process4.env["APPDATA"] ?? join4(homedir2(), "AppData", "Roaming");
53604
+ return process4.env["APPDATA"] ?? join5(homedir2(), "AppData", "Roaming");
53560
53605
  }
53561
53606
  function homePath(path) {
53562
- return typeof path === "string" ? join4(homedir2(), path) : join4(appDataRoot(), path.appData);
53607
+ return typeof path === "string" ? join5(homedir2(), path) : join5(appDataRoot(), path.appData);
53563
53608
  }
53564
53609
  function fileFor(harness, platform = process4.platform) {
53565
53610
  if (typeof harness.file === "string") {
@@ -53570,7 +53615,7 @@ function fileFor(harness, platform = process4.platform) {
53570
53615
  function configPath(harness, projectPath) {
53571
53616
  const file = fileFor(harness);
53572
53617
  if (harness.scope === "project") {
53573
- return join4(projectPath, file);
53618
+ return join5(projectPath, file);
53574
53619
  }
53575
53620
  return homePath(file);
53576
53621
  }
@@ -53629,11 +53674,11 @@ function present(harness, projectPath) {
53629
53674
  return true;
53630
53675
  }
53631
53676
  const root = harness.scope === "project" ? projectPath : homedir2();
53632
- const own = dirname3(path);
53677
+ const own = dirname4(path);
53633
53678
  if (own !== root && existsSync4(own)) {
53634
53679
  return true;
53635
53680
  }
53636
- return harness.marker !== undefined && existsSync4(join4(root, harness.marker));
53681
+ return harness.marker !== undefined && existsSync4(join5(root, harness.marker));
53637
53682
  }
53638
53683
  function candidates(projectPath) {
53639
53684
  const found = [];
@@ -53646,7 +53691,7 @@ function candidates(projectPath) {
53646
53691
  }
53647
53692
  if (present(harness, projectPath)) {
53648
53693
  found.push({ harness, reason: "configured" });
53649
- } else if (harness.home !== undefined && existsSync4(join4(homedir2(), harness.home))) {
53694
+ } else if (harness.home !== undefined && existsSync4(join5(homedir2(), harness.home))) {
53650
53695
  found.push({ harness, reason: "installed" });
53651
53696
  }
53652
53697
  }
@@ -53704,14 +53749,14 @@ function recordedEnginePath(projectPath) {
53704
53749
  }
53705
53750
  function mainCheckoutOf(projectPath) {
53706
53751
  let top = resolve2(projectPath);
53707
- while (!existsSync4(join4(top, ".git"))) {
53708
- const up = dirname3(top);
53752
+ while (!existsSync4(join5(top, ".git"))) {
53753
+ const up = dirname4(top);
53709
53754
  if (up === top) {
53710
53755
  return null;
53711
53756
  }
53712
53757
  top = up;
53713
53758
  }
53714
- const marker = join4(top, ".git");
53759
+ const marker = join5(top, ".git");
53715
53760
  if (!statSync3(marker).isFile()) {
53716
53761
  return null;
53717
53762
  }
@@ -53720,39 +53765,39 @@ function mainCheckoutOf(projectPath) {
53720
53765
  return null;
53721
53766
  }
53722
53767
  const entry = resolve2(top, pointer);
53723
- const commonFile = join4(entry, "commondir");
53768
+ const commonFile = join5(entry, "commondir");
53724
53769
  if (!existsSync4(commonFile)) {
53725
53770
  return null;
53726
53771
  }
53727
53772
  const common = resolve2(entry, readFileSync2(commonFile, "utf8").trim());
53728
- if (basename2(common) !== ".git") {
53773
+ if (basename3(common) !== ".git") {
53729
53774
  return null;
53730
53775
  }
53731
- const main = join4(dirname3(common), relative2(top, resolve2(projectPath)));
53732
- 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;
53733
53778
  }
53734
53779
  function worktreeCheckoutsOf(projectPath) {
53735
53780
  let top = resolve2(projectPath);
53736
- while (!existsSync4(join4(top, ".git"))) {
53737
- const up = dirname3(top);
53781
+ while (!existsSync4(join5(top, ".git"))) {
53782
+ const up = dirname4(top);
53738
53783
  if (up === top) {
53739
53784
  return [];
53740
53785
  }
53741
53786
  top = up;
53742
53787
  }
53743
- const listed = join4(top, ".git", "worktrees");
53744
- if (!statSync3(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
53788
+ const listed = join5(top, ".git", "worktrees");
53789
+ if (!statSync3(join5(top, ".git")).isDirectory() || !existsSync4(listed)) {
53745
53790
  return [];
53746
53791
  }
53747
53792
  const below = relative2(top, resolve2(projectPath));
53748
53793
  return readdirSync2(listed).flatMap((name) => {
53749
- const pointer = join4(listed, name, "gitdir");
53794
+ const pointer = join5(listed, name, "gitdir");
53750
53795
  if (!existsSync4(pointer)) {
53751
53796
  return [];
53752
53797
  }
53753
- const checkout = dirname3(resolve2(listed, name, readFileSync2(pointer, "utf8").trim()));
53754
- const project = join4(checkout, below);
53755
- 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] : [];
53756
53801
  });
53757
53802
  }
53758
53803
  function entryEnvironment(harness, projectPath) {
@@ -53894,7 +53939,7 @@ function connect(harness, projectPath, launch) {
53894
53939
  return was === undefined || was === now ? written : { ...written, wasLaunchedBy: was };
53895
53940
  }
53896
53941
  function put(harness, path, contents, already) {
53897
- mkdirSync2(dirname3(path), { recursive: true });
53942
+ mkdirSync2(dirname4(path), { recursive: true });
53898
53943
  writeFileSync2(path, contents, "utf8");
53899
53944
  return { harness, path, action: already ? "replaced" : "written" };
53900
53945
  }
@@ -53947,9 +53992,9 @@ init_setup();
53947
53992
  init_tool_definitions();
53948
53993
  init_tool_reference();
53949
53994
  import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
53950
- 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";
53951
53996
  var SKILL_NAME = "gdharness";
53952
- var SHARED_SKILLS = join15(".agents", "skills");
53997
+ var SHARED_SKILLS = join16(".agents", "skills");
53953
53998
  function skillMarkdown(version) {
53954
53999
  return `---
53955
54000
  name: ${SKILL_NAME}
@@ -54181,27 +54226,27 @@ what they report while starting comes back under \`engine_messages_before_operat
54181
54226
  function skillFiles(version) {
54182
54227
  return new Map([
54183
54228
  ["SKILL.md", skillMarkdown(version)],
54184
- [join15("references", "tools.md"), renderToolsMarkdown()]
54229
+ [join16("references", "tools.md"), renderToolsMarkdown()]
54185
54230
  ]);
54186
54231
  }
54187
54232
  function skillDirectories(harnesses, projectPath, exists) {
54188
- const directories = [join15(projectPath, SHARED_SKILLS, SKILL_NAME)];
54233
+ const directories = [join16(projectPath, SHARED_SKILLS, SKILL_NAME)];
54189
54234
  for (const harness of harnesses) {
54190
54235
  if (harness.skills === undefined) {
54191
54236
  continue;
54192
54237
  }
54193
- const own = join15(projectPath, harness.skills.dir);
54194
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join15(own, SKILL_NAME))) {
54195
- 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));
54196
54241
  }
54197
54242
  }
54198
54243
  return directories;
54199
54244
  }
54200
54245
  function everySkillDirectory(projectPath, harnesses) {
54201
- const directories = new Set([join15(projectPath, SHARED_SKILLS, SKILL_NAME)]);
54246
+ const directories = new Set([join16(projectPath, SHARED_SKILLS, SKILL_NAME)]);
54202
54247
  for (const harness of harnesses) {
54203
54248
  if (harness.skills !== undefined) {
54204
- directories.add(join15(projectPath, harness.skills.dir, SKILL_NAME));
54249
+ directories.add(join16(projectPath, harness.skills.dir, SKILL_NAME));
54205
54250
  }
54206
54251
  }
54207
54252
  return [...directories];
@@ -54211,21 +54256,21 @@ function removeSkill(directories, projectPath) {
54211
54256
  for (const directory of directories) {
54212
54257
  if (existsSync14(directory)) {
54213
54258
  rmSync6(directory, { recursive: true, force: true });
54214
- pruneEmpty(dirname8(directory), projectPath);
54259
+ pruneEmpty(dirname9(directory), projectPath);
54215
54260
  removed.push(directory);
54216
54261
  }
54217
54262
  }
54218
54263
  return removed;
54219
54264
  }
54220
54265
  function pruneEmpty(directory, projectPath) {
54221
- const shared = join15(projectPath, ".agents");
54266
+ const shared = join16(projectPath, ".agents");
54222
54267
  let path = directory;
54223
- while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
54268
+ while (path !== projectPath && (basename7(path) === "skills" || path === shared)) {
54224
54269
  if (readdirSync9(path).length > 0) {
54225
54270
  return;
54226
54271
  }
54227
54272
  rmSync6(path, { recursive: true, force: true });
54228
- path = dirname8(path);
54273
+ path = dirname9(path);
54229
54274
  }
54230
54275
  }
54231
54276
  function writeSkill(directories, version) {
@@ -54234,8 +54279,8 @@ function writeSkill(directories, version) {
54234
54279
  const replaced = existsSync14(directory);
54235
54280
  rmSync6(directory, { recursive: true, force: true });
54236
54281
  for (const [name, contents] of files) {
54237
- const path = join15(directory, name);
54238
- mkdirSync5(dirname8(path), { recursive: true });
54282
+ const path = join16(directory, name);
54283
+ mkdirSync5(dirname9(path), { recursive: true });
54239
54284
  writeFileSync7(path, contents, "utf8");
54240
54285
  }
54241
54286
  return { path: directory, replaced };
@@ -54244,13 +54289,13 @@ function writeSkill(directories, version) {
54244
54289
 
54245
54290
  // src/version.ts
54246
54291
  import { readFileSync as readFileSync12 } from "node:fs";
54247
- import { dirname as dirname9, join as join16 } from "node:path";
54292
+ import { dirname as dirname10, join as join17 } from "node:path";
54248
54293
  import { fileURLToPath as fileURLToPath2 } from "node:url";
54249
54294
  function getLocalVersion() {
54250
- const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
54295
+ const currentDirectory = dirname10(fileURLToPath2(import.meta.url));
54251
54296
  const packageCandidates = [
54252
- join16(currentDirectory, "..", "package.json"),
54253
- join16(currentDirectory, "..", "..", "package.json")
54297
+ join17(currentDirectory, "..", "package.json"),
54298
+ join17(currentDirectory, "..", "..", "package.json")
54254
54299
  ];
54255
54300
  for (const packagePath of packageCandidates) {
54256
54301
  try {
@@ -54272,7 +54317,7 @@ class UsageError extends Refusal {
54272
54317
  function projectArgument(at) {
54273
54318
  const named = args.slice(at).find((value) => !value.startsWith("--"));
54274
54319
  const projectPath = resolve8(named ?? ".");
54275
- if (!existsSync28(join35(projectPath, "project.godot"))) {
54320
+ if (!existsSync28(join36(projectPath, "project.godot"))) {
54276
54321
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
54277
54322
  }
54278
54323
  return projectPath;
@@ -54317,7 +54362,7 @@ async function registerRuntime(godot, projectPath) {
54317
54362
  return;
54318
54363
  }
54319
54364
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
54320
- if (!existsSync28(join35(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
54365
+ if (!existsSync28(join36(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
54321
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}`);
54322
54367
  }
54323
54368
  }