gdharness 1.0.7 → 1.0.8

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 +120 -80
  2. package/build/index.js +132 -94
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -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="], {
@@ -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
  }
@@ -37607,16 +37639,16 @@ import {
37607
37639
  closeSync,
37608
37640
  existsSync as existsSync14,
37609
37641
  fstatSync,
37610
- mkdirSync as mkdirSync11,
37642
+ mkdirSync as mkdirSync12,
37611
37643
  mkdtempSync as mkdtempSync3,
37612
37644
  openSync as openSync2,
37613
37645
  readdirSync as readdirSync7,
37614
- readFileSync as readFileSync18,
37646
+ readFileSync as readFileSync19,
37615
37647
  readSync,
37616
37648
  statSync as statSync5
37617
37649
  } from "node:fs";
37618
37650
  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";
37651
+ import { basename as basename4, dirname as dirname14, join as join21, normalize as normalize3, relative as relative3 } from "node:path";
37620
37652
  import { setTimeout as delay3 } from "node:timers/promises";
37621
37653
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37622
37654
  import { promisify as promisify7 } from "node:util";
@@ -37858,7 +37890,7 @@ function aboveTheRunner(entry) {
37858
37890
  }
37859
37891
  function elsewhereIn(projectPath, asked) {
37860
37892
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
37861
- if (wanted === "" || existsSync14(join20(projectPath, wanted))) {
37893
+ if (wanted === "" || existsSync14(join21(projectPath, wanted))) {
37862
37894
  return "";
37863
37895
  }
37864
37896
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -37872,7 +37904,7 @@ function elsewhereIn(projectPath, asked) {
37872
37904
  return near.length === 0 ? "" : ` The project does have ${there}.`;
37873
37905
  }
37874
37906
  function hasMainScene(projectFile) {
37875
- const scene = parseProjectGodot(readFileSync18(projectFile, "utf8"))["application"]?.["run/main_scene"];
37907
+ const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
37876
37908
  return typeof scene === "string" && scene !== "";
37877
37909
  }
37878
37910
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -37880,13 +37912,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
37880
37912
  return false;
37881
37913
  }
37882
37914
  try {
37883
- return readFileSync18(join20(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
37915
+ return readFileSync19(join21(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
37884
37916
  } catch {
37885
37917
  return false;
37886
37918
  }
37887
37919
  }
37888
37920
  function editorPlaysHeadless(projectFile) {
37889
- const runArgs = parseProjectGodot(readFileSync18(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37921
+ const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37890
37922
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
37891
37923
  }
37892
37924
  function camelCased(params) {
@@ -37902,7 +37934,7 @@ function camelCased(params) {
37902
37934
  class GodotServer {
37903
37935
  mcp;
37904
37936
  locator = new GodotLocator2;
37905
- operationsScript = join20(__dirname2, "godot", "operations", "godot_operations.gd");
37937
+ operationsScript = join21(__dirname2, "godot", "operations", "godot_operations.gd");
37906
37938
  godotBridge;
37907
37939
  tools = buildToolDefinitions();
37908
37940
  activeProcess = null;
@@ -37920,8 +37952,6 @@ class GodotServer {
37920
37952
  successorWatch = null;
37921
37953
  lastProjectPath = null;
37922
37954
  shutdownInitiated = false;
37923
- shortListEditors = new Map;
37924
- cacheLastRebuilt = new Map;
37925
37955
  launchedEditor = null;
37926
37956
  launchedFrom = null;
37927
37957
  ownProject;
@@ -38569,7 +38599,7 @@ class GodotServer {
38569
38599
  if (path === undefined) {
38570
38600
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
38571
38601
  }
38572
- const file = join20(path, "project.godot");
38602
+ const file = join21(path, "project.godot");
38573
38603
  if (!existsSync14(file)) {
38574
38604
  return {
38575
38605
  ok: false,
@@ -38793,7 +38823,7 @@ class GodotServer {
38793
38823
  if (!engine.ok) {
38794
38824
  return engine.response;
38795
38825
  }
38796
- const application = parseProjectGodot(readFileSync18(project.value.file, "utf8"))["application"] ?? {};
38826
+ const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
38797
38827
  const name = application["config/name"];
38798
38828
  const mainScene = application["run/main_scene"];
38799
38829
  const info = {
@@ -38854,18 +38884,18 @@ class GodotServer {
38854
38884
  if (!engine.ok) {
38855
38885
  return engine.response;
38856
38886
  }
38857
- const folder = dirname13(output.absolutePath);
38887
+ const folder = dirname14(output.absolutePath);
38858
38888
  try {
38859
- mkdirSync11(folder, { recursive: true });
38889
+ mkdirSync12(folder, { recursive: true });
38860
38890
  } catch (error) {
38861
- return this.createErrorResponse(`Cannot create ${dirname13(output.relativePath)} to export into: ${errorMessage(error)}`);
38891
+ return this.createErrorResponse(`Cannot create ${dirname14(output.relativePath)} to export into: ${errorMessage(error)}`);
38862
38892
  }
38863
38893
  const debug = readBoolean(args, "debug") ?? false;
38864
- const exportLogs = mkdtempSync3(join20(tmpdir5(), "gdharness-export-"));
38894
+ const exportLogs = mkdtempSync3(join21(tmpdir5(), "gdharness-export-"));
38865
38895
  const exportArgs = [
38866
38896
  "--headless",
38867
38897
  "--log-file",
38868
- join20(exportLogs, "engine.log"),
38898
+ join21(exportLogs, "engine.log"),
38869
38899
  "--path",
38870
38900
  project.value.path,
38871
38901
  debug ? "--export-debug" : "--export-release",
@@ -38923,7 +38953,7 @@ class GodotServer {
38923
38953
  return contained.response;
38924
38954
  }
38925
38955
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
38926
- if (!existsSync14(join20(project.value.path, runner))) {
38956
+ if (!existsSync14(join21(project.value.path, runner))) {
38927
38957
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
38928
38958
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
38929
38959
  ]);
@@ -38958,7 +38988,7 @@ class GodotServer {
38958
38988
  ];
38959
38989
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
38960
38990
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
38961
- const userData = mkdtempSync3(join20(tmpdir5(), "gdharness-tests-"));
38991
+ const userData = mkdtempSync3(join21(tmpdir5(), "gdharness-tests-"));
38962
38992
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
38963
38993
  const hung = await new Promise((resolve) => {
38964
38994
  const timer = setTimeout(() => {
@@ -38974,14 +39004,14 @@ class GodotServer {
38974
39004
  resolve(false);
38975
39005
  });
38976
39006
  });
38977
- const reportsDir = join20(project.value.path, ".godot", ...ours.split("/"));
39007
+ const reportsDir = join21(project.value.path, ".godot", ...ours.split("/"));
38978
39008
  let report = null;
38979
39009
  let reportProblem = null;
38980
39010
  try {
38981
39011
  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
39012
  const newest = written.at(-1);
38983
39013
  if (newest !== undefined) {
38984
- report = parseJUnit(readFileSync18(join20(reportsDir, newest, "results.xml"), "utf8"));
39014
+ report = parseJUnit(readFileSync19(join21(reportsDir, newest, "results.xml"), "utf8"));
38985
39015
  }
38986
39016
  } catch (error) {
38987
39017
  reportProblem = errorMessage(error);
@@ -39121,7 +39151,7 @@ class GodotServer {
39121
39151
  return null;
39122
39152
  }
39123
39153
  try {
39124
- return readFileSync18(contained.absolutePath, "utf8");
39154
+ return readFileSync19(contained.absolutePath, "utf8");
39125
39155
  } catch {
39126
39156
  return null;
39127
39157
  }
@@ -39159,8 +39189,15 @@ class GodotServer {
39159
39189
  };
39160
39190
  }
39161
39191
  editorWritesAShortList(projectPath) {
39162
- const key = resolve5(projectPath);
39163
- return this.shortListEditors.has(key) && this.shortListEditors.get(key) === this.godotBridge.getStatus().editorPid;
39192
+ const recorded = readClassNote(projectPath)?.shortListEditorPid;
39193
+ return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
39194
+ }
39195
+ noteAShortListEditor(projectPath) {
39196
+ const note = readClassNote(projectPath);
39197
+ writeClassNote(projectPath, {
39198
+ rebuiltWith: note?.rebuiltWith ?? [],
39199
+ shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
39200
+ });
39164
39201
  }
39165
39202
  sourceOfClass(projectPath, resourcePath) {
39166
39203
  const contained = resolveWithinProject(projectPath, resourcePath);
@@ -39168,7 +39205,7 @@ class GodotServer {
39168
39205
  return null;
39169
39206
  }
39170
39207
  try {
39171
- return readFileSync18(contained.absolutePath, "utf8");
39208
+ return readFileSync19(contained.absolutePath, "utf8");
39172
39209
  } catch {
39173
39210
  return null;
39174
39211
  }
@@ -39397,7 +39434,7 @@ class GodotServer {
39397
39434
  return null;
39398
39435
  }
39399
39436
  try {
39400
- return settingKeys(readFileSync18(join20(projectPath, "project.godot"), "utf8"));
39437
+ return settingKeys(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
39401
39438
  } catch {
39402
39439
  return null;
39403
39440
  }
@@ -39639,7 +39676,7 @@ class GodotServer {
39639
39676
  }
39640
39677
  async runtimeUp(projectPath, before, budget, withArgs) {
39641
39678
  const budgetMs = budget.ms;
39642
- if (!existsSync14(join20(projectPath, RUNTIME_AUTOLOAD2.path))) {
39679
+ if (!existsSync14(join21(projectPath, RUNTIME_AUTOLOAD2.path))) {
39643
39680
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
39644
39681
  }
39645
39682
  let asking = null;
@@ -40512,7 +40549,7 @@ class GodotServer {
40512
40549
  const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
40513
40550
  const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
40514
40551
  if (lost.length > 0) {
40515
- this.shortListEditors.set(resolve5(projectPath), this.godotBridge.getStatus().editorPid);
40552
+ this.noteAShortListEditor(projectPath);
40516
40553
  }
40517
40554
  const atMissingPaths = after === null ? [] : cachedAtMissingPaths(after, projectPath);
40518
40555
  const restored = lost.length > 0 || atMissingPaths.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
@@ -40578,9 +40615,8 @@ class GodotServer {
40578
40615
  return now === null ? [] : lost.filter((name) => now.has(name));
40579
40616
  }
40580
40617
  async rebuildClassCache(projectPath) {
40581
- const key = resolve5(projectPath);
40582
40618
  const before = cachedClasses(projectPath);
40583
- const remembered = this.cacheLastRebuilt.get(key);
40619
+ const noted = readClassNote(projectPath);
40584
40620
  const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
40585
40621
  if (!rebuilt.ok) {
40586
40622
  return rebuilt;
@@ -40589,12 +40625,15 @@ class GodotServer {
40589
40625
  if (after === null) {
40590
40626
  return rebuilt;
40591
40627
  }
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()) {
40628
+ const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
40629
+ const seenLosing = lost.length > 0 && this.godotBridge.isConnected();
40630
+ writeClassNote(projectPath, {
40631
+ rebuiltWith: [...after.keys()].sort(),
40632
+ ...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
40633
+ });
40634
+ if (!seenLosing) {
40595
40635
  return rebuilt;
40596
40636
  }
40597
- this.shortListEditors.set(key, this.godotBridge.getStatus().editorPid);
40598
40637
  const them = lost.length === 1 ? "it" : "them";
40599
40638
  return {
40600
40639
  ...rebuilt,
@@ -40751,8 +40790,8 @@ class GodotServer {
40751
40790
  ]);
40752
40791
  }
40753
40792
  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;
40793
+ const screenshotDir = expectsScreenshot ? mkdtempSync3(join21(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
40794
+ const screenshotPath = screenshotDir ? join21(screenshotDir, "capture.png") : null;
40756
40795
  try {
40757
40796
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
40758
40797
  if (!reply.ok) {
@@ -40776,7 +40815,7 @@ class GodotServer {
40776
40815
  type: "text",
40777
40816
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
40778
40817
  },
40779
- { type: "image", data: readFileSync18(screenshotPath).toString("base64"), mimeType: "image/png" }
40818
+ { type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
40780
40819
  ]
40781
40820
  };
40782
40821
  } finally {
@@ -40852,6 +40891,7 @@ var init_server3 = __esm(() => {
40852
40891
  init_breakpoint_note();
40853
40892
  init_bridge_announce();
40854
40893
  init_class_cache();
40894
+ init_class_note();
40855
40895
  init_config_pin();
40856
40896
  init_dap_client();
40857
40897
  init_errors();
@@ -40878,7 +40918,7 @@ var init_server3 = __esm(() => {
40878
40918
  init_tool_definitions();
40879
40919
  init_update_check();
40880
40920
  run7 = promisify7(execFile7);
40881
- __dirname2 = dirname13(fileURLToPath4(import.meta.url));
40921
+ __dirname2 = dirname14(fileURLToPath4(import.meta.url));
40882
40922
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
40883
40923
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
40884
40924
  VALUE_NAMES = {
@@ -40919,7 +40959,7 @@ var init_server3 = __esm(() => {
40919
40959
 
40920
40960
  // src/cli.ts
40921
40961
  import { existsSync as existsSync15 } from "fs";
40922
- import { join as join21, resolve as resolve6 } from "path";
40962
+ import { join as join22, resolve as resolve5 } from "path";
40923
40963
 
40924
40964
  // src/errors.ts
40925
40965
  class Refusal extends Error {
@@ -42623,8 +42663,8 @@ class UsageError extends Refusal {
42623
42663
  }
42624
42664
  function projectArgument(at) {
42625
42665
  const named = args.slice(at).find((value) => !value.startsWith("--"));
42626
- const projectPath = resolve6(named ?? ".");
42627
- if (!existsSync15(join21(projectPath, "project.godot"))) {
42666
+ const projectPath = resolve5(named ?? ".");
42667
+ if (!existsSync15(join22(projectPath, "project.godot"))) {
42628
42668
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
42629
42669
  }
42630
42670
  return projectPath;
@@ -42663,7 +42703,7 @@ async function registerRuntime(godot, projectPath) {
42663
42703
  return;
42664
42704
  }
42665
42705
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
42666
- if (!existsSync15(join21(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
42706
+ if (!existsSync15(join22(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
42667
42707
  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
42708
  }
42669
42709
  }
package/build/index.js CHANGED
@@ -10175,16 +10175,16 @@ 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
10184
  statSync as statSync5
10185
10185
  } from "node:fs";
10186
10186
  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";
10187
+ import { basename as basename3, dirname as dirname9, join as join15, normalize as normalize2, relative as relative3 } from "node:path";
10188
10188
  import { setTimeout as delay3 } from "node:timers/promises";
10189
10189
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10190
10190
  import { promisify as promisify5 } from "node:util";
@@ -25814,18 +25814,49 @@ function unseenByEditor(projectPath, editorHolds) {
25814
25814
  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
25815
  }
25816
25816
 
25817
+ // src/class-note.ts
25818
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
25819
+ import { dirname as dirname5, join as join7 } from "node:path";
25820
+ function classNotePath(projectPath) {
25821
+ return join7(projectPath, ".godot", "gdharness-classes.json");
25822
+ }
25823
+ function readClassNote(projectPath) {
25824
+ try {
25825
+ const fields = asParams(JSON.parse(readFileSync8(classNotePath(projectPath), "utf8")));
25826
+ const rebuiltWith = readArray(fields, "rebuiltWith");
25827
+ if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
25828
+ return null;
25829
+ }
25830
+ const pid = readNumber(fields, "shortListEditorPid");
25831
+ return {
25832
+ rebuiltWith: rebuiltWith.map(String),
25833
+ ...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
25834
+ };
25835
+ } catch {
25836
+ return null;
25837
+ }
25838
+ }
25839
+ function writeClassNote(projectPath, note) {
25840
+ try {
25841
+ const path = classNotePath(projectPath);
25842
+ mkdirSync5(dirname5(path), { recursive: true });
25843
+ writeFileSync5(path, `${JSON.stringify(note, null, 2)}
25844
+ `, "utf8");
25845
+ } catch {}
25846
+ }
25847
+
25817
25848
  // src/config-pin.ts
25818
- import { existsSync as existsSync4, readFileSync as readFileSync8 } from "node:fs";
25819
- import { join as join7 } from "node:path";
25849
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
25850
+ import { join as join8 } from "node:path";
25820
25851
  var PINNED = /\bgdharness@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/g;
25821
25852
  function versionsNamedInConfig(projectPath) {
25822
- const config = join7(projectPath, ".mcp.json");
25853
+ const config = join8(projectPath, ".mcp.json");
25823
25854
  if (!existsSync4(config)) {
25824
25855
  return [];
25825
25856
  }
25826
25857
  let text;
25827
25858
  try {
25828
- text = readFileSync8(config, "utf8");
25859
+ text = readFileSync9(config, "utf8");
25829
25860
  JSON.parse(text);
25830
25861
  } catch {
25831
25862
  return [];
@@ -27192,7 +27223,7 @@ import { promisify as promisify2 } from "node:util";
27192
27223
  // src/detection.ts
27193
27224
  import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
27194
27225
  import { homedir as homedir2 } from "node:os";
27195
- import { join as join8 } from "node:path";
27226
+ import { join as join9 } from "node:path";
27196
27227
  function resolveHomeDirectory() {
27197
27228
  try {
27198
27229
  return homedir2();
@@ -27216,7 +27247,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
27216
27247
  if (!pattern.test(name)) {
27217
27248
  continue;
27218
27249
  }
27219
- const fullPath = join8(directory, name);
27250
+ const fullPath = join9(directory, name);
27220
27251
  try {
27221
27252
  const stat = statSync3(fullPath);
27222
27253
  if (stat.isFile()) {
@@ -27225,7 +27256,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
27225
27256
  } catch {}
27226
27257
  }
27227
27258
  matches.sort((a, b) => b.mtime - a.mtime);
27228
- return matches.map((m) => join8(directory, m.name));
27259
+ return matches.map((m) => join9(directory, m.name));
27229
27260
  }
27230
27261
  function conventionalPaths(platform, home) {
27231
27262
  const paths = ["godot"];
@@ -27345,9 +27376,9 @@ class GodotLocator {
27345
27376
 
27346
27377
  // src/headless.ts
27347
27378
  import { execFile as execFile3 } from "node:child_process";
27348
- import { mkdtempSync, writeFileSync as writeFileSync5 } from "node:fs";
27379
+ import { mkdtempSync, writeFileSync as writeFileSync6 } from "node:fs";
27349
27380
  import { tmpdir as tmpdir3 } from "node:os";
27350
- import { join as join9 } from "node:path";
27381
+ import { join as join10 } from "node:path";
27351
27382
  import { promisify as promisify3 } from "node:util";
27352
27383
 
27353
27384
  // src/scratch.ts
@@ -27413,12 +27444,12 @@ function reason(stdout, stderr) {
27413
27444
  return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
27414
27445
  }
27415
27446
  async function runImport(godotPath, projectPath) {
27416
- const logDir = mkdtempSync(join9(tmpdir3(), "gdharness-import-"));
27447
+ const logDir = mkdtempSync(join10(tmpdir3(), "gdharness-import-"));
27417
27448
  try {
27418
27449
  const { stderr } = await run3(godotPath, [
27419
27450
  "--headless",
27420
27451
  "--log-file",
27421
- join9(logDir, "engine.log"),
27452
+ join10(logDir, "engine.log"),
27422
27453
  "--path",
27423
27454
  projectPath,
27424
27455
  "--import"
@@ -27443,13 +27474,13 @@ async function runImport(godotPath, projectPath) {
27443
27474
  }
27444
27475
  }
27445
27476
  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");
27477
+ const paramsDir = mkdtempSync(join10(tmpdir3(), "gdharness-params-"));
27478
+ const paramsFile = join10(paramsDir, `${operation}.json`);
27479
+ writeFileSync6(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
27449
27480
  const args = [
27450
27481
  "--headless",
27451
27482
  "--log-file",
27452
- join9(paramsDir, "engine.log"),
27483
+ join10(paramsDir, "engine.log"),
27453
27484
  "--path",
27454
27485
  projectPath,
27455
27486
  "--script",
@@ -27878,7 +27909,7 @@ function orphansPrinted(printed) {
27878
27909
  import { realpathSync as realpathSync2 } from "node:fs";
27879
27910
  import { readFile, realpath } from "node:fs/promises";
27880
27911
  import { createConnection as createConnection3 } from "node:net";
27881
- import { dirname as dirname5, resolve as resolve3 } from "node:path";
27912
+ import { dirname as dirname6, resolve as resolve3 } from "node:path";
27882
27913
  import { fileURLToPath, pathToFileURL } from "node:url";
27883
27914
  var DEFAULT_LSP_PORT = 6005;
27884
27915
  var DIAGNOSTICS_TIMEOUT_MS = 5000;
@@ -28008,7 +28039,7 @@ class GodotLSPClient {
28008
28039
  if (this.initialized) {
28009
28040
  return;
28010
28041
  }
28011
- const rootPath = this.rootPath ?? dirname5(resolve3(filePath));
28042
+ const rootPath = this.rootPath ?? dirname6(resolve3(filePath));
28012
28043
  await this.initialize(rootPath);
28013
28044
  }
28014
28045
  async sendRequest(method, params) {
@@ -28444,14 +28475,14 @@ function secondsFromClock(printed) {
28444
28475
  }
28445
28476
 
28446
28477
  // 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";
28478
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
28479
+ import { join as join11 } from "node:path";
28449
28480
  var SKIPPED = new Set(["node_modules"]);
28450
28481
  function skipped(name) {
28451
28482
  return name.startsWith(".") || SKIPPED.has(name);
28452
28483
  }
28453
28484
  function steppedOver(directory) {
28454
- return existsSync7(join10(directory, ".gdignore"));
28485
+ return existsSync7(join11(directory, ".gdignore"));
28455
28486
  }
28456
28487
  var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
28457
28488
  function projectStructure(projectPath) {
@@ -28465,7 +28496,7 @@ function projectStructure(projectPath) {
28465
28496
  continue;
28466
28497
  }
28467
28498
  if (entry.isDirectory()) {
28468
- visit(join10(directory, entry.name));
28499
+ visit(join11(directory, entry.name));
28469
28500
  } else if (entry.isFile()) {
28470
28501
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
28471
28502
  if (extension === "tscn") {
@@ -28496,14 +28527,14 @@ function scriptsWithoutUid(projectPath) {
28496
28527
  }
28497
28528
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
28498
28529
  if (entry.isDirectory()) {
28499
- visit(join10(directory, entry.name), spelled);
28530
+ visit(join11(directory, entry.name), spelled);
28500
28531
  continue;
28501
28532
  }
28502
28533
  if (!entry.isFile()) {
28503
28534
  continue;
28504
28535
  }
28505
28536
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
28506
- if (CARRIES_A_UID.has(extension) && !existsSync7(join10(directory, `${entry.name}.uid`))) {
28537
+ if (CARRIES_A_UID.has(extension) && !existsSync7(join11(directory, `${entry.name}.uid`))) {
28507
28538
  found.push(spelled);
28508
28539
  }
28509
28540
  }
@@ -28538,7 +28569,7 @@ function searchProject(projectPath, options) {
28538
28569
  if (skipped(entry.name)) {
28539
28570
  continue;
28540
28571
  }
28541
- const entryPath = join10(directory, entry.name);
28572
+ const entryPath = join11(directory, entry.name);
28542
28573
  if (entry.isDirectory()) {
28543
28574
  visit(entryPath);
28544
28575
  continue;
@@ -28549,7 +28580,7 @@ function searchProject(projectPath, options) {
28549
28580
  }
28550
28581
  result.summary.files_searched += 1;
28551
28582
  const matches = [];
28552
- for (const [index, line] of readFileSync9(entryPath, "utf8").split(`
28583
+ for (const [index, line] of readFileSync10(entryPath, "utf8").split(`
28553
28584
  `).entries()) {
28554
28585
  if (full()) {
28555
28586
  break;
@@ -28572,7 +28603,7 @@ function searchProject(projectPath, options) {
28572
28603
  }
28573
28604
 
28574
28605
  // src/resources.ts
28575
- import { readFileSync as readFileSync10 } from "node:fs";
28606
+ import { readFileSync as readFileSync11 } from "node:fs";
28576
28607
  import { extname, resolve as resolve4 } from "node:path";
28577
28608
  var STATIC_RESOURCES = [
28578
28609
  {
@@ -28767,7 +28798,7 @@ function readResourceText(uri, getProjectPath) {
28767
28798
  const parsedUri = parseGodotUri(uri);
28768
28799
  if (parsedUri.kind === "project-info") {
28769
28800
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
28770
- const rawProject = readFileSync10(projectFilePath, "utf-8");
28801
+ const rawProject = readFileSync11(projectFilePath, "utf-8");
28771
28802
  const parsedProject = parseProjectGodot(rawProject);
28772
28803
  return {
28773
28804
  mimeType: "application/json",
@@ -28776,7 +28807,7 @@ function readResourceText(uri, getProjectPath) {
28776
28807
  }
28777
28808
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
28778
28809
  ensureAllowedExtension(parsedUri.kind, filePath);
28779
- const text = readFileSync10(filePath, "utf-8");
28810
+ const text = readFileSync11(filePath, "utf-8");
28780
28811
  return {
28781
28812
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
28782
28813
  text
@@ -28813,16 +28844,16 @@ function setupResourceHandlers(mcp, getProjectPath) {
28813
28844
  }
28814
28845
 
28815
28846
  // 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";
28847
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
28848
+ import { dirname as dirname7, join as join12 } from "node:path";
28818
28849
  function restartNotePath(projectPath) {
28819
- return join11(projectPath, ".godot", "gdharness-restart.json");
28850
+ return join12(projectPath, ".godot", "gdharness-restart.json");
28820
28851
  }
28821
28852
  function noteRestartBegun(note) {
28822
28853
  const path = restartNotePath(note.projectPath);
28823
28854
  try {
28824
- mkdirSync5(dirname6(path), { recursive: true });
28825
- writeFileSync6(path, `${JSON.stringify(note, null, 2)}
28855
+ mkdirSync6(dirname7(path), { recursive: true });
28856
+ writeFileSync7(path, `${JSON.stringify(note, null, 2)}
28826
28857
  `, "utf8");
28827
28858
  } catch (error) {
28828
28859
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -28830,7 +28861,7 @@ function noteRestartBegun(note) {
28830
28861
  }
28831
28862
  function restartOwed(projectPath) {
28832
28863
  try {
28833
- const parsed = JSON.parse(readFileSync11(restartNotePath(projectPath), "utf8"));
28864
+ const parsed = JSON.parse(readFileSync12(restartNotePath(projectPath), "utf8"));
28834
28865
  if (typeof parsed !== "object" || parsed === null) {
28835
28866
  return null;
28836
28867
  }
@@ -28853,36 +28884,36 @@ function restartSettled(projectPath) {
28853
28884
 
28854
28885
  // src/run-record.ts
28855
28886
  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";
28887
+ import { mkdirSync as mkdirSync7, openSync, readdirSync as readdirSync5, readFileSync as readFileSync13, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync8 } from "node:fs";
28888
+ import { basename as basename2, join as join13 } from "node:path";
28858
28889
  function runsDirectory() {
28859
- return join12(runtimeDirectory(), "runs");
28890
+ return join13(runtimeDirectory(), "runs");
28860
28891
  }
28861
28892
  function recordPath() {
28862
- return join12(runsDirectory(), "run.json");
28893
+ return join13(runsDirectory(), "run.json");
28863
28894
  }
28864
28895
  function recordPaths() {
28865
- return runtimeDirectories().map((directory) => join12(directory, "runs", "run.json"));
28896
+ return runtimeDirectories().map((directory) => join13(directory, "runs", "run.json"));
28866
28897
  }
28867
28898
  function openTranscript(startedAt) {
28868
- mkdirSync6(runsDirectory(), { recursive: true });
28869
- const path = join12(runsDirectory(), `run-${startedAt}.log`);
28899
+ mkdirSync7(runsDirectory(), { recursive: true });
28900
+ const path = join13(runsDirectory(), `run-${startedAt}.log`);
28870
28901
  return { path, fd: openSync(path, "a") };
28871
28902
  }
28872
28903
  function writeRunRecord(record) {
28873
- mkdirSync6(runsDirectory(), { recursive: true });
28874
- writeFileSync7(recordPath(), JSON.stringify(record, null, 2), "utf8");
28904
+ mkdirSync7(runsDirectory(), { recursive: true });
28905
+ writeFileSync8(recordPath(), JSON.stringify(record, null, 2), "utf8");
28875
28906
  }
28876
28907
  function editorNotePath() {
28877
- return join12(runsDirectory(), "editor-run.json");
28908
+ return join13(runsDirectory(), "editor-run.json");
28878
28909
  }
28879
28910
  function writeEditorRunNote(note) {
28880
- mkdirSync6(runsDirectory(), { recursive: true });
28881
- writeFileSync7(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
28911
+ mkdirSync7(runsDirectory(), { recursive: true });
28912
+ writeFileSync8(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
28882
28913
  }
28883
28914
  function readEditorRunNote() {
28884
28915
  for (const directory of runtimeDirectories()) {
28885
- const note = editorNoteAt(join12(directory, "runs", "editor-run.json"));
28916
+ const note = editorNoteAt(join13(directory, "runs", "editor-run.json"));
28886
28917
  if (note !== null) {
28887
28918
  return note;
28888
28919
  }
@@ -28892,7 +28923,7 @@ function readEditorRunNote() {
28892
28923
  function editorNoteAt(path) {
28893
28924
  let parsed;
28894
28925
  try {
28895
- parsed = JSON.parse(readFileSync12(path, "utf8"));
28926
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
28896
28927
  } catch {
28897
28928
  return null;
28898
28929
  }
@@ -28915,7 +28946,7 @@ function recordRunEnded(pid, exitCode) {
28915
28946
  return;
28916
28947
  }
28917
28948
  try {
28918
- writeFileSync7(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
28949
+ writeFileSync8(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
28919
28950
  } catch {}
28920
28951
  }
28921
28952
  function readRunRecord() {
@@ -28930,7 +28961,7 @@ function readRunRecord() {
28930
28961
  function recordAt(path) {
28931
28962
  let parsed;
28932
28963
  try {
28933
- parsed = JSON.parse(readFileSync12(path, "utf8"));
28964
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
28934
28965
  } catch {
28935
28966
  return null;
28936
28967
  }
@@ -28975,7 +29006,7 @@ function runningAs(pid) {
28975
29006
  const began = startedAt(pid, null);
28976
29007
  const when = began === null ? {} : { startedAt: began };
28977
29008
  if (process.platform === "linux") {
28978
- const raw = readFileSync12(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
29009
+ const raw = readFileSync13(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
28979
29010
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
28980
29011
  }
28981
29012
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -29128,7 +29159,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
29128
29159
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
29129
29160
  continue;
29130
29161
  }
29131
- const path = join12(directory, entry);
29162
+ const path = join13(directory, entry);
29132
29163
  if (path === keep || ageMs(path, now) < keepMs) {
29133
29164
  continue;
29134
29165
  }
@@ -29139,8 +29170,8 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
29139
29170
  }
29140
29171
 
29141
29172
  // 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";
29173
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
29174
+ import { dirname as dirname8, join as join14 } from "node:path";
29144
29175
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
29145
29176
  var RUNTIME_AUTOLOAD = {
29146
29177
  name: "GdharnessRuntime",
@@ -29148,11 +29179,11 @@ var RUNTIME_AUTOLOAD = {
29148
29179
  };
29149
29180
  var VERSION_MARKER = ".gdharness-version";
29150
29181
  function installedAddonVersion(projectPath) {
29151
- const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29182
+ const marker = join14(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29152
29183
  if (!existsSync8(marker)) {
29153
29184
  return null;
29154
29185
  }
29155
- const said = readFileSync13(marker, "utf8").trim();
29186
+ const said = readFileSync14(marker, "utf8").trim();
29156
29187
  return said === "" ? null : said;
29157
29188
  }
29158
29189
 
@@ -30309,7 +30340,7 @@ function buildToolDefinitions() {
30309
30340
  var UPDATE_NOTICE_EVERY = 500;
30310
30341
  var FEEDBACK_NOTICE_EVERY = 250;
30311
30342
  var run5 = promisify5(execFile5);
30312
- var __dirname2 = dirname8(fileURLToPath2(import.meta.url));
30343
+ var __dirname2 = dirname9(fileURLToPath2(import.meta.url));
30313
30344
  var EDITOR_RESTART_TIMEOUT_MS = 90000;
30314
30345
  var HOLD_PING_MS = 2000;
30315
30346
  var SLOWEST_FRAME_RATE = 20;
@@ -30596,7 +30627,7 @@ function aboveTheRunner(entry) {
30596
30627
  }
30597
30628
  function elsewhereIn(projectPath, asked) {
30598
30629
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
30599
- if (wanted === "" || existsSync9(join14(projectPath, wanted))) {
30630
+ if (wanted === "" || existsSync9(join15(projectPath, wanted))) {
30600
30631
  return "";
30601
30632
  }
30602
30633
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -30610,7 +30641,7 @@ function elsewhereIn(projectPath, asked) {
30610
30641
  return near.length === 0 ? "" : ` The project does have ${there}.`;
30611
30642
  }
30612
30643
  function hasMainScene(projectFile) {
30613
- const scene = parseProjectGodot(readFileSync14(projectFile, "utf8"))["application"]?.["run/main_scene"];
30644
+ const scene = parseProjectGodot(readFileSync15(projectFile, "utf8"))["application"]?.["run/main_scene"];
30614
30645
  return typeof scene === "string" && scene !== "";
30615
30646
  }
30616
30647
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -30618,13 +30649,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
30618
30649
  return false;
30619
30650
  }
30620
30651
  try {
30621
- return readFileSync14(join14(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
30652
+ return readFileSync15(join15(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
30622
30653
  } catch {
30623
30654
  return false;
30624
30655
  }
30625
30656
  }
30626
30657
  function editorPlaysHeadless(projectFile) {
30627
- const runArgs = parseProjectGodot(readFileSync14(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
30658
+ const runArgs = parseProjectGodot(readFileSync15(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
30628
30659
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
30629
30660
  }
30630
30661
  function camelCased(params) {
@@ -30640,7 +30671,7 @@ function camelCased(params) {
30640
30671
  class GodotServer {
30641
30672
  mcp;
30642
30673
  locator = new GodotLocator;
30643
- operationsScript = join14(__dirname2, "godot", "operations", "godot_operations.gd");
30674
+ operationsScript = join15(__dirname2, "godot", "operations", "godot_operations.gd");
30644
30675
  godotBridge;
30645
30676
  tools = buildToolDefinitions();
30646
30677
  activeProcess = null;
@@ -30658,8 +30689,6 @@ class GodotServer {
30658
30689
  successorWatch = null;
30659
30690
  lastProjectPath = null;
30660
30691
  shutdownInitiated = false;
30661
- shortListEditors = new Map;
30662
- cacheLastRebuilt = new Map;
30663
30692
  launchedEditor = null;
30664
30693
  launchedFrom = null;
30665
30694
  ownProject;
@@ -31307,7 +31336,7 @@ class GodotServer {
31307
31336
  if (path === undefined) {
31308
31337
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
31309
31338
  }
31310
- const file = join14(path, "project.godot");
31339
+ const file = join15(path, "project.godot");
31311
31340
  if (!existsSync9(file)) {
31312
31341
  return {
31313
31342
  ok: false,
@@ -31531,7 +31560,7 @@ class GodotServer {
31531
31560
  if (!engine.ok) {
31532
31561
  return engine.response;
31533
31562
  }
31534
- const application = parseProjectGodot(readFileSync14(project.value.file, "utf8"))["application"] ?? {};
31563
+ const application = parseProjectGodot(readFileSync15(project.value.file, "utf8"))["application"] ?? {};
31535
31564
  const name = application["config/name"];
31536
31565
  const mainScene = application["run/main_scene"];
31537
31566
  const info = {
@@ -31592,18 +31621,18 @@ class GodotServer {
31592
31621
  if (!engine.ok) {
31593
31622
  return engine.response;
31594
31623
  }
31595
- const folder = dirname8(output.absolutePath);
31624
+ const folder = dirname9(output.absolutePath);
31596
31625
  try {
31597
- mkdirSync8(folder, { recursive: true });
31626
+ mkdirSync9(folder, { recursive: true });
31598
31627
  } catch (error) {
31599
- return this.createErrorResponse(`Cannot create ${dirname8(output.relativePath)} to export into: ${errorMessage(error)}`);
31628
+ return this.createErrorResponse(`Cannot create ${dirname9(output.relativePath)} to export into: ${errorMessage(error)}`);
31600
31629
  }
31601
31630
  const debug = readBoolean(args, "debug") ?? false;
31602
- const exportLogs = mkdtempSync2(join14(tmpdir4(), "gdharness-export-"));
31631
+ const exportLogs = mkdtempSync2(join15(tmpdir4(), "gdharness-export-"));
31603
31632
  const exportArgs = [
31604
31633
  "--headless",
31605
31634
  "--log-file",
31606
- join14(exportLogs, "engine.log"),
31635
+ join15(exportLogs, "engine.log"),
31607
31636
  "--path",
31608
31637
  project.value.path,
31609
31638
  debug ? "--export-debug" : "--export-release",
@@ -31661,7 +31690,7 @@ class GodotServer {
31661
31690
  return contained.response;
31662
31691
  }
31663
31692
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
31664
- if (!existsSync9(join14(project.value.path, runner))) {
31693
+ if (!existsSync9(join15(project.value.path, runner))) {
31665
31694
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
31666
31695
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
31667
31696
  ]);
@@ -31696,7 +31725,7 @@ class GodotServer {
31696
31725
  ];
31697
31726
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
31698
31727
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
31699
- const userData = mkdtempSync2(join14(tmpdir4(), "gdharness-tests-"));
31728
+ const userData = mkdtempSync2(join15(tmpdir4(), "gdharness-tests-"));
31700
31729
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
31701
31730
  const hung = await new Promise((resolve) => {
31702
31731
  const timer = setTimeout(() => {
@@ -31712,14 +31741,14 @@ class GodotServer {
31712
31741
  resolve(false);
31713
31742
  });
31714
31743
  });
31715
- const reportsDir = join14(project.value.path, ".godot", ...ours.split("/"));
31744
+ const reportsDir = join15(project.value.path, ".godot", ...ours.split("/"));
31716
31745
  let report = null;
31717
31746
  let reportProblem = null;
31718
31747
  try {
31719
31748
  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
31749
  const newest = written.at(-1);
31721
31750
  if (newest !== undefined) {
31722
- report = parseJUnit(readFileSync14(join14(reportsDir, newest, "results.xml"), "utf8"));
31751
+ report = parseJUnit(readFileSync15(join15(reportsDir, newest, "results.xml"), "utf8"));
31723
31752
  }
31724
31753
  } catch (error) {
31725
31754
  reportProblem = errorMessage(error);
@@ -31859,7 +31888,7 @@ class GodotServer {
31859
31888
  return null;
31860
31889
  }
31861
31890
  try {
31862
- return readFileSync14(contained.absolutePath, "utf8");
31891
+ return readFileSync15(contained.absolutePath, "utf8");
31863
31892
  } catch {
31864
31893
  return null;
31865
31894
  }
@@ -31897,8 +31926,15 @@ class GodotServer {
31897
31926
  };
31898
31927
  }
31899
31928
  editorWritesAShortList(projectPath) {
31900
- const key = resolve5(projectPath);
31901
- return this.shortListEditors.has(key) && this.shortListEditors.get(key) === this.godotBridge.getStatus().editorPid;
31929
+ const recorded = readClassNote(projectPath)?.shortListEditorPid;
31930
+ return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
31931
+ }
31932
+ noteAShortListEditor(projectPath) {
31933
+ const note = readClassNote(projectPath);
31934
+ writeClassNote(projectPath, {
31935
+ rebuiltWith: note?.rebuiltWith ?? [],
31936
+ shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
31937
+ });
31902
31938
  }
31903
31939
  sourceOfClass(projectPath, resourcePath) {
31904
31940
  const contained = resolveWithinProject(projectPath, resourcePath);
@@ -31906,7 +31942,7 @@ class GodotServer {
31906
31942
  return null;
31907
31943
  }
31908
31944
  try {
31909
- return readFileSync14(contained.absolutePath, "utf8");
31945
+ return readFileSync15(contained.absolutePath, "utf8");
31910
31946
  } catch {
31911
31947
  return null;
31912
31948
  }
@@ -32135,7 +32171,7 @@ class GodotServer {
32135
32171
  return null;
32136
32172
  }
32137
32173
  try {
32138
- return settingKeys(readFileSync14(join14(projectPath, "project.godot"), "utf8"));
32174
+ return settingKeys(readFileSync15(join15(projectPath, "project.godot"), "utf8"));
32139
32175
  } catch {
32140
32176
  return null;
32141
32177
  }
@@ -32377,7 +32413,7 @@ class GodotServer {
32377
32413
  }
32378
32414
  async runtimeUp(projectPath, before, budget, withArgs) {
32379
32415
  const budgetMs = budget.ms;
32380
- if (!existsSync9(join14(projectPath, RUNTIME_AUTOLOAD.path))) {
32416
+ if (!existsSync9(join15(projectPath, RUNTIME_AUTOLOAD.path))) {
32381
32417
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
32382
32418
  }
32383
32419
  let asking = null;
@@ -33250,7 +33286,7 @@ class GodotServer {
33250
33286
  const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
33251
33287
  const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
33252
33288
  if (lost.length > 0) {
33253
- this.shortListEditors.set(resolve5(projectPath), this.godotBridge.getStatus().editorPid);
33289
+ this.noteAShortListEditor(projectPath);
33254
33290
  }
33255
33291
  const atMissingPaths = after === null ? [] : cachedAtMissingPaths(after, projectPath);
33256
33292
  const restored = lost.length > 0 || atMissingPaths.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
@@ -33316,9 +33352,8 @@ class GodotServer {
33316
33352
  return now === null ? [] : lost.filter((name) => now.has(name));
33317
33353
  }
33318
33354
  async rebuildClassCache(projectPath) {
33319
- const key = resolve5(projectPath);
33320
33355
  const before = cachedClasses(projectPath);
33321
- const remembered = this.cacheLastRebuilt.get(key);
33356
+ const noted = readClassNote(projectPath);
33322
33357
  const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
33323
33358
  if (!rebuilt.ok) {
33324
33359
  return rebuilt;
@@ -33327,12 +33362,15 @@ class GodotServer {
33327
33362
  if (after === null) {
33328
33363
  return rebuilt;
33329
33364
  }
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()) {
33365
+ const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
33366
+ const seenLosing = lost.length > 0 && this.godotBridge.isConnected();
33367
+ writeClassNote(projectPath, {
33368
+ rebuiltWith: [...after.keys()].sort(),
33369
+ ...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
33370
+ });
33371
+ if (!seenLosing) {
33333
33372
  return rebuilt;
33334
33373
  }
33335
- this.shortListEditors.set(key, this.godotBridge.getStatus().editorPid);
33336
33374
  const them = lost.length === 1 ? "it" : "them";
33337
33375
  return {
33338
33376
  ...rebuilt,
@@ -33489,8 +33527,8 @@ class GodotServer {
33489
33527
  ]);
33490
33528
  }
33491
33529
  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;
33530
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join15(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
33531
+ const screenshotPath = screenshotDir ? join15(screenshotDir, "capture.png") : null;
33494
33532
  try {
33495
33533
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
33496
33534
  if (!reply.ok) {
@@ -33514,7 +33552,7 @@ class GodotServer {
33514
33552
  type: "text",
33515
33553
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
33516
33554
  },
33517
- { type: "image", data: readFileSync14(screenshotPath).toString("base64"), mimeType: "image/png" }
33555
+ { type: "image", data: readFileSync15(screenshotPath).toString("base64"), mimeType: "image/png" }
33518
33556
  ]
33519
33557
  };
33520
33558
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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",