gdharness 1.2.37 → 1.2.38

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
@@ -8181,6 +8181,9 @@ class GameLog {
8181
8181
  count(severity) {
8182
8182
  return this.entries.filter((entry) => entry.severity === severity).length;
8183
8183
  }
8184
+ crashReport() {
8185
+ return this.entries.find((entry) => CRASH.test(entry.text));
8186
+ }
8184
8187
  everything() {
8185
8188
  return this.entries;
8186
8189
  }
@@ -8260,21 +8263,98 @@ function runEngine(executable, args, options = {}) {
8260
8263
  });
8261
8264
  });
8262
8265
  }
8266
+ function exitCodeText(code) {
8267
+ if (code === null) {
8268
+ return "exit code unknown";
8269
+ }
8270
+ if (code < 3221225472) {
8271
+ return `exit code ${code}`;
8272
+ }
8273
+ const hex = `0x${code.toString(16).toUpperCase()}`;
8274
+ return `exit code ${hex}, ${WINDOWS_STATUSES.get(code) ?? "a Windows error status"}`;
8275
+ }
8263
8276
  function howItEnded(run) {
8264
8277
  if (run.failure !== null) {
8265
8278
  return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
8266
8279
  }
8267
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
8280
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}`;
8268
8281
  }
8282
+ var WINDOWS_STATUSES;
8269
8283
  var init_engine_run = __esm(() => {
8270
8284
  init_call_signal();
8271
8285
  init_game_log();
8272
8286
  init_spawn();
8287
+ WINDOWS_STATUSES = new Map([
8288
+ [3221225477, "an access violation, so it crashed"],
8289
+ [3221225501, "an illegal instruction, so it crashed"],
8290
+ [3221225620, "an integer division by zero, so it crashed"],
8291
+ [3221225725, "a stack overflow, so it crashed"],
8292
+ [3221225781, "a library it needs was not found, so it never started"],
8293
+ [3221225786, "its console was closed or sent Ctrl+C"],
8294
+ [3221226356, "a corrupted heap, so it crashed"],
8295
+ [3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
8296
+ ]);
8273
8297
  });
8274
8298
 
8275
- // src/library-copies.ts
8276
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8299
+ // src/doc-cache.ts
8300
+ import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
8301
+ import { homedir as homedir3 } from "node:os";
8277
8302
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
8303
+ import process5 from "node:process";
8304
+ function editorCacheDirectory(godotPath, platform = process5.platform, env = process5.env, home = homedir3()) {
8305
+ const beside = dirname4(godotPath);
8306
+ const bundle = platform === "darwin" && basename3(beside) === "MacOS" && basename3(dirname4(beside)) === "Contents" ? dirname4(dirname4(dirname4(beside))) : null;
8307
+ for (const folder of bundle === null ? [beside] : [beside, bundle]) {
8308
+ if (existsSync5(join5(folder, "._sc_")) || existsSync5(join5(folder, "_sc_"))) {
8309
+ return join5(folder, "editor_data", "cache");
8310
+ }
8311
+ }
8312
+ if (platform === "win32") {
8313
+ const set = (name) => env[name] === "" ? undefined : env[name];
8314
+ return join5(set("LOCALAPPDATA") ?? set("TEMP") ?? join5(home, "AppData", "Local"), "Godot");
8315
+ }
8316
+ if (platform === "darwin") {
8317
+ return join5(home, "Library", "Caches", "Godot");
8318
+ }
8319
+ const xdg = env["XDG_CACHE_HOME"];
8320
+ return join5(xdg !== undefined && isAbsolute2(xdg) ? xdg : join5(home, ".cache"), "godot");
8321
+ }
8322
+ function docCachesWritten(directory) {
8323
+ const written = new Map;
8324
+ let names;
8325
+ try {
8326
+ names = readdirSync3(directory);
8327
+ } catch {
8328
+ return written;
8329
+ }
8330
+ for (const name of names.filter((one) => CACHE_FILE.test(one))) {
8331
+ try {
8332
+ written.set(join5(directory, name), statSync4(join5(directory, name)).mtimeMs);
8333
+ } catch {}
8334
+ }
8335
+ return written;
8336
+ }
8337
+ function docCacheCrash(ran, before, after, what) {
8338
+ const report = ran.log.crashReport();
8339
+ if (ran.exitCode === 0 && ran.exitSignal === null && report === undefined || ran.failure !== null) {
8340
+ return null;
8341
+ }
8342
+ const rewritten = [...after].find(([path, at]) => before.get(path) !== at)?.[0];
8343
+ if (rewritten === undefined) {
8344
+ return null;
8345
+ }
8346
+ const said = report === undefined ? "" : `, and its crash handler printed "${report.text}"`;
8347
+ return `The first ${what} ended badly (${howItEnded(ran)}${said}) after rebuilding the editor's class reference ` + `cache, ${rewritten}. Godot crashes as an import or an export exits when it finds that cache ` + "stale, which it is whenever another build of the same minor version wrote it last, such as 4.7.1 " + `beside 4.7.2, and the crashing run writes a fresh one. So the ${what} was run again, and this answer ` + "is from that second run.";
8348
+ }
8349
+ var CACHE_FILE;
8350
+ var init_doc_cache = __esm(() => {
8351
+ init_engine_run();
8352
+ CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
8353
+ });
8354
+
8355
+ // src/library-copies.ts
8356
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8357
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute3, join as join6 } from "node:path";
8278
8358
  function freeLibraryCopies(projectPath, platform = process.platform) {
8279
8359
  const setAside = [];
8280
8360
  const swept = [];
@@ -8282,26 +8362,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8282
8362
  return { setAside, swept };
8283
8363
  }
