gdharness 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/build/cli.js +128 -83
  2. package/build/index.js +139 -96
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -31425,7 +31425,7 @@ async function askWindows() {
31425
31425
  "-NoProfile",
31426
31426
  "-NonInteractive",
31427
31427
  "-Command",
31428
- 'Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }'
31428
+ `${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
31429
31429
  ], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
31430
31430
  return stdout;
31431
31431
  }
@@ -31436,7 +31436,7 @@ async function askPosix() {
31436
31436
  });
31437
31437
  return stdout;
31438
31438
  }
31439
- var run4, ASK_TIMEOUT_MS = 15000;
31439
+ var run4, POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ", ASK_TIMEOUT_MS = 15000;
31440
31440
  var init_process_children = __esm(() => {
31441
31441
  run4 = promisify4(execFile4);
31442
31442
  });
@@ -31993,17 +31993,49 @@ var init_bridge_announce = __esm(() => {
31993
31993
  init_errors();
31994
31994
  });
31995
31995
 
31996
+ // src/class-note.ts
31997
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
31998
+ import { dirname as dirname11, join as join16 } from "node:path";
31999
+ function classNotePath(projectPath) {
32000
+ return join16(projectPath, ".godot", "gdharness-classes.json");
32001
+ }
32002
+ function readClassNote(projectPath) {
32003
+ try {
32004
+ const fields = asParams(JSON.parse(readFileSync14(classNotePath(projectPath), "utf8")));
32005
+ const rebuiltWith = readArray(fields, "rebuiltWith");
32006
+ if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
32007
+ return null;
32008
+ }
32009
+ const pid = readNumber(fields, "shortListEditorPid");
32010
+ return {
32011
+ rebuiltWith: rebuiltWith.map(String),
32012
+ ...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
32013
+ };
32014
+ } catch {
32015
+ return null;
32016
+ }
32017
+ }
32018
+ function writeClassNote(projectPath, note) {
32019
+ try {
32020
+ const path = classNotePath(projectPath);
32021
+ mkdirSync9(dirname11(path), { recursive: true });
32022
+ writeFileSync11(path, `${JSON.stringify(note, null, 2)}
32023
+ `, "utf8");
32024
+ } catch {}
32025
+ }
32026
+ var init_class_note = () => {};
32027
+
31996
32028
  // src/config-pin.ts
31997
- import { existsSync as existsSync11, readFileSync as readFileSync14 } from "node:fs";
31998
- import { join as join16 } from "node:path";
32029
+ import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
32030
+ import { join as join17 } from "node:path";
31999
32031
  function versionsNamedInConfig(projectPath) {
32000
- const config = join16(projectPath, ".mcp.json");
32032
+ const config = join17(projectPath, ".mcp.json");
32001
32033
  if (!existsSync11(config)) {
32002
32034
  return [];
32003
32035
  }
32004
32036
  let text;
32005
32037
  try {
32006
- text = readFileSync14(config, "utf8");
32038
+ text = readFileSync15(config, "utf8");
32007
32039
  JSON.parse(text);
32008
32040
  } catch {
32009
32041
  return [];
@@ -36564,7 +36596,7 @@ var init_junit = __esm(() => {
36564
36596
  import { realpathSync as realpathSync2 } from "node:fs";
36565
36597
  import { readFile, realpath } from "node:fs/promises";
36566
36598
  import { createConnection as createConnection3 } from "node:net";
36567
- import { dirname as dirname11, resolve as resolve4 } from "node:path";
36599
+ import { dirname as dirname12, resolve as resolve4 } from "node:path";
36568
36600
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
36569
36601
  function diagnosticsKey(uri) {
36570
36602
  let decoded;
@@ -36692,7 +36724,7 @@ class GodotLSPClient {
36692
36724
  if (this.initialized) {
36693
36725
  return;
36694
36726
  }
36695
- const rootPath = this.rootPath ?? dirname11(resolve4(filePath));
36727
+ const rootPath = this.rootPath ?? dirname12(resolve4(filePath));
36696
36728
  await this.initialize(rootPath);
36697
36729
  }
36698
36730
  async sendRequest(method, params) {
@@ -37137,13 +37169,13 @@ var init_process_time = __esm(() => {
37137
37169
  });
37138
37170
 
37139
37171
  // src/project-scan.ts
37140
- import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15 } from "node:fs";
37141
- import { join as join17 } from "node:path";
37172
+ import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "node:fs";
37173
+ import { join as join18 } from "node:path";
37142
37174
  function skipped(name) {
37143
37175
  return name.startsWith(".") || SKIPPED.has(name);
37144
37176
  }
37145
37177
  function steppedOver(directory) {
37146
- return existsSync13(join17(directory, ".gdignore"));
37178
+ return existsSync13(join18(directory, ".gdignore"));
37147
37179
  }
37148
37180
  function projectStructure(projectPath) {
37149
37181
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -37156,7 +37188,7 @@ function projectStructure(projectPath) {
37156
37188
  continue;
37157
37189
  }
37158
37190
  if (entry.isDirectory()) {
37159
- visit(join17(directory, entry.name));
37191
+ visit(join18(directory, entry.name));
37160
37192
  } else if (entry.isFile()) {
37161
37193
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37162
37194
  if (extension === "tscn") {
@@ -37186,14 +37218,14 @@ function scriptsWithoutUid(projectPath) {
37186
37218
  }
37187
37219
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
37188
37220
  if (entry.isDirectory()) {
37189
- visit(join17(directory, entry.name), spelled);
37221
+ visit(join18(directory, entry.name), spelled);
37190
37222
  continue;
37191
37223
  }
37192
37224
  if (!entry.isFile()) {
37193
37225
  continue;
37194
37226
  }
37195
37227
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37196
- if (CARRIES_A_UID.has(extension) && !existsSync13(join17(directory, `${entry.name}.uid`))) {
37228
+ if (CARRIES_A_UID.has(extension) && !existsSync13(join18(directory, `${entry.name}.uid`))) {
37197
37229
  found.push(spelled);
37198
37230
  }
37199
37231
  }
@@ -37228,7 +37260,7 @@ function searchProject(projectPath, options) {
37228
37260
  if (skipped(entry.name)) {
37229
37261
  continue;
37230
37262
  }
37231
- const entryPath = join17(directory, entry.name);
37263
+ const entryPath = join18(directory, entry.name);
37232
37264
  if (entry.isDirectory()) {
37233
37265
  visit(entryPath);
37234
37266
  continue;
@@ -37239,7 +37271,7 @@ function searchProject(projectPath, options) {
37239
37271
  }
37240
37272
  result.summary.files_searched += 1;
37241
37273
  const matches = [];
37242
- for (const [index, line] of readFileSync15(entryPath, "utf8").split(`
37274
+ for (const [index, line] of readFileSync16(entryPath, "utf8").split(`
37243
37275
  `).entries()) {
37244
37276
  if (full()) {
37245
37277
  break;
@@ -37268,16 +37300,16 @@ var init_project_scan = __esm(() => {
37268
37300
  });
37269
37301
 
37270
37302
  // src/restart-note.ts
37271
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync16, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
37272
- import { dirname as dirname12, join as join18 } from "node:path";
37303
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
37304
+ import { dirname as dirname13, join as join19 } from "node:path";
37273
37305
  function restartNotePath(projectPath) {
37274
- return join18(projectPath, ".godot", "gdharness-restart.json");
37306
+ return join19(projectPath, ".godot", "gdharness-restart.json");
37275
37307
  }
37276
37308
  function noteRestartBegun(note) {
37277
37309
  const path = restartNotePath(note.projectPath);
37278
37310
  try {
37279
- mkdirSync9(dirname12(path), { recursive: true });
37280
- writeFileSync11(path, `${JSON.stringify(note, null, 2)}
37311
+ mkdirSync10(dirname13(path), { recursive: true });
37312
+ writeFileSync12(path, `${JSON.stringify(note, null, 2)}
37281
37313
  `, "utf8");
37282
37314
  } catch (error) {
37283
37315
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -37285,7 +37317,7 @@ function noteRestartBegun(note) {
37285
37317
  }
37286
37318
  function restartOwed(projectPath) {
37287
37319
  try {
37288
- const parsed = JSON.parse(readFileSync16(restartNotePath(projectPath), "utf8"));
37320
+ const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
37289
37321
  if (typeof parsed !== "object" || parsed === null) {
37290
37322
  return null;
37291
37323
  }
@@ -37311,36 +37343,36 @@ var init_restart_note = __esm(() => {
37311
37343
 
37312
37344
  // src/run-record.ts
37313
37345
  import { execFileSync as execFileSync2 } from "node:child_process";
37314
- import { mkdirSync as mkdirSync10, openSync, readdirSync as readdirSync6, readFileSync as readFileSync17, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
37315
- import { basename as basename3, join as join19 } from "node:path";
37346
+ import { mkdirSync as mkdirSync11, openSync, readdirSync as readdirSync6, readFileSync as readFileSync18, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
37347
+ import { basename as basename3, join as join20 } from "node:path";
37316
37348
  function runsDirectory() {
37317
- return join19(runtimeDirectory(), "runs");
37349
+ return join20(runtimeDirectory(), "runs");
37318
37350
  }
37319
37351
  function recordPath() {
37320
- return join19(runsDirectory(), "run.json");
37352
+ return join20(runsDirectory(), "run.json");
37321
37353
  }
37322
37354
  function recordPaths() {
37323
- return runtimeDirectories().map((directory) => join19(directory, "runs", "run.json"));
37355
+ return runtimeDirectories().map((directory) => join20(directory, "runs", "run.json"));
37324
37356
  }
37325
37357
  function openTranscript(startedAt) {
37326
- mkdirSync10(runsDirectory(), { recursive: true });
37327
- const path = join19(runsDirectory(), `run-${startedAt}.log`);
37358
+ mkdirSync11(runsDirectory(), { recursive: true });
37359
+ const path = join20(runsDirectory(), `run-${startedAt}.log`);
37328
37360
  return { path, fd: openSync(path, "a") };
37329
37361
  }
37330
37362
  function writeRunRecord(record) {
37331
- mkdirSync10(runsDirectory(), { recursive: true });
37332
- writeFileSync12(recordPath(), JSON.stringify(record, null, 2), "utf8");
37363
+ mkdirSync11(runsDirectory(), { recursive: true });
37364
+ writeFileSync13(recordPath(), JSON.stringify(record, null, 2), "utf8");
37333
37365
  }
37334
37366
  function editorNotePath() {
37335
- return join19(runsDirectory(), "editor-run.json");
37367
+ return join20(runsDirectory(), "editor-run.json");
37336
37368
  }
37337
37369
  function writeEditorRunNote(note) {
37338
- mkdirSync10(runsDirectory(), { recursive: true });
37339
- writeFileSync12(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
37370
+ mkdirSync11(runsDirectory(), { recursive: true });
37371
+ writeFileSync13(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
37340
37372
  }
37341
37373
  function readEditorRunNote() {
37342
37374
  for (const directory of runtimeDirectories()) {
37343
- const note = editorNoteAt(join19(directory, "runs", "editor-run.json"));
37375
+ const note = editorNoteAt(join20(directory, "runs", "editor-run.json"));
37344
37376
  if (note !== null) {
37345
37377
  return note;
37346
37378
  }
@@ -37350,7 +37382,7 @@ function readEditorRunNote() {
37350
37382
  function editorNoteAt(path) {
37351
37383
  let parsed;
37352
37384
  try {
37353
- parsed = JSON.parse(readFileSync17(path, "utf8"));
37385
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
37354
37386
  } catch {
37355
37387
  return null;
37356
37388
  }
@@ -37373,7 +37405,7 @@ function recordRunEnded(pid, exitCode) {
37373
37405
  return;
37374
37406
  }
37375
37407
  try {
37376
- writeFileSync12(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
37408
+ writeFileSync13(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
37377
37409
  } catch {}
37378
37410
  }
37379
37411
  function readRunRecord() {
@@ -37388,7 +37420,7 @@ function readRunRecord() {
37388
37420
  function recordAt(path) {
37389
37421
  let parsed;
37390
37422
  try {
37391
- parsed = JSON.parse(readFileSync17(path, "utf8"));
37423
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
37392
37424
  } catch {
37393
37425
  return null;
37394
37426
  }
@@ -37433,7 +37465,7 @@ function runningAs(pid) {
37433
37465
  const began = startedAt(pid, null);
37434
37466
  const when = began === null ? {} : { startedAt: began };
37435
37467
  if (process.platform === "linux") {
37436
- const raw = readFileSync17(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
37468
+ const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
37437
37469
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
37438
37470
  }
37439
37471
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -37486,7 +37518,7 @@ function windowsCommandLine(pid) {
37486
37518
  "-NoProfile",
37487
37519
  "-NonInteractive",
37488
37520
  "-Command",
37489
- `$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
37521
+ `${POWERSHELL_UTF8}$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
37490
37522
  ], { encoding: "utf8", timeout: 15000, windowsHide: true }).trim();
37491
37523
  return answer === "" ? null : answer;
37492
37524
  } catch {
@@ -37584,7 +37616,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
37584
37616
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
37585
37617
  continue;
37586
37618
  }
37587
- const path = join19(directory, entry);
37619
+ const path = join20(directory, entry);
37588
37620
  if (path === keep || ageMs(path, now) < keepMs) {
37589
37621
  continue;
37590
37622
  }
@@ -37595,6 +37627,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
37595
37627
  }
37596
37628
  var AN_EDITOR, SAME_RUN_WINDOW_MS = 1e4;
37597
37629
  var init_run_record = __esm(() => {
37630
+ init_process_children();
37598
37631
  init_runtime_client();
37599
37632
  AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
37600
37633
  });
@@ -37607,16 +37640,17 @@ import {
37607
37640
  closeSync,
37608
37641
  existsSync as existsSync14,
37609
37642
  fstatSync,
37610
- mkdirSync as mkdirSync11,
37643
+ mkdirSync as mkdirSync12,
37611
37644
  mkdtempSync as mkdtempSync3,
37612
37645
  openSync as openSync2,
37613
37646
  readdirSync as readdirSync7,
37614
- readFileSync as readFileSync18,
37647
+ readFileSync as readFileSync19,
37615
37648
  readSync,
37649
+ rmdirSync,
37616
37650
  statSync as statSync5
37617
37651
  } from "node:fs";
37618
37652
  import { tmpdir as tmpdir5 } from "node:os";
37619
- import { basename as basename4, dirname as dirname13, join as join20, normalize as normalize3, relative as relative3, resolve as resolve5 } from "node:path";
37653
+ import { basename as basename4, dirname as dirname14, join as join21, normalize as normalize3, relative as relative3 } from "node:path";
37620
37654
  import { setTimeout as delay3 } from "node:timers/promises";
37621
37655
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37622
37656
  import { promisify as promisify7 } from "node:util";
@@ -37858,7 +37892,7 @@ function aboveTheRunner(entry) {
37858
37892
  }
37859
37893
  function elsewhereIn(projectPath, asked) {
37860
37894
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
37861
- if (wanted === "" || existsSync14(join20(projectPath, wanted))) {
37895
+ if (wanted === "" || existsSync14(join21(projectPath, wanted))) {
37862
37896
  return "";
37863
37897
  }
37864
37898
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -37872,7 +37906,7 @@ function elsewhereIn(projectPath, asked) {
37872
37906
  return near.length === 0 ? "" : ` The project does have ${there}.`;
37873
37907
  }
37874
37908
  function hasMainScene(projectFile) {
37875
- const scene = parseProjectGodot(readFileSync18(projectFile, "utf8"))["application"]?.["run/main_scene"];
37909
+ const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
37876
37910
  return typeof scene === "string" && scene !== "";
37877
37911
  }
37878
37912
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -37880,13 +37914,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
37880
37914
  return false;
37881
37915
  }
37882
37916
  try {
37883
- return readFileSync18(join20(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
37917
+ return readFileSync19(join21(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
37884
37918
  } catch {
37885
37919
  return false;
37886
37920
  }
37887
37921
  }
37888
37922
  function editorPlaysHeadless(projectFile) {
37889
- const runArgs = parseProjectGodot(readFileSync18(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37923
+ const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37890
37924
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
37891
37925
  }
37892
37926
  function camelCased(params) {
@@ -37902,7 +37936,7 @@ function camelCased(params) {
37902
37936
  class GodotServer {
37903
37937
  mcp;
37904
37938
  locator = new GodotLocator2;
37905
- operationsScript = join20(__dirname2, "godot", "operations", "godot_operations.gd");
37939
+ operationsScript = join21(__dirname2, "godot", "operations", "godot_operations.gd");
37906
37940
  godotBridge;
37907
37941
  tools = buildToolDefinitions();
37908
37942
  activeProcess = null;
@@ -37920,8 +37954,6 @@ class GodotServer {
37920
37954
  successorWatch = null;
37921
37955
  lastProjectPath = null;
37922
37956
  shutdownInitiated = false;
37923
- shortListEditors = new Map;
37924
- cacheLastRebuilt = new Map;
37925
37957
  launchedEditor = null;
37926
37958
  launchedFrom = null;
37927
37959
  ownProject;
@@ -38569,7 +38601,7 @@ class GodotServer {
38569
38601
  if (path === undefined) {
38570
38602
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
38571
38603
  }
38572
- const file = join20(path, "project.godot");
38604
+ const file = join21(path, "project.godot");
38573
38605
  if (!existsSync14(file)) {
38574
38606
  return {
38575
38607
  ok: false,
@@ -38793,7 +38825,7 @@ class GodotServer {
38793
38825
  if (!engine.ok) {
38794
38826
  return engine.response;
38795
38827
  }
38796
- const application = parseProjectGodot(readFileSync18(project.value.file, "utf8"))["application"] ?? {};
38828
+ const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
38797
38829
  const name = application["config/name"];
38798
38830
  const mainScene = application["run/main_scene"];
38799
38831
  const info = {
@@ -38854,18 +38886,18 @@ class GodotServer {
38854
38886
  if (!engine.ok) {
38855
38887
  return engine.response;
38856
38888
  }
38857
- const folder = dirname13(output.absolutePath);
38889
+ const folder = dirname14(output.absolutePath);
38858
38890
  try {
38859
- mkdirSync11(folder, { recursive: true });
38891
+ mkdirSync12(folder, { recursive: true });
38860
38892
  } catch (error) {
38861
- return this.createErrorResponse(`Cannot create ${dirname13(output.relativePath)} to export into: ${errorMessage(error)}`);
38893
+ return this.createErrorResponse(`Cannot create ${dirname14(output.relativePath)} to export into: ${errorMessage(error)}`);
38862
38894
  }
38863
38895
  const debug = readBoolean(args, "debug") ?? false;
38864
- const exportLogs = mkdtempSync3(join20(tmpdir5(), "gdharness-export-"));
38896
+ const exportLogs = mkdtempSync3(join21(tmpdir5(), "gdharness-export-"));
38865
38897
  const exportArgs = [
38866
38898
  "--headless",
38867
38899
  "--log-file",
38868
- join20(exportLogs, "engine.log"),
38900
+ join21(exportLogs, "engine.log"),
38869
38901
  "--path",
38870
38902
  project.value.path,
38871
38903
  debug ? "--export-debug" : "--export-release",
@@ -38923,7 +38955,7 @@ class GodotServer {
38923
38955
  return contained.response;
38924
38956
  }
38925
38957
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
38926
- if (!existsSync14(join20(project.value.path, runner))) {
38958
+ if (!existsSync14(join21(project.value.path, runner))) {
38927
38959
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
38928
38960
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
38929
38961
  ]);
@@ -38958,7 +38990,7 @@ class GodotServer {
38958
38990
  ];
38959
38991
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
38960
38992
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
38961
- const userData = mkdtempSync3(join20(tmpdir5(), "gdharness-tests-"));
38993
+ const userData = mkdtempSync3(join21(tmpdir5(), "gdharness-tests-"));
38962
38994
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
38963
38995
  const hung = await new Promise((resolve) => {
38964
38996
  const timer = setTimeout(() => {
@@ -38974,19 +39006,22 @@ class GodotServer {
38974
39006
  resolve(false);
38975
39007
  });
38976
39008
  });
38977
- const reportsDir = join20(project.value.path, ".godot", ...ours.split("/"));
39009
+ const reportsDir = join21(project.value.path, ".godot", ...ours.split("/"));
38978
39010
  let report = null;
38979
39011
  let reportProblem = null;
38980
39012
  try {
38981
39013
  const written = existsSync14(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
38982
39014
  const newest = written.at(-1);
38983
39015
  if (newest !== undefined) {
38984
- report = parseJUnit(readFileSync18(join20(reportsDir, newest, "results.xml"), "utf8"));
39016
+ report = parseJUnit(readFileSync19(join21(reportsDir, newest, "results.xml"), "utf8"));
38985
39017
  }
38986
39018
  } catch (error) {
38987
39019
  reportProblem = errorMessage(error);
38988
39020
  } finally {
38989
39021
  discard(reportsDir, userData);
39022
+ try {
39023
+ rmdirSync(dirname14(reportsDir));
39024
+ } catch {}
38990
39025
  }
38991
39026
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
38992
39027
  const verdicts = {
@@ -39121,7 +39156,7 @@ class GodotServer {
39121
39156
  return null;
39122
39157
  }
39123
39158
  try {
39124
- return readFileSync18(contained.absolutePath, "utf8");
39159
+ return readFileSync19(contained.absolutePath, "utf8");
39125
39160
  } catch {
39126
39161
  return null;
39127
39162
  }
@@ -39159,8 +39194,15 @@ class GodotServer {
39159
39194
  };
39160
39195
  }
39161
39196
  editorWritesAShortList(projectPath) {
39162
- const key = resolve5(projectPath);
39163
- return this.shortListEditors.has(key) && this.shortListEditors.get(key) === this.godotBridge.getStatus().editorPid;
39197
+ const recorded = readClassNote(projectPath)?.shortListEditorPid;
39198
+ return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
39199
+ }
39200
+ noteAShortListEditor(projectPath) {
39201
+ const note = readClassNote(projectPath);
39202
+ writeClassNote(projectPath, {
39203
+ rebuiltWith: note?.rebuiltWith ?? [],
39204
+ shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
39205
+ });
39164
39206
  }
39165
39207
  sourceOfClass(projectPath, resourcePath) {
39166
39208
  const contained = resolveWithinProject(projectPath, resourcePath);
@@ -39168,7 +39210,7 @@ class GodotServer {
39168
39210
  return null;
39169
39211
  }
39170
39212
  try {
39171
- return readFileSync18(contained.absolutePath, "utf8");
39213
+ return readFileSync19(contained.absolutePath, "utf8");
39172
39214
  } catch {
39173
39215
  return null;
39174
39216
  }
@@ -39397,7 +39439,7 @@ class GodotServer {
39397
39439
  return null;
39398
39440
  }
39399
39441
  try {
39400
- return settingKeys(readFileSync18(join20(projectPath, "project.godot"), "utf8"));
39442
+ return settingKeys(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
39401
39443
  } catch {
39402
39444
  return null;
39403
39445
  }
@@ -39639,7 +39681,7 @@ class GodotServer {
39639
39681
  }
39640
39682
  async runtimeUp(projectPath, before, budget, withArgs) {
39641
39683
  const budgetMs = budget.ms;
39642
- if (!existsSync14(join20(projectPath, RUNTIME_AUTOLOAD2.path))) {
39684
+ if (!existsSync14(join21(projectPath, RUNTIME_AUTOLOAD2.path))) {
39643
39685
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
39644
39686
  }
39645
39687
  let asking = null;
@@ -40512,7 +40554,7 @@ class GodotServer {
40512
40554
  const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
40513
40555
  const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
40514
40556
  if (lost.length > 0) {
40515
- this.shortListEditors.set(resolve5(projectPath), this.godotBridge.getStatus().editorPid);
40557
+ this.noteAShortListEditor(projectPath);
40516
40558
  }
40517
40559
  const atMissingPaths = after === null ? [] : cachedAtMissingPaths(after, projectPath);
40518
40560
  const restored = lost.length > 0 || atMissingPaths.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
@@ -40578,9 +40620,8 @@ class GodotServer {
40578
40620
  return now === null ? [] : lost.filter((name) => now.has(name));
40579
40621
  }
40580
40622
  async rebuildClassCache(projectPath) {
40581
- const key = resolve5(projectPath);
40582
40623
  const before = cachedClasses(projectPath);
40583
- const remembered = this.cacheLastRebuilt.get(key);
40624
+ const noted = readClassNote(projectPath);
40584
40625
  const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
40585
40626
  if (!rebuilt.ok) {
40586
40627
  return rebuilt;
@@ -40589,12 +40630,15 @@ class GodotServer {
40589
40630
  if (after === null) {
40590
40631
  return rebuilt;
40591
40632
  }
40592
- this.cacheLastRebuilt.set(key, new Set(after.keys()));
40593
- const lost = remembered === undefined || before === null ? [] : [...remembered].filter((name) => !before.has(name) && after.has(name)).sort();
40594
- if (lost.length === 0 || !this.godotBridge.isConnected()) {
40633
+ const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
40634
+ const seenLosing = lost.length > 0 && this.godotBridge.isConnected();
40635
+ writeClassNote(projectPath, {
40636
+ rebuiltWith: [...after.keys()].sort(),
40637
+ ...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
40638
+ });
40639
+ if (!seenLosing) {
40595
40640
  return rebuilt;
40596
40641
  }
40597
- this.shortListEditors.set(key, this.godotBridge.getStatus().editorPid);
40598
40642
  const them = lost.length === 1 ? "it" : "them";
40599
40643
  return {
40600
40644
  ...rebuilt,
@@ -40751,8 +40795,8 @@ class GodotServer {
40751
40795
  ]);
40752
40796
  }
40753
40797
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
40754
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join20(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
40755
- const screenshotPath = screenshotDir ? join20(screenshotDir, "capture.png") : null;
40798
+ const screenshotDir = expectsScreenshot ? mkdtempSync3(join21(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
40799
+ const screenshotPath = screenshotDir ? join21(screenshotDir, "capture.png") : null;
40756
40800
  try {
40757
40801
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
40758
40802
  if (!reply.ok) {
@@ -40776,7 +40820,7 @@ class GodotServer {
40776
40820
  type: "text",
40777
40821
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
40778
40822
  },
40779
- { type: "image", data: readFileSync18(screenshotPath).toString("base64"), mimeType: "image/png" }
40823
+ { type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
40780
40824
  ]
40781
40825
  };
40782
40826
  } finally {
@@ -40852,6 +40896,7 @@ var init_server3 = __esm(() => {
40852
40896
  init_breakpoint_note();
40853
40897
  init_bridge_announce();
40854
40898
  init_class_cache();
40899
+ init_class_note();
40855
40900
  init_config_pin();
40856
40901
  init_dap_client();
40857
40902
  init_errors();
@@ -40878,7 +40923,7 @@ var init_server3 = __esm(() => {
40878
40923
  init_tool_definitions();
40879
40924
  init_update_check();
40880
40925
  run7 = promisify7(execFile7);
40881
- __dirname2 = dirname13(fileURLToPath4(import.meta.url));
40926
+ __dirname2 = dirname14(fileURLToPath4(import.meta.url));
40882
40927
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
40883
40928
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
40884
40929
  VALUE_NAMES = {
@@ -40919,7 +40964,7 @@ var init_server3 = __esm(() => {
40919
40964
 
40920
40965
  // src/cli.ts
40921
40966
  import { existsSync as existsSync15 } from "fs";
40922
- import { join as join21, resolve as resolve6 } from "path";
40967
+ import { join as join22, resolve as resolve5 } from "path";
40923
40968
 
40924
40969
  // src/errors.ts
40925
40970
  class Refusal extends Error {
@@ -42623,8 +42668,8 @@ class UsageError extends Refusal {
42623
42668
  }
42624
42669
  function projectArgument(at) {
42625
42670
  const named = args.slice(at).find((value) => !value.startsWith("--"));
42626
- const projectPath = resolve6(named ?? ".");
42627
- if (!existsSync15(join21(projectPath, "project.godot"))) {
42671
+ const projectPath = resolve5(named ?? ".");
42672
+ if (!existsSync15(join22(projectPath, "project.godot"))) {
42628
42673
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
42629
42674
  }
42630
42675
  return projectPath;
@@ -42663,7 +42708,7 @@ async function registerRuntime(godot, projectPath) {
42663
42708
  return;
42664
42709
  }
42665
42710
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
42666
- if (!existsSync15(join21(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
42711
+ if (!existsSync15(join22(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
42667
42712
  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}`);
42668
42713
  }
42669
42714
  }
package/build/index.js CHANGED
@@ -10175,16 +10175,17 @@ import {
10175
10175
  closeSync,
10176
10176
  existsSync as existsSync9,
10177
10177
  fstatSync,
10178
- mkdirSync as mkdirSync8,
10178
+ mkdirSync as mkdirSync9,
10179
10179
  mkdtempSync as mkdtempSync2,
10180
10180
  openSync as openSync2,
10181
10181
  readdirSync as readdirSync6,
10182
- readFileSync as readFileSync14,
10182
+ readFileSync as readFileSync15,
10183
10183
  readSync,
10184
+ rmdirSync,
10184
10185
  statSync as statSync5
10185
10186
  } from "node:fs";
10186
10187
  import { tmpdir as tmpdir4 } from "node:os";
10187
- import { basename as basename3, dirname as dirname8, join as join14, normalize as normalize2, relative as relative3, resolve as resolve5 } from "node:path";
10188
+ import { basename as basename3, dirname as dirname9, join as join15, normalize as normalize2, relative as relative3 } from "node:path";
10188
10189
  import { setTimeout as delay3 } from "node:timers/promises";
10189
10190
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10190
10191
  import { promisify as promisify5 } from "node:util";
@@ -24817,6 +24818,7 @@ import { execFile, execFileSync, spawnSync } from "node:child_process";
24817
24818
  import { basename } from "node:path";
24818
24819
  import { promisify } from "node:util";
24819
24820
  var run = promisify(execFile);
24821
+ var POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ";
24820
24822
  function startTimesOf(pids) {
24821
24823
  const began = new Map;
24822
24824
  if (pids.length === 0) {
@@ -24999,7 +25001,7 @@ async function askWindows() {
24999
25001
  "-NoProfile",
25000
25002
  "-NonInteractive",
25001
25003
  "-Command",
25002
- 'Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }'
25004
+ `${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
25003
25005
  ], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
25004
25006
  return stdout;
25005
25007
  }
@@ -25814,18 +25816,49 @@ function unseenByEditor(projectPath, editorHolds) {
25814
25816
  return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path })).sort((one, other) => one.className < other.className ? -1 : one.className > other.className ? 1 : 0);
25815
25817
  }
25816
25818
 
25819
+ // src/class-note.ts
25820
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
25821
+ import { dirname as dirname5, join as join7 } from "node:path";
25822
+ function classNotePath(projectPath) {
25823
+ return join7(projectPath, ".godot", "gdharness-classes.json");
25824
+ }
25825
+ function readClassNote(projectPath) {
25826
+ try {
25827
+ const fields = asParams(JSON.parse(readFileSync8(classNotePath(projectPath), "utf8")));
25828
+ const rebuiltWith = readArray(fields, "rebuiltWith");
25829
+ if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
25830
+ return null;
25831
+ }
25832
+ const pid = readNumber(fields, "shortListEditorPid");
25833
+ return {
25834
+ rebuiltWith: rebuiltWith.map(String),
25835
+ ...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
25836
+ };
25837
+ } catch {
25838
+ return null;
25839
+ }
25840
+ }
25841
+ function writeClassNote(projectPath, note) {
25842
+ try {
25843
+ const path = classNotePath(projectPath);
25844
+ mkdirSync5(dirname5(path), { recursive: true });
25845
+ writeFileSync5(path, `${JSON.stringify(note, null, 2)}
25846
+ `, "utf8");
25847
+ } catch {}
25848
+ }
25849
+
25817
25850
  // src/config-pin.ts
25818
- import { existsSync as existsSync4, readFileSync as readFileSync8 } from "node:fs";
25819
- import { join as join7 } from "node:path";
25851
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
25852
+ import { join as join8 } from "node:path";
25820
25853
  var PINNED = /\bgdharness@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/g;
25821
25854
  function versionsNamedInConfig(projectPath) {
25822
- const config = join7(projectPath, ".mcp.json");
25855
+ const config = join8(projectPath, ".mcp.json");
25823
25856
  if (!existsSync4(config)) {
25824
25857
  return [];
25825
25858
  }
25826
25859
  let text;
25827
25860
  try {
25828
- text = readFileSync8(config, "utf8");
25861
+ text = readFileSync9(config, "utf8");
25829
25862
  JSON.parse(text);
25830
25863
  } catch {
25831
25864
  return [];
@@ -27192,7 +27225,7 @@ import { promisify as promisify2 } from "node:util";
27192
27225
  // src/detection.ts
27193
27226
  import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
27194
27227
  import { homedir as homedir2 } from "node:os";
27195
- import { join as join8 } from "node:path";
27228
+ import { join as join9 } from "node:path";
27196
27229
  function resolveHomeDirectory() {
27197
27230
  try {
27198
27231
  return homedir2();
@@ -27216,7 +27249,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
27216
27249
  if (!pattern.test(name)) {
27217
27250
  continue;
27218
27251
  }
27219
- const fullPath = join8(directory, name);
27252
+ const fullPath = join9(directory, name);
27220
27253
  try {
27221
27254
  const stat = statSync3(fullPath);
27222
27255
  if (stat.isFile()) {
@@ -27225,7 +27258,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
27225
27258
  } catch {}
27226
27259
  }
27227
27260
  matches.sort((a, b) => b.mtime - a.mtime);
27228
- return matches.map((m) => join8(directory, m.name));
27261
+ return matches.map((m) => join9(directory, m.name));
27229
27262
  }
27230
27263
  function conventionalPaths(platform, home) {
27231
27264
  const paths = ["godot"];
@@ -27345,9 +27378,9 @@ class GodotLocator {
27345
27378
 
27346
27379
  // src/headless.ts
27347
27380
  import { execFile as execFile3 } from "node:child_process";
27348
- import { mkdtempSync, writeFileSync as writeFileSync5 } from "node:fs";
27381
+ import { mkdtempSync, writeFileSync as writeFileSync6 } from "node:fs";
27349
27382
  import { tmpdir as tmpdir3 } from "node:os";
27350
- import { join as join9 } from "node:path";
27383
+ import { join as join10 } from "node:path";
27351
27384
  import { promisify as promisify3 } from "node:util";
27352
27385
 
27353
27386
  // src/scratch.ts
@@ -27413,12 +27446,12 @@ function reason(stdout, stderr) {
27413
27446
  return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
27414
27447
  }
27415
27448
  async function runImport(godotPath, projectPath) {
27416
- const logDir = mkdtempSync(join9(tmpdir3(), "gdharness-import-"));
27449
+ const logDir = mkdtempSync(join10(tmpdir3(), "gdharness-import-"));
27417
27450
  try {
27418
27451
  const { stderr } = await run3(godotPath, [
27419
27452
  "--headless",
27420
27453
  "--log-file",
27421
- join9(logDir, "engine.log"),
27454
+ join10(logDir, "engine.log"),
27422
27455
  "--path",
27423
27456
  projectPath,
27424
27457
  "--import"
@@ -27443,13 +27476,13 @@ async function runImport(godotPath, projectPath) {
27443
27476
  }
27444
27477
  }
27445
27478
  async function runOperation(engine, operation, params, projectPath) {
27446
- const paramsDir = mkdtempSync(join9(tmpdir3(), "gdharness-params-"));
27447
- const paramsFile = join9(paramsDir, `${operation}.json`);
27448
- writeFileSync5(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
27479
+ const paramsDir = mkdtempSync(join10(tmpdir3(), "gdharness-params-"));
27480
+ const paramsFile = join10(paramsDir, `${operation}.json`);
27481
+ writeFileSync6(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
27449
27482
  const args = [
27450
27483
  "--headless",
27451
27484
  "--log-file",
27452
- join9(paramsDir, "engine.log"),
27485
+ join10(paramsDir, "engine.log"),
27453
27486
  "--path",
27454
27487
  projectPath,
27455
27488
  "--script",
@@ -27878,7 +27911,7 @@ function orphansPrinted(printed) {
27878
27911
  import { realpathSync as realpathSync2 } from "node:fs";
27879
27912
  import { readFile, realpath } from "node:fs/promises";
27880
27913
  import { createConnection as createConnection3 } from "node:net";
27881
- import { dirname as dirname5, resolve as resolve3 } from "node:path";
27914
+ import { dirname as dirname6, resolve as resolve3 } from "node:path";
27882
27915
  import { fileURLToPath, pathToFileURL } from "node:url";
27883
27916
  var DEFAULT_LSP_PORT = 6005;
27884
27917
  var DIAGNOSTICS_TIMEOUT_MS = 5000;
@@ -28008,7 +28041,7 @@ class GodotLSPClient {
28008
28041
  if (this.initialized) {
28009
28042
  return;
28010
28043
  }
28011
- const rootPath = this.rootPath ?? dirname5(resolve3(filePath));
28044
+ const rootPath = this.rootPath ?? dirname6(resolve3(filePath));
28012
28045
  await this.initialize(rootPath);
28013
28046
  }
28014
28047
  async sendRequest(method, params) {
@@ -28444,14 +28477,14 @@ function secondsFromClock(printed) {
28444
28477
  }
28445
28478
 
28446
28479
  // src/project-scan.ts
28447
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
28448
- import { join as join10 } from "node:path";
28480
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
28481
+ import { join as join11 } from "node:path";
28449
28482
  var SKIPPED = new Set(["node_modules"]);
28450
28483
  function skipped(name) {
28451
28484
  return name.startsWith(".") || SKIPPED.has(name);
28452
28485
  }
28453
28486
  function steppedOver(directory) {
28454
- return existsSync7(join10(directory, ".gdignore"));
28487
+ return existsSync7(join11(directory, ".gdignore"));
28455
28488
  }
28456
28489
  var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
28457
28490
  function projectStructure(projectPath) {
@@ -28465,7 +28498,7 @@ function projectStructure(projectPath) {
28465
28498
  continue;
28466
28499
  }
28467
28500
  if (entry.isDirectory()) {
28468
- visit(join10(directory, entry.name));
28501
+ visit(join11(directory, entry.name));
28469
28502
  } else if (entry.isFile()) {
28470
28503
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
28471
28504
  if (extension === "tscn") {
@@ -28496,14 +28529,14 @@ function scriptsWithoutUid(projectPath) {
28496
28529
  }
28497
28530
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
28498
28531
  if (entry.isDirectory()) {
28499
- visit(join10(directory, entry.name), spelled);
28532
+ visit(join11(directory, entry.name), spelled);
28500
28533
  continue;
28501
28534
  }
28502
28535
  if (!entry.isFile()) {
28503
28536
  continue;
28504
28537
  }
28505
28538
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
28506
- if (CARRIES_A_UID.has(extension) && !existsSync7(join10(directory, `${entry.name}.uid`))) {
28539
+ if (CARRIES_A_UID.has(extension) && !existsSync7(join11(directory, `${entry.name}.uid`))) {
28507
28540
  found.push(spelled);
28508
28541
  }
28509
28542
  }
@@ -28538,7 +28571,7 @@ function searchProject(projectPath, options) {
28538
28571
  if (skipped(entry.name)) {
28539
28572
  continue;
28540
28573
  }
28541
- const entryPath = join10(directory, entry.name);
28574
+ const entryPath = join11(directory, entry.name);
28542
28575
  if (entry.isDirectory()) {
28543
28576
  visit(entryPath);
28544
28577
  continue;
@@ -28549,7 +28582,7 @@ function searchProject(projectPath, options) {
28549
28582
  }
28550
28583
  result.summary.files_searched += 1;
28551
28584
  const matches = [];
28552
- for (const [index, line] of readFileSync9(entryPath, "utf8").split(`
28585
+ for (const [index, line] of readFileSync10(entryPath, "utf8").split(`
28553
28586
  `).entries()) {
28554
28587
  if (full()) {
28555
28588
  break;
@@ -28572,7 +28605,7 @@ function searchProject(projectPath, options) {
28572
28605
  }
28573
28606
 
28574
28607
  // src/resources.ts
28575
- import { readFileSync as readFileSync10 } from "node:fs";
28608
+ import { readFileSync as readFileSync11 } from "node:fs";
28576
28609
  import { extname, resolve as resolve4 } from "node:path";
28577
28610
  var STATIC_RESOURCES = [
28578
28611
  {
@@ -28767,7 +28800,7 @@ function readResourceText(uri, getProjectPath) {
28767
28800
  const parsedUri = parseGodotUri(uri);
28768
28801
  if (parsedUri.kind === "project-info") {
28769
28802
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
28770
- const rawProject = readFileSync10(projectFilePath, "utf-8");
28803
+ const rawProject = readFileSync11(projectFilePath, "utf-8");
28771
28804
  const parsedProject = parseProjectGodot(rawProject);
28772
28805
  return {
28773
28806
  mimeType: "application/json",
@@ -28776,7 +28809,7 @@ function readResourceText(uri, getProjectPath) {
28776
28809
  }
28777
28810
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
28778
28811
  ensureAllowedExtension(parsedUri.kind, filePath);
28779
- const text = readFileSync10(filePath, "utf-8");
28812
+ const text = readFileSync11(filePath, "utf-8");
28780
28813
  return {
28781
28814
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
28782
28815
  text
@@ -28813,16 +28846,16 @@ function setupResourceHandlers(mcp, getProjectPath) {
28813
28846
  }
28814
28847
 
28815
28848
  // src/restart-note.ts
28816
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
28817
- import { dirname as dirname6, join as join11 } from "node:path";
28849
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
28850
+ import { dirname as dirname7, join as join12 } from "node:path";
28818
28851
  function restartNotePath(projectPath) {
28819
- return join11(projectPath, ".godot", "gdharness-restart.json");
28852
+ return join12(projectPath, ".godot", "gdharness-restart.json");
28820
28853
  }
28821
28854
  function noteRestartBegun(note) {
28822
28855
  const path = restartNotePath(note.projectPath);
28823
28856
  try {
28824
- mkdirSync5(dirname6(path), { recursive: true });
28825
- writeFileSync6(path, `${JSON.stringify(note, null, 2)}
28857
+ mkdirSync6(dirname7(path), { recursive: true });
28858
+ writeFileSync7(path, `${JSON.stringify(note, null, 2)}
28826
28859
  `, "utf8");
28827
28860
  } catch (error) {
28828
28861
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -28830,7 +28863,7 @@ function noteRestartBegun(note) {
28830
28863
  }
28831
28864
  function restartOwed(projectPath) {
28832
28865
  try {
28833
- const parsed = JSON.parse(readFileSync11(restartNotePath(projectPath), "utf8"));
28866
+ const parsed = JSON.parse(readFileSync12(restartNotePath(projectPath), "utf8"));
28834
28867
  if (typeof parsed !== "object" || parsed === null) {
28835
28868
  return null;
28836
28869
  }
@@ -28853,36 +28886,36 @@ function restartSettled(projectPath) {
28853
28886
 
28854
28887
  // src/run-record.ts
28855
28888
  import { execFileSync as execFileSync2 } from "node:child_process";
28856
- import { mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "node:fs";
28857
- import { basename as basename2, join as join12 } from "node:path";
28889
+ import { mkdirSync as mkdirSync7, openSync, readdirSync as readdirSync5, readFileSync as readFileSync13, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync8 } from "node:fs";
28890
+ import { basename as basename2, join as join13 } from "node:path";
28858
28891
  function runsDirectory() {
28859
- return join12(runtimeDirectory(), "runs");
28892
+ return join13(runtimeDirectory(), "runs");
28860
28893
  }
28861
28894
  function recordPath() {
28862
- return join12(runsDirectory(), "run.json");
28895
+ return join13(runsDirectory(), "run.json");
28863
28896
  }
28864
28897
  function recordPaths() {
28865
- return runtimeDirectories().map((directory) => join12(directory, "runs", "run.json"));
28898
+ return runtimeDirectories().map((directory) => join13(directory, "runs", "run.json"));
28866
28899
  }
28867
28900
  function openTranscript(startedAt) {
28868
- mkdirSync6(runsDirectory(), { recursive: true });
28869
- const path = join12(runsDirectory(), `run-${startedAt}.log`);
28901
+ mkdirSync7(runsDirectory(), { recursive: true });
28902
+ const path = join13(runsDirectory(), `run-${startedAt}.log`);
28870
28903
  return { path, fd: openSync(path, "a") };
28871
28904
  }
28872
28905
  function writeRunRecord(record) {
28873
- mkdirSync6(runsDirectory(), { recursive: true });
28874
- writeFileSync7(recordPath(), JSON.stringify(record, null, 2), "utf8");
28906
+ mkdirSync7(runsDirectory(), { recursive: true });
28907
+ writeFileSync8(recordPath(), JSON.stringify(record, null, 2), "utf8");
28875
28908
  }
28876
28909
  function editorNotePath() {
28877
- return join12(runsDirectory(), "editor-run.json");
28910
+ return join13(runsDirectory(), "editor-run.json");
28878
28911
  }
28879
28912
  function writeEditorRunNote(note) {
28880
- mkdirSync6(runsDirectory(), { recursive: true });
28881
- writeFileSync7(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
28913
+ mkdirSync7(runsDirectory(), { recursive: true });
28914
+ writeFileSync8(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
28882
28915
  }
28883
28916
  function readEditorRunNote() {
28884
28917
  for (const directory of runtimeDirectories()) {
28885
- const note = editorNoteAt(join12(directory, "runs", "editor-run.json"));
28918
+ const note = editorNoteAt(join13(directory, "runs", "editor-run.json"));
28886
28919
  if (note !== null) {
28887
28920
  return note;
28888
28921
  }
@@ -28892,7 +28925,7 @@ function readEditorRunNote() {
28892
28925
  function editorNoteAt(path) {
28893
28926
  let parsed;
28894
28927
  try {
28895
- parsed = JSON.parse(readFileSync12(path, "utf8"));
28928
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
28896
28929
  } catch {
28897
28930
  return null;
28898
28931
  }
@@ -28915,7 +28948,7 @@ function recordRunEnded(pid, exitCode) {
28915
28948
  return;
28916
28949
  }
28917
28950
  try {
28918
- writeFileSync7(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
28951
+ writeFileSync8(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
28919
28952
  } catch {}
28920
28953
  }
28921
28954
  function readRunRecord() {
@@ -28930,7 +28963,7 @@ function readRunRecord() {
28930
28963
  function recordAt(path) {
28931
28964
  let parsed;
28932
28965
  try {
28933
- parsed = JSON.parse(readFileSync12(path, "utf8"));
28966
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
28934
28967
  } catch {
28935
28968
  return null;
28936
28969
  }
@@ -28975,7 +29008,7 @@ function runningAs(pid) {
28975
29008
  const began = startedAt(pid, null);
28976
29009
  const when = began === null ? {} : { startedAt: began };
28977
29010
  if (process.platform === "linux") {
28978
- const raw = readFileSync12(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
29011
+ const raw = readFileSync13(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
28979
29012
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
28980
29013
  }
28981
29014
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -29028,7 +29061,7 @@ function windowsCommandLine(pid) {
29028
29061
  "-NoProfile",
29029
29062
  "-NonInteractive",
29030
29063
  "-Command",
29031
- `$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
29064
+ `${POWERSHELL_UTF8}$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
29032
29065
  ], { encoding: "utf8", timeout: 15000, windowsHide: true }).trim();
29033
29066
  return answer === "" ? null : answer;
29034
29067
  } catch {
@@ -29128,7 +29161,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
29128
29161
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
29129
29162
  continue;
29130
29163
  }
29131
- const path = join12(directory, entry);
29164
+ const path = join13(directory, entry);
29132
29165
  if (path === keep || ageMs(path, now) < keepMs) {
29133
29166
  continue;
29134
29167
  }
@@ -29139,8 +29172,8 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
29139
29172
  }
29140
29173
 
29141
29174
  // src/setup.ts
29142
- import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
29143
- import { dirname as dirname7, join as join13 } from "node:path";
29175
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
29176
+ import { dirname as dirname8, join as join14 } from "node:path";
29144
29177
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
29145
29178
  var RUNTIME_AUTOLOAD = {
29146
29179
  name: "GdharnessRuntime",
@@ -29148,11 +29181,11 @@ var RUNTIME_AUTOLOAD = {
29148
29181
  };
29149
29182
  var VERSION_MARKER = ".gdharness-version";
29150
29183
  function installedAddonVersion(projectPath) {
29151
- const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29184
+ const marker = join14(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29152
29185
  if (!existsSync8(marker)) {
29153
29186
  return null;
29154
29187
  }
29155
- const said = readFileSync13(marker, "utf8").trim();
29188
+ const said = readFileSync14(marker, "utf8").trim();
29156
29189
  return said === "" ? null : said;
29157
29190
  }
29158
29191
 
@@ -30309,7 +30342,7 @@ function buildToolDefinitions() {
30309
30342
  var UPDATE_NOTICE_EVERY = 500;
30310
30343
  var FEEDBACK_NOTICE_EVERY = 250;
30311
30344
  var run5 = promisify5(execFile5);
30312
- var __dirname2 = dirname8(fileURLToPath2(import.meta.url));
30345
+ var __dirname2 = dirname9(fileURLToPath2(import.meta.url));
30313
30346
  var EDITOR_RESTART_TIMEOUT_MS = 90000;
30314
30347
  var HOLD_PING_MS = 2000;
30315
30348
  var SLOWEST_FRAME_RATE = 20;
@@ -30596,7 +30629,7 @@ function aboveTheRunner(entry) {
30596
30629
  }
30597
30630
  function elsewhereIn(projectPath, asked) {
30598
30631
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
30599
- if (wanted === "" || existsSync9(join14(projectPath, wanted))) {
30632
+ if (wanted === "" || existsSync9(join15(projectPath, wanted))) {
30600
30633
  return "";
30601
30634
  }
30602
30635
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -30610,7 +30643,7 @@ function elsewhereIn(projectPath, asked) {
30610
30643
  return near.length === 0 ? "" : ` The project does have ${there}.`;
30611
30644
  }
30612
30645
  function hasMainScene(projectFile) {
30613
- const scene = parseProjectGodot(readFileSync14(projectFile, "utf8"))["application"]?.["run/main_scene"];
30646
+ const scene = parseProjectGodot(readFileSync15(projectFile, "utf8"))["application"]?.["run/main_scene"];
30614
30647
  return typeof scene === "string" && scene !== "";
30615
30648
  }
30616
30649
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -30618,13 +30651,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
30618
30651
  return false;
30619
30652
  }
30620
30653
  try {
30621
- return readFileSync14(join14(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
30654
+ return readFileSync15(join15(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
30622
30655
  } catch {
30623
30656
  return false;
30624
30657
  }
30625
30658
  }
30626
30659
  function editorPlaysHeadless(projectFile) {
30627
- const runArgs = parseProjectGodot(readFileSync14(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
30660
+ const runArgs = parseProjectGodot(readFileSync15(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
30628
30661
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
30629
30662
  }
30630
30663
  function camelCased(params) {
@@ -30640,7 +30673,7 @@ function camelCased(params) {
30640
30673
  class GodotServer {
30641
30674
  mcp;
30642
30675
  locator = new GodotLocator;
30643
- operationsScript = join14(__dirname2, "godot", "operations", "godot_operations.gd");
30676
+ operationsScript = join15(__dirname2, "godot", "operations", "godot_operations.gd");
30644
30677
  godotBridge;
30645
30678
  tools = buildToolDefinitions();
30646
30679
  activeProcess = null;
@@ -30658,8 +30691,6 @@ class GodotServer {
30658
30691
  successorWatch = null;
30659
30692
  lastProjectPath = null;
30660
30693
  shutdownInitiated = false;
30661
- shortListEditors = new Map;
30662
- cacheLastRebuilt = new Map;
30663
30694
  launchedEditor = null;
30664
30695
  launchedFrom = null;
30665
30696
  ownProject;
@@ -31307,7 +31338,7 @@ class GodotServer {
31307
31338
  if (path === undefined) {
31308
31339
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
31309
31340
  }
31310
- const file = join14(path, "project.godot");
31341
+ const file = join15(path, "project.godot");
31311
31342
  if (!existsSync9(file)) {
31312
31343
  return {
31313
31344
  ok: false,
@@ -31531,7 +31562,7 @@ class GodotServer {
31531
31562
  if (!engine.ok) {
31532
31563
  return engine.response;
31533
31564
  }
31534
- const application = parseProjectGodot(readFileSync14(project.value.file, "utf8"))["application"] ?? {};
31565
+ const application = parseProjectGodot(readFileSync15(project.value.file, "utf8"))["application"] ?? {};
31535
31566
  const name = application["config/name"];
31536
31567
  const mainScene = application["run/main_scene"];
31537
31568
  const info = {
@@ -31592,18 +31623,18 @@ class GodotServer {
31592
31623
  if (!engine.ok) {
31593
31624
  return engine.response;
31594
31625
  }
31595
- const folder = dirname8(output.absolutePath);
31626
+ const folder = dirname9(output.absolutePath);
31596
31627
  try {
31597
- mkdirSync8(folder, { recursive: true });
31628
+ mkdirSync9(folder, { recursive: true });
31598
31629
  } catch (error) {
31599
- return this.createErrorResponse(`Cannot create ${dirname8(output.relativePath)} to export into: ${errorMessage(error)}`);
31630
+ return this.createErrorResponse(`Cannot create ${dirname9(output.relativePath)} to export into: ${errorMessage(error)}`);
31600
31631
  }
31601
31632
  const debug = readBoolean(args, "debug") ?? false;
31602
- const exportLogs = mkdtempSync2(join14(tmpdir4(), "gdharness-export-"));
31633
+ const exportLogs = mkdtempSync2(join15(tmpdir4(), "gdharness-export-"));
31603
31634
  const exportArgs = [
31604
31635
  "--headless",
31605
31636
  "--log-file",
31606
- join14(exportLogs, "engine.log"),
31637
+ join15(exportLogs, "engine.log"),
31607
31638
  "--path",
31608
31639
  project.value.path,
31609
31640
  debug ? "--export-debug" : "--export-release",
@@ -31661,7 +31692,7 @@ class GodotServer {
31661
31692
  return contained.response;
31662
31693
  }
31663
31694
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
31664
- if (!existsSync9(join14(project.value.path, runner))) {
31695
+ if (!existsSync9(join15(project.value.path, runner))) {
31665
31696
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
31666
31697
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
31667
31698
  ]);
@@ -31696,7 +31727,7 @@ class GodotServer {
31696
31727
  ];
31697
31728
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
31698
31729
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
31699
- const userData = mkdtempSync2(join14(tmpdir4(), "gdharness-tests-"));
31730
+ const userData = mkdtempSync2(join15(tmpdir4(), "gdharness-tests-"));
31700
31731
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
31701
31732
  const hung = await new Promise((resolve) => {
31702
31733
  const timer = setTimeout(() => {
@@ -31712,19 +31743,22 @@ class GodotServer {
31712
31743
  resolve(false);
31713
31744
  });
31714
31745
  });
31715
- const reportsDir = join14(project.value.path, ".godot", ...ours.split("/"));
31746
+ const reportsDir = join15(project.value.path, ".godot", ...ours.split("/"));
31716
31747
  let report = null;
31717
31748
  let reportProblem = null;
31718
31749
  try {
31719
31750
  const written = existsSync9(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
31720
31751
  const newest = written.at(-1);
31721
31752
  if (newest !== undefined) {
31722
- report = parseJUnit(readFileSync14(join14(reportsDir, newest, "results.xml"), "utf8"));
31753
+ report = parseJUnit(readFileSync15(join15(reportsDir, newest, "results.xml"), "utf8"));
31723
31754
  }
31724
31755
  } catch (error) {
31725
31756
  reportProblem = errorMessage(error);
31726
31757
  } finally {
31727
31758
  discard(reportsDir, userData);
31759
+ try {
31760
+ rmdirSync(dirname9(reportsDir));
31761
+ } catch {}
31728
31762
  }
31729
31763
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
31730
31764
  const verdicts = {
@@ -31859,7 +31893,7 @@ class GodotServer {
31859
31893
  return null;
31860
31894
  }
31861
31895
  try {
31862
- return readFileSync14(contained.absolutePath, "utf8");
31896
+ return readFileSync15(contained.absolutePath, "utf8");
31863
31897
  } catch {
31864
31898
  return null;
31865
31899
  }
@@ -31897,8 +31931,15 @@ class GodotServer {
31897
31931
  };
31898
31932
  }
31899
31933
  editorWritesAShortList(projectPath) {
31900
- const key = resolve5(projectPath);
31901
- return this.shortListEditors.has(key) && this.shortListEditors.get(key) === this.godotBridge.getStatus().editorPid;
31934
+ const recorded = readClassNote(projectPath)?.shortListEditorPid;
31935
+ return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
31936
+ }
31937
+ noteAShortListEditor(projectPath) {
31938
+ const note = readClassNote(projectPath);
31939
+ writeClassNote(projectPath, {
31940
+ rebuiltWith: note?.rebuiltWith ?? [],
31941
+ shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
31942
+ });
31902
31943
  }
31903
31944
  sourceOfClass(projectPath, resourcePath) {
31904
31945
  const contained = resolveWithinProject(projectPath, resourcePath);
@@ -31906,7 +31947,7 @@ class GodotServer {
31906
31947
  return null;
31907
31948
  }
31908
31949
  try {
31909
- return readFileSync14(contained.absolutePath, "utf8");
31950
+ return readFileSync15(contained.absolutePath, "utf8");
31910
31951
  } catch {
31911
31952
  return null;
31912
31953
  }
@@ -32135,7 +32176,7 @@ class GodotServer {
32135
32176
  return null;
32136
32177
  }
32137
32178
  try {
32138
- return settingKeys(readFileSync14(join14(projectPath, "project.godot"), "utf8"));
32179
+ return settingKeys(readFileSync15(join15(projectPath, "project.godot"), "utf8"));
32139
32180
  } catch {
32140
32181
  return null;
32141
32182
  }
@@ -32377,7 +32418,7 @@ class GodotServer {
32377
32418
  }
32378
32419
  async runtimeUp(projectPath, before, budget, withArgs) {
32379
32420
  const budgetMs = budget.ms;
32380
- if (!existsSync9(join14(projectPath, RUNTIME_AUTOLOAD.path))) {
32421
+ if (!existsSync9(join15(projectPath, RUNTIME_AUTOLOAD.path))) {
32381
32422
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
32382
32423
  }
32383
32424
  let asking = null;
@@ -33250,7 +33291,7 @@ class GodotServer {
33250
33291
  const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
33251
33292
  const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
33252
33293
  if (lost.length > 0) {
33253
- this.shortListEditors.set(resolve5(projectPath), this.godotBridge.getStatus().editorPid);
33294
+ this.noteAShortListEditor(projectPath);
33254
33295
  }
33255
33296
  const atMissingPaths = after === null ? [] : cachedAtMissingPaths(after, projectPath);
33256
33297
  const restored = lost.length > 0 || atMissingPaths.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
@@ -33316,9 +33357,8 @@ class GodotServer {
33316
33357
  return now === null ? [] : lost.filter((name) => now.has(name));
33317
33358
  }
33318
33359
  async rebuildClassCache(projectPath) {
33319
- const key = resolve5(projectPath);
33320
33360
  const before = cachedClasses(projectPath);
33321
- const remembered = this.cacheLastRebuilt.get(key);
33361
+ const noted = readClassNote(projectPath);
33322
33362
  const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
33323
33363
  if (!rebuilt.ok) {
33324
33364
  return rebuilt;
@@ -33327,12 +33367,15 @@ class GodotServer {
33327
33367
  if (after === null) {
33328
33368
  return rebuilt;
33329
33369
  }
33330
- this.cacheLastRebuilt.set(key, new Set(after.keys()));
33331
- const lost = remembered === undefined || before === null ? [] : [...remembered].filter((name) => !before.has(name) && after.has(name)).sort();
33332
- if (lost.length === 0 || !this.godotBridge.isConnected()) {
33370
+ const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
33371
+ const seenLosing = lost.length > 0 && this.godotBridge.isConnected();
33372
+ writeClassNote(projectPath, {
33373
+ rebuiltWith: [...after.keys()].sort(),
33374
+ ...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
33375
+ });
33376
+ if (!seenLosing) {
33333
33377
  return rebuilt;
33334
33378
  }
33335
- this.shortListEditors.set(key, this.godotBridge.getStatus().editorPid);
33336
33379
  const them = lost.length === 1 ? "it" : "them";
33337
33380
  return {
33338
33381
  ...rebuilt,
@@ -33489,8 +33532,8 @@ class GodotServer {
33489
33532
  ]);
33490
33533
  }
33491
33534
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
33492
- const screenshotDir = expectsScreenshot ? mkdtempSync2(join14(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
33493
- const screenshotPath = screenshotDir ? join14(screenshotDir, "capture.png") : null;
33535
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join15(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
33536
+ const screenshotPath = screenshotDir ? join15(screenshotDir, "capture.png") : null;
33494
33537
  try {
33495
33538
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
33496
33539
  if (!reply.ok) {
@@ -33514,7 +33557,7 @@ class GodotServer {
33514
33557
  type: "text",
33515
33558
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
33516
33559
  },
33517
- { type: "image", data: readFileSync14(screenshotPath).toString("base64"), mimeType: "image/png" }
33560
+ { type: "image", data: readFileSync15(screenshotPath).toString("base64"), mimeType: "image/png" }
33518
33561
  ]
33519
33562
  };
33520
33563
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",