gdharness 1.2.36 → 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);
@@ -8487,6 +8567,7 @@ var init_scratch = __esm(() => {
8487
8567
  waiting = new Set;
8488
8568
  atExit = new Set;
8489
8569
  KINDS = [
8570
+ "answers",
8490
8571
  "desktop",
8491
8572
  "export",
8492
8573
  "import",
@@ -8497,8 +8578,8 @@ var init_scratch = __esm(() => {
8497
8578
  });
8498
8579
 
8499
8580
  // src/headless.ts
8500
- import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8501
- 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";
8502
8583
  function snakeCased(params) {
8503
8584
  const result = emptyRecord();
8504
8585
  for (const [key, value] of Object.entries(params)) {
@@ -8509,8 +8590,14 @@ function snakeCased(params) {
8509
8590
  }
8510
8591
  function engineExtras(outcome) {
8511
8592
  const after = outcome.afterAnswerMessages ?? [];
8593
+ const before = outcome.beforeOperationMessages ?? [];
8512
8594
  return {
8513
8595
  ...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
8596
+ ...before.length > 0 ? {
8597
+ engine_messages_before_operation: before,
8598
+ engine_messages_before_operation_note: BEFORE_OPERATION_NOTE
8599
+ } : {},
8600
+ ...outcome.beforeOperationOmitted === undefined ? {} : { engine_messages_before_operation_omitted: outcome.beforeOperationOmitted },
8514
8601
  ...outcome.messagesOmitted === undefined ? {} : { engine_messages_omitted: outcome.messagesOmitted },
8515
8602
  ...after.length > 0 ? { engine_messages_after_answer: after, engine_messages_after_answer_note: AFTER_ANSWER_NOTE } : {},
8516
8603
  ...outcome.afterAnswerOmitted === undefined ? {} : { engine_messages_after_answer_omitted: outcome.afterAnswerOmitted },
@@ -8553,7 +8640,7 @@ function refusalsIn(log) {
8553
8640
  }
8554
8641
  function scriptFault(problems, script) {
8555
8642
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
8556
- const directories = [...new Set([spelled(dirname5(script)), spelled(resolvedOr(dirname5(script)))])];
8643
+ const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
8557
8644
  return problems.find((entry) => {
8558
8645
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
8559
8646
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -8591,26 +8678,33 @@ async function runImport(godotPath, projectPath, options = {}) {
8591
8678
  };
8592
8679
  const first = await pass();
8593
8680
  const collided = librariesNotCopied(first.messages);
8594
- if (collided.length === 0) {
8681
+ const crash = first.docCacheNote;
8682
+ if (collided.length === 0 && crash === undefined) {
8595
8683
  return first;
8596
8684
  }
8597
- 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}` };
8598
8687
  const still = librariesNotCopied(second.messages);
8599
8688
  return {
8600
8689
  ...second,
8601
- librariesRetried: collided,
8602
- ...still.length === 0 ? {} : { librariesNotLoaded: still },
8603
- 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 }
8604
8696
  };
8605
8697
  }
8606
8698
  async function importOnce(godotPath, projectPath) {
8607
8699
  const logDir = scratchDirectory("import");
8700
+ const caches = editorCacheDirectory(godotPath);
8701
+ const cachesBefore = docCachesWritten(caches);
8608
8702
  let ran;
8609
8703
  try {
8610
8704
  ran = await runEngine(godotPath, [
8611
8705
  "--headless",
8612
8706
  "--log-file",
8613
- join8(logDir, "engine.log"),
8707
+ join9(logDir, "engine.log"),
8614
8708
  "--path",
8615
8709
  projectPath,
8616
8710
  "--import"
@@ -8619,24 +8713,26 @@ async function importOnce(godotPath, projectPath) {
8619
8713
  discard(logDir);
8620
8714
  }
8621
8715
  const problems = problemsIn(ran.log);
8716
+ const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
8622
8717
  if (ran.exitCode === 0 && ran.failure === null) {
8623
- return { ok: true, ...reported2(problems) };
8718
+ return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
8624
8719
  }
8625
8720
  return {
8626
8721
  ok: false,
8627
8722
  message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
8628
- ...reported2(problems)
8723
+ ...reported2(problems),
8724
+ ...crash === null ? {} : { docCacheNote: crash }
8629
8725
  };
8630
8726
  }
8631
8727
  async function runOperation(engine, operation, params, projectPath) {
8632
8728
  const paramsDir = scratchDirectory("params");
8633
- const paramsFile = join8(paramsDir, `${operation}.json`);
8634
- const answerFile = join8(paramsDir, "answer.json");
8729
+ const paramsFile = join9(paramsDir, `${operation}.json`);
8730
+ const answerFile = join9(paramsDir, "answer.json");
8635
8731
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
8636
8732
  const args = [
8637
8733
  "--headless",
8638
8734
  "--log-file",
8639
- join8(paramsDir, "engine.log"),
8735
+ join9(paramsDir, "engine.log"),
8640
8736
  "--path",
8641
8737
  projectPath,
8642
8738
  "--script",
@@ -8655,12 +8751,22 @@ async function runOperation(engine, operation, params, projectPath) {
8655
8751
  discard(paramsDir);
8656
8752
  }
8657
8753
  const everything = ran.log.everything();
8658
- const answered = everything.findIndex((entry) => entry.source === "stderr" && entry.text === ANSWERED);
8659
- const problems = problemsAmong(answered === -1 ? everything : everything.slice(0, answered));
8754
+ const marked = (text) => everything.findIndex((entry) => entry.source === "stderr" && entry.text === text);
8755
+ const answered = marked(ANSWERED);
8756
+ const waiting = marked(WAITING);
8757
+ const begun = marked(BEGUN);
8758
+ const ended = answered === -1 ? everything.length : answered;
8759
+ const problems = waiting === -1 ? problemsAmong(everything.slice(0, ended)) : problemsAmong([
8760
+ ...everything.slice(0, waiting),
8761
+ ...begun === -1 ? [] : everything.slice(begun + 1, ended)
8762
+ ]);
8763
+ const startup = waiting === -1 ? [] : problemsAmong(everything.slice(waiting + 1, begun === -1 ? ended : begun));
8660
8764
  const after = answered === -1 ? [] : problemsAmong(everything.slice(answered + 1));
8661
8765
  const afterAnswered = {
8662
8766
  ...after.length === 0 ? {} : { afterAnswerMessages: after.slice(-MESSAGES_KEPT) },
8663
- ...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {}
8767
+ ...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {},
8768
+ ...startup.length === 0 ? {} : { beforeOperationMessages: startup.slice(-MESSAGES_KEPT) },
8769
+ ...startup.length > MESSAGES_KEPT ? { beforeOperationOmitted: startup.length - MESSAGES_KEPT } : {}
8664
8770
  };
8665
8771
  if (answer.kind === "unreadable") {
8666
8772
  return {
@@ -8671,6 +8777,14 @@ async function runOperation(engine, operation, params, projectPath) {
8671
8777
  };
8672
8778
  }
8673
8779
  if (answer.kind === "none") {
8780
+ if (waiting !== -1 && begun === -1) {
8781
+ return {
8782
+ ok: false,
8783
+ message: `${operation} never ran: the engine ended (${howItEnded(ran)}) while it started the project's ` + `autoloads, which this operation waits for. The last it reported: ${reason(ran.log, startup)}`,
8784
+ ...reported2(problems),
8785
+ ...afterAnswered
8786
+ };
8787
+ }
8674
8788
  const refusals = refusalsIn(ran.log);
8675
8789
  if (refusals.length > 0) {
8676
8790
  const said = new Set(refusals.map((entry) => entry.index));
@@ -8706,7 +8820,7 @@ async function runOperation(engine, operation, params, projectPath) {
8706
8820
  };
8707
8821
  }
8708
8822
  function answerIn(file) {
8709
- if (!existsSync8(file)) {
8823
+ if (!existsSync9(file)) {
8710
8824
  return { kind: "none" };
8711
8825
  }
8712
8826
  const text = readFileSync5(file, "utf8");
@@ -8720,8 +8834,9 @@ function answerIn(file) {
8720
8834
  };
8721
8835
  }
8722
8836
  }
8723
- 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.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
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;
8724
8838
  var init_headless = __esm(() => {
8839
+ init_doc_cache();
8725
8840
  init_engine_run();
8726
8841
  init_library_copies();
8727
8842
  init_reimport();
@@ -8729,9 +8844,9 @@ var init_headless = __esm(() => {
8729
8844
  });
8730
8845
 
8731
8846
  // src/update-check.ts
8732
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8733
- import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
8734
- 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";
8735
8850
  function packageUrl(root) {
8736
8851
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
8737
8852
  }
@@ -8751,27 +8866,27 @@ function cacheDirectory(environment) {
8751
8866
  const value = environment[name];
8752
8867
  return value !== undefined && value !== "" ? value : null;
8753
8868
  };
8754
- const home = set("HOME") ?? homedir3();
8869
+ const home = set("HOME") ?? homedir4();
8755
8870
  if (process.platform === "win32") {
8756
- return join9(set("LOCALAPPDATA") ?? home, "gdharness");
8871
+ return join10(set("LOCALAPPDATA") ?? home, "gdharness");
8757
8872
  }
8758
8873
  if (process.platform === "darwin") {
8759
- return join9(home, "Library", "Caches", "gdharness");
8874
+ return join10(home, "Library", "Caches", "gdharness");
8760
8875
  }
8761
- return join9(set("XDG_CACHE_HOME") ?? join9(home, ".cache"), "gdharness");
8876
+ return join10(set("XDG_CACHE_HOME") ?? join10(home, ".cache"), "gdharness");
8762
8877
  }
8763
8878
  function cacheFile(environment = process.env) {
8764
8879
  try {
8765
8880
  const directory = cacheDirectory(environment);
8766
8881
  mkdirSync3(directory, { recursive: true, mode: 448 });
8767
- return join9(directory, "update-check.json");
8882
+ return join10(directory, "update-check.json");
8768
8883
  } catch {
8769
- return join9(tmpdir3(), "gdharness-update-check.json");
8884
+ return join10(tmpdir3(), "gdharness-update-check.json");
8770
8885
  }
8771
8886
  }
8772
8887
  function readCache(path) {
8773
8888
  try {
8774
- if (!existsSync9(path)) {
8889
+ if (!existsSync10(path)) {
8775
8890
  return null;
8776
8891
  }
8777
8892
  const parsed = JSON.parse(readFileSync6(path, "utf8"));
@@ -9006,7 +9121,7 @@ var init_server_version = __esm(() => {
9006
9121
 
9007
9122
  // src/issues.ts
9008
9123
  import { createHash as createHash2 } from "node:crypto";
9009
- import process7 from "node:process";
9124
+ import process8 from "node:process";
9010
9125
  function feedbackNotice(every) {
9011
9126
  return JSON.stringify({
9012
9127
  gdharness_feedback: {
@@ -9017,9 +9132,9 @@ function feedbackNotice(every) {
9017
9132
  }, null, 2);
9018
9133
  }
9019
9134
  function runtime() {
9020
- const versions = process7.versions;
9135
+ const versions = process8.versions;
9021
9136
  const bun = versions["bun"];
9022
- return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
9137
+ return bun === undefined ? `node ${process8.versions.node}` : `bun ${bun}`;
9023
9138
  }
9024
9139
  function defectSignature(where, message) {
9025
9140
  const normalised = `${where}
@@ -9031,7 +9146,7 @@ function facts(where, message, godotVersion) {
9031
9146
  ["Where", where],
9032
9147
  ["Error", message],
9033
9148
  ["Version", `gdharness ${SERVER_VERSION}`],
9034
- ["Runtime", `${runtime()}, ${process7.platform} ${process7.arch}`],
9149
+ ["Runtime", `${runtime()}, ${process8.platform} ${process8.arch}`],
9035
9150
  ["Godot", godotVersion ?? "not known at the point this failed"],
9036
9151
  ["Signature", defectSignature(where, message)]
9037
9152
  ];
@@ -9041,7 +9156,7 @@ function filledTemplate(where, message, godotVersion) {
9041
9156
  `**gdharness version**: ${SERVER_VERSION}`,
9042
9157
  `**Godot version**: ${godotVersion ?? ""}`,
9043
9158
  `**Bun version**: ${runtime()}`,
9044
- `**OS**: ${process7.platform} ${process7.arch}`,
9159
+ `**OS**: ${process8.platform} ${process8.arch}`,
9045
9160
  "**MCP client**:",
9046
9161
  "",
9047
9162
  "## What you did",
@@ -9781,8 +9896,8 @@ var init_gdscript_source = __esm(() => {
9781
9896
  });
9782
9897
 
9783
9898
  // src/class-cache.ts
9784
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
9785
- 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";
9786
9901
  function isToolScript(source) {
9787
9902
  return TOOL.test(codeOf(source));
9788
9903
  }
@@ -9798,14 +9913,14 @@ function declaredClasses(projectPath) {
9798
9913
  }
9799
9914
  function eachScript(projectPath, each) {
9800
9915
  const visit = (directory, prefix) => {
9801
- if (existsSync10(join10(directory, ".gdignore"))) {
9916
+ if (existsSync11(join11(directory, ".gdignore"))) {
9802
9917
  return;
9803
9918
  }
9804
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
9919
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
9805
9920
  if (entry.name.startsWith(".")) {
9806
9921
  continue;
9807
9922
  }
9808
- const path = join10(directory, entry.name);
9923
+ const path = join11(directory, entry.name);
9809
9924
  if (entry.isDirectory()) {
9810
9925
  visit(path, `${prefix}${entry.name}/`);
9811
9926
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -9831,8 +9946,8 @@ function scriptsNaming(projectPath, classes) {
9831
9946
  return naming.sort((a, b) => a.script.localeCompare(b.script));
9832
9947
  }
9833
9948
  function cachedClasses(projectPath) {
9834
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
9835
- if (!existsSync10(cache)) {
9949
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
9950
+ if (!existsSync11(cache)) {
9836
9951
  return null;
9837
9952
  }
9838
9953
  const listed = new Map;
@@ -10102,9 +10217,9 @@ function uncachedClassNote(types) {
10102
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.";
10103
10218
  }
10104
10219
  function cacheWrittenAt(projectPath) {
10105
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
10220
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
10106
10221
  try {
10107
- return statSync5(cache).mtimeMs;
10222
+ return statSync6(cache).mtimeMs;
10108
10223
  } catch {
10109
10224
  return null;
10110
10225
  }
@@ -10116,14 +10231,14 @@ function declaredSince(projectPath, paths, names, since) {
10116
10231
  return false;
10117
10232
  }
10118
10233
  try {
10119
- return statSync5(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
10234
+ return statSync6(join11(projectPath, path.slice("res://".length))).mtimeMs > since;
10120
10235
  } catch {
10121
10236
  return false;
10122
10237
  }
10123
10238
  });
10124
10239
  }
10125
10240
  function cachedAtMissingPaths(cached, projectPath) {
10126
- 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();
10127
10242
  }
10128
10243
  function staleAgainst(cached, projectPath) {
10129
10244
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -10148,8 +10263,8 @@ var init_class_cache = __esm(() => {
10148
10263
  });
10149
10264
 
10150
10265
  // src/project-scan.ts
10151
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
10152
- 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";
10153
10268
  function skipped(name) {
10154
10269
  return name.startsWith(".") || SKIPPED.has(name);
10155
10270
  }
@@ -10157,10 +10272,10 @@ function steppedOver(directory) {
10157
10272
  return gdignored(directory) || installedByUs(directory);
10158
10273
  }
10159
10274
  function gdignored(directory) {
10160
- return existsSync11(join11(directory, ".gdignore"));
10275
+ return existsSync12(join12(directory, ".gdignore"));
10161
10276
  }
10162
10277
  function installedByUs(directory) {
10163
- return existsSync11(join11(directory, VERSION_MARKER));
10278
+ return existsSync12(join12(directory, VERSION_MARKER));
10164
10279
  }
10165
10280
  function projectStructure(projectPath) {
10166
10281
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10168,12 +10283,12 @@ function projectStructure(projectPath) {
10168
10283
  if (steppedOver(directory)) {
10169
10284
  return;
10170
10285
  }
10171
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10286
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10172
10287
  if (skipped(entry.name)) {
10173
10288
  continue;
10174
10289
  }
10175
10290
  if (entry.isDirectory()) {
10176
- visit(join11(directory, entry.name));
10291
+ visit(join12(directory, entry.name));
10177
10292
  } else if (entry.isFile()) {
10178
10293
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10179
10294
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10197,13 +10312,13 @@ function projectFiles(projectPath, extensions) {
10197
10312
  if (steppedOver(directory)) {
10198
10313
  return;
10199
10314
  }
10200
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10315
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10201
10316
  if (skipped(entry.name)) {
10202
10317
  continue;
10203
10318
  }
10204
10319
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10205
10320
  if (entry.isDirectory()) {
10206
- visit(join11(directory, entry.name), spelled);
10321
+ visit(join12(directory, entry.name), spelled);
10207
10322
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10208
10323
  found.push(spelled);
10209
10324
  }
@@ -10218,20 +10333,20 @@ function scriptsWithoutUid(projectPath) {
10218
10333
  if (steppedOver(directory)) {
10219
10334
  return;
10220
10335
  }
10221
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10336
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10222
10337
  if (skipped(entry.name)) {
10223
10338
  continue;
10224
10339
  }
10225
10340
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10226
10341
  if (entry.isDirectory()) {
10227
- visit(join11(directory, entry.name), spelled);
10342
+ visit(join12(directory, entry.name), spelled);
10228
10343
  continue;
10229
10344
  }
10230
10345
  if (!entry.isFile()) {
10231
10346
  continue;
10232
10347
  }
10233
10348
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10234
- 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`))) {
10235
10350
  found.push(spelled);
10236
10351
  }
10237
10352
  }
@@ -10248,11 +10363,11 @@ function projectTexts(projectPath) {
10248
10363
  if (steppedOver(directory)) {
10249
10364
  return;
10250
10365
  }
10251
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10366
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10252
10367
  if (skipped(entry.name)) {
10253
10368
  continue;
10254
10369
  }
10255
- const absolute = join11(directory, entry.name);
10370
+ const absolute = join12(directory, entry.name);
10256
10371
  const spelled = `${prefix}${entry.name}`;
10257
10372
  if (entry.isDirectory()) {
10258
10373
  visit(absolute, `${spelled}/`);
@@ -10278,11 +10393,11 @@ function ignoredTexts(projectPath) {
10278
10393
  return;
10279
10394
  }
10280
10395
  const under = ignored || gdignored(directory);
10281
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10396
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10282
10397
  if (skipped(entry.name)) {
10283
10398
  continue;
10284
10399
  }
10285
- const absolute = join11(directory, entry.name);
10400
+ const absolute = join12(directory, entry.name);
10286
10401
  const spelled = `${prefix}${entry.name}`;
10287
10402
  if (entry.isDirectory()) {
10288
10403
  visit(absolute, `${spelled}/`, under);
@@ -10315,14 +10430,14 @@ function searchProject(projectPath, options) {
10315
10430
  if (steppedOver(directory)) {
10316
10431
  return;
10317
10432
  }
10318
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10433
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10319
10434
  if (full()) {
10320
10435
  return;
10321
10436
  }
10322
10437
  if (skipped(entry.name)) {
10323
10438
  continue;
10324
10439
  }
10325
- const entryPath = join11(directory, entry.name);
10440
+ const entryPath = join12(directory, entry.name);
10326
10441
  if (entry.isDirectory()) {
10327
10442
  visit(entryPath);
10328
10443
  continue;
@@ -10767,16 +10882,16 @@ function readParams(params, key) {
10767
10882
  import { createHash as createHash3 } from "node:crypto";
10768
10883
  import {
10769
10884
  cpSync,
10770
- existsSync as existsSync12,
10885
+ existsSync as existsSync13,
10771
10886
  mkdirSync as mkdirSync4,
10772
- readdirSync as readdirSync7,
10887
+ readdirSync as readdirSync8,
10773
10888
  readFileSync as readFileSync11,
10774
10889
  rmdirSync,
10775
10890
  rmSync as rmSync5,
10776
- statSync as statSync6,
10891
+ statSync as statSync7,
10777
10892
  writeFileSync as writeFileSync6
10778
10893
  } from "node:fs";
10779
- import { dirname as dirname6, join as join12 } from "node:path";
10894
+ import { dirname as dirname7, join as join13 } from "node:path";
10780
10895
  import { fileURLToPath } from "node:url";
10781
10896
  function editorAddonDigest(from = shippedAddonsDirectory()) {
10782
10897
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -10787,13 +10902,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
10787
10902
  function addonsDigest(names, from) {
10788
10903
  const hash = createHash3("sha256");
10789
10904
  const walk = (directory, relative) => {
10790
- for (const name of readdirSync7(directory).sort()) {
10905
+ for (const name of readdirSync8(directory).sort()) {
10791
10906
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
10792
10907
  continue;
10793
10908
  }
10794
- const path = join12(directory, name);
10909
+ const path = join13(directory, name);
10795
10910
  const inside = `${relative}/${name}`;
10796
- if (statSync6(path).isDirectory()) {
10911
+ if (statSync7(path).isDirectory()) {
10797
10912
  walk(path, inside);
10798
10913
  } else {
10799
10914
  hash.update(`${inside}
@@ -10805,7 +10920,7 @@ function addonsDigest(names, from) {
10805
10920
  }
10806
10921
  };
10807
10922
  for (const name of names) {
10808
- walk(join12(from, name), name);
10923
+ walk(join13(from, name), name);
10809
10924
  }
10810
10925
  return hash.digest("hex");
10811
10926
  }
@@ -10836,44 +10951,44 @@ function installedRuntimeDigest(projectPath) {
10836
10951
  return digestMarker(projectPath, RUNTIME_ADDON);
10837
10952
  }
10838
10953
  function digestMarker(projectPath, addon) {
10839
- const marker = join12(projectPath, "addons", addon, DIGEST_MARKER);
10840
- if (!existsSync12(marker)) {
10954
+ const marker = join13(projectPath, "addons", addon, DIGEST_MARKER);
10955
+ if (!existsSync13(marker)) {
10841
10956
  return null;
10842
10957
  }
10843
10958
  const said = readFileSync11(marker, "utf8").trim();
10844
10959
  return said === "" ? null : said;
10845
10960
  }
10846
10961
  function shippedAddonsDirectory() {
10847
- return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "addons");
10962
+ return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
10848
10963
  }
10849
10964
  function shippedOperationsScript() {
10850
- 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");
10851
10966
  }
10852
10967
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
10853
10968
  const installed = [];
10854
10969
  for (const name of ADDONS) {
10855
- const source = join12(from, name);
10856
- 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"))) {
10857
10972
  throw new Error(`The package holds no ${name} addon at ${source}.`);
10858
10973
  }
10859
- const target = join12(projectPath, "addons", name);
10860
- const replaced = existsSync12(target);
10974
+ const target = join13(projectPath, "addons", name);
10975
+ const replaced = existsSync13(target);
10861
10976
  rmSync5(target, { recursive: true, force: true });
10862
- mkdirSync4(dirname6(target), { recursive: true });
10977
+ mkdirSync4(dirname7(target), { recursive: true });
10863
10978
  cpSync(source, target, { recursive: true });
10864
- writeFileSync6(join12(target, VERSION_MARKER), `${SERVER_VERSION}
10979
+ writeFileSync6(join13(target, VERSION_MARKER), `${SERVER_VERSION}
10865
10980
  `);
10866
10981
  installed.push({ name, path: target, replaced });
10867
10982
  }
10868
- writeFileSync6(join12(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10983
+ writeFileSync6(join13(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10869
10984
  `);
10870
- writeFileSync6(join12(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10985
+ writeFileSync6(join13(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10871
10986
  `);
10872
10987
  return installed;
10873
10988
  }
10874
10989
  function installedAddonVersion(projectPath) {
10875
- const marker = join12(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10876
- if (!existsSync12(marker)) {
10990
+ const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10991
+ if (!existsSync13(marker)) {
10877
10992
  return null;
10878
10993
  }
10879
10994
  const said = readFileSync11(marker, "utf8").trim();
@@ -10882,14 +10997,14 @@ function installedAddonVersion(projectPath) {
10882
10997
  function removeAddons(projectPath) {
10883
10998
  const removed = [];
10884
10999
  for (const name of ADDONS) {
10885
- const target = join12(projectPath, "addons", name);
10886
- if (existsSync12(target)) {
11000
+ const target = join13(projectPath, "addons", name);
11001
+ if (existsSync13(target)) {
10887
11002
  rmSync5(target, { recursive: true, force: true });
10888
11003
  removed.push(target);
10889
11004
  }
10890
11005
  }
10891
- const addons = join12(projectPath, "addons");
10892
- 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) {
10893
11008
  rmdirSync(addons);
10894
11009
  removed.push(addons);
10895
11010
  }
@@ -10923,7 +11038,7 @@ function loaderAmong(projectPath, named) {
10923
11038
  }
10924
11039
  let source = null;
10925
11040
  try {
10926
- source = readFileSync11(join12(projectPath, path), "utf8");
11041
+ source = readFileSync11(join13(projectPath, path), "utf8");
10927
11042
  } catch {}
10928
11043
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
10929
11044
  return { name: entry, path, exists: true };
@@ -10965,10 +11080,10 @@ function enabledPlugins(settings) {
10965
11080
  function inspectProject(projectPath) {
10966
11081
  const problems = [];
10967
11082
  const addons = ADDONS.map((name) => {
10968
- const directory = join12(projectPath, "addons", name);
10969
- const marker = join12(directory, VERSION_MARKER);
10970
- const installed = existsSync12(directory);
10971
- 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;
10972
11087
  const current = version === SERVER_VERSION;
10973
11088
  if (!installed) {
10974
11089
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -10977,7 +11092,7 @@ function inspectProject(projectPath) {
10977
11092
  }
10978
11093
  return { name, installed, version, current };
10979
11094
  });
10980
- const settings = parseProjectGodot(readFileSync11(join12(projectPath, "project.godot"), "utf8"));
11095
+ const settings = parseProjectGodot(readFileSync11(join13(projectPath, "project.godot"), "utf8"));
10981
11096
  const pluginsEnabled = enabledPlugins(settings);
10982
11097
  for (const name of EDITOR_PLUGINS) {
10983
11098
  if (!pluginsEnabled.includes(name)) {
@@ -10993,7 +11108,7 @@ function inspectProject(projectPath) {
10993
11108
  const tracked = trackedByGit(projectPath, [...named.values()]);
10994
11109
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
10995
11110
  for (const [name, path] of named) {
10996
- if (!existsSync12(join12(projectPath, path))) {
11111
+ if (!existsSync13(join13(projectPath, path))) {
10997
11112
  if (!addonMissing(path)) {
10998
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`);
10999
11114
  }
@@ -11141,7 +11256,7 @@ function annotationsOf(spec) {
11141
11256
  openWorldHint: false
11142
11257
  };
11143
11258
  }
11144
- var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
11259
+ var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. An editor of the project that is running and has not reached this server is waited for rather than written around.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
11145
11260
  var init_tool_definitions = __esm(() => {
11146
11261
  init_launch();
11147
11262
  PROJECT_INFO_SECTIONS = [
@@ -11179,7 +11294,7 @@ var init_tool_definitions = __esm(() => {
11179
11294
  };
11180
11295
  PROPERTIES = {
11181
11296
  type: "object",
11182
- description: 'Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds.',
11297
+ description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. ${WHOLE_NUMBERS}`,
11183
11298
  additionalProperties: true
11184
11299
  };
11185
11300
  XY = {
@@ -11223,7 +11338,10 @@ var init_tool_definitions = __esm(() => {
11223
11338
  name: { type: "string" },
11224
11339
  params: { type: "string", description: 'Functions and signals: the parameter list, "delta: float".' },
11225
11340
  returnType: { type: "string", description: "Functions: the return type." },
11226
- body: { type: "string", description: "Functions: the body." },
11341
+ body: {
11342
+ type: "string",
11343
+ description: "Functions: the body, its lines separated by \\n. Indent shared by every line is taken off and one tab put on, so it can be written indented or not."
11344
+ },
11227
11345
  varType: {
11228
11346
  type: "string",
11229
11347
  description: "Variables and constants: the type annotation; without one, the type the value evaluates to."
@@ -11326,7 +11444,7 @@ var init_tool_definitions = __esm(() => {
11326
11444
  {
11327
11445
  name: "project_settings",
11328
11446
  title: "Project settings",
11329
- description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change touches only its own lines. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
11447
+ description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
11330
11448
  parameters: {
11331
11449
  projectPath: PROJECT_PATH,
11332
11450
  setting: {
@@ -11504,24 +11622,32 @@ var init_tool_definitions = __esm(() => {
11504
11622
  description: "The import pipeline: what needs importing, how a resource is imported, reimports, UIDs, and the global class list the editor and the engine read.",
11505
11623
  parameters: {
11506
11624
  projectPath: PROJECT_PATH,
11507
- resourcePath: RESOURCE_PATH,
11625
+ resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
11508
11626
  includeUpToDate: {
11509
11627
  type: "boolean",
11628
+ ops: ["status"],
11510
11629
  description: "status: list resources that are current as well. Default false."
11511
11630
  },
11512
11631
  options: {
11513
11632
  type: "object",
11633
+ ops: ["set_options"],
11514
11634
  description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads.`,
11515
11635
  additionalProperties: true
11516
11636
  },
11517
11637
  reimport: {
11518
11638
  type: "boolean",
11639
+ ops: ["set_options"],
11519
11640
  description: "set_options: reimport afterwards, answered under reimport as project_import reimport answers. Default true."
11520
11641
  },
11521
- force: { type: "boolean", description: "reimport: reimport even what is current. Default false." },
11642
+ force: {
11643
+ type: "boolean",
11644
+ ops: ["reimport"],
11645
+ description: "reimport: reimport even what is current. Default false."
11646
+ },
11522
11647
  timeoutMs: {
11523
11648
  type: "integer",
11524
11649
  minimum: 1,
11650
+ ops: ["reimport", "set_options"],
11525
11651
  description: "reimport, set_options: how long to wait for the editor to finish reimporting. Default 300000."
11526
11652
  }
11527
11653
  },
@@ -11599,7 +11725,7 @@ var init_tool_definitions = __esm(() => {
11599
11725
  name: "project_test",
11600
11726
  title: "Run tests",
11601
11727
  effect: "additive",
11602
- description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, every report under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
11728
+ description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
11603
11729
  parameters: {
11604
11730
  projectPath: PROJECT_PATH,
11605
11731
  path: {
@@ -11636,14 +11762,14 @@ var init_tool_definitions = __esm(() => {
11636
11762
  {
11637
11763
  name: "scene_create",
11638
11764
  title: "Create or save a scene",
11639
- description: "Creates a scene file where none is, saves one, or saves a copy under a new path. A save of a scene open in the editor saves the editor's copy, unsaved changes included. Needs the editor connected.",
11765
+ description: `Creates a scene file where none is, saves one, or saves a copy under a new path. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
11640
11766
  parameters: {
11641
11767
  projectPath: PROJECT_PATH,
11642
11768
  scenePath: SCENE_PATH,
11643
11769
  rootNodeType: {
11644
11770
  type: "string",
11645
11771
  ops: ["create"],
11646
- 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."
11647
11773
  },
11648
11774
  rootNodeName: {
11649
11775
  type: "string",
@@ -11681,29 +11807,32 @@ var init_tool_definitions = __esm(() => {
11681
11807
  {
11682
11808
  name: "scene_node",
11683
11809
  title: "Scene nodes",
11684
- 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. set with name 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. Needs the editor connected.",
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}`,
11685
11811
  parameters: {
11686
11812
  projectPath: PROJECT_PATH,
11687
11813
  scenePath: SCENE_PATH,
11688
- nodePath: NODE_PATH,
11814
+ nodePath: { ...NODE_PATH, ops: ["get", "set", "duplicate", "reparent", "delete", "set_tilemap_cells"] },
11689
11815
  parentNodePath: {
11690
11816
  type: "string",
11817
+ ops: ["add", "duplicate"],
11691
11818
  description: "add: where the node goes. Default the root. duplicate: where the copy goes."
11692
11819
  },
11693
11820
  nodeType: {
11694
11821
  type: "string",
11695
- description: 'add: the node class, such as "CharacterBody2D". Give this or instancePath.'
11822
+ ops: ["add"],
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.'
11696
11824
  },
11697
11825
  instancePath: {
11698
11826
  type: "string",
11699
11827
  ops: ["add"],
11700
11828
  description: `add: a scene to instance as the new node instead of a class, such as "ui/panel.tscn". It is saved as an instance of that file, the way the editor's Instantiate Child Scene saves one, so changes to that scene reach it; properties set its root. A scene that is the target or instances it is refused.`
11701
11829
  },
11702
- nodeName: { type: "string", description: "add: the new node's name." },
11703
- properties: PROPERTIES,
11704
- newName: { type: "string", description: "duplicate: the copy's name." },
11830
+ nodeName: { type: "string", ops: ["add"], description: "add: the new node's name." },
11831
+ properties: { ...PROPERTIES, ops: ["add", "set"] },
11832
+ newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
11705
11833
  newParentPath: {
11706
11834
  type: "string",
11835
+ ops: ["reparent"],
11707
11836
  description: "reparent: the new parent, which may be the one it has, to move it among its siblings."
11708
11837
  },
11709
11838
  index: {
@@ -11714,13 +11843,15 @@ var init_tool_definitions = __esm(() => {
11714
11843
  },
11715
11844
  includeDefaults: {
11716
11845
  type: "boolean",
11846
+ ops: ["get"],
11717
11847
  description: "get: include properties still at their default. Default false."
11718
11848
  },
11719
11849
  layer: {
11720
11850
  type: "number",
11851
+ ops: ["set_tilemap_cells"],
11721
11852
  description: "set_tilemap_cells: the layer of a TileMap. Default 0. A TileMapLayer is one layer."
11722
11853
  },
11723
- cells: TILEMAP_CELLS
11854
+ cells: { ...TILEMAP_CELLS, ops: ["set_tilemap_cells"] }
11724
11855
  },
11725
11856
  requires: ["projectPath", "scenePath"],
11726
11857
  operations: {
@@ -11760,16 +11891,32 @@ var init_tool_definitions = __esm(() => {
11760
11891
  {
11761
11892
  name: "scene_signal",
11762
11893
  title: "Scene signals",
11763
- description: "Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. Needs the editor connected.",
11894
+ description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. ${WITHOUT_AN_EDITOR}`,
11764
11895
  parameters: {
11765
11896
  projectPath: PROJECT_PATH,
11766
11897
  scenePath: SCENE_PATH,
11767
- sourceNodePath: { type: "string", description: "The node that emits." },
11768
- signalName: { type: "string", description: 'The signal it emits, such as "pressed".' },
11769
- targetNodePath: { type: "string", description: "The node whose method is called." },
11770
- methodName: { type: "string", description: "The method on that node, called when it emits." },
11771
- flags: { type: "number", description: "connect: Object.ConnectFlags, such as 1 for deferred." },
11772
- nodePath: { type: "string", description: "list: only connections involving this node." }
11898
+ sourceNodePath: { type: "string", ops: ["connect", "disconnect"], description: "The node that emits." },
11899
+ signalName: {
11900
+ type: "string",
11901
+ ops: ["connect", "disconnect"],
11902
+ description: 'The signal it emits, such as "pressed".'
11903
+ },
11904
+ targetNodePath: {
11905
+ type: "string",
11906
+ ops: ["connect", "disconnect"],
11907
+ description: "The node whose method is called."
11908
+ },
11909
+ methodName: {
11910
+ type: "string",
11911
+ ops: ["connect", "disconnect"],
11912
+ description: "The method on that node, called when it emits."
11913
+ },
11914
+ flags: {
11915
+ type: "number",
11916
+ ops: ["connect"],
11917
+ description: "connect: Object.ConnectFlags, such as 1 for deferred."
11918
+ },
11919
+ nodePath: { type: "string", ops: ["list"], description: "list: only connections involving this node." }
11773
11920
  },
11774
11921
  requires: ["projectPath", "scenePath"],
11775
11922
  operations: {
@@ -11894,18 +12041,28 @@ var init_tool_definitions = __esm(() => {
11894
12041
  parameters: {
11895
12042
  projectPath: PROJECT_PATH,
11896
12043
  scriptPath: SCRIPT_PATH,
11897
- className: { type: "string", description: "create: a class_name for the script. Not with content." },
11898
- extends: { type: "string", description: "create: the base class. Default Node. Not with content." },
12044
+ className: {
12045
+ type: "string",
12046
+ ops: ["create"],
12047
+ description: "create: a class_name for the script. Not with content."
12048
+ },
12049
+ extends: {
12050
+ type: "string",
12051
+ ops: ["create"],
12052
+ description: "create: the base class. Default Node. Not with content."
12053
+ },
11899
12054
  content: {
11900
12055
  type: "string",
12056
+ ops: ["create"],
11901
12057
  description: "create: the whole file, written as given, instead of a template, className and extends."
11902
12058
  },
11903
12059
  template: {
11904
12060
  type: "string",
11905
12061
  enum: ["singleton", "state_machine", "component", "resource"],
12062
+ ops: ["create"],
11906
12063
  description: "create: a starting shape."
11907
12064
  },
11908
- modifications: SCRIPT_MODIFICATIONS,
12065
+ modifications: { ...SCRIPT_MODIFICATIONS, ops: ["modify"] },
11909
12066
  symbol: {
11910
12067
  type: "string",
11911
12068
  ops: ["rename"],
@@ -11943,16 +12100,19 @@ var init_tool_definitions = __esm(() => {
11943
12100
  scriptPath: SCRIPT_PATH,
11944
12101
  includeInherited: {
11945
12102
  type: "boolean",
12103
+ ops: ["structure"],
11946
12104
  description: "structure: also list what the script inherits, walking extends through the project's other scripts. Each such member carries inherited_from, the file declaring it, and the answer gains inherits_from, the chain that was walked. A name the script overrides is listed at both its lines rather than once. A native base is not walked, since it declares nothing in a file: editor_classes info answers for those. Default false."
11947
12105
  },
11948
12106
  line: {
11949
12107
  type: "integer",
11950
12108
  minimum: 1,
12109
+ ops: ["completion", "hover"],
11951
12110
  description: "completion, hover: the line, counted from 1 as the editor shows it."
11952
12111
  },
11953
12112
  character: {
11954
12113
  type: "integer",
11955
12114
  minimum: 1,
12115
+ ops: ["completion", "hover"],
11956
12116
  description: "completion, hover: the column, counted from 1 as the editor shows it, with a tab counted as one. symbols gives each name at the line and column hover is asked at."
11957
12117
  }
11958
12118
  },
@@ -11960,7 +12120,7 @@ var init_tool_definitions = __esm(() => {
11960
12120
  operations: {
11961
12121
  structure: {
11962
12122
  effect: "read",
11963
- summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class",
12123
+ summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
11964
12124
  requires: []
11965
12125
  },
11966
12126
  symbols: {
@@ -11995,7 +12155,7 @@ var init_tool_definitions = __esm(() => {
11995
12155
  {
11996
12156
  name: "resource_edit",
11997
12157
  title: "Edit resources",
11998
- description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource need the editor connected.',
12158
+ description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
11999
12159
  parameters: {
12000
12160
  projectPath: PROJECT_PATH,
12001
12161
  resourcePath: {
@@ -12005,7 +12165,7 @@ var init_tool_definitions = __esm(() => {
12005
12165
  resourceType: {
12006
12166
  type: "string",
12007
12167
  ops: ["create"],
12008
- 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.'
12009
12169
  },
12010
12170
  properties: { ...PROPERTIES, ops: ["create", "modify"] },
12011
12171
  script: {
@@ -12199,6 +12359,17 @@ var init_tool_definitions = __esm(() => {
12199
12359
  ops: ["profile"],
12200
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."
12201
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
+ },
12202
12373
  limit: {
12203
12374
  type: "integer",
12204
12375
  minimum: 1,
@@ -12250,7 +12421,7 @@ var init_tool_definitions = __esm(() => {
12250
12421
  requires: []
12251
12422
  }
12252
12423
  },
12253
- 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.",
12254
12425
  defaultOperation: "start"
12255
12426
  },
12256
12427
  {
@@ -12346,9 +12517,10 @@ var init_tool_definitions = __esm(() => {
12346
12517
  description: "The engine's ClassDB: find classes, read one in full, or walk an inheritance tree.",
12347
12518
  parameters: {
12348
12519
  projectPath: PROJECT_PATH,
12349
- filter: { type: "string", description: "query: a substring of the class name." },
12520
+ filter: { type: "string", ops: ["query"], description: "query: a substring of the class name." },
12350
12521
  category: {
12351
12522
  type: "string",
12523
+ ops: ["query"],
12352
12524
  enum: [
12353
12525
  "node",
12354
12526
  "node2d",
@@ -12365,9 +12537,21 @@ var init_tool_definitions = __esm(() => {
12365
12537
  ],
12366
12538
  description: "query: limit to one family."
12367
12539
  },
12368
- instantiableOnly: { type: "boolean", description: "query: leave out abstract classes. Default false." },
12369
- className: { type: "string", description: "info, inheritance: the class." },
12370
- includeInherited: { type: "boolean", description: "info: include inherited members. Default false." },
12540
+ instantiableOnly: {
12541
+ type: "boolean",
12542
+ ops: ["query"],
12543
+ description: "query: leave out abstract classes. Default false."
12544
+ },
12545
+ className: {
12546
+ type: "string",
12547
+ ops: ["info", "inheritance"],
12548
+ description: "info, inheritance: the class."
12549
+ },
12550
+ includeInherited: {
12551
+ type: "boolean",
12552
+ ops: ["info"],
12553
+ description: "info: include inherited members. Default false."
12554
+ },
12371
12555
  member: {
12372
12556
  type: "string",
12373
12557
  ops: ["info"],
@@ -12521,7 +12705,7 @@ var init_tool_definitions = __esm(() => {
12521
12705
  value: {
12522
12706
  ops: ["set"],
12523
12707
  blank: true,
12524
- description: `set: the value, fitted to the property's type. A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
12708
+ description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
12525
12709
  },
12526
12710
  method: {
12527
12711
  type: "string",
@@ -12531,7 +12715,7 @@ var init_tool_definitions = __esm(() => {
12531
12715
  args: {
12532
12716
  type: "array",
12533
12717
  ops: ["call"],
12534
- description: `call: the arguments, fitted to the method's parameter types. A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
12718
+ description: `call: the arguments, fitted to the method's parameter types. An RID is passed as the {"_type": "RID", "id": ...} a read answers it with, such as a viewport's for RenderingServer's render timing. ${WHOLE_NUMBERS} A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
12535
12719
  },
12536
12720
  properties: {
12537
12721
  type: ["boolean", "array"],
@@ -13035,8 +13219,11 @@ function headlessSection() {
13035
13219
  "had none. The reading ops are the ones to gate on, and which is which is in each",
13036
13220
  "op's own summary below rather than assumed from this list.",
13037
13221
  "",
13038
- "Everything else needs the editor (`editor_*`, `debug_*`, `scene_*`, `resource_*`) or a running",
13039
- "game (`runtime_*`).",
13222
+ "`scene_create`, `scene_node`, `scene_signal` and `resource_edit` go through the editor when one",
13223
+ "serves the project and through a headless engine when none does, so they need nothing open",
13224
+ "either; a gate can call them, and the reads among them are the ones to gate on. Everything else",
13225
+ "needs the editor (`editor_*`, `debug_*`, `scene_animation` but its reads) or a running game",
13226
+ "(`runtime_*`).",
13040
13227
  "",
13041
13228
  'One of these can be pointed the other way: `project_settings get` takes `from: "editor"` to ask',
13042
13229
  "the open editor instead of the file, which is a different reading and costs no engine start. A",
@@ -32710,7 +32897,7 @@ var init_ajvProvider_CEoC__sr = __esm(() => {
32710
32897
  });
32711
32898
 
32712
32899
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
32713
- import process9 from "node:process";
32900
+ import process10 from "node:process";
32714
32901
  var init_shimsNode = __esm(() => {
32715
32902
  init_ajvProvider_CEoC__sr();
32716
32903
  });
@@ -34524,7 +34711,7 @@ var swallowsErrorsAfterClose, StdioServerTransport = class {
34524
34711
  _readBuffer;
34525
34712
  _started = false;
34526
34713
  _closed = false;
34527
- constructor(_stdin = process9.stdin, _stdout = process9.stdout, options) {
34714
+ constructor(_stdin = process10.stdin, _stdout = process10.stdout, options) {
34528
34715
  this._stdin = _stdin;
34529
34716
  this._stdout = _stdout;
34530
34717
  this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
@@ -34713,10 +34900,10 @@ var init_stdio = __esm(() => {
34713
34900
  });
34714
34901
 
34715
34902
  // src/runtime-client.ts
34716
- 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";
34717
34904
  import { createConnection } from "node:net";
34718
34905
  import { tmpdir as tmpdir4 } from "node:os";
34719
- 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";
34720
34907
  import { setTimeout as delay } from "node:timers/promises";
34721
34908
  function runtimeDirectory(variables = process.env) {
34722
34909
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -34724,20 +34911,20 @@ function runtimeDirectory(variables = process.env) {
34724
34911
  return explicit;
34725
34912
  }
34726
34913
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
34727
- return join15(perUser ?? tmpdir4(), "gdharness");
34914
+ return join16(perUser ?? tmpdir4(), "gdharness");
34728
34915
  }
34729
34916
  function runtimeDirectories(variables = process.env) {
34730
34917
  const candidates = [runtimeDirectory(variables)];
34731
34918
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
34732
34919
  for (const base of fallbacks) {
34733
- candidates.push(join15(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34920
+ candidates.push(join16(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34734
34921
  }
34735
34922
  return [...new Set(candidates.map((path) => resolve4(path)))];
34736
34923
  }
34737
34924
  function errorReportOf(pid, directories = runtimeDirectories()) {
34738
34925
  for (const directory of directories) {
34739
- const path = join15(directory, `runtime-${pid}.log`);
34740
- if (existsSync14(path)) {
34926
+ const path = join16(directory, `runtime-${pid}.log`);
34927
+ if (existsSync15(path)) {
34741
34928
  return path;
34742
34929
  }
34743
34930
  }
@@ -34785,7 +34972,7 @@ function parseAnnouncement(file, pid) {
34785
34972
  };
34786
34973
  let announcedAt;
34787
34974
  try {
34788
- announcedAt = statSync7(file).mtimeMs;
34975
+ announcedAt = statSync8(file).mtimeMs;
34789
34976
  } catch {}
34790
34977
  const when = announcedAt === undefined ? {} : { announcedAt };
34791
34978
  if (protocol !== RUNTIME_PROTOCOL) {
@@ -34877,29 +35064,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
34877
35064
  }).map((one) => one.file));
34878
35065
  }
34879
35066
  function announcedIn(directory) {
34880
- if (!existsSync14(directory)) {
35067
+ if (!existsSync15(directory)) {
34881
35068
  return [];
34882
35069
  }
34883
35070
  const found = [];
34884
35071
  const candidates = [];
34885
35072
  const entries = [];
34886
- for (const entry of readdirSync9(directory)) {
35073
+ for (const entry of readdirSync10(directory)) {
34887
35074
  const report = ERROR_REPORT_PATTERN.exec(entry);
34888
35075
  if (report) {
34889
- sweepErrorReport(join15(directory, entry), Number.parseInt(report[1] ?? "", 10));
35076
+ sweepErrorReport(join16(directory, entry), Number.parseInt(report[1] ?? "", 10));
34890
35077
  continue;
34891
35078
  }
34892
35079
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
34893
35080
  if (!match) {
34894
35081
  continue;
34895
35082
  }
34896
- const file = join15(directory, entry);
35083
+ const file = join16(directory, entry);
34897
35084
  const pid = Number.parseInt(match[1] ?? "", 10);
34898
35085
  const running = alive(pid);
34899
35086
  entries.push({ file, pid, running });
34900
35087
  if (running) {
34901
35088
  try {
34902
- candidates.push({ file, pid, writtenAt: statSync7(file).mtimeMs });
35089
+ candidates.push({ file, pid, writtenAt: statSync8(file).mtimeMs });
34903
35090
  } catch {}
34904
35091
  }
34905
35092
  }
@@ -34921,16 +35108,16 @@ function announcedIn(directory) {
34921
35108
  found.push(announced);
34922
35109
  }
34923
35110
  const held = new Set(entries.map((one) => one.file));
34924
- const here = join15(directory, ".");
35111
+ const here = join16(directory, ".");
34925
35112
  for (const file of verdicts.keys()) {
34926
- if (dirname9(file) === here && !held.has(file)) {
35113
+ if (dirname10(file) === here && !held.has(file)) {
34927
35114
  verdicts.delete(file);
34928
35115
  }
34929
35116
  }
34930
35117
  return found;
34931
35118
  }
34932
35119
  async function announcementEnded(pid, directories = runtimeDirectories()) {
34933
- 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));
34934
35121
  const first = files[0];
34935
35122
  if (first === undefined) {
34936
35123
  return;
@@ -34956,7 +35143,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
34956
35143
  return;
34957
35144
  }
34958
35145
  try {
34959
- if (now - statSync7(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
35146
+ if (now - statSync8(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
34960
35147
  unlinkSync2(path);
34961
35148
  }
34962
35149
  } catch {}
@@ -35232,9 +35419,9 @@ var init_runtime_client = __esm(() => {
35232
35419
 
35233
35420
  // src/boot-note.ts
35234
35421
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
35235
- import { dirname as dirname10, join as join16 } from "node:path";
35422
+ import { dirname as dirname11, join as join17 } from "node:path";
35236
35423
  function bootNotePath(projectPath) {
35237
- return join16(projectPath, ".godot", "gdharness-boot.json");
35424
+ return join17(projectPath, ".godot", "gdharness-boot.json");
35238
35425
  }
35239
35426
  function readScenes(projectPath) {
35240
35427
  try {
@@ -35254,7 +35441,7 @@ function readBootNote(projectPath, scene) {
35254
35441
  function writeBootNote(projectPath, scene, announcedAfterMs) {
35255
35442
  try {
35256
35443
  const path = bootNotePath(projectPath);
35257
- mkdirSync6(dirname10(path), { recursive: true });
35444
+ mkdirSync6(dirname11(path), { recursive: true });
35258
35445
  const scenes = {
35259
35446
  ...readScenes(projectPath),
35260
35447
  [scene]: {
@@ -35281,9 +35468,9 @@ var init_boot_note = __esm(() => {
35281
35468
 
35282
35469
  // src/breakpoint-note.ts
35283
35470
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
35284
- 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";
35285
35472
  function breakpointNotePath(projectPath) {
35286
- return join17(projectPath, ".godot", "gdharness-breakpoints.json");
35473
+ return join18(projectPath, ".godot", "gdharness-breakpoints.json");
35287
35474
  }
35288
35475
  function writeBreakpointNote(projectPath, held) {
35289
35476
  const path = breakpointNotePath(projectPath);
@@ -35300,7 +35487,7 @@ function writeBreakpointNote(projectPath, held) {
35300
35487
  lines: [...one.lines]
35301
35488
  }))
35302
35489
  };
35303
- mkdirSync7(dirname11(path), { recursive: true });
35490
+ mkdirSync7(dirname12(path), { recursive: true });
35304
35491
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
35305
35492
  `, "utf8");
35306
35493
  } catch (error) {
@@ -35319,12 +35506,12 @@ function readBreakpointNote(projectPath) {
35319
35506
  continue;
35320
35507
  }
35321
35508
  const { script, lines: listed } = entry;
35322
- if (typeof script !== "string" || isAbsolute3(script) || script.split("/").includes("..")) {
35509
+ if (typeof script !== "string" || isAbsolute4(script) || script.split("/").includes("..")) {
35323
35510
  continue;
35324
35511
  }
35325
35512
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
35326
35513
  if (lines.length > 0) {
35327
- held.push({ scriptPath: realPathOr(join17(projectPath, script)), lines });
35514
+ held.push({ scriptPath: realPathOr(join18(projectPath, script)), lines });
35328
35515
  }
35329
35516
  }
35330
35517
  return held;
@@ -35334,7 +35521,7 @@ function readBreakpointNote(projectPath) {
35334
35521
  }
35335
35522
  function withinProject(projectPath, scriptPath) {
35336
35523
  const rel = relative3(projectPath, scriptPath);
35337
- return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
35524
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
35338
35525
  }
35339
35526
  var init_breakpoint_note = __esm(() => {
35340
35527
  init_errors();
@@ -35374,9 +35561,9 @@ var init_whole_file = __esm(() => {
35374
35561
 
35375
35562
  // src/class-note.ts
35376
35563
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
35377
- import { dirname as dirname12, join as join18 } from "node:path";
35564
+ import { dirname as dirname13, join as join19 } from "node:path";
35378
35565
  function classNotePath(projectPath) {
35379
- return join18(projectPath, ".godot", "gdharness-classes.json");
35566
+ return join19(projectPath, ".godot", "gdharness-classes.json");
35380
35567
  }
35381
35568
  function readClassNote(projectPath) {
35382
35569
  try {
@@ -35410,7 +35597,7 @@ function unfinishedScanIn(value) {
35410
35597
  function writeClassNote(projectPath, note) {
35411
35598
  const path = classNotePath(projectPath);
35412
35599
  try {
35413
- mkdirSync8(dirname12(path), { recursive: true });
35600
+ mkdirSync8(dirname13(path), { recursive: true });
35414
35601
  } catch {
35415
35602
  return;
35416
35603
  }
@@ -35422,11 +35609,11 @@ var init_class_note = __esm(() => {
35422
35609
  });
35423
35610
 
35424
35611
  // src/config-pin.ts
35425
- import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
35426
- 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";
35427
35614
  function versionsNamedInConfig(projectPath) {
35428
- const config = join19(projectPath, ".mcp.json");
35429
- if (!existsSync15(config)) {
35615
+ const config = join20(projectPath, ".mcp.json");
35616
+ if (!existsSync16(config)) {
35430
35617
  return [];
35431
35618
  }
35432
35619
  let text;
@@ -35452,22 +35639,22 @@ var init_config_pin = __esm(() => {
35452
35639
  });
35453
35640
 
35454
35641
  // src/cut-test-run.ts
35455
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
35456
- 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";
35457
35644
  function keptAt(projectPath) {
35458
- return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35645
+ return join21(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35459
35646
  }
35460
35647
  function keepCutTestRun(projectPath, run) {
35461
35648
  const path = keptAt(projectPath);
35462
35649
  try {
35463
- mkdirSync9(dirname13(path), { recursive: true });
35650
+ mkdirSync9(dirname14(path), { recursive: true });
35464
35651
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
35465
35652
  `, "utf8");
35466
35653
  } catch {}
35467
35654
  }
35468
35655
  function takeCutTestRun(projectPath) {
35469
35656
  const path = keptAt(projectPath);
35470
- if (!existsSync16(path)) {
35657
+ if (!existsSync17(path)) {
35471
35658
  return null;
35472
35659
  }
35473
35660
  try {
@@ -36284,27 +36471,27 @@ var init_dap_client = __esm(() => {
36284
36471
  import { createHash as createHash4 } from "node:crypto";
36285
36472
  import {
36286
36473
  closeSync,
36287
- existsSync as existsSync17,
36474
+ existsSync as existsSync18,
36288
36475
  mkdtempSync as mkdtempSync2,
36289
36476
  openSync,
36290
36477
  readFileSync as readFileSync19,
36291
36478
  rmSync as rmSync10,
36292
- statSync as statSync8,
36479
+ statSync as statSync9,
36293
36480
  writeFileSync as writeFileSync12,
36294
36481
  writeSync
36295
36482
  } from "node:fs";
36296
36483
  import { tmpdir as tmpdir5 } from "node:os";
36297
- import { join as join21 } from "node:path";
36484
+ import { join as join22 } from "node:path";
36298
36485
  import { setTimeout as delay3 } from "node:timers/promises";
36299
36486
  function helperAssembly(directory = tmpdir5()) {
36300
- return join21(directory, `gdharness-desktop-${DIGEST}.dll`);
36487
+ return join22(directory, `gdharness-desktop-${DIGEST}.dll`);
36301
36488
  }
36302
36489
  async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
36303
36490
  const assembly = helperAssembly(directory);
36304
36491
  const lock = `${assembly}.compiling`;
36305
36492
  const until = Date.now() + budgetMs;
36306
36493
  while (Date.now() < until) {
36307
- if (existsSync17(assembly)) {
36494
+ if (existsSync18(assembly)) {
36308
36495
  return null;
36309
36496
  }
36310
36497
  let mine = false;
@@ -36323,7 +36510,7 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36323
36510
  }
36324
36511
  try {
36325
36512
  const holder = Number(readFileSync19(lock, "utf8").trim());
36326
- const old = Date.now() - statSync8(lock).mtimeMs > budgetMs;
36513
+ const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
36327
36514
  if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
36328
36515
  rmSync10(lock, { force: true });
36329
36516
  continue;
@@ -36337,8 +36524,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36337
36524
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
36338
36525
  }
36339
36526
  async function compileHelper(directory, assembly, budgetMs) {
36340
- const scratch = mkdtempSync2(join21(directory, "gdharness-desktop-source-"));
36341
- const source = join21(scratch, "helper.cs");
36527
+ const scratch = mkdtempSync2(join22(directory, "gdharness-desktop-source-"));
36528
+ const source = join22(scratch, "helper.cs");
36342
36529
  writeFileSync12(source, SOURCE, "utf8");
36343
36530
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
36344
36531
  const script = [
@@ -36376,7 +36563,7 @@ async function compileHelper(directory, assembly, budgetMs) {
36376
36563
  });
36377
36564
  compiler.once("close", (code) => {
36378
36565
  clearTimeout(timer);
36379
- 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)}`}`);
36380
36567
  });
36381
36568
  });
36382
36569
  } finally {
@@ -36543,21 +36730,21 @@ var init_desktop = __esm(() => {
36543
36730
  // src/editor-log.ts
36544
36731
  import {
36545
36732
  closeSync as closeSync2,
36546
- existsSync as existsSync18,
36733
+ existsSync as existsSync19,
36547
36734
  mkdirSync as mkdirSync10,
36548
36735
  openSync as openSync2,
36549
36736
  readFileSync as readFileSync20,
36550
36737
  readSync,
36551
36738
  rmSync as rmSync11,
36552
- statSync as statSync9,
36739
+ statSync as statSync10,
36553
36740
  writeFileSync as writeFileSync13
36554
36741
  } from "node:fs";
36555
- import { dirname as dirname14, join as join22 } from "node:path";
36742
+ import { dirname as dirname15, join as join23 } from "node:path";
36556
36743
  function editorLogPath(projectPath) {
36557
- return join22(projectPath, ".godot", "gdharness-editor.log");
36744
+ return join23(projectPath, ".godot", "gdharness-editor.log");
36558
36745
  }
36559
36746
  function editorLogNotePath(projectPath) {
36560
- return join22(projectPath, ".godot", "gdharness-editor.json");
36747
+ return join23(projectPath, ".godot", "gdharness-editor.json");
36561
36748
  }
36562
36749
  function readEditorLogNote(projectPath) {
36563
36750
  try {
@@ -36572,7 +36759,7 @@ function readEditorLogNote(projectPath) {
36572
36759
  function clearEditorLog(projectPath) {
36573
36760
  try {
36574
36761
  const path = editorLogPath(projectPath);
36575
- mkdirSync10(dirname14(path), { recursive: true });
36762
+ mkdirSync10(dirname15(path), { recursive: true });
36576
36763
  rmSync11(path, { force: true });
36577
36764
  rmSync11(editorLogNotePath(projectPath), { force: true });
36578
36765
  } catch {}
@@ -36580,7 +36767,7 @@ function clearEditorLog(projectPath) {
36580
36767
  function writeEditorLogNote(projectPath, pid) {
36581
36768
  try {
36582
36769
  const path = editorLogNotePath(projectPath);
36583
- mkdirSync10(dirname14(path), { recursive: true });
36770
+ mkdirSync10(dirname15(path), { recursive: true });
36584
36771
  writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
36585
36772
  } catch {}
36586
36773
  }
@@ -36602,7 +36789,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
36602
36789
  return { kind: "older server", notedPid: noted?.pid ?? null };
36603
36790
  }
36604
36791
  const path = editorLogPath(projectPath);
36605
- if (!existsSync18(path)) {
36792
+ if (!existsSync19(path)) {
36606
36793
  return { kind: "not written" };
36607
36794
  }
36608
36795
  const log = new GameLog;
@@ -36658,7 +36845,7 @@ function foldProgress(text) {
36658
36845
  `);
36659
36846
  }
36660
36847
  function theFirstOf(path) {
36661
- const whole = statSync9(path).size;
36848
+ const whole = statSync10(path).size;
36662
36849
  if (whole <= MOST_CONSOLE_BYTES) {
36663
36850
  return { text: readFileSync20(path, "utf8") };
36664
36851
  }
@@ -36821,8 +37008,8 @@ var init_editor_log = __esm(() => {
36821
37008
  });
36822
37009
 
36823
37010
  // src/rename.ts
36824
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync21, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
36825
- 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";
36826
37013
 
36827
37014
  class Script {
36828
37015
  path;
@@ -37530,21 +37717,21 @@ function applyRewrites(rewrites) {
37530
37717
  if (now !== rewrite.before) {
37531
37718
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
37532
37719
  }
37533
- if (rewrite.writeTo !== rewrite.absolute && existsSync19(rewrite.writeTo)) {
37720
+ if (rewrite.writeTo !== rewrite.absolute && existsSync20(rewrite.writeTo)) {
37534
37721
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
37535
37722
  }
37536
37723
  }
37537
37724
  const done = [];
37538
37725
  try {
37539
37726
  for (const rewrite of rewrites) {
37540
- mkdirSync11(dirname15(rewrite.writeTo), { recursive: true });
37727
+ mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
37541
37728
  const staging = `${rewrite.writeTo}.gdharness-rename`;
37542
37729
  writeFileSync14(staging, rewrite.after, "utf8");
37543
37730
  renameSync3(staging, rewrite.writeTo);
37544
37731
  let movedUid = false;
37545
37732
  if (rewrite.writeTo !== rewrite.absolute) {
37546
37733
  unlinkSync3(rewrite.absolute);
37547
- if (existsSync19(`${rewrite.absolute}.uid`)) {
37734
+ if (existsSync20(`${rewrite.absolute}.uid`)) {
37548
37735
  renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
37549
37736
  movedUid = true;
37550
37737
  }
@@ -37590,10 +37777,10 @@ var init_rename = __esm(() => {
37590
37777
  });
37591
37778
 
37592
37779
  // src/lsp_client.ts
37593
- import { existsSync as existsSync20, realpathSync as realpathSync4 } from "node:fs";
37780
+ import { existsSync as existsSync21, realpathSync as realpathSync4 } from "node:fs";
37594
37781
  import { readFile, realpath } from "node:fs/promises";
37595
37782
  import { createConnection as createConnection3 } from "node:net";
37596
- 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";
37597
37784
  import { setTimeout as delay4 } from "node:timers/promises";
37598
37785
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
37599
37786
  function initializeTimeoutMs() {
@@ -37755,7 +37942,7 @@ class GodotLSPClient {
37755
37942
  if (this.initialized) {
37756
37943
  return;
37757
37944
  }
37758
- const rootPath = this.rootPath ?? dirname16(resolve5(filePath));
37945
+ const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
37759
37946
  await this.initialize(rootPath);
37760
37947
  }
37761
37948
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -38271,7 +38458,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
38271
38458
  } catch {
38272
38459
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
38273
38460
  }
38274
- if (!existsSync20(join23(projectPath, "project.godot"))) {
38461
+ if (!existsSync21(join24(projectPath, "project.godot"))) {
38275
38462
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
38276
38463
  }
38277
38464
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -38431,8 +38618,8 @@ var init_lsp_client = __esm(() => {
38431
38618
  });
38432
38619
 
38433
38620
  // src/rename-symbol.ts
38434
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
38435
- 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";
38436
38623
  function resourcePathOf(projectPath, absolute) {
38437
38624
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
38438
38625
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -38445,13 +38632,13 @@ function onDiskSpelling(path) {
38445
38632
  let at = resolve6(path);
38446
38633
  for (;; ) {
38447
38634
  try {
38448
- return join24(realpathSync5.native(at), ...missing.reverse());
38635
+ return join25(realpathSync5.native(at), ...missing.reverse());
38449
38636
  } catch {
38450
- const parent = dirname17(at);
38637
+ const parent = dirname18(at);
38451
38638
  if (parent === at) {
38452
38639
  return resolve6(path);
38453
38640
  }
38454
- missing.push(basename6(at));
38641
+ missing.push(basename7(at));
38455
38642
  at = parent;
38456
38643
  }
38457
38644
  }
@@ -38498,7 +38685,7 @@ async function renameSymbol(request, services) {
38498
38685
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
38499
38686
  };
38500
38687
  }
38501
- if (existsSync21(request.newScriptPath)) {
38688
+ if (existsSync22(request.newScriptPath)) {
38502
38689
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
38503
38690
  }
38504
38691
  }
@@ -38674,7 +38861,7 @@ async function renameSymbol(request, services) {
38674
38861
  scripts,
38675
38862
  moves: new Map([[declaringPath, moveTo]]),
38676
38863
  named: [declaringPath],
38677
- exists: (path) => existsSync21(`${projectPath}/${path.slice("res://".length)}`)
38864
+ exists: (path) => existsSync22(`${projectPath}/${path.slice("res://".length)}`)
38678
38865
  }, plan);
38679
38866
  }
38680
38867
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -38873,15 +39060,15 @@ ${mention.kind}`;
38873
39060
  };
38874
39061
  }
38875
39062
  function writeLeftAloneDetail(projectPath, symbol, detail) {
38876
- const directory = join24(projectPath, ".godot", "gdharness-renames");
39063
+ const directory = join25(projectPath, ".godot", "gdharness-renames");
38877
39064
  mkdirSync12(directory, { recursive: true });
38878
39065
  detailWrites += 1;
38879
- 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`);
38880
39067
  writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
38881
39068
  `, "utf8");
38882
- const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
39069
+ const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
38883
39070
  for (const old of kept.slice(10)) {
38884
- rmSync12(join24(directory, old), { force: true });
39071
+ rmSync12(join25(directory, old), { force: true });
38885
39072
  }
38886
39073
  return path;
38887
39074
  }
@@ -38922,25 +39109,25 @@ var init_rename_symbol = __esm(() => {
38922
39109
 
38923
39110
  // src/file-move.ts
38924
39111
  import {
38925
- existsSync as existsSync22,
39112
+ existsSync as existsSync23,
38926
39113
  mkdirSync as mkdirSync13,
38927
- readdirSync as readdirSync11,
39114
+ readdirSync as readdirSync12,
38928
39115
  readFileSync as readFileSync22,
38929
39116
  renameSync as renameSync4,
38930
- statSync as statSync10,
39117
+ statSync as statSync11,
38931
39118
  writeFileSync as writeFileSync16
38932
39119
  } from "node:fs";
38933
- 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";
38934
39121
  function filesUnder(directory) {
38935
- return readdirSync11(directory, { withFileTypes: true }).flatMap((entry) => {
38936
- const path = join25(directory, entry.name);
39122
+ return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
39123
+ const path = join26(directory, entry.name);
38937
39124
  return entry.isDirectory() ? filesUnder(path) : [path];
38938
39125
  });
38939
39126
  }
38940
39127
  function sameFile(one, other) {
38941
39128
  try {
38942
- const a = statSync10(one);
38943
- const b = statSync10(other);
39129
+ const a = statSync11(one);
39130
+ const b = statSync11(other);
38944
39131
  return a.ino === b.ino && a.dev === b.dev;
38945
39132
  } catch {
38946
39133
  return false;
@@ -38954,8 +39141,8 @@ function unseenByTheEngine(projectPath, path) {
38954
39141
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
38955
39142
  }
38956
39143
  if (at < steps.length - 1) {
38957
- directory = join25(directory, step);
38958
- if (existsSync22(join25(directory, ".gdignore"))) {
39144
+ directory = join26(directory, step);
39145
+ if (existsSync23(join26(directory, ".gdignore"))) {
38959
39146
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
38960
39147
  }
38961
39148
  }
@@ -38967,21 +39154,21 @@ function installedAddonOf(projectPath, path) {
38967
39154
  if (steps[0] !== "addons" || steps[1] === undefined) {
38968
39155
  return null;
38969
39156
  }
38970
- 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;
38971
39158
  }
38972
39159
  function binaryResources(projectPath) {
38973
39160
  const found = [];
38974
39161
  const visit = (directory, prefix) => {
38975
- if (existsSync22(join25(directory, ".gdignore"))) {
39162
+ if (existsSync23(join26(directory, ".gdignore"))) {
38976
39163
  return;
38977
39164
  }
38978
- for (const entry of readdirSync11(directory, { withFileTypes: true })) {
39165
+ for (const entry of readdirSync12(directory, { withFileTypes: true })) {
38979
39166
  if (entry.name.startsWith(".")) {
38980
39167
  continue;
38981
39168
  }
38982
39169
  const path = `${prefix}${entry.name}`;
38983
39170
  if (entry.isDirectory()) {
38984
- visit(join25(directory, entry.name), `${path}/`);
39171
+ visit(join26(directory, entry.name), `${path}/`);
38985
39172
  } else if (/\.(scn|res)$/i.test(entry.name)) {
38986
39173
  found.push(path);
38987
39174
  }
@@ -38991,7 +39178,7 @@ function binaryResources(projectPath) {
38991
39178
  return found;
38992
39179
  }
38993
39180
  function targetPathOf(projectPath, absolute) {
38994
- const inside = relative5(onDiskSpelling(projectPath), join25(onDiskSpelling(dirname18(absolute)), basename7(absolute)));
39181
+ const inside = relative5(onDiskSpelling(projectPath), join26(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
38995
39182
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
38996
39183
  return null;
38997
39184
  }
@@ -39009,13 +39196,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39009
39196
  if (to === null) {
39010
39197
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
39011
39198
  }
39012
- if (!existsSync22(absoluteFrom)) {
39199
+ if (!existsSync23(absoluteFrom)) {
39013
39200
  return { ok: false, reason: `There is nothing at ${from} to move.` };
39014
39201
  }
39015
39202
  if (from === to) {
39016
39203
  return { ok: false, reason: `${from} is already there; nothing to move.` };
39017
39204
  }
39018
- const isDirectory = statSync10(absoluteFrom).isDirectory();
39205
+ const isDirectory = statSync11(absoluteFrom).isDirectory();
39019
39206
  if (from === "res://project.godot") {
39020
39207
  return {
39021
39208
  ok: false,
@@ -39038,7 +39225,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39038
39225
  if (isDirectory && to.startsWith(`${from}/`)) {
39039
39226
  return { ok: false, reason: `${from} cannot be moved into itself.` };
39040
39227
  }
39041
- if (existsSync22(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39228
+ if (existsSync23(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39042
39229
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
39043
39230
  }
39044
39231
  const unseen = unseenByTheEngine(projectPath, to);
@@ -39046,7 +39233,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39046
39233
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
39047
39234
  }
39048
39235
  if (!isDirectory) {
39049
- 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))));
39050
39237
  if (owner !== undefined && owner !== null) {
39051
39238
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
39052
39239
  }
@@ -39068,7 +39255,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39068
39255
  } else {
39069
39256
  moves.set(from, to);
39070
39257
  for (const sidecar of SIDECARS) {
39071
- if (existsSync22(`${absoluteFrom}${sidecar}`)) {
39258
+ if (existsSync23(`${absoluteFrom}${sidecar}`)) {
39072
39259
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
39073
39260
  }
39074
39261
  }
@@ -39080,12 +39267,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39080
39267
  scripts,
39081
39268
  moves,
39082
39269
  named: [from],
39083
- exists: (path) => existsSync22(join25(projectPath, path.slice("res://".length)))
39270
+ exists: (path) => existsSync23(join26(projectPath, path.slice("res://".length)))
39084
39271
  }, { edits: [], mentions: [] });
39085
39272
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
39086
39273
  const binaries = [];
39087
39274
  for (const path of binaryResources(projectPath)) {
39088
- const bytes = readFileSync22(join25(projectPath, path.slice("res://".length)));
39275
+ const bytes = readFileSync22(join26(projectPath, path.slice("res://".length)));
39089
39276
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
39090
39277
  if (names !== undefined) {
39091
39278
  binaries.push({
@@ -39104,7 +39291,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39104
39291
  }));
39105
39292
  const left = leftAloneReport(mentions);
39106
39293
  const rewrites = rewritesOf(plan, texts, null);
39107
- const absoluteOf = (path) => join25(projectPath, path.slice("res://".length));
39294
+ const absoluteOf = (path) => join26(projectPath, path.slice("res://".length));
39108
39295
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
39109
39296
  const moved = [...moves].map(([source, target]) => {
39110
39297
  const rewrite = rewritten.get(source);
@@ -39151,19 +39338,19 @@ function applyFileMove(outcome) {
39151
39338
  }
39152
39339
  }
39153
39340
  for (const file of outcome.moved) {
39154
- if (!existsSync22(file.absoluteFrom)) {
39341
+ if (!existsSync23(file.absoluteFrom)) {
39155
39342
  throw new Error(`${file.from} is gone from disk; nothing was written`);
39156
39343
  }
39157
39344
  if (file.text !== null && readFileSync22(file.absoluteFrom, "utf8") !== file.text.before) {
39158
39345
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
39159
39346
  }
39160
- if (existsSync22(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39347
+ if (existsSync23(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39161
39348
  throw new Error(`${file.to} already exists; nothing was written`);
39162
39349
  }
39163
39350
  }
39164
39351
  const undo = [];
39165
39352
  const rename = (source, target) => {
39166
- mkdirSync13(dirname18(target), { recursive: true });
39353
+ mkdirSync13(dirname19(target), { recursive: true });
39167
39354
  renameSync4(source, target);
39168
39355
  undo.push(() => {
39169
39356
  renameSync4(target, source);
@@ -43102,6 +43289,29 @@ ${note}`;
43102
43289
  return detail === entry.detail ? entry : { ...entry, detail };
43103
43290
  });
43104
43291
  }
43292
+ function shortened(detail) {
43293
+ if (detail === null || detail.length <= LONGEST_DETAIL) {
43294
+ return detail;
43295
+ }
43296
+ return `${detail.slice(0, LONGEST_DETAIL)}
43297
+ [${detail.length - LONGEST_DETAIL} more characters left out]`;
43298
+ }
43299
+ function foldedReports(reports) {
43300
+ const first = new Map;
43301
+ for (const report of reports) {
43302
+ const key = JSON.stringify([report.kind, report.message, report.detail]);
43303
+ const seen = first.get(key);
43304
+ if (seen === undefined) {
43305
+ first.set(key, { report, times: 1 });
43306
+ } else {
43307
+ seen.times += 1;
43308
+ }
43309
+ }
43310
+ return [...first.values()].map(({ report, times }) => {
43311
+ const kept = { ...report, detail: shortened(report.detail) };
43312
+ return times === 1 ? kept : { ...kept, times };
43313
+ });
43314
+ }
43105
43315
  function failedCases(report, printed) {
43106
43316
  const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
43107
43317
  const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
@@ -43109,6 +43319,8 @@ function failedCases(report, printed) {
43109
43319
  return cases.map(({ entry, path }) => {
43110
43320
  const reports = read.slice(next, next + entry.reports.length);
43111
43321
  next += entry.reports.length;
43322
+ const folded = foldedReports(reports);
43323
+ const shown = folded.slice(0, MOST_REPORTS_SHOWN);
43112
43324
  return {
43113
43325
  suite: entry.suite,
43114
43326
  name: entry.name,
@@ -43116,11 +43328,34 @@ function failedCases(report, printed) {
43116
43328
  time: entry.time,
43117
43329
  path,
43118
43330
  message: reports[0]?.message ?? null,
43119
- detail: reports[0]?.detail ?? null,
43120
- ...reports.length > 1 ? { reports } : {}
43331
+ detail: shortened(reports[0]?.detail ?? null),
43332
+ ...reports.length > 1 ? { reports: shown, reportCount: reports.length } : {},
43333
+ ...folded.length > shown.length ? { reportsLeftOut: folded.length - shown.length } : {}
43121
43334
  };
43122
43335
  });
43123
43336
  }
43337
+ function failedWithin(failed, budget) {
43338
+ const sizes = failed.map((entry) => JSON.stringify(entry).length);
43339
+ const smallestFirst = sizes.map((_, index) => index).sort((a, b) => (sizes[a] ?? 0) - (sizes[b] ?? 0));
43340
+ const whole = new Set;
43341
+ let spent = 0;
43342
+ for (const index of smallestFirst) {
43343
+ const size = sizes[index] ?? 0;
43344
+ if (spent + size > budget) {
43345
+ break;
43346
+ }
43347
+ spent += size;
43348
+ whole.add(index);
43349
+ }
43350
+ const kept = failed.map((entry, index) => {
43351
+ if (whole.has(index)) {
43352
+ return entry;
43353
+ }
43354
+ const { detail: _detail, reports: _reports, reportsLeftOut: _left, ...named } = entry;
43355
+ return { ...named, message: entry.message?.slice(0, 500) ?? null, detail: null };
43356
+ });
43357
+ return { failed: kept, briefed: failed.length - whole.size };
43358
+ }
43124
43359
  function masked(expected, actual) {
43125
43360
  const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
43126
43361
  return `${expected}${actual}`.includes(MASKED_BRACKET) && allSpelled(expected) && allSpelled(actual);
@@ -43199,7 +43434,7 @@ function testProgressMessage(printed, elapsedMs) {
43199
43434
  const done = finished.length === 0 ? "No suite has finished" : `${finished.length === 1 ? "One suite has" : `${finished.length} suites have`} finished${failing === 0 ? "" : `, ${failing} with failures`}`;
43200
43435
  return `${done}${running === null ? "" : `; running ${running}`}; ${took} in.`;
43201
43436
  }
43202
- var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
43437
+ var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MOST_REPORTS_SHOWN = 20, LONGEST_DETAIL = 20000, MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
43203
43438
  var init_junit = __esm(() => {
43204
43439
  MalformedReportError = class MalformedReportError extends Error {
43205
43440
  constructor(message) {
@@ -43301,25 +43536,25 @@ var DEBUG_PREFIX = "debug ";
43301
43536
  var init_keeper_channel = () => {};
43302
43537
 
43303
43538
  // src/lifecycle-log.ts
43304
- import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync11 } from "node:fs";
43305
- import { dirname as dirname19, join as join26 } from "node:path";
43306
- 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";
43307
43542
  function lifecycleLogPath() {
43308
- return join26(runtimeDirectory(), "servers.log");
43543
+ return join27(runtimeDirectory(), "servers.log");
43309
43544
  }
43310
43545
  function writeLifecycle(event, fields = {}) {
43311
43546
  const path = lifecycleLogPath();
43312
43547
  try {
43313
- mkdirSync14(dirname19(path), { recursive: true });
43314
- 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) {
43315
43550
  try {
43316
43551
  renameSync5(path, `${path}.1`);
43317
43552
  } catch {}
43318
43553
  }
43319
43554
  const line = {
43320
43555
  at: new Date().toISOString(),
43321
- pid: process10.pid,
43322
- ppid: process10.ppid,
43556
+ pid: process11.pid,
43557
+ ppid: process11.ppid,
43323
43558
  version: SERVER_VERSION,
43324
43559
  event
43325
43560
  };
@@ -43626,14 +43861,14 @@ var init_prompts = __esm(() => {
43626
43861
 
43627
43862
  // src/restart-note.ts
43628
43863
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
43629
- import { dirname as dirname20, join as join27 } from "node:path";
43864
+ import { dirname as dirname21, join as join28 } from "node:path";
43630
43865
  function restartNotePath(projectPath) {
43631
- return join27(projectPath, ".godot", "gdharness-restart.json");
43866
+ return join28(projectPath, ".godot", "gdharness-restart.json");
43632
43867
  }
43633
43868
  function noteRestartBegun(note) {
43634
43869
  const path = restartNotePath(note.projectPath);
43635
43870
  try {
43636
- mkdirSync15(dirname20(path), { recursive: true });
43871
+ mkdirSync15(dirname21(path), { recursive: true });
43637
43872
  writeFileSync17(path, `${JSON.stringify(note, null, 2)}
43638
43873
  `, "utf8");
43639
43874
  } catch (error) {
@@ -43668,23 +43903,23 @@ var init_restart_note = __esm(() => {
43668
43903
 
43669
43904
  // src/run-record.ts
43670
43905
  import { createHash as createHash5 } from "node:crypto";
43671
- 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";
43672
- 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";
43673
43908
  function runsDirectory() {
43674
- return join28(runtimeDirectory(), "runs");
43909
+ return join29(runtimeDirectory(), "runs");
43675
43910
  }
43676
43911
  function realAsFarAsItExists(path) {
43677
43912
  const missing = [];
43678
43913
  let at = path;
43679
43914
  for (;; ) {
43680
43915
  try {
43681
- return join28(realpathSync6.native(at), ...missing);
43916
+ return join29(realpathSync6.native(at), ...missing);
43682
43917
  } catch {
43683
- const parent = dirname21(at);
43918
+ const parent = dirname22(at);
43684
43919
  if (parent === at) {
43685
43920
  return path;
43686
43921
  }
43687
- missing.unshift(basename8(at));
43922
+ missing.unshift(basename9(at));
43688
43923
  at = parent;
43689
43924
  }
43690
43925
  }
@@ -43695,10 +43930,10 @@ function projectKey(projectPath) {
43695
43930
  return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
43696
43931
  }
43697
43932
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
43698
- return join28(directory, "runs", `note-${projectKey(projectPath)}.json`);
43933
+ return join29(directory, "runs", `note-${projectKey(projectPath)}.json`);
43699
43934
  }
43700
43935
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
43701
- return join28(directory, "runs", `latest-${projectKey(servedBy)}.json`);
43936
+ return join29(directory, "runs", `latest-${projectKey(servedBy)}.json`);
43702
43937
  }
43703
43938
  function writeLatestRun(servedBy, latest) {
43704
43939
  try {
@@ -43723,17 +43958,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
43723
43958
  }
43724
43959
  function openTranscript(startedAt) {
43725
43960
  mkdirSync16(runsDirectory(), { recursive: true });
43726
- const path = join28(runsDirectory(), `run-${startedAt}.log`);
43961
+ const path = join29(runsDirectory(), `run-${startedAt}.log`);
43727
43962
  return { path, fd: openSync3(path, "a") };
43728
43963
  }
43729
43964
  function profilePath(startedAt) {
43730
- 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`);
43731
43969
  }
43732
43970
  function noteIsAbout(note, playing) {
43733
43971
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
43734
43972
  }
43735
43973
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
43736
- return join28(directory, "runs", `editor-${projectKey(projectPath)}.json`);
43974
+ return join29(directory, "runs", `editor-${projectKey(projectPath)}.json`);
43737
43975
  }
43738
43976
  function writeEditorRunNote(note) {
43739
43977
  mkdirSync16(runsDirectory(), { recursive: true });
@@ -43744,7 +43982,7 @@ function writeEditorRunNote(note) {
43744
43982
  }
43745
43983
  function readEditorRunNote(projectPath) {
43746
43984
  for (const directory of runtimeDirectories()) {
43747
- 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"));
43748
43986
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
43749
43987
  return note;
43750
43988
  }
@@ -43778,7 +44016,7 @@ function readRunRecord(projectPath) {
43778
44016
  if (own !== null) {
43779
44017
  return own;
43780
44018
  }
43781
- const shared = recordAt(join28(directory, "runs", SHARED_NOTE));
44019
+ const shared = recordAt(join29(directory, "runs", SHARED_NOTE));
43782
44020
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
43783
44021
  return shared;
43784
44022
  }
@@ -43790,13 +44028,13 @@ function everyRunRecord() {
43790
44028
  for (const directory of runtimeDirectories()) {
43791
44029
  let entries;
43792
44030
  try {
43793
- entries = readdirSync12(join28(directory, "runs"));
44031
+ entries = readdirSync13(join29(directory, "runs"));
43794
44032
  } catch {
43795
44033
  continue;
43796
44034
  }
43797
44035
  for (const entry of entries) {
43798
44036
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
43799
- const record = recordAt(join28(directory, "runs", entry));
44037
+ const record = recordAt(join29(directory, "runs", entry));
43800
44038
  if (record !== null) {
43801
44039
  found.push(record);
43802
44040
  }
@@ -43965,7 +44203,7 @@ function whyNotTheRun(record, running, needed) {
43965
44203
  if (after > SAME_RUN_WINDOW_MS) {
43966
44204
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
43967
44205
  }
43968
- const engine = record.command === undefined ? null : basename8(record.command);
44206
+ const engine = record.command === undefined ? null : basename9(record.command);
43969
44207
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
43970
44208
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
43971
44209
  if (wanted !== null && !said.includes(wanted)) {
@@ -44004,7 +44242,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
44004
44242
  }
44005
44243
  function clearRunRecord(projectPath) {
44006
44244
  for (const directory of runtimeDirectories()) {
44007
- const shared = join28(directory, "runs", SHARED_NOTE);
44245
+ const shared = join29(directory, "runs", SHARED_NOTE);
44008
44246
  const sharedRecord = recordAt(shared);
44009
44247
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
44010
44248
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -44016,7 +44254,7 @@ function clearRunRecord(projectPath) {
44016
44254
  }
44017
44255
  function ageMs(path, now) {
44018
44256
  try {
44019
- return now - statSync12(path).mtimeMs;
44257
+ return now - statSync13(path).mtimeMs;
44020
44258
  } catch {
44021
44259
  return 0;
44022
44260
  }
@@ -44025,20 +44263,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
44025
44263
  const directory = runsDirectory();
44026
44264
  const kept = new Set(everyRunRecord().flatMap((record) => [
44027
44265
  record.transcript,
44266
+ profileMarkPath(record.startedAt),
44028
44267
  ...record.profile === undefined ? [] : [record.profile]
44029
44268
  ]));
44030
44269
  let entries;
44031
44270
  try {
44032
- entries = readdirSync12(directory);
44271
+ entries = readdirSync13(directory);
44033
44272
  } catch {
44034
44273
  return;
44035
44274
  }
44036
44275
  for (const entry of entries) {
44037
- 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"));
44038
44277
  if (!transcript && !entry.endsWith(".partial")) {
44039
44278
  continue;
44040
44279
  }
44041
- const path = join28(directory, entry);
44280
+ const path = join29(directory, entry);
44042
44281
  if (kept.has(path) || ageMs(path, now) < keepMs) {
44043
44282
  continue;
44044
44283
  }
@@ -44059,7 +44298,7 @@ var init_run_record = __esm(() => {
44059
44298
  });
44060
44299
 
44061
44300
  // src/script-origins.ts
44062
- import { join as join29 } from "node:path";
44301
+ import { join as join30 } from "node:path";
44063
44302
  function unresolvedClassIn(message) {
44064
44303
  return UNRESOLVED.exec(message)?.[1] ?? null;
44065
44304
  }
@@ -44112,7 +44351,7 @@ function checkWithEngine(godotPath, projectPath) {
44112
44351
  const ran = await runEngine(godotPath, [
44113
44352
  "--headless",
44114
44353
  "--log-file",
44115
- join29(logDir, "engine.log"),
44354
+ join30(logDir, "engine.log"),
44116
44355
  "--path",
44117
44356
  projectPath,
44118
44357
  "--check-only",
@@ -44205,6 +44444,52 @@ function isCalibration(signature) {
44205
44444
  const parts = signatureParts(signature);
44206
44445
  return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
44207
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
+ }
44208
44493
  function perCallCostOf(totals) {
44209
44494
  for (const [signature, sum] of Object.entries(totals.functions)) {
44210
44495
  const parts = signatureParts(signature);
@@ -44337,17 +44622,18 @@ import {
44337
44622
  closeSync as closeSync3,
44338
44623
  constants,
44339
44624
  copyFileSync,
44340
- existsSync as existsSync23,
44625
+ existsSync as existsSync24,
44341
44626
  fstatSync,
44342
44627
  mkdirSync as mkdirSync17,
44343
44628
  openSync as openSync4,
44344
- readdirSync as readdirSync13,
44629
+ readdirSync as readdirSync14,
44345
44630
  readFileSync as readFileSync26,
44346
44631
  readSync as readSync2,
44347
44632
  rmdirSync as rmdirSync2,
44348
- statSync as statSync13
44633
+ statSync as statSync14,
44634
+ writeFileSync as writeFileSync18
44349
44635
  } from "node:fs";
44350
- 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";
44351
44637
  import { setTimeout as delay5 } from "node:timers/promises";
44352
44638
  import { fileURLToPath as fileURLToPath5 } from "node:url";
44353
44639
  function editorRestartTimeoutMs() {
@@ -44377,22 +44663,22 @@ function writtenFileRefusal(path, rule) {
44377
44663
  if (/^(res|user):\/\//.test(path)) {
44378
44664
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
44379
44665
  }
44380
- if (!isAbsolute4(path)) {
44666
+ if (!isAbsolute5(path)) {
44381
44667
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
44382
44668
  }
44383
44669
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
44384
44670
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
44385
44671
  }
44386
- const directory = realPathOr(dirname22(path));
44387
- if (!existsSync23(directory)) {
44388
- 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.`;
44389
44675
  }
44390
- const where = join30(directory, basename9(path));
44676
+ const where = join31(directory, basename10(path));
44391
44677
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
44392
44678
  if (inside !== undefined) {
44393
44679
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
44394
44680
  }
44395
- const taken = rule.writes(where).find((file) => existsSync23(file));
44681
+ const taken = rule.writes(where).find((file) => existsSync24(file));
44396
44682
  if (taken !== undefined) {
44397
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.`;
44398
44684
  }
@@ -44501,7 +44787,7 @@ function exited(run) {
44501
44787
  return run.exitCode !== null || run.exitSignal !== null;
44502
44788
  }
44503
44789
  function howItExited(run) {
44504
- 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`;
44505
44791
  }
44506
44792
  function errorsUnread(run) {
44507
44793
  return run.throughEditor && typeof run.errorReport !== "string";
@@ -44609,7 +44895,7 @@ function endedWithoutACode(run) {
44609
44895
  function ourRunEnded(run, now) {
44610
44896
  const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
44611
44897
  const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
44612
- 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}`;
44613
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.`;
44614
44900
  }
44615
44901
  function stopVerdict(stop) {
@@ -44928,7 +45214,7 @@ function notRunNote(notRun, failFast) {
44928
45214
  function exportPresetRefusal(projectPath, preset) {
44929
45215
  let text;
44930
45216
  try {
44931
- text = readFileSync26(join30(projectPath, "export_presets.cfg"), "utf8");
45217
+ text = readFileSync26(join31(projectPath, "export_presets.cfg"), "utf8");
44932
45218
  } catch {
44933
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.`;
44934
45220
  }
@@ -44950,7 +45236,7 @@ function exportPresetRefusal(projectPath, preset) {
44950
45236
  }
44951
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(", ")}.`;
44952
45238
  }
44953
- function exportAnswer(ending, asked, before, after, beside = []) {
45239
+ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
44954
45240
  const { log } = ending;
44955
45241
  const written = after !== null && after !== before;
44956
45242
  const notCopied = librariesNotCopied(log.all);
@@ -44967,6 +45253,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
44967
45253
  librariesNotLoaded: notCopied,
44968
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.`
44969
45255
  },
45256
+ ...docCacheNote === null ? {} : { docCacheNote },
44970
45257
  ...foldedEntriesAnswer(log)
44971
45258
  };
44972
45259
  if (verdict.exported) {
@@ -44991,9 +45278,9 @@ function writtenBeside(before, after, output, within) {
44991
45278
  function filesIn(folder) {
44992
45279
  const found = new Map;
44993
45280
  try {
44994
- for (const entry of readdirSync13(folder, { withFileTypes: true })) {
45281
+ for (const entry of readdirSync14(folder, { withFileTypes: true })) {
44995
45282
  if (entry.isFile()) {
44996
- found.set(entry.name, statSync13(join30(folder, entry.name)).mtimeMs);
45283
+ found.set(entry.name, statSync14(join31(folder, entry.name)).mtimeMs);
44997
45284
  }
44998
45285
  }
44999
45286
  } catch {}
@@ -45023,7 +45310,7 @@ function unsavedInEditorNote(scripts) {
45023
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.`;
45024
45311
  }
45025
45312
  function editorAddonMissing(projectPath) {
45026
- if (!existsSync23(join30(projectPath, "project.godot"))) {
45313
+ if (!existsSync24(join31(projectPath, "project.godot"))) {
45027
45314
  return null;
45028
45315
  }
45029
45316
  const report = inspectProject(projectPath);
@@ -45261,7 +45548,7 @@ function profileFrameFunctions(projectPath) {
45261
45548
  let projectGodot = null;
45262
45549
  if (projectPath !== null) {
45263
45550
  try {
45264
- projectGodot = readFileSync26(join30(projectPath, "project.godot"), "utf8");
45551
+ projectGodot = readFileSync26(join31(projectPath, "project.godot"), "utf8");
45265
45552
  } catch {}
45266
45553
  }
45267
45554
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -45435,7 +45722,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
45435
45722
  }
45436
45723
  function gdUnitFailsOnScriptErrors(projectPath) {
45437
45724
  try {
45438
- 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;
45439
45726
  } catch {
45440
45727
  return true;
45441
45728
  }
@@ -45450,13 +45737,13 @@ function uncaughtNote(uncaught) {
45450
45737
  }
45451
45738
  function elsewhereIn(projectPath, asked) {
45452
45739
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
45453
- if (wanted === "" || existsSync23(join30(projectPath, wanted))) {
45740
+ if (wanted === "" || existsSync24(join31(projectPath, wanted))) {
45454
45741
  return "";
45455
45742
  }
45456
45743
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
45457
45744
  let near = [];
45458
45745
  try {
45459
- 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()));
45460
45747
  } catch {
45461
45748
  return "";
45462
45749
  }
@@ -45547,23 +45834,23 @@ function engineChecked(why) {
45547
45834
  }
45548
45835
  function isDirectory(path) {
45549
45836
  try {
45550
- return statSync13(path).isDirectory();
45837
+ return statSync14(path).isDirectory();
45551
45838
  } catch {
45552
45839
  return false;
45553
45840
  }
45554
45841
  }
45555
45842
  function scriptsUnder(directory, prefix) {
45556
- if (existsSync23(join30(directory, ".gdignore"))) {
45843
+ if (existsSync24(join31(directory, ".gdignore"))) {
45557
45844
  return [];
45558
45845
  }
45559
45846
  const found = [];
45560
- 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)) {
45561
45848
  if (entry.name.startsWith(".")) {
45562
45849
  continue;
45563
45850
  }
45564
45851
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
45565
45852
  if (entry.isDirectory()) {
45566
- found.push(...scriptsUnder(join30(directory, entry.name), path));
45853
+ found.push(...scriptsUnder(join31(directory, entry.name), path));
45567
45854
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
45568
45855
  found.push(path);
45569
45856
  }
@@ -45579,7 +45866,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
45579
45866
  return false;
45580
45867
  }
45581
45868
  try {
45582
- return readFileSync26(join30(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
45869
+ return readFileSync26(join31(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
45583
45870
  } catch {
45584
45871
  return false;
45585
45872
  }
@@ -45604,7 +45891,7 @@ function needsItsOwnProcess(asked) {
45604
45891
  class GodotServer {
45605
45892
  stdio = null;
45606
45893
  locator = new GodotLocator;
45607
- operationsScript = join30(__dirname2, "godot", "operations", "godot_operations.gd");
45894
+ operationsScript = join31(__dirname2, "godot", "operations", "godot_operations.gd");
45608
45895
  godotBridge;
45609
45896
  tools = buildToolDefinitions();
45610
45897
  activeProcess = null;
@@ -45631,6 +45918,7 @@ class GodotServer {
45631
45918
  inFlight = new Map;
45632
45919
  callsOnGames = new Set;
45633
45920
  callsTaken = 0;
45921
+ answersDirectory;
45634
45922
  clientNoted = null;
45635
45923
  parentCommand = null;
45636
45924
  endNoted = false;
@@ -45864,14 +46152,14 @@ class GodotServer {
45864
46152
  }
45865
46153
  noteTheStart() {
45866
46154
  const cwd = process.cwd();
45867
- const mcpJson = join30(cwd, ".mcp.json");
46155
+ const mcpJson = join31(cwd, ".mcp.json");
45868
46156
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
45869
46157
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
45870
46158
  writeLifecycle("started", {
45871
46159
  argv: process.argv.slice(1),
45872
46160
  cwd,
45873
46161
  project: this.ownProject,
45874
- mcpJson: existsSync23(mcpJson) ? mcpJson : null
46162
+ mcpJson: existsSync24(mcpJson) ? mcpJson : null
45875
46163
  });
45876
46164
  if (predecessor !== null) {
45877
46165
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -46012,7 +46300,7 @@ class GodotServer {
46012
46300
  return checked.response;
46013
46301
  }
46014
46302
  const named = args["projectPath"];
46015
- if (typeof named === "string" && existsSync23(join30(named, "project.godot"))) {
46303
+ if (typeof named === "string" && existsSync24(join31(named, "project.godot"))) {
46016
46304
  this.lastProjectPath = named;
46017
46305
  }
46018
46306
  this.updates.refresh();
@@ -46029,12 +46317,66 @@ class GodotServer {
46029
46317
  this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
46030
46318
  try {
46031
46319
  const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress);
46032
- return this.withFeedbackNotice(this.withUpdateNotice(answer));
46320
+ return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
46033
46321
  } finally {
46034
46322
  this.inFlight.delete(call);
46035
46323
  }
46036
46324
  });
46037
46325
  }
46326
+ withinTheClientsLimit(tool, answer) {
46327
+ const sizeOf = (item) => item.type === "text" ? Buffer.byteLength(item.text) : item.data.length;
46328
+ const bytes = answer.content.reduce((sum, item) => sum + sizeOf(item), 0);
46329
+ if (bytes <= ANSWER_BUDGET) {
46330
+ return answer;
46331
+ }
46332
+ this.answersDirectory ??= scratchDirectory("answers");
46333
+ const directory = this.answersDirectory;
46334
+ const stem = `${tool}-${Date.now()}`;
46335
+ let savedAt;
46336
+ let notSaved;
46337
+ try {
46338
+ savedAt = answer.content.map((item, index) => {
46339
+ const file = join31(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46340
+ writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
46341
+ return file;
46342
+ });
46343
+ } catch (error) {
46344
+ notSaved = errorMessage(error);
46345
+ }
46346
+ const first = answer.content[0];
46347
+ let parsed = null;
46348
+ try {
46349
+ parsed = first?.type === "text" ? JSON.parse(first.text) : null;
46350
+ } catch {}
46351
+ const small = {};
46352
+ const leftOut = [];
46353
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
46354
+ for (const [key, value] of Object.entries(parsed)) {
46355
+ if (JSON.stringify(value).length <= 2000) {
46356
+ small[key] = value;
46357
+ } else {
46358
+ leftOut.push(key);
46359
+ }
46360
+ }
46361
+ }
46362
+ return {
46363
+ ...answer,
46364
+ content: [
46365
+ {
46366
+ type: "text",
46367
+ text: answerJson({
46368
+ ...small,
46369
+ answerTooLarge: {
46370
+ bytes,
46371
+ savedAt,
46372
+ leftOut: leftOut.length > 0 ? leftOut : undefined,
46373
+ note: savedAt === undefined ? `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), and writing it to a file failed: ${notSaved ?? "no reason given"}. The fields of its top level that are short are kept here.` : `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), so it was written whole to savedAt; read it from there. The fields of its top level that are short are kept here. The files are removed once this server has stopped.`
46374
+ }
46375
+ })
46376
+ }
46377
+ ]
46378
+ };
46379
+ }
46038
46380
  async answered(tool, op, args) {
46039
46381
  try {
46040
46382
  return await this.dispatch(tool, op, args);
@@ -46286,6 +46628,7 @@ class GodotServer {
46286
46628
  const contained = this.containProjectFiles({ ...arguments_, ...extra });
46287
46629
  return contained.ok ? await this.handleViaBridge(command, contained.value, op === "" ? tool : `${tool} ${op}`) : contained.response;
46288
46630
  };
46631
+ const editorOrEngine = async (command, effect, extra = {}) => await this.editorOrEngine(command, { ...arguments_, ...extra }, `${tool} ${op}`, effect);
46289
46632
  switch (tool) {
46290
46633
  case "project_info":
46291
46634
  return await this.handleProjectInfo(args);
@@ -46298,7 +46641,7 @@ class GodotServer {
46298
46641
  case "project_test":
46299
46642
  return await this.handleRunTests(args);
46300
46643
  case "scene_create":
46301
- return op === "create" ? await bridge("create_scene") : await bridge("save_scene");
46644
+ return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
46302
46645
  case "scene_tree":
46303
46646
  return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
46304
46647
  scenePath: file,
@@ -46308,28 +46651,28 @@ class GodotServer {
46308
46651
  case "scene_node":
46309
46652
  switch (op) {
46310
46653
  case "add":
46311
- return await bridge("add_node");
46654
+ return await editorOrEngine("add_node", "writes");
46312
46655
  case "get":
46313
- return await bridge("get_node_properties");
46656
+ return await editorOrEngine("get_node_properties", "reads");
46314
46657
  case "set":
46315
- return await bridge("set_node_properties");
46658
+ return await editorOrEngine("set_node_properties", "writes");
46316
46659
  case "duplicate":
46317
- return await bridge("duplicate_node", { parentPath: args["parentNodePath"] });
46660
+ return await editorOrEngine("duplicate_node", "writes", { parentPath: args["parentNodePath"] });
46318
46661
  case "reparent":
46319
- return await bridge("reparent_node");
46662
+ return await editorOrEngine("reparent_node", "writes");
46320
46663
  case "delete":
46321
- return await bridge("delete_node");
46664
+ return await editorOrEngine("delete_node", "writes");
46322
46665
  default:
46323
- return await bridge("set_tilemap_cells", { tilemapNodePath: args["nodePath"] });
46666
+ return await editorOrEngine("set_tilemap_cells", "writes", { tilemapNodePath: args["nodePath"] });
46324
46667
  }
46325
46668
  case "scene_signal":
46326
46669
  switch (op) {
46327
46670
  case "connect":
46328
- return await bridge("connect_signal");
46671
+ return await editorOrEngine("connect_signal", "writes");
46329
46672
  case "disconnect":
46330
- return await bridge("disconnect_signal");
46673
+ return await editorOrEngine("disconnect_signal", "writes");
46331
46674
  default:
46332
- return await bridge("list_connections");
46675
+ return await editorOrEngine("list_connections", "reads");
46333
46676
  }
46334
46677
  case "scene_animation":
46335
46678
  switch (op) {
@@ -46380,17 +46723,17 @@ class GodotServer {
46380
46723
  }), async () => await bridge("read_resource", kept));
46381
46724
  }
46382
46725
  case "create":
46383
- return await bridge("create_resource");
46726
+ return await editorOrEngine("create_resource", "writes");
46384
46727
  case "modify":
46385
- return await bridge("modify_resource");
46728
+ return await editorOrEngine("modify_resource", "writes");
46386
46729
  case "create_shader":
46387
- return await bridge("create_shader", { shaderPath: args["resourcePath"] });
46730
+ return await editorOrEngine("create_shader", "writes", { shaderPath: args["resourcePath"] });
46388
46731
  case "create_tileset":
46389
- return await bridge("create_tileset", { tilesetPath: args["resourcePath"] });
46732
+ return await editorOrEngine("create_tileset", "writes", { tilesetPath: args["resourcePath"] });
46390
46733
  case "set_theme_color":
46391
- return await bridge("set_theme_color", { themePath: args["resourcePath"] });
46734
+ return await editorOrEngine("set_theme_color", "writes", { themePath: args["resourcePath"] });
46392
46735
  default:
46393
- return await bridge("set_theme_font_size", { themePath: args["resourcePath"] });
46736
+ return await editorOrEngine("set_theme_font_size", "writes", { themePath: args["resourcePath"] });
46394
46737
  }
46395
46738
  case "editor_launch":
46396
46739
  if (op === "close") {
@@ -46611,8 +46954,8 @@ class GodotServer {
46611
46954
  if (path === undefined) {
46612
46955
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
46613
46956
  }
46614
- const file = join30(path, "project.godot");
46615
- if (!existsSync23(file)) {
46957
+ const file = join31(path, "project.godot");
46958
+ if (!existsSync24(file)) {
46616
46959
  return {
46617
46960
  ok: false,
46618
46961
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -46658,7 +47001,7 @@ class GodotServer {
46658
47001
  }
46659
47002
  runEnvironment(args) {
46660
47003
  const savesIn = readNonEmptyString(args, "savesIn");
46661
- if (savesIn !== undefined && !isAbsolute4(savesIn)) {
47004
+ if (savesIn !== undefined && !isAbsolute5(savesIn)) {
46662
47005
  return {
46663
47006
  ok: false,
46664
47007
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -46917,7 +47260,8 @@ class GodotServer {
46917
47260
  uidsCreated: given,
46918
47261
  stillWithoutUid: after,
46919
47262
  note: uidsLeftNote(after.length),
46920
- ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
47263
+ ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
47264
+ ...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
46921
47265
  }
46922
47266
  }, scanned));
46923
47267
  }
@@ -47156,6 +47500,9 @@ class GodotServer {
47156
47500
  if (imported.extensionNote !== undefined) {
47157
47501
  extra["extensionNote"] = imported.extensionNote;
47158
47502
  }
47503
+ if (imported.docCacheNote !== undefined) {
47504
+ extra["docCacheNote"] = imported.docCacheNote;
47505
+ }
47159
47506
  }
47160
47507
  const after = await this.operation("get_import_status", statusAsk, projectPath);
47161
47508
  if (!after.ok) {
@@ -47256,6 +47603,10 @@ class GodotServer {
47256
47603
  if (unknown.length > 0) {
47257
47604
  return this.createErrorResponse(`project_info cannot include ${unknown.join(", ")}. Sections: ${Object.keys(PROJECT_INFO_SECTIONS2).join(", ")}.`);
47258
47605
  }
47606
+ const forValidation = ["preset", "detail"].filter((name) => args[name] !== undefined);
47607
+ if (forValidation.length > 0 && !include.includes("validation")) {
47608
+ return this.createErrorResponse(`${forValidation.join(" and ")} ${forValidation.length === 1 ? "is" : "are"} read only by the validation section, which this call does not include: add "validation" to include, or leave ${forValidation.length === 1 ? "it" : "them"} out.`);
47609
+ }
47259
47610
  const project = this.project(args);
47260
47611
  if (!project.ok) {
47261
47612
  return project.response;
@@ -47268,7 +47619,7 @@ class GodotServer {
47268
47619
  const name = application["config/name"];
47269
47620
  const mainScene = application["run/main_scene"];
47270
47621
  const info = {
47271
- name: typeof name === "string" && name !== "" ? name : basename9(project.value.path),
47622
+ name: typeof name === "string" && name !== "" ? name : basename10(project.value.path),
47272
47623
  path: project.value.path,
47273
47624
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
47274
47625
  godotVersion: await this.godotVersion(engine.value),
@@ -47351,18 +47702,18 @@ class GodotServer {
47351
47702
  if (!engine.ok) {
47352
47703
  return engine.response;
47353
47704
  }
47354
- const folder = dirname22(output.absolutePath);
47705
+ const folder = dirname23(output.absolutePath);
47355
47706
  try {
47356
47707
  mkdirSync17(folder, { recursive: true });
47357
47708
  } catch (error) {
47358
- 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)}`);
47359
47710
  }
47360
47711
  const debug = readBoolean(args, "debug") ?? false;
47361
47712
  const exportLogs = scratchDirectory("export");
47362
47713
  const exportArgs = [
47363
47714
  "--headless",
47364
47715
  "--log-file",
47365
- join30(exportLogs, "engine.log"),
47716
+ join31(exportLogs, "engine.log"),
47366
47717
  "--path",
47367
47718
  project.value.path,
47368
47719
  debug ? "--export-debug" : "--export-release",
@@ -47370,24 +47721,37 @@ class GodotServer {
47370
47721
  output.absolutePath
47371
47722
  ];
47372
47723
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
47373
- const writtenAt = () => existsSync23(output.absolutePath) ? statSync13(output.absolutePath).mtimeMs : null;
47374
- const before = writtenAt();
47375
- const listedBefore = filesIn(folder);
47376
- const freed = freeLibraryCopies(project.value.path);
47377
- if (freed.setAside.length > 0 || freed.swept.length > 0) {
47378
- this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
47379
- }
47380
- 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;
47381
47742
  try {
47382
- 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();
47383
47746
  } finally {
47384
47747
  discard(exportLogs);
47385
47748
  }
47749
+ const { ending, before, listedBefore } = run;
47386
47750
  if (ending.exitSignal === null && ending.exitCode === null) {
47387
47751
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
47388
47752
  }
47389
- const beside = writtenBeside(listedBefore, filesIn(folder), basename9(output.absolutePath), dirname22(output.relativePath).replaceAll("\\", "/"));
47390
- 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);
47391
47755
  }
47392
47756
  reportTestProgress(log) {
47393
47757
  const progress = callProgress();
@@ -47440,10 +47804,10 @@ class GodotServer {
47440
47804
  if (movie !== null) {
47441
47805
  return this.createErrorResponse(movie);
47442
47806
  }
47443
- 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))));
47444
47808
  const asked = paths.value.filter((path) => !missing.includes(path));
47445
47809
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
47446
- const noRunner = !existsSync23(join30(project.value.path, runner));
47810
+ const noRunner = !existsSync24(join31(project.value.path, runner));
47447
47811
  if (asked.length === 0) {
47448
47812
  const verdict = `nothing at ${missing.join(" and ")}`;
47449
47813
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -47534,21 +47898,21 @@ class GodotServer {
47534
47898
  await untilGone([run.process.pid]);
47535
47899
  }
47536
47900
  await run.settled;
47537
- const reportsDir = join30(project.value.path, ".godot", ...ours.split("/"));
47901
+ const reportsDir = join31(project.value.path, ".godot", ...ours.split("/"));
47538
47902
  let report = null;
47539
47903
  let reportProblem = null;
47540
47904
  try {
47541
- 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))) : [];
47542
47906
  const newest = written.at(-1);
47543
47907
  if (newest !== undefined) {
47544
- report = parseJUnit(readFileSync26(join30(reportsDir, newest, "results.xml"), "utf8"));
47908
+ report = parseJUnit(readFileSync26(join31(reportsDir, newest, "results.xml"), "utf8"));
47545
47909
  }
47546
47910
  } catch (error) {
47547
47911
  reportProblem = errorMessage(error);
47548
47912
  } finally {
47549
47913
  discard(reportsDir, userData);
47550
47914
  try {
47551
- rmdirSync2(dirname22(reportsDir));
47915
+ rmdirSync2(dirname23(reportsDir));
47552
47916
  } catch {}
47553
47917
  }
47554
47918
  const engineEntries = foldedRepeats(run.log.select({
@@ -47580,7 +47944,7 @@ class GodotServer {
47580
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: [] };
47581
47945
  const scriptErrors = [...declaring.origins, ...printedErrors];
47582
47946
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
47583
- 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);
47584
47948
  const nothingAt = missing.join(" and ");
47585
47949
  const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
47586
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] ?? "")}`;
@@ -47640,7 +48004,7 @@ class GodotServer {
47640
48004
  isError: true
47641
48005
  };
47642
48006
  }
47643
- const failed = failedCases(report, Buffer.concat(console2).toString("utf8"));
48007
+ const { failed, briefed } = failedWithin(failedCases(report, Buffer.concat(console2).toString("utf8")), FAILED_BUDGET);
47644
48008
  const orphans = orphansPrinted(said);
47645
48009
  const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
47646
48010
  const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
@@ -47680,6 +48044,7 @@ class GodotServer {
47680
48044
  skipped: report.skipped,
47681
48045
  time: report.time,
47682
48046
  failed,
48047
+ failedWithoutDetail: briefed > 0 ? briefed : undefined,
47683
48048
  hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
47684
48049
  uncaughtScriptErrors: uncaughtFolded.length > 0 ? uncaughtFolded : undefined,
47685
48050
  warnings: warnings.length > 0 ? warnings : undefined,
@@ -47691,6 +48056,7 @@ class GodotServer {
47691
48056
  ...scanned,
47692
48057
  note: [
47693
48058
  missingNote,
48059
+ briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
47694
48060
  notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
47695
48061
  uncaughtNote(uncaught),
47696
48062
  ranAs.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRunNote(notRun, readBoolean(args, "failFast") === true)
@@ -47854,7 +48220,11 @@ class GodotServer {
47854
48220
  };
47855
48221
  }
47856
48222
  markedIfStale(answer) {
47857
- return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
48223
+ const status = this.godotBridge.getStatus();
48224
+ if (!hasSaidWhoItIs(status)) {
48225
+ return answer;
48226
+ }
48227
+ return markIfStale(answer, status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
47858
48228
  }
47859
48229
  async diagnosticsOf(args) {
47860
48230
  const waited = await this.waitForTheEditorOpenedHere(args, []);
@@ -47901,7 +48271,7 @@ class GodotServer {
47901
48271
  const cached = cachedClasses(projectPath);
47902
48272
  const sourceOf = (resourcePath) => {
47903
48273
  const contained = resolveWithinProject(projectPath, resourcePath);
47904
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48274
+ if (!contained.ok || !existsSync24(contained.absolutePath)) {
47905
48275
  return null;
47906
48276
  }
47907
48277
  try {
@@ -47995,7 +48365,7 @@ class GodotServer {
47995
48365
  }
47996
48366
  sourceOfClass(projectPath, resourcePath) {
47997
48367
  const contained = resolveWithinProject(projectPath, resourcePath);
47998
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48368
+ if (!contained.ok || !existsSync24(contained.absolutePath)) {
47999
48369
  return null;
48000
48370
  }
48001
48371
  try {
@@ -48157,7 +48527,7 @@ class GodotServer {
48157
48527
  ...Array.isArray(stack) ? {
48158
48528
  stack: stack.map((frame) => ({
48159
48529
  ...frame,
48160
- ...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)) } : {}
48161
48531
  }))
48162
48532
  } : {}
48163
48533
  });
@@ -48436,7 +48806,7 @@ class GodotServer {
48436
48806
  return null;
48437
48807
  }
48438
48808
  try {
48439
- return settingKeys(readFileSync26(join30(projectPath, "project.godot"), "utf8"));
48809
+ return settingKeys(readFileSync26(join31(projectPath, "project.godot"), "utf8"));
48440
48810
  } catch {
48441
48811
  return null;
48442
48812
  }
@@ -48675,7 +49045,7 @@ class GodotServer {
48675
49045
  return;
48676
49046
  }
48677
49047
  const connected = this.godotBridge.isConnected() ? this.godotBridge.getStatus().editorPid : undefined;
48678
- const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected);
49048
+ const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected && alive(pid));
48679
49049
  const started = await startTimesOf(pids);
48680
49050
  return pids.map((pid) => {
48681
49051
  const at = started.get(pid) ?? tree.get(pid)?.startedAt;
@@ -48873,7 +49243,7 @@ class GodotServer {
48873
49243
  }
48874
49244
  async runtimeUp(projectPath, before, budget, withArgs) {
48875
49245
  const budgetMs = budget.ms;
48876
- if (!existsSync23(join30(projectPath, RUNTIME_AUTOLOAD.path))) {
49246
+ if (!existsSync24(join31(projectPath, RUNTIME_AUTOLOAD.path))) {
48877
49247
  return runtimeVerdict(null, {
48878
49248
  addon: false,
48879
49249
  budgetMs,
@@ -48959,7 +49329,7 @@ class GodotServer {
48959
49329
  }
48960
49330
  let announcedAt;
48961
49331
  try {
48962
- announcedAt = statSync13(endpoint.file).mtimeMs;
49332
+ announcedAt = statSync14(endpoint.file).mtimeMs;
48963
49333
  } catch {
48964
49334
  return;
48965
49335
  }
@@ -49849,9 +50219,13 @@ class GodotServer {
49849
50219
  if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
49850
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"]);
49851
50221
  }
50222
+ const asked = {
50223
+ mark: readBoolean(args, "mark") === true,
50224
+ since: readString(args, "since") === "mark"
50225
+ };
49852
50226
  const going = await this.runStillGoing(run);
49853
50227
  const projectPath = run.projectPath;
49854
- const projectGone = projectPath !== null && !existsSync23(join30(projectPath, "project.godot"));
50228
+ const projectGone = projectPath !== null && !existsSync24(join31(projectPath, "project.godot"));
49855
50229
  if (projectGone && callersName !== undefined) {
49856
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"]);
49857
50231
  }
@@ -49861,7 +50235,7 @@ class GodotServer {
49861
50235
  ...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
49862
50236
  };
49863
50237
  if (run.throughEditor) {
49864
- return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
50238
+ return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
49865
50239
  }
49866
50240
  const path = run.profile;
49867
50241
  if (path === undefined) {
@@ -49874,15 +50248,16 @@ class GodotServer {
49874
50248
  ]);
49875
50249
  }
49876
50250
  const totals = file.connected ? file : null;
49877
- return this.jsonTextResponse(profileAnswer({
50251
+ return this.profileReply(run, {
49878
50252
  through: "gdharness",
49879
50253
  running: going,
49880
50254
  totals,
49881
50255
  coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
49882
50256
  ...fromProject
49883
- }, limit));
50257
+ }, limit, asked);
49884
50258
  }
49885
- async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
50259
+ async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
50260
+ const { projectPath } = run;
49886
50261
  const read = await this.handleViaBridge("profile_read", {});
49887
50262
  if (read.isError === true) {
49888
50263
  return read;
@@ -49904,12 +50279,13 @@ class GodotServer {
49904
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"]);
49905
50280
  }
49906
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." : "";
49907
50283
  return this.jsonTextResponse({
49908
50284
  through: "editor",
49909
50285
  running: true,
49910
50286
  started: true,
49911
50287
  functions: [],
49912
- 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}`
49913
50289
  });
49914
50290
  }
49915
50291
  const functions = asParams(held["functions"]);
@@ -49932,13 +50308,70 @@ class GodotServer {
49932
50308
  ];
49933
50309
  }))
49934
50310
  } : null;
49935
- return this.jsonTextResponse(profileAnswer({
50311
+ return this.profileReply(run, {
49936
50312
  through: "editor",
49937
50313
  running: going,
49938
50314
  totals,
49939
50315
  coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
49940
50316
  ...fromProject
49941
- }, 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
+ });
49942
50375
  }
49943
50376
  async firstProfiledFrame() {
49944
50377
  const deadline = Date.now() + PROFILER_ON_MS;
@@ -50139,7 +50572,7 @@ class GodotServer {
50139
50572
  notes.push(endedWithoutACode({
50140
50573
  throughEditor: run.throughEditor,
50141
50574
  announced: announced !== undefined || run.announcedPid !== undefined,
50142
- couldAnnounce: run.projectPath !== null && existsSync23(join30(run.projectPath, RUNTIME_AUTOLOAD.path)),
50575
+ couldAnnounce: run.projectPath !== null && existsSync24(join31(run.projectPath, RUNTIME_AUTOLOAD.path)),
50143
50576
  endedHere: typeof run.endedHere === "string",
50144
50577
  pickedUp: run.pickedUp === true
50145
50578
  }));
@@ -50769,7 +51202,7 @@ class GodotServer {
50769
51202
  if (!scriptPath.ok) {
50770
51203
  return this.createErrorResponse(scriptPath.reason);
50771
51204
  }
50772
- if (!existsSync23(scriptPath.absolutePath)) {
51205
+ if (!existsSync24(scriptPath.absolutePath)) {
50773
51206
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
50774
51207
  }
50775
51208
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -50914,6 +51347,45 @@ class GodotServer {
50914
51347
  ...engineExtras(outcome)
50915
51348
  });
50916
51349
  }
51350
+ async editorOrEngine(command, args, askedAs, effect) {
51351
+ const contained = this.containProjectFiles(args);
51352
+ if (!contained.ok) {
51353
+ return contained.response;
51354
+ }
51355
+ const project = this.project(contained.value);
51356
+ if (!project.ok) {
51357
+ return project.response;
51358
+ }
51359
+ const projectPath = project.value.path;
51360
+ const waited = await this.waitForTheEditorOpenedHere(contained.value, []);
51361
+ if (this.editorHasOpen(projectPath)) {
51362
+ return await this.handleViaBridge(command, contained.value, askedAs);
51363
+ }
51364
+ if (effect === "writes") {
51365
+ if (waited !== null) {
51366
+ return waited;
51367
+ }
51368
+ const arriving = await this.editorsNotYetConnected(projectPath);
51369
+ if (arriving !== undefined && arriving.length > 0) {
51370
+ const named = arriving.map((one) => `pid ${one.pid}`).join(", ");
51371
+ return this.createErrorResponse(`${askedAs} was not run: an editor of this project is running (${named}) and has not reached this server, and saving a scene or resource it holds would put back what a write made around it.`, [
51372
+ "editor_status names it under editorsNotYetConnected and says when it has connected; the write then goes through it",
51373
+ `If it never connects it is not reaching this server: end ${arriving.length === 1 ? "that pid" : "those pids"} and ask again`
51374
+ ]);
51375
+ }
51376
+ }
51377
+ const outcome = await this.operation("run_editor_tool", { command, arguments: contained.value }, projectPath);
51378
+ if (!outcome.ok) {
51379
+ return this.answer(outcome);
51380
+ }
51381
+ return this.jsonTextResponse({
51382
+ ...outcome.payload,
51383
+ ...effect === "reads" ? {
51384
+ readBy: "a headless engine, since no editor serves this project; unsaved changes in an editor are not seen"
51385
+ } : { writtenBy: "a headless engine, since no editor serves this project" },
51386
+ ...engineExtras(outcome)
51387
+ });
51388
+ }
50917
51389
  async handleMoveFile(given) {
50918
51390
  const project = this.project(given);
50919
51391
  if (!project.ok) {
@@ -51312,7 +51784,7 @@ class GodotServer {
51312
51784
  async ownRunOnItsWay(projectPath, budgetMs) {
51313
51785
  const run = await this.currentRun();
51314
51786
  const project = projectPath ?? run?.projectPath ?? null;
51315
- 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)) {
51316
51788
  return null;
51317
51789
  }
51318
51790
  const already = runtimesAnnounced().running;
@@ -51321,7 +51793,7 @@ class GodotServer {
51321
51793
  return !run.announcedBefore.has(one.pid);
51322
51794
  }
51323
51795
  try {
51324
- return statSync13(one.file).mtimeMs >= run.startedAt;
51796
+ return statSync14(one.file).mtimeMs >= run.startedAt;
51325
51797
  } catch {
51326
51798
  return false;
51327
51799
  }
@@ -51402,7 +51874,7 @@ class GodotServer {
51402
51874
  }
51403
51875
  }
51404
51876
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
51405
- const screenshotPath = screenshotDir ? join30(screenshotDir, "capture.png") : null;
51877
+ const screenshotPath = screenshotDir ? join31(screenshotDir, "capture.png") : null;
51406
51878
  const staleNote = this.runtimeStaleNote(choice.endpoint);
51407
51879
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
51408
51880
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
@@ -51583,7 +52055,7 @@ async function runGodotServer() {
51583
52055
  const server = new GodotServer;
51584
52056
  await server.run();
51585
52057
  }
51586
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. 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 listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
52058
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. 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 listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
51587
52059
  var init_server = __esm(() => {
51588
52060
  init_dist();
51589
52061
  init_stdio();
@@ -51599,6 +52071,7 @@ var init_server = __esm(() => {
51599
52071
  init_cut_test_run();
51600
52072
  init_dap_client();
51601
52073
  init_desktop();
52074
+ init_doc_cache();
51602
52075
  init_editor_log();
51603
52076
  init_engine_run();
51604
52077
  init_errors();
@@ -51641,7 +52114,7 @@ var init_server = __esm(() => {
51641
52114
  init_tool_definitions();
51642
52115
  init_tool_reference();
51643
52116
  init_update_check();
51644
- __dirname2 = dirname22(fileURLToPath5(import.meta.url));
52117
+ __dirname2 = dirname23(fileURLToPath5(import.meta.url));
51645
52118
  KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
51646
52119
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
51647
52120
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
@@ -51701,8 +52174,8 @@ var init_server = __esm(() => {
51701
52174
  init_bridge_announce();
51702
52175
  init_errors();
51703
52176
  init_godot_path();
51704
- import { existsSync as existsSync24 } from "node:fs";
51705
- 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";
51706
52179
 
51707
52180
  // src/harnesses.ts
51708
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";
@@ -52637,10 +53110,10 @@ init_issues();
52637
53110
  init_paths();
52638
53111
 
52639
53112
  // src/prompt.ts
52640
- import process8 from "node:process";
53113
+ import process9 from "node:process";
52641
53114
  import { createInterface } from "node:readline/promises";
52642
53115
  function interactive() {
52643
- return process8.stdin.isTTY && process8.stdout.isTTY;
53116
+ return process9.stdin.isTTY && process9.stdout.isTTY;
52644
53117
  }
52645
53118
 
52646
53119
  class Ask {
@@ -52649,7 +53122,7 @@ class Ask {
52649
53122
  if (!interactive()) {
52650
53123
  return fallback;
52651
53124
  }
52652
- this.line ??= createInterface({ input: process8.stdin, output: process8.stdout });
53125
+ this.line ??= createInterface({ input: process9.stdin, output: process9.stdout });
52653
53126
  const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
52654
53127
  return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
52655
53128
  }
@@ -52667,10 +53140,10 @@ init_setup();
52667
53140
  init_setup();
52668
53141
  init_tool_definitions();
52669
53142
  init_tool_reference();
52670
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
52671
- 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";
52672
53145
  var SKILL_NAME = "gdharness";
52673
- var SHARED_SKILLS = join13(".agents", "skills");
53146
+ var SHARED_SKILLS = join14(".agents", "skills");
52674
53147
  function skillMarkdown(version) {
52675
53148
  return `---
52676
53149
  name: ${SKILL_NAME}
@@ -52757,7 +53230,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
52757
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 |
52758
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 |
52759
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 |
52760
- | 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 |
52761
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 |
52762
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 |
52763
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 |
@@ -52799,9 +53272,13 @@ so take it off when the question it was for is answered.
52799
53272
 
52800
53273
  \`scene_tree\` for what a scene holds, rather than what the \`.tscn\` text implies, and
52801
53274
  \`resource_edit read\` for a resource: both read the file through the editor when one serves the
52802
- project and through a headless engine when none does, with the same answer. \`scene_node\`,
52803
- \`scene_signal\` and the other \`resource_edit\` ops but \`move\` change it through the editor, so the
52804
- files keep their formatting. \`script_info\` comes from the editor's own language server, and so does
53275
+ project and through a headless engine when none does, with the same answer. \`scene_create\`,
53276
+ \`scene_node\`, \`scene_signal\` and the other \`resource_edit\` ops but \`move\` change it through the
53277
+ editor when one serves the project, so the files keep their formatting, and through a headless
53278
+ engine running the editor's own tool when none does. That engine runs the scene's scripts, which an
53279
+ editor runs only as tools, so the answer puts back what they changed as the file loaded, and refuses a
53280
+ file where it cannot, such as a scene whose script does not compile; open the editor
53281
+ (\`editor_launch\` with \`hidden: true\`) and ask again for those. \`script_info\` comes from the editor's own language server, and so does
52805
53282
  \`script_diagnostics\` when this project's editor is open; without it a headless engine compiles the
52806
53283
  scripts and reports their errors, with lines but no columns and no warnings.
52807
53284
 
@@ -52878,7 +53355,9 @@ control outside a headless viewport. Read the refusal instead of retrying the ca
52878
53355
  Every answer is read back from the engine after the change rather than echoed from the request, and
52879
53356
  whatever the engine said on stderr comes back under \`engine_messages\`. A headless engine runs the
52880
53357
  project's autoloads after the operation has answered, and what it reports from then until it exits
52881
- comes back separately, under \`engine_messages_after_answer\`: none of it changes the answer.
53358
+ comes back separately, under \`engine_messages_after_answer\`: none of it changes the answer. A
53359
+ check or write of a script waits for the autoloads to start, so the scripts naming them compile, and
53360
+ what they report while starting comes back under \`engine_messages_before_operation\`.
52882
53361
 
52883
53362
  ## More
52884
53363
 
@@ -52889,27 +53368,27 @@ comes back separately, under \`engine_messages_after_answer\`: none of it change
52889
53368
  function skillFiles(version) {
52890
53369
  return new Map([
52891
53370
  ["SKILL.md", skillMarkdown(version)],
52892
- [join13("references", "tools.md"), renderToolsMarkdown()]
53371
+ [join14("references", "tools.md"), renderToolsMarkdown()]
52893
53372
  ]);
52894
53373
  }
52895
53374
  function skillDirectories(harnesses, projectPath, exists) {
52896
- const directories = [join13(projectPath, SHARED_SKILLS, SKILL_NAME)];
53375
+ const directories = [join14(projectPath, SHARED_SKILLS, SKILL_NAME)];
52897
53376
  for (const harness of harnesses) {
52898
53377
  if (harness.skills === undefined) {
52899
53378
  continue;
52900
53379
  }
52901
- const own = join13(projectPath, harness.skills.dir);
52902
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join13(own, SKILL_NAME))) {
52903
- 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));
52904
53383
  }
52905
53384
  }
52906
53385
  return directories;
52907
53386
  }
52908
53387
  function everySkillDirectory(projectPath, harnesses) {
52909
- const directories = new Set([join13(projectPath, SHARED_SKILLS, SKILL_NAME)]);
53388
+ const directories = new Set([join14(projectPath, SHARED_SKILLS, SKILL_NAME)]);
52910
53389
  for (const harness of harnesses) {
52911
53390
  if (harness.skills !== undefined) {
52912
- directories.add(join13(projectPath, harness.skills.dir, SKILL_NAME));
53391
+ directories.add(join14(projectPath, harness.skills.dir, SKILL_NAME));
52913
53392
  }
52914
53393
  }
52915
53394
  return [...directories];
@@ -52917,33 +53396,33 @@ function everySkillDirectory(projectPath, harnesses) {
52917
53396
  function removeSkill(directories, projectPath) {
52918
53397
  const removed = [];
52919
53398
  for (const directory of directories) {
52920
- if (existsSync13(directory)) {
53399
+ if (existsSync14(directory)) {
52921
53400
  rmSync6(directory, { recursive: true, force: true });
52922
- pruneEmpty(dirname7(directory), projectPath);
53401
+ pruneEmpty(dirname8(directory), projectPath);
52923
53402
  removed.push(directory);
52924
53403
  }
52925
53404
  }
52926
53405
  return removed;
52927
53406
  }
52928
53407
  function pruneEmpty(directory, projectPath) {
52929
- const shared = join13(projectPath, ".agents");
53408
+ const shared = join14(projectPath, ".agents");
52930
53409
  let path = directory;
52931
- while (path !== projectPath && (basename5(path) === "skills" || path === shared)) {
52932
- if (readdirSync8(path).length > 0) {
53410
+ while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
53411
+ if (readdirSync9(path).length > 0) {
52933
53412
  return;
52934
53413
  }
52935
53414
  rmSync6(path, { recursive: true, force: true });
52936
- path = dirname7(path);
53415
+ path = dirname8(path);
52937
53416
  }
52938
53417
  }
52939
53418
  function writeSkill(directories, version) {
52940
53419
  const files = skillFiles(version);
52941
53420
  return directories.map((directory) => {
52942
- const replaced = existsSync13(directory);
53421
+ const replaced = existsSync14(directory);
52943
53422
  rmSync6(directory, { recursive: true, force: true });
52944
53423
  for (const [name, contents] of files) {
52945
- const path = join13(directory, name);
52946
- mkdirSync5(dirname7(path), { recursive: true });
53424
+ const path = join14(directory, name);
53425
+ mkdirSync5(dirname8(path), { recursive: true });
52947
53426
  writeFileSync7(path, contents, "utf8");
52948
53427
  }
52949
53428
  return { path: directory, replaced };
@@ -52952,13 +53431,13 @@ function writeSkill(directories, version) {
52952
53431
 
52953
53432
  // src/version.ts
52954
53433
  import { readFileSync as readFileSync12 } from "node:fs";
52955
- import { dirname as dirname8, join as join14 } from "node:path";
53434
+ import { dirname as dirname9, join as join15 } from "node:path";
52956
53435
  import { fileURLToPath as fileURLToPath2 } from "node:url";
52957
53436
  function getLocalVersion() {
52958
- const currentDirectory = dirname8(fileURLToPath2(import.meta.url));
53437
+ const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
52959
53438
  const packageCandidates = [
52960
- join14(currentDirectory, "..", "package.json"),
52961
- join14(currentDirectory, "..", "..", "package.json")
53439
+ join15(currentDirectory, "..", "package.json"),
53440
+ join15(currentDirectory, "..", "..", "package.json")
52962
53441
  ];
52963
53442
  for (const packagePath of packageCandidates) {
52964
53443
  try {
@@ -52980,7 +53459,7 @@ class UsageError extends Refusal {
52980
53459
  function projectArgument(at) {
52981
53460
  const named = args.slice(at).find((value) => !value.startsWith("--"));
52982
53461
  const projectPath = resolve8(named ?? ".");
52983
- if (!existsSync24(join31(projectPath, "project.godot"))) {
53462
+ if (!existsSync25(join32(projectPath, "project.godot"))) {
52984
53463
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
52985
53464
  }
52986
53465
  return projectPath;
@@ -53025,7 +53504,7 @@ async function registerRuntime(godot, projectPath) {
53025
53504
  return;
53026
53505
  }
53027
53506
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
53028
- if (!existsSync24(join31(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53507
+ if (!existsSync25(join32(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53029
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}`);
53030
53509
  }
53031
53510
  }
@@ -53157,7 +53636,7 @@ async function setup() {
53157
53636
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
53158
53637
  }
53159
53638
  if (!args.includes("--no-skill")) {
53160
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync24);
53639
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync25);
53161
53640
  for (const written of writeSkill(directories, getLocalVersion())) {
53162
53641
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53163
53642
  }
@@ -53271,7 +53750,7 @@ async function upgrade() {
53271
53750
  moved.push(took.from);
53272
53751
  }
53273
53752
  }
53274
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync24), version)) {
53753
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync25), version)) {
53275
53754
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53276
53755
  }
53277
53756
  console.log(`