8284
8364
  for (const library of windowsLibraries(projectPath)) {
8285
- const folder = dirname4(library);
8286
- const copy = `~${basename3(library)}`;
8287
- if (existsSync5(join5(folder, copy))) {
8288
- const aside = join5(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8365
+ const folder = dirname5(library);
8366
+ const copy = `~${basename4(library)}`;
8367
+ if (existsSync6(join6(folder, copy))) {
8368
+ const aside = join6(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8289
8369
  try {
8290
- renameSync(join5(folder, copy), aside);
8370
+ renameSync(join6(folder, copy), aside);
8291
8371
  setAside.push(aside);
8292
8372
  } catch {}
8293
8373
  }
8294
8374
  let names;
8295
8375
  try {
8296
- names = readdirSync3(folder);
8376
+ names = readdirSync4(folder);
8297
8377
  } catch {
8298
8378
  continue;
8299
8379
  }
8300
8380
  for (const name of names) {
8301
8381
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
8302
8382
  try {
8303
- unlinkSync(join5(folder, name));
8304
- swept.push(join5(folder, name));
8383
+ unlinkSync(join6(folder, name));
8384
+ swept.push(join6(folder, name));
8305
8385
  } catch {}
8306
8386
  }
8307
8387
  }
@@ -8309,7 +8389,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8309
8389
  return { setAside, swept };
8310
8390
  }
8311
8391
  function windowsLibraries(projectPath) {
8312
- const listed = readOrNull(join5(projectPath, ".godot", "extension_list.cfg"));
8392
+ const listed = readOrNull(join6(projectPath, ".godot", "extension_list.cfg"));
8313
8393
  if (listed === null) {
8314
8394
  return [];
8315
8395
  }
@@ -8328,7 +8408,7 @@ function windowsLibraries(projectPath) {
8328
8408
  continue;
8329
8409
  }
8330
8410
  const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
8331
- const library = named === undefined ? null : resolveResource(projectPath, dirname4(manifest), named);
8411
+ const library = named === undefined ? null : resolveResource(projectPath, dirname5(manifest), named);
8332
8412
  if (library !== null) {
8333
8413
  libraries.add(library);
8334
8414
  }
@@ -8341,9 +8421,9 @@ function resolveResource(projectPath, relativeTo, path) {
8341
8421
  return null;
8342
8422
  }
8343
8423
  if (path.startsWith("res://")) {
8344
- return join5(projectPath, path.slice("res://".length));
8424
+ return join6(projectPath, path.slice("res://".length));
8345
8425
  }
8346
- return isAbsolute2(path) ? path : join5(relativeTo, path);
8426
+ return isAbsolute3(path) ? path : join6(relativeTo, path);
8347
8427
  }
8348
8428
  function readOrNull(path) {
8349
8429
  try {
@@ -8356,33 +8436,33 @@ var init_library_copies = () => {};
8356
8436
 
8357
8437
  // src/reimport.ts
8358
8438
  import { createHash } from "node:crypto";
8359
- import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync4, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8360
- import { basename as basename4, join as join6 } from "node:path";
8439
+ import { existsSync as existsSync7, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync5, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8440
+ import { basename as basename5, join as join7 } from "node:path";
8361
8441
  function forceNextImport(projectPath, resourcePath) {
8362
8442
  const relative = resourcePath.replace(/^res:\/\//, "");
8363
- const sidecar = join6(projectPath, `${relative}.import`);
8364
- if (!existsSync6(sidecar)) {
8443
+ const sidecar = join7(projectPath, `${relative}.import`);
8444
+ if (!existsSync7(sidecar)) {
8365
8445
  return;
8366
8446
  }
8367
- const { atime, mtimeMs } = statSync4(sidecar);
8447
+ const { atime, mtimeMs } = statSync5(sidecar);
8368
8448
  writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
8369
8449
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
8370
8450
  rmSync3(importRecord(projectPath, resourcePath), { force: true });
8371
8451
  }
8372
8452
  function importRecord(projectPath, resourcePath) {
8373
8453
  const digest = createHash("md5").update(resourcePath).digest("hex");
8374
- return join6(projectPath, ".godot", "imported", `${basename4(resourcePath)}-${digest}.md5`);
8454
+ return join7(projectPath, ".godot", "imported", `${basename5(resourcePath)}-${digest}.md5`);
8375
8455
  }
8376
8456
  var init_reimport = () => {};
8377
8457
 
8378
8458
  // src/alive.ts
8379
- import process5 from "node:process";
8459
+ import process6 from "node:process";
8380
8460
  function alive(pid) {
8381
8461
  if (pid === undefined || pid === null) {
8382
8462
  return false;
8383
8463
  }
8384
8464
  try {
8385
- process5.kill(pid, 0);
8465
+ process6.kill(pid, 0);
8386
8466
  return true;
8387
8467
  } catch (error) {
8388
8468
  return error.code === "EPERM";
@@ -8391,17 +8471,17 @@ function alive(pid) {
8391
8471
  var init_alive = () => {};
8392
8472
 
8393
8473
  // src/scratch.ts
8394
- import { existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
8474
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "node:fs";
8395
8475
  import { tmpdir as tmpdir2 } from "node:os";
8396
- import { join as join7 } from "node:path";
8397
- import process6 from "node:process";
8476
+ import { join as join8 } from "node:path";
8477
+ import process7 from "node:process";
8398
8478
  function scratchDirectory(kind) {
8399
- return mkdtempSync(join7(tmpdir2(), `gdharness-${kind}-${process6.pid}-`));
8479
+ return mkdtempSync(join8(tmpdir2(), `gdharness-${kind}-${process7.pid}-`));
8400
8480
  }
8401
8481
  function sweepAbandonedScratch(directory = tmpdir2()) {
8402
8482
  let entries;
8403
8483
  try {
8404
- entries = readdirSync4(directory);
8484
+ entries = readdirSync5(directory);
8405
8485
  } catch {
8406
8486
  return;
8407
8487
  }
@@ -8412,7 +8492,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
8412
8492
  continue;
8413
8493
  }
8414
8494
  try {
8415
- remove(join7(directory, entry));
8495
+ remove(join8(directory, entry));
8416
8496
  } catch {}
8417
8497
  }
8418
8498
  }
@@ -8450,7 +8530,7 @@ function tryAgain(path, attempt) {
8450
8530
  lastTryOnExit();
8451
8531
  }
8452
8532
  const after = AGAIN_AFTER_MS[attempt];
8453
- if (after === undefined || !existsSync7(path)) {
8533
+ if (after === undefined || !existsSync8(path)) {
8454
8534
  waiting.delete(path);
8455
8535
  if (after === undefined) {
8456
8536
  atExit.add(path);
@@ -8471,7 +8551,7 @@ function lastTryOnExit() {
8471
8551
  return;
8472
8552
  }
8473
8553
  lastTryWatched = true;
8474
- process6.once("exit", () => {
8554
+ process7.once("exit", () => {
8475
8555
  for (const path of [...waiting, ...atExit]) {
8476
8556
  try {
8477
8557
  remove(path);
@@ -8498,8 +8578,8 @@ var init_scratch = __esm(() => {
8498
8578
  });
8499
8579
 
8500
8580
  // src/headless.ts
8501
- import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8502
- import { dirname as dirname5, join as join8 } from "node:path";
8581
+ import { existsSync as existsSync9, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8582
+ import { dirname as dirname6, join as join9 } from "node:path";
8503
8583
  function snakeCased(params) {
8504
8584
  const result = emptyRecord();
8505
8585
  for (const [key, value] of Object.entries(params)) {
@@ -8560,7 +8640,7 @@ function refusalsIn(log) {
8560
8640
  }
8561
8641
  function scriptFault(problems, script) {
8562
8642
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
8563
- const directories = [...new Set([spelled(dirname5(script)), spelled(resolvedOr(dirname5(script)))])];
8643
+ const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
8564
8644
  return problems.find((entry) => {
8565
8645
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
8566
8646
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -8598,26 +8678,33 @@ async function runImport(godotPath, projectPath, options = {}) {
8598
8678
  };
8599
8679
  const first = await pass();
8600
8680
  const collided = librariesNotCopied(first.messages);
8601
- if (collided.length === 0) {
8681
+ const crash = first.docCacheNote;
8682
+ if (collided.length === 0 && crash === undefined) {
8602
8683
  return first;
8603
8684
  }
8604
- const second = await pass();
8685
+ const { docCacheNote: _, ...again } = await pass();
8686
+ const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
8605
8687
  const still = librariesNotCopied(second.messages);
8606
8688
  return {
8607
8689
  ...second,
8608
- librariesRetried: collided,
8609
- ...still.length === 0 ? {} : { librariesNotLoaded: still },
8610
- extensionNote: extensionRetryNote(collided, still)
8690
+ ...collided.length === 0 ? {} : {
8691
+ librariesRetried: collided,
8692
+ ...still.length === 0 ? {} : { librariesNotLoaded: still },
8693
+ extensionNote: extensionRetryNote(collided, still)
8694
+ },
8695
+ ...crash === undefined ? {} : { docCacheNote: crash }
8611
8696
  };
8612
8697
  }
8613
8698
  async function importOnce(godotPath, projectPath) {
8614
8699
  const logDir = scratchDirectory("import");
8700
+ const caches = editorCacheDirectory(godotPath);
8701
+ const cachesBefore = docCachesWritten(caches);
8615
8702
  let ran;
8616
8703
  try {
8617
8704
  ran = await runEngine(godotPath, [
8618
8705
  "--headless",
8619
8706
  "--log-file",
8620
- join8(logDir, "engine.log"),
8707
+ join9(logDir, "engine.log"),
8621
8708
  "--path",
8622
8709
  projectPath,
8623
8710
  "--import"
@@ -8626,24 +8713,26 @@ async function importOnce(godotPath, projectPath) {
8626
8713
  discard(logDir);
8627
8714
  }
8628
8715
  const problems = problemsIn(ran.log);
8716
+ const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
8629
8717
  if (ran.exitCode === 0 && ran.failure === null) {
8630
- return { ok: true, ...reported2(problems) };
8718
+ return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
8631
8719
  }
8632
8720
  return {
8633
8721
  ok: false,
8634
8722
  message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
8635
- ...reported2(problems)
8723
+ ...reported2(problems),
8724
+ ...crash === null ? {} : { docCacheNote: crash }
8636
8725
  };
8637
8726
  }
8638
8727
  async function runOperation(engine, operation, params, projectPath) {
8639
8728
  const paramsDir = scratchDirectory("params");
8640
- const paramsFile = join8(paramsDir, `${operation}.json`);
8641
- const answerFile = join8(paramsDir, "answer.json");
8729
+ const paramsFile = join9(paramsDir, `${operation}.json`);
8730
+ const answerFile = join9(paramsDir, "answer.json");
8642
8731
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
8643
8732
  const args = [
8644
8733
  "--headless",
8645
8734
  "--log-file",
8646
- join8(paramsDir, "engine.log"),
8735
+ join9(paramsDir, "engine.log"),
8647
8736
  "--path",
8648
8737
  projectPath,
8649
8738
  "--script",
@@ -8731,7 +8820,7 @@ async function runOperation(engine, operation, params, projectPath) {
8731
8820
  };
8732
8821
  }
8733
8822
  function answerIn(file) {
8734
- if (!existsSync8(file)) {
8823
+ if (!existsSync9(file)) {
8735
8824
  return { kind: "none" };
8736
8825
  }
8737
8826
  const text = readFileSync5(file, "utf8");
@@ -8747,6 +8836,7 @@ function answerIn(file) {
8747
8836
  }
8748
8837
  var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
8749
8838
  var init_headless = __esm(() => {
8839
+ init_doc_cache();
8750
8840
  init_engine_run();
8751
8841
  init_library_copies();
8752
8842
  init_reimport();
@@ -8754,9 +8844,9 @@ var init_headless = __esm(() => {
8754
8844
  });
8755
8845
 
8756
8846
  // src/update-check.ts
8757
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8758
- import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
8759
- import { join as join9 } from "node:path";
8847
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8848
+ import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
8849
+ import { join as join10 } from "node:path";
8760
8850
  function packageUrl(root) {
8761
8851
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
8762
8852
  }
@@ -8776,27 +8866,27 @@ function cacheDirectory(environment) {
8776
8866
  const value = environment[name];
8777
8867
  return value !== undefined && value !== "" ? value : null;
8778
8868
  };
8779
- const home = set("HOME") ?? homedir3();
8869
+ const home = set("HOME") ?? homedir4();
8780
8870
  if (process.platform === "win32") {
8781
- return join9(set("LOCALAPPDATA") ?? home, "gdharness");
8871
+ return join10(set("LOCALAPPDATA") ?? home, "gdharness");
8782
8872
  }
8783
8873
  if (process.platform === "darwin") {
8784
- return join9(home, "Library", "Caches", "gdharness");
8874
+ return join10(home, "Library", "Caches", "gdharness");
8785
8875
  }
8786
- return join9(set("XDG_CACHE_HOME") ?? join9(home, ".cache"), "gdharness");
8876
+ return join10(set("XDG_CACHE_HOME") ?? join10(home, ".cache"), "gdharness");
8787
8877
  }
8788
8878
  function cacheFile(environment = process.env) {
8789
8879
  try {
8790
8880
  const directory = cacheDirectory(environment);
8791
8881
  mkdirSync3(directory, { recursive: true, mode: 448 });
8792
- return join9(directory, "update-check.json");
8882
+ return join10(directory, "update-check.json");
8793
8883
  } catch {
8794
- return join9(tmpdir3(), "gdharness-update-check.json");
8884
+ return join10(tmpdir3(), "gdharness-update-check.json");
8795
8885
  }
8796
8886
  }
8797
8887
  function readCache(path) {
8798
8888
  try {
8799
- if (!existsSync9(path)) {
8889
+ if (!existsSync10(path)) {
8800
8890
  return null;
8801
8891
  }
8802
8892
  const parsed = JSON.parse(readFileSync6(path, "utf8"));
@@ -9031,7 +9121,7 @@ var init_server_version = __esm(() => {
9031
9121
 
9032
9122
  // src/issues.ts
9033
9123
  import { createHash as createHash2 } from "node:crypto";
9034
- import process7 from "node:process";
9124
+ import process8 from "node:process";
9035
9125
  function feedbackNotice(every) {
9036
9126
  return JSON.stringify({
9037
9127
  gdharness_feedback: {
@@ -9042,9 +9132,9 @@ function feedbackNotice(every) {
9042
9132
  }, null, 2);
9043
9133
  }
9044
9134
  function runtime() {
9045
- const versions = process7.versions;
9135
+ const versions = process8.versions;
9046
9136
  const bun = versions["bun"];
9047
- return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
9137
+ return bun === undefined ? `node ${process8.versions.node}` : `bun ${bun}`;
9048
9138
  }
9049
9139
  function defectSignature(where, message) {
9050
9140
  const normalised = `${where}
@@ -9056,7 +9146,7 @@ function facts(where, message, godotVersion) {
9056
9146
  ["Where", where],
9057
9147
  ["Error", message],
9058
9148
  ["Version", `gdharness ${SERVER_VERSION}`],
9059
- ["Runtime", `${runtime()}, ${process7.platform} ${process7.arch}`],
9149
+ ["Runtime", `${runtime()}, ${process8.platform} ${process8.arch}`],
9060
9150
  ["Godot", godotVersion ?? "not known at the point this failed"],
9061
9151
  ["Signature", defectSignature(where, message)]
9062
9152
  ];
@@ -9066,7 +9156,7 @@ function filledTemplate(where, message, godotVersion) {
9066
9156
  `**gdharness version**: ${SERVER_VERSION}`,
9067
9157
  `**Godot version**: ${godotVersion ?? ""}`,
9068
9158
  `**Bun version**: ${runtime()}`,
9069
- `**OS**: ${process7.platform} ${process7.arch}`,
9159
+ `**OS**: ${process8.platform} ${process8.arch}`,
9070
9160
  "**MCP client**:",
9071
9161
  "",
9072
9162
  "## What you did",
@@ -9806,8 +9896,8 @@ var init_gdscript_source = __esm(() => {
9806
9896
  });
9807
9897
 
9808
9898
  // src/class-cache.ts
9809
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
9810
- import { join as join10, posix } from "node:path";
9899
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
9900
+ import { join as join11, posix } from "node:path";
9811
9901
  function isToolScript(source) {
9812
9902
  return TOOL.test(codeOf(source));
9813
9903
  }
@@ -9823,14 +9913,14 @@ function declaredClasses(projectPath) {
9823
9913
  }
9824
9914
  function eachScript(projectPath, each) {
9825
9915
  const visit = (directory, prefix) => {
9826
- if (existsSync10(join10(directory, ".gdignore"))) {
9916
+ if (existsSync11(join11(directory, ".gdignore"))) {
9827
9917
  return;
9828
9918
  }
9829
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
9919
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
9830
9920
  if (entry.name.startsWith(".")) {
9831
9921
  continue;
9832
9922
  }
9833
- const path = join10(directory, entry.name);
9923
+ const path = join11(directory, entry.name);
9834
9924
  if (entry.isDirectory()) {
9835
9925
  visit(path, `${prefix}${entry.name}/`);
9836
9926
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -9856,8 +9946,8 @@ function scriptsNaming(projectPath, classes) {
9856
9946
  return naming.sort((a, b) => a.script.localeCompare(b.script));
9857
9947
  }
9858
9948
  function cachedClasses(projectPath) {
9859
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
9860
- if (!existsSync10(cache)) {
9949
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
9950
+ if (!existsSync11(cache)) {
9861
9951
  return null;
9862
9952
  }
9863
9953
  const listed = new Map;
@@ -10127,9 +10217,9 @@ function uncachedClassNote(types) {
10127
10217
  return `${types.join(", ")} ${one ? "is declared" : "are declared"} in this project and missing from ` + `.godot/global_script_class_cache.cfg, so a game launched now would not resolve ${one ? "it" : "them"} ` + "either. Try editor_rescan first: it asks the editor already running and starts nothing, and it " + "cleared this in 314ms in a second project with a 31-worker fan-out still importing. It is not " + "always enough, because a file another engine has already imported reads as settled and the " + "walk does not look inside it. project_import refresh_classes rewrites the cache from the " + "declarations on disk and does reach that case, at the cost of a short headless engine, which " + "is worth knowing when something else is already running against this project.";
10128
10218
  }
10129
10219
  function cacheWrittenAt(projectPath) {
10130
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
10220
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
10131
10221
  try {
10132
- return statSync5(cache).mtimeMs;
10222
+ return statSync6(cache).mtimeMs;
10133
10223
  } catch {
10134
10224
  return null;
10135
10225
  }
@@ -10141,14 +10231,14 @@ function declaredSince(projectPath, paths, names, since) {
10141
10231
  return false;
10142
10232
  }
10143
10233
  try {
10144
- return statSync5(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
10234
+ return statSync6(join11(projectPath, path.slice("res://".length))).mtimeMs > since;
10145
10235
  } catch {
10146
10236
  return false;
10147
10237
  }
10148
10238
  });
10149
10239
  }
10150
10240
  function cachedAtMissingPaths(cached, projectPath) {
10151
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync10(join10(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10241
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join11(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10152
10242
  }
10153
10243
  function staleAgainst(cached, projectPath) {
10154
10244
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -10173,8 +10263,8 @@ var init_class_cache = __esm(() => {
10173
10263
  });
10174
10264
 
10175
10265
  // src/project-scan.ts
10176
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
10177
- import { extname, join as join11 } from "node:path";
10266
+ import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
10267
+ import { extname, join as join12 } from "node:path";
10178
10268
  function skipped(name) {
10179
10269
  return name.startsWith(".") || SKIPPED.has(name);
10180
10270
  }
@@ -10182,10 +10272,10 @@ function steppedOver(directory) {
10182
10272
  return gdignored(directory) || installedByUs(directory);
10183
10273
  }
10184
10274
  function gdignored(directory) {
10185
- return existsSync11(join11(directory, ".gdignore"));
10275
+ return existsSync12(join12(directory, ".gdignore"));
10186
10276
  }
10187
10277
  function installedByUs(directory) {
10188
- return existsSync11(join11(directory, VERSION_MARKER));
10278
+ return existsSync12(join12(directory, VERSION_MARKER));
10189
10279
  }
10190
10280
  function projectStructure(projectPath) {
10191
10281
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10193,12 +10283,12 @@ function projectStructure(projectPath) {
10193
10283
  if (steppedOver(directory)) {
10194
10284
  return;
10195
10285
  }
10196
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10286
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10197
10287
  if (skipped(entry.name)) {
10198
10288
  continue;
10199
10289
  }
10200
10290
  if (entry.isDirectory()) {
10201
- visit(join11(directory, entry.name));
10291
+ visit(join12(directory, entry.name));
10202
10292
  } else if (entry.isFile()) {
10203
10293
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10204
10294
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10222,13 +10312,13 @@ function projectFiles(projectPath, extensions) {
10222
10312
  if (steppedOver(directory)) {
10223
10313
  return;
10224
10314
  }
10225
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10315
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10226
10316
  if (skipped(entry.name)) {
10227
10317
  continue;
10228
10318
  }
10229
10319
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10230
10320
  if (entry.isDirectory()) {
10231
- visit(join11(directory, entry.name), spelled);
10321
+ visit(join12(directory, entry.name), spelled);
10232
10322
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10233
10323
  found.push(spelled);
10234
10324
  }
@@ -10243,20 +10333,20 @@ function scriptsWithoutUid(projectPath) {
10243
10333
  if (steppedOver(directory)) {
10244
10334
  return;
10245
10335
  }
10246
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10336
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10247
10337
  if (skipped(entry.name)) {
10248
10338
  continue;
10249
10339
  }
10250
10340
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10251
10341
  if (entry.isDirectory()) {
10252
- visit(join11(directory, entry.name), spelled);
10342
+ visit(join12(directory, entry.name), spelled);
10253
10343
  continue;
10254
10344
  }
10255
10345
  if (!entry.isFile()) {
10256
10346
  continue;
10257
10347
  }
10258
10348
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10259
- if (CARRIES_A_UID.has(extension) && !existsSync11(join11(directory, `${entry.name}.uid`))) {
10349
+ if (CARRIES_A_UID.has(extension) && !existsSync12(join12(directory, `${entry.name}.uid`))) {
10260
10350
  found.push(spelled);
10261
10351
  }
10262
10352
  }
@@ -10273,11 +10363,11 @@ function projectTexts(projectPath) {
10273
10363
  if (steppedOver(directory)) {
10274
10364
  return;
10275
10365
  }
10276
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10366
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10277
10367
  if (skipped(entry.name)) {
10278
10368
  continue;
10279
10369
  }
10280
- const absolute = join11(directory, entry.name);
10370
+ const absolute = join12(directory, entry.name);
10281
10371
  const spelled = `${prefix}${entry.name}`;
10282
10372
  if (entry.isDirectory()) {
10283
10373
  visit(absolute, `${spelled}/`);
@@ -10303,11 +10393,11 @@ function ignoredTexts(projectPath) {
10303
10393
  return;
10304
10394
  }
10305
10395
  const under = ignored || gdignored(directory);
10306
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10396
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10307
10397
  if (skipped(entry.name)) {
10308
10398
  continue;
10309
10399
  }
10310
- const absolute = join11(directory, entry.name);
10400
+ const absolute = join12(directory, entry.name);
10311
10401
  const spelled = `${prefix}${entry.name}`;
10312
10402
  if (entry.isDirectory()) {
10313
10403
  visit(absolute, `${spelled}/`, under);
@@ -10340,14 +10430,14 @@ function searchProject(projectPath, options) {
10340
10430
  if (steppedOver(directory)) {
10341
10431
  return;
10342
10432
  }
10343
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10433
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10344
10434
  if (full()) {
10345
10435
  return;
10346
10436
  }
10347
10437
  if (skipped(entry.name)) {
10348
10438
  continue;
10349
10439
  }
10350
- const entryPath = join11(directory, entry.name);
10440
+ const entryPath = join12(directory, entry.name);
10351
10441
  if (entry.isDirectory()) {
10352
10442
  visit(entryPath);
10353
10443
  continue;
@@ -10792,16 +10882,16 @@ function readParams(params, key) {
10792
10882
  import { createHash as createHash3 } from "node:crypto";
10793
10883
  import {
10794
10884
  cpSync,
10795
- existsSync as existsSync12,
10885
+ existsSync as existsSync13,
10796
10886
  mkdirSync as mkdirSync4,
10797
- readdirSync as readdirSync7,
10887
+ readdirSync as readdirSync8,
10798
10888
  readFileSync as readFileSync11,
10799
10889
  rmdirSync,
10800
10890
  rmSync as rmSync5,
10801
- statSync as statSync6,
10891
+ statSync as statSync7,
10802
10892
  writeFileSync as writeFileSync6
10803
10893
  } from "node:fs";
10804
- import { dirname as dirname6, join as join12 } from "node:path";
10894
+ import { dirname as dirname7, join as join13 } from "node:path";
10805
10895
  import { fileURLToPath } from "node:url";
10806
10896
  function editorAddonDigest(from = shippedAddonsDirectory()) {
10807
10897
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -10812,13 +10902,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
10812
10902
  function addonsDigest(names, from) {
10813
10903
  const hash = createHash3("sha256");
10814
10904
  const walk = (directory, relative) => {
10815
- for (const name of readdirSync7(directory).sort()) {
10905
+ for (const name of readdirSync8(directory).sort()) {
10816
10906
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
10817
10907
  continue;
10818
10908
  }
10819
- const path = join12(directory, name);
10909
+ const path = join13(directory, name);
10820
10910
  const inside = `${relative}/${name}`;
10821
- if (statSync6(path).isDirectory()) {
10911
+ if (statSync7(path).isDirectory()) {
10822
10912
  walk(path, inside);
10823
10913
  } else {
10824
10914
  hash.update(`${inside}
@@ -10830,7 +10920,7 @@ function addonsDigest(names, from) {
10830
10920
  }
10831
10921
  };
10832
10922
  for (const name of names) {
10833
- walk(join12(from, name), name);
10923
+ walk(join13(from, name), name);
10834
10924
  }
10835
10925
  return hash.digest("hex");
10836
10926
  }
@@ -10861,44 +10951,44 @@ function installedRuntimeDigest(projectPath) {
10861
10951
  return digestMarker(projectPath, RUNTIME_ADDON);
10862
10952
  }
10863
10953
  function digestMarker(projectPath, addon) {
10864
- const marker = join12(projectPath, "addons", addon, DIGEST_MARKER);
10865
- if (!existsSync12(marker)) {
10954
+ const marker = join13(projectPath, "addons", addon, DIGEST_MARKER);
10955
+ if (!existsSync13(marker)) {
10866
10956
  return null;
10867
10957
  }
10868
10958
  const said = readFileSync11(marker, "utf8").trim();
10869
10959
  return said === "" ? null : said;
10870
10960
  }
10871
10961
  function shippedAddonsDirectory() {
10872
- return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "addons");
10962
+ return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
10873
10963
  }
10874
10964
  function shippedOperationsScript() {
10875
- return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10965
+ return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10876
10966
  }
10877
10967
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
10878
10968
  const installed = [];
10879
10969
  for (const name of ADDONS) {
10880
- const source = join12(from, name);
10881
- if (!existsSync12(join12(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10970
+ const source = join13(from, name);
10971
+ if (!existsSync13(join13(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10882
10972
  throw new Error(`The package holds no ${name} addon at ${source}.`);
10883
10973
  }
10884
- const target = join12(projectPath, "addons", name);
10885
- const replaced = existsSync12(target);
10974
+ const target = join13(projectPath, "addons", name);
10975
+ const replaced = existsSync13(target);
10886
10976
  rmSync5(target, { recursive: true, force: true });
10887
- mkdirSync4(dirname6(target), { recursive: true });
10977
+ mkdirSync4(dirname7(target), { recursive: true });
10888
10978
  cpSync(source, target, { recursive: true });
10889
- writeFileSync6(join12(target, VERSION_MARKER), `${SERVER_VERSION}
10979
+ writeFileSync6(join13(target, VERSION_MARKER), `${SERVER_VERSION}
10890
10980
  `);
10891
10981
  installed.push({ name, path: target, replaced });
10892
10982
  }
10893
- writeFileSync6(join12(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10983
+ writeFileSync6(join13(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10894
10984
  `);
10895
- writeFileSync6(join12(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10985
+ writeFileSync6(join13(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10896
10986
  `);
10897
10987
  return installed;
10898
10988
  }
10899
10989
  function installedAddonVersion(projectPath) {
10900
- const marker = join12(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10901
- if (!existsSync12(marker)) {
10990
+ const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10991
+ if (!existsSync13(marker)) {
10902
10992
  return null;
10903
10993
  }
10904
10994
  const said = readFileSync11(marker, "utf8").trim();
@@ -10907,14 +10997,14 @@ function installedAddonVersion(projectPath) {
10907
10997
  function removeAddons(projectPath) {
10908
10998
  const removed = [];
10909
10999
  for (const name of ADDONS) {
10910
- const target = join12(projectPath, "addons", name);
10911
- if (existsSync12(target)) {
11000
+ const target = join13(projectPath, "addons", name);
11001
+ if (existsSync13(target)) {
10912
11002
  rmSync5(target, { recursive: true, force: true });
10913
11003
  removed.push(target);
10914
11004
  }
10915
11005
  }
10916
- const addons = join12(projectPath, "addons");
10917
- if (removed.length > 0 && existsSync12(addons) && readdirSync7(addons).length === 0) {
11006
+ const addons = join13(projectPath, "addons");
11007
+ if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
10918
11008
  rmdirSync(addons);
10919
11009
  removed.push(addons);
10920
11010
  }
@@ -10948,7 +11038,7 @@ function loaderAmong(projectPath, named) {
10948
11038
  }
10949
11039
  let source = null;
10950
11040
  try {
10951
- source = readFileSync11(join12(projectPath, path), "utf8");
11041
+ source = readFileSync11(join13(projectPath, path), "utf8");
10952
11042
  } catch {}
10953
11043
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
10954
11044
  return { name: entry, path, exists: true };
@@ -10990,10 +11080,10 @@ function enabledPlugins(settings) {
10990
11080
  function inspectProject(projectPath) {
10991
11081
  const problems = [];
10992
11082
  const addons = ADDONS.map((name) => {
10993
- const directory = join12(projectPath, "addons", name);
10994
- const marker = join12(directory, VERSION_MARKER);
10995
- const installed = existsSync12(directory);
10996
- const version = existsSync12(marker) ? readFileSync11(marker, "utf8").trim() : null;
11083
+ const directory = join13(projectPath, "addons", name);
11084
+ const marker = join13(directory, VERSION_MARKER);
11085
+ const installed = existsSync13(directory);
11086
+ const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
10997
11087
  const current = version === SERVER_VERSION;
10998
11088
  if (!installed) {
10999
11089
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -11002,7 +11092,7 @@ function inspectProject(projectPath) {
11002
11092
  }
11003
11093
  return { name, installed, version, current };
11004
11094
  });
11005
- const settings = parseProjectGodot(readFileSync11(join12(projectPath, "project.godot"), "utf8"));
11095
+ const settings = parseProjectGodot(readFileSync11(join13(projectPath, "project.godot"), "utf8"));
11006
11096
  const pluginsEnabled = enabledPlugins(settings);
11007
11097
  for (const name of EDITOR_PLUGINS) {
11008
11098
  if (!pluginsEnabled.includes(name)) {
@@ -11018,7 +11108,7 @@ function inspectProject(projectPath) {
11018
11108
  const tracked = trackedByGit(projectPath, [...named.values()]);
11019
11109
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
11020
11110
  for (const [name, path] of named) {
11021
- if (!existsSync12(join12(projectPath, path))) {
11111
+ if (!existsSync13(join13(projectPath, path))) {
11022
11112
  if (!addonMissing(path)) {
11023
11113
  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`);
11024
11114
  }
@@ -11679,7 +11769,7 @@ var init_tool_definitions = __esm(() => {
11679
11769
  rootNodeType: {
11680
11770
  type: "string",
11681
11771
  ops: ["create"],
11682
- description: "create: the root node class. Default Node."
11772
+ description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
11683
11773
  },
11684
11774
  rootNodeName: {
11685
11775
  type: "string",
@@ -11717,7 +11807,7 @@ var init_tool_definitions = __esm(() => {
11717
11807
  {
11718
11808
  name: "scene_node",
11719
11809
  title: "Scene nodes",
11720
- description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
11810
+ description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
11721
11811
  parameters: {
11722
11812
  projectPath: PROJECT_PATH,
11723
11813
  scenePath: SCENE_PATH,
@@ -11730,7 +11820,7 @@ var init_tool_definitions = __esm(() => {
11730
11820
  nodeType: {
11731
11821
  type: "string",
11732
11822
  ops: ["add"],
11733
- description: 'add: the node class, such as "CharacterBody2D". Give this or instancePath.'
11823
+ description: 'add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath.'
11734
11824
  },
11735
11825
  instancePath: {
11736
11826
  type: "string",
@@ -12075,7 +12165,7 @@ var init_tool_definitions = __esm(() => {
12075
12165
  resourceType: {
12076
12166
  type: "string",
12077
12167
  ops: ["create"],
12078
- description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
12168
+ description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D", or a class_name a project script declares, which the resource is made as with that script attached.'
12079
12169
  },
12080
12170
  properties: { ...PROPERTIES, ops: ["create", "modify"] },
12081
12171
  script: {
@@ -12269,6 +12359,17 @@ var init_tool_definitions = __esm(() => {
12269
12359
  ops: ["profile"],
12270
12360
  description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not places."
12271
12361
  },
12362
+ mark: {
12363
+ type: "boolean",
12364
+ ops: ["profile"],
12365
+ description: `profile: answer as usual and set the run's mark at these totals, so a later profile with since: "mark" answers for one action, a press or a commit, apart from the boot and everything before it. The mark is kept beside the run's profile, so a server that replaces this one reads it too, and setting it again moves it. Given with since, the answer counts from the mark before it moves, which times one span after another. Default false.`
12366
+ },
12367
+ since: {
12368
+ type: "string",
12369
+ enum: ["mark"],
12370
+ ops: ["profile"],
12371
+ description: 'profile: "mark" answers only what was added after the mark that mark: true set in this run: per function the calls and self and total milliseconds since it, with the functions that added no call left out, frames and coveredMs since it, and callersOf read off the same difference. Refused when no mark was set in this run, or when the profile holds less than it did at the mark, which is a profiler started again since.'
12372
+ },
12272
12373
  limit: {
12273
12374
  type: "integer",
12274
12375
  minimum: 1,
@@ -12320,7 +12421,7 @@ var init_tool_definitions = __esm(() => {
12320
12421
  requires: []
12321
12422
  }
12322
12423
  },
12323
- answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
12424
+ answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
12324
12425
  defaultOperation: "start"
12325
12426
  },
12326
12427
  {
@@ -32796,7 +32897,7 @@ var init_ajvProvider_CEoC__sr = __esm(() => {
32796
32897
  });
32797
32898
 
32798
32899
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
32799
- import process9 from "node:process";
32900
+ import process10 from "node:process";
32800
32901
  var init_shimsNode = __esm(() => {
32801
32902
  init_ajvProvider_CEoC__sr();
32802
32903
  });
@@ -34610,7 +34711,7 @@ var swallowsErrorsAfterClose, StdioServerTransport = class {
34610
34711
  _readBuffer;
34611
34712
  _started = false;
34612
34713
  _closed = false;
34613
- constructor(_stdin = process9.stdin, _stdout = process9.stdout, options) {
34714
+ constructor(_stdin = process10.stdin, _stdout = process10.stdout, options) {
34614
34715
  this._stdin = _stdin;
34615
34716
  this._stdout = _stdout;
34616
34717
  this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
@@ -34799,10 +34900,10 @@ var init_stdio = __esm(() => {
34799
34900
  });
34800
34901
 
34801
34902
  // src/runtime-client.ts
34802
- import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync7, unlinkSync as unlinkSync2 } from "node:fs";
34903
+ import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync13, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
34803
34904
  import { createConnection } from "node:net";
34804
34905
  import { tmpdir as tmpdir4 } from "node:os";
34805
- import { dirname as dirname9, join as join15, resolve as resolve4 } from "node:path";
34906
+ import { dirname as dirname10, join as join16, resolve as resolve4 } from "node:path";
34806
34907
  import { setTimeout as delay } from "node:timers/promises";
34807
34908
  function runtimeDirectory(variables = process.env) {
34808
34909
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -34810,20 +34911,20 @@ function runtimeDirectory(variables = process.env) {
34810
34911
  return explicit;
34811
34912
  }
34812
34913
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
34813
- return join15(perUser ?? tmpdir4(), "gdharness");
34914
+ return join16(perUser ?? tmpdir4(), "gdharness");
34814
34915
  }
34815
34916
  function runtimeDirectories(variables = process.env) {
34816
34917
  const candidates = [runtimeDirectory(variables)];
34817
34918
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
34818
34919
  for (const base of fallbacks) {
34819
- candidates.push(join15(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34920
+ candidates.push(join16(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34820
34921
  }
34821
34922
  return [...new Set(candidates.map((path) => resolve4(path)))];
34822
34923
  }
34823
34924
  function errorReportOf(pid, directories = runtimeDirectories()) {
34824
34925
  for (const directory of directories) {
34825
- const path = join15(directory, `runtime-${pid}.log`);
34826
- if (existsSync14(path)) {
34926
+ const path = join16(directory, `runtime-${pid}.log`);
34927
+ if (existsSync15(path)) {
34827
34928
  return path;
34828
34929
  }
34829
34930
  }
@@ -34871,7 +34972,7 @@ function parseAnnouncement(file, pid) {
34871
34972
  };
34872
34973
  let announcedAt;
34873
34974
  try {
34874
- announcedAt = statSync7(file).mtimeMs;
34975
+ announcedAt = statSync8(file).mtimeMs;
34875
34976
  } catch {}
34876
34977
  const when = announcedAt === undefined ? {} : { announcedAt };
34877
34978
  if (protocol !== RUNTIME_PROTOCOL) {
@@ -34963,29 +35064,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
34963
35064
  }).map((one) => one.file));
34964
35065
  }
34965
35066
  function announcedIn(directory) {
34966
- if (!existsSync14(directory)) {
35067
+ if (!existsSync15(directory)) {
34967
35068
  return [];
34968
35069
  }
34969
35070
  const found = [];
34970
35071
  const candidates = [];
34971
35072
  const entries = [];
34972
- for (const entry of readdirSync9(directory)) {
35073
+ for (const entry of readdirSync10(directory)) {
34973
35074
  const report = ERROR_REPORT_PATTERN.exec(entry);
34974
35075
  if (report) {
34975
- sweepErrorReport(join15(directory, entry), Number.parseInt(report[1] ?? "", 10));
35076
+ sweepErrorReport(join16(directory, entry), Number.parseInt(report[1] ?? "", 10));
34976
35077
  continue;
34977
35078
  }
34978
35079
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
34979
35080
  if (!match) {
34980
35081
  continue;
34981
35082
  }
34982
- const file = join15(directory, entry);
35083
+ const file = join16(directory, entry);
34983
35084
  const pid = Number.parseInt(match[1] ?? "", 10);
34984
35085
  const running = alive(pid);
34985
35086
  entries.push({ file, pid, running });
34986
35087
  if (running) {
34987
35088
  try {
34988
- candidates.push({ file, pid, writtenAt: statSync7(file).mtimeMs });
35089
+ candidates.push({ file, pid, writtenAt: statSync8(file).mtimeMs });
34989
35090
  } catch {}
34990
35091
  }
34991
35092
  }
@@ -35007,16 +35108,16 @@ function announcedIn(directory) {
35007
35108
  found.push(announced);
35008
35109
  }
35009
35110
  const held = new Set(entries.map((one) => one.file));
35010
- const here = join15(directory, ".");
35111
+ const here = join16(directory, ".");
35011
35112
  for (const file of verdicts.keys()) {
35012
- if (dirname9(file) === here && !held.has(file)) {
35113
+ if (dirname10(file) === here && !held.has(file)) {
35013
35114
  verdicts.delete(file);
35014
35115
  }
35015
35116
  }
35016
35117
  return found;
35017
35118
  }
35018
35119
  async function announcementEnded(pid, directories = runtimeDirectories()) {
35019
- const files = directories.map((directory) => join15(directory, `runtime-${pid}.json`)).filter((file) => existsSync14(file));
35120
+ const files = directories.map((directory) => join16(directory, `runtime-${pid}.json`)).filter((file) => existsSync15(file));
35020
35121
  const first = files[0];
35021
35122
  if (first === undefined) {
35022
35123
  return;
@@ -35042,7 +35143,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
35042
35143
  return;
35043
35144
  }
35044
35145
  try {
35045
- if (now - statSync7(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
35146
+ if (now - statSync8(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
35046
35147
  unlinkSync2(path);
35047
35148
  }
35048
35149
  } catch {}
@@ -35318,9 +35419,9 @@ var init_runtime_client = __esm(() => {
35318
35419
 
35319
35420
  // src/boot-note.ts
35320
35421
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
35321
- import { dirname as dirname10, join as join16 } from "node:path";
35422
+ import { dirname as dirname11, join as join17 } from "node:path";
35322
35423
  function bootNotePath(projectPath) {
35323
- return join16(projectPath, ".godot", "gdharness-boot.json");
35424
+ return join17(projectPath, ".godot", "gdharness-boot.json");
35324
35425
  }
35325
35426
  function readScenes(projectPath) {
35326
35427
  try {
@@ -35340,7 +35441,7 @@ function readBootNote(projectPath, scene) {
35340
35441
  function writeBootNote(projectPath, scene, announcedAfterMs) {
35341
35442
  try {
35342
35443
  const path = bootNotePath(projectPath);
35343
- mkdirSync6(dirname10(path), { recursive: true });
35444
+ mkdirSync6(dirname11(path), { recursive: true });
35344
35445
  const scenes = {
35345
35446
  ...readScenes(projectPath),
35346
35447
  [scene]: {
@@ -35367,9 +35468,9 @@ var init_boot_note = __esm(() => {
35367
35468
 
35368
35469
  // src/breakpoint-note.ts
35369
35470
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
35370
- import { dirname as dirname11, isAbsolute as isAbsolute3, join as join17, relative as relative3 } from "node:path";
35471
+ import { dirname as dirname12, isAbsolute as isAbsolute4, join as join18, relative as relative3 } from "node:path";
35371
35472
  function breakpointNotePath(projectPath) {
35372
- return join17(projectPath, ".godot", "gdharness-breakpoints.json");
35473
+ return join18(projectPath, ".godot", "gdharness-breakpoints.json");
35373
35474
  }
35374
35475
  function writeBreakpointNote(projectPath, held) {
35375
35476
  const path = breakpointNotePath(projectPath);
@@ -35386,7 +35487,7 @@ function writeBreakpointNote(projectPath, held) {
35386
35487
  lines: [...one.lines]
35387
35488
  }))
35388
35489
  };
35389
- mkdirSync7(dirname11(path), { recursive: true });
35490
+ mkdirSync7(dirname12(path), { recursive: true });
35390
35491
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
35391
35492
  `, "utf8");
35392
35493
  } catch (error) {
@@ -35405,12 +35506,12 @@ function readBreakpointNote(projectPath) {
35405
35506
  continue;
35406
35507
  }
35407
35508
  const { script, lines: listed } = entry;
35408
- if (typeof script !== "string" || isAbsolute3(script) || script.split("/").includes("..")) {
35509
+ if (typeof script !== "string" || isAbsolute4(script) || script.split("/").includes("..")) {
35409
35510
  continue;
35410
35511
  }
35411
35512
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
35412
35513
  if (lines.length > 0) {
35413
- held.push({ scriptPath: realPathOr(join17(projectPath, script)), lines });
35514
+ held.push({ scriptPath: realPathOr(join18(projectPath, script)), lines });
35414
35515
  }
35415
35516
  }
35416
35517
  return held;
@@ -35420,7 +35521,7 @@ function readBreakpointNote(projectPath) {
35420
35521
  }
35421
35522
  function withinProject(projectPath, scriptPath) {
35422
35523
  const rel = relative3(projectPath, scriptPath);
35423
- return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
35524
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
35424
35525
  }
35425
35526
  var init_breakpoint_note = __esm(() => {
35426
35527
  init_errors();
@@ -35460,9 +35561,9 @@ var init_whole_file = __esm(() => {
35460
35561
 
35461
35562
  // src/class-note.ts
35462
35563
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
35463
- import { dirname as dirname12, join as join18 } from "node:path";
35564
+ import { dirname as dirname13, join as join19 } from "node:path";
35464
35565
  function classNotePath(projectPath) {
35465
- return join18(projectPath, ".godot", "gdharness-classes.json");
35566
+ return join19(projectPath, ".godot", "gdharness-classes.json");
35466
35567
  }
35467
35568
  function readClassNote(projectPath) {
35468
35569
  try {
@@ -35496,7 +35597,7 @@ function unfinishedScanIn(value) {
35496
35597
  function writeClassNote(projectPath, note) {
35497
35598
  const path = classNotePath(projectPath);
35498
35599
  try {
35499
- mkdirSync8(dirname12(path), { recursive: true });
35600
+ mkdirSync8(dirname13(path), { recursive: true });
35500
35601
  } catch {
35501
35602
  return;
35502
35603
  }
@@ -35508,11 +35609,11 @@ var init_class_note = __esm(() => {
35508
35609
  });
35509
35610
 
35510
35611
  // src/config-pin.ts
35511
- import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
35512
- import { join as join19 } from "node:path";
35612
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "node:fs";
35613
+ import { join as join20 } from "node:path";
35513
35614
  function versionsNamedInConfig(projectPath) {
35514
- const config = join19(projectPath, ".mcp.json");
35515
- if (!existsSync15(config)) {
35615
+ const config = join20(projectPath, ".mcp.json");
35616
+ if (!existsSync16(config)) {
35516
35617
  return [];
35517
35618
  }
35518
35619
  let text;
@@ -35538,22 +35639,22 @@ var init_config_pin = __esm(() => {
35538
35639
  });
35539
35640
 
35540
35641
  // src/cut-test-run.ts
35541
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
35542
- import { dirname as dirname13, join as join20 } from "node:path";
35642
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
35643
+ import { dirname as dirname14, join as join21 } from "node:path";
35543
35644
  function keptAt(projectPath) {
35544
- return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35645
+ return join21(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35545
35646
  }
35546
35647
  function keepCutTestRun(projectPath, run) {
35547
35648
  const path = keptAt(projectPath);
35548
35649
  try {
35549
- mkdirSync9(dirname13(path), { recursive: true });
35650
+ mkdirSync9(dirname14(path), { recursive: true });
35550
35651
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
35551
35652
  `, "utf8");
35552
35653
  } catch {}
35553
35654
  }
35554
35655
  function takeCutTestRun(projectPath) {
35555
35656
  const path = keptAt(projectPath);
35556
- if (!existsSync16(path)) {
35657
+ if (!existsSync17(path)) {
35557
35658
  return null;
35558
35659
  }
35559
35660
  try {
@@ -36370,27 +36471,27 @@ var init_dap_client = __esm(() => {
36370
36471
  import { createHash as createHash4 } from "node:crypto";
36371
36472
  import {
36372
36473
  closeSync,
36373
- existsSync as existsSync17,
36474
+ existsSync as existsSync18,
36374
36475
  mkdtempSync as mkdtempSync2,
36375
36476
  openSync,
36376
36477
  readFileSync as readFileSync19,
36377
36478
  rmSync as rmSync10,
36378
- statSync as statSync8,
36479
+ statSync as statSync9,
36379
36480
  writeFileSync as writeFileSync12,
36380
36481
  writeSync
36381
36482
  } from "node:fs";
36382
36483
  import { tmpdir as tmpdir5 } from "node:os";
36383
- import { join as join21 } from "node:path";
36484
+ import { join as join22 } from "node:path";
36384
36485
  import { setTimeout as delay3 } from "node:timers/promises";
36385
36486
  function helperAssembly(directory = tmpdir5()) {
36386
- return join21(directory, `gdharness-desktop-${DIGEST}.dll`);
36487
+ return join22(directory, `gdharness-desktop-${DIGEST}.dll`);
36387
36488
  }
36388
36489
  async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
36389
36490
  const assembly = helperAssembly(directory);
36390
36491
  const lock = `${assembly}.compiling`;
36391
36492
  const until = Date.now() + budgetMs;
36392
36493
  while (Date.now() < until) {
36393
- if (existsSync17(assembly)) {
36494
+ if (existsSync18(assembly)) {
36394
36495
  return null;
36395
36496
  }
36396
36497
  let mine = false;
@@ -36409,7 +36510,7 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36409
36510
  }
36410
36511
  try {
36411
36512
  const holder = Number(readFileSync19(lock, "utf8").trim());
36412
- const old = Date.now() - statSync8(lock).mtimeMs > budgetMs;
36513
+ const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
36413
36514
  if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
36414
36515
  rmSync10(lock, { force: true });
36415
36516
  continue;
@@ -36423,8 +36524,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36423
36524
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
36424
36525
  }
36425
36526
  async function compileHelper(directory, assembly, budgetMs) {
36426
- const scratch = mkdtempSync2(join21(directory, "gdharness-desktop-source-"));
36427
- const source = join21(scratch, "helper.cs");
36527
+ const scratch = mkdtempSync2(join22(directory, "gdharness-desktop-source-"));
36528
+ const source = join22(scratch, "helper.cs");
36428
36529
  writeFileSync12(source, SOURCE, "utf8");
36429
36530
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
36430
36531
  const script = [
@@ -36462,7 +36563,7 @@ async function compileHelper(directory, assembly, budgetMs) {
36462
36563
  });
36463
36564
  compiler.once("close", (code) => {
36464
36565
  clearTimeout(timer);
36465
- resolve(existsSync17(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
36566
+ resolve(existsSync18(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
36466
36567
  });
36467
36568
  });
36468
36569
  } finally {
@@ -36629,21 +36730,21 @@ var init_desktop = __esm(() => {
36629
36730
  // src/editor-log.ts
36630
36731
  import {
36631
36732
  closeSync as closeSync2,
36632
- existsSync as existsSync18,
36733
+ existsSync as existsSync19,
36633
36734
  mkdirSync as mkdirSync10,
36634
36735
  openSync as openSync2,
36635
36736
  readFileSync as readFileSync20,
36636
36737
  readSync,
36637
36738
  rmSync as rmSync11,
36638
- statSync as statSync9,
36739
+ statSync as statSync10,
36639
36740
  writeFileSync as writeFileSync13
36640
36741
  } from "node:fs";
36641
- import { dirname as dirname14, join as join22 } from "node:path";
36742
+ import { dirname as dirname15, join as join23 } from "node:path";
36642
36743
  function editorLogPath(projectPath) {
36643
- return join22(projectPath, ".godot", "gdharness-editor.log");
36744
+ return join23(projectPath, ".godot", "gdharness-editor.log");
36644
36745
  }
36645
36746
  function editorLogNotePath(projectPath) {
36646
- return join22(projectPath, ".godot", "gdharness-editor.json");
36747
+ return join23(projectPath, ".godot", "gdharness-editor.json");
36647
36748
  }
36648
36749
  function readEditorLogNote(projectPath) {
36649
36750
  try {
@@ -36658,7 +36759,7 @@ function readEditorLogNote(projectPath) {
36658
36759
  function clearEditorLog(projectPath) {
36659
36760
  try {
36660
36761
  const path = editorLogPath(projectPath);
36661
- mkdirSync10(dirname14(path), { recursive: true });
36762
+ mkdirSync10(dirname15(path), { recursive: true });
36662
36763
  rmSync11(path, { force: true });
36663
36764
  rmSync11(editorLogNotePath(projectPath), { force: true });
36664
36765
  } catch {}
@@ -36666,7 +36767,7 @@ function clearEditorLog(projectPath) {
36666
36767
  function writeEditorLogNote(projectPath, pid) {
36667
36768
  try {
36668
36769
  const path = editorLogNotePath(projectPath);
36669
- mkdirSync10(dirname14(path), { recursive: true });
36770
+ mkdirSync10(dirname15(path), { recursive: true });
36670
36771
  writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
36671
36772
  } catch {}
36672
36773
  }
@@ -36688,7 +36789,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
36688
36789
  return { kind: "older server", notedPid: noted?.pid ?? null };
36689
36790
  }
36690
36791
  const path = editorLogPath(projectPath);
36691
- if (!existsSync18(path)) {
36792
+ if (!existsSync19(path)) {
36692
36793
  return { kind: "not written" };
36693
36794
  }
36694
36795
  const log = new GameLog;
@@ -36744,7 +36845,7 @@ function foldProgress(text) {
36744
36845
  `);
36745
36846
  }
36746
36847
  function theFirstOf(path) {
36747
- const whole = statSync9(path).size;
36848
+ const whole = statSync10(path).size;
36748
36849
  if (whole <= MOST_CONSOLE_BYTES) {
36749
36850
  return { text: readFileSync20(path, "utf8") };
36750
36851
  }
@@ -36907,8 +37008,8 @@ var init_editor_log = __esm(() => {
36907
37008
  });
36908
37009
 
36909
37010
  // src/rename.ts
36910
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync21, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
36911
- import { dirname as dirname15, posix as posix2 } from "node:path";
37011
+ import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync21, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
37012
+ import { dirname as dirname16, posix as posix2 } from "node:path";
36912
37013
 
36913
37014
  class Script {
36914
37015
  path;
@@ -37616,21 +37717,21 @@ function applyRewrites(rewrites) {
37616
37717
  if (now !== rewrite.before) {
37617
37718
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
37618
37719
  }
37619
- if (rewrite.writeTo !== rewrite.absolute && existsSync19(rewrite.writeTo)) {
37720
+ if (rewrite.writeTo !== rewrite.absolute && existsSync20(rewrite.writeTo)) {
37620
37721
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
37621
37722
  }
37622
37723
  }
37623
37724
  const done = [];
37624
37725
  try {
37625
37726
  for (const rewrite of rewrites) {
37626
- mkdirSync11(dirname15(rewrite.writeTo), { recursive: true });
37727
+ mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
37627
37728
  const staging = `${rewrite.writeTo}.gdharness-rename`;
37628
37729
  writeFileSync14(staging, rewrite.after, "utf8");
37629
37730
  renameSync3(staging, rewrite.writeTo);
37630
37731
  let movedUid = false;
37631
37732
  if (rewrite.writeTo !== rewrite.absolute) {
37632
37733
  unlinkSync3(rewrite.absolute);
37633
- if (existsSync19(`${rewrite.absolute}.uid`)) {
37734
+ if (existsSync20(`${rewrite.absolute}.uid`)) {
37634
37735
  renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
37635
37736
  movedUid = true;
37636
37737
  }
@@ -37676,10 +37777,10 @@ var init_rename = __esm(() => {
37676
37777
  });
37677
37778
 
37678
37779
  // src/lsp_client.ts
37679
- import { existsSync as existsSync20, realpathSync as realpathSync4 } from "node:fs";
37780
+ import { existsSync as existsSync21, realpathSync as realpathSync4 } from "node:fs";
37680
37781
  import { readFile, realpath } from "node:fs/promises";
37681
37782
  import { createConnection as createConnection3 } from "node:net";
37682
- import { dirname as dirname16, join as join23, resolve as resolve5 } from "node:path";
37783
+ import { dirname as dirname17, join as join24, resolve as resolve5 } from "node:path";
37683
37784
  import { setTimeout as delay4 } from "node:timers/promises";
37684
37785
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
37685
37786
  function initializeTimeoutMs() {
@@ -37841,7 +37942,7 @@ class GodotLSPClient {
37841
37942
  if (this.initialized) {
37842
37943
  return;
37843
37944
  }
37844
- const rootPath = this.rootPath ?? dirname16(resolve5(filePath));
37945
+ const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
37845
37946
  await this.initialize(rootPath);
37846
37947
  }
37847
37948
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -38357,7 +38458,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
38357
38458
  } catch {
38358
38459
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
38359
38460
  }
38360
- if (!existsSync20(join23(projectPath, "project.godot"))) {
38461
+ if (!existsSync21(join24(projectPath, "project.godot"))) {
38361
38462
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
38362
38463
  }
38363
38464
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -38517,8 +38618,8 @@ var init_lsp_client = __esm(() => {
38517
38618
  });
38518
38619
 
38519
38620
  // src/rename-symbol.ts
38520
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
38521
- import { basename as basename6, dirname as dirname17, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
38621
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
38622
+ import { basename as basename7, dirname as dirname18, join as join25, relative as relative4, resolve as resolve6 } from "node:path";
38522
38623
  function resourcePathOf(projectPath, absolute) {
38523
38624
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
38524
38625
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -38531,13 +38632,13 @@ function onDiskSpelling(path) {
38531
38632
  let at = resolve6(path);
38532
38633
  for (;; ) {
38533
38634
  try {
38534
- return join24(realpathSync5.native(at), ...missing.reverse());
38635
+ return join25(realpathSync5.native(at), ...missing.reverse());
38535
38636
  } catch {
38536
- const parent = dirname17(at);
38637
+ const parent = dirname18(at);
38537
38638
  if (parent === at) {
38538
38639
  return resolve6(path);
38539
38640
  }
38540
- missing.push(basename6(at));
38641
+ missing.push(basename7(at));
38541
38642
  at = parent;
38542
38643
  }
38543
38644
  }
@@ -38584,7 +38685,7 @@ async function renameSymbol(request, services) {
38584
38685
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
38585
38686
  };
38586
38687
  }
38587
- if (existsSync21(request.newScriptPath)) {
38688
+ if (existsSync22(request.newScriptPath)) {
38588
38689
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
38589
38690
  }
38590
38691
  }
@@ -38760,7 +38861,7 @@ async function renameSymbol(request, services) {
38760
38861
  scripts,
38761
38862
  moves: new Map([[declaringPath, moveTo]]),
38762
38863
  named: [declaringPath],
38763
- exists: (path) => existsSync21(`${projectPath}/${path.slice("res://".length)}`)
38864
+ exists: (path) => existsSync22(`${projectPath}/${path.slice("res://".length)}`)
38764
38865
  }, plan);
38765
38866
  }
38766
38867
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -38959,15 +39060,15 @@ ${mention.kind}`;
38959
39060
  };
38960
39061
  }
38961
39062
  function writeLeftAloneDetail(projectPath, symbol, detail) {
38962
- const directory = join24(projectPath, ".godot", "gdharness-renames");
39063
+ const directory = join25(projectPath, ".godot", "gdharness-renames");
38963
39064
  mkdirSync12(directory, { recursive: true });
38964
39065
  detailWrites += 1;
38965
- const path = join24(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
39066
+ const path = join25(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
38966
39067
  writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
38967
39068
  `, "utf8");
38968
- const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
39069
+ const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
38969
39070
  for (const old of kept.slice(10)) {
38970
- rmSync12(join24(directory, old), { force: true });
39071
+ rmSync12(join25(directory, old), { force: true });
38971
39072
  }
38972
39073
  return path;
38973
39074
  }
@@ -39008,25 +39109,25 @@ var init_rename_symbol = __esm(() => {
39008
39109
 
39009
39110
  // src/file-move.ts
39010
39111
  import {
39011
- existsSync as existsSync22,
39112
+ existsSync as existsSync23,
39012
39113
  mkdirSync as mkdirSync13,
39013
- readdirSync as readdirSync11,
39114
+ readdirSync as readdirSync12,
39014
39115
  readFileSync as readFileSync22,
39015
39116
  renameSync as renameSync4,
39016
- statSync as statSync10,
39117
+ statSync as statSync11,
39017
39118
  writeFileSync as writeFileSync16
39018
39119
  } from "node:fs";
39019
- import { basename as basename7, dirname as dirname18, extname as extname3, join as join25, relative as relative5 } from "node:path";
39120
+ import { basename as basename8, dirname as dirname19, extname as extname3, join as join26, relative as relative5 } from "node:path";
39020
39121
  function filesUnder(directory) {
39021
- return readdirSync11(directory, { withFileTypes: true }).flatMap((entry) => {
39022
- const path = join25(directory, entry.name);
39122
+ return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
39123
+ const path = join26(directory, entry.name);
39023
39124
  return entry.isDirectory() ? filesUnder(path) : [path];
39024
39125
  });
39025
39126
  }
39026
39127
  function sameFile(one, other) {
39027
39128
  try {
39028
- const a = statSync10(one);
39029
- const b = statSync10(other);
39129
+ const a = statSync11(one);
39130
+ const b = statSync11(other);
39030
39131
  return a.ino === b.ino && a.dev === b.dev;
39031
39132
  } catch {
39032
39133
  return false;
@@ -39040,8 +39141,8 @@ function unseenByTheEngine(projectPath, path) {
39040
39141
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
39041
39142
  }
39042
39143
  if (at < steps.length - 1) {
39043
- directory = join25(directory, step);
39044
- if (existsSync22(join25(directory, ".gdignore"))) {
39144
+ directory = join26(directory, step);
39145
+ if (existsSync23(join26(directory, ".gdignore"))) {
39045
39146
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
39046
39147
  }
39047
39148
  }
@@ -39053,21 +39154,21 @@ function installedAddonOf(projectPath, path) {
39053
39154
  if (steps[0] !== "addons" || steps[1] === undefined) {
39054
39155
  return null;
39055
39156
  }
39056
- return existsSync22(join25(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
39157
+ return existsSync23(join26(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
39057
39158
  }
39058
39159
  function binaryResources(projectPath) {
39059
39160
  const found = [];
39060
39161
  const visit = (directory, prefix) => {
39061
- if (existsSync22(join25(directory, ".gdignore"))) {
39162
+ if (existsSync23(join26(directory, ".gdignore"))) {
39062
39163
  return;
39063
39164
  }
39064
- for (const entry of readdirSync11(directory, { withFileTypes: true })) {
39165
+ for (const entry of readdirSync12(directory, { withFileTypes: true })) {
39065
39166
  if (entry.name.startsWith(".")) {
39066
39167
  continue;
39067
39168
  }
39068
39169
  const path = `${prefix}${entry.name}`;
39069
39170
  if (entry.isDirectory()) {
39070
- visit(join25(directory, entry.name), `${path}/`);
39171
+ visit(join26(directory, entry.name), `${path}/`);
39071
39172
  } else if (/\.(scn|res)$/i.test(entry.name)) {
39072
39173
  found.push(path);
39073
39174
  }
@@ -39077,7 +39178,7 @@ function binaryResources(projectPath) {
39077
39178
  return found;
39078
39179
  }
39079
39180
  function targetPathOf(projectPath, absolute) {
39080
- const inside = relative5(onDiskSpelling(projectPath), join25(onDiskSpelling(dirname18(absolute)), basename7(absolute)));
39181
+ const inside = relative5(onDiskSpelling(projectPath), join26(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
39081
39182
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
39082
39183
  return null;
39083
39184
  }
@@ -39095,13 +39196,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39095
39196
  if (to === null) {
39096
39197
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
39097
39198
  }
39098
- if (!existsSync22(absoluteFrom)) {
39199
+ if (!existsSync23(absoluteFrom)) {
39099
39200
  return { ok: false, reason: `There is nothing at ${from} to move.` };
39100
39201
  }
39101
39202
  if (from === to) {
39102
39203
  return { ok: false, reason: `${from} is already there; nothing to move.` };
39103
39204
  }
39104
- const isDirectory = statSync10(absoluteFrom).isDirectory();
39205
+ const isDirectory = statSync11(absoluteFrom).isDirectory();
39105
39206
  if (from === "res://project.godot") {
39106
39207
  return {
39107
39208
  ok: false,
@@ -39124,7 +39225,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39124
39225
  if (isDirectory && to.startsWith(`${from}/`)) {
39125
39226
  return { ok: false, reason: `${from} cannot be moved into itself.` };
39126
39227
  }
39127
- if (existsSync22(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39228
+ if (existsSync23(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39128
39229
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
39129
39230
  }
39130
39231
  const unseen = unseenByTheEngine(projectPath, to);
@@ -39132,7 +39233,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39132
39233
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
39133
39234
  }
39134
39235
  if (!isDirectory) {
39135
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync22(join25(projectPath, path.slice("res://".length))));
39236
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync23(join26(projectPath, path.slice("res://".length))));
39136
39237
  if (owner !== undefined && owner !== null) {
39137
39238
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
39138
39239
  }
@@ -39154,7 +39255,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39154
39255
  } else {
39155
39256
  moves.set(from, to);
39156
39257
  for (const sidecar of SIDECARS) {
39157
- if (existsSync22(`${absoluteFrom}${sidecar}`)) {
39258
+ if (existsSync23(`${absoluteFrom}${sidecar}`)) {
39158
39259
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
39159
39260
  }
39160
39261
  }
@@ -39166,12 +39267,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39166
39267
  scripts,
39167
39268
  moves,
39168
39269
  named: [from],
39169
- exists: (path) => existsSync22(join25(projectPath, path.slice("res://".length)))
39270
+ exists: (path) => existsSync23(join26(projectPath, path.slice("res://".length)))
39170
39271
  }, { edits: [], mentions: [] });
39171
39272
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
39172
39273
  const binaries = [];
39173
39274
  for (const path of binaryResources(projectPath)) {
39174
- const bytes = readFileSync22(join25(projectPath, path.slice("res://".length)));
39275
+ const bytes = readFileSync22(join26(projectPath, path.slice("res://".length)));
39175
39276
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
39176
39277
  if (names !== undefined) {
39177
39278
  binaries.push({
@@ -39190,7 +39291,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39190
39291
  }));
39191
39292
  const left = leftAloneReport(mentions);
39192
39293
  const rewrites = rewritesOf(plan, texts, null);
39193
- const absoluteOf = (path) => join25(projectPath, path.slice("res://".length));
39294
+ const absoluteOf = (path) => join26(projectPath, path.slice("res://".length));
39194
39295
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
39195
39296
  const moved = [...moves].map(([source, target]) => {
39196
39297
  const rewrite = rewritten.get(source);
@@ -39237,19 +39338,19 @@ function applyFileMove(outcome) {
39237
39338
  }
39238
39339
  }
39239
39340
  for (const file of outcome.moved) {
39240
- if (!existsSync22(file.absoluteFrom)) {
39341
+ if (!existsSync23(file.absoluteFrom)) {
39241
39342
  throw new Error(`${file.from} is gone from disk; nothing was written`);
39242
39343
  }
39243
39344
  if (file.text !== null && readFileSync22(file.absoluteFrom, "utf8") !== file.text.before) {
39244
39345
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
39245
39346
  }
39246
- if (existsSync22(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39347
+ if (existsSync23(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39247
39348
  throw new Error(`${file.to} already exists; nothing was written`);
39248
39349
  }
39249
39350
  }
39250
39351
  const undo = [];
39251
39352
  const rename = (source, target) => {
39252
- mkdirSync13(dirname18(target), { recursive: true });
39353
+ mkdirSync13(dirname19(target), { recursive: true });
39253
39354
  renameSync4(source, target);
39254
39355
  undo.push(() => {
39255
39356
  renameSync4(target, source);
@@ -43435,25 +43536,25 @@ var DEBUG_PREFIX = "debug ";
43435
43536
  var init_keeper_channel = () => {};
43436
43537
 
43437
43538
  // src/lifecycle-log.ts
43438
- import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync11 } from "node:fs";
43439
- import { dirname as dirname19, join as join26 } from "node:path";
43440
- import process10 from "node:process";
43539
+ import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync12 } from "node:fs";
43540
+ import { dirname as dirname20, join as join27 } from "node:path";
43541
+ import process11 from "node:process";
43441
43542
  function lifecycleLogPath() {
43442
- return join26(runtimeDirectory(), "servers.log");
43543
+ return join27(runtimeDirectory(), "servers.log");
43443
43544
  }
43444
43545
  function writeLifecycle(event, fields = {}) {
43445
43546
  const path = lifecycleLogPath();
43446
43547
  try {
43447
- mkdirSync14(dirname19(path), { recursive: true });
43448
- if ((statSync11(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
43548
+ mkdirSync14(dirname20(path), { recursive: true });
43549
+ if ((statSync12(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
43449
43550
  try {
43450
43551
  renameSync5(path, `${path}.1`);
43451
43552
  } catch {}
43452
43553
  }
43453
43554
  const line = {
43454
43555
  at: new Date().toISOString(),
43455
- pid: process10.pid,
43456
- ppid: process10.ppid,
43556
+ pid: process11.pid,
43557
+ ppid: process11.ppid,
43457
43558
  version: SERVER_VERSION,
43458
43559
  event
43459
43560
  };
@@ -43760,14 +43861,14 @@ var init_prompts = __esm(() => {
43760
43861
 
43761
43862
  // src/restart-note.ts
43762
43863
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
43763
- import { dirname as dirname20, join as join27 } from "node:path";
43864
+ import { dirname as dirname21, join as join28 } from "node:path";
43764
43865
  function restartNotePath(projectPath) {
43765
- return join27(projectPath, ".godot", "gdharness-restart.json");
43866
+ return join28(projectPath, ".godot", "gdharness-restart.json");
43766
43867
  }
43767
43868
  function noteRestartBegun(note) {
43768
43869
  const path = restartNotePath(note.projectPath);
43769
43870
  try {
43770
- mkdirSync15(dirname20(path), { recursive: true });
43871
+ mkdirSync15(dirname21(path), { recursive: true });
43771
43872
  writeFileSync17(path, `${JSON.stringify(note, null, 2)}
43772
43873
  `, "utf8");
43773
43874
  } catch (error) {
@@ -43802,23 +43903,23 @@ var init_restart_note = __esm(() => {
43802
43903
 
43803
43904
  // src/run-record.ts
43804
43905
  import { createHash as createHash5 } from "node:crypto";
43805
- import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync12, readFileSync as readFileSync24, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync12 } from "node:fs";
43806
- import { basename as basename8, dirname as dirname21, join as join28, resolve as resolve7 } from "node:path";
43906
+ import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync13, readFileSync as readFileSync24, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync13 } from "node:fs";
43907
+ import { basename as basename9, dirname as dirname22, join as join29, resolve as resolve7 } from "node:path";
43807
43908
  function runsDirectory() {
43808
- return join28(runtimeDirectory(), "runs");
43909
+ return join29(runtimeDirectory(), "runs");
43809
43910
  }
43810
43911
  function realAsFarAsItExists(path) {
43811
43912
  const missing = [];
43812
43913
  let at = path;
43813
43914
  for (;; ) {
43814
43915
  try {
43815
- return join28(realpathSync6.native(at), ...missing);
43916
+ return join29(realpathSync6.native(at), ...missing);
43816
43917
  } catch {
43817
- const parent = dirname21(at);
43918
+ const parent = dirname22(at);
43818
43919
  if (parent === at) {
43819
43920
  return path;
43820
43921
  }
43821
- missing.unshift(basename8(at));
43922
+ missing.unshift(basename9(at));
43822
43923
  at = parent;
43823
43924
  }
43824
43925
  }
@@ -43829,10 +43930,10 @@ function projectKey(projectPath) {
43829
43930
  return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
43830
43931
  }
43831
43932
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
43832
- return join28(directory, "runs", `note-${projectKey(projectPath)}.json`);
43933
+ return join29(directory, "runs", `note-${projectKey(projectPath)}.json`);
43833
43934
  }
43834
43935
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
43835
- return join28(directory, "runs", `latest-${projectKey(servedBy)}.json`);
43936
+ return join29(directory, "runs", `latest-${projectKey(servedBy)}.json`);
43836
43937
  }
43837
43938
  function writeLatestRun(servedBy, latest) {
43838
43939
  try {
@@ -43857,17 +43958,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
43857
43958
  }
43858
43959
  function openTranscript(startedAt) {
43859
43960
  mkdirSync16(runsDirectory(), { recursive: true });
43860
- const path = join28(runsDirectory(), `run-${startedAt}.log`);
43961
+ const path = join29(runsDirectory(), `run-${startedAt}.log`);
43861
43962
  return { path, fd: openSync3(path, "a") };
43862
43963
  }
43863
43964
  function profilePath(startedAt) {
43864
- return join28(runsDirectory(), `run-${startedAt}.profile.json`);
43965
+ return join29(runsDirectory(), `run-${startedAt}.profile.json`);
43966
+ }
43967
+ function profileMarkPath(startedAt) {
43968
+ return join29(runsDirectory(), `run-${startedAt}.profile-mark.json`);
43865
43969
  }
43866
43970
  function noteIsAbout(note, playing) {
43867
43971
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
43868
43972
  }
43869
43973
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
43870
- return join28(directory, "runs", `editor-${projectKey(projectPath)}.json`);
43974
+ return join29(directory, "runs", `editor-${projectKey(projectPath)}.json`);
43871
43975
  }
43872
43976
  function writeEditorRunNote(note) {
43873
43977
  mkdirSync16(runsDirectory(), { recursive: true });
@@ -43878,7 +43982,7 @@ function writeEditorRunNote(note) {
43878
43982
  }
43879
43983
  function readEditorRunNote(projectPath) {
43880
43984
  for (const directory of runtimeDirectories()) {
43881
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join28(directory, "runs", "editor-run.json"));
43985
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join29(directory, "runs", "editor-run.json"));
43882
43986
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
43883
43987
  return note;
43884
43988
  }
@@ -43912,7 +44016,7 @@ function readRunRecord(projectPath) {
43912
44016
  if (own !== null) {
43913
44017
  return own;
43914
44018
  }
43915
- const shared = recordAt(join28(directory, "runs", SHARED_NOTE));
44019
+ const shared = recordAt(join29(directory, "runs", SHARED_NOTE));
43916
44020
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
43917
44021
  return shared;
43918
44022
  }
@@ -43924,13 +44028,13 @@ function everyRunRecord() {
43924
44028
  for (const directory of runtimeDirectories()) {
43925
44029
  let entries;
43926
44030
  try {
43927
- entries = readdirSync12(join28(directory, "runs"));
44031
+ entries = readdirSync13(join29(directory, "runs"));
43928
44032
  } catch {
43929
44033
  continue;
43930
44034
  }
43931
44035
  for (const entry of entries) {
43932
44036
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
43933
- const record = recordAt(join28(directory, "runs", entry));
44037
+ const record = recordAt(join29(directory, "runs", entry));
43934
44038
  if (record !== null) {
43935
44039
  found.push(record);
43936
44040
  }
@@ -44099,7 +44203,7 @@ function whyNotTheRun(record, running, needed) {
44099
44203
  if (after > SAME_RUN_WINDOW_MS) {
44100
44204
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
44101
44205
  }
44102
- const engine = record.command === undefined ? null : basename8(record.command);
44206
+ const engine = record.command === undefined ? null : basename9(record.command);
44103
44207
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
44104
44208
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
44105
44209
  if (wanted !== null && !said.includes(wanted)) {
@@ -44138,7 +44242,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
44138
44242
  }
44139
44243
  function clearRunRecord(projectPath) {
44140
44244
  for (const directory of runtimeDirectories()) {
44141
- const shared = join28(directory, "runs", SHARED_NOTE);
44245
+ const shared = join29(directory, "runs", SHARED_NOTE);
44142
44246
  const sharedRecord = recordAt(shared);
44143
44247
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
44144
44248
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -44150,7 +44254,7 @@ function clearRunRecord(projectPath) {
44150
44254
  }
44151
44255
  function ageMs(path, now) {
44152
44256
  try {
44153
- return now - statSync12(path).mtimeMs;
44257
+ return now - statSync13(path).mtimeMs;
44154
44258
  } catch {
44155
44259
  return 0;
44156
44260
  }
@@ -44159,20 +44263,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
44159
44263
  const directory = runsDirectory();
44160
44264
  const kept = new Set(everyRunRecord().flatMap((record) => [
44161
44265
  record.transcript,
44266
+ profileMarkPath(record.startedAt),
44162
44267
  ...record.profile === undefined ? [] : [record.profile]
44163
44268
  ]));
44164
44269
  let entries;
44165
44270
  try {
44166
- entries = readdirSync12(directory);
44271
+ entries = readdirSync13(directory);
44167
44272
  } catch {
44168
44273
  return;
44169
44274
  }
44170
44275
  for (const entry of entries) {
44171
- const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
44276
+ const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json") || entry.endsWith(".profile-mark.json"));
44172
44277
  if (!transcript && !entry.endsWith(".partial")) {
44173
44278
  continue;
44174
44279
  }
44175
- const path = join28(directory, entry);
44280
+ const path = join29(directory, entry);
44176
44281
  if (kept.has(path) || ageMs(path, now) < keepMs) {
44177
44282
  continue;
44178
44283
  }
@@ -44193,7 +44298,7 @@ var init_run_record = __esm(() => {
44193
44298
  });
44194
44299
 
44195
44300
  // src/script-origins.ts
44196
- import { join as join29 } from "node:path";
44301
+ import { join as join30 } from "node:path";
44197
44302
  function unresolvedClassIn(message) {
44198
44303
  return UNRESOLVED.exec(message)?.[1] ?? null;
44199
44304
  }
@@ -44246,7 +44351,7 @@ function checkWithEngine(godotPath, projectPath) {
44246
44351
  const ran = await runEngine(godotPath, [
44247
44352
  "--headless",
44248
44353
  "--log-file",
44249
- join29(logDir, "engine.log"),
44354
+ join30(logDir, "engine.log"),
44250
44355
  "--path",
44251
44356
  projectPath,
44252
44357
  "--check-only",
@@ -44339,6 +44444,52 @@ function isCalibration(signature) {
44339
44444
  const parts = signatureParts(signature);
44340
44445
  return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
44341
44446
  }
44447
+ function writeProfileMark(path, mark) {
44448
+ return writeWhole(path, JSON.stringify(mark));
44449
+ }
44450
+ function readProfileMark(path) {
44451
+ try {
44452
+ const parsed = JSON.parse(readFileSync25(path, "utf8"));
44453
+ return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
44454
+ } catch {
44455
+ return null;
44456
+ }
44457
+ }
44458
+ function totalsSince(now, mark) {
44459
+ if (now.frames < mark.frames || now.startedAt !== mark.startedAt) {
44460
+ return null;
44461
+ }
44462
+ let shrunk = 0;
44463
+ const functions = {};
44464
+ for (const [signature, sum] of Object.entries(now.functions)) {
44465
+ const before = mark.functions[signature];
44466
+ if (isCalibration(signature) || before === undefined) {
44467
+ functions[signature] = sum;
44468
+ continue;
44469
+ }
44470
+ const added = {
44471
+ calls: sum.calls - before.calls,
44472
+ selfSeconds: sum.selfSeconds - before.selfSeconds,
44473
+ totalSeconds: sum.totalSeconds - before.totalSeconds
44474
+ };
44475
+ if (added.calls < 0 || added.selfSeconds < 0 || added.totalSeconds < 0) {
44476
+ shrunk += 1;
44477
+ } else if (added.calls > 0) {
44478
+ functions[signature] = added;
44479
+ }
44480
+ }
44481
+ shrunk += Object.keys(mark.functions).filter((signature) => now.functions[signature] === undefined && (mark.functions[signature]?.calls ?? 0) > 0).length;
44482
+ return {
44483
+ totals: {
44484
+ ...now,
44485
+ frames: now.frames - mark.frames,
44486
+ unreadable: Math.max(0, now.unreadable - mark.unreadable),
44487
+ ...now.cappedFrames === undefined ? {} : { cappedFrames: Math.max(0, now.cappedFrames - (mark.cappedFrames ?? 0)) },
44488
+ functions
44489
+ },
44490
+ shrunk
44491
+ };
44492
+ }
44342
44493
  function perCallCostOf(totals) {
44343
44494
  for (const [signature, sum] of Object.entries(totals.functions)) {
44344
44495
  const parts = signatureParts(signature);
@@ -44471,18 +44622,18 @@ import {
44471
44622
  closeSync as closeSync3,
44472
44623
  constants,
44473
44624
  copyFileSync,
44474
- existsSync as existsSync23,
44625
+ existsSync as existsSync24,
44475
44626
  fstatSync,
44476
44627
  mkdirSync as mkdirSync17,
44477
44628
  openSync as openSync4,
44478
- readdirSync as readdirSync13,
44629
+ readdirSync as readdirSync14,
44479
44630
  readFileSync as readFileSync26,
44480
44631
  readSync as readSync2,
44481
44632
  rmdirSync as rmdirSync2,
44482
- statSync as statSync13,
44633
+ statSync as statSync14,
44483
44634
  writeFileSync as writeFileSync18
44484
44635
  } from "node:fs";
44485
- import { basename as basename9, dirname as dirname22, extname as extname4, isAbsolute as isAbsolute4, join as join30, normalize as normalize2, relative as relative6 } from "node:path";
44636
+ import { basename as basename10, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join31, normalize as normalize2, relative as relative6 } from "node:path";
44486
44637
  import { setTimeout as delay5 } from "node:timers/promises";
44487
44638
  import { fileURLToPath as fileURLToPath5 } from "node:url";
44488
44639
  function editorRestartTimeoutMs() {
@@ -44512,22 +44663,22 @@ function writtenFileRefusal(path, rule) {
44512
44663
  if (/^(res|user):\/\//.test(path)) {
44513
44664
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
44514
44665
  }
44515
- if (!isAbsolute4(path)) {
44666
+ if (!isAbsolute5(path)) {
44516
44667
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
44517
44668
  }
44518
44669
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
44519
44670
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
44520
44671
  }
44521
- const directory = realPathOr(dirname22(path));
44522
- if (!existsSync23(directory)) {
44523
- return `${named}'s directory ${dirname22(path)} does not exist; ${what} creates no directories.`;
44672
+ const directory = realPathOr(dirname23(path));
44673
+ if (!existsSync24(directory)) {
44674
+ return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
44524
44675
  }
44525
- const where = join30(directory, basename9(path));
44676
+ const where = join31(directory, basename10(path));
44526
44677
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
44527
44678
  if (inside !== undefined) {
44528
44679
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
44529
44680
  }
44530
- const taken = rule.writes(where).find((file) => existsSync23(file));
44681
+ const taken = rule.writes(where).find((file) => existsSync24(file));
44531
44682
  if (taken !== undefined) {
44532
44683
  return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
44533
44684
  }
@@ -44636,7 +44787,7 @@ function exited(run) {
44636
44787
  return run.exitCode !== null || run.exitSignal !== null;
44637
44788
  }
44638
44789
  function howItExited(run) {
44639
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
44790
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
44640
44791
  }
44641
44792
  function errorsUnread(run) {
44642
44793
  return run.throughEditor && typeof run.errorReport !== "string";
@@ -44744,7 +44895,7 @@ function endedWithoutACode(run) {
44744
44895
  function ourRunEnded(run, now) {
44745
44896
  const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
44746
44897
  const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
44747
- const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with code ${run.exitCode}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
44898
+ const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with ${exitCodeText(run.exitCode)}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
44748
44899
  return `No game with the runtime addon is running: the game this server ${which} ended${when}, and ${how}. editor_output has what it printed, and editor_run start runs it again.`;
44749
44900
  }
44750
44901
  function stopVerdict(stop) {
@@ -45063,7 +45214,7 @@ function notRunNote(notRun, failFast) {
45063
45214
  function exportPresetRefusal(projectPath, preset) {
45064
45215
  let text;
45065
45216
  try {
45066
- text = readFileSync26(join30(projectPath, "export_presets.cfg"), "utf8");
45217
+ text = readFileSync26(join31(projectPath, "export_presets.cfg"), "utf8");
45067
45218
  } catch {
45068
45219
  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.`;
45069
45220
  }
@@ -45085,7 +45236,7 @@ function exportPresetRefusal(projectPath, preset) {
45085
45236
  }
45086
45237
  return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
45087
45238
  }
45088
- function exportAnswer(ending, asked, before, after, beside = []) {
45239
+ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
45089
45240
  const { log } = ending;
45090
45241
  const written = after !== null && after !== before;
45091
45242
  const notCopied = librariesNotCopied(log.all);
@@ -45102,6 +45253,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
45102
45253
  librariesNotLoaded: notCopied,
45103
45254
  extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
45104
45255
  },
45256
+ ...docCacheNote === null ? {} : { docCacheNote },
45105
45257
  ...foldedEntriesAnswer(log)
45106
45258
  };
45107
45259
  if (verdict.exported) {
@@ -45126,9 +45278,9 @@ function writtenBeside(before, after, output, within) {
45126
45278
  function filesIn(folder) {
45127
45279
  const found = new Map;
45128
45280
  try {
45129
- for (const entry of readdirSync13(folder, { withFileTypes: true })) {
45281
+ for (const entry of readdirSync14(folder, { withFileTypes: true })) {
45130
45282
  if (entry.isFile()) {
45131
- found.set(entry.name, statSync13(join30(folder, entry.name)).mtimeMs);
45283
+ found.set(entry.name, statSync14(join31(folder, entry.name)).mtimeMs);
45132
45284
  }
45133
45285
  }
45134
45286
  } catch {}
@@ -45158,7 +45310,7 @@ function unsavedInEditorNote(scripts) {
45158
45310
  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.`;
45159
45311
  }
45160
45312
  function editorAddonMissing(projectPath) {
45161
- if (!existsSync23(join30(projectPath, "project.godot"))) {
45313
+ if (!existsSync24(join31(projectPath, "project.godot"))) {
45162
45314
  return null;
45163
45315
  }
45164
45316
  const report = inspectProject(projectPath);
@@ -45396,7 +45548,7 @@ function profileFrameFunctions(projectPath) {
45396
45548
  let projectGodot = null;
45397
45549
  if (projectPath !== null) {
45398
45550
  try {
45399
- projectGodot = readFileSync26(join30(projectPath, "project.godot"), "utf8");
45551
+ projectGodot = readFileSync26(join31(projectPath, "project.godot"), "utf8");
45400
45552
  } catch {}
45401
45553
  }
45402
45554
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -45570,7 +45722,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
45570
45722
  }
45571
45723
  function gdUnitFailsOnScriptErrors(projectPath) {
45572
45724
  try {
45573
- return settingKeys(readFileSync26(join30(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
45725
+ return settingKeys(readFileSync26(join31(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
45574
45726
  } catch {
45575
45727
  return true;
45576
45728
  }
@@ -45585,13 +45737,13 @@ function uncaughtNote(uncaught) {
45585
45737
  }
45586
45738
  function elsewhereIn(projectPath, asked) {
45587
45739
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
45588
- if (wanted === "" || existsSync23(join30(projectPath, wanted))) {
45740
+ if (wanted === "" || existsSync24(join31(projectPath, wanted))) {
45589
45741
  return "";
45590
45742
  }
45591
45743
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
45592
45744
  let near = [];
45593
45745
  try {
45594
- near = readdirSync13(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
45746
+ near = readdirSync14(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
45595
45747
  } catch {
45596
45748
  return "";
45597
45749
  }
@@ -45682,23 +45834,23 @@ function engineChecked(why) {
45682
45834
  }
45683
45835
  function isDirectory(path) {
45684
45836
  try {
45685
- return statSync13(path).isDirectory();
45837
+ return statSync14(path).isDirectory();
45686
45838
  } catch {
45687
45839
  return false;
45688
45840
  }
45689
45841
  }
45690
45842
  function scriptsUnder(directory, prefix) {
45691
- if (existsSync23(join30(directory, ".gdignore"))) {
45843
+ if (existsSync24(join31(directory, ".gdignore"))) {
45692
45844
  return [];
45693
45845
  }
45694
45846
  const found = [];
45695
- for (const entry of readdirSync13(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
45847
+ for (const entry of readdirSync14(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
45696
45848
  if (entry.name.startsWith(".")) {
45697
45849
  continue;
45698
45850
  }
45699
45851
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
45700
45852
  if (entry.isDirectory()) {
45701
- found.push(...scriptsUnder(join30(directory, entry.name), path));
45853
+ found.push(...scriptsUnder(join31(directory, entry.name), path));
45702
45854
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
45703
45855
  found.push(path);
45704
45856
  }
@@ -45714,7 +45866,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
45714
45866
  return false;
45715
45867
  }
45716
45868
  try {
45717
- return readFileSync26(join30(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
45869
+ return readFileSync26(join31(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
45718
45870
  } catch {
45719
45871
  return false;
45720
45872
  }
@@ -45739,7 +45891,7 @@ function needsItsOwnProcess(asked) {
45739
45891
  class GodotServer {
45740
45892
  stdio = null;
45741
45893
  locator = new GodotLocator;
45742
- operationsScript = join30(__dirname2, "godot", "operations", "godot_operations.gd");
45894
+ operationsScript = join31(__dirname2, "godot", "operations", "godot_operations.gd");
45743
45895
  godotBridge;
45744
45896
  tools = buildToolDefinitions();
45745
45897
  activeProcess = null;
@@ -46000,14 +46152,14 @@ class GodotServer {
46000
46152
  }
46001
46153
  noteTheStart() {
46002
46154
  const cwd = process.cwd();
46003
- const mcpJson = join30(cwd, ".mcp.json");
46155
+ const mcpJson = join31(cwd, ".mcp.json");
46004
46156
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
46005
46157
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
46006
46158
  writeLifecycle("started", {
46007
46159
  argv: process.argv.slice(1),
46008
46160
  cwd,
46009
46161
  project: this.ownProject,
46010
- mcpJson: existsSync23(mcpJson) ? mcpJson : null
46162
+ mcpJson: existsSync24(mcpJson) ? mcpJson : null
46011
46163
  });
46012
46164
  if (predecessor !== null) {
46013
46165
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -46148,7 +46300,7 @@ class GodotServer {
46148
46300
  return checked.response;
46149
46301
  }
46150
46302
  const named = args["projectPath"];
46151
- if (typeof named === "string" && existsSync23(join30(named, "project.godot"))) {
46303
+ if (typeof named === "string" && existsSync24(join31(named, "project.godot"))) {
46152
46304
  this.lastProjectPath = named;
46153
46305
  }
46154
46306
  this.updates.refresh();
@@ -46184,7 +46336,7 @@ class GodotServer {
46184
46336
  let notSaved;
46185
46337
  try {
46186
46338
  savedAt = answer.content.map((item, index) => {
46187
- const file = join30(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46339
+ const file = join31(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46188
46340
  writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
46189
46341
  return file;
46190
46342
  });
@@ -46802,8 +46954,8 @@ class GodotServer {
46802
46954
  if (path === undefined) {
46803
46955
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
46804
46956
  }
46805
- const file = join30(path, "project.godot");
46806
- if (!existsSync23(file)) {
46957
+ const file = join31(path, "project.godot");
46958
+ if (!existsSync24(file)) {
46807
46959
  return {
46808
46960
  ok: false,
46809
46961
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -46849,7 +47001,7 @@ class GodotServer {
46849
47001
  }
46850
47002
  runEnvironment(args) {
46851
47003
  const savesIn = readNonEmptyString(args, "savesIn");
46852
- if (savesIn !== undefined && !isAbsolute4(savesIn)) {
47004
+ if (savesIn !== undefined && !isAbsolute5(savesIn)) {
46853
47005
  return {
46854
47006
  ok: false,
46855
47007
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -47108,7 +47260,8 @@ class GodotServer {
47108
47260
  uidsCreated: given,
47109
47261
  stillWithoutUid: after,
47110
47262
  note: uidsLeftNote(after.length),
47111
- ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
47263
+ ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
47264
+ ...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
47112
47265
  }
47113
47266
  }, scanned));
47114
47267
  }
@@ -47347,6 +47500,9 @@ class GodotServer {
47347
47500
  if (imported.extensionNote !== undefined) {
47348
47501
  extra["extensionNote"] = imported.extensionNote;
47349
47502
  }
47503
+ if (imported.docCacheNote !== undefined) {
47504
+ extra["docCacheNote"] = imported.docCacheNote;
47505
+ }
47350
47506
  }
47351
47507
  const after = await this.operation("get_import_status", statusAsk, projectPath);
47352
47508
  if (!after.ok) {
@@ -47463,7 +47619,7 @@ class GodotServer {
47463
47619
  const name = application["config/name"];
47464
47620
  const mainScene = application["run/main_scene"];
47465
47621
  const info = {
47466
- name: typeof name === "string" && name !== "" ? name : basename9(project.value.path),
47622
+ name: typeof name === "string" && name !== "" ? name : basename10(project.value.path),
47467
47623
  path: project.value.path,
47468
47624
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
47469
47625
  godotVersion: await this.godotVersion(engine.value),
@@ -47546,18 +47702,18 @@ class GodotServer {
47546
47702
  if (!engine.ok) {
47547
47703
  return engine.response;
47548
47704
  }
47549
- const folder = dirname22(output.absolutePath);
47705
+ const folder = dirname23(output.absolutePath);
47550
47706
  try {
47551
47707
  mkdirSync17(folder, { recursive: true });
47552
47708
  } catch (error) {
47553
- return this.createErrorResponse(`Cannot create ${dirname22(output.relativePath)} to export into: ${errorMessage(error)}`);
47709
+ return this.createErrorResponse(`Cannot create ${dirname23(output.relativePath)} to export into: ${errorMessage(error)}`);
47554
47710
  }
47555
47711
  const debug = readBoolean(args, "debug") ?? false;
47556
47712
  const exportLogs = scratchDirectory("export");
47557
47713
  const exportArgs = [
47558
47714
  "--headless",
47559
47715
  "--log-file",
47560
- join30(exportLogs, "engine.log"),
47716
+ join31(exportLogs, "engine.log"),
47561
47717
  "--path",
47562
47718
  project.value.path,
47563
47719
  debug ? "--export-debug" : "--export-release",
@@ -47565,24 +47721,37 @@ class GodotServer {
47565
47721
  output.absolutePath
47566
47722
  ];
47567
47723
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
47568
- const writtenAt = () => existsSync23(output.absolutePath) ? statSync13(output.absolutePath).mtimeMs : null;
47569
- const before = writtenAt();
47570
- const listedBefore = filesIn(folder);
47571
- const freed = freeLibraryCopies(project.value.path);
47572
- if (freed.setAside.length > 0 || freed.swept.length > 0) {
47573
- this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
47574
- }
47575
- let ending;
47724
+ const writtenAt = () => existsSync24(output.absolutePath) ? statSync14(output.absolutePath).mtimeMs : null;
47725
+ const caches = editorCacheDirectory(engine.value);
47726
+ const exportOnce = async () => {
47727
+ const before = writtenAt();
47728
+ const listedBefore = filesIn(folder);
47729
+ const cachesBefore = docCachesWritten(caches);
47730
+ const freed = freeLibraryCopies(project.value.path);
47731
+ if (freed.setAside.length > 0 || freed.swept.length > 0) {
47732
+ this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
47733
+ }
47734
+ const ending = await runEngine(engine.value, exportArgs, {
47735
+ timeout: { ms: 300000, said: "five minutes" }
47736
+ });
47737
+ const crash = docCacheCrash(ending, cachesBefore, docCachesWritten(caches), "export");
47738
+ return { ending, before, listedBefore, crash };
47739
+ };
47740
+ let run;
47741
+ let docCacheNote;
47576
47742
  try {
47577
- ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
47743
+ const first = await exportOnce();
47744
+ docCacheNote = first.crash;
47745
+ run = first.crash === null ? first : await exportOnce();
47578
47746
  } finally {
47579
47747
  discard(exportLogs);
47580
47748
  }
47749
+ const { ending, before, listedBefore } = run;
47581
47750
  if (ending.exitSignal === null && ending.exitCode === null) {
47582
47751
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
47583
47752
  }
47584
- const beside = writtenBeside(listedBefore, filesIn(folder), basename9(output.absolutePath), dirname22(output.relativePath).replaceAll("\\", "/"));
47585
- return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
47753
+ const beside = writtenBeside(listedBefore, filesIn(folder), basename10(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
47754
+ return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
47586
47755
  }
47587
47756
  reportTestProgress(log) {
47588
47757
  const progress = callProgress();
@@ -47635,10 +47804,10 @@ class GodotServer {
47635
47804
  if (movie !== null) {
47636
47805
  return this.createErrorResponse(movie);
47637
47806
  }
47638
- const missing = paths.value.filter((path) => !existsSync23(join30(project.value.path, path.slice("res://".length))));
47807
+ const missing = paths.value.filter((path) => !existsSync24(join31(project.value.path, path.slice("res://".length))));
47639
47808
  const asked = paths.value.filter((path) => !missing.includes(path));
47640
47809
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
47641
- const noRunner = !existsSync23(join30(project.value.path, runner));
47810
+ const noRunner = !existsSync24(join31(project.value.path, runner));
47642
47811
  if (asked.length === 0) {
47643
47812
  const verdict = `nothing at ${missing.join(" and ")}`;
47644
47813
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -47729,21 +47898,21 @@ class GodotServer {
47729
47898
  await untilGone([run.process.pid]);
47730
47899
  }
47731
47900
  await run.settled;
47732
- const reportsDir = join30(project.value.path, ".godot", ...ours.split("/"));
47901
+ const reportsDir = join31(project.value.path, ".godot", ...ours.split("/"));
47733
47902
  let report = null;
47734
47903
  let reportProblem = null;
47735
47904
  try {
47736
- const written = existsSync23(reportsDir) ? readdirSync13(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
47905
+ const written = existsSync24(reportsDir) ? readdirSync14(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
47737
47906
  const newest = written.at(-1);
47738
47907
  if (newest !== undefined) {
47739
- report = parseJUnit(readFileSync26(join30(reportsDir, newest, "results.xml"), "utf8"));
47908
+ report = parseJUnit(readFileSync26(join31(reportsDir, newest, "results.xml"), "utf8"));
47740
47909
  }
47741
47910
  } catch (error) {
47742
47911
  reportProblem = errorMessage(error);
47743
47912
  } finally {
47744
47913
  discard(reportsDir, userData);
47745
47914
  try {
47746
- rmdirSync2(dirname22(reportsDir));
47915
+ rmdirSync2(dirname23(reportsDir));
47747
47916
  } catch {}
47748
47917
  }
47749
47918
  const engineEntries = foldedRepeats(run.log.select({
@@ -47775,7 +47944,7 @@ class GodotServer {
47775
47944
  const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
47776
47945
  const scriptErrors = [...declaring.origins, ...printedErrors];
47777
47946
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
47778
- const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
47947
+ const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
47779
47948
  const nothingAt = missing.join(" and ");
47780
47949
  const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
47781
47950
  const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
@@ -48102,7 +48271,7 @@ class GodotServer {
48102
48271
  const cached = cachedClasses(projectPath);
48103
48272
  const sourceOf = (resourcePath) => {
48104
48273
  const contained = resolveWithinProject(projectPath, resourcePath);
48105
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48274
+ if (!contained.ok || !existsSync24(contained.absolutePath)) {
48106
48275
  return null;
48107
48276
  }
48108
48277
  try {
@@ -48196,7 +48365,7 @@ class GodotServer {
48196
48365
  }
48197
48366
  sourceOfClass(projectPath, resourcePath) {
48198
48367
  const contained = resolveWithinProject(projectPath, resourcePath);
48199
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48368
+ if (!contained.ok || !existsSync24(contained.absolutePath)) {
48200
48369
  return null;
48201
48370
  }
48202
48371
  try {
@@ -48358,7 +48527,7 @@ class GodotServer {
48358
48527
  ...Array.isArray(stack) ? {
48359
48528
  stack: stack.map((frame) => ({
48360
48529
  ...frame,
48361
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join30(projectPath, frame["script"].slice("res://".length)) } : {}
48530
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join31(projectPath, frame["script"].slice("res://".length)) } : {}
48362
48531
  }))
48363
48532
  } : {}
48364
48533
  });
@@ -48637,7 +48806,7 @@ class GodotServer {
48637
48806
  return null;
48638
48807
  }
48639
48808
  try {
48640
- return settingKeys(readFileSync26(join30(projectPath, "project.godot"), "utf8"));
48809
+ return settingKeys(readFileSync26(join31(projectPath, "project.godot"), "utf8"));
48641
48810
  } catch {
48642
48811
  return null;
48643
48812
  }
@@ -49074,7 +49243,7 @@ class GodotServer {
49074
49243
  }
49075
49244
  async runtimeUp(projectPath, before, budget, withArgs) {
49076
49245
  const budgetMs = budget.ms;
49077
- if (!existsSync23(join30(projectPath, RUNTIME_AUTOLOAD.path))) {
49246
+ if (!existsSync24(join31(projectPath, RUNTIME_AUTOLOAD.path))) {
49078
49247
  return runtimeVerdict(null, {
49079
49248
  addon: false,
49080
49249
  budgetMs,
@@ -49160,7 +49329,7 @@ class GodotServer {
49160
49329
  }
49161
49330
  let announcedAt;
49162
49331
  try {
49163
- announcedAt = statSync13(endpoint.file).mtimeMs;
49332
+ announcedAt = statSync14(endpoint.file).mtimeMs;
49164
49333
  } catch {
49165
49334
  return;
49166
49335
  }
@@ -50050,9 +50219,13 @@ class GodotServer {
50050
50219
  if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
50051
50220
  return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
50052
50221
  }
50222
+ const asked = {
50223
+ mark: readBoolean(args, "mark") === true,
50224
+ since: readString(args, "since") === "mark"
50225
+ };
50053
50226
  const going = await this.runStillGoing(run);
50054
50227
  const projectPath = run.projectPath;
50055
- const projectGone = projectPath !== null && !existsSync23(join30(projectPath, "project.godot"));
50228
+ const projectGone = projectPath !== null && !existsSync24(join31(projectPath, "project.godot"));
50056
50229
  if (projectGone && callersName !== undefined) {
50057
50230
  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"]);
50058
50231
  }
@@ -50062,7 +50235,7 @@ class GodotServer {
50062
50235
  ...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
50063
50236
  };
50064
50237
  if (run.throughEditor) {
50065
- return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
50238
+ return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
50066
50239
  }
50067
50240
  const path = run.profile;
50068
50241
  if (path === undefined) {
@@ -50075,15 +50248,16 @@ class GodotServer {
50075
50248
  ]);
50076
50249
  }
50077
50250
  const totals = file.connected ? file : null;
50078
- return this.jsonTextResponse(profileAnswer({
50251
+ return this.profileReply(run, {
50079
50252
  through: "gdharness",
50080
50253
  running: going,
50081
50254
  totals,
50082
50255
  coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
50083
50256
  ...fromProject
50084
- }, limit));
50257
+ }, limit, asked);
50085
50258
  }
50086
- async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
50259
+ async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
50260
+ const { projectPath } = run;
50087
50261
  const read = await this.handleViaBridge("profile_read", {});
50088
50262
  if (read.isError === true) {
50089
50263
  return read;
@@ -50105,12 +50279,13 @@ class GodotServer {
50105
50279
  return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
50106
50280
  }
50107
50281
  const on = await this.firstProfiledFrame();
50282
+ const unmarked = asked.mark || asked.since ? " Nothing was marked, and nothing is counted from a mark: the profile begins now, so an answer without since covers the same span." : "";
50108
50283
  return this.jsonTextResponse({
50109
50284
  through: "editor",
50110
50285
  running: true,
50111
50286
  started: true,
50112
50287
  functions: [],
50113
- note: on ? "The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start." : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.`
50288
+ note: on ? `The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}` : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}`
50114
50289
  });
50115
50290
  }
50116
50291
  const functions = asParams(held["functions"]);
@@ -50133,13 +50308,70 @@ class GodotServer {
50133
50308
  ];
50134
50309
  }))
50135
50310
  } : null;
50136
- return this.jsonTextResponse(profileAnswer({
50311
+ return this.profileReply(run, {
50137
50312
  through: "editor",
50138
50313
  running: going,
50139
50314
  totals,
50140
50315
  coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
50141
50316
  ...fromProject
50142
- }, limit));
50317
+ }, limit, asked);
50318
+ }
50319
+ profileReply(run, reading, limit, asked) {
50320
+ const markPath = profileMarkPath(run.startedAt);
50321
+ const now = Date.now();
50322
+ let shown = reading;
50323
+ const lead = [];
50324
+ let sinceFields = {};
50325
+ if (asked.since) {
50326
+ const mark = readProfileMark(markPath);
50327
+ if (mark === null || mark.runStartedAt !== run.startedAt || mark.projectPath !== run.projectPath) {
50328
+ return this.createErrorResponse("No mark has been set in this run, so there is nothing to count from.", ["editor_run profile with mark: true sets one at the totals it answers with"]);
50329
+ }
50330
+ const added = reading.totals === null ? null : totalsSince(reading.totals, mark.totals);
50331
+ if (added === null) {
50332
+ return this.createErrorResponse("The profile holds less than it did at the mark, so the profiler was started again since, and what it holds now cannot be counted from the mark.", ["editor_run profile with mark: true sets a mark in the profile as it is now"]);
50333
+ }
50334
+ shown = {
50335
+ ...reading,
50336
+ totals: added.totals,
50337
+ coveredMs: Math.max(0, reading.coveredMs - mark.coveredMs)
50338
+ };
50339
+ const ago = now - mark.takenAt;
50340
+ sinceFields = {
50341
+ since: "mark",
50342
+ markSetMsAgo: ago,
50343
+ ...added.shrunk > 0 ? { lessThanAtMark: added.shrunk } : {}
50344
+ };
50345
+ lead.push(`Counted from the mark set ${ago}ms ago: each function's calls and milliseconds are what it added after the mark, frames and coveredMs are those since it, and a function that added no call is left out.`);
50346
+ if (added.shrunk > 0) {
50347
+ const one = added.shrunk === 1;
50348
+ lead.push(`${one ? "One function reads" : `${added.shrunk} functions read`} less than at the mark and ${one ? "is" : "are"} left out, counted under lessThanAtMark: the engine's own totals arrived after the mark and replaced the frames' sum, and capped totals leave out the functions with the least time.`);
50349
+ }
50350
+ }
50351
+ if (asked.mark) {
50352
+ if (reading.totals === null) {
50353
+ return this.createErrorResponse("There is nothing to mark yet: the game has not connected to the profiler.", ["Ask again in a moment, once the game is up"]);
50354
+ }
50355
+ const mark = {
50356
+ takenAt: now,
50357
+ projectPath: run.projectPath,
50358
+ runStartedAt: run.startedAt,
50359
+ coveredMs: reading.coveredMs,
50360
+ totals: reading.totals
50361
+ };
50362
+ if (!writeProfileMark(markPath, mark)) {
50363
+ return this.createErrorResponse(`The mark could not be written to ${markPath}, so none is set.`);
50364
+ }
50365
+ lead.push(asked.since ? 'The mark is moved to the totals as they are now, so the next since: "mark" counts from here.' : 'The mark is set at these totals: editor_run profile with since: "mark" answers only what is added after it.');
50366
+ }
50367
+ const answer = profileAnswer(shown, limit);
50368
+ const note = [...lead, ...typeof answer["note"] === "string" ? [answer["note"]] : []].join(" ");
50369
+ return this.jsonTextResponse({
50370
+ ...answer,
50371
+ ...sinceFields,
50372
+ ...asked.mark ? { marked: true } : {},
50373
+ note: note === "" ? undefined : note
50374
+ });
50143
50375
  }
50144
50376
  async firstProfiledFrame() {
50145
50377
  const deadline = Date.now() + PROFILER_ON_MS;
@@ -50340,7 +50572,7 @@ class GodotServer {
50340
50572
  notes.push(endedWithoutACode({
50341
50573
  throughEditor: run.throughEditor,
50342
50574
  announced: announced !== undefined || run.announcedPid !== undefined,
50343
- couldAnnounce: run.projectPath !== null && existsSync23(join30(run.projectPath, RUNTIME_AUTOLOAD.path)),
50575
+ couldAnnounce: run.projectPath !== null && existsSync24(join31(run.projectPath, RUNTIME_AUTOLOAD.path)),
50344
50576
  endedHere: typeof run.endedHere === "string",
50345
50577
  pickedUp: run.pickedUp === true
50346
50578
  }));
@@ -50970,7 +51202,7 @@ class GodotServer {
50970
51202
  if (!scriptPath.ok) {
50971
51203
  return this.createErrorResponse(scriptPath.reason);
50972
51204
  }
50973
- if (!existsSync23(scriptPath.absolutePath)) {
51205
+ if (!existsSync24(scriptPath.absolutePath)) {
50974
51206
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
50975
51207
  }
50976
51208
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -51552,7 +51784,7 @@ class GodotServer {
51552
51784
  async ownRunOnItsWay(projectPath, budgetMs) {
51553
51785
  const run = await this.currentRun();
51554
51786
  const project = projectPath ?? run?.projectPath ?? null;
51555
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync23(join30(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
51787
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync24(join31(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
51556
51788
  return null;
51557
51789
  }
51558
51790
  const already = runtimesAnnounced().running;
@@ -51561,7 +51793,7 @@ class GodotServer {
51561
51793
  return !run.announcedBefore.has(one.pid);
51562
51794
  }
51563
51795
  try {
51564
- return statSync13(one.file).mtimeMs >= run.startedAt;
51796
+ return statSync14(one.file).mtimeMs >= run.startedAt;
51565
51797
  } catch {
51566
51798
  return false;
51567
51799
  }
@@ -51642,7 +51874,7 @@ class GodotServer {
51642
51874
  }
51643
51875
  }
51644
51876
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
51645
- const screenshotPath = screenshotDir ? join30(screenshotDir, "capture.png") : null;
51877
+ const screenshotPath = screenshotDir ? join31(screenshotDir, "capture.png") : null;
51646
51878
  const staleNote = this.runtimeStaleNote(choice.endpoint);
51647
51879
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
51648
51880
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
@@ -51839,6 +52071,7 @@ var init_server = __esm(() => {
51839
52071
  init_cut_test_run();
51840
52072
  init_dap_client();
51841
52073
  init_desktop();
52074
+ init_doc_cache();
51842
52075
  init_editor_log();
51843
52076
  init_engine_run();
51844
52077
  init_errors();
@@ -51881,7 +52114,7 @@ var init_server = __esm(() => {
51881
52114
  init_tool_definitions();
51882
52115
  init_tool_reference();
51883
52116
  init_update_check();
51884
- __dirname2 = dirname22(fileURLToPath5(import.meta.url));
52117
+ __dirname2 = dirname23(fileURLToPath5(import.meta.url));
51885
52118
  KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
51886
52119
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
51887
52120
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
@@ -51941,8 +52174,8 @@ var init_server = __esm(() => {
51941
52174
  init_bridge_announce();
51942
52175
  init_errors();
51943
52176
  init_godot_path();
51944
- import { existsSync as existsSync24 } from "node:fs";
51945
- import { join as join31, resolve as resolve8 } from "node:path";
52177
+ import { existsSync as existsSync25 } from "node:fs";
52178
+ import { join as join32, resolve as resolve8 } from "node:path";
51946
52179
 
51947
52180
  // src/harnesses.ts
51948
52181
  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";
@@ -52877,10 +53110,10 @@ init_issues();
52877
53110
  init_paths();
52878
53111
 
52879
53112
  // src/prompt.ts
52880
- import process8 from "node:process";
53113
+ import process9 from "node:process";
52881
53114
  import { createInterface } from "node:readline/promises";
52882
53115
  function interactive() {
52883
- return process8.stdin.isTTY && process8.stdout.isTTY;
53116
+ return process9.stdin.isTTY && process9.stdout.isTTY;
52884
53117
  }
52885
53118
 
52886
53119
  class Ask {
@@ -52889,7 +53122,7 @@ class Ask {
52889
53122
  if (!interactive()) {
52890
53123
  return fallback;
52891
53124
  }
52892
- this.line ??= createInterface({ input: process8.stdin, output: process8.stdout });
53125
+ this.line ??= createInterface({ input: process9.stdin, output: process9.stdout });
52893
53126
  const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
52894
53127
  return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
52895
53128
  }
@@ -52907,10 +53140,10 @@ init_setup();
52907
53140
  init_setup();
52908
53141
  init_tool_definitions();
52909
53142
  init_tool_reference();
52910
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
52911
- import { basename as basename5, dirname as dirname7, join as join13 } from "node:path";
53143
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
53144
+ import { basename as basename6, dirname as dirname8, join as join14 } from "node:path";
52912
53145
  var SKILL_NAME = "gdharness";
52913
- var SHARED_SKILLS = join13(".agents", "skills");
53146
+ var SHARED_SKILLS = join14(".agents", "skills");
52914
53147
  function skillMarkdown(version) {
52915
53148
  return `---
52916
53149
  name: ${SKILL_NAME}
@@ -52997,7 +53230,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
52997
53230
  | What every label on a screen says | \`runtime_inspect tree\` with \`properties\` naming \`text\`, which reads it off every node that has one; \`includeProperties\` is every stored property of every node and ran to seventy-six thousand characters for one row |
52998
53231
  | Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
52999
53232
  | Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
53000
- | Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
53233
+ | Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. For one action, a press or a commit, set \`mark\` on a profile before it and pass \`since: "mark"\` after it, which answers only what the action added. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
53001
53234
  | End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
53002
53235
  | End a game of the project that no server is holding | \`editor_run stop\` with its \`pid\` from \`editor_status\` \`runtimes\`, never a process kill by hand: it is ended only when it announced itself for the project and its command line is the project's engine run |
53003
53236
  | Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
@@ -53135,27 +53368,27 @@ what they report while starting comes back under \`engine_messages_before_operat
53135
53368
  function skillFiles(version) {
53136
53369
  return new Map([
53137
53370
  ["SKILL.md", skillMarkdown(version)],
53138
- [join13("references", "tools.md"), renderToolsMarkdown()]
53371
+ [join14("references", "tools.md"), renderToolsMarkdown()]
53139
53372
  ]);
53140
53373
  }
53141
53374
  function skillDirectories(harnesses, projectPath, exists) {
53142
- const directories = [join13(projectPath, SHARED_SKILLS, SKILL_NAME)];
53375
+ const directories = [join14(projectPath, SHARED_SKILLS, SKILL_NAME)];
53143
53376
  for (const harness of harnesses) {
53144
53377
  if (harness.skills === undefined) {
53145
53378
  continue;
53146
53379
  }
53147
- const own = join13(projectPath, harness.skills.dir);
53148
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join13(own, SKILL_NAME))) {
53149
- directories.push(join13(own, SKILL_NAME));
53380
+ const own = join14(projectPath, harness.skills.dir);
53381
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join14(own, SKILL_NAME))) {
53382
+ directories.push(join14(own, SKILL_NAME));
53150
53383
  }
53151
53384
  }
53152
53385
  return directories;
53153
53386
  }
53154
53387
  function everySkillDirectory(projectPath, harnesses) {
53155
- const directories = new Set([join13(projectPath, SHARED_SKILLS, SKILL_NAME)]);
53388
+ const directories = new Set([join14(projectPath, SHARED_SKILLS, SKILL_NAME)]);
53156
53389
  for (const harness of harnesses) {
53157
53390
  if (harness.skills !== undefined) {
53158
- directories.add(join13(projectPath, harness.skills.dir, SKILL_NAME));
53391
+ directories.add(join14(projectPath, harness.skills.dir, SKILL_NAME));
53159
53392
  }
53160
53393
  }
53161
53394
  return [...directories];
@@ -53163,33 +53396,33 @@ function everySkillDirectory(projectPath, harnesses) {
53163
53396
  function removeSkill(directories, projectPath) {
53164
53397
  const removed = [];
53165
53398
  for (const directory of directories) {
53166
- if (existsSync13(directory)) {
53399
+ if (existsSync14(directory)) {
53167
53400
  rmSync6(directory, { recursive: true, force: true });
53168
- pruneEmpty(dirname7(directory), projectPath);
53401
+ pruneEmpty(dirname8(directory), projectPath);
53169
53402
  removed.push(directory);
53170
53403
  }
53171
53404
  }
53172
53405
  return removed;
53173
53406
  }
53174
53407
  function pruneEmpty(directory, projectPath) {
53175
- const shared = join13(projectPath, ".agents");
53408
+ const shared = join14(projectPath, ".agents");
53176
53409
  let path = directory;
53177
- while (path !== projectPath && (basename5(path) === "skills" || path === shared)) {
53178
- if (readdirSync8(path).length > 0) {
53410
+ while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
53411
+ if (readdirSync9(path).length > 0) {
53179
53412
  return;
53180
53413
  }
53181
53414
  rmSync6(path, { recursive: true, force: true });
53182
- path = dirname7(path);
53415
+ path = dirname8(path);
53183
53416
  }
53184
53417
  }
53185
53418
  function writeSkill(directories, version) {
53186
53419
  const files = skillFiles(version);
53187
53420
  return directories.map((directory) => {
53188
- const replaced = existsSync13(directory);
53421
+ const replaced = existsSync14(directory);
53189
53422
  rmSync6(directory, { recursive: true, force: true });
53190
53423
  for (const [name, contents] of files) {
53191
- const path = join13(directory, name);
53192
- mkdirSync5(dirname7(path), { recursive: true });
53424
+ const path = join14(directory, name);
53425
+ mkdirSync5(dirname8(path), { recursive: true });
53193
53426
  writeFileSync7(path, contents, "utf8");
53194
53427
  }
53195
53428
  return { path: directory, replaced };
@@ -53198,13 +53431,13 @@ function writeSkill(directories, version) {
53198
53431
 
53199
53432
  // src/version.ts
53200
53433
  import { readFileSync as readFileSync12 } from "node:fs";
53201
- import { dirname as dirname8, join as join14 } from "node:path";
53434
+ import { dirname as dirname9, join as join15 } from "node:path";
53202
53435
  import { fileURLToPath as fileURLToPath2 } from "node:url";
53203
53436
  function getLocalVersion() {
53204
- const currentDirectory = dirname8(fileURLToPath2(import.meta.url));
53437
+ const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
53205
53438
  const packageCandidates = [
53206
- join14(currentDirectory, "..", "package.json"),
53207
- join14(currentDirectory, "..", "..", "package.json")
53439
+ join15(currentDirectory, "..", "package.json"),
53440
+ join15(currentDirectory, "..", "..", "package.json")
53208
53441
  ];
53209
53442
  for (const packagePath of packageCandidates) {
53210
53443
  try {
@@ -53226,7 +53459,7 @@ class UsageError extends Refusal {
53226
53459
  function projectArgument(at) {
53227
53460
  const named = args.slice(at).find((value) => !value.startsWith("--"));
53228
53461
  const projectPath = resolve8(named ?? ".");
53229
- if (!existsSync24(join31(projectPath, "project.godot"))) {
53462
+ if (!existsSync25(join32(projectPath, "project.godot"))) {
53230
53463
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
53231
53464
  }
53232
53465
  return projectPath;
@@ -53271,7 +53504,7 @@ async function registerRuntime(godot, projectPath) {
53271
53504
  return;
53272
53505
  }
53273
53506
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
53274
- if (!existsSync24(join31(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53507
+ if (!existsSync25(join32(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53275
53508
  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}`);
53276
53509
  }
53277
53510
  }
@@ -53403,7 +53636,7 @@ async function setup() {
53403
53636
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
53404
53637
  }
53405
53638
  if (!args.includes("--no-skill")) {
53406
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync24);
53639
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync25);
53407
53640
  for (const written of writeSkill(directories, getLocalVersion())) {
53408
53641
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53409
53642
  }
@@ -53517,7 +53750,7 @@ async function upgrade() {
53517
53750
  moved.push(took.from);
53518
53751
  }
53519
53752
  }
53520
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync24), version)) {
53753
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync25), version)) {
53521
53754
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53522
53755
  }
53523
53756
  console.log(`