gdharness 1.1.48 → 1.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/build/cli.js +267 -120
  2. package/build/index.js +182 -24
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -17843,10 +17843,14 @@ var init_tool_definitions = __esm(() => {
17843
17843
  },
17844
17844
  {
17845
17845
  name: "script_diagnostics",
17846
- description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
17846
+ description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
17847
17847
  parameters: {
17848
17848
  projectPath: PROJECT_PATH,
17849
- scriptPath: SCRIPT_PATH
17849
+ scriptPath: {
17850
+ type: ["string", "array"],
17851
+ items: { type: "string" },
17852
+ description: 'A script inside the project, a folder, or a list of either, such as "scripts/player.gd" or ["scripts/economy", "ui/shop.gd"]. A folder is its .gd files at any depth, skipping what the engine skips. One script is answered as it always was; several are answered together under scripts, each in that same shape, with clean, errors and warnings for all of them beside it, and a script that could not be read is named there with why while the rest are still asked about. At most 300 scripts a call.'
17853
+ }
17850
17854
  },
17851
17855
  requires: ["projectPath", "scriptPath"]
17852
17856
  },
@@ -18589,6 +18593,106 @@ var init_headless_operations = __esm(() => {
18589
18593
  });
18590
18594
  });
18591
18595
 
18596
+ // src/tool-reference.ts
18597
+ function namedType(declared) {
18598
+ if (typeof declared === "string") {
18599
+ return declared;
18600
+ }
18601
+ if (Array.isArray(declared) && declared.every((kind) => typeof kind === "string")) {
18602
+ return declared.join(" or ");
18603
+ }
18604
+ return "any";
18605
+ }
18606
+ function argumentShape(schema) {
18607
+ const named = namedType(schema["type"]);
18608
+ const items = schema["items"];
18609
+ const held = typeof items === "object" && items !== null && !Array.isArray(items) ? namedType(items["type"]) : "any";
18610
+ return named.split(" or ").map((kind) => kind === "array" && held !== "any" ? `array of ${held}s` : kind).join(" or ");
18611
+ }
18612
+ function headlessSection() {
18613
+ const everyOp = new Map;
18614
+ for (const table of [HEADLESS_OPERATIONS, ENGINE_PASSES]) {
18615
+ for (const [tool, ops] of Object.entries(table)) {
18616
+ everyOp.set(tool, [...everyOp.get(tool) ?? [], ...Object.keys(ops)]);
18617
+ }
18618
+ }
18619
+ const byTool = [...everyOp].map(([tool, ops]) => `\`${tool}\` (${ops.join(", ")})`).sort();
18620
+ return [
18621
+ "## What a gate can call",
18622
+ "",
18623
+ "These answer from a short-lived headless engine: no editor window, no running game, and no",
18624
+ "process or port outliving the answer. They are the only calls an automatic check can make at",
18625
+ "all, because every other tool needs an editor somebody has open or a game somebody is running.",
18626
+ "",
18627
+ ...byTool.map((line) => `- ${line}`),
18628
+ "",
18629
+ "Needing nothing is not the same as changing nothing, and a gate wants both. Most of these",
18630
+ "write to the project because that is what they are for: `script_edit create` writes a file,",
18631
+ "`project_settings set` rewrites `project.godot`, `project_import reimport` and `refresh_classes`",
18632
+ "write under `.godot`, and `project_import refresh_uids` writes a `.uid` beside each script that",
18633
+ "had none. The reading ops are the ones to gate on, and which is which is in each",
18634
+ "op's own summary below rather than assumed from this list.",
18635
+ "",
18636
+ "Everything else needs the editor (`editor_*`, `debug_*`, `scene_*`, `resource_*`) or a running",
18637
+ "game (`runtime_*`).",
18638
+ "",
18639
+ 'One of these can be pointed the other way: `project_settings get` takes `from: "editor"` to ask',
18640
+ "the open editor instead of the file, which is a different reading and costs no engine start. A",
18641
+ "gate leaves it alone, because there is no editor on a build machine and the call is refused",
18642
+ "rather than answered from the file.",
18643
+ ""
18644
+ ];
18645
+ }
18646
+ function renderToolsMarkdown() {
18647
+ const lines = [
18648
+ "# Tools",
18649
+ "",
18650
+ `${TOOL_SPECS.length} tools, named \`domain_verb\`. A tool that does several related things takes an`,
18651
+ "`op`. An unknown op or argument is refused with the valid set listed. Generated from the server.",
18652
+ "",
18653
+ `${projectPathSentence()}: the \`runtime_*\` and \`debug_*\` ones pick between running games`,
18654
+ "instead, and the others answer about the editor on this server. The pick, when a call names",
18655
+ `neither \`projectPath\` nor \`pid\`: ${theGamePicked()}. An argument a tool does not`,
18656
+ "declare is refused rather than ignored. Answers are read from the engine after the change, not",
18657
+ "echoed from the request. Engine stderr comes back under `engine_messages`.",
18658
+ "",
18659
+ ...headlessSection()
18660
+ ];
18661
+ for (const tool of TOOL_SPECS) {
18662
+ lines.push(`## ${tool.name}`, "", tool.description, "");
18663
+ const operations = Object.entries(tool.operations ?? {});
18664
+ if (operations.length > 0) {
18665
+ for (const [op, spec] of operations) {
18666
+ const needs = [...tool.requires, ...spec.requires];
18667
+ const isDefault = tool.defaultOperation === op ? " (default)" : "";
18668
+ const wants = needs.length > 0 ? ` Needs: ${needs.map((name) => `\`${name}\``).join(", ")}.` : "";
18669
+ const spare = argumentsOf(tool, op).filter((name) => !needs.includes(name) && name !== "projectPath");
18670
+ const takes = spare.length > 0 ? ` Takes: ${spare.map((name) => `\`${name}\``).join(", ")}.` : "";
18671
+ lines.push(`- \`op: ${op}\`${isDefault}: ${spec.summary}.${wants}${takes}`);
18672
+ }
18673
+ lines.push("");
18674
+ } else if (tool.requires.length > 0) {
18675
+ lines.push(`Needs: ${tool.requires.map((name) => `\`${name}\``).join(", ")}.`, "");
18676
+ }
18677
+ const parameters = Object.entries(tool.parameters).filter(([name]) => name !== "projectPath");
18678
+ if (parameters.length > 0) {
18679
+ lines.push("Arguments:", "");
18680
+ for (const [name, schema] of parameters) {
18681
+ const type = argumentShape(schema);
18682
+ const note = typeof schema["description"] === "string" ? `: ${schema["description"]}` : "";
18683
+ lines.push(`- \`${name}\` (${type})${note}`);
18684
+ }
18685
+ lines.push("");
18686
+ }
18687
+ }
18688
+ return lines.join(`
18689
+ `);
18690
+ }
18691
+ var init_tool_reference = __esm(() => {
18692
+ init_headless_operations();
18693
+ init_tool_definitions();
18694
+ });
18695
+
18592
18696
  // node_modules/zod/v3/helpers/util.js
