gdharness 1.1.8 → 1.1.10

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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @bun
3
2
  import { createRequire } from "node:module";
4
3
  var __create = Object.create;
5
4
  var __getProtoOf = Object.getPrototypeOf;
@@ -272,6 +271,83 @@ var init_process_children = __esm(() => {
272
271
  run = promisify(execFile);
273
272
  });
274
273
 
274
+ // src/bridge-announce.ts
275
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
276
+ import { dirname, join } from "node:path";
277
+ function announcementPath(projectPath) {
278
+ return join(projectPath, ".godot", "gdharness-bridge.json");
279
+ }
280
+ function readAnnouncement(path) {
281
+ try {
282
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
283
+ if (typeof parsed !== "object" || parsed === null) {
284
+ return null;
285
+ }
286
+ const said = parsed;
287
+ if (typeof said.port !== "number" || typeof said.pid !== "number") {
288
+ return null;
289
+ }
290
+ return said;
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ function serverBehind(announcement, startedAt) {
296
+ if (announcement === null || typeof announcement.version !== "string" || startedAt === undefined) {
297
+ return null;
298
+ }
299
+ const announcedAt = Date.parse(announcement.startedAt);
300
+ if (Number.isNaN(announcedAt) || startedAt > announcedAt + 1000) {
301
+ return null;
302
+ }
303
+ return { version: announcement.version, pid: announcement.pid };
304
+ }
305
+ function serverServing(projectPath) {
306
+ const announcement = readAnnouncement(announcementPath(projectPath));
307
+ if (announcement === null) {
308
+ return null;
309
+ }
310
+ return serverBehind(announcement, startTimesOf([announcement.pid]).get(announcement.pid));
311
+ }
312
+ function announcementIsOurs(path) {
313
+ return readAnnouncement(path)?.pid === process.pid;
314
+ }
315
+ function announceBridge(projectPath, bridge) {
316
+ const path = announcementPath(projectPath);
317
+ try {
318
+ mkdirSync(dirname(path), { recursive: true });
319
+ const announcement = {
320
+ protocol: BRIDGE_ANNOUNCE_PROTOCOL,
321
+ host: bridge.host,
322
+ port: bridge.port,
323
+ pid: process.pid,
324
+ version: bridge.version,
325
+ startedAt: new Date().toISOString()
326
+ };
327
+ writeFileSync(path, `${JSON.stringify(announcement, null, 2)}
328
+ `, "utf8");
329
+ return path;
330
+ } catch (error) {
331
+ console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
332
+ return null;
333
+ }
334
+ }
335
+ function withdrawBridge(path) {
336
+ if (path === null || !announcementIsOurs(path)) {
337
+ return;
338
+ }
339
+ try {
340
+ rmSync(path, { force: true });
341
+ } catch (error) {
342
+ console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
343
+ }
344
+ }
345
+ var BRIDGE_ANNOUNCE_PROTOCOL = 1;
346
+ var init_bridge_announce = __esm(() => {
347
+ init_errors();
348
+ init_process_children();
349
+ });
350
+
275
351
  // src/detection.ts
276
352
  import { existsSync, readdirSync, statSync } from "node:fs";
277
353
  import { homedir } from "node:os";
@@ -421,6 +497,84 @@ function environmentFor(options, variables = process.env) {
421
497
  }
422
498
  var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER", RESERVED_VARIABLE_PREFIX = "GDHARNESS_", SAVES_NOT_MOVED_NOTE = "On macOS the engine reads user:// off HOME and ignores the variables that move it elsewhere, measured on Godot 4.7.2, so this run wrote its saves where the player keeps theirs. A suite that saves a game has written into the same folder as the copy being played, and one that tidies up after itself may have removed real saves.";
423
499
 
500
+ // src/godot-path.ts
501
+ import { execFile as execFile2 } from "node:child_process";
502
+ import { existsSync as existsSync2 } from "node:fs";
503
+ import { normalize } from "node:path";
504
+ import { promisify as promisify2 } from "node:util";
505
+
506
+ class GodotLocator {
507
+ answered = new Map;
508
+ found = null;
509
+ async find() {
510
+ if (this.found !== null && await this.whyNot(this.found) === null) {
511
+ return { ok: true, path: this.found };
512
+ }
513
+ this.found = null;
514
+ const named = envValue("GODOT_PATH");
515
+ if (named !== undefined) {
516
+ const path = normalize(named);
517
+ const reason = await this.whyNot(path);
518
+ if (reason === null) {
519
+ this.found = path;
520
+ return { ok: true, path };
521
+ }
522
+ return { ok: false, named: path, reason };
523
+ }
524
+ for (const candidate of godotCandidates(process.platform, resolveHomeDirectory())) {
525
+ const path = normalize(candidate);
526
+ if (await this.whyNot(path) === null) {
527
+ this.found = path;
528
+ return { ok: true, path };
529
+ }
530
+ }
531
+ return { ok: false, named: null };
532
+ }
533
+ static refusal(located) {
534
+ if (located.named === null) {
535
+ return {
536
+ message: "No Godot executable found.",
537
+ advice: [
538
+ "Set GODOT_PATH to the Godot 4 executable",
539
+ "Or install Godot where it is usually found, such as /usr/bin/godot or C:\\Program Files\\Godot"
540
+ ]
541
+ };
542
+ }
543
+ return {
544
+ message: `GODOT_PATH is set to ${located.named}, which ${located.reason ?? "does not answer"}.`,
545
+ advice: [
546
+ "Point GODOT_PATH at a Godot 4 executable that answers --version",
547
+ "Or unset it, and the usual install locations are searched instead"
548
+ ]
549
+ };
550
+ }
551
+ async whyNot(path) {
552
+ const known = this.answered.get(path);
553
+ if (known !== undefined) {
554
+ return known;
555
+ }
556
+ let reason;
557
+ if (path !== "godot" && !existsSync2(path)) {
558
+ reason = "does not exist";
559
+ } else {
560
+ try {
561
+ await run2(path, ["--version"]);
562
+ reason = null;
563
+ } catch (error) {
564
+ reason = `does not answer --version: ${errorMessage(error)}`;
565
+ }
566
+ }
567
+ this.answered.set(path, reason);
568
+ return reason;
569
+ }
570
+ }
571
+ var run2;
572
+ var init_godot_path = __esm(() => {
573
+ init_detection();
574
+ init_errors();
575
+ run2 = promisify2(execFile2);
576
+ });
577
+
424
578
  // node_modules/yaml/dist/nodes/identity.js
425
579
  var require_identity = __commonJS(function(exports) {
426
580
  var ALIAS = Symbol.for("yaml.alias");
@@ -7502,19 +7656,21 @@ class GameLog {
7502
7656
  this.seen[severity] = this.entries.length;
7503
7657
  }
7504
7658
  }
7505
- const matching = this.entries.slice(floor).filter((entry) => {
7506
- if (!wanted.has(entry.severity)) {
7507
- return false;
7508
- }
7509
- if (needle === undefined) {
7510
- return true;
7511
- }
7512
- return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
7513
- });
7659
+ const matching = this.entries.slice(floor).filter((entry) => answersTo(entry, options.severity, options.contains));
7514
7660
  const omitted = Math.max(0, matching.length - options.limit);
7515
7661
  return { entries: matching.slice(omitted), omitted };
7516
7662
  }
7517
7663
  }
7664
+ function answersTo(entry, severity, contains) {
7665
+ if (!ADMITTED[severity].has(entry.severity)) {
7666
+ return false;
7667
+ }
7668
+ if (contains === undefined) {
7669
+ return true;
7670
+ }
7671
+ const needle = contains.toLowerCase();
7672
+ return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
7673
+ }
7518
7674
  var HEADLINE, COLOUR_CODE, STREAMS, ADMITTED;
7519
7675
  var init_game_log = __esm(() => {
7520
7676
  HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
@@ -7553,6 +7709,127 @@ var init_scratch = __esm(() => {
7553
7709
  SWEPT = { recursive: true, force: true, maxRetries: 20, retryDelay: 250 };
7554
7710
  });
7555
7711
 
7712
+ // src/headless.ts
7713
+ import { execFile as execFile3 } from "node:child_process";
7714
+ import { mkdtempSync, writeFileSync as writeFileSync3 } from "node:fs";
7715
+ import { tmpdir } from "node:os";
7716
+ import { join as join5 } from "node:path";
7717
+ import { promisify as promisify3 } from "node:util";
7718
+ function snakeCased(params) {
7719
+ const result = emptyRecord();
7720
+ for (const [key, value] of Object.entries(params)) {
7721
+ const name = key.startsWith("_") ? key : key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
7722
+ result[name] = value;
7723
+ }
7724
+ return result;
7725
+ }
7726
+ function lastJsonObject(stdout) {
7727
+ const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{"));
7728
+ for (const line of lines.reverse()) {
7729
+ try {
7730
+ const parsed = JSON.parse(line);
7731
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
7732
+ return parsed;
7733
+ }
7734
+ } catch {}
7735
+ }
7736
+ return null;
7737
+ }
7738
+ function problems(stderr) {
7739
+ const log = new GameLog;
7740
+ log.append("stderr", stderr);
7741
+ log.finish();
7742
+ return log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
7743
+ }
7744
+ function reason(stdout, stderr) {
7745
+ const own = stderr.split(/\r?\n/).filter((line) => line.startsWith("[ERROR] ")).map((line) => line.slice("[ERROR] ".length));
7746
+ if (own.length > 0) {
7747
+ return own.join("; ");
7748
+ }
7749
+ const engine = problems(stderr).filter((entry) => entry.severity === "error").map((entry) => entry.text);
7750
+ if (engine.length > 0) {
7751
+ return engine.join("; ");
7752
+ }
7753
+ return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
7754
+ }
7755
+ async function runImport(godotPath, projectPath) {
7756
+ const logDir = mkdtempSync(join5(tmpdir(), "gdharness-import-"));
7757
+ try {
7758
+ const { stderr } = await run3(godotPath, ["--headless", "--log-file", join5(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
7759
+ return { ok: true, messages: problems(stderr) };
7760
+ } catch (error) {
7761
+ if (error instanceof Error && "stdout" in error && "stderr" in error) {
7762
+ const failed = error;
7763
+ return {
7764
+ ok: false,
7765
+ message: `the import pass failed (exit ${failed.code ?? "unknown"}): ${reason(failed.stdout, failed.stderr)}`,
7766
+ messages: problems(failed.stderr)
7767
+ };
7768
+ }
7769
+ return {
7770
+ ok: false,
7771
+ message: `the import pass could not be run: ${error instanceof Error ? error.message : String(error)}`,
7772
+ messages: []
7773
+ };
7774
+ } finally {
7775
+ discard(logDir);
7776
+ }
7777
+ }
7778
+ async function runOperation(engine, operation, params, projectPath) {
7779
+ const paramsDir = mkdtempSync(join5(tmpdir(), "gdharness-params-"));
7780
+ const paramsFile = join5(paramsDir, `${operation}.json`);
7781
+ writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
7782
+ const args = [
7783
+ "--headless",
7784
+ "--log-file",
7785
+ join5(paramsDir, "engine.log"),
7786
+ "--path",
7787
+ projectPath,
7788
+ "--script",
7789
+ engine.script,
7790
+ operation,
7791
+ `@file:${paramsFile}`,
7792
+ ...engine.debug ? ["--debug-godot"] : []
7793
+ ];
7794
+ let stdout;
7795
+ let stderr;
7796
+ try {
7797
+ ({ stdout, stderr } = await run3(engine.godotPath, args, { signal: callSignal() }));
7798
+ } catch (error) {
7799
+ if (error instanceof Error && "stdout" in error && "stderr" in error) {
7800
+ const failed = error;
7801
+ return {
7802
+ ok: false,
7803
+ message: `${operation} failed (exit ${failed.code ?? "unknown"}): ${reason(failed.stdout, failed.stderr)}`,
7804
+ messages: problems(failed.stderr)
7805
+ };
7806
+ }
7807
+ return {
7808
+ ok: false,
7809
+ message: `${operation} could not be run: ${error instanceof Error ? error.message : String(error)}`,
7810
+ messages: []
7811
+ };
7812
+ } finally {
7813
+ discard(paramsDir);
7814
+ }
7815
+ const payload = lastJsonObject(stdout);
7816
+ if (payload === null) {
7817
+ return {
7818
+ ok: false,
7819
+ message: `${operation} produced no result: ${reason(stdout, stderr)}`,
7820
+ messages: problems(stderr)
7821
+ };
7822
+ }
7823
+ return { ok: true, payload, messages: problems(stderr) };
7824
+ }
7825
+ var run3;
7826
+ var init_headless = __esm(() => {
7827
+ init_call_signal();
7828
+ init_game_log();
7829
+ init_scratch();
7830
+ run3 = promisify3(execFile3);
7831
+ });
7832
+
7556
7833
  // src/update-check.ts
7557
7834
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
7558
7835
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
@@ -7797,8 +8074,132 @@ var init_server_version = __esm(() => {
7797
8074
  })();
7798
8075
  });
7799
8076
 
8077
+ // src/issues.ts
8078
+ import { createHash } from "node:crypto";
8079
+ import process4 from "node:process";
8080
+ function feedbackNotice() {
8081
+ return JSON.stringify({
8082
+ gdharness_feedback: {
8083
+ what_to_do: "If this session has run into something gdharness cannot do, or a tool that should " + "exist and does not, tell the user what is missing and offer to open an enhancement " + "issue for them. Only if they say yes, and only for a real gap: a call you got wrong " + "is not one. If you cannot open an issue yourself, give them the link and what to " + "write in it.",
8084
+ enhancement_url: ENHANCEMENT_URL
8085
+ }
8086
+ }, null, 2);
8087
+ }
8088
+ function runtime() {
8089
+ const versions = process4.versions;
8090
+ const bun = versions["bun"];
8091
+ return bun === undefined ? `node ${process4.versions.node}` : `bun ${bun}`;
8092
+ }
8093
+ function defectSignature(where, message) {
8094
+ const normalised = `${where}
8095
+ ${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
8096
+ return createHash("sha256").update(normalised).digest("hex").slice(0, 8);
8097
+ }
8098
+ function facts(where, message, godotVersion) {
8099
+ return [
8100
+ ["Where", where],
8101
+ ["Error", message],
8102
+ ["Version", `gdharness ${SERVER_VERSION}`],
8103
+ ["Runtime", `${runtime()}, ${process4.platform} ${process4.arch}`],
8104
+ ["Godot", godotVersion ?? "not known at the point this failed"],
8105
+ ["Signature", defectSignature(where, message)]
8106
+ ];
8107
+ }
8108
+ function filledTemplate(where, message, godotVersion) {
8109
+ return [
8110
+ `**gdharness version**: ${SERVER_VERSION}`,
8111
+ `**Godot version**: ${godotVersion ?? ""}`,
8112
+ `**Bun version**: ${runtime()}`,
8113
+ `**OS**: ${process4.platform} ${process4.arch}`,
8114
+ "**MCP client**:",
8115
+ "",
8116
+ "## What you did",
8117
+ "",
8118
+ where,
8119
+ "",
8120
+ "## What you expected",
8121
+ "",
8122
+ "The call to answer, or to refuse with a reason.",
8123
+ "",
8124
+ "## What happened",
8125
+ "",
8126
+ "gdharness failed in a way it does not model.",
8127
+ "",
8128
+ "```text",
8129
+ message,
8130
+ "```",
8131
+ "",
8132
+ `Signature: \`${defectSignature(where, message)}\``,
8133
+ "",
8134
+ "If the real cause turned out to be the project, the environment or the call, say so here:",
8135
+ "reaching you as a defect rather than as a refusal naming what would have worked is then the",
8136
+ "thing to fix.",
8137
+ "",
8138
+ "## Anything that makes it reproducible",
8139
+ ""
8140
+ ].join(`
8141
+ `);
8142
+ }
8143
+
8144
+ class DefectsSeen {
8145
+ counts = new Map;
8146
+ before(where, message) {
8147
+ const signature = defectSignature(where, message);
8148
+ const seen = this.counts.get(signature) ?? 0;
8149
+ this.counts.set(signature, seen + 1);
8150
+ return seen;
8151
+ }
8152
+ }
8153
+ function defectReport(where, error, context = {}) {
8154
+ const { godotVersion, seenBefore = 0 } = context;
8155
+ const message = errorMessage(error);
8156
+ const title = `Defect ${defectSignature(where, message)}: ${where}`;
8157
+ const url = `${NEW_ISSUE}?template=bug_report.md&title=${encodeURIComponent(title)}` + `&body=${encodeURIComponent(filledTemplate(where, message, godotVersion))}`;
8158
+ const width = Math.max(...facts(where, message, godotVersion).map(([label]) => (label ?? "").length));
8159
+ return [
8160
+ "gdharness failed in a way it does not model. Whatever went wrong underneath, it reached you",
8161
+ "as a crash rather than as a refusal naming what would have worked, and that much is a defect",
8162
+ "in gdharness however the rest of it turns out.",
8163
+ "",
8164
+ ...seenBefore === 0 ? [
8165
+ "Send the call once more before anything else. Some of what reaches this line is a file",
8166
+ "another process had open, or a connection that dropped, and those are gone by the next",
8167
+ "attempt. If the same Signature comes back, it is not one of those, and repeating it",
8168
+ "further only spends turns."
8169
+ ] : [
8170
+ `You have had this exact failure ${seenBefore === 1 ? "once" : `${seenBefore} times`} in this session already, so it is a`,
8171
+ "state rather than a passing one, and sending the call again will not change it."
8172
+ ],
8173
+ "",
8174
+ ...facts(where, message, godotVersion).map(([label, value]) => ` ${(label ?? "").padEnd(width)} ${value ?? ""}`),
8175
+ "",
8176
+ "Do not open an issue on your own initiative. Tell the person whose machine this is what",
8177
+ "broke, and ask whether you may report it to gdharness on their behalf. What the report",
8178
+ "carries is the lines above and nothing else, and whether that goes into a public tracker",
8179
+ "is theirs to decide.",
8180
+ "",
8181
+ "If they say yes, this link carries the report already filled in:",
8182
+ url,
8183
+ "",
8184
+ "If they say yes and you have no way to open an issue yourself, give them that link and",
8185
+ "those lines, and stay with them while they file it.",
8186
+ "",
8187
+ "Worth reporting either way. If you can see that what actually went wrong was the project,",
8188
+ "the environment or the call, then this message is the defect: a failure gdharness knows",
8189
+ "about is meant to arrive as a refusal naming what would have worked, not as this. Say that",
8190
+ "in the report and it is the more useful of the two."
8191
+ ].join(`
8192
+ `);
8193
+ }
8194
+ var NEW_ISSUE = "https://github.com/Aureliolo/gdharness/issues/new", ENHANCEMENT_URL;
8195
+ var init_issues = __esm(() => {
8196
+ init_errors();
8197
+ init_server_version();
8198
+ ENHANCEMENT_URL = `${NEW_ISSUE}?template=feature_request.md`;
8199
+ });
8200
+
7800
8201
  // src/class-cache.ts
7801
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
8202
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
7802
8203
  import { join as join7 } from "node:path";
7803
8204
  function declaredClasses(projectPath) {
7804
8205
  const declared = new Map;
@@ -7814,7 +8215,7 @@ function declaredClasses(projectPath) {
7814
8215
  if (entry.isDirectory()) {
7815
8216
  visit(path, `${prefix}${entry.name}/`);
7816
8217
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
7817
- const found = DECLARATION.exec(readFileSync6(path, "utf8"));
8218
+ const found = DECLARATION.exec(readFileSync5(path, "utf8"));
7818
8219
  if (found?.[1]) {
7819
8220
  declared.set(found[1], `res://${prefix}${entry.name}`);
7820
8221
  }
@@ -7830,7 +8231,7 @@ function cachedClasses(projectPath) {
7830
8231
  return null;
7831
8232
  }
7832
8233
  const listed = new Map;
7833
- const text = readFileSync6(cache, "utf8");
8234
+ const text = readFileSync5(cache, "utf8");
7834
8235
  for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
7835
8236
  listed.set(entry[1] ?? "", entry[2] ?? "");
7836
8237
  }
@@ -7982,127 +8383,6 @@ var init_class_cache = __esm(() => {
7982
8383
  DECLARATION = /^(?:@[a-z_]+(?:\([^)\n]*\))?\s+)*class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m;
7983
8384
  });
7984
8385
 
7985
- // src/headless.ts
7986
- import { execFile as execFile4 } from "node:child_process";
7987
- import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync5 } from "node:fs";
7988
- import { tmpdir as tmpdir3 } from "node:os";
7989
- import { join as join8 } from "node:path";
7990
- import { promisify as promisify4 } from "node:util";
7991
- function snakeCased2(params) {
7992
- const result = emptyRecord();
7993
- for (const [key, value] of Object.entries(params)) {
7994
- const name = key.startsWith("_") ? key : key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
7995
- result[name] = value;
7996
- }
7997
- return result;
7998
- }
7999
- function lastJsonObject2(stdout) {
8000
- const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{"));
8001
- for (const line of lines.reverse()) {
8002
- try {
8003
- const parsed = JSON.parse(line);
8004
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
8005
- return parsed;
8006
- }
8007
- } catch {}
8008
- }
8009
- return null;
8010
- }
8011
- function problems2(stderr) {
8012
- const log = new GameLog;
8013
- log.append("stderr", stderr);
8014
- log.finish();
8015
- return log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
8016
- }
8017
- function reason2(stdout, stderr) {
8018
- const own = stderr.split(/\r?\n/).filter((line) => line.startsWith("[ERROR] ")).map((line) => line.slice("[ERROR] ".length));
8019
- if (own.length > 0) {
8020
- return own.join("; ");
8021
- }
8022
- const engine = problems2(stderr).filter((entry) => entry.severity === "error").map((entry) => entry.text);
8023
- if (engine.length > 0) {
8024
- return engine.join("; ");
8025
- }
8026
- return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
8027
- }
8028
- async function runImport(godotPath, projectPath) {
8029
- const logDir = mkdtempSync2(join8(tmpdir3(), "gdharness-import-"));
8030
- try {
8031
- const { stderr } = await run4(godotPath, ["--headless", "--log-file", join8(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
8032
- return { ok: true, messages: problems2(stderr) };
8033
- } catch (error) {
8034
- if (error instanceof Error && "stdout" in error && "stderr" in error) {
8035
- const failed = error;
8036
- return {
8037
- ok: false,
8038
- message: `the import pass failed (exit ${failed.code ?? "unknown"}): ${reason2(failed.stdout, failed.stderr)}`,
8039
- messages: problems2(failed.stderr)
8040
- };
8041
- }
8042
- return {
8043
- ok: false,
8044
- message: `the import pass could not be run: ${error instanceof Error ? error.message : String(error)}`,
8045
- messages: []
8046
- };
8047
- } finally {
8048
- discard(logDir);
8049
- }
8050
- }
8051
- async function runOperation2(engine, operation, params, projectPath) {
8052
- const paramsDir = mkdtempSync2(join8(tmpdir3(), "gdharness-params-"));
8053
- const paramsFile = join8(paramsDir, `${operation}.json`);
8054
- writeFileSync5(paramsFile, JSON.stringify(snakeCased2(params)), "utf8");
8055
- const args = [
8056
- "--headless",
8057
- "--log-file",
8058
- join8(paramsDir, "engine.log"),
8059
- "--path",
8060
- projectPath,
8061
- "--script",
8062
- engine.script,
8063
- operation,
8064
- `@file:${paramsFile}`,
8065
- ...engine.debug ? ["--debug-godot"] : []
8066
- ];
8067
- let stdout;
8068
- let stderr;
8069
- try {
8070
- ({ stdout, stderr } = await run4(engine.godotPath, args, { signal: callSignal() }));
8071
- } catch (error) {
8072
- if (error instanceof Error && "stdout" in error && "stderr" in error) {
8073
- const failed = error;
8074
- return {
8075
- ok: false,
8076
- message: `${operation} failed (exit ${failed.code ?? "unknown"}): ${reason2(failed.stdout, failed.stderr)}`,
8077
- messages: problems2(failed.stderr)
8078
- };
8079
- }
8080
- return {
8081
- ok: false,
8082
- message: `${operation} could not be run: ${error instanceof Error ? error.message : String(error)}`,
8083
- messages: []
8084
- };
8085
- } finally {
8086
- discard(paramsDir);
8087
- }
8088
- const payload = lastJsonObject2(stdout);
8089
- if (payload === null) {
8090
- return {
8091
- ok: false,
8092
- message: `${operation} produced no result: ${reason2(stdout, stderr)}`,
8093
- messages: problems2(stderr)
8094
- };
8095
- }
8096
- return { ok: true, payload, messages: problems2(stderr) };
8097
- }
8098
- var run4;
8099
- var init_headless = __esm(() => {
8100
- init_call_signal();
8101
- init_game_log();
8102
- init_scratch();
8103
- run4 = promisify4(execFile4);
8104
- });
8105
-
8106
8386
  // node_modules/zod/v4/core/util.js
8107
8387
  function getEnumValues(entries) {
8108
8388
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -15268,7 +15548,7 @@ var init_paths = __esm(() => {
15268
15548
  });
15269
15549
 
15270
15550
  // src/resources.ts
15271
- import { readFileSync as readFileSync7 } from "node:fs";
15551
+ import { readFileSync as readFileSync6 } from "node:fs";
15272
15552
  import { extname, resolve as resolve2 } from "node:path";
15273
15553
  function ensureProjectPath(getProjectPath) {
15274
15554
  const projectPath = getProjectPath();
@@ -15434,7 +15714,7 @@ function readResourceText(uri, getProjectPath) {
15434
15714
  const parsedUri = parseGodotUri(uri);
15435
15715
  if (parsedUri.kind === "project-info") {
15436
15716
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
15437
- const rawProject = readFileSync7(projectFilePath, "utf-8");
15717
+ const rawProject = readFileSync6(projectFilePath, "utf-8");
15438
15718
  const parsedProject = parseProjectGodot(rawProject);
15439
15719
  return {
15440
15720
  mimeType: "application/json",
@@ -15443,7 +15723,7 @@ function readResourceText(uri, getProjectPath) {
15443
15723
  }
15444
15724
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
15445
15725
  ensureAllowedExtension(parsedUri.kind, filePath);
15446
- const text = readFileSync7(filePath, "utf-8");
15726
+ const text = readFileSync6(filePath, "utf-8");
15447
15727
  return {
15448
15728
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
15449
15729
  text
@@ -15571,74 +15851,250 @@ function readParams(params, key) {
15571
15851
  }
15572
15852
 
15573
15853
  // src/setup.ts
15574
- import { createHash as createHash3 } from "node:crypto";
15854
+ import { spawnSync as spawnSync2 } from "node:child_process";
15855
+ import { createHash as createHash2 } from "node:crypto";
15575
15856
  import {
15576
- cpSync as cpSync2,
15577
- existsSync as existsSync8,
15578
- mkdirSync as mkdirSync5,
15579
- readdirSync as readdirSync4,
15580
- readFileSync as readFileSync9,
15581
- rmSync as rmSync5,
15582
- statSync as statSync4,
15583
- writeFileSync as writeFileSync7
15857
+ cpSync,
15858
+ existsSync as existsSync7,
15859
+ mkdirSync as mkdirSync4,
15860
+ readdirSync as readdirSync3,
15861
+ readFileSync as readFileSync7,
15862
+ rmdirSync,
15863
+ rmSync as rmSync4,
15864
+ statSync as statSync3,
15865
+ writeFileSync as writeFileSync5
15584
15866
  } from "node:fs";
15585
- import { dirname as dirname5, join as join10 } from "node:path";
15586
- import { fileURLToPath as fileURLToPath2 } from "node:url";
15587
- function editorAddonDigest2(from = shippedAddonsDirectory2()) {
15588
- const hash = createHash3("sha256");
15867
+ import { dirname as dirname4, join as join8 } from "node:path";
15868
+ import { fileURLToPath } from "node:url";
15869
+ function editorAddonDigest(from = shippedAddonsDirectory()) {
15870
+ const hash = createHash2("sha256");
15589
15871
  const walk = (directory, relative) => {
15590
- for (const name of readdirSync4(directory).sort()) {
15591
- if (name === VERSION_MARKER2 || name === DIGEST_MARKER2) {
15872
+ for (const name of readdirSync3(directory).sort()) {
15873
+ if (name === VERSION_MARKER || name === DIGEST_MARKER) {
15592
15874
  continue;
15593
15875
  }
15594
- const path = join10(directory, name);
15876
+ const path = join8(directory, name);
15595
15877
  const inside = `${relative}/${name}`;
15596
- if (statSync4(path).isDirectory()) {
15878
+ if (statSync3(path).isDirectory()) {
15597
15879
  walk(path, inside);
15598
15880
  } else {
15599
15881
  hash.update(`${inside}
15600
15882
  `);
15601
- hash.update(readFileSync9(path));
15883
+ hash.update(readFileSync7(path));
15602
15884
  hash.update(`
15603
15885
  `);
15604
15886
  }
15605
15887
  }
15606
15888
  };
15607
- for (const name of EDITOR_PLUGINS2) {
15608
- walk(join10(from, name), name);
15889
+ for (const name of EDITOR_PLUGINS) {
15890
+ walk(join8(from, name), name);
15609
15891
  }
15610
15892
  return hash.digest("hex");
15611
15893
  }
15612
15894
  function shippedEditorDigest() {
15613
15895
  if (shippedDigest === undefined) {
15614
15896
  try {
15615
- shippedDigest = editorAddonDigest2();
15897
+ shippedDigest = editorAddonDigest();
15616
15898
  } catch {
15617
15899
  shippedDigest = null;
15618
15900
  }
15619
15901
  }
15620
15902
  return shippedDigest ?? undefined;
15621
15903
  }
15622
- function shippedAddonsDirectory2() {
15623
- return join10(dirname5(fileURLToPath2(import.meta.url)), "godot", "addons");
15904
+ function installedEditorDigest(projectPath) {
15905
+ const marker = join8(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
15906
+ if (!existsSync7(marker)) {
15907
+ return null;
15908
+ }
15909
+ const said = readFileSync7(marker, "utf8").trim();
15910
+ return said === "" ? null : said;
15911
+ }
15912
+ function shippedAddonsDirectory() {
15913
+ return join8(dirname4(fileURLToPath(import.meta.url)), "godot", "addons");
15914
+ }
15915
+ function shippedOperationsScript() {
15916
+ return join8(dirname4(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
15917
+ }
15918
+ function installAddons(projectPath, from = shippedAddonsDirectory()) {
15919
+ const installed = [];
15920
+ for (const name of ADDONS) {
15921
+ const source = join8(from, name);
15922
+ if (!existsSync7(join8(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
15923
+ throw new Error(`The package holds no ${name} addon at ${source}.`);
15924
+ }
15925
+ const target = join8(projectPath, "addons", name);
15926
+ const replaced = existsSync7(target);
15927
+ rmSync4(target, { recursive: true, force: true });
15928
+ mkdirSync4(dirname4(target), { recursive: true });
15929
+ cpSync(source, target, { recursive: true });
15930
+ writeFileSync5(join8(target, VERSION_MARKER), `${SERVER_VERSION}
15931
+ `);
15932
+ installed.push({ name, path: target, replaced });
15933
+ }
15934
+ writeFileSync5(join8(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
15935
+ `);
15936
+ return installed;
15624
15937
  }
15625
15938
  function installedAddonVersion(projectPath) {
15626
- const marker = join10(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
15627
- if (!existsSync8(marker)) {
15939
+ const marker = join8(projectPath, "addons", ADDONS[0], VERSION_MARKER);
15940
+ if (!existsSync7(marker)) {
15628
15941
  return null;
15629
15942
  }
15630
- const said = readFileSync9(marker, "utf8").trim();
15943
+ const said = readFileSync7(marker, "utf8").trim();
15631
15944
  return said === "" ? null : said;
15632
15945
  }
15633
- var ADDONS2, EDITOR_PLUGINS2, RUNTIME_AUTOLOAD2, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER2 = ".gdharness-version", DIGEST_MARKER2 = ".gdharness-digest", shippedDigest;
15946
+ function removeAddons(projectPath) {
15947
+ const removed = [];
15948
+ for (const name of ADDONS) {
15949
+ const target = join8(projectPath, "addons", name);
15950
+ if (existsSync7(target)) {
15951
+ rmSync4(target, { recursive: true, force: true });
15952
+ removed.push(target);
15953
+ }
15954
+ }
15955
+ const addons = join8(projectPath, "addons");
15956
+ if (removed.length > 0 && existsSync7(addons) && readdirSync3(addons).length === 0) {
15957
+ rmdirSync(addons);
15958
+ removed.push(addons);
15959
+ }
15960
+ return removed;
15961
+ }
15962
+ async function disablePlugins(engine, projectPath, names) {
15963
+ const outcomes = [];
15964
+ for (const name of names) {
15965
+ outcomes.push(await runOperation(engine, "disable_plugin", { pluginName: name }, projectPath));
15966
+ }
15967
+ return outcomes;
15968
+ }
15969
+ async function enablePlugins(engine, projectPath, names) {
15970
+ const outcomes = [];
15971
+ for (const name of names) {
15972
+ outcomes.push(await runOperation(engine, "enable_plugin", { pluginName: name }, projectPath));
15973
+ }
15974
+ return outcomes;
15975
+ }
15976
+ function autoloadIsOurs(named) {
15977
+ return named === null || named === `res://${RUNTIME_AUTOLOAD.path}`;
15978
+ }
15979
+ async function setRuntime(engine, projectPath, on) {
15980
+ return on ? await runOperation(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`, enabled: true }, projectPath) : await runOperation(engine, "remove_autoload", { name: RUNTIME_AUTOLOAD.name }, projectPath);
15981
+ }
15982
+ function loaderAmong(projectPath, named) {
15983
+ let byName = null;
15984
+ for (const [entry, path] of named) {
15985
+ if (entry === RUNTIME_AUTOLOAD.name || path === RUNTIME_AUTOLOAD.path) {
15986
+ continue;
15987
+ }
15988
+ let source = null;
15989
+ try {
15990
+ source = readFileSync7(join8(projectPath, path), "utf8");
15991
+ } catch {}
15992
+ if (source?.includes(RUNTIME_AUTOLOAD.path)) {
15993
+ return { name: entry, path, exists: true };
15994
+ }
15995
+ byName ??= path.toLowerCase().includes("gdharness") ? { name: entry, path, exists: source !== null } : null;
15996
+ }
15997
+ return byName;
15998
+ }
15999
+ function autoloadPaths(settings) {
16000
+ const named = new Map;
16001
+ for (const [name, value] of Object.entries(settings["autoload"] ?? {})) {
16002
+ const said = typeof value === "string" ? value.replace(/^\*/, "") : "";
16003
+ if (said.startsWith("res://")) {
16004
+ named.set(name, said.slice("res://".length));
16005
+ }
16006
+ }
16007
+ return named;
16008
+ }
16009
+ function trackedByGit(projectPath, wanted) {
16010
+ if (wanted.length === 0) {
16011
+ return new Set;
16012
+ }
16013
+ const asked = spawnSync2("git", ["ls-files", "-z", "--", ...wanted], {
16014
+ cwd: projectPath,
16015
+ encoding: "utf8"
16016
+ });
16017
+ if (asked.status !== 0) {
16018
+ return null;
16019
+ }
16020
+ return new Set(asked.stdout.split("\x00").filter(Boolean));
16021
+ }
16022
+ function enabledPlugins(settings) {
16023
+ const raw = settings["editor_plugins"]?.["enabled"];
16024
+ if (typeof raw !== "string") {
16025
+ return [];
16026
+ }
16027
+ return [...raw.matchAll(/res:\/\/addons\/([^/"]+)\/plugin\.cfg/g)].map((match) => match[1] ?? "").filter(Boolean);
16028
+ }
16029
+ function inspectProject(projectPath) {
16030
+ const problems = [];
16031
+ const addons = ADDONS.map((name) => {
16032
+ const directory = join8(projectPath, "addons", name);
16033
+ const marker = join8(directory, VERSION_MARKER);
16034
+ const installed = existsSync7(directory);
16035
+ const version = existsSync7(marker) ? readFileSync7(marker, "utf8").trim() : null;
16036
+ const current = version === SERVER_VERSION;
16037
+ if (!installed) {
16038
+ problems.push(`addons/${name} is not installed; run gdharness setup`);
16039
+ } else if (!current) {
16040
+ problems.push(`addons/${name} is ${version ?? "of an unknown version"}, this gdharness is ${SERVER_VERSION}; run gdharness setup`);
16041
+ }
16042
+ return { name, installed, version, current };
16043
+ });
16044
+ const settings = parseProjectGodot(readFileSync7(join8(projectPath, "project.godot"), "utf8"));
16045
+ const pluginsEnabled = enabledPlugins(settings);
16046
+ for (const name of EDITOR_PLUGINS) {
16047
+ if (!pluginsEnabled.includes(name)) {
16048
+ problems.push(`the ${name} plugin is not enabled in project.godot; run gdharness setup`);
16049
+ }
16050
+ }
16051
+ const autoload = settings["autoload"];
16052
+ const registered = autoload === undefined ? undefined : readString(autoload, RUNTIME_AUTOLOAD.name);
16053
+ const runtimeAutoloadPath = typeof registered === "string" ? registered.replace(/^\*/, "") : null;
16054
+ const named = autoloadPaths(settings);
16055
+ const runtimeLoaderAutoload = loaderAmong(projectPath, named);
16056
+ const runtimeAutoload = runtimeAutoloadPath !== null || runtimeLoaderAutoload !== null;
16057
+ const tracked = trackedByGit(projectPath, [...named.values()]);
16058
+ const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
16059
+ for (const [name, path] of named) {
16060
+ if (!existsSync7(join8(projectPath, path))) {
16061
+ if (!addonMissing(path)) {
16062
+ problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
16063
+ }
16064
+ continue;
16065
+ }
16066
+ if (tracked !== null && !tracked.has(path)) {
16067
+ problems.push(`the ${name} autoload names res://${path}, which git does not carry, so a clone boots with a missing script; commit that file, or point the autoload at one the project tracks`);
16068
+ }
16069
+ }
16070
+ const cached = cachedClasses(projectPath);
16071
+ const staleClasses = cached === null ? [] : staleAgainst(cached, projectPath);
16072
+ if (cached === null) {
16073
+ problems.push("no .godot/global_script_class_cache.cfg: the engine knows no class_name; run project_import refresh_classes or open the editor");
16074
+ } else if (staleClasses.length > 0) {
16075
+ problems.push(`the class cache does not list ${staleClasses.join(", ")}; run project_import refresh_classes or gdharness classes`);
16076
+ }
16077
+ return {
16078
+ projectPath,
16079
+ addons,
16080
+ pluginsEnabled,
16081
+ runtimeAutoload,
16082
+ runtimeAutoloadPath,
16083
+ runtimeLoaderAutoload,
16084
+ staleClasses,
16085
+ classCacheExists: cached !== null,
16086
+ problems
16087
+ };
16088
+ }
16089
+ var ADDONS, EDITOR_PLUGINS, RUNTIME_AUTOLOAD, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER = ".gdharness-version", DIGEST_MARKER = ".gdharness-digest", shippedDigest;
15634
16090
  var init_setup = __esm(() => {
15635
16091
  init_class_cache();
15636
16092
  init_headless();
15637
16093
  init_resources();
15638
16094
  init_server_version();
15639
- ADDONS2 = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
15640
- EDITOR_PLUGINS2 = ["gdharness_editor", "auto_reload"];
15641
- RUNTIME_AUTOLOAD2 = {
16095
+ ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
16096
+ EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
16097
+ RUNTIME_AUTOLOAD = {
15642
16098
  name: "GdharnessRuntime",
15643
16099
  path: "addons/gdharness_runtime/runtime_autoload.gd"
15644
16100
  };
@@ -16088,6 +16544,10 @@ var init_tool_definitions = __esm(() => {
16088
16544
  type: "integer",
16089
16545
  minimum: 1,
16090
16546
  description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path."
16547
+ },
16548
+ printed: {
16549
+ type: "string",
16550
+ description: "Also answer with every line the run printed that contains this, under printed, whether it passed or not: a suite that prints a marker beside the numbers it measured carries them back without failing an assertion to do it. Matched case aside, in the text and the backtrace, at most 200 lines, the newest kept; printedOmitted counts the rest."
16091
16551
  }
16092
16552
  },
16093
16553
  requires: ["projectPath"]
@@ -16737,14 +17197,19 @@ var init_tool_definitions = __esm(() => {
16737
17197
  },
16738
17198
  {
16739
17199
  name: "runtime_input",
16740
- description: "Input to the running game: a whole click on a Control or a 3D node named by path, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
17200
+ description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
16741
17201
  parameters: {
16742
17202
  projectPath: RUNNING_PROJECT_PATH,
16743
17203
  pid: RUNNING_PID,
16744
17204
  nodePath: {
16745
17205
  type: "string",
16746
17206
  ops: ["click", "choose"],
16747
- description: "click: the Control to click, at its centre, or the 3D node to click, where it is drawn. choose: the PopupMenu, or the OptionButton or MenuButton in front of one."
17207
+ description: "click: the Control to click, at its centre, or the 3D node to click, where it is drawn; with says, where to look for the control instead, default the whole tree. choose: the PopupMenu, or the OptionButton or MenuButton in front of one."
17208
+ },
17209
+ says: {
17210
+ type: "string",
17211
+ ops: ["click"],
17212
+ description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. Several matches are refused with each one listed, and index picks one."
16748
17213
  },
16749
17214
  action: {
16750
17215
  type: "string",
@@ -16781,8 +17246,8 @@ var init_tool_definitions = __esm(() => {
16781
17246
  },
16782
17247
  index: {
16783
17248
  type: "number",
16784
- ops: ["choose"],
16785
- description: "choose: the item to take, by where it is in the list, when text will not do."
17249
+ ops: ["click", "choose"],
17250
+ description: "click: which of several controls saying the words, counting from 0 in the order a refusal lists them. choose: the item to take, by where it is in the list, when text will not do."
16786
17251
  },
16787
17252
  shift: { type: "boolean", ops: ["key"], description: "key: hold Shift with it. Default false." },
16788
17253
  ctrl: { type: "boolean", ops: ["key"], description: "key: hold Ctrl with it. Default false." },
@@ -16821,8 +17286,8 @@ var init_tool_definitions = __esm(() => {
16821
17286
  requires: [],
16822
17287
  operations: {
16823
17288
  click: {
16824
- summary: "press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. A control out of sight inside a ScrollContainer is scrolled to first, and scrolled_into_view says whether the view moved. A 3D node is clicked where it is drawn, and landed then says the interface did not swallow the press",
16825
- requires: ["nodePath"]
17289
+ summary: "press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. A control out of sight inside a ScrollContainer is scrolled to first, and scrolled_into_view says whether the view moved. A 3D node is clicked where it is drawn, and landed then says the interface did not swallow the press",
17290
+ requires: []
16826
17291
  },
16827
17292
  choose: {
16828
17293
  summary: "take an item out of a menu, by what it says or by where it is in the list. A menu's items are drawn rather than built, so there is nothing to click: the item takes the focus and Enter presses it, which is the engine's own path and needs no window. Answers with what was chosen and what the button in front of it shows now",
@@ -31702,10 +32167,10 @@ var init_stdio2 = __esm(() => {
31702
32167
  });
31703
32168
 
31704
32169
  // src/runtime-client.ts
31705
- import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync5, unlinkSync } from "node:fs";
32170
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync9, statSync as statSync4, unlinkSync } from "node:fs";
31706
32171
  import { createConnection } from "node:net";
31707
- import { tmpdir as tmpdir4 } from "node:os";
31708
- import { dirname as dirname8, join as join13, resolve as resolve3 } from "node:path";
32172
+ import { tmpdir as tmpdir3 } from "node:os";
32173
+ import { dirname as dirname7, join as join11, resolve as resolve3 } from "node:path";
31709
32174
  import { setTimeout as delay } from "node:timers/promises";
31710
32175
  function runtimeDirectory(variables = process.env) {
31711
32176
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -31713,20 +32178,20 @@ function runtimeDirectory(variables = process.env) {
31713
32178
  return explicit;
31714
32179
  }
31715
32180
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
31716
- return join13(perUser ?? tmpdir4(), "gdharness");
32181
+ return join11(perUser ?? tmpdir3(), "gdharness");
31717
32182
  }
31718
32183
  function runtimeDirectories(variables = process.env) {
31719
32184
  const candidates = [runtimeDirectory(variables)];
31720
32185
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
31721
32186
  for (const base of fallbacks) {
31722
- candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
32187
+ candidates.push(join11(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
31723
32188
  }
31724
32189
  return [...new Set(candidates.map((path) => resolve3(path)))];
31725
32190
  }
31726
32191
  function errorReportOf(pid, directories = runtimeDirectories()) {
31727
32192
  for (const directory of directories) {
31728
- const path = join13(directory, `runtime-${pid}.log`);
31729
- if (existsSync10(path)) {
32193
+ const path = join11(directory, `runtime-${pid}.log`);
32194
+ if (existsSync9(path)) {
31730
32195
  return path;
31731
32196
  }
31732
32197
  }
@@ -31743,7 +32208,7 @@ function processAlive(pid) {
31743
32208
  function parseAnnouncement(file, pid) {
31744
32209
  let fields;
31745
32210
  try {
31746
- fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
32211
+ fields = asParams(JSON.parse(readFileSync9(file, "utf8")));
31747
32212
  } catch {
31748
32213
  return { kind: "rubbish" };
31749
32214
  }
@@ -31760,7 +32225,7 @@ function parseAnnouncement(file, pid) {
31760
32225
  };
31761
32226
  let announcedAt;
31762
32227
  try {
31763
- announcedAt = statSync5(file).mtimeMs;
32228
+ announcedAt = statSync4(file).mtimeMs;
31764
32229
  } catch {}
31765
32230
  const when = announcedAt === undefined ? {} : { announcedAt };
31766
32231
  if (protocol !== RUNTIME_PROTOCOL) {
@@ -31801,7 +32266,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
31801
32266
  function wentAway(file, pid) {
31802
32267
  let project = "";
31803
32268
  try {
31804
- const fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
32269
+ const fields = asParams(JSON.parse(readFileSync9(file, "utf8")));
31805
32270
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
31806
32271
  } catch {
31807
32272
  return;
@@ -31838,29 +32303,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
31838
32303
  return new Set(candidates.filter((one) => verdicts.get(one.file)?.stranger === true).map((one) => one.file));
31839
32304
  }
31840
32305
  function announcedIn(directory) {
31841
- if (!existsSync10(directory)) {
32306
+ if (!existsSync9(directory)) {
31842
32307
  return [];
31843
32308
  }
31844
32309
  const found = [];
31845
32310
  const candidates = [];
31846
32311
  const entries = [];
31847
- for (const entry of readdirSync6(directory)) {
32312
+ for (const entry of readdirSync5(directory)) {
31848
32313
  const report = ERROR_REPORT_PATTERN.exec(entry);
31849
32314
  if (report) {
31850
- sweepErrorReport(join13(directory, entry), Number.parseInt(report[1] ?? "", 10));
32315
+ sweepErrorReport(join11(directory, entry), Number.parseInt(report[1] ?? "", 10));
31851
32316
  continue;
31852
32317
  }
31853
32318
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
31854
32319
  if (!match) {
31855
32320
  continue;
31856
32321
  }
31857
- const file = join13(directory, entry);
32322
+ const file = join11(directory, entry);
31858
32323
  const pid = Number.parseInt(match[1] ?? "", 10);
31859
32324
  const alive = processAlive(pid);
31860
32325
  entries.push({ file, pid, alive });
31861
32326
  if (alive) {
31862
32327
  try {
31863
- candidates.push({ file, pid, writtenAt: statSync5(file).mtimeMs });
32328
+ candidates.push({ file, pid, writtenAt: statSync4(file).mtimeMs });
31864
32329
  } catch {}
31865
32330
  }
31866
32331
  }
@@ -31882,16 +32347,16 @@ function announcedIn(directory) {
31882
32347
  found.push(announced);
31883
32348
  }
31884
32349
  const held = new Set(entries.map((one) => one.file));
31885
- const here = join13(directory, ".");
32350
+ const here = join11(directory, ".");
31886
32351
  for (const file of verdicts.keys()) {
31887
- if (dirname8(file) === here && !held.has(file)) {
32352
+ if (dirname7(file) === here && !held.has(file)) {
31888
32353
  verdicts.delete(file);
31889
32354
  }
31890
32355
  }
31891
32356
  return found;
31892
32357
  }
31893
32358
  async function announcementEnded(pid, directories = runtimeDirectories()) {
31894
- const files = directories.map((directory) => join13(directory, `runtime-${pid}.json`)).filter((file) => existsSync10(file));
32359
+ const files = directories.map((directory) => join11(directory, `runtime-${pid}.json`)).filter((file) => existsSync9(file));
31895
32360
  const first = files[0];
31896
32361
  if (first === undefined) {
31897
32362
  return;
@@ -31917,7 +32382,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
31917
32382
  return;
31918
32383
  }
31919
32384
  try {
31920
- if (now - statSync5(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
32385
+ if (now - statSync4(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
31921
32386
  unlinkSync(path);
31922
32387
  }
31923
32388
  } catch {}
@@ -32190,14 +32655,14 @@ var init_runtime_client = __esm(() => {
32190
32655
  });
32191
32656
 
32192
32657
  // src/boot-note.ts
32193
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
32194
- import { dirname as dirname9, join as join14 } from "node:path";
32658
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
32659
+ import { dirname as dirname8, join as join12 } from "node:path";
32195
32660
  function bootNotePath(projectPath) {
32196
- return join14(projectPath, ".godot", "gdharness-boot.json");
32661
+ return join12(projectPath, ".godot", "gdharness-boot.json");
32197
32662
  }
32198
32663
  function readBootNote(projectPath) {
32199
32664
  try {
32200
- const noted = readNumber(asParams(JSON.parse(readFileSync12(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
32665
+ const noted = readNumber(asParams(JSON.parse(readFileSync10(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
32201
32666
  return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
32202
32667
  } catch {
32203
32668
  return null;
@@ -32206,8 +32671,8 @@ function readBootNote(projectPath) {
32206
32671
  function writeBootNote(projectPath, announcedAfterMs) {
32207
32672
  try {
32208
32673
  const path = bootNotePath(projectPath);
32209
- mkdirSync7(dirname9(path), { recursive: true });
32210
- writeFileSync9(path, JSON.stringify({ announcedAfterMs: Math.round(announcedAfterMs), at: new Date().toISOString() }, null, 2), "utf8");
32674
+ mkdirSync6(dirname8(path), { recursive: true });
32675
+ writeFileSync7(path, JSON.stringify({ announcedAfterMs: Math.round(announcedAfterMs), at: new Date().toISOString() }, null, 2), "utf8");
32211
32676
  } catch {}
32212
32677
  }
32213
32678
  function halfAsLongAgain(lastBootMs) {
@@ -32225,10 +32690,10 @@ var init_boot_note = __esm(() => {
32225
32690
  });
32226
32691
 
32227
32692
  // src/breakpoint-note.ts
32228
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync13, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
32229
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative as relative2 } from "node:path";
32693
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
32694
+ import { dirname as dirname9, isAbsolute as isAbsolute2, join as join13, relative as relative2 } from "node:path";
32230
32695
  function breakpointNotePath(projectPath) {
32231
- return join15(projectPath, ".godot", "gdharness-breakpoints.json");
32696
+ return join13(projectPath, ".godot", "gdharness-breakpoints.json");
32232
32697
  }
32233
32698
  function writeBreakpointNote(projectPath, held) {
32234
32699
  const path = breakpointNotePath(projectPath);
@@ -32236,7 +32701,7 @@ function writeBreakpointNote(projectPath, held) {
32236
32701
  const inside = held.map((one) => ({ scriptPath: realPathOr(one.scriptPath), lines: one.lines })).filter((one) => one.lines.length > 0 && withinProject(root, one.scriptPath));
32237
32702
  try {
32238
32703
  if (inside.length === 0) {
32239
- rmSync7(path, { force: true });
32704
+ rmSync6(path, { force: true });
32240
32705
  return;
32241
32706
  }
32242
32707
  const note = {
@@ -32245,8 +32710,8 @@ function writeBreakpointNote(projectPath, held) {
32245
32710
  lines: [...one.lines]
32246
32711
  }))
32247
32712
  };
32248
- mkdirSync8(dirname10(path), { recursive: true });
32249
- writeFileSync10(path, `${JSON.stringify(note, null, 2)}
32713
+ mkdirSync7(dirname9(path), { recursive: true });
32714
+ writeFileSync8(path, `${JSON.stringify(note, null, 2)}
32250
32715
  `, "utf8");
32251
32716
  } catch (error) {
32252
32717
  console.error(`[SERVER] Could not record the breakpoints at ${path}: ${errorMessage(error)}`);
@@ -32254,7 +32719,7 @@ function writeBreakpointNote(projectPath, held) {
32254
32719
  }
32255
32720
  function readBreakpointNote(projectPath) {
32256
32721
  try {
32257
- const parsed = JSON.parse(readFileSync13(breakpointNotePath(projectPath), "utf8"));
32722
+ const parsed = JSON.parse(readFileSync11(breakpointNotePath(projectPath), "utf8"));
32258
32723
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
32259
32724
  return [];
32260
32725
  }
@@ -32269,7 +32734,7 @@ function readBreakpointNote(projectPath) {
32269
32734
  }
32270
32735
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
32271
32736
  if (lines.length > 0) {
32272
- held.push({ scriptPath: realPathOr(join15(projectPath, script)), lines });
32737
+ held.push({ scriptPath: realPathOr(join13(projectPath, script)), lines });
32273
32738
  }
32274
32739
  }
32275
32740
  return held;
@@ -32286,75 +32751,15 @@ var init_breakpoint_note = __esm(() => {
32286
32751
  init_paths();
32287
32752
  });
32288
32753
 
32289
- // src/bridge-announce.ts
32290
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
32291
- import { dirname as dirname11, join as join16 } from "node:path";
32292
- function announcementPath2(projectPath) {
32293
- return join16(projectPath, ".godot", "gdharness-bridge.json");
32294
- }
32295
- function readAnnouncement2(path) {
32296
- try {
32297
- const parsed = JSON.parse(readFileSync14(path, "utf8"));
32298
- if (typeof parsed !== "object" || parsed === null) {
32299
- return null;
32300
- }
32301
- const said = parsed;
32302
- if (typeof said.port !== "number" || typeof said.pid !== "number") {
32303
- return null;
32304
- }
32305
- return said;
32306
- } catch {
32307
- return null;
32308
- }
32309
- }
32310
- function announcementIsOurs(path) {
32311
- return readAnnouncement2(path)?.pid === process.pid;
32312
- }
32313
- function announceBridge(projectPath, bridge) {
32314
- const path = announcementPath2(projectPath);
32315
- try {
32316
- mkdirSync9(dirname11(path), { recursive: true });
32317
- const announcement = {
32318
- protocol: BRIDGE_ANNOUNCE_PROTOCOL,
32319
- host: bridge.host,
32320
- port: bridge.port,
32321
- pid: process.pid,
32322
- version: bridge.version,
32323
- startedAt: new Date().toISOString()
32324
- };
32325
- writeFileSync11(path, `${JSON.stringify(announcement, null, 2)}
32326
- `, "utf8");
32327
- return path;
32328
- } catch (error) {
32329
- console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
32330
- return null;
32331
- }
32332
- }
32333
- function withdrawBridge(path) {
32334
- if (path === null || !announcementIsOurs(path)) {
32335
- return;
32336
- }
32337
- try {
32338
- rmSync8(path, { force: true });
32339
- } catch (error) {
32340
- console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
32341
- }
32342
- }
32343
- var BRIDGE_ANNOUNCE_PROTOCOL = 1;
32344
- var init_bridge_announce = __esm(() => {
32345
- init_errors();
32346
- init_process_children();
32347
- });
32348
-
32349
32754
  // src/class-note.ts
32350
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "node:fs";
32351
- import { dirname as dirname12, join as join17 } from "node:path";
32755
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
32756
+ import { dirname as dirname10, join as join14 } from "node:path";
32352
32757
  function classNotePath(projectPath) {
32353
- return join17(projectPath, ".godot", "gdharness-classes.json");
32758
+ return join14(projectPath, ".godot", "gdharness-classes.json");
32354
32759
  }
32355
32760
  function readClassNote(projectPath) {
32356
32761
  try {
32357
- const fields = asParams(JSON.parse(readFileSync15(classNotePath(projectPath), "utf8")));
32762
+ const fields = asParams(JSON.parse(readFileSync12(classNotePath(projectPath), "utf8")));
32358
32763
  const rebuiltWith = readArray(fields, "rebuiltWith");
32359
32764
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
32360
32765
  return null;
@@ -32371,24 +32776,24 @@ function readClassNote(projectPath) {
32371
32776
  function writeClassNote(projectPath, note) {
32372
32777
  try {
32373
32778
  const path = classNotePath(projectPath);
32374
- mkdirSync10(dirname12(path), { recursive: true });
32375
- writeFileSync12(path, `${JSON.stringify(note, null, 2)}
32779
+ mkdirSync8(dirname10(path), { recursive: true });
32780
+ writeFileSync9(path, `${JSON.stringify(note, null, 2)}
32376
32781
  `, "utf8");
32377
32782
  } catch {}
32378
32783
  }
32379
32784
  var init_class_note = () => {};
32380
32785
 
32381
32786
  // src/config-pin.ts
32382
- import { existsSync as existsSync11, readFileSync as readFileSync16 } from "node:fs";
32383
- import { join as join18 } from "node:path";
32787
+ import { existsSync as existsSync10, readFileSync as readFileSync13 } from "node:fs";
32788
+ import { join as join15 } from "node:path";
32384
32789
  function versionsNamedInConfig(projectPath) {
32385
- const config = join18(projectPath, ".mcp.json");
32386
- if (!existsSync11(config)) {
32790
+ const config = join15(projectPath, ".mcp.json");
32791
+ if (!existsSync10(config)) {
32387
32792
  return [];
32388
32793
  }
32389
32794
  let text;
32390
32795
  try {
32391
- text = readFileSync16(config, "utf8");
32796
+ text = readFileSync13(config, "utf8");
32392
32797
  JSON.parse(text);
32393
32798
  } catch {
32394
32799
  return [];
@@ -33145,25 +33550,25 @@ var init_desktop = () => {};
33145
33550
  // src/editor-log.ts
33146
33551
  import {
33147
33552
  closeSync,
33148
- existsSync as existsSync12,
33149
- mkdirSync as mkdirSync11,
33553
+ existsSync as existsSync11,
33554
+ mkdirSync as mkdirSync9,
33150
33555
  openSync,
33151
- readFileSync as readFileSync17,
33556
+ readFileSync as readFileSync14,
33152
33557
  readSync,
33153
- rmSync as rmSync9,
33154
- statSync as statSync6,
33155
- writeFileSync as writeFileSync13
33558
+ rmSync as rmSync7,
33559
+ statSync as statSync5,
33560
+ writeFileSync as writeFileSync10
33156
33561
  } from "node:fs";
33157
- import { dirname as dirname13, join as join19 } from "node:path";
33562
+ import { dirname as dirname11, join as join16 } from "node:path";
33158
33563
  function editorLogPath(projectPath) {
33159
- return join19(projectPath, ".godot", "gdharness-editor.log");
33564
+ return join16(projectPath, ".godot", "gdharness-editor.log");
33160
33565
  }
33161
33566
  function editorLogNotePath(projectPath) {
33162
- return join19(projectPath, ".godot", "gdharness-editor.json");
33567
+ return join16(projectPath, ".godot", "gdharness-editor.json");
33163
33568
  }
33164
33569
  function readEditorLogNote(projectPath) {
33165
33570
  try {
33166
- const fields = asParams(JSON.parse(readFileSync17(editorLogNotePath(projectPath), "utf8")));
33571
+ const fields = asParams(JSON.parse(readFileSync14(editorLogNotePath(projectPath), "utf8")));
33167
33572
  const pid = readNumber(fields, "pid");
33168
33573
  const startedAt = readString(fields, "startedAt");
33169
33574
  return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
@@ -33174,16 +33579,16 @@ function readEditorLogNote(projectPath) {
33174
33579
  function clearEditorLog(projectPath) {
33175
33580
  try {
33176
33581
  const path = editorLogPath(projectPath);
33177
- mkdirSync11(dirname13(path), { recursive: true });
33178
- rmSync9(path, { force: true });
33179
- rmSync9(editorLogNotePath(projectPath), { force: true });
33582
+ mkdirSync9(dirname11(path), { recursive: true });
33583
+ rmSync7(path, { force: true });
33584
+ rmSync7(editorLogNotePath(projectPath), { force: true });
33180
33585
  } catch {}
33181
33586
  }
33182
33587
  function writeEditorLogNote(projectPath, pid) {
33183
33588
  try {
33184
33589
  const path = editorLogNotePath(projectPath);
33185
- mkdirSync11(dirname13(path), { recursive: true });
33186
- writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
33590
+ mkdirSync9(dirname11(path), { recursive: true });
33591
+ writeFileSync10(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
33187
33592
  } catch {}
33188
33593
  }
33189
33594
  function theConsoleWasNotCaptured(why, projectPath) {
@@ -33204,7 +33609,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
33204
33609
  return { kind: "older server", notedPid: noted?.pid ?? null };
33205
33610
  }
33206
33611
  const path = editorLogPath(projectPath);
33207
- if (!existsSync12(path)) {
33612
+ if (!existsSync11(path)) {
33208
33613
  return { kind: "not written" };
33209
33614
  }
33210
33615
  const log = new GameLog;
@@ -33214,9 +33619,9 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
33214
33619
  return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
33215
33620
  }
33216
33621
  function theFirstOf(path) {
33217
- const whole = statSync6(path).size;
33622
+ const whole = statSync5(path).size;
33218
33623
  if (whole <= MOST_CONSOLE_BYTES) {
33219
- return { text: readFileSync17(path, "utf8") };
33624
+ return { text: readFileSync14(path, "utf8") };
33220
33625
  }
33221
33626
  const handle = openSync(path, "r");
33222
33627
  try {
@@ -33258,7 +33663,7 @@ function shapeOf(text) {
33258
33663
  }
33259
33664
  return { shape, values };
33260
33665
  }
33261
- function bursts(entries, before) {
33666
+ function bursts(entries, before, keep) {
33262
33667
  const shapes = new Map;
33263
33668
  const shaped = entries.map((entry) => shapeOf(entry.text));
33264
33669
  for (const [at, entry] of entries.entries()) {
@@ -33286,6 +33691,9 @@ function bursts(entries, before) {
33286
33691
  if (entry === undefined) {
33287
33692
  continue;
33288
33693
  }
33694
+ if (keep !== undefined && !group.indices.some((at) => entries[at] !== undefined && keep(entries[at]))) {
33695
+ continue;
33696
+ }
33289
33697
  repeated.push({
33290
33698
  shape: key.slice(key.indexOf("\x00") + 1),
33291
33699
  count: group.indices.length,
@@ -36755,208 +37163,6 @@ var init_godot_bridge = __esm(() => {
36755
37163
  };
36756
37164
  });
36757
37165
 
36758
- // src/godot-path.ts
36759
- import { execFile as execFile5 } from "node:child_process";
36760
- import { existsSync as existsSync13 } from "node:fs";
36761
- import { normalize as normalize2 } from "node:path";
36762
- import { promisify as promisify5 } from "node:util";
36763
-
36764
- class GodotLocator2 {
36765
- answered = new Map;
36766
- found = null;
36767
- async find() {
36768
- if (this.found !== null && await this.whyNot(this.found) === null) {
36769
- return { ok: true, path: this.found };
36770
- }
36771
- this.found = null;
36772
- const named = envValue("GODOT_PATH");
36773
- if (named !== undefined) {
36774
- const path = normalize2(named);
36775
- const reason = await this.whyNot(path);
36776
- if (reason === null) {
36777
- this.found = path;
36778
- return { ok: true, path };
36779
- }
36780
- return { ok: false, named: path, reason };
36781
- }
36782
- for (const candidate of godotCandidates(process.platform, resolveHomeDirectory())) {
36783
- const path = normalize2(candidate);
36784
- if (await this.whyNot(path) === null) {
36785
- this.found = path;
36786
- return { ok: true, path };
36787
- }
36788
- }
36789
- return { ok: false, named: null };
36790
- }
36791
- static refusal(located) {
36792
- if (located.named === null) {
36793
- return {
36794
- message: "No Godot executable found.",
36795
- advice: [
36796
- "Set GODOT_PATH to the Godot 4 executable",
36797
- "Or install Godot where it is usually found, such as /usr/bin/godot or C:\\Program Files\\Godot"
36798
- ]
36799
- };
36800
- }
36801
- return {
36802
- message: `GODOT_PATH is set to ${located.named}, which ${located.reason ?? "does not answer"}.`,
36803
- advice: [
36804
- "Point GODOT_PATH at a Godot 4 executable that answers --version",
36805
- "Or unset it, and the usual install locations are searched instead"
36806
- ]
36807
- };
36808
- }
36809
- async whyNot(path) {
36810
- const known = this.answered.get(path);
36811
- if (known !== undefined) {
36812
- return known;
36813
- }
36814
- let reason;
36815
- if (path !== "godot" && !existsSync13(path)) {
36816
- reason = "does not exist";
36817
- } else {
36818
- try {
36819
- await run5(path, ["--version"]);
36820
- reason = null;
36821
- } catch (error) {
36822
- reason = `does not answer --version: ${errorMessage(error)}`;
36823
- }
36824
- }
36825
- this.answered.set(path, reason);
36826
- return reason;
36827
- }
36828
- }
36829
- var run5;
36830
- var init_godot_path = __esm(() => {
36831
- init_detection();
36832
- init_errors();
36833
- run5 = promisify5(execFile5);
36834
- });
36835
-
36836
- // src/issues.ts
36837
- import { createHash as createHash4 } from "node:crypto";
36838
- import process7 from "node:process";
36839
- function feedbackNotice() {
36840
- return JSON.stringify({
36841
- gdharness_feedback: {
36842
- what_to_do: "If this session has run into something gdharness cannot do, or a tool that should " + "exist and does not, tell the user what is missing and offer to open an enhancement " + "issue for them. Only if they say yes, and only for a real gap: a call you got wrong " + "is not one. If you cannot open an issue yourself, give them the link and what to " + "write in it.",
36843
- enhancement_url: ENHANCEMENT_URL2
36844
- }
36845
- }, null, 2);
36846
- }
36847
- function runtime2() {
36848
- const versions = process7.versions;
36849
- const bun = versions["bun"];
36850
- return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
36851
- }
36852
- function defectSignature2(where, message) {
36853
- const normalised = `${where}
36854
- ${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
36855
- return createHash4("sha256").update(normalised).digest("hex").slice(0, 8);
36856
- }
36857
- function facts2(where, message, godotVersion) {
36858
- return [
36859
- ["Where", where],
36860
- ["Error", message],
36861
- ["Version", `gdharness ${SERVER_VERSION}`],
36862
- ["Runtime", `${runtime2()}, ${process7.platform} ${process7.arch}`],
36863
- ["Godot", godotVersion ?? "not known at the point this failed"],
36864
- ["Signature", defectSignature2(where, message)]
36865
- ];
36866
- }
36867
- function filledTemplate2(where, message, godotVersion) {
36868
- return [
36869
- `**gdharness version**: ${SERVER_VERSION}`,
36870
- `**Godot version**: ${godotVersion ?? ""}`,
36871
- `**Bun version**: ${runtime2()}`,
36872
- `**OS**: ${process7.platform} ${process7.arch}`,
36873
- "**MCP client**:",
36874
- "",
36875
- "## What you did",
36876
- "",
36877
- where,
36878
- "",
36879
- "## What you expected",
36880
- "",
36881
- "The call to answer, or to refuse with a reason.",
36882
- "",
36883
- "## What happened",
36884
- "",
36885
- "gdharness failed in a way it does not model.",
36886
- "",
36887
- "```text",
36888
- message,
36889
- "```",
36890
- "",
36891
- `Signature: \`${defectSignature2(where, message)}\``,
36892
- "",
36893
- "If the real cause turned out to be the project, the environment or the call, say so here:",
36894
- "reaching you as a defect rather than as a refusal naming what would have worked is then the",
36895
- "thing to fix.",
36896
- "",
36897
- "## Anything that makes it reproducible",
36898
- ""
36899
- ].join(`
36900
- `);
36901
- }
36902
-
36903
- class DefectsSeen {
36904
- counts = new Map;
36905
- before(where, message) {
36906
- const signature = defectSignature2(where, message);
36907
- const seen = this.counts.get(signature) ?? 0;
36908
- this.counts.set(signature, seen + 1);
36909
- return seen;
36910
- }
36911
- }
36912
- function defectReport2(where, error, context = {}) {
36913
- const { godotVersion, seenBefore = 0 } = context;
36914
- const message = errorMessage(error);
36915
- const title = `Defect ${defectSignature2(where, message)}: ${where}`;
36916
- const url = `${NEW_ISSUE2}?template=bug_report.md&title=${encodeURIComponent(title)}` + `&body=${encodeURIComponent(filledTemplate2(where, message, godotVersion))}`;
36917
- const width = Math.max(...facts2(where, message, godotVersion).map(([label]) => (label ?? "").length));
36918
- return [
36919
- "gdharness failed in a way it does not model. Whatever went wrong underneath, it reached you",
36920
- "as a crash rather than as a refusal naming what would have worked, and that much is a defect",
36921
- "in gdharness however the rest of it turns out.",
36922
- "",
36923
- ...seenBefore === 0 ? [
36924
- "Send the call once more before anything else. Some of what reaches this line is a file",
36925
- "another process had open, or a connection that dropped, and those are gone by the next",
36926
- "attempt. If the same Signature comes back, it is not one of those, and repeating it",
36927
- "further only spends turns."
36928
- ] : [
36929
- `You have had this exact failure ${seenBefore === 1 ? "once" : `${seenBefore} times`} in this session already, so it is a`,
36930
- "state rather than a passing one, and sending the call again will not change it."
36931
- ],
36932
- "",
36933
- ...facts2(where, message, godotVersion).map(([label, value]) => ` ${(label ?? "").padEnd(width)} ${value ?? ""}`),
36934
- "",
36935
- "Do not open an issue on your own initiative. Tell the person whose machine this is what",
36936
- "broke, and ask whether you may report it to gdharness on their behalf. What the report",
36937
- "carries is the lines above and nothing else, and whether that goes into a public tracker",
36938
- "is theirs to decide.",
36939
- "",
36940
- "If they say yes, this link carries the report already filled in:",
36941
- url,
36942
- "",
36943
- "If they say yes and you have no way to open an issue yourself, give them that link and",
36944
- "those lines, and stay with them while they file it.",
36945
- "",
36946
- "Worth reporting either way. If you can see that what actually went wrong was the project,",
36947
- "the environment or the call, then this message is the defect: a failure gdharness knows",
36948
- "about is meant to arrive as a refusal naming what would have worked, not as this. Say that",
36949
- "in the report and it is the more useful of the two."
36950
- ].join(`
36951
- `);
36952
- }
36953
- var NEW_ISSUE2 = "https://github.com/Aureliolo/gdharness/issues/new", ENHANCEMENT_URL2;
36954
- var init_issues = __esm(() => {
36955
- init_errors();
36956
- init_server_version();
36957
- ENHANCEMENT_URL2 = `${NEW_ISSUE2}?template=feature_request.md`;
36958
- });
36959
-
36960
37166
  // src/junit.ts
36961
37167
  function decoded(text) {
36962
37168
  return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, entity) => {
@@ -37148,7 +37354,7 @@ function scriptErrorsPrinted(printed) {
37148
37354
  for (const line of printed.slice(start + 1)) {
37149
37355
  const at = /^\s*at (res:\/\/.+):(\d+)\s*$/.exec(line);
37150
37356
  if (at !== null && message !== null) {
37151
- found.push({ path: at[1] ?? "", line: Number(at[2]), message });
37357
+ found.push({ path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]), message });
37152
37358
  message = null;
37153
37359
  } else if (at === null && message === null && /^ {2}\S/.test(line)) {
37154
37360
  message = line.trim();
@@ -37156,7 +37362,7 @@ function scriptErrorsPrinted(printed) {
37156
37362
  break;
37157
37363
  }
37158
37364
  }
37159
- return found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : a.line - b.line);
37365
+ return found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : (a.line ?? 0) - (b.line ?? 0));
37160
37366
  }
37161
37367
  function unspelled(text) {
37162
37368
  return text.replace(/<(?:LF|CR|TAB|BS|FF|VT|BEL|ESC|DEL|0x[0-9A-F]{2})>/g, (spelling) => spelling.startsWith("<0x") ? String.fromCharCode(Number.parseInt(spelling.slice(3, 5), 16)) : SPELLED[spelling] ?? spelling);
@@ -37298,8 +37504,8 @@ var init_junit = __esm(() => {
37298
37504
  import { realpathSync as realpathSync2 } from "node:fs";
37299
37505
  import { readFile, realpath } from "node:fs/promises";
37300
37506
  import { createConnection as createConnection3 } from "node:net";
37301
- import { dirname as dirname14, resolve as resolve4 } from "node:path";
37302
- import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
37507
+ import { dirname as dirname12, resolve as resolve4 } from "node:path";
37508
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
37303
37509
  function diagnosticsKey(uri) {
37304
37510
  let decoded;
37305
37511
  try {
@@ -37311,7 +37517,7 @@ function diagnosticsKey(uri) {
37311
37517
  return decoded;
37312
37518
  }
37313
37519
  try {
37314
- return realpathSync2.native(fileURLToPath4(decoded)).toLowerCase();
37520
+ return realpathSync2.native(fileURLToPath3(decoded)).toLowerCase();
37315
37521
  } catch {
37316
37522
  return decoded.toLowerCase();
37317
37523
  }
@@ -37426,7 +37632,7 @@ class GodotLSPClient {
37426
37632
  if (this.initialized) {
37427
37633
  return;
37428
37634
  }
37429
- const rootPath = this.rootPath ?? dirname14(resolve4(filePath));
37635
+ const rootPath = this.rootPath ?? dirname12(resolve4(filePath));
37430
37636
  await this.initialize(rootPath);
37431
37637
  }
37432
37638
  async sendRequest(method, params) {
@@ -37833,7 +38039,7 @@ var init_lsp_client = __esm(() => {
37833
38039
 
37834
38040
  // src/outside.ts
37835
38041
  import { spawn } from "node:child_process";
37836
- import { fileURLToPath as fileURLToPath5 } from "node:url";
38042
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
37837
38043
  function changesFrom(env, base) {
37838
38044
  const changes = {};
37839
38045
  if (env === undefined) {
@@ -37896,12 +38102,12 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
37896
38102
  }
37897
38103
  var KEEPER_SCRIPT;
37898
38104
  var init_outside = __esm(() => {
37899
- KEEPER_SCRIPT = fileURLToPath5(new URL("./keeper.js", import.meta.url));
38105
+ KEEPER_SCRIPT = fileURLToPath4(new URL("./keeper.js", import.meta.url));
37900
38106
  });
37901
38107
 
37902
38108
  // src/process-time.ts
37903
- import { execFile as execFile6 } from "node:child_process";
37904
- import { promisify as promisify6 } from "node:util";
38109
+ import { execFile as execFile4 } from "node:child_process";
38110
+ import { promisify as promisify4 } from "node:util";
37905
38111
  async function cpuSecondsOf(pid) {
37906
38112
  try {
37907
38113
  return process.platform === "win32" ? await askWindows2(pid) : await askPosix2(pid);
@@ -37910,12 +38116,12 @@ async function cpuSecondsOf(pid) {
37910
38116
  }
37911
38117
  }
37912
38118
  async function askWindows2(pid) {
37913
- const { stdout } = await run6("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `(Get-Process -Id ${pid}).CPU`], { timeout: ASK_TIMEOUT_MS2, windowsHide: true });
38119
+ const { stdout } = await run4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `(Get-Process -Id ${pid}).CPU`], { timeout: ASK_TIMEOUT_MS2, windowsHide: true });
37914
38120
  const seconds = Number(stdout.trim().replace(",", "."));
37915
38121
  return Number.isFinite(seconds) ? seconds : undefined;
37916
38122
  }
37917
38123
  async function askPosix2(pid) {
37918
- const { stdout } = await run6("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
38124
+ const { stdout } = await run4("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
37919
38125
  return secondsFromClock(stdout.trim());
37920
38126
  }
37921
38127
  function secondsFromClock(printed) {
@@ -37933,19 +38139,19 @@ function secondsFromClock(printed) {
37933
38139
  const extra = days === null ? 0 : Number(days) * 86400;
37934
38140
  return Number.isFinite(extra) ? seconds + extra : undefined;
37935
38141
  }
37936
- var run6, ASK_TIMEOUT_MS2 = 6000;
38142
+ var run4, ASK_TIMEOUT_MS2 = 6000;
37937
38143
  var init_process_time = __esm(() => {
37938
- run6 = promisify6(execFile6);
38144
+ run4 = promisify4(execFile4);
37939
38145
  });
37940
38146
 
37941
38147
  // src/project-scan.ts
37942
- import { existsSync as existsSync14, readdirSync as readdirSync7, readFileSync as readFileSync18 } from "node:fs";
37943
- import { join as join20 } from "node:path";
38148
+ import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
38149
+ import { join as join17 } from "node:path";
37944
38150
  function skipped(name) {
37945
38151
  return name.startsWith(".") || SKIPPED.has(name);
37946
38152
  }
37947
38153
  function steppedOver(directory) {
37948
- return existsSync14(join20(directory, ".gdignore"));
38154
+ return existsSync12(join17(directory, ".gdignore"));
37949
38155
  }
37950
38156
  function projectStructure(projectPath) {
37951
38157
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -37953,12 +38159,12 @@ function projectStructure(projectPath) {
37953
38159
  if (steppedOver(directory)) {
37954
38160
  return;
37955
38161
  }
37956
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38162
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
37957
38163
  if (skipped(entry.name)) {
37958
38164
  continue;
37959
38165
  }
37960
38166
  if (entry.isDirectory()) {
37961
- visit(join20(directory, entry.name));
38167
+ visit(join17(directory, entry.name));
37962
38168
  } else if (entry.isFile()) {
37963
38169
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37964
38170
  if (extension === "tscn") {
@@ -37982,20 +38188,20 @@ function scriptsWithoutUid(projectPath) {
37982
38188
  if (steppedOver(directory)) {
37983
38189
  return;
37984
38190
  }
37985
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38191
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
37986
38192
  if (skipped(entry.name)) {
37987
38193
  continue;
37988
38194
  }
37989
38195
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
37990
38196
  if (entry.isDirectory()) {
37991
- visit(join20(directory, entry.name), spelled);
38197
+ visit(join17(directory, entry.name), spelled);
37992
38198
  continue;
37993
38199
  }
37994
38200
  if (!entry.isFile()) {
37995
38201
  continue;
37996
38202
  }
37997
38203
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37998
- if (CARRIES_A_UID.has(extension) && !existsSync14(join20(directory, `${entry.name}.uid`))) {
38204
+ if (CARRIES_A_UID.has(extension) && !existsSync12(join17(directory, `${entry.name}.uid`))) {
37999
38205
  found.push(spelled);
38000
38206
  }
38001
38207
  }
@@ -38023,14 +38229,14 @@ function searchProject(projectPath, options) {
38023
38229
  if (steppedOver(directory)) {
38024
38230
  return;
38025
38231
  }
38026
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38232
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
38027
38233
  if (full()) {
38028
38234
  return;
38029
38235
  }
38030
38236
  if (skipped(entry.name)) {
38031
38237
  continue;
38032
38238
  }
38033
- const entryPath = join20(directory, entry.name);
38239
+ const entryPath = join17(directory, entry.name);
38034
38240
  if (entry.isDirectory()) {
38035
38241
  visit(entryPath);
38036
38242
  continue;
@@ -38041,7 +38247,7 @@ function searchProject(projectPath, options) {
38041
38247
  }
38042
38248
  result.summary.files_searched += 1;
38043
38249
  const matches = [];
38044
- for (const [index, line] of readFileSync18(entryPath, "utf8").split(`
38250
+ for (const [index, line] of readFileSync15(entryPath, "utf8").split(`
38045
38251
  `).entries()) {
38046
38252
  if (full()) {
38047
38253
  break;
@@ -38070,37 +38276,37 @@ var init_project_scan = __esm(() => {
38070
38276
  });
38071
38277
 
38072
38278
  // src/reimport.ts
38073
- import { createHash as createHash5 } from "node:crypto";
38074
- import { existsSync as existsSync15, readFileSync as readFileSync19, rmSync as rmSync10, statSync as statSync7, utimesSync, writeFileSync as writeFileSync14 } from "node:fs";
38075
- import { basename as basename3, join as join21 } from "node:path";
38279
+ import { createHash as createHash3 } from "node:crypto";
38280
+ import { existsSync as existsSync13, readFileSync as readFileSync16, rmSync as rmSync8, statSync as statSync6, utimesSync, writeFileSync as writeFileSync11 } from "node:fs";
38281
+ import { basename as basename3, join as join18 } from "node:path";
38076
38282
  function forceNextImport(projectPath, resourcePath) {
38077
38283
  const relative = resourcePath.replace(/^res:\/\//, "");
38078
- const sidecar = join21(projectPath, `${relative}.import`);
38079
- if (!existsSync15(sidecar)) {
38284
+ const sidecar = join18(projectPath, `${relative}.import`);
38285
+ if (!existsSync13(sidecar)) {
38080
38286
  return;
38081
38287
  }
38082
- const { atime, mtimeMs } = statSync7(sidecar);
38083
- writeFileSync14(sidecar, readFileSync19(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
38288
+ const { atime, mtimeMs } = statSync6(sidecar);
38289
+ writeFileSync11(sidecar, readFileSync16(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
38084
38290
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
38085
- rmSync10(importRecord(projectPath, resourcePath), { force: true });
38291
+ rmSync8(importRecord(projectPath, resourcePath), { force: true });
38086
38292
  }
38087
38293
  function importRecord(projectPath, resourcePath) {
38088
- const digest = createHash5("md5").update(resourcePath).digest("hex");
38089
- return join21(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
38294
+ const digest = createHash3("md5").update(resourcePath).digest("hex");
38295
+ return join18(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
38090
38296
  }
38091
38297
  var init_reimport = () => {};
38092
38298
 
38093
38299
  // src/restart-note.ts
38094
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
38095
- import { dirname as dirname15, join as join22 } from "node:path";
38300
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
38301
+ import { dirname as dirname13, join as join19 } from "node:path";
38096
38302
  function restartNotePath(projectPath) {
38097
- return join22(projectPath, ".godot", "gdharness-restart.json");
38303
+ return join19(projectPath, ".godot", "gdharness-restart.json");
38098
38304
  }
38099
38305
  function noteRestartBegun(note) {
38100
38306
  const path = restartNotePath(note.projectPath);
38101
38307
  try {
38102
- mkdirSync12(dirname15(path), { recursive: true });
38103
- writeFileSync15(path, `${JSON.stringify(note, null, 2)}
38308
+ mkdirSync10(dirname13(path), { recursive: true });
38309
+ writeFileSync12(path, `${JSON.stringify(note, null, 2)}
38104
38310
  `, "utf8");
38105
38311
  } catch (error) {
38106
38312
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -38108,7 +38314,7 @@ function noteRestartBegun(note) {
38108
38314
  }
38109
38315
  function restartOwed(projectPath) {
38110
38316
  try {
38111
- const parsed = JSON.parse(readFileSync20(restartNotePath(projectPath), "utf8"));
38317
+ const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
38112
38318
  if (typeof parsed !== "object" || parsed === null) {
38113
38319
  return null;
38114
38320
  }
@@ -38123,7 +38329,7 @@ function restartOwed(projectPath) {
38123
38329
  }
38124
38330
  function restartSettled(projectPath) {
38125
38331
  try {
38126
- rmSync11(restartNotePath(projectPath), { force: true });
38332
+ rmSync9(restartNotePath(projectPath), { force: true });
38127
38333
  } catch (error) {
38128
38334
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
38129
38335
  }
@@ -38134,29 +38340,29 @@ var init_restart_note = __esm(() => {
38134
38340
 
38135
38341
  // src/run-record.ts
38136
38342
  import { execFileSync as execFileSync2 } from "node:child_process";
38137
- import { createHash as createHash6 } from "node:crypto";
38343
+ import { createHash as createHash4 } from "node:crypto";
38138
38344
  import {
38139
- mkdirSync as mkdirSync13,
38345
+ mkdirSync as mkdirSync11,
38140
38346
  openSync as openSync2,
38141
- readdirSync as readdirSync8,
38142
- readFileSync as readFileSync21,
38347
+ readdirSync as readdirSync7,
38348
+ readFileSync as readFileSync18,
38143
38349
  realpathSync as realpathSync3,
38144
- rmSync as rmSync12,
38145
- statSync as statSync8,
38146
- writeFileSync as writeFileSync16
38350
+ rmSync as rmSync10,
38351
+ statSync as statSync7,
38352
+ writeFileSync as writeFileSync13
38147
38353
  } from "node:fs";
38148
- import { basename as basename4, dirname as dirname16, join as join23, resolve as resolve5 } from "node:path";
38354
+ import { basename as basename4, dirname as dirname14, join as join20, resolve as resolve5 } from "node:path";
38149
38355
  function runsDirectory() {
38150
- return join23(runtimeDirectory(), "runs");
38356
+ return join20(runtimeDirectory(), "runs");
38151
38357
  }
38152
38358
  function realAsFarAsItExists(path) {
38153
38359
  const missing = [];
38154
38360
  let at = path;
38155
38361
  for (;; ) {
38156
38362
  try {
38157
- return join23(realpathSync3.native(at), ...missing);
38363
+ return join20(realpathSync3.native(at), ...missing);
38158
38364
  } catch {
38159
- const parent = dirname16(at);
38365
+ const parent = dirname14(at);
38160
38366
  if (parent === at) {
38161
38367
  return path;
38162
38368
  }
@@ -38168,26 +38374,26 @@ function realAsFarAsItExists(path) {
38168
38374
  function projectKey(projectPath) {
38169
38375
  const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
38170
38376
  const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
38171
- return createHash6("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
38377
+ return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
38172
38378
  }
38173
38379
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
38174
- return join23(directory, "runs", `note-${projectKey(projectPath)}.json`);
38380
+ return join20(directory, "runs", `note-${projectKey(projectPath)}.json`);
38175
38381
  }
38176
38382
  function openTranscript(startedAt) {
38177
- mkdirSync13(runsDirectory(), { recursive: true });
38178
- const path = join23(runsDirectory(), `run-${startedAt}.log`);
38383
+ mkdirSync11(runsDirectory(), { recursive: true });
38384
+ const path = join20(runsDirectory(), `run-${startedAt}.log`);
38179
38385
  return { path, fd: openSync2(path, "a") };
38180
38386
  }
38181
38387
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
38182
- return join23(directory, "runs", `editor-${projectKey(projectPath)}.json`);
38388
+ return join20(directory, "runs", `editor-${projectKey(projectPath)}.json`);
38183
38389
  }
38184
38390
  function writeEditorRunNote(note) {
38185
- mkdirSync13(runsDirectory(), { recursive: true });
38186
- writeFileSync16(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
38391
+ mkdirSync11(runsDirectory(), { recursive: true });
38392
+ writeFileSync13(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
38187
38393
  }
38188
38394
  function readEditorRunNote(projectPath) {
38189
38395
  for (const directory of runtimeDirectories()) {
38190
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join23(directory, "runs", "editor-run.json"));
38396
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join20(directory, "runs", "editor-run.json"));
38191
38397
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
38192
38398
  return note;
38193
38399
  }
@@ -38197,7 +38403,7 @@ function readEditorRunNote(projectPath) {
38197
38403
  function editorNoteAt(path) {
38198
38404
  let parsed;
38199
38405
  try {
38200
- parsed = JSON.parse(readFileSync21(path, "utf8"));
38406
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
38201
38407
  } catch {
38202
38408
  return null;
38203
38409
  }
@@ -38219,7 +38425,7 @@ function readRunRecord(projectPath) {
38219
38425
  if (own !== null) {
38220
38426
  return own;
38221
38427
  }
38222
- const shared = recordAt(join23(directory, "runs", SHARED_NOTE));
38428
+ const shared = recordAt(join20(directory, "runs", SHARED_NOTE));
38223
38429
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
38224
38430
  return shared;
38225
38431
  }
@@ -38231,13 +38437,13 @@ function everyRunRecord() {
38231
38437
  for (const directory of runtimeDirectories()) {
38232
38438
  let entries;
38233
38439
  try {
38234
- entries = readdirSync8(join23(directory, "runs"));
38440
+ entries = readdirSync7(join20(directory, "runs"));
38235
38441
  } catch {
38236
38442
  continue;
38237
38443
  }
38238
38444
  for (const entry of entries) {
38239
38445
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
38240
- const record = recordAt(join23(directory, "runs", entry));
38446
+ const record = recordAt(join20(directory, "runs", entry));
38241
38447
  if (record !== null) {
38242
38448
  found.push(record);
38243
38449
  }
@@ -38249,7 +38455,7 @@ function everyRunRecord() {
38249
38455
  function recordAt(path) {
38250
38456
  let parsed;
38251
38457
  try {
38252
- parsed = JSON.parse(readFileSync21(path, "utf8"));
38458
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
38253
38459
  } catch {
38254
38460
  return null;
38255
38461
  }
@@ -38295,7 +38501,7 @@ function runningAs(pid) {
38295
38501
  const began = startedAt(pid, null);
38296
38502
  const when = began === null ? {} : { startedAt: began };
38297
38503
  if (process.platform === "linux") {
38298
- const raw = readFileSync21(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
38504
+ const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
38299
38505
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
38300
38506
  }
38301
38507
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -38433,19 +38639,19 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
38433
38639
  }
38434
38640
  function clearRunRecord(projectPath) {
38435
38641
  for (const directory of runtimeDirectories()) {
38436
- const shared = join23(directory, "runs", SHARED_NOTE);
38642
+ const shared = join20(directory, "runs", SHARED_NOTE);
38437
38643
  const sharedRecord = recordAt(shared);
38438
38644
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
38439
38645
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
38440
38646
  try {
38441
- rmSync12(path, { force: true });
38647
+ rmSync10(path, { force: true });
38442
38648
  } catch {}
38443
38649
  }
38444
38650
  }
38445
38651
  }
38446
38652
  function ageMs(path, now) {
38447
38653
  try {
38448
- return now - statSync8(path).mtimeMs;
38654
+ return now - statSync7(path).mtimeMs;
38449
38655
  } catch {
38450
38656
  return 0;
38451
38657
  }
@@ -38455,7 +38661,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38455
38661
  const kept = new Set(everyRunRecord().map((record) => record.transcript));
38456
38662
  let entries;
38457
38663
  try {
38458
- entries = readdirSync8(directory);
38664
+ entries = readdirSync7(directory);
38459
38665
  } catch {
38460
38666
  return;
38461
38667
  }
@@ -38463,12 +38669,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38463
38669
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
38464
38670
  continue;
38465
38671
  }
38466
- const path = join23(directory, entry);
38672
+ const path = join20(directory, entry);
38467
38673
  if (kept.has(path) || ageMs(path, now) < keepMs) {
38468
38674
  continue;
38469
38675
  }
38470
38676
  try {
38471
- rmSync12(path);
38677
+ rmSync10(path);
38472
38678
  } catch {}
38473
38679
  }
38474
38680
  }
@@ -38480,29 +38686,29 @@ var init_run_record = __esm(() => {
38480
38686
  });
38481
38687
 
38482
38688
  // src/server.ts
38483
- import { execFile as execFile7, spawn as spawn2 } from "node:child_process";
38689
+ import { execFile as execFile5, spawn as spawn2 } from "node:child_process";
38484
38690
  import { randomUUID as randomUUID2 } from "node:crypto";
38485
38691
  import {
38486
38692
  appendFileSync,
38487
38693
  closeSync as closeSync2,
38488
38694
  constants,
38489
38695
  copyFileSync,
38490
- existsSync as existsSync16,
38696
+ existsSync as existsSync14,
38491
38697
  fstatSync,
38492
- mkdirSync as mkdirSync14,
38493
- mkdtempSync as mkdtempSync3,
38698
+ mkdirSync as mkdirSync12,
38699
+ mkdtempSync as mkdtempSync2,
38494
38700
  openSync as openSync3,
38495
- readdirSync as readdirSync9,
38496
- readFileSync as readFileSync22,
38701
+ readdirSync as readdirSync8,
38702
+ readFileSync as readFileSync19,
38497
38703
  readSync as readSync2,
38498
- rmdirSync,
38499
- statSync as statSync9
38704
+ rmdirSync as rmdirSync2,
38705
+ statSync as statSync8
38500
38706
  } from "node:fs";
38501
- import { tmpdir as tmpdir5 } from "node:os";
38502
- import { basename as basename5, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute3, join as join24, normalize as normalize3, relative as relative3 } from "node:path";
38707
+ import { tmpdir as tmpdir4 } from "node:os";
38708
+ import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute3, join as join21, normalize as normalize2, relative as relative3 } from "node:path";
38503
38709
  import { setTimeout as delay3 } from "node:timers/promises";
38504
- import { fileURLToPath as fileURLToPath6 } from "node:url";
38505
- import { promisify as promisify7 } from "node:util";
38710
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
38711
+ import { promisify as promisify5 } from "node:util";
38506
38712
  function captureDestinationRefusal(outputPath, projects) {
38507
38713
  if (/^(res|user):\/\//.test(outputPath)) {
38508
38714
  return `outputPath ${outputPath} is a Godot path; a capture is saved outside the project, at an absolute path on this machine.`;
@@ -38513,16 +38719,16 @@ function captureDestinationRefusal(outputPath, projects) {
38513
38719
  if (extname2(outputPath).toLowerCase() !== ".png") {
38514
38720
  return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
38515
38721
  }
38516
- const directory = realPathOr(dirname17(outputPath));
38517
- if (!existsSync16(directory)) {
38518
- return `outputPath's directory ${dirname17(outputPath)} does not exist; a capture creates no directories.`;
38722
+ const directory = realPathOr(dirname15(outputPath));
38723
+ if (!existsSync14(directory)) {
38724
+ return `outputPath's directory ${dirname15(outputPath)} does not exist; a capture creates no directories.`;
38519
38725
  }
38520
- const where = join24(directory, basename5(outputPath));
38726
+ const where = join21(directory, basename5(outputPath));
38521
38727
  const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
38522
38728
  if (inside !== undefined) {
38523
38729
  return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
38524
38730
  }
38525
- if (existsSync16(where)) {
38731
+ if (existsSync14(where)) {
38526
38732
  return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
38527
38733
  }
38528
38734
  return null;
@@ -38887,15 +39093,24 @@ function changedSettings(before, after) {
38887
39093
  return [...names].filter((name) => JSON.stringify(before.get(name)) !== JSON.stringify(after.get(name))).sort();
38888
39094
  }
38889
39095
  function scriptErrorsNote(errors) {
38890
- const first = new Map;
39096
+ const causes = new Map;
38891
39097
  for (const error of errors) {
38892
- if (!first.has(error.path)) {
38893
- first.set(error.path, error);
39098
+ if (!error.message.includes(DEPENDED_FAILED) && !causes.has(error.path)) {
39099
+ causes.set(error.path, error);
38894
39100
  }
38895
39101
  }
38896
- const scripts = [...first.values()].map((error) => `${error.path}:${error.line} ${error.message}${error.message.endsWith(".") ? "" : "."}`).join(" ");
38897
- const more = errors.length - first.size;
38898
- return `No tests ran: gdUnit4 could not load ${first.size === 1 ? "one script" : `${first.size} scripts`} while looking for suites, and it runs no suite at all when one fails to load. ${scripts}${more > 0 ? ` ${more === 1 ? "One more error" : `${more} more errors`} in ${first.size === 1 ? "that script" : "those scripts"} ${more === 1 ? "is" : "are"} under scriptErrors.` : ""}`;
39102
+ const dependents = [...new Set(errors.map((error) => error.path))].filter((path) => !causes.has(path));
39103
+ const scripts = causes.size + dependents.length;
39104
+ const named = [...causes.values()].map((error) => `${error.line === null ? error.path : `${error.path}:${error.line}`} ${error.message}${/[.!?]\)?$/.test(error.message) ? "" : "."}`).join(" ");
39105
+ const following = dependents.length === 0 ? "" : causes.size === 0 ? `${dependents.join(", ")} ${dependents.length === 1 ? "depends" : "depend"} on a script that did not compile, and gdUnit4 named none with an error of its own: script_diagnostics on one of them finds it.` : `${dependents.length === 1 ? "One more" : `${dependents.length} more`} failed only because a script ${dependents.length === 1 ? "it depends" : "they depend"} on did: ${dependents.join(", ")}.`;
39106
+ const more = errors.filter((error) => !error.message.includes(DEPENDED_FAILED)).length - causes.size;
39107
+ const rest = more > 0 ? `${more === 1 ? "One more error" : `${more} more errors`} in ${causes.size === 1 ? "that script" : "those scripts"} ${more === 1 ? "is" : "are"} under scriptErrors.` : "";
39108
+ return [
39109
+ `No tests ran: gdUnit4 could not load ${scripts === 1 ? "one script" : `${scripts} scripts`} while looking for suites, and it runs no suite at all when one fails to load.`,
39110
+ named,
39111
+ following,
39112
+ rest
39113
+ ].filter((part) => part !== "").join(" ");
38899
39114
  }
38900
39115
  function timedOutVerdict(timeoutMs, silentForMs) {
38901
39116
  const quiet = Math.round(silentForMs);
@@ -38952,13 +39167,13 @@ function aboveTheRunner(entry) {
38952
39167
  }
38953
39168
  function elsewhereIn(projectPath, asked) {
38954
39169
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
38955
- if (wanted === "" || existsSync16(join24(projectPath, wanted))) {
39170
+ if (wanted === "" || existsSync14(join21(projectPath, wanted))) {
38956
39171
  return "";
38957
39172
  }
38958
39173
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
38959
39174
  let near = [];
38960
39175
  try {
38961
- near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
39176
+ near = readdirSync8(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
38962
39177
  } catch {
38963
39178
  return "";
38964
39179
  }
@@ -38966,7 +39181,7 @@ function elsewhereIn(projectPath, asked) {
38966
39181
  return near.length === 0 ? "" : ` The project does have ${there}.`;
38967
39182
  }
38968
39183
  function hasMainScene(projectFile) {
38969
- const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
39184
+ const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
38970
39185
  return typeof scene === "string" && scene !== "";
38971
39186
  }
38972
39187
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -38974,13 +39189,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
38974
39189
  return false;
38975
39190
  }
38976
39191
  try {
38977
- return readFileSync22(join24(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
39192
+ return readFileSync19(join21(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
38978
39193
  } catch {
38979
39194
  return false;
38980
39195
  }
38981
39196
  }
38982
39197
  function editorPlaysHeadless(projectFile) {
38983
- const runArgs = parseProjectGodot(readFileSync22(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
39198
+ const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
38984
39199
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
38985
39200
  }
38986
39201
  function camelCased(params) {
@@ -38998,8 +39213,8 @@ function needsItsOwnProcess(asked) {
38998
39213
 
38999
39214
  class GodotServer {
39000
39215
  mcp;
39001
- locator = new GodotLocator2;
39002
- operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
39216
+ locator = new GodotLocator;
39217
+ operationsScript = join21(__dirname2, "godot", "operations", "godot_operations.gd");
39003
39218
  godotBridge;
39004
39219
  tools = buildToolDefinitions();
39005
39220
  activeProcess = null;
@@ -39058,7 +39273,7 @@ class GodotServer {
39058
39273
  async run() {
39059
39274
  const located = await this.locator.find();
39060
39275
  if (!located.ok) {
39061
- console.error(`[SERVER] ${GodotLocator2.refusal(located).message} Until that is fixed every tool that runs the engine will say so.`);
39276
+ console.error(`[SERVER] ${GodotLocator.refusal(located).message} Until that is fixed every tool that runs the engine will say so.`);
39062
39277
  } else {
39063
39278
  console.error(`[SERVER] Using Godot at: ${located.path}`);
39064
39279
  }
@@ -39089,7 +39304,7 @@ class GodotServer {
39089
39304
  return;
39090
39305
  }
39091
39306
  const wanted = this.godotBridge.configuredPort;
39092
- const announced = readAnnouncement2(announcementPath2(this.ownProject));
39307
+ const announced = readAnnouncement(announcementPath(this.ownProject));
39093
39308
  if (announced === null || announced.pid === process.pid || announced.port !== wanted || !alive(announced.pid)) {
39094
39309
  return;
39095
39310
  }
@@ -39175,7 +39390,7 @@ class GodotServer {
39175
39390
  if (this.ownProject === null) {
39176
39391
  return null;
39177
39392
  }
39178
- const announced = readAnnouncement2(announcementPath2(this.ownProject));
39393
+ const announced = readAnnouncement(announcementPath(this.ownProject));
39179
39394
  if (announced === null || announced.pid === process.pid) {
39180
39395
  return null;
39181
39396
  }
@@ -39298,7 +39513,7 @@ class GodotServer {
39298
39513
  content: [
39299
39514
  {
39300
39515
  type: "text",
39301
- text: defectReport2(where, error, {
39516
+ text: defectReport(where, error, {
39302
39517
  seenBefore,
39303
39518
  godotVersion: this.lastGodotVersion ?? undefined
39304
39519
  })
@@ -39640,11 +39855,16 @@ class GodotServer {
39640
39855
  return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args);
39641
39856
  case "runtime_input":
39642
39857
  if (op === "click") {
39858
+ if (readNonEmptyString(args, "nodePath") === undefined && readNonEmptyString(args, "says") === undefined) {
39859
+ return this.createErrorResponse("runtime_input click needs nodePath, or says with the words on the control to click.");
39860
+ }
39643
39861
  return await this.handleRuntimeCommand("click", {
39644
39862
  ...whichGame(args),
39645
39863
  path: readNonEmptyString(args, "nodePath") ?? "",
39646
39864
  button: readString(args, "button") ?? "left",
39647
- double: readBoolean(args, "doubleClick") ?? false
39865
+ double: readBoolean(args, "doubleClick") ?? false,
39866
+ ...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
39867
+ ...args["index"] === undefined ? {} : { index: args["index"] }
39648
39868
  });
39649
39869
  }
39650
39870
  if (op === "choose") {
@@ -39703,7 +39923,7 @@ class GodotServer {
39703
39923
  async engine() {
39704
39924
  const located = await this.locator.find();
39705
39925
  if (!located.ok) {
39706
- const refusal = GodotLocator2.refusal(located);
39926
+ const refusal = GodotLocator.refusal(located);
39707
39927
  return { ok: false, response: this.createErrorResponse(refusal.message, [...refusal.advice]) };
39708
39928
  }
39709
39929
  return { ok: true, value: located.path };
@@ -39713,8 +39933,8 @@ class GodotServer {
39713
39933
  if (path === undefined) {
39714
39934
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
39715
39935
  }
39716
- const file = join24(path, "project.godot");
39717
- if (!existsSync16(file)) {
39936
+ const file = join21(path, "project.godot");
39937
+ if (!existsSync14(file)) {
39718
39938
  return {
39719
39939
  ok: false,
39720
39940
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -40212,7 +40432,7 @@ class GodotServer {
40212
40432
  }
40213
40433
  const scanned = await this.untilTheEditorsScanIsWritten(projectPath);
40214
40434
  this.logDebug(`Running ${operation} in ${projectPath}: ${JSON.stringify(params)}`);
40215
- const outcome = await runOperation2({ godotPath: engine.value, script: this.operationsScript, debug: GODOT_DEBUG_MODE_DEFAULT }, operation, params, projectPath);
40435
+ const outcome = await runOperation({ godotPath: engine.value, script: this.operationsScript, debug: GODOT_DEBUG_MODE_DEFAULT }, operation, params, projectPath);
40216
40436
  return withScanWait(outcome, scanned);
40217
40437
  }
40218
40438
  answer(outcome) {
@@ -40242,7 +40462,7 @@ class GodotServer {
40242
40462
  if (!engine.ok) {
40243
40463
  return engine.response;
40244
40464
  }
40245
- const application = parseProjectGodot(readFileSync22(project.value.file, "utf8"))["application"] ?? {};
40465
+ const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
40246
40466
  const name = application["config/name"];
40247
40467
  const mainScene = application["run/main_scene"];
40248
40468
  const info = {
@@ -40303,18 +40523,18 @@ class GodotServer {
40303
40523
  if (!engine.ok) {
40304
40524
  return engine.response;
40305
40525
  }
40306
- const folder = dirname17(output.absolutePath);
40526
+ const folder = dirname15(output.absolutePath);
40307
40527
  try {
40308
- mkdirSync14(folder, { recursive: true });
40528
+ mkdirSync12(folder, { recursive: true });
40309
40529
  } catch (error) {
40310
- return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
40530
+ return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
40311
40531
  }
40312
40532
  const debug = readBoolean(args, "debug") ?? false;
40313
- const exportLogs = mkdtempSync3(join24(tmpdir5(), "gdharness-export-"));
40533
+ const exportLogs = mkdtempSync2(join21(tmpdir4(), "gdharness-export-"));
40314
40534
  const exportArgs = [
40315
40535
  "--headless",
40316
40536
  "--log-file",
40317
- join24(exportLogs, "engine.log"),
40537
+ join21(exportLogs, "engine.log"),
40318
40538
  "--path",
40319
40539
  project.value.path,
40320
40540
  debug ? "--export-debug" : "--export-release",
@@ -40325,7 +40545,7 @@ class GodotServer {
40325
40545
  const log = new GameLog;
40326
40546
  let ending = { exitCode: 0, exitSignal: null, failure: null };
40327
40547
  try {
40328
- const { stdout, stderr } = await run7(engine.value, exportArgs, {
40548
+ const { stdout, stderr } = await run5(engine.value, exportArgs, {
40329
40549
  timeout: 300000,
40330
40550
  maxBuffer: 64 * 1024 * 1024,
40331
40551
  signal: callSignal()
@@ -40346,7 +40566,7 @@ class GodotServer {
40346
40566
  log.finish();
40347
40567
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
40348
40568
  const verdict = {
40349
- exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync16(output.absolutePath),
40569
+ exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync14(output.absolutePath),
40350
40570
  preset,
40351
40571
  outputPath: output.relativePath,
40352
40572
  debug,
@@ -40382,7 +40602,7 @@ class GodotServer {
40382
40602
  return contained.response;
40383
40603
  }
40384
40604
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
40385
- if (!existsSync16(join24(project.value.path, runner))) {
40605
+ if (!existsSync14(join21(project.value.path, runner))) {
40386
40606
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
40387
40607
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
40388
40608
  ]);
@@ -40418,7 +40638,7 @@ class GodotServer {
40418
40638
  ];
40419
40639
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
40420
40640
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
40421
- const userData = mkdtempSync3(join24(tmpdir5(), "gdharness-tests-"));
40641
+ const userData = mkdtempSync2(join21(tmpdir4(), "gdharness-tests-"));
40422
40642
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
40423
40643
  const console2 = [];
40424
40644
  let lastSaid = Date.now();
@@ -40445,21 +40665,21 @@ class GodotServer {
40445
40665
  resolve(false);
40446
40666
  });
40447
40667
  });
40448
- const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
40668
+ const reportsDir = join21(project.value.path, ".godot", ...ours.split("/"));
40449
40669
  let report = null;
40450
40670
  let reportProblem = null;
40451
40671
  try {
40452
- const written = existsSync16(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
40672
+ const written = existsSync14(reportsDir) ? readdirSync8(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
40453
40673
  const newest = written.at(-1);
40454
40674
  if (newest !== undefined) {
40455
- report = parseJUnit(readFileSync22(join24(reportsDir, newest, "results.xml"), "utf8"));
40675
+ report = parseJUnit(readFileSync19(join21(reportsDir, newest, "results.xml"), "utf8"));
40456
40676
  }
40457
40677
  } catch (error) {
40458
40678
  reportProblem = errorMessage(error);
40459
40679
  } finally {
40460
40680
  discard(reportsDir, userData);
40461
40681
  try {
40462
- rmdirSync(dirname17(reportsDir));
40682
+ rmdirSync2(dirname15(reportsDir));
40463
40683
  } catch {}
40464
40684
  }
40465
40685
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
@@ -40481,6 +40701,12 @@ class GodotServer {
40481
40701
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
40482
40702
  const verdict = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
40483
40703
  const savesNote = savesStayPut() && nothingRan === null ? SAVES_NOT_MOVED_NOTE : undefined;
40704
+ const marker = readNonEmptyString(args, "printed");
40705
+ const echoed = marker === undefined ? undefined : run.log.select({ severity: "info", sinceLastCall: false, contains: marker, limit: 200 });
40706
+ const printedLines = echoed === undefined ? {} : {
40707
+ printed: echoed.entries.map((entry) => entry.text),
40708
+ ...echoed.omitted > 0 ? { printedOmitted: echoed.omitted } : {}
40709
+ };
40484
40710
  if (report === null) {
40485
40711
  const note = nothingRan !== null ? `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : `The test run wrote no report (${verdict}${reportProblem ? `; ${reportProblem}` : ""}).`;
40486
40712
  return {
@@ -40499,6 +40725,7 @@ class GodotServer {
40499
40725
  ...scriptErrors.length > 0 ? { scriptErrors } : {},
40500
40726
  arguments: cmdArgs,
40501
40727
  entries: forAnswer(printed.slice(0, 60).map(aboveTheRunner)),
40728
+ ...printedLines,
40502
40729
  savesNote,
40503
40730
  ...scanned
40504
40731
  }, null, 2)
@@ -40535,6 +40762,7 @@ class GodotServer {
40535
40762
  warnings: warnings.length > 0 ? warnings : undefined,
40536
40763
  orphans: orphans.total > 0 ? orphans.total : undefined,
40537
40764
  notRun: notRun > 0 ? notRun : undefined,
40765
+ ...printedLines,
40538
40766
  savesNote,
40539
40767
  ...scanned,
40540
40768
  note: verdict.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRun > 0 ? `This run stopped at the first failure in each suite it failed in, so ${notRun} case${notRun === 1 ? "" : "s"} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : undefined,
@@ -40558,7 +40786,7 @@ class GodotServer {
40558
40786
  }
40559
40787
  async godotVersion(godotPath) {
40560
40788
  try {
40561
- const { stdout } = await run7(godotPath, ["--version"], { timeout: 1e4 });
40789
+ const { stdout } = await run5(godotPath, ["--version"], { timeout: 1e4 });
40562
40790
  this.lastGodotVersion = stdout.trim();
40563
40791
  return this.lastGodotVersion;
40564
40792
  } catch (error) {
@@ -40602,11 +40830,11 @@ class GodotServer {
40602
40830
  const cached = cachedClasses(projectPath);
40603
40831
  const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
40604
40832
  const contained = resolveWithinProject(projectPath, resourcePath);
40605
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
40833
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
40606
40834
  return null;
40607
40835
  }
40608
40836
  try {
40609
- return readFileSync22(contained.absolutePath, "utf8");
40837
+ return readFileSync19(contained.absolutePath, "utf8");
40610
40838
  } catch {
40611
40839
  return null;
40612
40840
  }
@@ -40656,11 +40884,11 @@ class GodotServer {
40656
40884
  }
40657
40885
  sourceOfClass(projectPath, resourcePath) {
40658
40886
  const contained = resolveWithinProject(projectPath, resourcePath);
40659
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
40887
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
40660
40888
  return null;
40661
40889
  }
40662
40890
  try {
40663
- return readFileSync22(contained.absolutePath, "utf8");
40891
+ return readFileSync19(contained.absolutePath, "utf8");
40664
40892
  } catch {
40665
40893
  return null;
40666
40894
  }
@@ -40906,7 +41134,7 @@ class GodotServer {
40906
41134
  godot: {
40907
41135
  path: located.ok ? located.path : null,
40908
41136
  version: located.ok ? await this.godotVersion(located.path) : null,
40909
- problem: located.ok ? undefined : GodotLocator2.refusal(located).message
41137
+ problem: located.ok ? undefined : GodotLocator.refusal(located).message
40910
41138
  },
40911
41139
  game: {
40912
41140
  processActive: active,
@@ -40929,7 +41157,7 @@ class GodotServer {
40929
41157
  return null;
40930
41158
  }
40931
41159
  try {
40932
- return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8"));
41160
+ return settingKeys(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
40933
41161
  } catch {
40934
41162
  return null;
40935
41163
  }
@@ -41182,7 +41410,7 @@ class GodotServer {
41182
41410
  }
41183
41411
  async runtimeUp(projectPath, before, budget, withArgs) {
41184
41412
  const budgetMs = budget.ms;
41185
- if (!existsSync16(join24(projectPath, RUNTIME_AUTOLOAD2.path))) {
41413
+ if (!existsSync14(join21(projectPath, RUNTIME_AUTOLOAD.path))) {
41186
41414
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
41187
41415
  }
41188
41416
  let asking = null;
@@ -41245,7 +41473,7 @@ class GodotServer {
41245
41473
  }
41246
41474
  let announcedAt;
41247
41475
  try {
41248
- announcedAt = statSync9(endpoint.file).mtimeMs;
41476
+ announcedAt = statSync8(endpoint.file).mtimeMs;
41249
41477
  } catch {
41250
41478
  return;
41251
41479
  }
@@ -41920,15 +42148,18 @@ class GodotServer {
41920
42148
  "editor_output with no op answers about the game a run is playing, which is a different log"
41921
42149
  ]);
41922
42150
  }
41923
- const severity = readString(args, "severity");
42151
+ const asked = readString(args, "severity");
42152
+ const severity = asked === "error" || asked === "warning" ? asked : "info";
42153
+ const contains = readNonEmptyString(args, "contains");
41924
42154
  const selected = console2.log.select({
41925
- severity: severity === "error" || severity === "warning" ? severity : "info",
42155
+ severity,
41926
42156
  sinceLastCall: false,
41927
- contains: readNonEmptyString(args, "contains"),
42157
+ contains,
41928
42158
  limit: readPositiveNumber(args, "limit") ?? 200
41929
42159
  });
41930
42160
  const everything = console2.log.everything();
41931
- const grouped = bursts(everything, readNonNegativeNumber(args, "before") ?? 3);
42161
+ const filtered = severity !== "info" || contains !== undefined;
42162
+ const grouped = bursts(everything, readNonNegativeNumber(args, "before") ?? 3, filtered ? (entry) => answersTo(entry, severity, contains) : undefined);
41932
42163
  const shown = grouped.slice(0, MOST_GROUPS);
41933
42164
  return this.jsonTextResponse({
41934
42165
  editorPid: editorPid ?? undefined,
@@ -41996,7 +42227,7 @@ class GodotServer {
41996
42227
  notes.push(endedWithoutACode({
41997
42228
  throughEditor: run.throughEditor,
41998
42229
  announced: announced !== undefined || run.announcedPid !== undefined,
41999
- couldAnnounce: run.projectPath !== null && existsSync16(join24(run.projectPath, RUNTIME_AUTOLOAD2.path)),
42230
+ couldAnnounce: run.projectPath !== null && existsSync14(join21(run.projectPath, RUNTIME_AUTOLOAD.path)),
42000
42231
  endedHere: typeof run.endedHere === "string",
42001
42232
  pickedUp: run.pickedUp === true
42002
42233
  }));
@@ -42509,7 +42740,7 @@ class GodotServer {
42509
42740
  async ownRunOnItsWay(projectPath, budgetMs) {
42510
42741
  const run = this.currentRun();
42511
42742
  const project = projectPath ?? run?.projectPath ?? null;
42512
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync16(join24(project, RUNTIME_AUTOLOAD2.path)) || !await this.runStillGoing(run)) {
42743
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync14(join21(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
42513
42744
  return null;
42514
42745
  }
42515
42746
  const already = runtimesAnnounced().running;
@@ -42518,7 +42749,7 @@ class GodotServer {
42518
42749
  return !run.announcedBefore.has(one.pid);
42519
42750
  }
42520
42751
  try {
42521
- return statSync9(one.file).mtimeMs >= run.startedAt;
42752
+ return statSync8(one.file).mtimeMs >= run.startedAt;
42522
42753
  } catch {
42523
42754
  return false;
42524
42755
  }
@@ -42588,8 +42819,8 @@ class GodotServer {
42588
42819
  return this.createErrorResponse(refusal);
42589
42820
  }
42590
42821
  }
42591
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join24(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
42592
- const screenshotPath = screenshotDir ? join24(screenshotDir, "capture.png") : null;
42822
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join21(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
42823
+ const screenshotPath = screenshotDir ? join21(screenshotDir, "capture.png") : null;
42593
42824
  try {
42594
42825
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
42595
42826
  if (!reply.ok && reply.requestId !== undefined) {
@@ -42613,7 +42844,7 @@ class GodotServer {
42613
42844
  if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
42614
42845
  return this.createErrorResponse(`The game answered a capture with ${JSON.stringify(payload)}`);
42615
42846
  }
42616
- if (normalize3(returnedPath) !== normalize3(screenshotPath)) {
42847
+ if (normalize2(returnedPath) !== normalize2(screenshotPath)) {
42617
42848
  return this.createErrorResponse(`Rejected screenshot file path outside the managed capture path: '${returnedPath}'`);
42618
42849
  }
42619
42850
  const dimensions = `${readNumber(payload, "width") ?? 0}x${readNumber(payload, "height") ?? 0} ${readString(payload, "format") ?? "unknown"}`;
@@ -42630,7 +42861,7 @@ class GodotServer {
42630
42861
  type: "text",
42631
42862
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}`
42632
42863
  },
42633
- { type: "image", data: readFileSync22(screenshotPath).toString("base64"), mimeType: "image/png" }
42864
+ { type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
42634
42865
  ]
42635
42866
  };
42636
42867
  } finally {
@@ -42730,7 +42961,7 @@ async function runGodotServer() {
42730
42961
  const server = new GodotServer;
42731
42962
  await server.run();
42732
42963
  }
42733
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
42964
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run5, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
42734
42965
  var init_server3 = __esm(() => {
42735
42966
  init_mcp();
42736
42967
  init_stdio2();
@@ -42770,8 +43001,8 @@ var init_server3 = __esm(() => {
42770
43001
  init_setup();
42771
43002
  init_tool_definitions();
42772
43003
  init_update_check();
42773
- run7 = promisify7(execFile7);
42774
- __dirname2 = dirname17(fileURLToPath6(import.meta.url));
43004
+ run5 = promisify5(execFile5);
43005
+ __dirname2 = dirname15(fileURLToPath5(import.meta.url));
42775
43006
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
42776
43007
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
42777
43008
  VALUE_NAMES = {
@@ -42817,128 +43048,11 @@ var init_server3 = __esm(() => {
42817
43048
  });
42818
43049
 
42819
43050
  // src/cli.ts
42820
- import { existsSync as existsSync17 } from "fs";
42821
- import { join as join25, resolve as resolve6 } from "path";
42822
-
42823
- // src/bridge-announce.ts
43051
+ init_bridge_announce();
42824
43052
  init_errors();
42825
- init_process_children();
42826
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
42827
- import { dirname, join } from "node:path";
42828
- function announcementPath(projectPath) {
42829
- return join(projectPath, ".godot", "gdharness-bridge.json");
42830
- }
42831
- function readAnnouncement(path) {
42832
- try {
42833
- const parsed = JSON.parse(readFileSync(path, "utf8"));
42834
- if (typeof parsed !== "object" || parsed === null) {
42835
- return null;
42836
- }
42837
- const said = parsed;
42838
- if (typeof said.port !== "number" || typeof said.pid !== "number") {
42839
- return null;
42840
- }
42841
- return said;
42842
- } catch {
42843
- return null;
42844
- }
42845
- }
42846
- function serverBehind(announcement, startedAt) {
42847
- if (announcement === null || typeof announcement.version !== "string" || startedAt === undefined) {
42848
- return null;
42849
- }
42850
- const announcedAt = Date.parse(announcement.startedAt);
42851
- if (Number.isNaN(announcedAt) || startedAt > announcedAt + 1000) {
42852
- return null;
42853
- }
42854
- return { version: announcement.version, pid: announcement.pid };
42855
- }
42856
- function serverServing(projectPath) {
42857
- const announcement = readAnnouncement(announcementPath(projectPath));
42858
- if (announcement === null) {
42859
- return null;
42860
- }
42861
- return serverBehind(announcement, startTimesOf([announcement.pid]).get(announcement.pid));
42862
- }
42863
-
42864
- // src/errors.ts
42865
- class Refusal2 extends Error {
42866
- }
42867
-
42868
- // src/godot-path.ts
42869
- init_detection();
42870
- init_errors();
42871
- import { execFile as execFile2 } from "node:child_process";
42872
- import { existsSync as existsSync2 } from "node:fs";
42873
- import { normalize } from "node:path";
42874
- import { promisify as promisify2 } from "node:util";
42875
- var run2 = promisify2(execFile2);
42876
-
42877
- class GodotLocator {
42878
- answered = new Map;
42879
- found = null;
42880
- async find() {
42881
- if (this.found !== null && await this.whyNot(this.found) === null) {
42882
- return { ok: true, path: this.found };
42883
- }
42884
- this.found = null;
42885
- const named = envValue("GODOT_PATH");
42886
- if (named !== undefined) {
42887
- const path = normalize(named);
42888
- const reason = await this.whyNot(path);
42889
- if (reason === null) {
42890
- this.found = path;
42891
- return { ok: true, path };
42892
- }
42893
- return { ok: false, named: path, reason };
42894
- }
42895
- for (const candidate of godotCandidates(process.platform, resolveHomeDirectory())) {
42896
- const path = normalize(candidate);
42897
- if (await this.whyNot(path) === null) {
42898
- this.found = path;
42899
- return { ok: true, path };
42900
- }
42901
- }
42902
- return { ok: false, named: null };
42903
- }
42904
- static refusal(located) {
42905
- if (located.named === null) {
42906
- return {
42907
- message: "No Godot executable found.",
42908
- advice: [
42909
- "Set GODOT_PATH to the Godot 4 executable",
42910
- "Or install Godot where it is usually found, such as /usr/bin/godot or C:\\Program Files\\Godot"
42911
- ]
42912
- };
42913
- }
42914
- return {
42915
- message: `GODOT_PATH is set to ${located.named}, which ${located.reason ?? "does not answer"}.`,
42916
- advice: [
42917
- "Point GODOT_PATH at a Godot 4 executable that answers --version",
42918
- "Or unset it, and the usual install locations are searched instead"
42919
- ]
42920
- };
42921
- }
42922
- async whyNot(path) {
42923
- const known = this.answered.get(path);
42924
- if (known !== undefined) {
42925
- return known;
42926
- }
42927
- let reason;
42928
- if (path !== "godot" && !existsSync2(path)) {
42929
- reason = "does not exist";
42930
- } else {
42931
- try {
42932
- await run2(path, ["--version"]);
42933
- reason = null;
42934
- } catch (error) {
42935
- reason = `does not answer --version: ${errorMessage(error)}`;
42936
- }
42937
- }
42938
- this.answered.set(path, reason);
42939
- return reason;
42940
- }
42941
- }
43053
+ init_godot_path();
43054
+ import { existsSync as existsSync15 } from "node:fs";
43055
+ import { join as join22, resolve as resolve6 } from "node:path";
42942
43056
 
42943
43057
  // src/harnesses.ts
42944
43058
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
@@ -43803,204 +43917,9 @@ function taken(harness, path, held, stripped) {
43803
43917
  return { harness, path, action: "removed" };
43804
43918
  }
43805
43919
 
43806
- // src/headless.ts
43807
- init_call_signal();
43808
- init_game_log();
43809
- init_scratch();
43810
- import { execFile as execFile3 } from "node:child_process";
43811
- import { mkdtempSync, writeFileSync as writeFileSync3 } from "node:fs";
43812
- import { tmpdir } from "node:os";
43813
- import { join as join5 } from "node:path";
43814
- import { promisify as promisify3 } from "node:util";
43815
- var run3 = promisify3(execFile3);
43816
- function snakeCased(params) {
43817
- const result = emptyRecord();
43818
- for (const [key, value] of Object.entries(params)) {
43819
- const name = key.startsWith("_") ? key : key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
43820
- result[name] = value;
43821
- }
43822
- return result;
43823
- }
43824
- function lastJsonObject(stdout) {
43825
- const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{"));
43826
- for (const line of lines.reverse()) {
43827
- try {
43828
- const parsed = JSON.parse(line);
43829
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
43830
- return parsed;
43831
- }
43832
- } catch {}
43833
- }
43834
- return null;
43835
- }
43836
- function problems(stderr) {
43837
- const log = new GameLog;
43838
- log.append("stderr", stderr);
43839
- log.finish();
43840
- return log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
43841
- }
43842
- function reason(stdout, stderr) {
43843
- const own = stderr.split(/\r?\n/).filter((line) => line.startsWith("[ERROR] ")).map((line) => line.slice("[ERROR] ".length));
43844
- if (own.length > 0) {
43845
- return own.join("; ");
43846
- }
43847
- const engine = problems(stderr).filter((entry) => entry.severity === "error").map((entry) => entry.text);
43848
- if (engine.length > 0) {
43849
- return engine.join("; ");
43850
- }
43851
- return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
43852
- }
43853
- async function runOperation(engine, operation, params, projectPath) {
43854
- const paramsDir = mkdtempSync(join5(tmpdir(), "gdharness-params-"));
43855
- const paramsFile = join5(paramsDir, `${operation}.json`);
43856
- writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
43857
- const args = [
43858
- "--headless",
43859
- "--log-file",
43860
- join5(paramsDir, "engine.log"),
43861
- "--path",
43862
- projectPath,
43863
- "--script",
43864
- engine.script,
43865
- operation,
43866
- `@file:${paramsFile}`,
43867
- ...engine.debug ? ["--debug-godot"] : []
43868
- ];
43869
- let stdout;
43870
- let stderr;
43871
- try {
43872
- ({ stdout, stderr } = await run3(engine.godotPath, args, { signal: callSignal() }));
43873
- } catch (error) {
43874
- if (error instanceof Error && "stdout" in error && "stderr" in error) {
43875
- const failed = error;
43876
- return {
43877
- ok: false,
43878
- message: `${operation} failed (exit ${failed.code ?? "unknown"}): ${reason(failed.stdout, failed.stderr)}`,
43879
- messages: problems(failed.stderr)
43880
- };
43881
- }
43882
- return {
43883
- ok: false,
43884
- message: `${operation} could not be run: ${error instanceof Error ? error.message : String(error)}`,
43885
- messages: []
43886
- };
43887
- } finally {
43888
- discard(paramsDir);
43889
- }
43890
- const payload = lastJsonObject(stdout);
43891
- if (payload === null) {
43892
- return {
43893
- ok: false,
43894
- message: `${operation} produced no result: ${reason(stdout, stderr)}`,
43895
- messages: problems(stderr)
43896
- };
43897
- }
43898
- return { ok: true, payload, messages: problems(stderr) };
43899
- }
43900
-
43901
- // src/issues.ts
43902
- init_errors();
43903
- init_server_version();
43904
- import { createHash } from "node:crypto";
43905
- import process4 from "node:process";
43906
- var NEW_ISSUE = "https://github.com/Aureliolo/gdharness/issues/new";
43907
- var ENHANCEMENT_URL = `${NEW_ISSUE}?template=feature_request.md`;
43908
- function runtime() {
43909
- const versions = process4.versions;
43910
- const bun = versions["bun"];
43911
- return bun === undefined ? `node ${process4.versions.node}` : `bun ${bun}`;
43912
- }
43913
- function defectSignature(where, message) {
43914
- const normalised = `${where}
43915
- ${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
43916
- return createHash("sha256").update(normalised).digest("hex").slice(0, 8);
43917
- }
43918
- function facts(where, message, godotVersion) {
43919
- return [
43920
- ["Where", where],
43921
- ["Error", message],
43922
- ["Version", `gdharness ${SERVER_VERSION}`],
43923
- ["Runtime", `${runtime()}, ${process4.platform} ${process4.arch}`],
43924
- ["Godot", godotVersion ?? "not known at the point this failed"],
43925
- ["Signature", defectSignature(where, message)]
43926
- ];
43927
- }
43928
- function filledTemplate(where, message, godotVersion) {
43929
- return [
43930
- `**gdharness version**: ${SERVER_VERSION}`,
43931
- `**Godot version**: ${godotVersion ?? ""}`,
43932
- `**Bun version**: ${runtime()}`,
43933
- `**OS**: ${process4.platform} ${process4.arch}`,
43934
- "**MCP client**:",
43935
- "",
43936
- "## What you did",
43937
- "",
43938
- where,
43939
- "",
43940
- "## What you expected",
43941
- "",
43942
- "The call to answer, or to refuse with a reason.",
43943
- "",
43944
- "## What happened",
43945
- "",
43946
- "gdharness failed in a way it does not model.",
43947
- "",
43948
- "```text",
43949
- message,
43950
- "```",
43951
- "",
43952
- `Signature: \`${defectSignature(where, message)}\``,
43953
- "",
43954
- "If the real cause turned out to be the project, the environment or the call, say so here:",
43955
- "reaching you as a defect rather than as a refusal naming what would have worked is then the",
43956
- "thing to fix.",
43957
- "",
43958
- "## Anything that makes it reproducible",
43959
- ""
43960
- ].join(`
43961
- `);
43962
- }
43963
- function defectReport(where, error, context = {}) {
43964
- const { godotVersion, seenBefore = 0 } = context;
43965
- const message = errorMessage(error);
43966
- const title = `Defect ${defectSignature(where, message)}: ${where}`;
43967
- const url = `${NEW_ISSUE}?template=bug_report.md&title=${encodeURIComponent(title)}` + `&body=${encodeURIComponent(filledTemplate(where, message, godotVersion))}`;
43968
- const width = Math.max(...facts(where, message, godotVersion).map(([label]) => (label ?? "").length));
43969
- return [
43970
- "gdharness failed in a way it does not model. Whatever went wrong underneath, it reached you",
43971
- "as a crash rather than as a refusal naming what would have worked, and that much is a defect",
43972
- "in gdharness however the rest of it turns out.",
43973
- "",
43974
- ...seenBefore === 0 ? [
43975
- "Send the call once more before anything else. Some of what reaches this line is a file",
43976
- "another process had open, or a connection that dropped, and those are gone by the next",
43977
- "attempt. If the same Signature comes back, it is not one of those, and repeating it",
43978
- "further only spends turns."
43979
- ] : [
43980
- `You have had this exact failure ${seenBefore === 1 ? "once" : `${seenBefore} times`} in this session already, so it is a`,
43981
- "state rather than a passing one, and sending the call again will not change it."
43982
- ],
43983
- "",
43984
- ...facts(where, message, godotVersion).map(([label, value]) => ` ${(label ?? "").padEnd(width)} ${value ?? ""}`),
43985
- "",
43986
- "Do not open an issue on your own initiative. Tell the person whose machine this is what",
43987
- "broke, and ask whether you may report it to gdharness on their behalf. What the report",
43988
- "carries is the lines above and nothing else, and whether that goes into a public tracker",
43989
- "is theirs to decide.",
43990
- "",
43991
- "If they say yes, this link carries the report already filled in:",
43992
- url,
43993
- "",
43994
- "If they say yes and you have no way to open an issue yourself, give them that link and",
43995
- "those lines, and stay with them while they file it.",
43996
- "",
43997
- "Worth reporting either way. If you can see that what actually went wrong was the project,",
43998
- "the environment or the call, then this message is the defect: a failure gdharness knows",
43999
- "about is meant to arrive as a refusal naming what would have worked, not as this. Say that",
44000
- "in the report and it is the more useful of the two."
44001
- ].join(`
44002
- `);
44003
- }
43920
+ // src/cli.ts
43921
+ init_headless();
43922
+ init_issues();
44004
43923
 
44005
43924
  // src/prompt.ts
44006
43925
  import process5 from "node:process";
@@ -44025,250 +43944,15 @@ class Ask {
44025
43944
  }
44026
43945
  }
44027
43946
 
44028
- // src/server-version.ts
44029
- init_update_check();
44030
- import { readFileSync as readFileSync5 } from "node:fs";
44031
- var DEBUG_MODE2 = process.env["DEBUG"] === "true";
44032
- var GODOT_DEBUG_MODE_DEFAULT2 = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE2;
44033
- var SERVER_VERSION2 = (() => {
44034
- try {
44035
- const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
44036
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
44037
- } catch {
44038
- return "0.0.0";
44039
- }
44040
- })();
44041
-
44042
- // src/setup.ts
44043
- init_class_cache();
44044
- init_headless();
44045
- init_resources();
43947
+ // src/cli.ts
44046
43948
  init_server_version();
44047
- import { spawnSync as spawnSync2 } from "node:child_process";
44048
- import { createHash as createHash2 } from "node:crypto";
44049
- import {
44050
- cpSync,
44051
- existsSync as existsSync7,
44052
- mkdirSync as mkdirSync4,
44053
- readdirSync as readdirSync3,
44054
- readFileSync as readFileSync8,
44055
- rmSync as rmSync4,
44056
- statSync as statSync3,
44057
- writeFileSync as writeFileSync6
44058
- } from "node:fs";
44059
- import { dirname as dirname4, join as join9 } from "node:path";
44060
- import { fileURLToPath } from "node:url";
44061
- var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
44062
- var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
44063
- var RUNTIME_AUTOLOAD = {
44064
- name: "GdharnessRuntime",
44065
- path: "addons/gdharness_runtime/runtime_autoload.gd"
44066
- };
44067
- var VERSION_MARKER = ".gdharness-version";
44068
- var DIGEST_MARKER = ".gdharness-digest";
44069
- function editorAddonDigest(from = shippedAddonsDirectory()) {
44070
- const hash = createHash2("sha256");
44071
- const walk = (directory, relative) => {
44072
- for (const name of readdirSync3(directory).sort()) {
44073
- if (name === VERSION_MARKER || name === DIGEST_MARKER) {
44074
- continue;
44075
- }
44076
- const path = join9(directory, name);
44077
- const inside = `${relative}/${name}`;
44078
- if (statSync3(path).isDirectory()) {
44079
- walk(path, inside);
44080
- } else {
44081
- hash.update(`${inside}
44082
- `);
44083
- hash.update(readFileSync8(path));
44084
- hash.update(`
44085
- `);
44086
- }
44087
- }
44088
- };
44089
- for (const name of EDITOR_PLUGINS) {
44090
- walk(join9(from, name), name);
44091
- }
44092
- return hash.digest("hex");
44093
- }
44094
- function installedEditorDigest(projectPath) {
44095
- const marker = join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
44096
- if (!existsSync7(marker)) {
44097
- return null;
44098
- }
44099
- const said = readFileSync8(marker, "utf8").trim();
44100
- return said === "" ? null : said;
44101
- }
44102
- function shippedAddonsDirectory() {
44103
- return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "addons");
44104
- }
44105
- function shippedOperationsScript() {
44106
- return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
44107
- }
44108
- function installAddons(projectPath, from = shippedAddonsDirectory()) {
44109
- const installed = [];
44110
- for (const name of ADDONS) {
44111
- const source = join9(from, name);
44112
- if (!existsSync7(join9(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
44113
- throw new Error(`The package holds no ${name} addon at ${source}.`);
44114
- }
44115
- const target = join9(projectPath, "addons", name);
44116
- const replaced = existsSync7(target);
44117
- rmSync4(target, { recursive: true, force: true });
44118
- mkdirSync4(dirname4(target), { recursive: true });
44119
- cpSync(source, target, { recursive: true });
44120
- writeFileSync6(join9(target, VERSION_MARKER), `${SERVER_VERSION}
44121
- `);
44122
- installed.push({ name, path: target, replaced });
44123
- }
44124
- writeFileSync6(join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
44125
- `);
44126
- return installed;
44127
- }
44128
- function removeAddons(projectPath) {
44129
- const removed = [];
44130
- for (const name of ADDONS) {
44131
- const target = join9(projectPath, "addons", name);
44132
- if (existsSync7(target)) {
44133
- rmSync4(target, { recursive: true, force: true });
44134
- removed.push(target);
44135
- }
44136
- }
44137
- return removed;
44138
- }
44139
- async function disablePlugins(engine, projectPath, names) {
44140
- const outcomes = [];
44141
- for (const name of names) {
44142
- outcomes.push(await runOperation2(engine, "disable_plugin", { pluginName: name }, projectPath));
44143
- }
44144
- return outcomes;
44145
- }
44146
- async function enablePlugins(engine, projectPath, names) {
44147
- const outcomes = [];
44148
- for (const name of names) {
44149
- outcomes.push(await runOperation2(engine, "enable_plugin", { pluginName: name }, projectPath));
44150
- }
44151
- return outcomes;
44152
- }
44153
- function autoloadIsOurs(named) {
44154
- return named === null || named === `res://${RUNTIME_AUTOLOAD.path}`;
44155
- }
44156
- async function setRuntime(engine, projectPath, on) {
44157
- return on ? await runOperation2(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`, enabled: true }, projectPath) : await runOperation2(engine, "remove_autoload", { name: RUNTIME_AUTOLOAD.name }, projectPath);
44158
- }
44159
- function loaderAmong(projectPath, named) {
44160
- let byName = null;
44161
- for (const [entry, path] of named) {
44162
- if (entry === RUNTIME_AUTOLOAD.name || path === RUNTIME_AUTOLOAD.path) {
44163
- continue;
44164
- }
44165
- let source = null;
44166
- try {
44167
- source = readFileSync8(join9(projectPath, path), "utf8");
44168
- } catch {}
44169
- if (source?.includes(RUNTIME_AUTOLOAD.path)) {
44170
- return { name: entry, path, exists: true };
44171
- }
44172
- byName ??= path.toLowerCase().includes("gdharness") ? { name: entry, path, exists: source !== null } : null;
44173
- }
44174
- return byName;
44175
- }
44176
- function autoloadPaths(settings) {
44177
- const named = new Map;
44178
- for (const [name, value] of Object.entries(settings["autoload"] ?? {})) {
44179
- const said = typeof value === "string" ? value.replace(/^\*/, "") : "";
44180
- if (said.startsWith("res://")) {
44181
- named.set(name, said.slice("res://".length));
44182
- }
44183
- }
44184
- return named;
44185
- }
44186
- function trackedByGit(projectPath, wanted) {
44187
- if (wanted.length === 0) {
44188
- return new Set;
44189
- }
44190
- const asked = spawnSync2("git", ["ls-files", "-z", "--", ...wanted], {
44191
- cwd: projectPath,
44192
- encoding: "utf8"
44193
- });
44194
- if (asked.status !== 0) {
44195
- return null;
44196
- }
44197
- return new Set(asked.stdout.split("\x00").filter(Boolean));
44198
- }
44199
- function enabledPlugins(settings) {
44200
- const raw = settings["editor_plugins"]?.["enabled"];
44201
- if (typeof raw !== "string") {
44202
- return [];
44203
- }
44204
- return [...raw.matchAll(/res:\/\/addons\/([^/"]+)\/plugin\.cfg/g)].map((match) => match[1] ?? "").filter(Boolean);
44205
- }
44206
- function inspectProject(projectPath) {
44207
- const problems = [];
44208
- const addons = ADDONS.map((name) => {
44209
- const directory = join9(projectPath, "addons", name);
44210
- const marker = join9(directory, VERSION_MARKER);
44211
- const installed = existsSync7(directory);
44212
- const version = existsSync7(marker) ? readFileSync8(marker, "utf8").trim() : null;
44213
- const current = version === SERVER_VERSION;
44214
- if (!installed) {
44215
- problems.push(`addons/${name} is not installed; run gdharness setup`);
44216
- } else if (!current) {
44217
- problems.push(`addons/${name} is ${version ?? "of an unknown version"}, this gdharness is ${SERVER_VERSION}; run gdharness setup`);
44218
- }
44219
- return { name, installed, version, current };
44220
- });
44221
- const settings = parseProjectGodot(readFileSync8(join9(projectPath, "project.godot"), "utf8"));
44222
- const pluginsEnabled = enabledPlugins(settings);
44223
- for (const name of EDITOR_PLUGINS) {
44224
- if (!pluginsEnabled.includes(name)) {
44225
- problems.push(`the ${name} plugin is not enabled in project.godot; run gdharness setup`);
44226
- }
44227
- }
44228
- const autoload = settings["autoload"];
44229
- const registered = autoload === undefined ? undefined : readString(autoload, RUNTIME_AUTOLOAD.name);
44230
- const runtimeAutoloadPath = typeof registered === "string" ? registered.replace(/^\*/, "") : null;
44231
- const named = autoloadPaths(settings);
44232
- const runtimeLoaderAutoload = loaderAmong(projectPath, named);
44233
- const runtimeAutoload = runtimeAutoloadPath !== null || runtimeLoaderAutoload !== null;
44234
- const tracked = trackedByGit(projectPath, [...named.values()]);
44235
- const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
44236
- for (const [name, path] of named) {
44237
- if (!existsSync7(join9(projectPath, path))) {
44238
- if (!addonMissing(path)) {
44239
- problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
44240
- }
44241
- continue;
44242
- }
44243
- if (tracked !== null && !tracked.has(path)) {
44244
- problems.push(`the ${name} autoload names res://${path}, which git does not carry, so a clone boots with a missing script; commit that file, or point the autoload at one the project tracks`);
44245
- }
44246
- }
44247
- const cached = cachedClasses(projectPath);
44248
- const staleClasses = cached === null ? [] : staleAgainst(cached, projectPath);
44249
- if (cached === null) {
44250
- problems.push("no .godot/global_script_class_cache.cfg: the engine knows no class_name; run project_import refresh_classes or open the editor");
44251
- } else if (staleClasses.length > 0) {
44252
- problems.push(`the class cache does not list ${staleClasses.join(", ")}; run project_import refresh_classes or gdharness classes`);
44253
- }
44254
- return {
44255
- projectPath,
44256
- addons,
44257
- pluginsEnabled,
44258
- runtimeAutoload,
44259
- runtimeAutoloadPath,
44260
- runtimeLoaderAutoload,
44261
- staleClasses,
44262
- classCacheExists: cached !== null,
44263
- problems
44264
- };
44265
- }
43949
+ init_setup();
44266
43950
 
44267
43951
  // src/skill.ts
44268
43952
  init_setup();
44269
43953
  init_tool_definitions();
44270
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
44271
- import { basename as basename2, dirname as dirname6, join as join11 } from "node:path";
43954
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync4, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
43955
+ import { basename as basename2, dirname as dirname5, join as join9 } from "node:path";
44272
43956
 
44273
43957
  // src/tool-reference.ts
44274
43958
  init_headless_operations();
@@ -44364,7 +44048,7 @@ function renderToolsMarkdown() {
44364
44048
 
44365
44049
  // src/skill.ts
44366
44050
  var SKILL_NAME = "gdharness";
44367
- var SHARED_SKILLS = join11(".agents", "skills");
44051
+ var SHARED_SKILLS = join9(".agents", "skills");
44368
44052
  function skillMarkdown(version) {
44369
44053
  return `---
44370
44054
  name: ${SKILL_NAME}
@@ -44434,7 +44118,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
44434
44118
  | What a property reads right now, on a node whose path you already hold | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
44435
44119
  | What a property on an object a node holds reads | the same two, with colons: \`_game:clock:speed\` |
44436
44120
  | Which control holds the focus, or anything only a method answers | the same path with a step written as a call, \`get_viewport():gui_get_focus_owner\`, which calls a method taking no arguments and walks into what it returned |
44437
- | Press a button | \`runtime_input click\`, which says what was under the pointer |
44121
+ | Press a button | \`runtime_input click\` with \`says\` and the words on it, which finds it and presses it in the same frame and says what was under the pointer. A \`find\` and then a click by the path it answered is two calls over a generated path, and a panel that rebuilds between them frees the button first; \`nodePath\` narrows where \`says\` looks, and several matches are refused with an \`index\` for each |
44438
44122
  | Press the next thing after a click that changed screen | \`runtime_wait until\` with \`says\` and a word the new screen shows (no \`nodePath\` needed: without one the whole game is searched), then the click. A click sent straight after a screen change lands on a control the new screen is about to free, answers \`landed: true\`, and does nothing; a frame count is a number nobody can pick correctly across a cold start and a warm one, and waiting for the words answered in about 130ms where twenty frames had not been enough |
44439
44123
  | Fill in a field | \`runtime_input click\` on it, then \`runtime_input text\` with \`replace\`; a submitted field has to be clicked again |
44440
44124
  | Click somebody standing in a 3D room | \`runtime_input click\` on the Node3D, which aims at what it draws |
@@ -44553,27 +44237,27 @@ whatever the engine said on stderr comes back under \`engine_messages\`.
44553
44237
  function skillFiles(version) {
44554
44238
  return new Map([
44555
44239
  ["SKILL.md", skillMarkdown(version)],
44556
- [join11("references", "tools.md"), renderToolsMarkdown()]
44240
+ [join9("references", "tools.md"), renderToolsMarkdown()]
44557
44241
  ]);
44558
44242
  }
44559
44243
  function skillDirectories(harnesses, projectPath, exists) {
44560
- const directories = [join11(projectPath, SHARED_SKILLS, SKILL_NAME)];
44244
+ const directories = [join9(projectPath, SHARED_SKILLS, SKILL_NAME)];
44561
44245
  for (const harness of harnesses) {
44562
44246
  if (harness.skills === undefined) {
44563
44247
  continue;
44564
44248
  }
44565
- const own = join11(projectPath, harness.skills.dir);
44566
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join11(own, SKILL_NAME))) {
44567
- directories.push(join11(own, SKILL_NAME));
44249
+ const own = join9(projectPath, harness.skills.dir);
44250
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join9(own, SKILL_NAME))) {
44251
+ directories.push(join9(own, SKILL_NAME));
44568
44252
  }
44569
44253
  }
44570
44254
  return directories;
44571
44255
  }
44572
44256
  function everySkillDirectory(projectPath, harnesses) {
44573
- const directories = new Set([join11(projectPath, SHARED_SKILLS, SKILL_NAME)]);
44257
+ const directories = new Set([join9(projectPath, SHARED_SKILLS, SKILL_NAME)]);
44574
44258
  for (const harness of harnesses) {
44575
44259
  if (harness.skills !== undefined) {
44576
- directories.add(join11(projectPath, harness.skills.dir, SKILL_NAME));
44260
+ directories.add(join9(projectPath, harness.skills.dir, SKILL_NAME));
44577
44261
  }
44578
44262
  }
44579
44263
  return [...directories];
@@ -44581,52 +44265,52 @@ function everySkillDirectory(projectPath, harnesses) {
44581
44265
  function removeSkill(directories, projectPath) {
44582
44266
  const removed = [];
44583
44267
  for (const directory of directories) {
44584
- if (existsSync9(directory)) {
44585
- rmSync6(directory, { recursive: true, force: true });
44586
- pruneEmpty(dirname6(directory), projectPath);
44268
+ if (existsSync8(directory)) {
44269
+ rmSync5(directory, { recursive: true, force: true });
44270
+ pruneEmpty(dirname5(directory), projectPath);
44587
44271
  removed.push(directory);
44588
44272
  }
44589
44273
  }
44590
44274
  return removed;
44591
44275
  }
44592
44276
  function pruneEmpty(directory, projectPath) {
44593
- const shared = join11(projectPath, ".agents");
44277
+ const shared = join9(projectPath, ".agents");
44594
44278
  let path = directory;
44595
44279
  while (path !== projectPath && (basename2(path) === "skills" || path === shared)) {
44596
- if (readdirSync5(path).length > 0) {
44280
+ if (readdirSync4(path).length > 0) {
44597
44281
  return;
44598
44282
  }
44599
- rmSync6(path, { recursive: true, force: true });
44600
- path = dirname6(path);
44283
+ rmSync5(path, { recursive: true, force: true });
44284
+ path = dirname5(path);
44601
44285
  }
44602
44286
  }
44603
44287
  function writeSkill(directories, version) {
44604
44288
  const files = skillFiles(version);
44605
44289
  return directories.map((directory) => {
44606
- const replaced = existsSync9(directory);
44607
- rmSync6(directory, { recursive: true, force: true });
44290
+ const replaced = existsSync8(directory);
44291
+ rmSync5(directory, { recursive: true, force: true });
44608
44292
  for (const [name, contents] of files) {
44609
- const path = join11(directory, name);
44610
- mkdirSync6(dirname6(path), { recursive: true });
44611
- writeFileSync8(path, contents, "utf8");
44293
+ const path = join9(directory, name);
44294
+ mkdirSync5(dirname5(path), { recursive: true });
44295
+ writeFileSync6(path, contents, "utf8");
44612
44296
  }
44613
44297
  return { path: directory, replaced };
44614
44298
  });
44615
44299
  }
44616
44300
 
44617
44301
  // src/version.ts
44618
- import { readFileSync as readFileSync10 } from "node:fs";
44619
- import { dirname as dirname7, join as join12 } from "node:path";
44620
- import { fileURLToPath as fileURLToPath3 } from "node:url";
44302
+ import { readFileSync as readFileSync8 } from "node:fs";
44303
+ import { dirname as dirname6, join as join10 } from "node:path";
44304
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
44621
44305
  function getLocalVersion() {
44622
- const currentDirectory = dirname7(fileURLToPath3(import.meta.url));
44306
+ const currentDirectory = dirname6(fileURLToPath2(import.meta.url));
44623
44307
  const packageCandidates = [
44624
- join12(currentDirectory, "..", "package.json"),
44625
- join12(currentDirectory, "..", "..", "package.json")
44308
+ join10(currentDirectory, "..", "package.json"),
44309
+ join10(currentDirectory, "..", "..", "package.json")
44626
44310
  ];
44627
44311
  for (const packagePath of packageCandidates) {
44628
44312
  try {
44629
- const value = JSON.parse(readFileSync10(packagePath, "utf-8"));
44313
+ const value = JSON.parse(readFileSync8(packagePath, "utf-8"));
44630
44314
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
44631
44315
  return value.version;
44632
44316
  }
@@ -44639,12 +44323,12 @@ function getLocalVersion() {
44639
44323
  var args = process.argv.slice(2);
44640
44324
  var command = args[0];
44641
44325
 
44642
- class UsageError extends Refusal2 {
44326
+ class UsageError extends Refusal {
44643
44327
  }
44644
44328
  function projectArgument(at) {
44645
44329
  const named = args.slice(at).find((value) => !value.startsWith("--"));
44646
44330
  const projectPath = resolve6(named ?? ".");
44647
- if (!existsSync17(join25(projectPath, "project.godot"))) {
44331
+ if (!existsSync15(join22(projectPath, "project.godot"))) {
44648
44332
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
44649
44333
  }
44650
44334
  return projectPath;
@@ -44652,18 +44336,18 @@ function projectArgument(at) {
44652
44336
  async function engine(projectPath) {
44653
44337
  const located = await new GodotLocator().find();
44654
44338
  if (located.ok) {
44655
- return { godotPath: located.path, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
44339
+ return { godotPath: located.path, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
44656
44340
  }
44657
44341
  const recorded = located.named === null && projectPath !== undefined ? recordedEnginePath(projectPath) : null;
44658
44342
  if (recorded === null) {
44659
44343
  const refusal = GodotLocator.refusal(located);
44660
44344
  throw new UsageError(`${refusal.message} ${refusal.advice.join(". ")}.`);
44661
44345
  }
44662
- return { godotPath: recorded, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
44346
+ return { godotPath: recorded, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
44663
44347
  }
44664
44348
  function said2(outcome, what) {
44665
44349
  if (!outcome.ok) {
44666
- throw new Refusal2(`${what}: ${outcome.message}`);
44350
+ throw new Refusal(`${what}: ${outcome.message}`);
44667
44351
  }
44668
44352
  for (const entry of outcome.messages) {
44669
44353
  console.error(` engine ${entry.severity}: ${entry.text}`);
@@ -44679,11 +44363,11 @@ async function registerRuntime(godot, projectPath) {
44679
44363
  }
44680
44364
  if (autoloadIsOurs(named)) {
44681
44365
  said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
44682
- console.log(`${RUNTIME_AUTOLOAD.name} autoload registered at res://${RUNTIME_AUTOLOAD.path}`);
44366
+ console.log(`${RUNTIME_AUTOLOAD.name} autoload ${named === null ? "registered" : "already registered"} at res://${RUNTIME_AUTOLOAD.path}`);
44683
44367
  return;
44684
44368
  }
44685
44369
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
44686
- if (!existsSync17(join25(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
44370
+ if (!existsSync15(join22(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
44687
44371
  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}`);
44688
44372
  }
44689
44373
  }
@@ -44815,7 +44499,7 @@ async function setup() {
44815
44499
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
44816
44500
  }
44817
44501
  if (!args.includes("--no-skill")) {
44818
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync17);
44502
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync15);
44819
44503
  for (const written of writeSkill(directories, getLocalVersion())) {
44820
44504
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
44821
44505
  }
@@ -44909,7 +44593,7 @@ async function upgrade() {
44909
44593
  moved.push(took.from);
44910
44594
  }
44911
44595
  }
44912
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync17), version)) {
44596
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync15), version)) {
44913
44597
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
44914
44598
  }
44915
44599
  console.log(`
@@ -44941,7 +44625,7 @@ function doctorReport(projectPath) {
44941
44625
  process.exitCode = 1;
44942
44626
  }
44943
44627
  }
44944
- async function runtime3() {
44628
+ async function runtime2() {
44945
44629
  const state = args[1];
44946
44630
  if (state !== "on" && state !== "off") {
44947
44631
  throw new UsageError("gdharness runtime takes on or off, then the project directory.");
@@ -45050,7 +44734,7 @@ async function main() {
45050
44734
  doctorReport(projectArgument(1));
45051
44735
  return;
45052
44736
  case "runtime":
45053
- await runtime3();
44737
+ await runtime2();
45054
44738
  return;
45055
44739
  case "classes":
45056
44740
  await classes();
@@ -45079,7 +44763,7 @@ async function main() {
45079
44763
  }
45080
44764
  }
45081
44765
  await main().catch((error) => {
45082
- if (error instanceof Refusal2) {
44766
+ if (error instanceof Refusal) {
45083
44767
  console.error("gdharness:", error.message);
45084
44768
  process.exit(error instanceof UsageError ? 2 : 1);
45085
44769
  }