gdharness 0.13.13 → 0.13.14

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
@@ -7557,6 +7557,41 @@ function cachedClasses(projectPath) {
7557
7557
  }
7558
7558
  return listed;
7559
7559
  }
7560
+ function missingMemberIn(message) {
7561
+ const found = /The (method|property) "([^"(]+)(?:\(\))?" is not present on the inferred type "([^"]+)"/.exec(message);
7562
+ if (found === null) {
7563
+ return null;
7564
+ }
7565
+ return {
7566
+ kind: found[1] === "property" ? "property" : "method",
7567
+ member: found[2] ?? "",
7568
+ type: found[3] ?? ""
7569
+ };
7570
+ }
7571
+ function declaresMember(source, missing) {
7572
+ const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7573
+ const declaration = missing.kind === "method" ? new RegExp(String.raw`^\s*(?:static\s+)?func\s+${name}\s*\(`, "m") : new RegExp(String.raw`^\s*(?:static\s+)?(?:@export\s+)?(?:var|const)\s+${name}\b`, "m");
7574
+ return declaration.test(source);
7575
+ }
7576
+ function contradictedDiagnostics(messages, classes, sourceOf) {
7577
+ const read = new Map;
7578
+ const found = [];
7579
+ for (const message of messages) {
7580
+ const missing = missingMemberIn(message);
7581
+ const declaredIn = missing === null ? undefined : classes.get(missing.type);
7582
+ if (missing === null || declaredIn === undefined) {
7583
+ continue;
7584
+ }
7585
+ if (!read.has(declaredIn)) {
7586
+ read.set(declaredIn, sourceOf(declaredIn));
7587
+ }
7588
+ const source = read.get(declaredIn) ?? null;
7589
+ if (source !== null && declaresMember(source, missing)) {
7590
+ found.push({ ...missing, declaredIn });
7591
+ }
7592
+ }
7593
+ return found;
7594
+ }
7560
7595
  function cacheWrittenAt(projectPath) {
7561
7596
  const cache = join6(projectPath, ".godot", "global_script_class_cache.cfg");
7562
7597
  try {
@@ -15749,7 +15784,7 @@ var init_tool_definitions = __esm(() => {
15749
15784
  },
15750
15785
  {
15751
15786
  name: "script_diagnostics",
15752
- description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running.",
15787
+ description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Any diagnostic saying a member is not present on an inferred type is checked against the file the class cache points at, and one the file contradicts is named under contradictedByTheFile with staleAnalysis saying what to do: the language server hands dependents the type it analysed at startup, so a method added to an existing class_name is reported missing at every caller until the editor restarts.",
15753
15788
  parameters: {
15754
15789
  projectPath: PROJECT_PATH,
15755
15790
  scriptPath: SCRIPT_PATH
@@ -38015,14 +38050,44 @@ class GodotServer {
38015
38050
  const severity = asParams(entry)["severity"];
38016
38051
  return severity === 1 || severity === "error" || severity === "ERROR";
38017
38052
  }).length;
38053
+ const contradicted = this.contradictedByTheFile(diagnostics, args);
38018
38054
  return this.jsonTextResponse(markIfStale({
38019
38055
  scriptPath: readString(args, "scriptPath"),
38020
38056
  clean: errors === 0,
38021
38057
  errors,
38022
38058
  warnings: diagnostics.length - errors,
38023
- diagnostics
38059
+ diagnostics,
38060
+ ...contradicted.length === 0 ? {} : {
38061
+ contradictedByTheFile: contradicted,
38062
+ staleAnalysis: `The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named above. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. editor_launch restart clears it; editor_rescan and project_import refresh_classes do not, because neither reaches the analysed types the language server built at startup.`
38063
+ }
38024
38064
  }, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
38025
38065
  }
38066
+ contradictedByTheFile(diagnostics, args) {
38067
+ const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
38068
+ if (!messages.some((message) => missingMemberIn(message) !== null)) {
38069
+ return [];
38070
+ }
38071
+ const project = this.project(args);
38072
+ if (!project.ok) {
38073
+ return [];
38074
+ }
38075
+ const classes = cachedClasses(project.value.path);
38076
+ if (classes === null) {
38077
+ return [];
38078
+ }
38079
+ return contradictedDiagnostics(messages, classes, (resourcePath) => {
38080
+ const contained = resolveWithinProject(project.value.path, resourcePath);
38081
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
38082
+ return null;
38083
+ }
38084
+ try {
38085
+ return readFileSync15(contained.absolutePath, "utf8");
38086
+ } catch {
38087
+ return null;
38088
+ }
38089
+ });
38090
+ }
38026
38091
  async handleLSP(toolName, args) {
38027
38092
  const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
38028
38093
  if (this.lspClient !== null && this.lspClient.port !== port) {
@@ -40459,7 +40524,8 @@ function headlessSection() {
40459
40524
  "Needing nothing is not the same as changing nothing, and a gate wants both. Most of these",
40460
40525
  "write to the project because that is what they are for: `script_edit create` writes a file,",
40461
40526
  "`project_settings set` rewrites `project.godot`, `project_import reimport` and `refresh_classes`",
40462
- "write under `.godot`. The reading ops are the ones to gate on, and which is which is in each",
40527
+ "write under `.godot`, and `project_import refresh_uids` writes a `.uid` beside each script that",
40528
+ "had none. The reading ops are the ones to gate on, and which is which is in each",
40463
40529
  "op's own summary below rather than assumed from this list.",
40464
40530
  "",
40465
40531
  "Everything else needs the editor (`editor_*`, `debug_*`, `scene_*`, `resource_*`) or a running",
package/build/index.js CHANGED
@@ -24817,6 +24817,41 @@ function cachedClasses(projectPath) {
24817
24817
  }
24818
24818
  return listed;
24819
24819
  }
24820
+ function missingMemberIn(message) {
24821
+ const found = /The (method|property) "([^"(]+)(?:\(\))?" is not present on the inferred type "([^"]+)"/.exec(message);
24822
+ if (found === null) {
24823
+ return null;
24824
+ }
24825
+ return {
24826
+ kind: found[1] === "property" ? "property" : "method",
24827
+ member: found[2] ?? "",
24828
+ type: found[3] ?? ""
24829
+ };
24830
+ }
24831
+ function declaresMember(source, missing) {
24832
+ const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24833
+ const declaration = missing.kind === "method" ? new RegExp(String.raw`^\s*(?:static\s+)?func\s+${name}\s*\(`, "m") : new RegExp(String.raw`^\s*(?:static\s+)?(?:@export\s+)?(?:var|const)\s+${name}\b`, "m");
24834
+ return declaration.test(source);
24835
+ }
24836
+ function contradictedDiagnostics(messages, classes, sourceOf) {
24837
+ const read = new Map;
24838
+ const found = [];
24839
+ for (const message of messages) {
24840
+ const missing = missingMemberIn(message);
24841
+ const declaredIn = missing === null ? undefined : classes.get(missing.type);
24842
+ if (missing === null || declaredIn === undefined) {
24843
+ continue;
24844
+ }
24845
+ if (!read.has(declaredIn)) {
24846
+ read.set(declaredIn, sourceOf(declaredIn));
24847
+ }
24848
+ const source = read.get(declaredIn) ?? null;
24849
+ if (source !== null && declaresMember(source, missing)) {
24850
+ found.push({ ...missing, declaredIn });
24851
+ }
24852
+ }
24853
+ return found;
24854
+ }
24820
24855
  function cacheWrittenAt(projectPath) {
24821
24856
  const cache = join3(projectPath, ".godot", "global_script_class_cache.cfg");
24822
24857
  try {
@@ -28866,7 +28901,7 @@ var TOOL_SPECS = [
28866
28901
  },
28867
28902
  {
28868
28903
  name: "script_diagnostics",
28869
- description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running.",
28904
+ description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Any diagnostic saying a member is not present on an inferred type is checked against the file the class cache points at, and one the file contradicts is named under contradictedByTheFile with staleAnalysis saying what to do: the language server hands dependents the type it analysed at startup, so a method added to an existing class_name is reported missing at every caller until the editor restarts.",
28870
28905
  parameters: {
28871
28906
  projectPath: PROJECT_PATH,
28872
28907
  scriptPath: SCRIPT_PATH
@@ -30767,14 +30802,44 @@ class GodotServer {
30767
30802
  const severity = asParams(entry)["severity"];
30768
30803
  return severity === 1 || severity === "error" || severity === "ERROR";
30769
30804
  }).length;
30805
+ const contradicted = this.contradictedByTheFile(diagnostics, args);
30770
30806
  return this.jsonTextResponse(markIfStale({
30771
30807
  scriptPath: readString(args, "scriptPath"),
30772
30808
  clean: errors === 0,
30773
30809
  errors,
30774
30810
  warnings: diagnostics.length - errors,
30775
- diagnostics
30811
+ diagnostics,
30812
+ ...contradicted.length === 0 ? {} : {
30813
+ contradictedByTheFile: contradicted,
30814
+ staleAnalysis: `The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named above. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. editor_launch restart clears it; editor_rescan and project_import refresh_classes do not, because neither reaches the analysed types the language server built at startup.`
30815
+ }
30776
30816
  }, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
30777
30817
  }
30818
+ contradictedByTheFile(diagnostics, args) {
30819
+ const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
30820
+ if (!messages.some((message) => missingMemberIn(message) !== null)) {
30821
+ return [];
30822
+ }
30823
+ const project = this.project(args);
30824
+ if (!project.ok) {
30825
+ return [];
30826
+ }
30827
+ const classes = cachedClasses(project.value.path);
30828
+ if (classes === null) {
30829
+ return [];
30830
+ }
30831
+ return contradictedDiagnostics(messages, classes, (resourcePath) => {
30832
+ const contained = resolveWithinProject(project.value.path, resourcePath);
30833
+ if (!contained.ok || !existsSync9(contained.absolutePath)) {
30834
+ return null;
30835
+ }
30836
+ try {
30837
+ return readFileSync11(contained.absolutePath, "utf8");
30838
+ } catch {
30839
+ return null;
30840
+ }
30841
+ });
30842
+ }
30778
30843
  async handleLSP(toolName, args) {
30779
30844
  const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
30780
30845
  if (this.lspClient !== null && this.lspClient.port !== port) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.13.13",
3
+ "version": "0.13.14",
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",