gdharness 0.13.1 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -7493,7 +7493,7 @@ var init_server_version = __esm(() => {
7493
7493
  });
7494
7494
 
7495
7495
  // src/class-cache.ts
7496
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
7496
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
7497
7497
  import { join as join6 } from "node:path";
7498
7498
  function declaredClasses(projectPath) {
7499
7499
  const declared = new Map;
@@ -7531,6 +7531,14 @@ function cachedClasses(projectPath) {
7531
7531
  }
7532
7532
  return listed;
7533
7533
  }
7534
+ function cacheWrittenAt(projectPath) {
7535
+ const cache = join6(projectPath, ".godot", "global_script_class_cache.cfg");
7536
+ try {
7537
+ return statSync2(cache).mtimeMs;
7538
+ } catch {
7539
+ return null;
7540
+ }
7541
+ }
7534
7542
  function staleAgainst(cached, projectPath) {
7535
7543
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
7536
7544
  }
@@ -14744,9 +14752,17 @@ var init_types = __esm(() => {
14744
14752
  });
14745
14753
 
14746
14754
  // src/paths.ts
14755
+ import { realpathSync } from "node:fs";
14747
14756
  import { isAbsolute, relative, resolve, sep } from "node:path";
14757
+ function realPathOr(path) {
14758
+ try {
14759
+ return realpathSync.native(path);
14760
+ } catch {
14761
+ return path;
14762
+ }
14763
+ }
14748
14764
  function isSameDirectory(onePath, otherPath) {
14749
- return relative(resolve(onePath), resolve(otherPath)) === "";
14765
+ return relative(realPathOr(resolve(onePath)), realPathOr(resolve(otherPath))) === "";
14750
14766
  }
14751
14767
  function isWithinRoot(rootPath, candidatePath) {
14752
14768
  const step = relative(resolve(rootPath), resolve(candidatePath));
@@ -15127,8 +15143,16 @@ function buildToolDefinitions() {
15127
15143
  };
15128
15144
  });
15129
15145
  }