18593
18697
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
18594
18698
  const t = typeof data;
@@ -39388,7 +39492,7 @@ class GodotLSPClient {
39388
39492
  const diagnosticsPromise = new Promise((resolveDiagnostics, rejectDiagnostics) => {
39389
39493
  const timer = setTimeout(() => {
39390
39494
  this.diagnosticsWaiters.delete(key);
39391
- rejectDiagnostics(new Error(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
39495
+ rejectDiagnostics(new NoDiagnosticsPublished(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
39392
39496
  }, DIAGNOSTICS_TIMEOUT_MS);
39393
39497
  this.diagnosticsWaiters.set(key, {
39394
39498
  resolve: resolveDiagnostics,
@@ -39618,11 +39722,12 @@ async function handleLSPTool(client, toolName, args) {
39618
39722
  return asToolResponse({
39619
39723
  error: normalizeLSPError(error, client.port),
39620
39724
  ...error instanceof AnotherProjectsServer ? { serves: error.serves } : {},
39621
- ...error instanceof ArgumentRefusal ? { refusedArguments: true } : {}
39725
+ ...error instanceof ArgumentRefusal ? { refusedArguments: true } : {},
39726
+ ...error instanceof NoDiagnosticsPublished ? { aboutThisFile: true } : {}
39622
39727
  });
39623
39728
  }
39624
39729
  }
39625
- var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, ArgumentRefusal, AnotherProjectsServer;
39730
+ var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, ArgumentRefusal, NoDiagnosticsPublished, AnotherProjectsServer;
39626
39731
  var init_lsp_client = __esm(() => {
39627
39732
  init_answer_json();
39628
39733
  init_errors();
@@ -39640,6 +39745,8 @@ var init_lsp_client = __esm(() => {
39640
39745
  };
39641
39746
  ArgumentRefusal = class ArgumentRefusal extends Refusal {
39642
39747
  };
39748
+ NoDiagnosticsPublished = class NoDiagnosticsPublished extends Error {
39749
+ };
39643
39750
  AnotherProjectsServer = class AnotherProjectsServer extends Refusal {
39644
39751
  serves;
39645
39752
  constructor(port, serves, asked) {
@@ -42468,6 +42575,82 @@ function testPathsIn(projectPath, given) {
42468
42575
  value: unique.filter((path) => !unique.some((other) => other !== path && within(path, other))).map((path) => `res://${path}`)
42469
42576
  };
42470
42577
  }
42578
+ function scriptsToDiagnose(projectPath, given) {
42579
+ const listed = Array.isArray(given);
42580
+ if (!listed) {
42581
+ const one = typeof given === "string" ? resolveWithinProject(projectPath, given) : null;
42582
+ if (one === null || !one.ok || !isDirectory(one.absolutePath)) {
42583
+ return { ok: true, value: null };
42584
+ }
42585
+ }
42586
+ const entries = listed ? given : [given];
42587
+ if (entries.length === 0) {
42588
+ return {
42589
+ ok: false,
42590
+ reason: "scriptPath is an empty list: name a script, a folder, or a list of either."
42591
+ };
42592
+ }
42593
+ const found = [];
42594
+ const empty = [];
42595
+ for (const [index, entry] of entries.entries()) {
42596
+ if (typeof entry !== "string" || entry.trim() === "") {
42597
+ return {
42598
+ ok: false,
42599
+ reason: `scriptPath${listed ? `[${index}]` : ""} is ${typeof entry === "string" ? "empty" : describeValue(entry)}, not a script or a folder in the project.`
42600
+ };
42601
+ }
42602
+ const location = resolveWithinProject(projectPath, entry);
42603
+ if (!location.ok) {
42604
+ return { ok: false, reason: location.reason };
42605
+ }
42606
+ const relative = location.relativePath.replace(/\/+$/, "");
42607
+ if (!isDirectory(location.absolutePath)) {
42608
+ found.push(relative);
42609
+ continue;
42610
+ }
42611
+ const inside = scriptsUnder(location.absolutePath, relative);
42612
+ if (inside.length === 0) {
42613
+ empty.push(entry);
42614
+ }
42615
+ found.push(...inside);
42616
+ }
42617
+ const unique = [...new Set(found)];
42618
+ if (unique.length === 0) {
42619
+ return { ok: false, reason: `No .gd script is under ${empty.join(" or ")}.` };
42620
+ }
42621
+ if (unique.length > MOST_DIAGNOSED) {
42622
+ return {
42623
+ ok: false,
42624
+ reason: `scriptPath names ${unique.length} scripts, and one call asks about at most ${MOST_DIAGNOSED}, each a round trip through the language server: name narrower folders, or call again for the rest.`
42625
+ };
42626
+ }
42627
+ return { ok: true, value: unique };
42628
+ }
42629
+ function isDirectory(path) {
42630
+ try {
42631
+ return statSync9(path).isDirectory();
42632
+ } catch {
42633
+ return false;
42634
+ }
42635
+ }
42636
+ function scriptsUnder(directory, prefix) {
42637
+ if (existsSync20(join26(directory, ".gdignore"))) {
42638
+ return [];
42639
+ }
42640
+ const found = [];
42641
+ for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
42642
+ if (entry.name.startsWith(".")) {
42643
+ continue;
42644
+ }
42645
+ const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
42646
+ if (entry.isDirectory()) {
42647
+ found.push(...scriptsUnder(join26(directory, entry.name), path));
42648
+ } else if (entry.isFile() && entry.name.endsWith(".gd")) {
42649
+ found.push(path);
42650
+ }
42651
+ }
42652
+ return found;
42653
+ }
42471
42654
  function hasMainScene(projectFile) {
42472
42655
  const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
42473
42656
  return typeof scene === "string" && scene !== "";
@@ -42945,7 +43128,14 @@ class GodotServer {
42945
43128
  const asked = readString(args, "op");
42946
43129
  const named = asked !== undefined && spec.operations?.[asked] !== undefined ? asked : null;
42947
43130
  const wanted = named === null ? [...known] : [...argumentsOf(spec, named), "op"];
42948
- const takes = wanted.length === 0 ? "It takes no arguments." : `It takes: ${wanted.join(", ")}.`;
43131
+ const shaped = wanted.map((name) => {
43132
+ const schema = spec.parameters[name];
43133
+ if (schema !== undefined) {
43134
+ return `${name} (${argumentShape(schema)})`;
43135
+ }
43136
+ return name === "op" && named === null && spec.operations !== undefined ? `op (${Object.keys(spec.operations).join(", ")})` : name;
43137
+ });
43138
+ const takes = wanted.length === 0 ? "It takes no arguments." : `It takes: ${shaped.join(", ")}.`;
42949
43139
  const where = named === null ? spec.name : `${spec.name} ${named}`;
42950
43140
  return {
42951
43141
  ok: false,
@@ -44289,39 +44479,88 @@ class GodotServer {
44289
44479
  }
44290
44480
  }
44291
44481
  async handleScriptDiagnostics(args) {
44482
+ const project = this.project(args);
44483
+ const scripts = project.ok ? scriptsToDiagnose(project.value.path, args["scriptPath"]) : null;
44484
+ if (scripts !== null && !scripts.ok) {
44485
+ return this.createErrorResponse(scripts.reason);
44486
+ }
44487
+ if (scripts?.value == null) {
44488
+ const one = await this.diagnosticsOf(args);
44489
+ return "refusal" in one ? one.refusal : this.jsonTextResponse(this.markedIfStale(one.answer));
44490
+ }
44491
+ const progress = callProgress();
44492
+ const answers = [];
44493
+ let errors = 0;
44494
+ let warnings = 0;
44495
+ let unread = 0;
44496
+ for (const [at, scriptPath] of scripts.value.entries()) {
44497
+ progress?.(at + 1, `${at + 1} of ${scripts.value.length}: ${scriptPath}`);
44498
+ const one = await this.diagnosticsOf({ ...args, scriptPath });
44499
+ if ("refusal" in one) {
44500
+ if (!one.perScript) {
44501
+ return one.refusal;
44502
+ }
44503
+ unread += 1;
44504
+ answers.push({ scriptPath, error: one.refusal.content[0]?.text ?? "not read" });
44505
+ continue;
44506
+ }
44507
+ errors += Number(one.answer["errors"]);
44508
+ warnings += Number(one.answer["warnings"]);
44509
+ answers.push(one.answer);
44510
+ }
44511
+ return this.jsonTextResponse(this.markedIfStale({
44512
+ clean: errors === 0 && unread === 0,
44513
+ scriptsChecked: answers.length,
44514
+ errors,
44515
+ warnings,
44516
+ ...unread > 0 ? { scriptsNotRead: unread } : {},
44517
+ scripts: answers
44518
+ }));
44519
+ }
44520
+ markedIfStale(answer) {
44521
+ return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
44522
+ }
44523
+ async diagnosticsOf(args) {
44292
44524
  const answer = await this.handleLSP("lsp_get_diagnostics", args);
44293
44525
  const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
44294
44526
  if (payload["error"] !== undefined) {
44295
44527
  const reason = typeof payload["error"] === "string" ? payload["error"] : JSON.stringify(payload["error"]);
44296
44528
  if (payload["refusedArguments"] === true) {
44297
- return this.createErrorResponse(reason);
44529
+ return { refusal: this.createErrorResponse(reason), perScript: true };
44298
44530
  }
44299
44531
  if (typeof payload["serves"] === "string") {
44300
- return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
44301
- "editor_status names the editor on the bridge and the port it says it serves",
44302
- "editor_launch opens this project's own editor, on a language server port of its own",
44303
- "GDHARNESS_LSP_PORT points this server at the right one"
44304
- ]);
44532
+ return {
44533
+ refusal: this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
44534
+ "editor_status names the editor on the bridge and the port it says it serves",
44535
+ "editor_launch opens this project's own editor, on a language server port of its own",
44536
+ "GDHARNESS_LSP_PORT points this server at the right one"
44537
+ ]),
44538
+ perScript: false
44539
+ };
44305
44540
  }
44306
- return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
44307
- `Ensure the Godot editor is running with its language server enabled, on port ${this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT)}`,
44308
- "GDHARNESS_LSP_PORT points this server at another one"
44309
- ]);
44541
+ return {
44542
+ refusal: this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
44543
+ `Ensure the Godot editor is running with its language server enabled, on port ${this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT)}`,
44544
+ "GDHARNESS_LSP_PORT points this server at another one"
44545
+ ]),
44546
+ perScript: payload["aboutThisFile"] === true
44547
+ };
44310
44548
  }
44311
44549
  const diagnostics = readArray(payload, "diagnostics") ?? [];
44312
44550
  const errors = diagnostics.filter((entry) => {
44313
44551
  const severity = asParams(entry)["severity"];
44314
44552
  return severity === 1 || severity === "error" || severity === "ERROR";
44315
44553
  }).length;
44316
- const disagrees = this.whereTheFileDisagrees(diagnostics, args);
44317
- return this.jsonTextResponse(markIfStale({
44318
- scriptPath: readString(args, "scriptPath"),
44319
- clean: errors === 0,
44320
- errors,
44321
- warnings: diagnostics.length - errors,
44322
- diagnostics,
44323
- ...disagrees
44324
- }, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
44554
+ return {
44555
+ answer: {
44556
+ scriptPath: readString(args, "scriptPath"),
44557
+ clean: errors === 0,
44558
+ errors,
44559
+ warnings: diagnostics.length - errors,
44560
+ diagnostics,
44561
+ ...this.whereTheFileDisagrees(diagnostics, args)
44562
+ }
44563
+ };
44325
44564
  }
44326
44565
  whereTheFileDisagrees(diagnostics, args) {
44327
44566
  const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
@@ -47131,7 +47370,7 @@ async function runGodotServer() {
47131
47370
  const server = new GodotServer;
47132
47371
  await server.run();
47133
47372
  }
47134
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, 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/";
47373
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, 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/", MOST_DIAGNOSED = 300;
47135
47374
  var init_server3 = __esm(() => {
47136
47375
  init_mcp();
47137
47376
  init_stdio2();
@@ -47177,6 +47416,7 @@ var init_server3 = __esm(() => {
47177
47416
  init_server_version();
47178
47417
  init_setup();
47179
47418
  init_tool_definitions();
47419
+ init_tool_reference();
47180
47420
  init_update_check();
47181
47421
  run4 = promisify5(execFile5);
47182
47422
  __dirname2 = dirname19(fileURLToPath5(import.meta.url));
@@ -48170,102 +48410,9 @@ init_setup();
48170
48410
  // src/skill.ts
48171
48411
  init_setup();
48172
48412
  init_tool_definitions();
48413
+ init_tool_reference();
48173
48414
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
48174
48415
  import { basename as basename4, dirname as dirname6, join as join11 } from "node:path";
48175
-
48176
- // src/tool-reference.ts
48177
- init_headless_operations();
48178
- init_tool_definitions();
48179
- function namedType(declared) {
48180
- if (typeof declared === "string") {
48181
- return declared;
48182
- }
48183
- if (Array.isArray(declared) && declared.every((kind) => typeof kind === "string")) {
48184
- return declared.join(" or ");
48185
- }
48186
- return "any";
48187
- }
48188
- function headlessSection() {
48189
- const everyOp = new Map;
48190
- for (const table of [HEADLESS_OPERATIONS, ENGINE_PASSES]) {
48191
- for (const [tool, ops] of Object.entries(table)) {
48192
- everyOp.set(tool, [...everyOp.get(tool) ?? [], ...Object.keys(ops)]);
48193
- }
48194
- }
48195
- const byTool = [...everyOp].map(([tool, ops]) => `\`${tool}\` (${ops.join(", ")})`).sort();
48196
- return [
48197
- "## What a gate can call",
48198
- "",
48199
- "These answer from a short-lived headless engine: no editor window, no running game, and no",
48200
- "process or port outliving the answer. They are the only calls an automatic check can make at",
48201
- "all, because every other tool needs an editor somebody has open or a game somebody is running.",
48202
- "",
48203
- ...byTool.map((line) => `- ${line}`),
48204
- "",
48205
- "Needing nothing is not the same as changing nothing, and a gate wants both. Most of these",
48206
- "write to the project because that is what they are for: `script_edit create` writes a file,",
48207
- "`project_settings set` rewrites `project.godot`, `project_import reimport` and `refresh_classes`",
48208
- "write under `.godot`, and `project_import refresh_uids` writes a `.uid` beside each script that",
48209
- "had none. The reading ops are the ones to gate on, and which is which is in each",
48210
- "op's own summary below rather than assumed from this list.",
48211
- "",
48212
- "Everything else needs the editor (`editor_*`, `debug_*`, `scene_*`, `resource_*`) or a running",
48213
- "game (`runtime_*`).",
48214
- "",
48215
- 'One of these can be pointed the other way: `project_settings get` takes `from: "editor"` to ask',
48216
- "the open editor instead of the file, which is a different reading and costs no engine start. A",
48217
- "gate leaves it alone, because there is no editor on a build machine and the call is refused",
48218
- "rather than answered from the file.",
48219
- ""
48220
- ];
48221
- }
48222
- function renderToolsMarkdown() {
48223
- const lines = [
48224
- "# Tools",
48225
- "",
48226
- `${TOOL_SPECS.length} tools, named \`domain_verb\`. A tool that does several related things takes an`,
48227
- "`op`. An unknown op or argument is refused with the valid set listed. Generated from the server.",
48228
- "",
48229
- `${projectPathSentence()}: the \`runtime_*\` and \`debug_*\` ones pick between running games`,
48230
- "instead, and the others answer about the editor on this server. The pick, when a call names",
48231
- `neither \`projectPath\` nor \`pid\`: ${theGamePicked()}. An argument a tool does not`,
48232
- "declare is refused rather than ignored. Answers are read from the engine after the change, not",
48233
- "echoed from the request. Engine stderr comes back under `engine_messages`.",
48234
- "",
48235
- ...headlessSection()
48236
- ];
48237
- for (const tool of TOOL_SPECS) {
48238
- lines.push(`## ${tool.name}`, "", tool.description, "");
48239
- const operations = Object.entries(tool.operations ?? {});
48240
- if (operations.length > 0) {
48241
- for (const [op, spec] of operations) {
48242
- const needs = [...tool.requires, ...spec.requires];
48243
- const isDefault = tool.defaultOperation === op ? " (default)" : "";
48244
- const wants = needs.length > 0 ? ` Needs: ${needs.map((name) => `\`${name}\``).join(", ")}.` : "";
48245
- const spare = argumentsOf(tool, op).filter((name) => !needs.includes(name) && name !== "projectPath");
48246
- const takes = spare.length > 0 ? ` Takes: ${spare.map((name) => `\`${name}\``).join(", ")}.` : "";
48247
- lines.push(`- \`op: ${op}\`${isDefault}: ${spec.summary}.${wants}${takes}`);
48248
- }
48249
- lines.push("");
48250
- } else if (tool.requires.length > 0) {
48251
- lines.push(`Needs: ${tool.requires.map((name) => `\`${name}\``).join(", ")}.`, "");
48252
- }
48253
- const parameters = Object.entries(tool.parameters).filter(([name]) => name !== "projectPath");
48254
- if (parameters.length > 0) {
48255
- lines.push("Arguments:", "");
48256
- for (const [name, schema] of parameters) {
48257
- const type = namedType(schema["type"]);
48258
- const note = typeof schema["description"] === "string" ? `: ${schema["description"]}` : "";
48259
- lines.push(`- \`${name}\` (${type})${note}`);
48260
- }
48261
- lines.push("");
48262
- }
48263
- }
48264
- return lines.join(`
48265
- `);
48266
- }
48267
-
48268
- // src/skill.ts
48269
48416
  var SKILL_NAME = "gdharness";
48270
48417
  var SHARED_SKILLS = join11(".agents", "skills");
48271
48418
  function skillMarkdown(version) {
package/build/index.js CHANGED
@@ -29729,6 +29729,9 @@ class LSPTimeout extends Error {
29729
29729
  class ArgumentRefusal extends Refusal {
29730
29730
  }
29731
29731
 
29732
+ class NoDiagnosticsPublished extends Error {
29733
+ }
29734
+
29732
29735
  class AnotherProjectsServer extends Refusal {
29733
29736
  serves;
29734
29737
  constructor(port, serves, asked) {
@@ -30116,7 +30119,7 @@ class GodotLSPClient {
30116
30119
  const diagnosticsPromise = new Promise((resolveDiagnostics, rejectDiagnostics) => {
30117
30120
  const timer = setTimeout(() => {
30118
30121
  this.diagnosticsWaiters.delete(key);
30119
- rejectDiagnostics(new Error(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
30122
+ rejectDiagnostics(new NoDiagnosticsPublished(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
30120
30123
  }, DIAGNOSTICS_TIMEOUT_MS);
30121
30124
  this.diagnosticsWaiters.set(key, {
30122
30125
  resolve: resolveDiagnostics,
@@ -30346,7 +30349,8 @@ async function handleLSPTool(client, toolName, args) {
30346
30349
  return asToolResponse({
30347
30350
  error: normalizeLSPError(error, client.port),
30348
30351
  ...error instanceof AnotherProjectsServer ? { serves: error.serves } : {},
30349
- ...error instanceof ArgumentRefusal ? { refusedArguments: true } : {}
30352
+ ...error instanceof ArgumentRefusal ? { refusedArguments: true } : {},
30353
+ ...error instanceof NoDiagnosticsPublished ? { aboutThisFile: true } : {}
30350
30354
  });
30351
30355
  }
30352
30356
  }
@@ -33520,10 +33524,14 @@ var TOOL_SPECS = [
33520
33524
  },
33521
33525
  {
33522
33526
  name: "script_diagnostics",
33523
- description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
33527
+ description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, a method, property, constant, static function or enum member, comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
33524
33528
  parameters: {
33525
33529
  projectPath: PROJECT_PATH,
33526
- scriptPath: SCRIPT_PATH
33530
+ scriptPath: {
33531
+ type: ["string", "array"],
33532
+ items: { type: "string" },
33533
+ description: 'A script inside the project, a folder, or a list of either, such as "scripts/player.gd" or ["scripts/economy", "ui/shop.gd"]. A folder is its .gd files at any depth, skipping what the engine skips. One script is answered as it always was; several are answered together under scripts, each in that same shape, with clean, errors and warnings for all of them beside it, and a script that could not be read is named there with why while the rest are still asked about. At most 300 scripts a call.'
33534
+ }
33527
33535
  },
33528
33536
  requires: ["projectPath", "scriptPath"]
33529
33537
  },
@@ -34268,6 +34276,23 @@ function buildToolDefinitions() {
34268
34276
  });
34269
34277
  }
34270
34278
 
34279
+ // src/tool-reference.ts
34280
+ function namedType(declared) {
34281
+ if (typeof declared === "string") {
34282
+ return declared;
34283
+ }
34284
+ if (Array.isArray(declared) && declared.every((kind) => typeof kind === "string")) {
34285
+ return declared.join(" or ");
34286
+ }
34287
+ return "any";
34288
+ }
34289
+ function argumentShape(schema) {
34290
+ const named = namedType(schema["type"]);
34291
+ const items = schema["items"];
34292
+ const held = typeof items === "object" && items !== null && !Array.isArray(items) ? namedType(items["type"]) : "any";
34293
+ return named.split(" or ").map((kind) => kind === "array" && held !== "any" ? `array of ${held}s` : kind).join(" or ");
34294
+ }
34295
+
34271
34296
  // src/server.ts
34272
34297
  var UPDATE_NOTICE_EVERY = 500;
34273
34298
  var FEEDBACK_NOTICE_EVERY = 250;
@@ -35037,6 +35062,83 @@ function testPathsIn(projectPath, given) {
35037
35062
  value: unique.filter((path) => !unique.some((other) => other !== path && within(path, other))).map((path) => `res://${path}`)
35038
35063
  };
35039
35064
  }
35065
+ var MOST_DIAGNOSED = 300;
35066
+ function scriptsToDiagnose(projectPath, given) {
35067
+ const listed = Array.isArray(given);
35068
+ if (!listed) {
35069
+ const one = typeof given === "string" ? resolveWithinProject(projectPath, given) : null;
35070
+ if (one === null || !one.ok || !isDirectory(one.absolutePath)) {
35071
+ return { ok: true, value: null };
35072
+ }
35073
+ }
35074
+ const entries = listed ? given : [given];
35075
+ if (entries.length === 0) {
35076
+ return {
35077
+ ok: false,
35078
+ reason: "scriptPath is an empty list: name a script, a folder, or a list of either."
35079
+ };
35080
+ }
35081
+ const found = [];
35082
+ const empty = [];
35083
+ for (const [index, entry] of entries.entries()) {
35084
+ if (typeof entry !== "string" || entry.trim() === "") {
35085
+ return {
35086
+ ok: false,
35087
+ reason: `scriptPath${listed ? `[${index}]` : ""} is ${typeof entry === "string" ? "empty" : describeValue(entry)}, not a script or a folder in the project.`
35088
+ };
35089
+ }
35090
+ const location = resolveWithinProject(projectPath, entry);
35091
+ if (!location.ok) {
35092
+ return { ok: false, reason: location.reason };
35093
+ }
35094
+ const relative = location.relativePath.replace(/\/+$/, "");
35095
+ if (!isDirectory(location.absolutePath)) {
35096
+ found.push(relative);
35097
+ continue;
35098
+ }
35099
+ const inside = scriptsUnder(location.absolutePath, relative);
35100
+ if (inside.length === 0) {
35101
+ empty.push(entry);
35102
+ }
35103
+ found.push(...inside);
35104
+ }
35105
+ const unique = [...new Set(found)];
35106
+ if (unique.length === 0) {
35107
+ return { ok: false, reason: `No .gd script is under ${empty.join(" or ")}.` };
35108
+ }
35109
+ if (unique.length > MOST_DIAGNOSED) {
35110
+ return {
35111
+ ok: false,
35112
+ reason: `scriptPath names ${unique.length} scripts, and one call asks about at most ${MOST_DIAGNOSED}, each a round trip through the language server: name narrower folders, or call again for the rest.`
35113
+ };
35114
+ }
35115
+ return { ok: true, value: unique };
35116
+ }
35117
+ function isDirectory(path) {
35118
+ try {
35119
+ return statSync8(path).isDirectory();
35120
+ } catch {
35121
+ return false;
35122
+ }
35123
+ }
35124
+ function scriptsUnder(directory, prefix) {
35125
+ if (existsSync17(join22(directory, ".gdignore"))) {
35126
+ return [];
35127
+ }
35128
+ const found = [];
35129
+ for (const entry of readdirSync9(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
35130
+ if (entry.name.startsWith(".")) {
35131
+ continue;
35132
+ }
35133
+ const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
35134
+ if (entry.isDirectory()) {
35135
+ found.push(...scriptsUnder(join22(directory, entry.name), path));
35136
+ } else if (entry.isFile() && entry.name.endsWith(".gd")) {
35137
+ found.push(path);
35138
+ }
35139
+ }
35140
+ return found;
35141
+ }
35040
35142
  function hasMainScene(projectFile) {
35041
35143
  const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
35042
35144
  return typeof scene === "string" && scene !== "";
@@ -35514,7 +35616,14 @@ class GodotServer {
35514
35616
  const asked = readString(args, "op");
35515
35617
  const named = asked !== undefined && spec.operations?.[asked] !== undefined ? asked : null;
35516
35618
  const wanted = named === null ? [...known] : [...argumentsOf(spec, named), "op"];
35517
- const takes = wanted.length === 0 ? "It takes no arguments." : `It takes: ${wanted.join(", ")}.`;
35619
+ const shaped = wanted.map((name) => {
35620
+ const schema = spec.parameters[name];
35621
+ if (schema !== undefined) {
35622
+ return `${name} (${argumentShape(schema)})`;
35623
+ }
35624
+ return name === "op" && named === null && spec.operations !== undefined ? `op (${Object.keys(spec.operations).join(", ")})` : name;
35625
+ });
35626
+ const takes = wanted.length === 0 ? "It takes no arguments." : `It takes: ${shaped.join(", ")}.`;
35518
35627
  const where = named === null ? spec.name : `${spec.name} ${named}`;
35519
35628
  return {
35520
35629
  ok: false,
@@ -36858,39 +36967,88 @@ class GodotServer {
36858
36967
  }
36859
36968
  }
36860
36969
  async handleScriptDiagnostics(args) {
36970
+ const project = this.project(args);
36971
+ const scripts = project.ok ? scriptsToDiagnose(project.value.path, args["scriptPath"]) : null;
36972
+ if (scripts !== null && !scripts.ok) {
36973
+ return this.createErrorResponse(scripts.reason);
36974
+ }
36975
+ if (scripts?.value == null) {
36976
+ const one = await this.diagnosticsOf(args);
36977
+ return "refusal" in one ? one.refusal : this.jsonTextResponse(this.markedIfStale(one.answer));
36978
+ }
36979
+ const progress = callProgress();
36980
+ const answers = [];
36981
+ let errors = 0;
36982
+ let warnings = 0;
36983
+ let unread = 0;
36984
+ for (const [at, scriptPath] of scripts.value.entries()) {
36985
+ progress?.(at + 1, `${at + 1} of ${scripts.value.length}: ${scriptPath}`);
36986
+ const one = await this.diagnosticsOf({ ...args, scriptPath });
36987
+ if ("refusal" in one) {
36988
+ if (!one.perScript) {
36989
+ return one.refusal;
36990
+ }
36991
+ unread += 1;
36992
+ answers.push({ scriptPath, error: one.refusal.content[0]?.text ?? "not read" });
36993
+ continue;
36994
+ }
36995
+ errors += Number(one.answer["errors"]);
36996
+ warnings += Number(one.answer["warnings"]);
36997
+ answers.push(one.answer);
36998
+ }
36999
+ return this.jsonTextResponse(this.markedIfStale({
37000
+ clean: errors === 0 && unread === 0,
37001
+ scriptsChecked: answers.length,
37002
+ errors,
37003
+ warnings,
37004
+ ...unread > 0 ? { scriptsNotRead: unread } : {},
37005
+ scripts: answers
37006
+ }));
37007
+ }
37008
+ markedIfStale(answer) {
37009
+ return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
37010
+ }
37011
+ async diagnosticsOf(args) {
36861
37012
  const answer = await this.handleLSP("lsp_get_diagnostics", args);
36862
37013
  const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
36863
37014
  if (payload["error"] !== undefined) {
36864
37015
  const reason = typeof payload["error"] === "string" ? payload["error"] : JSON.stringify(payload["error"]);
36865
37016
  if (payload["refusedArguments"] === true) {
36866
- return this.createErrorResponse(reason);
37017
+ return { refusal: this.createErrorResponse(reason), perScript: true };
36867
37018
  }
36868
37019
  if (typeof payload["serves"] === "string") {
36869
- return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
36870
- "editor_status names the editor on the bridge and the port it says it serves",
36871
- "editor_launch opens this project's own editor, on a language server port of its own",
36872
- "GDHARNESS_LSP_PORT points this server at the right one"
36873
- ]);
37020
+ return {
37021
+ refusal: this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
37022
+ "editor_status names the editor on the bridge and the port it says it serves",
37023
+ "editor_launch opens this project's own editor, on a language server port of its own",
37024
+ "GDHARNESS_LSP_PORT points this server at the right one"
37025
+ ]),
37026
+ perScript: false
37027
+ };
36874
37028
  }
36875
- return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
36876
- `Ensure the Godot editor is running with its language server enabled, on port ${this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT)}`,
36877
- "GDHARNESS_LSP_PORT points this server at another one"
36878
- ]);
37029
+ return {
37030
+ refusal: this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
37031
+ `Ensure the Godot editor is running with its language server enabled, on port ${this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT)}`,
37032
+ "GDHARNESS_LSP_PORT points this server at another one"
37033
+ ]),
37034
+ perScript: payload["aboutThisFile"] === true
37035
+ };
36879
37036
  }
36880
37037
  const diagnostics = readArray(payload, "diagnostics") ?? [];
36881
37038
  const errors = diagnostics.filter((entry) => {
36882
37039
  const severity = asParams(entry)["severity"];
36883
37040
  return severity === 1 || severity === "error" || severity === "ERROR";
36884
37041
  }).length;
36885
- const disagrees = this.whereTheFileDisagrees(diagnostics, args);
36886
- return this.jsonTextResponse(markIfStale({
36887
- scriptPath: readString(args, "scriptPath"),
36888
- clean: errors === 0,
36889
- errors,
36890
- warnings: diagnostics.length - errors,
36891
- diagnostics,
36892
- ...disagrees
36893
- }, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
37042
+ return {
37043
+ answer: {
37044
+ scriptPath: readString(args, "scriptPath"),
37045
+ clean: errors === 0,
37046
+ errors,
37047
+ warnings: diagnostics.length - errors,
37048
+ diagnostics,
37049
+ ...this.whereTheFileDisagrees(diagnostics, args)
37050
+ }
37051
+ };
36894
37052
  }
36895
37053
  whereTheFileDisagrees(diagnostics, args) {
36896
37054
  const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.1.48",
3
+ "version": "1.1.50",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",