gdharness 1.1.9 → 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,75 +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
- rmdirSync as rmdirSync2,
15582
- rmSync as rmSync5,
15583
- statSync as statSync4,
15584
- 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
15585
15866
  } from "node:fs";
15586
- import { dirname as dirname5, join as join10 } from "node:path";
15587
- import { fileURLToPath as fileURLToPath2 } from "node:url";
15588
- function editorAddonDigest2(from = shippedAddonsDirectory2()) {
15589
- 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");
15590
15871
  const walk = (directory, relative) => {
15591
- for (const name of readdirSync4(directory).sort()) {
15592
- if (name === VERSION_MARKER2 || name === DIGEST_MARKER2) {
15872
+ for (const name of readdirSync3(directory).sort()) {
15873
+ if (name === VERSION_MARKER || name === DIGEST_MARKER) {
15593
15874
  continue;
15594
15875
  }
15595
- const path = join10(directory, name);
15876
+ const path = join8(directory, name);
15596
15877
  const inside = `${relative}/${name}`;
15597
- if (statSync4(path).isDirectory()) {
15878
+ if (statSync3(path).isDirectory()) {
15598
15879
  walk(path, inside);
15599
15880
  } else {
15600
15881
  hash.update(`${inside}
15601
15882
  `);
15602
- hash.update(readFileSync9(path));
15883
+ hash.update(readFileSync7(path));
15603
15884
  hash.update(`
15604
15885
  `);
15605
15886
  }
15606
15887
  }
15607
15888
  };
15608
- for (const name of EDITOR_PLUGINS2) {
15609
- walk(join10(from, name), name);
15889
+ for (const name of EDITOR_PLUGINS) {
15890
+ walk(join8(from, name), name);
15610
15891
  }
15611
15892
  return hash.digest("hex");
15612
15893
  }
15613
15894
  function shippedEditorDigest() {
15614
15895
  if (shippedDigest === undefined) {
15615
15896
  try {
15616
- shippedDigest = editorAddonDigest2();
15897
+ shippedDigest = editorAddonDigest();
15617
15898
  } catch {
15618
15899
  shippedDigest = null;
15619
15900
  }
15620
15901
  }
15621
15902
  return shippedDigest ?? undefined;
15622
15903
  }
15623
- function shippedAddonsDirectory2() {
15624
- 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;
15625
15937
  }
15626
15938
  function installedAddonVersion(projectPath) {
15627
- const marker = join10(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
15628
- if (!existsSync8(marker)) {
15939
+ const marker = join8(projectPath, "addons", ADDONS[0], VERSION_MARKER);
15940
+ if (!existsSync7(marker)) {
15629
15941
  return null;
15630
15942
  }
15631
- const said = readFileSync9(marker, "utf8").trim();
15943
+ const said = readFileSync7(marker, "utf8").trim();
15632
15944
  return said === "" ? null : said;
15633
15945
  }
15634
- 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;
15635
16090
  var init_setup = __esm(() => {
15636
16091
  init_class_cache();
15637
16092
  init_headless();
15638
16093
  init_resources();
15639
16094
  init_server_version();
15640
- ADDONS2 = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
15641
- EDITOR_PLUGINS2 = ["gdharness_editor", "auto_reload"];
15642
- RUNTIME_AUTOLOAD2 = {
16095
+ ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
16096
+ EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
16097
+ RUNTIME_AUTOLOAD = {
15643
16098
  name: "GdharnessRuntime",
15644
16099
  path: "addons/gdharness_runtime/runtime_autoload.gd"
15645
16100
  };
@@ -16089,6 +16544,10 @@ var init_tool_definitions = __esm(() => {
16089
16544
  type: "integer",
16090
16545
  minimum: 1,
16091
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."
16092
16551
  }
16093
16552
  },
16094
16553
  requires: ["projectPath"]
@@ -16738,14 +17197,19 @@ var init_tool_definitions = __esm(() => {
16738
17197
  },
16739
17198
  {
16740
17199
  name: "runtime_input",
16741
- 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.",
16742
17201
  parameters: {
16743
17202
  projectPath: RUNNING_PROJECT_PATH,
16744
17203
  pid: RUNNING_PID,
16745
17204
  nodePath: {
16746
17205
  type: "string",
16747
17206
  ops: ["click", "choose"],
16748
- 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."
16749
17213
  },
16750
17214
  action: {
16751
17215
  type: "string",
@@ -16782,8 +17246,8 @@ var init_tool_definitions = __esm(() => {
16782
17246
  },
16783
17247
  index: {
16784
17248
  type: "number",
16785
- ops: ["choose"],
16786
- 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."
16787
17251
  },
16788
17252
  shift: { type: "boolean", ops: ["key"], description: "key: hold Shift with it. Default false." },
16789
17253
  ctrl: { type: "boolean", ops: ["key"], description: "key: hold Ctrl with it. Default false." },
@@ -16822,8 +17286,8 @@ var init_tool_definitions = __esm(() => {
16822
17286
  requires: [],
16823
17287
  operations: {
16824
17288
  click: {
16825
- 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",
16826
- 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: []
16827
17291
  },
16828
17292
  choose: {
16829
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",
@@ -31703,10 +32167,10 @@ var init_stdio2 = __esm(() => {
31703
32167
  });
31704
32168
 
31705
32169
  // src/runtime-client.ts
31706
- 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";
31707
32171
  import { createConnection } from "node:net";
31708
- import { tmpdir as tmpdir4 } from "node:os";
31709
- 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";
31710
32174
  import { setTimeout as delay } from "node:timers/promises";
31711
32175
  function runtimeDirectory(variables = process.env) {
31712
32176
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -31714,20 +32178,20 @@ function runtimeDirectory(variables = process.env) {
31714
32178
  return explicit;
31715
32179
  }
31716
32180
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
31717
- return join13(perUser ?? tmpdir4(), "gdharness");
32181
+ return join11(perUser ?? tmpdir3(), "gdharness");
31718
32182
  }
31719
32183
  function runtimeDirectories(variables = process.env) {
31720
32184
  const candidates = [runtimeDirectory(variables)];
31721
32185
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
31722
32186
  for (const base of fallbacks) {
31723
- candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
32187
+ candidates.push(join11(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
31724
32188
  }
31725
32189
  return [...new Set(candidates.map((path) => resolve3(path)))];
31726
32190
  }
31727
32191
  function errorReportOf(pid, directories = runtimeDirectories()) {
31728
32192
  for (const directory of directories) {
31729
- const path = join13(directory, `runtime-${pid}.log`);
31730
- if (existsSync10(path)) {
32193
+ const path = join11(directory, `runtime-${pid}.log`);
32194
+ if (existsSync9(path)) {
31731
32195
  return path;
31732
32196
  }
31733
32197
  }
@@ -31744,7 +32208,7 @@ function processAlive(pid) {
31744
32208
  function parseAnnouncement(file, pid) {
31745
32209
  let fields;
31746
32210
  try {
31747
- fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
32211
+ fields = asParams(JSON.parse(readFileSync9(file, "utf8")));
31748
32212
  } catch {
31749
32213
  return { kind: "rubbish" };
31750
32214
  }
@@ -31761,7 +32225,7 @@ function parseAnnouncement(file, pid) {
31761
32225
  };
31762
32226
  let announcedAt;
31763
32227
  try {
31764
- announcedAt = statSync5(file).mtimeMs;
32228
+ announcedAt = statSync4(file).mtimeMs;
31765
32229
  } catch {}
31766
32230
  const when = announcedAt === undefined ? {} : { announcedAt };
31767
32231
  if (protocol !== RUNTIME_PROTOCOL) {
@@ -31802,7 +32266,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
31802
32266
  function wentAway(file, pid) {
31803
32267
  let project = "";
31804
32268
  try {
31805
- const fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
32269
+ const fields = asParams(JSON.parse(readFileSync9(file, "utf8")));
31806
32270
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
31807
32271
  } catch {
31808
32272
  return;
@@ -31839,29 +32303,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
31839
32303
  return new Set(candidates.filter((one) => verdicts.get(one.file)?.stranger === true).map((one) => one.file));
31840
32304
  }
31841
32305
  function announcedIn(directory) {
31842
- if (!existsSync10(directory)) {
32306
+ if (!existsSync9(directory)) {
31843
32307
  return [];
31844
32308
  }
31845
32309
  const found = [];
31846
32310
  const candidates = [];
31847
32311
  const entries = [];
31848
- for (const entry of readdirSync6(directory)) {
32312
+ for (const entry of readdirSync5(directory)) {
31849
32313
  const report = ERROR_REPORT_PATTERN.exec(entry);
31850
32314
  if (report) {
31851
- sweepErrorReport(join13(directory, entry), Number.parseInt(report[1] ?? "", 10));
32315
+ sweepErrorReport(join11(directory, entry), Number.parseInt(report[1] ?? "", 10));
31852
32316
  continue;
31853
32317
  }
31854
32318
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
31855
32319
  if (!match) {
31856
32320
  continue;
31857
32321
  }
31858
- const file = join13(directory, entry);
32322
+ const file = join11(directory, entry);
31859
32323
  const pid = Number.parseInt(match[1] ?? "", 10);
31860
32324
  const alive = processAlive(pid);
31861
32325
  entries.push({ file, pid, alive });
31862
32326
  if (alive) {
31863
32327
  try {
31864
- candidates.push({ file, pid, writtenAt: statSync5(file).mtimeMs });
32328
+ candidates.push({ file, pid, writtenAt: statSync4(file).mtimeMs });
31865
32329
  } catch {}
31866
32330
  }
31867
32331
  }
@@ -31883,16 +32347,16 @@ function announcedIn(directory) {
31883
32347
  found.push(announced);
31884
32348
  }
31885
32349
  const held = new Set(entries.map((one) => one.file));
31886
- const here = join13(directory, ".");
32350
+ const here = join11(directory, ".");
31887
32351
  for (const file of verdicts.keys()) {
31888
- if (dirname8(file) === here && !held.has(file)) {
32352
+ if (dirname7(file) === here && !held.has(file)) {
31889
32353
  verdicts.delete(file);
31890
32354
  }
31891
32355
  }
31892
32356
  return found;
31893
32357
  }
31894
32358
  async function announcementEnded(pid, directories = runtimeDirectories()) {
31895
- 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));
31896
32360
  const first = files[0];
31897
32361
  if (first === undefined) {
31898
32362
  return;
@@ -31918,7 +32382,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
31918
32382
  return;
31919
32383
  }
31920
32384
  try {
31921
- if (now - statSync5(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
32385
+ if (now - statSync4(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
31922
32386
  unlinkSync(path);
31923
32387
  }
31924
32388
  } catch {}
@@ -32191,14 +32655,14 @@ var init_runtime_client = __esm(() => {
32191
32655
  });
32192
32656
 
32193
32657
  // src/boot-note.ts
32194
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
32195
- 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";
32196
32660
  function bootNotePath(projectPath) {
32197
- return join14(projectPath, ".godot", "gdharness-boot.json");
32661
+ return join12(projectPath, ".godot", "gdharness-boot.json");
32198
32662
  }
32199
32663
  function readBootNote(projectPath) {
32200
32664
  try {
32201
- const noted = readNumber(asParams(JSON.parse(readFileSync12(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
32665
+ const noted = readNumber(asParams(JSON.parse(readFileSync10(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
32202
32666
  return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
32203
32667
  } catch {
32204
32668
  return null;
@@ -32207,8 +32671,8 @@ function readBootNote(projectPath) {
32207
32671
  function writeBootNote(projectPath, announcedAfterMs) {
32208
32672
  try {
32209
32673
  const path = bootNotePath(projectPath);
32210
- mkdirSync7(dirname9(path), { recursive: true });
32211
- 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");
32212
32676
  } catch {}
32213
32677
  }
32214
32678
  function halfAsLongAgain(lastBootMs) {
@@ -32226,10 +32690,10 @@ var init_boot_note = __esm(() => {
32226
32690
  });
32227
32691
 
32228
32692
  // src/breakpoint-note.ts
32229
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync13, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
32230
- 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";
32231
32695
  function breakpointNotePath(projectPath) {
32232
- return join15(projectPath, ".godot", "gdharness-breakpoints.json");
32696
+ return join13(projectPath, ".godot", "gdharness-breakpoints.json");
32233
32697
  }
32234
32698
  function writeBreakpointNote(projectPath, held) {
32235
32699
  const path = breakpointNotePath(projectPath);
@@ -32237,7 +32701,7 @@ function writeBreakpointNote(projectPath, held) {
32237
32701
  const inside = held.map((one) => ({ scriptPath: realPathOr(one.scriptPath), lines: one.lines })).filter((one) => one.lines.length > 0 && withinProject(root, one.scriptPath));
32238
32702
  try {
32239
32703
  if (inside.length === 0) {
32240
- rmSync7(path, { force: true });
32704
+ rmSync6(path, { force: true });
32241
32705
  return;
32242
32706
  }
32243
32707
  const note = {
@@ -32246,8 +32710,8 @@ function writeBreakpointNote(projectPath, held) {
32246
32710
  lines: [...one.lines]
32247
32711
  }))
32248
32712
  };
32249
- mkdirSync8(dirname10(path), { recursive: true });
32250
- writeFileSync10(path, `${JSON.stringify(note, null, 2)}
32713
+ mkdirSync7(dirname9(path), { recursive: true });
32714
+ writeFileSync8(path, `${JSON.stringify(note, null, 2)}
32251
32715
  `, "utf8");
32252
32716
  } catch (error) {
32253
32717
  console.error(`[SERVER] Could not record the breakpoints at ${path}: ${errorMessage(error)}`);
@@ -32255,7 +32719,7 @@ function writeBreakpointNote(projectPath, held) {
32255
32719
  }
32256
32720
  function readBreakpointNote(projectPath) {
32257
32721
  try {
32258
- const parsed = JSON.parse(readFileSync13(breakpointNotePath(projectPath), "utf8"));
32722
+ const parsed = JSON.parse(readFileSync11(breakpointNotePath(projectPath), "utf8"));
32259
32723
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
32260
32724
  return [];
32261
32725
  }
@@ -32270,7 +32734,7 @@ function readBreakpointNote(projectPath) {
32270
32734
  }
32271
32735
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
32272
32736
  if (lines.length > 0) {
32273
- held.push({ scriptPath: realPathOr(join15(projectPath, script)), lines });
32737
+ held.push({ scriptPath: realPathOr(join13(projectPath, script)), lines });
32274
32738
  }
32275
32739
  }
32276
32740
  return held;
@@ -32287,75 +32751,15 @@ var init_breakpoint_note = __esm(() => {
32287
32751
  init_paths();
32288
32752
  });
32289
32753
 
32290
- // src/bridge-announce.ts
32291
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
32292
- import { dirname as dirname11, join as join16 } from "node:path";
32293
- function announcementPath2(projectPath) {
32294
- return join16(projectPath, ".godot", "gdharness-bridge.json");
32295
- }
32296
- function readAnnouncement2(path) {
32297
- try {
32298
- const parsed = JSON.parse(readFileSync14(path, "utf8"));
32299
- if (typeof parsed !== "object" || parsed === null) {
32300
- return null;
32301
- }
32302
- const said = parsed;
32303
- if (typeof said.port !== "number" || typeof said.pid !== "number") {
32304
- return null;
32305
- }
32306
- return said;
32307
- } catch {
32308
- return null;
32309
- }
32310
- }
32311
- function announcementIsOurs(path) {
32312
- return readAnnouncement2(path)?.pid === process.pid;
32313
- }
32314
- function announceBridge(projectPath, bridge) {
32315
- const path = announcementPath2(projectPath);
32316
- try {
32317
- mkdirSync9(dirname11(path), { recursive: true });
32318
- const announcement = {
32319
- protocol: BRIDGE_ANNOUNCE_PROTOCOL,
32320
- host: bridge.host,
32321
- port: bridge.port,
32322
- pid: process.pid,
32323
- version: bridge.version,
32324
- startedAt: new Date().toISOString()
32325
- };
32326
- writeFileSync11(path, `${JSON.stringify(announcement, null, 2)}
32327
- `, "utf8");
32328
- return path;
32329
- } catch (error) {
32330
- console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
32331
- return null;
32332
- }
32333
- }
32334
- function withdrawBridge(path) {
32335
- if (path === null || !announcementIsOurs(path)) {
32336
- return;
32337
- }
32338
- try {
32339
- rmSync8(path, { force: true });
32340
- } catch (error) {
32341
- console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
32342
- }
32343
- }
32344
- var BRIDGE_ANNOUNCE_PROTOCOL = 1;
32345
- var init_bridge_announce = __esm(() => {
32346
- init_errors();
32347
- init_process_children();
32348
- });
32349
-
32350
32754
  // src/class-note.ts
32351
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "node:fs";
32352
- 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";
32353
32757
  function classNotePath(projectPath) {
32354
- return join17(projectPath, ".godot", "gdharness-classes.json");
32758
+ return join14(projectPath, ".godot", "gdharness-classes.json");
32355
32759
  }
32356
32760
  function readClassNote(projectPath) {
32357
32761
  try {
32358
- const fields = asParams(JSON.parse(readFileSync15(classNotePath(projectPath), "utf8")));
32762
+ const fields = asParams(JSON.parse(readFileSync12(classNotePath(projectPath), "utf8")));
32359
32763
  const rebuiltWith = readArray(fields, "rebuiltWith");
32360
32764
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
32361
32765
  return null;
@@ -32372,24 +32776,24 @@ function readClassNote(projectPath) {
32372
32776
  function writeClassNote(projectPath, note) {
32373
32777
  try {
32374
32778
  const path = classNotePath(projectPath);
32375
- mkdirSync10(dirname12(path), { recursive: true });
32376
- writeFileSync12(path, `${JSON.stringify(note, null, 2)}
32779
+ mkdirSync8(dirname10(path), { recursive: true });
32780
+ writeFileSync9(path, `${JSON.stringify(note, null, 2)}
32377
32781
  `, "utf8");
32378
32782
  } catch {}
32379
32783
  }
32380
32784
  var init_class_note = () => {};
32381
32785
 
32382
32786
  // src/config-pin.ts
32383
- import { existsSync as existsSync11, readFileSync as readFileSync16 } from "node:fs";
32384
- 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";
32385
32789
  function versionsNamedInConfig(projectPath) {
32386
- const config = join18(projectPath, ".mcp.json");
32387
- if (!existsSync11(config)) {
32790
+ const config = join15(projectPath, ".mcp.json");
32791
+ if (!existsSync10(config)) {
32388
32792
  return [];
32389
32793
  }
32390
32794
  let text;
32391
32795
  try {
32392
- text = readFileSync16(config, "utf8");
32796
+ text = readFileSync13(config, "utf8");
32393
32797
  JSON.parse(text);
32394
32798
  } catch {
32395
32799
  return [];
@@ -33146,25 +33550,25 @@ var init_desktop = () => {};
33146
33550
  // src/editor-log.ts
33147
33551
  import {
33148
33552
  closeSync,
33149
- existsSync as existsSync12,
33150
- mkdirSync as mkdirSync11,
33553
+ existsSync as existsSync11,
33554
+ mkdirSync as mkdirSync9,
33151
33555
  openSync,
33152
- readFileSync as readFileSync17,
33556
+ readFileSync as readFileSync14,
33153
33557
  readSync,
33154
- rmSync as rmSync9,
33155
- statSync as statSync6,
33156
- writeFileSync as writeFileSync13
33558
+ rmSync as rmSync7,
33559
+ statSync as statSync5,
33560
+ writeFileSync as writeFileSync10
33157
33561
  } from "node:fs";
33158
- import { dirname as dirname13, join as join19 } from "node:path";
33562
+ import { dirname as dirname11, join as join16 } from "node:path";
33159
33563
  function editorLogPath(projectPath) {
33160
- return join19(projectPath, ".godot", "gdharness-editor.log");
33564
+ return join16(projectPath, ".godot", "gdharness-editor.log");
33161
33565
  }
33162
33566
  function editorLogNotePath(projectPath) {
33163
- return join19(projectPath, ".godot", "gdharness-editor.json");
33567
+ return join16(projectPath, ".godot", "gdharness-editor.json");
33164
33568
  }
33165
33569
  function readEditorLogNote(projectPath) {
33166
33570
  try {
33167
- const fields = asParams(JSON.parse(readFileSync17(editorLogNotePath(projectPath), "utf8")));
33571
+ const fields = asParams(JSON.parse(readFileSync14(editorLogNotePath(projectPath), "utf8")));
33168
33572
  const pid = readNumber(fields, "pid");
33169
33573
  const startedAt = readString(fields, "startedAt");
33170
33574
  return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
@@ -33175,16 +33579,16 @@ function readEditorLogNote(projectPath) {
33175
33579
  function clearEditorLog(projectPath) {
33176
33580
  try {
33177
33581
  const path = editorLogPath(projectPath);
33178
- mkdirSync11(dirname13(path), { recursive: true });
33179
- rmSync9(path, { force: true });
33180
- rmSync9(editorLogNotePath(projectPath), { force: true });
33582
+ mkdirSync9(dirname11(path), { recursive: true });
33583
+ rmSync7(path, { force: true });
33584
+ rmSync7(editorLogNotePath(projectPath), { force: true });
33181
33585
  } catch {}
33182
33586
  }
33183
33587
  function writeEditorLogNote(projectPath, pid) {
33184
33588
  try {
33185
33589
  const path = editorLogNotePath(projectPath);
33186
- mkdirSync11(dirname13(path), { recursive: true });
33187
- 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");
33188
33592
  } catch {}
33189
33593
  }
33190
33594
  function theConsoleWasNotCaptured(why, projectPath) {
@@ -33205,7 +33609,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
33205
33609
  return { kind: "older server", notedPid: noted?.pid ?? null };
33206
33610
  }
33207
33611
  const path = editorLogPath(projectPath);
33208
- if (!existsSync12(path)) {
33612
+ if (!existsSync11(path)) {
33209
33613
  return { kind: "not written" };
33210
33614
  }
33211
33615
  const log = new GameLog;
@@ -33215,9 +33619,9 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
33215
33619
  return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
33216
33620
  }
33217
33621
  function theFirstOf(path) {
33218
- const whole = statSync6(path).size;
33622
+ const whole = statSync5(path).size;
33219
33623
  if (whole <= MOST_CONSOLE_BYTES) {
33220
- return { text: readFileSync17(path, "utf8") };
33624
+ return { text: readFileSync14(path, "utf8") };
33221
33625
  }
33222
33626
  const handle = openSync(path, "r");
33223
33627
  try {
@@ -33259,7 +33663,7 @@ function shapeOf(text) {
33259
33663
  }
33260
33664
  return { shape, values };
33261
33665
  }
33262
- function bursts(entries, before) {
33666
+ function bursts(entries, before, keep) {
33263
33667
  const shapes = new Map;
33264
33668
  const shaped = entries.map((entry) => shapeOf(entry.text));
33265
33669
  for (const [at, entry] of entries.entries()) {
@@ -33287,6 +33691,9 @@ function bursts(entries, before) {
33287
33691
  if (entry === undefined) {
33288
33692
  continue;
33289
33693
  }
33694
+ if (keep !== undefined && !group.indices.some((at) => entries[at] !== undefined && keep(entries[at]))) {
33695
+ continue;
33696
+ }
33290
33697
  repeated.push({
33291
33698
  shape: key.slice(key.indexOf("\x00") + 1),
33292
33699
  count: group.indices.length,
@@ -36756,208 +37163,6 @@ var init_godot_bridge = __esm(() => {
36756
37163
  };
36757
37164
  });
36758
37165
 
36759
- // src/godot-path.ts
36760
- import { execFile as execFile5 } from "node:child_process";
36761
- import { existsSync as existsSync13 } from "node:fs";
36762
- import { normalize as normalize2 } from "node:path";
36763
- import { promisify as promisify5 } from "node:util";
36764
-
36765
- class GodotLocator2 {
36766
- answered = new Map;
36767
- found = null;
36768
- async find() {
36769
- if (this.found !== null && await this.whyNot(this.found) === null) {
36770
- return { ok: true, path: this.found };
36771
- }
36772
- this.found = null;
36773
- const named = envValue("GODOT_PATH");
36774
- if (named !== undefined) {
36775
- const path = normalize2(named);
36776
- const reason = await this.whyNot(path);
36777
- if (reason === null) {
36778
- this.found = path;
36779
- return { ok: true, path };
36780
- }
36781
- return { ok: false, named: path, reason };
36782
- }
36783
- for (const candidate of godotCandidates(process.platform, resolveHomeDirectory())) {
36784
- const path = normalize2(candidate);
36785
- if (await this.whyNot(path) === null) {
36786
- this.found = path;
36787
- return { ok: true, path };
36788
- }
36789
- }
36790
- return { ok: false, named: null };
36791
- }
36792
- static refusal(located) {
36793
- if (located.named === null) {
36794
- return {
36795
- message: "No Godot executable found.",
36796
- advice: [
36797
- "Set GODOT_PATH to the Godot 4 executable",
36798
- "Or install Godot where it is usually found, such as /usr/bin/godot or C:\\Program Files\\Godot"
36799
- ]
36800
- };
36801
- }
36802
- return {
36803
- message: `GODOT_PATH is set to ${located.named}, which ${located.reason ?? "does not answer"}.`,
36804
- advice: [
36805
- "Point GODOT_PATH at a Godot 4 executable that answers --version",
36806
- "Or unset it, and the usual install locations are searched instead"
36807
- ]
36808
- };
36809
- }
36810
- async whyNot(path) {
36811
- const known = this.answered.get(path);
36812
- if (known !== undefined) {
36813
- return known;
36814
- }
36815
- let reason;
36816
- if (path !== "godot" && !existsSync13(path)) {
36817
- reason = "does not exist";
36818
- } else {
36819
- try {
36820
- await run5(path, ["--version"]);
36821
- reason = null;
36822
- } catch (error) {
36823
- reason = `does not answer --version: ${errorMessage(error)}`;
36824
- }
36825
- }
36826
- this.answered.set(path, reason);
36827
- return reason;
36828
- }
36829
- }
36830
- var run5;
36831
- var init_godot_path = __esm(() => {
36832
- init_detection();
36833
- init_errors();
36834
- run5 = promisify5(execFile5);
36835
- });
36836
-
36837
- // src/issues.ts
36838
- import { createHash as createHash4 } from "node:crypto";
36839
- import process7 from "node:process";
36840
- function feedbackNotice() {
36841
- return JSON.stringify({
36842
- gdharness_feedback: {
36843
- 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.",
36844
- enhancement_url: ENHANCEMENT_URL2
36845
- }
36846
- }, null, 2);
36847
- }
36848
- function runtime2() {
36849
- const versions = process7.versions;
36850
- const bun = versions["bun"];
36851
- return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
36852
- }
36853
- function defectSignature2(where, message) {
36854
- const normalised = `${where}
36855
- ${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
36856
- return createHash4("sha256").update(normalised).digest("hex").slice(0, 8);
36857
- }
36858
- function facts2(where, message, godotVersion) {
36859
- return [
36860
- ["Where", where],
36861
- ["Error", message],
36862
- ["Version", `gdharness ${SERVER_VERSION}`],
36863
- ["Runtime", `${runtime2()}, ${process7.platform} ${process7.arch}`],
36864
- ["Godot", godotVersion ?? "not known at the point this failed"],
36865
- ["Signature", defectSignature2(where, message)]
36866
- ];
36867
- }
36868
- function filledTemplate2(where, message, godotVersion) {
36869
- return [
36870
- `**gdharness version**: ${SERVER_VERSION}`,
36871
- `**Godot version**: ${godotVersion ?? ""}`,
36872
- `**Bun version**: ${runtime2()}`,
36873
- `**OS**: ${process7.platform} ${process7.arch}`,
36874
- "**MCP client**:",
36875
- "",
36876
- "## What you did",
36877
- "",
36878
- where,
36879
- "",
36880
- "## What you expected",
36881
- "",
36882
- "The call to answer, or to refuse with a reason.",
36883
- "",
36884
- "## What happened",
36885
- "",
36886
- "gdharness failed in a way it does not model.",
36887
- "",
36888
- "```text",
36889
- message,
36890
- "```",
36891
- "",
36892
- `Signature: \`${defectSignature2(where, message)}\``,
36893
- "",
36894
- "If the real cause turned out to be the project, the environment or the call, say so here:",
36895
- "reaching you as a defect rather than as a refusal naming what would have worked is then the",
36896
- "thing to fix.",
36897
- "",
36898
- "## Anything that makes it reproducible",
36899
- ""
36900
- ].join(`
36901
- `);
36902
- }
36903
-
36904
- class DefectsSeen {
36905
- counts = new Map;
36906
- before(where, message) {
36907
- const signature = defectSignature2(where, message);
36908
- const seen = this.counts.get(signature) ?? 0;
36909
- this.counts.set(signature, seen + 1);
36910
- return seen;
36911
- }
36912
- }
36913
- function defectReport2(where, error, context = {}) {
36914
- const { godotVersion, seenBefore = 0 } = context;
36915
- const message = errorMessage(error);
36916
- const title = `Defect ${defectSignature2(where, message)}: ${where}`;
36917
- const url = `${NEW_ISSUE2}?template=bug_report.md&title=${encodeURIComponent(title)}` + `&body=${encodeURIComponent(filledTemplate2(where, message, godotVersion))}`;
36918
- const width = Math.max(...facts2(where, message, godotVersion).map(([label]) => (label ?? "").length));
36919
- return [
36920
- "gdharness failed in a way it does not model. Whatever went wrong underneath, it reached you",
36921
- "as a crash rather than as a refusal naming what would have worked, and that much is a defect",
36922
- "in gdharness however the rest of it turns out.",
36923
- "",
36924
- ...seenBefore === 0 ? [
36925
- "Send the call once more before anything else. Some of what reaches this line is a file",
36926
- "another process had open, or a connection that dropped, and those are gone by the next",
36927
- "attempt. If the same Signature comes back, it is not one of those, and repeating it",
36928
- "further only spends turns."
36929
- ] : [
36930
- `You have had this exact failure ${seenBefore === 1 ? "once" : `${seenBefore} times`} in this session already, so it is a`,
36931
- "state rather than a passing one, and sending the call again will not change it."
36932
- ],
36933
- "",
36934
- ...facts2(where, message, godotVersion).map(([label, value]) => ` ${(label ?? "").padEnd(width)} ${value ?? ""}`),
36935
- "",
36936
- "Do not open an issue on your own initiative. Tell the person whose machine this is what",
36937
- "broke, and ask whether you may report it to gdharness on their behalf. What the report",
36938
- "carries is the lines above and nothing else, and whether that goes into a public tracker",
36939
- "is theirs to decide.",
36940
- "",
36941
- "If they say yes, this link carries the report already filled in:",
36942
- url,
36943
- "",
36944
- "If they say yes and you have no way to open an issue yourself, give them that link and",
36945
- "those lines, and stay with them while they file it.",
36946
- "",
36947
- "Worth reporting either way. If you can see that what actually went wrong was the project,",
36948
- "the environment or the call, then this message is the defect: a failure gdharness knows",
36949
- "about is meant to arrive as a refusal naming what would have worked, not as this. Say that",
36950
- "in the report and it is the more useful of the two."
36951
- ].join(`
36952
- `);
36953
- }
36954
- var NEW_ISSUE2 = "https://github.com/Aureliolo/gdharness/issues/new", ENHANCEMENT_URL2;
36955
- var init_issues = __esm(() => {
36956
- init_errors();
36957
- init_server_version();
36958
- ENHANCEMENT_URL2 = `${NEW_ISSUE2}?template=feature_request.md`;
36959
- });
36960
-
36961
37166
  // src/junit.ts
36962
37167
  function decoded(text) {
36963
37168
  return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, entity) => {
@@ -37149,7 +37354,7 @@ function scriptErrorsPrinted(printed) {
37149
37354
  for (const line of printed.slice(start + 1)) {
37150
37355
  const at = /^\s*at (res:\/\/.+):(\d+)\s*$/.exec(line);
37151
37356
  if (at !== null && message !== null) {
37152
- 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 });
37153
37358
  message = null;
37154
37359
  } else if (at === null && message === null && /^ {2}\S/.test(line)) {
37155
37360
  message = line.trim();
@@ -37157,7 +37362,7 @@ function scriptErrorsPrinted(printed) {
37157
37362
  break;
37158
37363
  }
37159
37364
  }
37160
- 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));
37161
37366
  }
37162
37367
  function unspelled(text) {
37163
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);
@@ -37299,8 +37504,8 @@ var init_junit = __esm(() => {
37299
37504
  import { realpathSync as realpathSync2 } from "node:fs";
37300
37505
  import { readFile, realpath } from "node:fs/promises";
37301
37506
  import { createConnection as createConnection3 } from "node:net";
37302
- import { dirname as dirname14, resolve as resolve4 } from "node:path";
37303
- 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";
37304
37509
  function diagnosticsKey(uri) {
37305
37510
  let decoded;
37306
37511
  try {
@@ -37312,7 +37517,7 @@ function diagnosticsKey(uri) {
37312
37517
  return decoded;
37313
37518
  }
37314
37519
  try {
37315
- return realpathSync2.native(fileURLToPath4(decoded)).toLowerCase();
37520
+ return realpathSync2.native(fileURLToPath3(decoded)).toLowerCase();
37316
37521
  } catch {
37317
37522
  return decoded.toLowerCase();
37318
37523
  }
@@ -37427,7 +37632,7 @@ class GodotLSPClient {
37427
37632
  if (this.initialized) {
37428
37633
  return;
37429
37634
  }
37430
- const rootPath = this.rootPath ?? dirname14(resolve4(filePath));
37635
+ const rootPath = this.rootPath ?? dirname12(resolve4(filePath));
37431
37636
  await this.initialize(rootPath);
37432
37637
  }
37433
37638
  async sendRequest(method, params) {
@@ -37834,7 +38039,7 @@ var init_lsp_client = __esm(() => {
37834
38039
 
37835
38040
  // src/outside.ts
37836
38041
  import { spawn } from "node:child_process";
37837
- import { fileURLToPath as fileURLToPath5 } from "node:url";
38042
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
37838
38043
  function changesFrom(env, base) {
37839
38044
  const changes = {};
37840
38045
  if (env === undefined) {
@@ -37897,12 +38102,12 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
37897
38102
  }
37898
38103
  var KEEPER_SCRIPT;
37899
38104
  var init_outside = __esm(() => {
37900
- KEEPER_SCRIPT = fileURLToPath5(new URL("./keeper.js", import.meta.url));
38105
+ KEEPER_SCRIPT = fileURLToPath4(new URL("./keeper.js", import.meta.url));
37901
38106
  });
37902
38107
 
37903
38108
  // src/process-time.ts
37904
- import { execFile as execFile6 } from "node:child_process";
37905
- import { promisify as promisify6 } from "node:util";
38109
+ import { execFile as execFile4 } from "node:child_process";
38110
+ import { promisify as promisify4 } from "node:util";
37906
38111
  async function cpuSecondsOf(pid) {
37907
38112
  try {
37908
38113
  return process.platform === "win32" ? await askWindows2(pid) : await askPosix2(pid);
@@ -37911,12 +38116,12 @@ async function cpuSecondsOf(pid) {
37911
38116
  }
37912
38117
  }
37913
38118
  async function askWindows2(pid) {
37914
- 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 });
37915
38120
  const seconds = Number(stdout.trim().replace(",", "."));
37916
38121
  return Number.isFinite(seconds) ? seconds : undefined;
37917
38122
  }
37918
38123
  async function askPosix2(pid) {
37919
- 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 });
37920
38125
  return secondsFromClock(stdout.trim());
37921
38126
  }
37922
38127
  function secondsFromClock(printed) {
@@ -37934,19 +38139,19 @@ function secondsFromClock(printed) {
37934
38139
  const extra = days === null ? 0 : Number(days) * 86400;
37935
38140
  return Number.isFinite(extra) ? seconds + extra : undefined;
37936
38141
  }
37937
- var run6, ASK_TIMEOUT_MS2 = 6000;
38142
+ var run4, ASK_TIMEOUT_MS2 = 6000;
37938
38143
  var init_process_time = __esm(() => {
37939
- run6 = promisify6(execFile6);
38144
+ run4 = promisify4(execFile4);
37940
38145
  });
37941
38146
 
37942
38147
  // src/project-scan.ts
37943
- import { existsSync as existsSync14, readdirSync as readdirSync7, readFileSync as readFileSync18 } from "node:fs";
37944
- 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";
37945
38150
  function skipped(name) {
37946
38151
  return name.startsWith(".") || SKIPPED.has(name);
37947
38152
  }
37948
38153
  function steppedOver(directory) {
37949
- return existsSync14(join20(directory, ".gdignore"));
38154
+ return existsSync12(join17(directory, ".gdignore"));
37950
38155
  }
37951
38156
  function projectStructure(projectPath) {
37952
38157
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -37954,12 +38159,12 @@ function projectStructure(projectPath) {
37954
38159
  if (steppedOver(directory)) {
37955
38160
  return;
37956
38161
  }
37957
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38162
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
37958
38163
  if (skipped(entry.name)) {
37959
38164
  continue;
37960
38165
  }
37961
38166
  if (entry.isDirectory()) {
37962
- visit(join20(directory, entry.name));
38167
+ visit(join17(directory, entry.name));
37963
38168
  } else if (entry.isFile()) {
37964
38169
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37965
38170
  if (extension === "tscn") {
@@ -37983,20 +38188,20 @@ function scriptsWithoutUid(projectPath) {
37983
38188
  if (steppedOver(directory)) {
37984
38189
  return;
37985
38190
  }
37986
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38191
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
37987
38192
  if (skipped(entry.name)) {
37988
38193
  continue;
37989
38194
  }
37990
38195
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
37991
38196
  if (entry.isDirectory()) {
37992
- visit(join20(directory, entry.name), spelled);
38197
+ visit(join17(directory, entry.name), spelled);
37993
38198
  continue;
37994
38199
  }
37995
38200
  if (!entry.isFile()) {
37996
38201
  continue;
37997
38202
  }
37998
38203
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
37999
- 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`))) {
38000
38205
  found.push(spelled);
38001
38206
  }
38002
38207
  }
@@ -38024,14 +38229,14 @@ function searchProject(projectPath, options) {
38024
38229
  if (steppedOver(directory)) {
38025
38230
  return;
38026
38231
  }
38027
- for (const entry of readdirSync7(directory, { withFileTypes: true })) {
38232
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
38028
38233
  if (full()) {
38029
38234
  return;
38030
38235
  }
38031
38236
  if (skipped(entry.name)) {
38032
38237
  continue;
38033
38238
  }
38034
- const entryPath = join20(directory, entry.name);
38239
+ const entryPath = join17(directory, entry.name);
38035
38240
  if (entry.isDirectory()) {
38036
38241
  visit(entryPath);
38037
38242
  continue;
@@ -38042,7 +38247,7 @@ function searchProject(projectPath, options) {
38042
38247
  }
38043
38248
  result.summary.files_searched += 1;
38044
38249
  const matches = [];
38045
- for (const [index, line] of readFileSync18(entryPath, "utf8").split(`
38250
+ for (const [index, line] of readFileSync15(entryPath, "utf8").split(`
38046
38251
  `).entries()) {
38047
38252
  if (full()) {
38048
38253
  break;
@@ -38071,37 +38276,37 @@ var init_project_scan = __esm(() => {
38071
38276
  });
38072
38277
 
38073
38278
  // src/reimport.ts
38074
- import { createHash as createHash5 } from "node:crypto";
38075
- import { existsSync as existsSync15, readFileSync as readFileSync19, rmSync as rmSync10, statSync as statSync7, utimesSync, writeFileSync as writeFileSync14 } from "node:fs";
38076
- 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";
38077
38282
  function forceNextImport(projectPath, resourcePath) {
38078
38283
  const relative = resourcePath.replace(/^res:\/\//, "");
38079
- const sidecar = join21(projectPath, `${relative}.import`);
38080
- if (!existsSync15(sidecar)) {
38284
+ const sidecar = join18(projectPath, `${relative}.import`);
38285
+ if (!existsSync13(sidecar)) {
38081
38286
  return;
38082
38287
  }
38083
- const { atime, mtimeMs } = statSync7(sidecar);
38084
- 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, ""));
38085
38290
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
38086
- rmSync10(importRecord(projectPath, resourcePath), { force: true });
38291
+ rmSync8(importRecord(projectPath, resourcePath), { force: true });
38087
38292
  }
38088
38293
  function importRecord(projectPath, resourcePath) {
38089
- const digest = createHash5("md5").update(resourcePath).digest("hex");
38090
- 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`);
38091
38296
  }
38092
38297
  var init_reimport = () => {};
38093
38298
 
38094
38299
  // src/restart-note.ts
38095
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
38096
- 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";
38097
38302
  function restartNotePath(projectPath) {
38098
- return join22(projectPath, ".godot", "gdharness-restart.json");
38303
+ return join19(projectPath, ".godot", "gdharness-restart.json");
38099
38304
  }
38100
38305
  function noteRestartBegun(note) {
38101
38306
  const path = restartNotePath(note.projectPath);
38102
38307
  try {
38103
- mkdirSync12(dirname15(path), { recursive: true });
38104
- writeFileSync15(path, `${JSON.stringify(note, null, 2)}
38308
+ mkdirSync10(dirname13(path), { recursive: true });
38309
+ writeFileSync12(path, `${JSON.stringify(note, null, 2)}
38105
38310
  `, "utf8");
38106
38311
  } catch (error) {
38107
38312
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -38109,7 +38314,7 @@ function noteRestartBegun(note) {
38109
38314
  }
38110
38315
  function restartOwed(projectPath) {
38111
38316
  try {
38112
- const parsed = JSON.parse(readFileSync20(restartNotePath(projectPath), "utf8"));
38317
+ const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
38113
38318
  if (typeof parsed !== "object" || parsed === null) {
38114
38319
  return null;
38115
38320
  }
@@ -38124,7 +38329,7 @@ function restartOwed(projectPath) {
38124
38329
  }
38125
38330
  function restartSettled(projectPath) {
38126
38331
  try {
38127
- rmSync11(restartNotePath(projectPath), { force: true });
38332
+ rmSync9(restartNotePath(projectPath), { force: true });
38128
38333
  } catch (error) {
38129
38334
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
38130
38335
  }
@@ -38135,29 +38340,29 @@ var init_restart_note = __esm(() => {
38135
38340
 
38136
38341
  // src/run-record.ts
38137
38342
  import { execFileSync as execFileSync2 } from "node:child_process";
38138
- import { createHash as createHash6 } from "node:crypto";
38343
+ import { createHash as createHash4 } from "node:crypto";
38139
38344
  import {
38140
- mkdirSync as mkdirSync13,
38345
+ mkdirSync as mkdirSync11,
38141
38346
  openSync as openSync2,
38142
- readdirSync as readdirSync8,
38143
- readFileSync as readFileSync21,
38347
+ readdirSync as readdirSync7,
38348
+ readFileSync as readFileSync18,
38144
38349
  realpathSync as realpathSync3,
38145
- rmSync as rmSync12,
38146
- statSync as statSync8,
38147
- writeFileSync as writeFileSync16
38350
+ rmSync as rmSync10,
38351
+ statSync as statSync7,
38352
+ writeFileSync as writeFileSync13
38148
38353
  } from "node:fs";
38149
- 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";
38150
38355
  function runsDirectory() {
38151
- return join23(runtimeDirectory(), "runs");
38356
+ return join20(runtimeDirectory(), "runs");
38152
38357
  }
38153
38358
  function realAsFarAsItExists(path) {
38154
38359
  const missing = [];
38155
38360
  let at = path;
38156
38361
  for (;; ) {
38157
38362
  try {
38158
- return join23(realpathSync3.native(at), ...missing);
38363
+ return join20(realpathSync3.native(at), ...missing);
38159
38364
  } catch {
38160
- const parent = dirname16(at);
38365
+ const parent = dirname14(at);
38161
38366
  if (parent === at) {
38162
38367
  return path;
38163
38368
  }
@@ -38169,26 +38374,26 @@ function realAsFarAsItExists(path) {
38169
38374
  function projectKey(projectPath) {
38170
38375
  const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
38171
38376
  const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
38172
- return createHash6("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
38377
+ return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
38173
38378
  }
38174
38379
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
38175
- return join23(directory, "runs", `note-${projectKey(projectPath)}.json`);
38380
+ return join20(directory, "runs", `note-${projectKey(projectPath)}.json`);
38176
38381
  }
38177
38382
  function openTranscript(startedAt) {
38178
- mkdirSync13(runsDirectory(), { recursive: true });
38179
- const path = join23(runsDirectory(), `run-${startedAt}.log`);
38383
+ mkdirSync11(runsDirectory(), { recursive: true });
38384
+ const path = join20(runsDirectory(), `run-${startedAt}.log`);
38180
38385
  return { path, fd: openSync2(path, "a") };
38181
38386
  }
38182
38387
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
38183
- return join23(directory, "runs", `editor-${projectKey(projectPath)}.json`);
38388
+ return join20(directory, "runs", `editor-${projectKey(projectPath)}.json`);
38184
38389
  }
38185
38390
  function writeEditorRunNote(note) {
38186
- mkdirSync13(runsDirectory(), { recursive: true });
38187
- 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");
38188
38393
  }
38189
38394
  function readEditorRunNote(projectPath) {
38190
38395
  for (const directory of runtimeDirectories()) {
38191
- 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"));
38192
38397
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
38193
38398
  return note;
38194
38399
  }
@@ -38198,7 +38403,7 @@ function readEditorRunNote(projectPath) {
38198
38403
  function editorNoteAt(path) {
38199
38404
  let parsed;
38200
38405
  try {
38201
- parsed = JSON.parse(readFileSync21(path, "utf8"));
38406
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
38202
38407
  } catch {
38203
38408
  return null;
38204
38409
  }
@@ -38220,7 +38425,7 @@ function readRunRecord(projectPath) {
38220
38425
  if (own !== null) {
38221
38426
  return own;
38222
38427
  }
38223
- const shared = recordAt(join23(directory, "runs", SHARED_NOTE));
38428
+ const shared = recordAt(join20(directory, "runs", SHARED_NOTE));
38224
38429
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
38225
38430
  return shared;
38226
38431
  }
@@ -38232,13 +38437,13 @@ function everyRunRecord() {
38232
38437
  for (const directory of runtimeDirectories()) {
38233
38438
  let entries;
38234
38439
  try {
38235
- entries = readdirSync8(join23(directory, "runs"));
38440
+ entries = readdirSync7(join20(directory, "runs"));
38236
38441
  } catch {
38237
38442
  continue;
38238
38443
  }
38239
38444
  for (const entry of entries) {
38240
38445
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
38241
- const record = recordAt(join23(directory, "runs", entry));
38446
+ const record = recordAt(join20(directory, "runs", entry));
38242
38447
  if (record !== null) {
38243
38448
  found.push(record);
38244
38449
  }
@@ -38250,7 +38455,7 @@ function everyRunRecord() {
38250
38455
  function recordAt(path) {
38251
38456
  let parsed;
38252
38457
  try {
38253
- parsed = JSON.parse(readFileSync21(path, "utf8"));
38458
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
38254
38459
  } catch {
38255
38460
  return null;
38256
38461
  }
@@ -38296,7 +38501,7 @@ function runningAs(pid) {
38296
38501
  const began = startedAt(pid, null);
38297
38502
  const when = began === null ? {} : { startedAt: began };
38298
38503
  if (process.platform === "linux") {
38299
- const raw = readFileSync21(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
38504
+ const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
38300
38505
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
38301
38506
  }
38302
38507
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -38434,19 +38639,19 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
38434
38639
  }
38435
38640
  function clearRunRecord(projectPath) {
38436
38641
  for (const directory of runtimeDirectories()) {
38437
- const shared = join23(directory, "runs", SHARED_NOTE);
38642
+ const shared = join20(directory, "runs", SHARED_NOTE);
38438
38643
  const sharedRecord = recordAt(shared);
38439
38644
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
38440
38645
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
38441
38646
  try {
38442
- rmSync12(path, { force: true });
38647
+ rmSync10(path, { force: true });
38443
38648
  } catch {}
38444
38649
  }
38445
38650
  }
38446
38651
  }
38447
38652
  function ageMs(path, now) {
38448
38653
  try {
38449
- return now - statSync8(path).mtimeMs;
38654
+ return now - statSync7(path).mtimeMs;
38450
38655
  } catch {
38451
38656
  return 0;
38452
38657
  }
@@ -38456,7 +38661,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38456
38661
  const kept = new Set(everyRunRecord().map((record) => record.transcript));
38457
38662
  let entries;
38458
38663
  try {
38459
- entries = readdirSync8(directory);
38664
+ entries = readdirSync7(directory);
38460
38665
  } catch {
38461
38666
  return;
38462
38667
  }
@@ -38464,12 +38669,12 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38464
38669
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
38465
38670
  continue;
38466
38671
  }
38467
- const path = join23(directory, entry);
38672
+ const path = join20(directory, entry);
38468
38673
  if (kept.has(path) || ageMs(path, now) < keepMs) {
38469
38674
  continue;
38470
38675
  }
38471
38676
  try {
38472
- rmSync12(path);
38677
+ rmSync10(path);
38473
38678
  } catch {}
38474
38679
  }
38475
38680
  }
@@ -38481,29 +38686,29 @@ var init_run_record = __esm(() => {
38481
38686
  });
38482
38687
 
38483
38688
  // src/server.ts
38484
- import { execFile as execFile7, spawn as spawn2 } from "node:child_process";
38689
+ import { execFile as execFile5, spawn as spawn2 } from "node:child_process";
38485
38690
  import { randomUUID as randomUUID2 } from "node:crypto";
38486
38691
  import {
38487
38692
  appendFileSync,
38488
38693
  closeSync as closeSync2,
38489
38694
  constants,
38490
38695
  copyFileSync,
38491
- existsSync as existsSync16,
38696
+ existsSync as existsSync14,
38492
38697
  fstatSync,
38493
- mkdirSync as mkdirSync14,
38494
- mkdtempSync as mkdtempSync3,
38698
+ mkdirSync as mkdirSync12,
38699
+ mkdtempSync as mkdtempSync2,
38495
38700
  openSync as openSync3,
38496
- readdirSync as readdirSync9,
38497
- readFileSync as readFileSync22,
38701
+ readdirSync as readdirSync8,
38702
+ readFileSync as readFileSync19,
38498
38703
  readSync as readSync2,
38499
- rmdirSync as rmdirSync3,
38500
- statSync as statSync9
38704
+ rmdirSync as rmdirSync2,
38705
+ statSync as statSync8
38501
38706
  } from "node:fs";
38502
- import { tmpdir as tmpdir5 } from "node:os";
38503
- 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";
38504
38709
  import { setTimeout as delay3 } from "node:timers/promises";
38505
- import { fileURLToPath as fileURLToPath6 } from "node:url";
38506
- import { promisify as promisify7 } from "node:util";
38710
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
38711
+ import { promisify as promisify5 } from "node:util";
38507
38712
  function captureDestinationRefusal(outputPath, projects) {
38508
38713
  if (/^(res|user):\/\//.test(outputPath)) {
38509
38714
  return `outputPath ${outputPath} is a Godot path; a capture is saved outside the project, at an absolute path on this machine.`;
@@ -38514,16 +38719,16 @@ function captureDestinationRefusal(outputPath, projects) {
38514
38719
  if (extname2(outputPath).toLowerCase() !== ".png") {
38515
38720
  return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
38516
38721
  }
38517
- const directory = realPathOr(dirname17(outputPath));
38518
- if (!existsSync16(directory)) {
38519
- 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.`;
38520
38725
  }
38521
- const where = join24(directory, basename5(outputPath));
38726
+ const where = join21(directory, basename5(outputPath));
38522
38727
  const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
38523
38728
  if (inside !== undefined) {
38524
38729
  return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
38525
38730
  }
38526
- if (existsSync16(where)) {
38731
+ if (existsSync14(where)) {
38527
38732
  return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
38528
38733
  }
38529
38734
  return null;
@@ -38888,15 +39093,24 @@ function changedSettings(before, after) {
38888
39093
  return [...names].filter((name) => JSON.stringify(before.get(name)) !== JSON.stringify(after.get(name))).sort();
38889
39094
  }
38890
39095
  function scriptErrorsNote(errors) {
38891
- const first = new Map;
39096
+ const causes = new Map;
38892
39097
  for (const error of errors) {
38893
- if (!first.has(error.path)) {
38894
- first.set(error.path, error);
39098
+ if (!error.message.includes(DEPENDED_FAILED) && !causes.has(error.path)) {
39099
+ causes.set(error.path, error);
38895
39100
  }
38896
39101
  }
38897
- const scripts = [...first.values()].map((error) => `${error.path}:${error.line} ${error.message}${error.message.endsWith(".") ? "" : "."}`).join(" ");
38898
- const more = errors.length - first.size;
38899
- 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(" ");
38900
39114
  }
38901
39115
  function timedOutVerdict(timeoutMs, silentForMs) {
38902
39116
  const quiet = Math.round(silentForMs);
@@ -38953,13 +39167,13 @@ function aboveTheRunner(entry) {
38953
39167
  }
38954
39168
  function elsewhereIn(projectPath, asked) {
38955
39169
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
38956
- if (wanted === "" || existsSync16(join24(projectPath, wanted))) {
39170
+ if (wanted === "" || existsSync14(join21(projectPath, wanted))) {
38957
39171
  return "";
38958
39172
  }
38959
39173
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
38960
39174
  let near = [];
38961
39175
  try {
38962
- 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()));
38963
39177
  } catch {
38964
39178
  return "";
38965
39179
  }
@@ -38967,7 +39181,7 @@ function elsewhereIn(projectPath, asked) {
38967
39181
  return near.length === 0 ? "" : ` The project does have ${there}.`;
38968
39182
  }
38969
39183
  function hasMainScene(projectFile) {
38970
- const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
39184
+ const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
38971
39185
  return typeof scene === "string" && scene !== "";
38972
39186
  }
38973
39187
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -38975,13 +39189,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
38975
39189
  return false;
38976
39190
  }
38977
39191
  try {
38978
- return readFileSync22(join24(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
39192
+ return readFileSync19(join21(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
38979
39193
  } catch {
38980
39194
  return false;
38981
39195
  }
38982
39196
  }
38983
39197
  function editorPlaysHeadless(projectFile) {
38984
- const runArgs = parseProjectGodot(readFileSync22(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
39198
+ const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
38985
39199
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
38986
39200
  }
38987
39201
  function camelCased(params) {
@@ -38999,8 +39213,8 @@ function needsItsOwnProcess(asked) {
38999
39213
 
39000
39214
  class GodotServer {
39001
39215
  mcp;
39002
- locator = new GodotLocator2;
39003
- operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
39216
+ locator = new GodotLocator;
39217
+ operationsScript = join21(__dirname2, "godot", "operations", "godot_operations.gd");
39004
39218
  godotBridge;
39005
39219
  tools = buildToolDefinitions();
39006
39220
  activeProcess = null;
@@ -39059,7 +39273,7 @@ class GodotServer {
39059
39273
  async run() {
39060
39274
  const located = await this.locator.find();
39061
39275
  if (!located.ok) {
39062
- 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.`);
39063
39277
  } else {
39064
39278
  console.error(`[SERVER] Using Godot at: ${located.path}`);
39065
39279
  }
@@ -39090,7 +39304,7 @@ class GodotServer {
39090
39304
  return;
39091
39305
  }
39092
39306
  const wanted = this.godotBridge.configuredPort;
39093
- const announced = readAnnouncement2(announcementPath2(this.ownProject));
39307
+ const announced = readAnnouncement(announcementPath(this.ownProject));
39094
39308
  if (announced === null || announced.pid === process.pid || announced.port !== wanted || !alive(announced.pid)) {
39095
39309
  return;
39096
39310
  }
@@ -39176,7 +39390,7 @@ class GodotServer {
39176
39390
  if (this.ownProject === null) {
39177
39391
  return null;
39178
39392
  }
39179
- const announced = readAnnouncement2(announcementPath2(this.ownProject));
39393
+ const announced = readAnnouncement(announcementPath(this.ownProject));
39180
39394
  if (announced === null || announced.pid === process.pid) {
39181
39395
  return null;
39182
39396
  }
@@ -39299,7 +39513,7 @@ class GodotServer {
39299
39513
  content: [
39300
39514
  {
39301
39515
  type: "text",
39302
- text: defectReport2(where, error, {
39516
+ text: defectReport(where, error, {
39303
39517
  seenBefore,
39304
39518
  godotVersion: this.lastGodotVersion ?? undefined
39305
39519
  })
@@ -39641,11 +39855,16 @@ class GodotServer {
39641
39855
  return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args);
39642
39856
  case "runtime_input":
39643
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
+ }
39644
39861
  return await this.handleRuntimeCommand("click", {
39645
39862
  ...whichGame(args),
39646
39863
  path: readNonEmptyString(args, "nodePath") ?? "",
39647
39864
  button: readString(args, "button") ?? "left",
39648
- 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"] }
39649
39868
  });
39650
39869
  }
39651
39870
  if (op === "choose") {
@@ -39704,7 +39923,7 @@ class GodotServer {
39704
39923
  async engine() {
39705
39924
  const located = await this.locator.find();
39706
39925
  if (!located.ok) {
39707
- const refusal = GodotLocator2.refusal(located);
39926
+ const refusal = GodotLocator.refusal(located);
39708
39927
  return { ok: false, response: this.createErrorResponse(refusal.message, [...refusal.advice]) };
39709
39928
  }
39710
39929
  return { ok: true, value: located.path };
@@ -39714,8 +39933,8 @@ class GodotServer {
39714
39933
  if (path === undefined) {
39715
39934
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
39716
39935
  }
39717
- const file = join24(path, "project.godot");
39718
- if (!existsSync16(file)) {
39936
+ const file = join21(path, "project.godot");
39937
+ if (!existsSync14(file)) {
39719
39938
  return {
39720
39939
  ok: false,
39721
39940
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -40213,7 +40432,7 @@ class GodotServer {
40213
40432
  }
40214
40433
  const scanned = await this.untilTheEditorsScanIsWritten(projectPath);
40215
40434
  this.logDebug(`Running ${operation} in ${projectPath}: ${JSON.stringify(params)}`);
40216
- 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);
40217
40436
  return withScanWait(outcome, scanned);
40218
40437
  }
40219
40438
  answer(outcome) {
@@ -40243,7 +40462,7 @@ class GodotServer {
40243
40462
  if (!engine.ok) {
40244
40463
  return engine.response;
40245
40464
  }
40246
- const application = parseProjectGodot(readFileSync22(project.value.file, "utf8"))["application"] ?? {};
40465
+ const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
40247
40466
  const name = application["config/name"];
40248
40467
  const mainScene = application["run/main_scene"];
40249
40468
  const info = {
@@ -40304,18 +40523,18 @@ class GodotServer {
40304
40523
  if (!engine.ok) {
40305
40524
  return engine.response;
40306
40525
  }
40307
- const folder = dirname17(output.absolutePath);
40526
+ const folder = dirname15(output.absolutePath);
40308
40527
  try {
40309
- mkdirSync14(folder, { recursive: true });
40528
+ mkdirSync12(folder, { recursive: true });
40310
40529
  } catch (error) {
40311
- 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)}`);
40312
40531
  }
40313
40532
  const debug = readBoolean(args, "debug") ?? false;
40314
- const exportLogs = mkdtempSync3(join24(tmpdir5(), "gdharness-export-"));
40533
+ const exportLogs = mkdtempSync2(join21(tmpdir4(), "gdharness-export-"));
40315
40534
  const exportArgs = [
40316
40535
  "--headless",
40317
40536
  "--log-file",
40318
- join24(exportLogs, "engine.log"),
40537
+ join21(exportLogs, "engine.log"),
40319
40538
  "--path",
40320
40539
  project.value.path,
40321
40540
  debug ? "--export-debug" : "--export-release",
@@ -40326,7 +40545,7 @@ class GodotServer {
40326
40545
  const log = new GameLog;
40327
40546
  let ending = { exitCode: 0, exitSignal: null, failure: null };
40328
40547
  try {
40329
- const { stdout, stderr } = await run7(engine.value, exportArgs, {
40548
+ const { stdout, stderr } = await run5(engine.value, exportArgs, {
40330
40549
  timeout: 300000,
40331
40550
  maxBuffer: 64 * 1024 * 1024,
40332
40551
  signal: callSignal()
@@ -40347,7 +40566,7 @@ class GodotServer {
40347
40566
  log.finish();
40348
40567
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
40349
40568
  const verdict = {
40350
- exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync16(output.absolutePath),
40569
+ exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync14(output.absolutePath),
40351
40570
  preset,
40352
40571
  outputPath: output.relativePath,
40353
40572
  debug,
@@ -40383,7 +40602,7 @@ class GodotServer {
40383
40602
  return contained.response;
40384
40603
  }
40385
40604
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
40386
- if (!existsSync16(join24(project.value.path, runner))) {
40605
+ if (!existsSync14(join21(project.value.path, runner))) {
40387
40606
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
40388
40607
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
40389
40608
  ]);
@@ -40419,7 +40638,7 @@ class GodotServer {
40419
40638
  ];
40420
40639
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
40421
40640
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
40422
- const userData = mkdtempSync3(join24(tmpdir5(), "gdharness-tests-"));
40641
+ const userData = mkdtempSync2(join21(tmpdir4(), "gdharness-tests-"));
40423
40642
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
40424
40643
  const console2 = [];
40425
40644
  let lastSaid = Date.now();
@@ -40446,21 +40665,21 @@ class GodotServer {
40446
40665
  resolve(false);
40447
40666
  });
40448
40667
  });
40449
- const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
40668
+ const reportsDir = join21(project.value.path, ".godot", ...ours.split("/"));
40450
40669
  let report = null;
40451
40670
  let reportProblem = null;
40452
40671
  try {
40453
- 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))) : [];
40454
40673
  const newest = written.at(-1);
40455
40674
  if (newest !== undefined) {
40456
- report = parseJUnit(readFileSync22(join24(reportsDir, newest, "results.xml"), "utf8"));
40675
+ report = parseJUnit(readFileSync19(join21(reportsDir, newest, "results.xml"), "utf8"));
40457
40676
  }
40458
40677
  } catch (error) {
40459
40678
  reportProblem = errorMessage(error);
40460
40679
  } finally {
40461
40680
  discard(reportsDir, userData);
40462
40681
  try {
40463
- rmdirSync3(dirname17(reportsDir));
40682
+ rmdirSync2(dirname15(reportsDir));
40464
40683
  } catch {}
40465
40684
  }
40466
40685
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
@@ -40482,6 +40701,12 @@ class GodotServer {
40482
40701
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
40483
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));
40484
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
+ };
40485
40710
  if (report === null) {
40486
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}` : ""}).`;
40487
40712
  return {
@@ -40500,6 +40725,7 @@ class GodotServer {
40500
40725
  ...scriptErrors.length > 0 ? { scriptErrors } : {},
40501
40726
  arguments: cmdArgs,
40502
40727
  entries: forAnswer(printed.slice(0, 60).map(aboveTheRunner)),
40728
+ ...printedLines,
40503
40729
  savesNote,
40504
40730
  ...scanned
40505
40731
  }, null, 2)
@@ -40536,6 +40762,7 @@ class GodotServer {
40536
40762
  warnings: warnings.length > 0 ? warnings : undefined,
40537
40763
  orphans: orphans.total > 0 ? orphans.total : undefined,
40538
40764
  notRun: notRun > 0 ? notRun : undefined,
40765
+ ...printedLines,
40539
40766
  savesNote,
40540
40767
  ...scanned,
40541
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,
@@ -40559,7 +40786,7 @@ class GodotServer {
40559
40786
  }
40560
40787
  async godotVersion(godotPath) {
40561
40788
  try {
40562
- const { stdout } = await run7(godotPath, ["--version"], { timeout: 1e4 });
40789
+ const { stdout } = await run5(godotPath, ["--version"], { timeout: 1e4 });
40563
40790
  this.lastGodotVersion = stdout.trim();
40564
40791
  return this.lastGodotVersion;
40565
40792
  } catch (error) {
@@ -40603,11 +40830,11 @@ class GodotServer {
40603
40830
  const cached = cachedClasses(projectPath);
40604
40831
  const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
40605
40832
  const contained = resolveWithinProject(projectPath, resourcePath);
40606
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
40833
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
40607
40834
  return null;
40608
40835
  }
40609
40836
  try {
40610
- return readFileSync22(contained.absolutePath, "utf8");
40837
+ return readFileSync19(contained.absolutePath, "utf8");
40611
40838
  } catch {
40612
40839
  return null;
40613
40840
  }
@@ -40657,11 +40884,11 @@ class GodotServer {
40657
40884
  }
40658
40885
  sourceOfClass(projectPath, resourcePath) {
40659
40886
  const contained = resolveWithinProject(projectPath, resourcePath);
40660
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
40887
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
40661
40888
  return null;
40662
40889
  }
40663
40890
  try {
40664
- return readFileSync22(contained.absolutePath, "utf8");
40891
+ return readFileSync19(contained.absolutePath, "utf8");
40665
40892
  } catch {
40666
40893
  return null;
40667
40894
  }
@@ -40907,7 +41134,7 @@ class GodotServer {
40907
41134
  godot: {
40908
41135
  path: located.ok ? located.path : null,
40909
41136
  version: located.ok ? await this.godotVersion(located.path) : null,
40910
- problem: located.ok ? undefined : GodotLocator2.refusal(located).message
41137
+ problem: located.ok ? undefined : GodotLocator.refusal(located).message
40911
41138
  },
40912
41139
  game: {
40913
41140
  processActive: active,
@@ -40930,7 +41157,7 @@ class GodotServer {
40930
41157
  return null;
40931
41158
  }
40932
41159
  try {
40933
- return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8"));
41160
+ return settingKeys(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
40934
41161
  } catch {
40935
41162
  return null;
40936
41163
  }
@@ -41183,7 +41410,7 @@ class GodotServer {
41183
41410
  }
41184
41411
  async runtimeUp(projectPath, before, budget, withArgs) {
41185
41412
  const budgetMs = budget.ms;
41186
- if (!existsSync16(join24(projectPath, RUNTIME_AUTOLOAD2.path))) {
41413
+ if (!existsSync14(join21(projectPath, RUNTIME_AUTOLOAD.path))) {
41187
41414
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
41188
41415
  }
41189
41416
  let asking = null;
@@ -41246,7 +41473,7 @@ class GodotServer {
41246
41473
  }
41247
41474
  let announcedAt;
41248
41475
  try {
41249
- announcedAt = statSync9(endpoint.file).mtimeMs;
41476
+ announcedAt = statSync8(endpoint.file).mtimeMs;
41250
41477
  } catch {
41251
41478
  return;
41252
41479
  }
@@ -41921,15 +42148,18 @@ class GodotServer {
41921
42148
  "editor_output with no op answers about the game a run is playing, which is a different log"
41922
42149
  ]);
41923
42150
  }
41924
- 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");
41925
42154
  const selected = console2.log.select({
41926
- severity: severity === "error" || severity === "warning" ? severity : "info",
42155
+ severity,
41927
42156
  sinceLastCall: false,
41928
- contains: readNonEmptyString(args, "contains"),
42157
+ contains,
41929
42158
  limit: readPositiveNumber(args, "limit") ?? 200
41930
42159
  });
41931
42160
  const everything = console2.log.everything();
41932
- 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);
41933
42163
  const shown = grouped.slice(0, MOST_GROUPS);
41934
42164
  return this.jsonTextResponse({
41935
42165
  editorPid: editorPid ?? undefined,
@@ -41997,7 +42227,7 @@ class GodotServer {
41997
42227
  notes.push(endedWithoutACode({
41998
42228
  throughEditor: run.throughEditor,
41999
42229
  announced: announced !== undefined || run.announcedPid !== undefined,
42000
- couldAnnounce: run.projectPath !== null && existsSync16(join24(run.projectPath, RUNTIME_AUTOLOAD2.path)),
42230
+ couldAnnounce: run.projectPath !== null && existsSync14(join21(run.projectPath, RUNTIME_AUTOLOAD.path)),
42001
42231
  endedHere: typeof run.endedHere === "string",
42002
42232
  pickedUp: run.pickedUp === true
42003
42233
  }));
@@ -42510,7 +42740,7 @@ class GodotServer {
42510
42740
  async ownRunOnItsWay(projectPath, budgetMs) {
42511
42741
  const run = this.currentRun();
42512
42742
  const project = projectPath ?? run?.projectPath ?? null;
42513
- 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)) {
42514
42744
  return null;
42515
42745
  }
42516
42746
  const already = runtimesAnnounced().running;
@@ -42519,7 +42749,7 @@ class GodotServer {
42519
42749
  return !run.announcedBefore.has(one.pid);
42520
42750
  }
42521
42751
  try {
42522
- return statSync9(one.file).mtimeMs >= run.startedAt;
42752
+ return statSync8(one.file).mtimeMs >= run.startedAt;
42523
42753
  } catch {
42524
42754
  return false;
42525
42755
  }
@@ -42589,8 +42819,8 @@ class GodotServer {
42589
42819
  return this.createErrorResponse(refusal);
42590
42820
  }
42591
42821
  }
42592
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join24(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
42593
- 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;
42594
42824
  try {
42595
42825
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
42596
42826
  if (!reply.ok && reply.requestId !== undefined) {
@@ -42614,7 +42844,7 @@ class GodotServer {
42614
42844
  if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
42615
42845
  return this.createErrorResponse(`The game answered a capture with ${JSON.stringify(payload)}`);
42616
42846
  }
42617
- if (normalize3(returnedPath) !== normalize3(screenshotPath)) {
42847
+ if (normalize2(returnedPath) !== normalize2(screenshotPath)) {
42618
42848
  return this.createErrorResponse(`Rejected screenshot file path outside the managed capture path: '${returnedPath}'`);
42619
42849
  }
42620
42850
  const dimensions = `${readNumber(payload, "width") ?? 0}x${readNumber(payload, "height") ?? 0} ${readString(payload, "format") ?? "unknown"}`;
@@ -42631,7 +42861,7 @@ class GodotServer {
42631
42861
  type: "text",
42632
42862
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}`
42633
42863
  },
42634
- { type: "image", data: readFileSync22(screenshotPath).toString("base64"), mimeType: "image/png" }
42864
+ { type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
42635
42865
  ]
42636
42866
  };
42637
42867
  } finally {
@@ -42731,7 +42961,7 @@ async function runGodotServer() {
42731
42961
  const server = new GodotServer;
42732
42962
  await server.run();
42733
42963
  }
42734
- 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/";
42735
42965
  var init_server3 = __esm(() => {
42736
42966
  init_mcp();
42737
42967
  init_stdio2();
@@ -42771,8 +43001,8 @@ var init_server3 = __esm(() => {
42771
43001
  init_setup();
42772
43002
  init_tool_definitions();
42773
43003
  init_update_check();
42774
- run7 = promisify7(execFile7);
42775
- __dirname2 = dirname17(fileURLToPath6(import.meta.url));
43004
+ run5 = promisify5(execFile5);
43005
+ __dirname2 = dirname15(fileURLToPath5(import.meta.url));
42776
43006
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
42777
43007
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
42778
43008
  VALUE_NAMES = {
@@ -42818,128 +43048,11 @@ var init_server3 = __esm(() => {
42818
43048
  });
42819
43049
 
42820
43050
  // src/cli.ts
42821
- import { existsSync as existsSync17 } from "fs";
42822
- import { join as join25, resolve as resolve6 } from "path";
42823
-
42824
- // src/bridge-announce.ts
42825
- init_errors();
42826
- init_process_children();
42827
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
42828
- import { dirname, join } from "node:path";
42829
- function announcementPath(projectPath) {
42830
- return join(projectPath, ".godot", "gdharness-bridge.json");
42831
- }
42832
- function readAnnouncement(path) {
42833
- try {
42834
- const parsed = JSON.parse(readFileSync(path, "utf8"));
42835
- if (typeof parsed !== "object" || parsed === null) {
42836
- return null;
42837
- }
42838
- const said = parsed;
42839
- if (typeof said.port !== "number" || typeof said.pid !== "number") {
42840
- return null;
42841
- }
42842
- return said;
42843
- } catch {
42844
- return null;
42845
- }
42846
- }
42847
- function serverBehind(announcement, startedAt) {
42848
- if (announcement === null || typeof announcement.version !== "string" || startedAt === undefined) {
42849
- return null;
42850
- }
42851
- const announcedAt = Date.parse(announcement.startedAt);
42852
- if (Number.isNaN(announcedAt) || startedAt > announcedAt + 1000) {
42853
- return null;
42854
- }
42855
- return { version: announcement.version, pid: announcement.pid };
42856
- }
42857
- function serverServing(projectPath) {
42858
- const announcement = readAnnouncement(announcementPath(projectPath));
42859
- if (announcement === null) {
42860
- return null;
42861
- }
42862
- return serverBehind(announcement, startTimesOf([announcement.pid]).get(announcement.pid));
42863
- }
42864
-
42865
- // src/errors.ts
42866
- class Refusal2 extends Error {
42867
- }
42868
-
42869
- // src/godot-path.ts
42870
- init_detection();
43051
+ init_bridge_announce();
42871
43052
  init_errors();
42872
- import { execFile as execFile2 } from "node:child_process";
42873
- import { existsSync as existsSync2 } from "node:fs";
42874
- import { normalize } from "node:path";
42875
- import { promisify as promisify2 } from "node:util";
42876
- var run2 = promisify2(execFile2);
42877
-
42878
- class GodotLocator {
42879
- answered = new Map;
42880
- found = null;
42881
- async find() {
42882
- if (this.found !== null && await this.whyNot(this.found) === null) {
42883
- return { ok: true, path: this.found };
42884
- }
42885
- this.found = null;
42886
- const named = envValue("GODOT_PATH");
42887
- if (named !== undefined) {
42888
- const path = normalize(named);
42889
- const reason = await this.whyNot(path);
42890
- if (reason === null) {
42891
- this.found = path;
42892
- return { ok: true, path };
42893
- }
42894
- return { ok: false, named: path, reason };
42895
- }
42896
- for (const candidate of godotCandidates(process.platform, resolveHomeDirectory())) {
42897
- const path = normalize(candidate);
42898
- if (await this.whyNot(path) === null) {
42899
- this.found = path;
42900
- return { ok: true, path };
42901
- }
42902
- }
42903
- return { ok: false, named: null };
42904
- }
42905
- static refusal(located) {
42906
- if (located.named === null) {
42907
- return {
42908
- message: "No Godot executable found.",
42909
- advice: [
42910
- "Set GODOT_PATH to the Godot 4 executable",
42911
- "Or install Godot where it is usually found, such as /usr/bin/godot or C:\\Program Files\\Godot"
42912
- ]
42913
- };
42914
- }
42915
- return {
42916
- message: `GODOT_PATH is set to ${located.named}, which ${located.reason ?? "does not answer"}.`,
42917
- advice: [
42918
- "Point GODOT_PATH at a Godot 4 executable that answers --version",
42919
- "Or unset it, and the usual install locations are searched instead"
42920
- ]
42921
- };
42922
- }
42923
- async whyNot(path) {
42924
- const known = this.answered.get(path);
42925
- if (known !== undefined) {
42926
- return known;
42927
- }
42928
- let reason;
42929
- if (path !== "godot" && !existsSync2(path)) {
42930
- reason = "does not exist";
42931
- } else {
42932
- try {
42933
- await run2(path, ["--version"]);
42934
- reason = null;
42935
- } catch (error) {
42936
- reason = `does not answer --version: ${errorMessage(error)}`;
42937
- }
42938
- }
42939
- this.answered.set(path, reason);
42940
- return reason;
42941
- }
42942
- }
43053
+ init_godot_path();
43054
+ import { existsSync as existsSync15 } from "node:fs";
43055
+ import { join as join22, resolve as resolve6 } from "node:path";
42943
43056
 
42944
43057
  // src/harnesses.ts
42945
43058
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
@@ -43804,204 +43917,9 @@ function taken(harness, path, held, stripped) {
43804
43917
  return { harness, path, action: "removed" };
43805
43918
  }
43806
43919
 
43807
- // src/headless.ts
43808
- init_call_signal();
43809
- init_game_log();
43810
- init_scratch();
43811
- import { execFile as execFile3 } from "node:child_process";
43812
- import { mkdtempSync, writeFileSync as writeFileSync3 } from "node:fs";
43813
- import { tmpdir } from "node:os";
43814
- import { join as join5 } from "node:path";
43815
- import { promisify as promisify3 } from "node:util";
43816
- var run3 = promisify3(execFile3);
43817
- function snakeCased(params) {
43818
- const result = emptyRecord();
43819
- for (const [key, value] of Object.entries(params)) {
43820
- const name = key.startsWith("_") ? key : key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
43821
- result[name] = value;
43822
- }
43823
- return result;
43824
- }
43825
- function lastJsonObject(stdout) {
43826
- const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{"));
43827
- for (const line of lines.reverse()) {
43828
- try {
43829
- const parsed = JSON.parse(line);
43830
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
43831
- return parsed;
43832
- }
43833
- } catch {}
43834
- }
43835
- return null;
43836
- }
43837
- function problems(stderr) {
43838
- const log = new GameLog;
43839
- log.append("stderr", stderr);
43840
- log.finish();
43841
- return log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
43842
- }
43843
- function reason(stdout, stderr) {
43844
- const own = stderr.split(/\r?\n/).filter((line) => line.startsWith("[ERROR] ")).map((line) => line.slice("[ERROR] ".length));
43845
- if (own.length > 0) {
43846
- return own.join("; ");
43847
- }
43848
- const engine = problems(stderr).filter((entry) => entry.severity === "error").map((entry) => entry.text);
43849
- if (engine.length > 0) {
43850
- return engine.join("; ");
43851
- }
43852
- return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
43853
- }
43854
- async function runOperation(engine, operation, params, projectPath) {
43855
- const paramsDir = mkdtempSync(join5(tmpdir(), "gdharness-params-"));
43856
- const paramsFile = join5(paramsDir, `${operation}.json`);
43857
- writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
43858
- const args = [
43859
- "--headless",
43860
- "--log-file",
43861
- join5(paramsDir, "engine.log"),
43862
- "--path",
43863
- projectPath,
43864
- "--script",
43865
- engine.script,
43866
- operation,
43867
- `@file:${paramsFile}`,
43868
- ...engine.debug ? ["--debug-godot"] : []
43869
- ];
43870
- let stdout;
43871
- let stderr;
43872
- try {
43873
- ({ stdout, stderr } = await run3(engine.godotPath, args, { signal: callSignal() }));
43874
- } catch (error) {
43875
- if (error instanceof Error && "stdout" in error && "stderr" in error) {
43876
- const failed = error;
43877
- return {
43878
- ok: false,
43879
- message: `${operation} failed (exit ${failed.code ?? "unknown"}): ${reason(failed.stdout, failed.stderr)}`,
43880
- messages: problems(failed.stderr)
43881
- };
43882
- }
43883
- return {
43884
- ok: false,
43885
- message: `${operation} could not be run: ${error instanceof Error ? error.message : String(error)}`,
43886
- messages: []
43887
- };
43888
- } finally {
43889
- discard(paramsDir);
43890
- }
43891
- const payload = lastJsonObject(stdout);
43892
- if (payload === null) {
43893
- return {
43894
- ok: false,
43895
- message: `${operation} produced no result: ${reason(stdout, stderr)}`,
43896
- messages: problems(stderr)
43897
- };
43898
- }
43899
- return { ok: true, payload, messages: problems(stderr) };
43900
- }
43901
-
43902
- // src/issues.ts
43903
- init_errors();
43904
- init_server_version();
43905
- import { createHash } from "node:crypto";
43906
- import process4 from "node:process";
43907
- var NEW_ISSUE = "https://github.com/Aureliolo/gdharness/issues/new";
43908
- var ENHANCEMENT_URL = `${NEW_ISSUE}?template=feature_request.md`;
43909
- function runtime() {
43910
- const versions = process4.versions;
43911
- const bun = versions["bun"];
43912
- return bun === undefined ? `node ${process4.versions.node}` : `bun ${bun}`;
43913
- }
43914
- function defectSignature(where, message) {
43915
- const normalised = `${where}
43916
- ${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
43917
- return createHash("sha256").update(normalised).digest("hex").slice(0, 8);
43918
- }
43919
- function facts(where, message, godotVersion) {
43920
- return [
43921
- ["Where", where],
43922
- ["Error", message],
43923
- ["Version", `gdharness ${SERVER_VERSION}`],
43924
- ["Runtime", `${runtime()}, ${process4.platform} ${process4.arch}`],
43925
- ["Godot", godotVersion ?? "not known at the point this failed"],
43926
- ["Signature", defectSignature(where, message)]
43927
- ];
43928
- }
43929
- function filledTemplate(where, message, godotVersion) {
43930
- return [
43931
- `**gdharness version**: ${SERVER_VERSION}`,
43932
- `**Godot version**: ${godotVersion ?? ""}`,
43933
- `**Bun version**: ${runtime()}`,
43934
- `**OS**: ${process4.platform} ${process4.arch}`,
43935
- "**MCP client**:",
43936
- "",
43937
- "## What you did",
43938
- "",
43939
- where,
43940
- "",
43941
- "## What you expected",
43942
- "",
43943
- "The call to answer, or to refuse with a reason.",
43944
- "",
43945
- "## What happened",
43946
- "",
43947
- "gdharness failed in a way it does not model.",
43948
- "",
43949
- "```text",
43950
- message,
43951
- "```",
43952
- "",
43953
- `Signature: \`${defectSignature(where, message)}\``,
43954
- "",
43955
- "If the real cause turned out to be the project, the environment or the call, say so here:",
43956
- "reaching you as a defect rather than as a refusal naming what would have worked is then the",
43957
- "thing to fix.",
43958
- "",
43959
- "## Anything that makes it reproducible",
43960
- ""
43961
- ].join(`
43962
- `);
43963
- }
43964
- function defectReport(where, error, context = {}) {
43965
- const { godotVersion, seenBefore = 0 } = context;
43966
- const message = errorMessage(error);
43967
- const title = `Defect ${defectSignature(where, message)}: ${where}`;
43968
- const url = `${NEW_ISSUE}?template=bug_report.md&title=${encodeURIComponent(title)}` + `&body=${encodeURIComponent(filledTemplate(where, message, godotVersion))}`;
43969
- const width = Math.max(...facts(where, message, godotVersion).map(([label]) => (label ?? "").length));
43970
- return [
43971
- "gdharness failed in a way it does not model. Whatever went wrong underneath, it reached you",
43972
- "as a crash rather than as a refusal naming what would have worked, and that much is a defect",
43973
- "in gdharness however the rest of it turns out.",
43974
- "",
43975
- ...seenBefore === 0 ? [
43976
- "Send the call once more before anything else. Some of what reaches this line is a file",
43977
- "another process had open, or a connection that dropped, and those are gone by the next",
43978
- "attempt. If the same Signature comes back, it is not one of those, and repeating it",
43979
- "further only spends turns."
43980
- ] : [
43981
- `You have had this exact failure ${seenBefore === 1 ? "once" : `${seenBefore} times`} in this session already, so it is a`,
43982
- "state rather than a passing one, and sending the call again will not change it."
43983
- ],
43984
- "",
43985
- ...facts(where, message, godotVersion).map(([label, value]) => ` ${(label ?? "").padEnd(width)} ${value ?? ""}`),
43986
- "",
43987
- "Do not open an issue on your own initiative. Tell the person whose machine this is what",
43988
- "broke, and ask whether you may report it to gdharness on their behalf. What the report",
43989
- "carries is the lines above and nothing else, and whether that goes into a public tracker",
43990
- "is theirs to decide.",
43991
- "",
43992
- "If they say yes, this link carries the report already filled in:",
43993
- url,
43994
- "",
43995
- "If they say yes and you have no way to open an issue yourself, give them that link and",
43996
- "those lines, and stay with them while they file it.",
43997
- "",
43998
- "Worth reporting either way. If you can see that what actually went wrong was the project,",
43999
- "the environment or the call, then this message is the defect: a failure gdharness knows",
44000
- "about is meant to arrive as a refusal naming what would have worked, not as this. Say that",
44001
- "in the report and it is the more useful of the two."
44002
- ].join(`
44003
- `);
44004
- }
43920
+ // src/cli.ts
43921
+ init_headless();
43922
+ init_issues();
44005
43923
 
44006
43924
  // src/prompt.ts
44007
43925
  import process5 from "node:process";
@@ -44026,256 +43944,15 @@ class Ask {
44026
43944
  }
44027
43945
  }
44028
43946
 
44029
- // src/server-version.ts
44030
- init_update_check();
44031
- import { readFileSync as readFileSync5 } from "node:fs";
44032
- var DEBUG_MODE2 = process.env["DEBUG"] === "true";
44033
- var GODOT_DEBUG_MODE_DEFAULT2 = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE2;
44034
- var SERVER_VERSION2 = (() => {
44035
- try {
44036
- const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
44037
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
44038
- } catch {
44039
- return "0.0.0";
44040
- }
44041
- })();
44042
-
44043
- // src/setup.ts
44044
- init_class_cache();
44045
- init_headless();
44046
- init_resources();
43947
+ // src/cli.ts
44047
43948
  init_server_version();
44048
- import { spawnSync as spawnSync2 } from "node:child_process";
44049
- import { createHash as createHash2 } from "node:crypto";
44050
- import {
44051
- cpSync,
44052
- existsSync as existsSync7,
44053
- mkdirSync as mkdirSync4,
44054
- readdirSync as readdirSync3,
44055
- readFileSync as readFileSync8,
44056
- rmdirSync,
44057
- rmSync as rmSync4,
44058
- statSync as statSync3,
44059
- writeFileSync as writeFileSync6
44060
- } from "node:fs";
44061
- import { dirname as dirname4, join as join9 } from "node:path";
44062
- import { fileURLToPath } from "node:url";
44063
- var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
44064
- var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
44065
- var RUNTIME_AUTOLOAD = {
44066
- name: "GdharnessRuntime",
44067
- path: "addons/gdharness_runtime/runtime_autoload.gd"
44068
- };
44069
- var VERSION_MARKER = ".gdharness-version";
44070
- var DIGEST_MARKER = ".gdharness-digest";
44071
- function editorAddonDigest(from = shippedAddonsDirectory()) {
44072
- const hash = createHash2("sha256");
44073
- const walk = (directory, relative) => {
44074
- for (const name of readdirSync3(directory).sort()) {
44075
- if (name === VERSION_MARKER || name === DIGEST_MARKER) {
44076
- continue;
44077
- }
44078
- const path = join9(directory, name);
44079
- const inside = `${relative}/${name}`;
44080
- if (statSync3(path).isDirectory()) {
44081
- walk(path, inside);
44082
- } else {
44083
- hash.update(`${inside}
44084
- `);
44085
- hash.update(readFileSync8(path));
44086
- hash.update(`
44087
- `);
44088
- }
44089
- }
44090
- };
44091
- for (const name of EDITOR_PLUGINS) {
44092
- walk(join9(from, name), name);
44093
- }
44094
- return hash.digest("hex");
44095
- }
44096
- function installedEditorDigest(projectPath) {
44097
- const marker = join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
44098
- if (!existsSync7(marker)) {
44099
- return null;
44100
- }
44101
- const said = readFileSync8(marker, "utf8").trim();
44102
- return said === "" ? null : said;
44103
- }
44104
- function shippedAddonsDirectory() {
44105
- return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "addons");
44106
- }
44107
- function shippedOperationsScript() {
44108
- return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
44109
- }
44110
- function installAddons(projectPath, from = shippedAddonsDirectory()) {
44111
- const installed = [];
44112
- for (const name of ADDONS) {
44113
- const source = join9(from, name);
44114
- if (!existsSync7(join9(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
44115
- throw new Error(`The package holds no ${name} addon at ${source}.`);
44116
- }
44117
- const target = join9(projectPath, "addons", name);
44118
- const replaced = existsSync7(target);
44119
- rmSync4(target, { recursive: true, force: true });
44120
- mkdirSync4(dirname4(target), { recursive: true });
44121
- cpSync(source, target, { recursive: true });
44122
- writeFileSync6(join9(target, VERSION_MARKER), `${SERVER_VERSION}
44123
- `);
44124
- installed.push({ name, path: target, replaced });
44125
- }
44126
- writeFileSync6(join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
44127
- `);
44128
- return installed;
44129
- }
44130
- function removeAddons(projectPath) {
44131
- const removed = [];
44132
- for (const name of ADDONS) {
44133
- const target = join9(projectPath, "addons", name);
44134
- if (existsSync7(target)) {
44135
- rmSync4(target, { recursive: true, force: true });
44136
- removed.push(target);
44137
- }
44138
- }
44139
- const addons = join9(projectPath, "addons");
44140
- if (removed.length > 0 && existsSync7(addons) && readdirSync3(addons).length === 0) {
44141
- rmdirSync(addons);
44142
- removed.push(addons);
44143
- }
44144
- return removed;
44145
- }
44146
- async function disablePlugins(engine, projectPath, names) {
44147
- const outcomes = [];
44148
- for (const name of names) {
44149
- outcomes.push(await runOperation2(engine, "disable_plugin", { pluginName: name }, projectPath));
44150
- }
44151
- return outcomes;
44152
- }
44153
- async function enablePlugins(engine, projectPath, names) {
44154
- const outcomes = [];
44155
- for (const name of names) {
44156
- outcomes.push(await runOperation2(engine, "enable_plugin", { pluginName: name }, projectPath));
44157
- }
44158
- return outcomes;
44159
- }
44160
- function autoloadIsOurs(named) {
44161
- return named === null || named === `res://${RUNTIME_AUTOLOAD.path}`;
44162
- }
44163
- async function setRuntime(engine, projectPath, on) {
44164
- 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);
44165
- }
44166
- function loaderAmong(projectPath, named) {
44167
- let byName = null;
44168
- for (const [entry, path] of named) {
44169
- if (entry === RUNTIME_AUTOLOAD.name || path === RUNTIME_AUTOLOAD.path) {
44170
- continue;
44171
- }
44172
- let source = null;
44173
- try {
44174
- source = readFileSync8(join9(projectPath, path), "utf8");
44175
- } catch {}
44176
- if (source?.includes(RUNTIME_AUTOLOAD.path)) {
44177
- return { name: entry, path, exists: true };
44178
- }
44179
- byName ??= path.toLowerCase().includes("gdharness") ? { name: entry, path, exists: source !== null } : null;
44180
- }
44181
- return byName;
44182
- }
44183
- function autoloadPaths(settings) {
44184
- const named = new Map;
44185
- for (const [name, value] of Object.entries(settings["autoload"] ?? {})) {
44186
- const said = typeof value === "string" ? value.replace(/^\*/, "") : "";
44187
- if (said.startsWith("res://")) {
44188
- named.set(name, said.slice("res://".length));
44189
- }
44190
- }
44191
- return named;
44192
- }
44193
- function trackedByGit(projectPath, wanted) {
44194
- if (wanted.length === 0) {
44195
- return new Set;
44196
- }
44197
- const asked = spawnSync2("git", ["ls-files", "-z", "--", ...wanted], {
44198
- cwd: projectPath,
44199
- encoding: "utf8"
44200
- });
44201
- if (asked.status !== 0) {
44202
- return null;
44203
- }
44204
- return new Set(asked.stdout.split("\x00").filter(Boolean));
44205
- }
44206
- function enabledPlugins(settings) {
44207
- const raw = settings["editor_plugins"]?.["enabled"];
44208
- if (typeof raw !== "string") {
44209
- return [];
44210
- }
44211
- return [...raw.matchAll(/res:\/\/addons\/([^/"]+)\/plugin\.cfg/g)].map((match) => match[1] ?? "").filter(Boolean);
44212
- }
44213
- function inspectProject(projectPath) {
44214
- const problems = [];
44215
- const addons = ADDONS.map((name) => {
44216
- const directory = join9(projectPath, "addons", name);
44217
- const marker = join9(directory, VERSION_MARKER);
44218
- const installed = existsSync7(directory);
44219
- const version = existsSync7(marker) ? readFileSync8(marker, "utf8").trim() : null;
44220
- const current = version === SERVER_VERSION;
44221
- if (!installed) {
44222
- problems.push(`addons/${name} is not installed; run gdharness setup`);
44223
- } else if (!current) {
44224
- problems.push(`addons/${name} is ${version ?? "of an unknown version"}, this gdharness is ${SERVER_VERSION}; run gdharness setup`);
44225
- }
44226
- return { name, installed, version, current };
44227
- });
44228
- const settings = parseProjectGodot(readFileSync8(join9(projectPath, "project.godot"), "utf8"));
44229
- const pluginsEnabled = enabledPlugins(settings);
44230
- for (const name of EDITOR_PLUGINS) {
44231
- if (!pluginsEnabled.includes(name)) {
44232
- problems.push(`the ${name} plugin is not enabled in project.godot; run gdharness setup`);
44233
- }
44234
- }
44235
- const autoload = settings["autoload"];
44236
- const registered = autoload === undefined ? undefined : readString(autoload, RUNTIME_AUTOLOAD.name);
44237
- const runtimeAutoloadPath = typeof registered === "string" ? registered.replace(/^\*/, "") : null;
44238
- const named = autoloadPaths(settings);
44239
- const runtimeLoaderAutoload = loaderAmong(projectPath, named);
44240
- const runtimeAutoload = runtimeAutoloadPath !== null || runtimeLoaderAutoload !== null;
44241
- const tracked = trackedByGit(projectPath, [...named.values()]);
44242
- const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
44243
- for (const [name, path] of named) {
44244
- if (!existsSync7(join9(projectPath, path))) {
44245
- if (!addonMissing(path)) {
44246
- 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`);
44247
- }
44248
- continue;
44249
- }
44250
- if (tracked !== null && !tracked.has(path)) {
44251
- 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`);
44252
- }
44253
- }
44254
- const cached = cachedClasses(projectPath);
44255
- const staleClasses = cached === null ? [] : staleAgainst(cached, projectPath);
44256
- if (cached === null) {
44257
- problems.push("no .godot/global_script_class_cache.cfg: the engine knows no class_name; run project_import refresh_classes or open the editor");
44258
- } else if (staleClasses.length > 0) {
44259
- problems.push(`the class cache does not list ${staleClasses.join(", ")}; run project_import refresh_classes or gdharness classes`);
44260
- }
44261
- return {
44262
- projectPath,
44263
- addons,
44264
- pluginsEnabled,
44265
- runtimeAutoload,
44266
- runtimeAutoloadPath,
44267
- runtimeLoaderAutoload,
44268
- staleClasses,
44269
- classCacheExists: cached !== null,
44270
- problems
44271
- };
44272
- }
43949
+ init_setup();
44273
43950
 
44274
43951
  // src/skill.ts
44275
43952
  init_setup();
44276
43953
  init_tool_definitions();
44277
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
44278
- 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";
44279
43956
 
44280
43957
  // src/tool-reference.ts
44281
43958
  init_headless_operations();
@@ -44371,7 +44048,7 @@ function renderToolsMarkdown() {
44371
44048
 
44372
44049
  // src/skill.ts
44373
44050
  var SKILL_NAME = "gdharness";
44374
- var SHARED_SKILLS = join11(".agents", "skills");
44051
+ var SHARED_SKILLS = join9(".agents", "skills");
44375
44052
  function skillMarkdown(version) {
44376
44053
  return `---
44377
44054
  name: ${SKILL_NAME}
@@ -44441,7 +44118,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
44441
44118
  | What a property reads right now, on a node whose path you already hold | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
44442
44119
  | What a property on an object a node holds reads | the same two, with colons: \`_game:clock:speed\` |
44443
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 |
44444
- | 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 |
44445
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 |
44446
44123
  | Fill in a field | \`runtime_input click\` on it, then \`runtime_input text\` with \`replace\`; a submitted field has to be clicked again |
44447
44124
  | Click somebody standing in a 3D room | \`runtime_input click\` on the Node3D, which aims at what it draws |
@@ -44560,27 +44237,27 @@ whatever the engine said on stderr comes back under \`engine_messages\`.
44560
44237
  function skillFiles(version) {
44561
44238
  return new Map([
44562
44239
  ["SKILL.md", skillMarkdown(version)],
44563
- [join11("references", "tools.md"), renderToolsMarkdown()]
44240
+ [join9("references", "tools.md"), renderToolsMarkdown()]
44564
44241
  ]);
44565
44242
  }
44566
44243
  function skillDirectories(harnesses, projectPath, exists) {
44567
- const directories = [join11(projectPath, SHARED_SKILLS, SKILL_NAME)];
44244
+ const directories = [join9(projectPath, SHARED_SKILLS, SKILL_NAME)];
44568
44245
  for (const harness of harnesses) {
44569
44246
  if (harness.skills === undefined) {
44570
44247
  continue;
44571
44248
  }
44572
- const own = join11(projectPath, harness.skills.dir);
44573
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join11(own, SKILL_NAME))) {
44574
- 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));
44575
44252
  }
44576
44253
  }
44577
44254
  return directories;
44578
44255
  }
44579
44256
  function everySkillDirectory(projectPath, harnesses) {
44580
- const directories = new Set([join11(projectPath, SHARED_SKILLS, SKILL_NAME)]);
44257
+ const directories = new Set([join9(projectPath, SHARED_SKILLS, SKILL_NAME)]);
44581
44258
  for (const harness of harnesses) {
44582
44259
  if (harness.skills !== undefined) {
44583
- directories.add(join11(projectPath, harness.skills.dir, SKILL_NAME));
44260
+ directories.add(join9(projectPath, harness.skills.dir, SKILL_NAME));
44584
44261
  }
44585
44262
  }
44586
44263
  return [...directories];
@@ -44588,52 +44265,52 @@ function everySkillDirectory(projectPath, harnesses) {
44588
44265
  function removeSkill(directories, projectPath) {
44589
44266
  const removed = [];
44590
44267
  for (const directory of directories) {
44591
- if (existsSync9(directory)) {
44592
- rmSync6(directory, { recursive: true, force: true });
44593
- pruneEmpty(dirname6(directory), projectPath);
44268
+ if (existsSync8(directory)) {
44269
+ rmSync5(directory, { recursive: true, force: true });
44270
+ pruneEmpty(dirname5(directory), projectPath);
44594
44271
  removed.push(directory);
44595
44272
  }
44596
44273
  }
44597
44274
  return removed;
44598
44275
  }
44599
44276
  function pruneEmpty(directory, projectPath) {
44600
- const shared = join11(projectPath, ".agents");
44277
+ const shared = join9(projectPath, ".agents");
44601
44278
  let path = directory;
44602
44279
  while (path !== projectPath && (basename2(path) === "skills" || path === shared)) {
44603
- if (readdirSync5(path).length > 0) {
44280
+ if (readdirSync4(path).length > 0) {
44604
44281
  return;
44605
44282
  }
44606
- rmSync6(path, { recursive: true, force: true });
44607
- path = dirname6(path);
44283
+ rmSync5(path, { recursive: true, force: true });
44284
+ path = dirname5(path);
44608
44285
  }
44609
44286
  }
44610
44287
  function writeSkill(directories, version) {
44611
44288
  const files = skillFiles(version);
44612
44289
  return directories.map((directory) => {
44613
- const replaced = existsSync9(directory);
44614
- rmSync6(directory, { recursive: true, force: true });
44290
+ const replaced = existsSync8(directory);
44291
+ rmSync5(directory, { recursive: true, force: true });
44615
44292
  for (const [name, contents] of files) {
44616
- const path = join11(directory, name);
44617
- mkdirSync6(dirname6(path), { recursive: true });
44618
- writeFileSync8(path, contents, "utf8");
44293
+ const path = join9(directory, name);
44294
+ mkdirSync5(dirname5(path), { recursive: true });
44295
+ writeFileSync6(path, contents, "utf8");
44619
44296
  }
44620
44297
  return { path: directory, replaced };
44621
44298
  });
44622
44299
  }
44623
44300
 
44624
44301
  // src/version.ts
44625
- import { readFileSync as readFileSync10 } from "node:fs";
44626
- import { dirname as dirname7, join as join12 } from "node:path";
44627
- 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";
44628
44305
  function getLocalVersion() {
44629
- const currentDirectory = dirname7(fileURLToPath3(import.meta.url));
44306
+ const currentDirectory = dirname6(fileURLToPath2(import.meta.url));
44630
44307
  const packageCandidates = [
44631
- join12(currentDirectory, "..", "package.json"),
44632
- join12(currentDirectory, "..", "..", "package.json")
44308
+ join10(currentDirectory, "..", "package.json"),
44309
+ join10(currentDirectory, "..", "..", "package.json")
44633
44310
  ];
44634
44311
  for (const packagePath of packageCandidates) {
44635
44312
  try {
44636
- const value = JSON.parse(readFileSync10(packagePath, "utf-8"));
44313
+ const value = JSON.parse(readFileSync8(packagePath, "utf-8"));
44637
44314
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
44638
44315
  return value.version;
44639
44316
  }
@@ -44646,12 +44323,12 @@ function getLocalVersion() {
44646
44323
  var args = process.argv.slice(2);
44647
44324
  var command = args[0];
44648
44325
 
44649
- class UsageError extends Refusal2 {
44326
+ class UsageError extends Refusal {
44650
44327
  }
44651
44328
  function projectArgument(at) {
44652
44329
  const named = args.slice(at).find((value) => !value.startsWith("--"));
44653
44330
  const projectPath = resolve6(named ?? ".");
44654
- if (!existsSync17(join25(projectPath, "project.godot"))) {
44331
+ if (!existsSync15(join22(projectPath, "project.godot"))) {
44655
44332
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
44656
44333
  }
44657
44334
  return projectPath;
@@ -44659,18 +44336,18 @@ function projectArgument(at) {
44659
44336
  async function engine(projectPath) {
44660
44337
  const located = await new GodotLocator().find();
44661
44338
  if (located.ok) {
44662
- return { godotPath: located.path, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
44339
+ return { godotPath: located.path, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
44663
44340
  }
44664
44341
  const recorded = located.named === null && projectPath !== undefined ? recordedEnginePath(projectPath) : null;
44665
44342
  if (recorded === null) {
44666
44343
  const refusal = GodotLocator.refusal(located);
44667
44344
  throw new UsageError(`${refusal.message} ${refusal.advice.join(". ")}.`);
44668
44345
  }
44669
- return { godotPath: recorded, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
44346
+ return { godotPath: recorded, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
44670
44347
  }
44671
44348
  function said2(outcome, what) {
44672
44349
  if (!outcome.ok) {
44673
- throw new Refusal2(`${what}: ${outcome.message}`);
44350
+ throw new Refusal(`${what}: ${outcome.message}`);
44674
44351
  }
44675
44352
  for (const entry of outcome.messages) {
44676
44353
  console.error(` engine ${entry.severity}: ${entry.text}`);
@@ -44690,7 +44367,7 @@ async function registerRuntime(godot, projectPath) {
44690
44367
  return;
44691
44368
  }
44692
44369
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
44693
- if (!existsSync17(join25(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
44370
+ if (!existsSync15(join22(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
44694
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}`);
44695
44372
  }
44696
44373
  }
@@ -44822,7 +44499,7 @@ async function setup() {
44822
44499
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
44823
44500
  }
44824
44501
  if (!args.includes("--no-skill")) {
44825
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync17);
44502
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync15);
44826
44503
  for (const written of writeSkill(directories, getLocalVersion())) {
44827
44504
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
44828
44505
  }
@@ -44916,7 +44593,7 @@ async function upgrade() {
44916
44593
  moved.push(took.from);
44917
44594
  }
44918
44595
  }
44919
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync17), version)) {
44596
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync15), version)) {
44920
44597
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
44921
44598
  }
44922
44599
  console.log(`
@@ -44948,7 +44625,7 @@ function doctorReport(projectPath) {
44948
44625
  process.exitCode = 1;
44949
44626
  }
44950
44627
  }
44951
- async function runtime3() {
44628
+ async function runtime2() {
44952
44629
  const state = args[1];
44953
44630
  if (state !== "on" && state !== "off") {
44954
44631
  throw new UsageError("gdharness runtime takes on or off, then the project directory.");
@@ -45057,7 +44734,7 @@ async function main() {
45057
44734
  doctorReport(projectArgument(1));
45058
44735
  return;
45059
44736
  case "runtime":
45060
- await runtime3();
44737
+ await runtime2();
45061
44738
  return;
45062
44739
  case "classes":
45063
44740
  await classes();
@@ -45086,7 +44763,7 @@ async function main() {
45086
44763
  }
45087
44764
  }
45088
44765
  await main().catch((error) => {
45089
- if (error instanceof Refusal2) {
44766
+ if (error instanceof Refusal) {
45090
44767
  console.error("gdharness:", error.message);
45091
44768
  process.exit(error instanceof UsageError ? 2 : 1);
45092
44769
  }