15130
- var PROJECT_PATH, RUNNING_PROJECT_PATH, SCENE_PATH, SCRIPT_PATH, RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
15146
+ var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, SCENE_PATH, SCRIPT_PATH, RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
15131
15147
  var init_tool_definitions = __esm(() => {
15148
+ PROJECT_INFO_SECTIONS = [
15149
+ "autoloads",
15150
+ "plugins",
15151
+ "export_presets",
15152
+ "audio_buses",
15153
+ "health",
15154
+ "validation"
15155
+ ];
15132
15156
  PROJECT_PATH = {
15133
15157
  type: "string",
15134
15158
  description: "Absolute path to the project directory, the one holding project.godot."
@@ -15155,7 +15179,7 @@ var init_tool_definitions = __esm(() => {
15155
15179
  };
15156
15180
  PROPERTIES = {
15157
15181
  type: "object",
15158
- description: 'Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}.',
15182
+ description: 'Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another.',
15159
15183
  additionalProperties: true
15160
15184
  };
15161
15185
  XY = {
@@ -15278,10 +15302,7 @@ var init_tool_definitions = __esm(() => {
15278
15302
  projectPath: PROJECT_PATH,
15279
15303
  include: {
15280
15304
  type: "array",
15281
- items: {
15282
- type: "string",
15283
- enum: ["autoloads", "plugins", "export_presets", "audio_buses", "health", "validation"]
15284
- },
15305
+ items: { type: "string", enum: PROJECT_INFO_SECTIONS },
15285
15306
  description: "Extra sections: registered autoloads, addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation."
15286
15307
  },
15287
15308
  preset: { type: "string", description: "For validation: the export preset to validate against." },
@@ -15299,6 +15320,12 @@ var init_tool_definitions = __esm(() => {
15299
15320
  parameters: {
15300
15321
  projectPath: PROJECT_PATH,
15301
15322
  setting: { type: "string", description: 'Setting path, such as "display/window/size/viewport_width".' },
15323
+ from: {
15324
+ type: "string",
15325
+ enum: ["disk", "editor"],
15326
+ ops: ["get"],
15327
+ description: "get: where to read. Default disk, which starts a short-lived engine and reads project.godot, needs no editor and is what an automatic check can reproduce. editor asks the open editor instead, which costs no engine and answers what the editor holds, including changes nobody has saved. Asking for editor with none connected is refused rather than answered from disk, so an answer never means the other one quietly."
15328
+ },
15302
15329
  prefix: {
15303
15330
  type: "string",
15304
15331
  ops: ["get"],
@@ -16151,7 +16178,7 @@ var init_tool_definitions = __esm(() => {
16151
16178
  });
16152
16179
 
16153
16180
  // src/headless-operations.ts
16154
- var HEADLESS_OPERATIONS;
16181
+ var HEADLESS_OPERATIONS, EDITOR_READS;
16155
16182
  var init_headless_operations = __esm(() => {
16156
16183
  HEADLESS_OPERATIONS = dictionary({
16157
16184
  project_settings: dictionary({
@@ -16185,6 +16212,9 @@ var init_headless_operations = __esm(() => {
16185
16212
  inheritance: "inspect_inheritance"
16186
16213
  })
16187
16214
  });
16215
+ EDITOR_READS = dictionary({
16216
+ project_settings: dictionary({ get: "get_project_setting" })
16217
+ });
16188
16218
  });
16189
16219
 
16190
16220
  // node_modules/zod/v3/helpers/util.js
@@ -30955,6 +30985,36 @@ var init_bridge_announce = __esm(() => {
30955
30985
  init_errors();
30956
30986
  });
30957
30987
 
30988
+ // src/config-pin.ts
30989
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
30990
+ import { join as join12 } from "node:path";
30991
+ function versionsNamedInConfig(projectPath) {
30992
+ const config = join12(projectPath, ".mcp.json");
30993
+ if (!existsSync9(config)) {
30994
+ return [];
30995
+ }
30996
+ let text;
30997
+ try {
30998
+ text = readFileSync10(config, "utf8");
30999
+ JSON.parse(text);
31000
+ } catch {
31001
+ return [];
31002
+ }
31003
+ return [...new Set([...text.matchAll(PINNED)].map((found) => found[1] ?? ""))].filter((version) => version !== "");
31004
+ }
31005
+ function configDisagrees(projectPath, serverVersion) {
31006
+ const named = versionsNamedInConfig(projectPath);
31007
+ const others = named.filter((version) => version !== serverVersion);
31008
+ if (others.length === 0) {
31009
+ return null;
31010
+ }
31011
+ return `.mcp.json in this project names gdharness@${others.join(" and gdharness@")}, and this server ` + `is ${serverVersion}. Whichever is meant to be right, nothing will change version until the ` + "MCP server is reconnected, and a pin kept anywhere else does not reach this file.";
31012
+ }
31013
+ var PINNED;
31014
+ var init_config_pin = __esm(() => {
31015
+ PINNED = /\bgdharness@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/g;
31016
+ });
31017
+
30958
31018
  // src/framing.ts
30959
31019
  function frame(message) {
30960
31020
  const body = Buffer.from(JSON.stringify(message), "utf8");
@@ -34932,7 +34992,7 @@ var init_godot_bridge = __esm(() => {
34932
34992
 
34933
34993
  // src/godot-path.ts
34934
34994
  import { execFile as execFile4 } from "node:child_process";
34935
- import { existsSync as existsSync9 } from "node:fs";
34995
+ import { existsSync as existsSync10 } from "node:fs";
34936
34996
  import { normalize as normalize2 } from "node:path";
34937
34997
  import { promisify as promisify4 } from "node:util";
34938
34998
  var run4, GodotLocator2;
@@ -34974,7 +35034,7 @@ var init_godot_path = __esm(() => {
34974
35034
  return known;
34975
35035
  }
34976
35036
  let ok = false;
34977
- if (path === "godot" || existsSync9(path)) {
35037
+ if (path === "godot" || existsSync10(path)) {
34978
35038
  try {
34979
35039
  await run4(path, ["--version"]);
34980
35040
  ok = true;
@@ -35319,7 +35379,7 @@ var init_junit = __esm(() => {
35319
35379
  });
35320
35380
 
35321
35381
  // src/lsp_client.ts
35322
- import { realpathSync } from "node:fs";
35382
+ import { realpathSync as realpathSync2 } from "node:fs";
35323
35383
  import { readFile, realpath } from "node:fs/promises";
35324
35384
  import { createConnection as createConnection2 } from "node:net";
35325
35385
  import { dirname as dirname7, resolve as resolve3 } from "node:path";
@@ -35335,7 +35395,7 @@ function diagnosticsKey(uri) {
35335
35395
  return decoded;
35336
35396
  }
35337
35397
  try {
35338
- return realpathSync.native(fileURLToPath3(decoded)).toLowerCase();
35398
+ return realpathSync2.native(fileURLToPath3(decoded)).toLowerCase();
35339
35399
  } catch {
35340
35400
  return decoded.toLowerCase();
35341
35401
  }
@@ -35895,13 +35955,13 @@ var init_process_time = __esm(() => {
35895
35955
  });
35896
35956
 
35897
35957
  // src/project-scan.ts
35898
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
35899
- import { join as join12 } from "node:path";
35958
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
35959
+ import { join as join13 } from "node:path";
35900
35960
  function skipped(name) {
35901
35961
  return name.startsWith(".") || SKIPPED.has(name);
35902
35962
  }
35903
35963
  function steppedOver(directory) {
35904
- return existsSync10(join12(directory, ".gdignore"));
35964
+ return existsSync11(join13(directory, ".gdignore"));
35905
35965
  }
35906
35966
  function projectStructure(projectPath) {
35907
35967
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -35914,7 +35974,7 @@ function projectStructure(projectPath) {
35914
35974
  continue;
35915
35975
  }
35916
35976
  if (entry.isDirectory()) {
35917
- visit(join12(directory, entry.name));
35977
+ visit(join13(directory, entry.name));
35918
35978
  } else if (entry.isFile()) {
35919
35979
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
35920
35980
  if (extension === "tscn") {
@@ -35959,7 +36019,7 @@ function searchProject(projectPath, options) {
35959
36019
  if (skipped(entry.name)) {
35960
36020
  continue;
35961
36021
  }
35962
- const entryPath = join12(directory, entry.name);
36022
+ const entryPath = join13(directory, entry.name);
35963
36023
  if (entry.isDirectory()) {
35964
36024
  visit(entryPath);
35965
36025
  continue;
@@ -35970,7 +36030,7 @@ function searchProject(projectPath, options) {
35970
36030
  }
35971
36031
  result.summary.files_searched += 1;
35972
36032
  const matches = [];
35973
- for (const [index, line] of readFileSync10(entryPath, "utf8").split(`
36033
+ for (const [index, line] of readFileSync11(entryPath, "utf8").split(`
35974
36034
  `).entries()) {
35975
36035
  if (full()) {
35976
36036
  break;
@@ -35998,10 +36058,10 @@ var init_project_scan = __esm(() => {
35998
36058
  });
35999
36059
 
36000
36060
  // src/runtime-client.ts
36001
- import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync11, unlinkSync } from "node:fs";
36061
+ import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync12, unlinkSync } from "node:fs";
36002
36062
  import { createConnection as createConnection3 } from "node:net";
36003
36063
  import { tmpdir as tmpdir4 } from "node:os";
36004
- import { join as join13, resolve as resolve4 } from "node:path";
36064
+ import { join as join14, resolve as resolve4 } from "node:path";
36005
36065
  import { setTimeout as delay2 } from "node:timers/promises";
36006
36066
  function runtimeDirectory(variables = process.env) {
36007
36067
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -36009,13 +36069,13 @@ function runtimeDirectory(variables = process.env) {
36009
36069
  return explicit;
36010
36070
  }
36011
36071
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
36012
- return join13(perUser ?? tmpdir4(), "gdharness");
36072
+ return join14(perUser ?? tmpdir4(), "gdharness");
36013
36073
  }
36014
36074
  function runtimeDirectories(variables = process.env) {
36015
36075
  const candidates = [runtimeDirectory(variables)];
36016
36076
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
36017
36077
  for (const base of fallbacks) {
36018
- candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
36078
+ candidates.push(join14(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
36019
36079
  }
36020
36080
  return [...new Set(candidates.map((path) => resolve4(path)))];
36021
36081
  }
@@ -36030,7 +36090,7 @@ function processAlive(pid) {
36030
36090
  function parseAnnouncement(file, pid) {
36031
36091
  let fields;
36032
36092
  try {
36033
- fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
36093
+ fields = asParams(JSON.parse(readFileSync12(file, "utf8")));
36034
36094
  } catch {
36035
36095
  return { kind: "rubbish" };
36036
36096
  }
@@ -36069,7 +36129,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
36069
36129
  return runtimesAnnounced(directories).running;
36070
36130
  }
36071
36131
  function announcedIn(directory) {
36072
- if (!existsSync11(directory)) {
36132
+ if (!existsSync12(directory)) {
36073
36133
  return [];
36074
36134
  }
36075
36135
  const found = [];
@@ -36078,7 +36138,7 @@ function announcedIn(directory) {
36078
36138
  if (!match) {
36079
36139
  continue;
36080
36140
  }
36081
- const file = join13(directory, entry);
36141
+ const file = join14(directory, entry);
36082
36142
  const pid = Number.parseInt(match[1] ?? "", 10);
36083
36143
  const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
36084
36144
  if (announced.kind === "rubbish") {
@@ -36240,20 +36300,20 @@ var init_runtime_client = __esm(() => {
36240
36300
 
36241
36301
  // src/run-record.ts
36242
36302
  import { execFileSync } from "node:child_process";
36243
- import { mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync6, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync2, writeFileSync as writeFileSync8 } from "node:fs";
36244
- import { basename as basename2, join as join14 } from "node:path";
36303
+ import { mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync6, readFileSync as readFileSync13, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
36304
+ import { basename as basename2, join as join15 } from "node:path";
36245
36305
  function runsDirectory() {
36246
- return join14(runtimeDirectory(), "runs");
36306
+ return join15(runtimeDirectory(), "runs");
36247
36307
  }
36248
36308
  function recordPath() {
36249
- return join14(runsDirectory(), "run.json");
36309
+ return join15(runsDirectory(), "run.json");
36250
36310
  }
36251
36311
  function recordPaths() {
36252
- return runtimeDirectories().map((directory) => join14(directory, "runs", "run.json"));
36312
+ return runtimeDirectories().map((directory) => join15(directory, "runs", "run.json"));
36253
36313
  }
36254
36314
  function openTranscript(startedAt) {
36255
36315
  mkdirSync6(runsDirectory(), { recursive: true });
36256
- const path = join14(runsDirectory(), `run-${startedAt}.log`);
36316
+ const path = join15(runsDirectory(), `run-${startedAt}.log`);
36257
36317
  return { path, fd: openSync(path, "a") };
36258
36318
  }
36259
36319
  function writeRunRecord(record) {
@@ -36282,7 +36342,7 @@ function readRunRecord() {
36282
36342
  function recordAt(path) {
36283
36343
  let parsed;
36284
36344
  try {
36285
- parsed = JSON.parse(readFileSync12(path, "utf8"));
36345
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
36286
36346
  } catch {
36287
36347
  return null;
36288
36348
  }
@@ -36318,7 +36378,7 @@ function runningAs(pid) {
36318
36378
  }
36319
36379
  try {
36320
36380
  if (process.platform === "linux") {
36321
- const raw = readFileSync12(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
36381
+ const raw = readFileSync13(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
36322
36382
  return raw === "" ? null : { kind: "commandLine", text: raw };
36323
36383
  }
36324
36384
  const args = execFileSync("ps", ["-p", String(pid), "-o", "args="], {
@@ -36393,7 +36453,7 @@ function clearRunRecord(ours = () => true) {
36393
36453
  }
36394
36454
  function ageMs(path, now) {
36395
36455
  try {
36396
- return now - statSync2(path).mtimeMs;
36456
+ return now - statSync3(path).mtimeMs;
36397
36457
  } catch {
36398
36458
  return 0;
36399
36459
  }
@@ -36411,7 +36471,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
36411
36471
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
36412
36472
  continue;
36413
36473
  }
36414
- const path = join14(directory, entry);
36474
+ const path = join15(directory, entry);
36415
36475
  if (path === keep || ageMs(path, now) < keepMs) {
36416
36476
  continue;
36417
36477
  }
@@ -36425,14 +36485,14 @@ var init_run_record = __esm(() => {
36425
36485
  });
36426
36486
 
36427
36487
  // src/setup.ts
36428
- import { cpSync as cpSync2, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
36429
- import { dirname as dirname8, join as join15 } from "node:path";
36488
+ import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
36489
+ import { dirname as dirname8, join as join16 } from "node:path";
36430
36490
  function installedAddonVersion(projectPath) {
36431
- const marker = join15(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
36432
- if (!existsSync12(marker)) {
36491
+ const marker = join16(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
36492
+ if (!existsSync13(marker)) {
36433
36493
  return null;
36434
36494
  }
36435
- const said = readFileSync13(marker, "utf8").trim();
36495
+ const said = readFileSync14(marker, "utf8").trim();
36436
36496
  return said === "" ? null : said;
36437
36497
  }
36438
36498
  var ADDONS2, RUNTIME_AUTOLOAD2, VERSION_MARKER2 = ".gdharness-version";
@@ -36453,19 +36513,18 @@ import { execFile as execFile6, spawn } from "node:child_process";
36453
36513
  import { randomUUID as randomUUID2 } from "node:crypto";
36454
36514
  import {
36455
36515
  closeSync,
36456
- existsSync as existsSync13,
36516
+ existsSync as existsSync14,
36457
36517
  fstatSync,
36458
36518
  mkdirSync as mkdirSync8,
36459
36519
  mkdtempSync as mkdtempSync3,
36460
36520
  openSync as openSync2,
36461
36521
  readdirSync as readdirSync7,
36462
- readFileSync as readFileSync14,
36522
+ readFileSync as readFileSync15,
36463
36523
  readSync,
36464
- realpathSync as realpathSync2,
36465
36524
  rmSync as rmSync9
36466
36525
  } from "node:fs";
36467
36526
  import { tmpdir as tmpdir5 } from "node:os";
36468
- import { basename as basename3, dirname as dirname9, join as join16, normalize as normalize3 } from "node:path";
36527
+ import { basename as basename3, dirname as dirname9, join as join17, normalize as normalize3 } from "node:path";
36469
36528
  import { setTimeout as delay3 } from "node:timers/promises";
36470
36529
  import { fileURLToPath as fileURLToPath4 } from "node:url";
36471
36530
  import { promisify as promisify6 } from "node:util";
@@ -36563,6 +36622,30 @@ function wrongTypes(spec, args) {
36563
36622
  }
36564
36623
  return complaints;
36565
36624
  }
36625
+ function unlistedValues(schema, value, path) {
36626
+ if (typeof schema !== "object" || schema === null) {
36627
+ return [];
36628
+ }
36629
+ const fields = schema;
36630
+ const listed = fields["enum"];
36631
+ if (Array.isArray(listed) && typeof value === "string") {
36632
+ return listed.includes(value) ? [] : [`${path} as one of ${listed.join(", ")}, not ${JSON.stringify(value)}`];
36633
+ }
36634
+ if (Array.isArray(value)) {
36635
+ return value.flatMap((element, index) => unlistedValues(fields["items"], element, `${path}[${index}]`));
36636
+ }
36637
+ const properties = fields["properties"];
36638
+ if (isPlainObject3(properties) && isPlainObject3(value)) {
36639
+ return Object.entries(value).flatMap(([field, held]) => Object.hasOwn(properties, field) ? unlistedValues(properties[field], held, `${path}.${field}`) : []);
36640
+ }
36641
+ return [];
36642
+ }
36643
+ function isPlainObject3(value) {
36644
+ return typeof value === "object" && value !== null && !Array.isArray(value);
36645
+ }
36646
+ function outsideTheirLists(spec, args) {
36647
+ return Object.entries(args).flatMap(([name, value]) => unlistedValues(spec.parameters[name], value, name));
36648
+ }
36566
36649
  function escapingPropertyValue(properties) {
36567
36650
  if (typeof properties !== "object" || properties === null) {
36568
36651
  return;
@@ -36577,17 +36660,6 @@ function escapingPropertyValue(properties) {
36577
36660
  }
36578
36661
  return;
36579
36662
  }
36580
- function realPathOr(path) {
36581
- try {
36582
- return realpathSync2.native(path);
36583
- } catch {
36584
- return path;
36585
- }
36586
- }
36587
- function samePath(one, other) {
36588
- const settled = (path) => realPathOr(path).replaceAll("\\", "/").replace(/\/+$/, "").toLowerCase();
36589
- return settled(one) === settled(other);
36590
- }
36591
36663
  function aboveTheRunner(entry) {
36592
36664
  const runner = entry.detail.findIndex((line) => line.includes(RUNNER_DIRECTORY));
36593
36665
  if (runner < 0) {
@@ -36601,7 +36673,7 @@ function aboveTheRunner(entry) {
36601
36673
  }
36602
36674
  function elsewhereIn(projectPath, asked) {
36603
36675
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
36604
- if (wanted === "" || existsSync13(join16(projectPath, wanted))) {
36676
+ if (wanted === "" || existsSync14(join17(projectPath, wanted))) {
36605
36677
  return "";
36606
36678
  }
36607
36679
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -36615,11 +36687,11 @@ function elsewhereIn(projectPath, asked) {
36615
36687
  return near.length === 0 ? "" : ` The project does have ${there}.`;
36616
36688
  }
36617
36689
  function hasMainScene(projectFile) {
36618
- const scene = parseProjectGodot(readFileSync14(projectFile, "utf8"))["application"]?.["run/main_scene"];
36690
+ const scene = parseProjectGodot(readFileSync15(projectFile, "utf8"))["application"]?.["run/main_scene"];
36619
36691
  return typeof scene === "string" && scene !== "";
36620
36692
  }
36621
36693
  function editorPlaysHeadless(projectFile) {
36622
- const runArgs = parseProjectGodot(readFileSync14(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
36694
+ const runArgs = parseProjectGodot(readFileSync15(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
36623
36695
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
36624
36696
  }
36625
36697
  function camelCased(params) {
@@ -36635,7 +36707,7 @@ function camelCased(params) {
36635
36707
  class GodotServer {
36636
36708
  mcp;
36637
36709
  locator = new GodotLocator2;
36638
- operationsScript = join16(__dirname2, "godot", "operations", "godot_operations.gd");
36710
+ operationsScript = join17(__dirname2, "godot", "operations", "godot_operations.gd");
36639
36711
  godotBridge;
36640
36712
  tools = buildToolDefinitions();
36641
36713
  activeProcess = null;
@@ -36909,6 +36981,18 @@ class GodotServer {
36909
36981
  }
36910
36982
  });
36911
36983
  }
36984
+ const disagreement = this.configDisagreement();
36985
+ if (disagreement !== null) {
36986
+ this.noticedUpdate = true;
36987
+ this.callsSinceNotice = 0;
36988
+ return this.saying(answer, {
36989
+ config_names_another_version: {
36990
+ server_is: SERVER_VERSION,
36991
+ said: disagreement,
36992
+ what_to_do: "Tell the user which version their config names and which one is answering, and that " + "the two will not meet until .mcp.json is right and the MCP server is reconnected. Do " + "not edit their config without asking: which of the two they meant is theirs to say."
36993
+ }
36994
+ });
36995
+ }
36912
36996
  const notice = this.updates.notice();
36913
36997
  if (notice === null) {
36914
36998
  return answer;
@@ -36935,6 +37019,9 @@ class GodotServer {
36935
37019
  const installed = installedAddonVersion(this.ownProject);
36936
37020
  return installed === null || installed === SERVER_VERSION ? null : installed;
36937
37021
  }
37022
+ configDisagreement() {
37023
+ return this.ownProject === null ? null : configDisagrees(this.ownProject, SERVER_VERSION);
37024
+ }
36938
37025
  withFeedbackNotice(answer) {
36939
37026
  this.callsSinceFeedback += 1;
36940
37027
  if (this.callsSinceFeedback < FEEDBACK_NOTICE_EVERY) {
@@ -36950,13 +37037,17 @@ class GodotServer {
36950
37037
  const known = new Set([...Object.keys(spec.parameters), ...spec.operations ? ["op"] : []]);
36951
37038
  const unknown = Object.keys(args).filter((key) => !known.has(key));
36952
37039
  if (unknown.length > 0) {
36953
- const takes = known.size === 0 ? "It takes no arguments." : `It takes: ${[...known].join(", ")}.`;
37040
+ const asked = readString(args, "op");
37041
+ const named = asked !== undefined && spec.operations?.[asked] !== undefined ? asked : null;
37042
+ const wanted = named === null ? [...known] : [...argumentsOf(spec, named), "op"];
37043
+ const takes = wanted.length === 0 ? "It takes no arguments." : `It takes: ${wanted.join(", ")}.`;
37044
+ const where = named === null ? spec.name : `${spec.name} ${named}`;
36954
37045
  return {
36955
37046
  ok: false,
36956
- response: this.createErrorResponse(`${spec.name} does not take ${unknown.join(", ")}. ${takes}`)
37047
+ response: this.createErrorResponse(`${where} does not take ${unknown.join(", ")}. ${takes} This is gdharness ${SERVER_VERSION}, which is what decides whether an argument exists.`)
36957
37048
  };
36958
37049
  }
36959
- const wrong = wrongTypes(spec, args);
37050
+ const wrong = [...wrongTypes(spec, args), ...outsideTheirLists(spec, args)];
36960
37051
  if (wrong.length > 0) {
36961
37052
  return { ok: false, response: this.createErrorResponse(`${spec.name} takes ${wrong.join("; ")}.`) };
36962
37053
  }
@@ -37008,7 +37099,25 @@ class GodotServer {
37008
37099
  async dispatch(tool, op, args) {
37009
37100
  const headless = HEADLESS_OPERATIONS[tool]?.[op];
37010
37101
  if (headless !== undefined) {
37011
- const answered = await this.headless(headless, args);
37102
+ const { from: asked, ...answerable } = args;
37103
+ if (asked === "editor") {
37104
+ const editorSide = EDITOR_READS[tool]?.[op];
37105
+ if (editorSide === undefined) {
37106
+ return this.createErrorResponse(`${tool} ${op} cannot be read from the editor, only from disk.`, [
37107
+ 'Leave from unset, or pass from: "disk"'
37108
+ ]);
37109
+ }
37110
+ if (!this.godotBridge.isConnected()) {
37111
+ return this.createErrorResponse(`${tool} ${op} was asked of the editor, and no editor has reached this server.`, [
37112
+ "editor_status says whether the bridge is up and whether an editor may yet connect",
37113
+ 'Open the project with editor_launch, or pass from: "disk" to read the file instead'
37114
+ ]);
37115
+ }
37116
+ const { op: _dropped, ...asks } = answerable;
37117
+ const contained = this.containProjectFiles(asks);
37118
+ return contained.ok ? await this.handleViaBridge(editorSide, contained.value) : contained.response;
37119
+ }
37120
+ const answered = await this.headless(headless, answerable);
37012
37121
  return headless === "refresh_class_cache" && this.godotBridge.isConnected() ? await this.alsoSayWhatTheEditorCannotSee(answered, args) : answered;
37013
37122
  }
37014
37123
  const { op: _op, ...arguments_ } = args;
@@ -37223,8 +37332,8 @@ class GodotServer {
37223
37332
  if (path === undefined) {
37224
37333
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
37225
37334
  }
37226
- const file = join16(path, "project.godot");
37227
- if (!existsSync13(file)) {
37335
+ const file = join17(path, "project.godot");
37336
+ if (!existsSync14(file)) {
37228
37337
  return {
37229
37338
  ok: false,
37230
37339
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -37381,9 +37490,9 @@ class GodotServer {
37381
37490
  }
37382
37491
  async handleProjectInfo(args) {
37383
37492
  const include = readStringArray(args, "include") ?? [];
37384
- const unknown = include.filter((name) => PROJECT_INFO_SECTIONS[name] === undefined);
37493
+ const unknown = include.filter((name) => PROJECT_INFO_SECTIONS2[name] === undefined);
37385
37494
  if (unknown.length > 0) {
37386
- return this.createErrorResponse(`project_info cannot include ${unknown.join(", ")}. Sections: ${Object.keys(PROJECT_INFO_SECTIONS).join(", ")}.`);
37495
+ return this.createErrorResponse(`project_info cannot include ${unknown.join(", ")}. Sections: ${Object.keys(PROJECT_INFO_SECTIONS2).join(", ")}.`);
37387
37496
  }
37388
37497
  const project = this.project(args);
37389
37498
  if (!project.ok) {
@@ -37393,7 +37502,7 @@ class GodotServer {
37393
37502
  if (!engine.ok) {
37394
37503
  return engine.response;
37395
37504
  }
37396
- const application = parseProjectGodot(readFileSync14(project.value.file, "utf8"))["application"] ?? {};
37505
+ const application = parseProjectGodot(readFileSync15(project.value.file, "utf8"))["application"] ?? {};
37397
37506
  const name = application["config/name"];
37398
37507
  const mainScene = application["run/main_scene"];
37399
37508
  const info = {
@@ -37405,7 +37514,7 @@ class GodotServer {
37405
37514
  };
37406
37515
  const detailed = args["detail"] === "full";
37407
37516
  for (const section of include) {
37408
- const spec = PROJECT_INFO_SECTIONS[section];
37517
+ const spec = PROJECT_INFO_SECTIONS2[section];
37409
37518
  if (spec === undefined) {
37410
37519
  continue;
37411
37520
  }
@@ -37461,11 +37570,11 @@ class GodotServer {
37461
37570
  return this.createErrorResponse(`Cannot create ${dirname9(output.relativePath)} to export into: ${errorMessage(error)}`);
37462
37571
  }
37463
37572
  const debug = readBoolean(args, "debug") ?? false;
37464
- const exportLogs = mkdtempSync3(join16(tmpdir5(), "gdharness-export-"));
37573
+ const exportLogs = mkdtempSync3(join17(tmpdir5(), "gdharness-export-"));
37465
37574
  const exportArgs = [
37466
37575
  "--headless",
37467
37576
  "--log-file",
37468
- join16(exportLogs, "engine.log"),
37577
+ join17(exportLogs, "engine.log"),
37469
37578
  "--path",
37470
37579
  project.value.path,
37471
37580
  debug ? "--export-debug" : "--export-release",
@@ -37493,7 +37602,7 @@ class GodotServer {
37493
37602
  log.finish();
37494
37603
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
37495
37604
  const verdict = {
37496
- exported: exitCode === 0 && log.count("error") === 0 && existsSync13(output.absolutePath),
37605
+ exported: exitCode === 0 && log.count("error") === 0 && existsSync14(output.absolutePath),
37497
37606
  preset,
37498
37607
  outputPath: output.relativePath,
37499
37608
  debug,
@@ -37523,7 +37632,7 @@ class GodotServer {
37523
37632
  return contained.response;
37524
37633
  }
37525
37634
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
37526
- if (!existsSync13(join16(project.value.path, runner))) {
37635
+ if (!existsSync14(join17(project.value.path, runner))) {
37527
37636
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
37528
37637
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
37529
37638
  ]);
@@ -37558,7 +37667,7 @@ class GodotServer {
37558
37667
  ];
37559
37668
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
37560
37669
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
37561
- const userData = mkdtempSync3(join16(tmpdir5(), "gdharness-tests-"));
37670
+ const userData = mkdtempSync3(join17(tmpdir5(), "gdharness-tests-"));
37562
37671
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
37563
37672
  const hung = await new Promise((resolve) => {
37564
37673
  const timer = setTimeout(() => {
@@ -37574,14 +37683,14 @@ class GodotServer {
37574
37683
  resolve(false);
37575
37684
  });
37576
37685
  });
37577
- const reportsDir = join16(project.value.path, ".godot", ...ours.split("/"));
37686
+ const reportsDir = join17(project.value.path, ".godot", ...ours.split("/"));
37578
37687
  let report = null;
37579
37688
  let reportProblem = null;
37580
37689
  try {
37581
- const written = existsSync13(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
37690
+ const written = existsSync14(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
37582
37691
  const newest = written.at(-1);
37583
37692
  if (newest !== undefined) {
37584
- report = parseJUnit(readFileSync14(join16(reportsDir, newest, "results.xml"), "utf8"));
37693
+ report = parseJUnit(readFileSync15(join17(reportsDir, newest, "results.xml"), "utf8"));
37585
37694
  }
37586
37695
  } catch (error) {
37587
37696
  reportProblem = errorMessage(error);
@@ -37693,13 +37802,13 @@ class GodotServer {
37693
37802
  const severity = asParams(entry)["severity"];
37694
37803
  return severity === 1 || severity === "error" || severity === "ERROR";
37695
37804
  }).length;
37696
- return this.jsonTextResponse({
37805
+ return this.jsonTextResponse(markIfStale({
37697
37806
  scriptPath: readString(args, "scriptPath"),
37698
37807
  clean: errors === 0,
37699
37808
  errors,
37700
37809
  warnings: diagnostics.length - errors,
37701
37810
  diagnostics
37702
- });
37811
+ }, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
37703
37812
  }
37704
37813
  async handleLSP(toolName, args) {
37705
37814
  const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
@@ -37744,6 +37853,7 @@ class GodotServer {
37744
37853
  retryingBridge: this.bridgeRetry === null ? undefined : true,
37745
37854
  announcedAt: this.announcedAt ?? undefined,
37746
37855
  projectIs: this.projectHasMovedOn() ?? undefined,
37856
+ configIs: this.configDisagreement() ?? undefined,
37747
37857
  note: isPortConflict ? "Bridge port is already in use. Another gdharness instance owns the editor bridge, so this server cannot reach the editor. Usually the server this one replaced, still on its way out." : undefined,
37748
37858
  suggestion: isPortConflict ? `This server is asking for the port again every ${BRIDGE_RETRY_MS / 1000}s and takes it the moment the other one lets go, so editor tools come back on their own. Ask again, or end the older gdharness process to have it now.` : undefined
37749
37859
  };
@@ -37999,7 +38109,7 @@ class GodotServer {
37999
38109
  return runIsUp(run, editorSays);
38000
38110
  }
38001
38111
  async runtimeUp(projectPath, before, budgetMs) {
38002
- if (!existsSync13(join16(projectPath, RUNTIME_AUTOLOAD2.path))) {
38112
+ if (!existsSync14(join17(projectPath, RUNTIME_AUTOLOAD2.path))) {
38003
38113
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false });
38004
38114
  }
38005
38115
  const endpoint = await announcedSince(projectPath, before, {
@@ -38254,7 +38364,7 @@ class GodotServer {
38254
38364
  if (mine === null) {
38255
38365
  return false;
38256
38366
  }
38257
- return project !== "" && samePath(mine, project);
38367
+ return project !== "" && isSameDirectory(mine, project);
38258
38368
  }
38259
38369
  currentRun() {
38260
38370
  return this.activeProcess ?? this.adoptRecordedRun();
@@ -38385,6 +38495,11 @@ class GodotServer {
38385
38495
  async handleRescanFilesystem(args) {
38386
38496
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 30000;
38387
38497
  const started = Date.now();
38498
+ const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
38499
+ const before = projectPath === "" ? null : cachedClasses(projectPath);
38500
+ const writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
38501
+ const blind = projectPath === "" ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
38502
+ const atRisk = before === null ? [] : blind.unseen.filter((one) => before.has(one.className));
38388
38503
  const first = await this.handleViaBridge("rescan_filesystem", args);
38389
38504
  if (first.isError) {
38390
38505
  return first;
@@ -38393,17 +38508,27 @@ class GodotServer {
38393
38508
  while (busy && Date.now() - started < timeoutMs) {
38394
38509
  await new Promise((settle) => setTimeout(settle, 100));
38395
38510
  const status = asParams(await this.godotBridge.invokeTool("rescan_filesystem", { ...args, statusOnly: true }));
38396
- busy = Boolean(status["scanning"]) || Boolean(status["importing"]);
38511
+ const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
38512
+ busy = Boolean(status["scanning"]) || Boolean(status["importing"]) || pending;
38513
+ }
38514
+ if (!busy && atRisk.length > 0) {
38515
+ const until = Date.now() + CACHE_WRITE_MS;
38516
+ while (Date.now() < until && cacheWrittenAt(projectPath) === writtenBefore) {
38517
+ await new Promise((settle) => setTimeout(settle, 100));
38518
+ }
38397
38519
  }
38520
+ const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
38521
+ const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
38398
38522
  const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
38399
38523
  const unseen = checked.unseen;
38400
38524
  return this.jsonTextResponse({
38401
- ok: !busy && unseen.length === 0 && checked.unchecked === undefined,
38525
+ ok: !busy && unseen.length === 0 && lost.length === 0 && checked.unchecked === undefined,
38402
38526
  stillWorking: busy,
38403
38527
  waitedMs: Date.now() - started,
38404
38528
  unseenByEditor: unseen.length > 0 ? unseen : undefined,
38529
+ cacheLost: lost.length > 0 ? lost : undefined,
38405
38530
  classesUnchecked: checked.unchecked,
38406
- note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Change the declaring script and rescan, or restart the editor with editor_launch restart." : undefined
38531
+ note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : lost.length > 0 ? "The scan wrote the class cache from the list this editor is holding, and that list is shorter than the file was: these classes were in it before the scan and are not now. Every engine that reads the cache next, including a test run, will report them as unknown identifiers in files nobody touched. Run project_import refresh_classes to put them back, and do not rescan again until the editor has been restarted with editor_launch restart, because it will write the same short list over it." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Change the declaring script and rescan, or restart the editor with editor_launch restart." : undefined
38407
38532
  });
38408
38533
  }
38409
38534
  async alsoSayWhatTheEditorCannotSee(answered, args) {
@@ -38428,7 +38553,7 @@ class GodotServer {
38428
38553
  return { unseen: [], unchecked: "no projectPath, so there was nothing to read the cache from" };
38429
38554
  }
38430
38555
  const open = this.godotBridge.getStatus().projectPath;
38431
- if (open === undefined || !samePath(open, projectPath)) {
38556
+ if (open === undefined || !isSameDirectory(open, projectPath)) {
38432
38557
  return {
38433
38558
  unseen: [],
38434
38559
  unchecked: `the connected editor is open on ${open ?? "a project it did not name"}, not this one`
@@ -38456,6 +38581,14 @@ class GodotServer {
38456
38581
  "editor_status says whether the bridge is up and what it is listening on"
38457
38582
  ]);
38458
38583
  }
38584
+ const open = this.godotBridge.getStatus().projectPath;
38585
+ const meant = readNonEmptyString(args, "projectPath");
38586
+ if (open !== undefined && open !== "" && meant !== undefined && !isSameDirectory(open, meant)) {
38587
+ return this.createErrorResponse(`This call names ${meant}, and the editor on this bridge has ${open} open.`, [
38588
+ "editor_status names the project this server's editor is showing",
38589
+ "One server serves one editor: start a second server for the other project"
38590
+ ]);
38591
+ }
38459
38592
  try {
38460
38593
  return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
38461
38594
  } catch (error) {
@@ -38474,8 +38607,8 @@ class GodotServer {
38474
38607
  return this.createErrorResponse(choice.problem);
38475
38608
  }
38476
38609
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
38477
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join16(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
38478
- const screenshotPath = screenshotDir ? join16(screenshotDir, "capture.png") : null;
38610
+ const screenshotDir = expectsScreenshot ? mkdtempSync3(join17(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
38611
+ const screenshotPath = screenshotDir ? join17(screenshotDir, "capture.png") : null;
38479
38612
  try {
38480
38613
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
38481
38614
  if (!reply.ok) {
@@ -38496,7 +38629,7 @@ class GodotServer {
38496
38629
  return {
38497
38630
  content: [
38498
38631
  { type: "text", text: `Screenshot captured: ${dimensions}` },
38499
- { type: "image", data: readFileSync14(screenshotPath).toString("base64"), mimeType: "image/png" }
38632
+ { type: "image", data: readFileSync15(screenshotPath).toString("base64"), mimeType: "image/png" }
38500
38633
  ]
38501
38634
  };
38502
38635
  } finally {
@@ -38564,13 +38697,14 @@ async function runGodotServer() {
38564
38697
  const server = new GodotServer;
38565
38698
  await server.run();
38566
38699
  }
38567
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run6, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, SLOWEST_FRAME_RATE = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS, RUNNER_DIRECTORY = "addons/gdUnit4/";
38700
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run6, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, SLOWEST_FRAME_RATE = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
38568
38701
  var init_server3 = __esm(() => {
38569
38702
  init_mcp();
38570
38703
  init_stdio2();
38571
38704
  init_types();
38572
38705
  init_bridge_announce();
38573
38706
  init_class_cache();
38707
+ init_config_pin();
38574
38708
  init_dap_client();
38575
38709
  init_errors();
38576
38710
  init_game_log();
@@ -38617,7 +38751,7 @@ var init_server3 = __esm(() => {
38617
38751
  output: "dap_get_output",
38618
38752
  variables: "dap_get_variables"
38619
38753
  });
38620
- PROJECT_INFO_SECTIONS = dictionary({
38754
+ PROJECT_INFO_SECTIONS2 = dictionary({
38621
38755
  autoloads: { operation: "list_autoloads", params: () => ({}) },
38622
38756
  plugins: { operation: "list_plugins", params: () => ({}) },
38623
38757
  export_presets: { operation: "list_export_presets", params: () => ({}) },
@@ -38631,8 +38765,8 @@ var init_server3 = __esm(() => {
38631
38765
  });
38632
38766
 
38633
38767
  // src/cli.ts
38634
- import { existsSync as existsSync14 } from "fs";
38635
- import { join as join17, resolve as resolve5 } from "path";
38768
+ import { existsSync as existsSync15 } from "fs";
38769
+ import { join as join18, resolve as resolve5 } from "path";
38636
38770
 
38637
38771
  // src/errors.ts
38638
38772
  class Refusal extends Error {
@@ -39969,6 +40103,11 @@ function headlessSection() {
39969
40103
  "",
39970
40104
  "Everything else needs the editor (`editor_*`, `debug_*`, `scene_*`, `resource_*`) or a running",
39971
40105
  "game (`runtime_*`).",
40106
+ "",
40107
+ 'One of these can be pointed the other way: `project_settings get` takes `from: "editor"` to ask',
40108
+ "the open editor instead of the file, which is a different reading and costs no engine start. A",
40109
+ "gate leaves it alone, because there is no editor on a build machine and the call is refused",
40110
+ "rather than answered from the file.",
39972
40111
  ""
39973
40112
  ];
39974
40113
  }
@@ -40237,7 +40376,7 @@ class UsageError extends Refusal {
40237
40376
  function projectArgument(at) {
40238
40377
  const named = args.slice(at).find((value) => !value.startsWith("--"));
40239
40378
  const projectPath = resolve5(named ?? ".");
40240
- if (!existsSync14(join17(projectPath, "project.godot"))) {
40379
+ if (!existsSync15(join18(projectPath, "project.godot"))) {
40241
40380
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
40242
40381
  }
40243
40382
  return projectPath;
@@ -40271,7 +40410,7 @@ async function registerRuntime(godot, projectPath) {
40271
40410
  return;
40272
40411
  }
40273
40412
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
40274
- if (!existsSync14(join17(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
40413
+ if (!existsSync15(join18(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
40275
40414
  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}`);
40276
40415
  }
40277
40416
  }
@@ -40403,7 +40542,7 @@ async function setup() {
40403
40542
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
40404
40543
  }
40405
40544
  if (!args.includes("--no-skill")) {
40406
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync14);
40545
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync15);
40407
40546
  for (const written of writeSkill(directories, getLocalVersion())) {
40408
40547
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
40409
40548
  }
@@ -40492,7 +40631,7 @@ async function upgrade() {
40492
40631
  if (already.length === 0) {
40493
40632
  console.log("no harness config names gdharness, so none was re-pinned");
40494
40633
  }
40495
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync14), version)) {
40634
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync15), version)) {
40496
40635
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
40497
40636
  }
40498
40637
  console.log(`