gdharness 0.13.19 → 0.13.21

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
@@ -7591,6 +7591,15 @@ function unloadedTypes(messages, declared, cached) {
7591
7591
  }
7592
7592
  return found;
7593
7593
  }
7594
+ function classesNamedIn(source, known) {
7595
+ const named = new Set;
7596
+ for (const [word] of source.matchAll(/\b[A-Z][A-Za-z0-9_]*\b/g)) {
7597
+ if (known.has(word)) {
7598
+ named.add(word);
7599
+ }
7600
+ }
7601
+ return [...named].sort();
7602
+ }
7594
7603
  function contradictedDiagnostics(messages, classes, sourceOf) {
7595
7604
  const read = new Map;
7596
7605
  const found = [];
@@ -7625,6 +7634,10 @@ function staleClassNames(projectPath) {
7625
7634
  const cached = cachedClasses(projectPath);
7626
7635
  return cached === null ? [...declaredClasses(projectPath).keys()] : staleAgainst(cached, projectPath);
7627
7636
  }
7637
+ function heldButGone(projectPath, editorHolds) {
7638
+ const declared = declaredClasses(projectPath);
7639
+ return editorHolds.filter((name) => !declared.has(name)).sort();
7640
+ }
7628
7641
  function unseenByEditor(projectPath, editorHolds) {
7629
7642
  const held = new Set(editorHolds);
7630
7643
  return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path }));
@@ -38101,7 +38114,19 @@ class GodotServer {
38101
38114
  const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
38102
38115
  const notes = [];
38103
38116
  if (contradicted.length > 0) {
38104
- notes.push(`The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named under contradictedByTheFile. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. Try editor_rescan first: it costs about half a second and cleared this in one of five measured attempts. If it does not, changing anything the named type itself depends on and rescanning again cleared it on the one occasion that has been tried. editor_launch restart is the only remedy that has always worked, and is the one to reach for when the count above is not good enough odds. project_import refresh_classes does not, and answers added: [] while this is happening.`);
38117
+ const dependsOn = new Set;
38118
+ for (const one of contradicted) {
38119
+ const source = cached === null ? null : this.sourceOfClass(projectPath, one.declaredIn);
38120
+ if (source === null || cached === null) {
38121
+ continue;
38122
+ }
38123
+ for (const name of classesNamedIn(source, cached)) {
38124
+ if (name !== one.type) {
38125
+ dependsOn.add(name);
38126
+ }
38127
+ }
38128
+ }
38129
+ notes.push(`The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named under contradictedByTheFile. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. Try editor_rescan first: it costs about half a second and cleared this in one of five measured attempts. If it does not, the lever is that a held type is refreshed when something it depends on changes rather than when it changes itself${dependsOn.size === 0 ? ", though the named types depend on no other global class, which leaves only the restart" : `, so change ${[...dependsOn].sort().join(", ")} and rescan again`}. editor_launch restart is the only remedy that has always worked, and is the one to reach for when the count above is not good enough odds. project_import refresh_classes does not, and answers added: [] while this is happening.`);
38105
38130
  }
38106
38131
  const uncached = unloaded.filter((type) => !type.inTheClassCache);
38107
38132
  if (uncached.length > 0) {
@@ -38117,6 +38142,17 @@ class GodotServer {
38117
38142
  ...notes.length === 0 ? {} : { staleAnalysis: notes.join(" ") }
38118
38143
  };
38119
38144
  }
38145
+ sourceOfClass(projectPath, resourcePath) {
38146
+ const contained = resolveWithinProject(projectPath, resourcePath);
38147
+ if (!contained.ok || !existsSync14(contained.absolutePath)) {
38148
+ return null;
38149
+ }
38150
+ try {
38151
+ return readFileSync15(contained.absolutePath, "utf8");
38152
+ } catch {
38153
+ return null;
38154
+ }
38155
+ }
38120
38156
  async handleLSP(toolName, args) {
38121
38157
  const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
38122
38158
  if (this.lspClient !== null && this.lspClient.port !== port) {
@@ -38986,14 +39022,23 @@ class GodotServer {
38986
39022
  return answered;
38987
39023
  }
38988
39024
  const checked = await this.classesTheEditorCannotSee(args);
38989
- if (checked.unseen.length === 0 && checked.unchecked === undefined) {
39025
+ const stillHeld = checked.stillHeld ?? [];
39026
+ if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined) {
38990
39027
  return answered;
38991
39028
  }
39029
+ const notes = [];
39030
+ if (checked.unseen.length > 0) {
39031
+ notes.push("The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan does, on its own and with no change to the declaring script; editor_launch restart also does, and costs more.");
39032
+ }
39033
+ if (stillHeld.length > 0) {
39034
+ notes.push(`The editor is still holding ${stillHeld.join(", ")}, which this project no longer declares, and it will write that list back over the cache this call just corrected. The entry returns pointing at a script that is gone, and the next engine to walk get_global_class_list() dies on "File not found". A rescan does not clear this one: it picks up a class that has appeared and does not drop one that has gone. editor_launch restart does.`);
39035
+ }
38992
39036
  return this.jsonTextResponse({
38993
39037
  ...asParams(JSON.parse(first.text)),
38994
39038
  unseenByEditor: checked.unseen.length > 0 ? checked.unseen : undefined,
39039
+ stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
38995
39040
  classesUnchecked: checked.unchecked,
38996
- note: checked.unseen.length > 0 ? "The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan does, on its own and with no change to the declaring script; editor_launch restart also does, and costs more." : undefined
39041
+ note: notes.length > 0 ? notes.join(" ") : undefined
38997
39042
  });
38998
39043
  }
38999
39044
  async classesTheEditorCannotSee(args) {
@@ -39020,7 +39065,8 @@ class GodotServer {
39020
39065
  if (!Array.isArray(held["classes"])) {
39021
39066
  return { unseen: [], unchecked: "the editor answered without a class list in it" };
39022
39067
  }
39023
- return { unseen: unseenByEditor(projectPath, held["classes"].map(String)) };
39068
+ const holds = held["classes"].map(String);
39069
+ return { unseen: unseenByEditor(projectPath, holds), stillHeld: heldButGone(projectPath, holds) };
39024
39070
  }
39025
39071
  async handleViaBridge(toolName, args) {
39026
39072
  if (!this.godotBridge.isConnected()) {
package/build/index.js CHANGED
@@ -24851,6 +24851,15 @@ function unloadedTypes(messages, declared, cached) {
24851
24851
  }
24852
24852
  return found;
24853
24853
  }
24854
+ function classesNamedIn(source, known) {
24855
+ const named = new Set;
24856
+ for (const [word] of source.matchAll(/\b[A-Z][A-Za-z0-9_]*\b/g)) {
24857
+ if (known.has(word)) {
24858
+ named.add(word);
24859
+ }
24860
+ }
24861
+ return [...named].sort();
24862
+ }
24854
24863
  function contradictedDiagnostics(messages, classes, sourceOf) {
24855
24864
  const read = new Map;
24856
24865
  const found = [];
@@ -24885,6 +24894,10 @@ function staleClassNames(projectPath) {
24885
24894
  const cached = cachedClasses(projectPath);
24886
24895
  return cached === null ? [...declaredClasses(projectPath).keys()] : staleAgainst(cached, projectPath);
24887
24896
  }
24897
+ function heldButGone(projectPath, editorHolds) {
24898
+ const declared = declaredClasses(projectPath);
24899
+ return editorHolds.filter((name) => !declared.has(name)).sort();
24900
+ }
24888
24901
  function unseenByEditor(projectPath, editorHolds) {
24889
24902
  const held = new Set(editorHolds);
24890
24903
  return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path }));
@@ -30853,7 +30866,19 @@ class GodotServer {
30853
30866
  const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
30854
30867
  const notes = [];
30855
30868
  if (contradicted.length > 0) {
30856
- notes.push(`The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named under contradictedByTheFile. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. Try editor_rescan first: it costs about half a second and cleared this in one of five measured attempts. If it does not, changing anything the named type itself depends on and rescanning again cleared it on the one occasion that has been tried. editor_launch restart is the only remedy that has always worked, and is the one to reach for when the count above is not good enough odds. project_import refresh_classes does not, and answers added: [] while this is happening.`);
30869
+ const dependsOn = new Set;
30870
+ for (const one of contradicted) {
30871
+ const source = cached === null ? null : this.sourceOfClass(projectPath, one.declaredIn);
30872
+ if (source === null || cached === null) {
30873
+ continue;
30874
+ }
30875
+ for (const name of classesNamedIn(source, cached)) {
30876
+ if (name !== one.type) {
30877
+ dependsOn.add(name);
30878
+ }
30879
+ }
30880
+ }
30881
+ notes.push(`The editor is reporting against an older copy of ${contradicted.length === 1 ? "a type" : "some types"} named under contradictedByTheFile. Each member listed is declared in the file the class cache points at, so those diagnostics are wrong however the code is written. Try editor_rescan first: it costs about half a second and cleared this in one of five measured attempts. If it does not, the lever is that a held type is refreshed when something it depends on changes rather than when it changes itself${dependsOn.size === 0 ? ", though the named types depend on no other global class, which leaves only the restart" : `, so change ${[...dependsOn].sort().join(", ")} and rescan again`}. editor_launch restart is the only remedy that has always worked, and is the one to reach for when the count above is not good enough odds. project_import refresh_classes does not, and answers added: [] while this is happening.`);
30857
30882
  }
30858
30883
  const uncached = unloaded.filter((type) => !type.inTheClassCache);
30859
30884
  if (uncached.length > 0) {
@@ -30869,6 +30894,17 @@ class GodotServer {
30869
30894
  ...notes.length === 0 ? {} : { staleAnalysis: notes.join(" ") }
30870
30895
  };
30871
30896
  }
30897
+ sourceOfClass(projectPath, resourcePath) {
30898
+ const contained = resolveWithinProject(projectPath, resourcePath);
30899
+ if (!contained.ok || !existsSync9(contained.absolutePath)) {
30900
+ return null;
30901
+ }
30902
+ try {
30903
+ return readFileSync11(contained.absolutePath, "utf8");
30904
+ } catch {
30905
+ return null;
30906
+ }
30907
+ }
30872
30908
  async handleLSP(toolName, args) {
30873
30909
  const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
30874
30910
  if (this.lspClient !== null && this.lspClient.port !== port) {
@@ -31738,14 +31774,23 @@ class GodotServer {
31738
31774
  return answered;
31739
31775
  }
31740
31776
  const checked = await this.classesTheEditorCannotSee(args);
31741
- if (checked.unseen.length === 0 && checked.unchecked === undefined) {
31777
+ const stillHeld = checked.stillHeld ?? [];
31778
+ if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined) {
31742
31779
  return answered;
31743
31780
  }
31781
+ const notes = [];
31782
+ if (checked.unseen.length > 0) {
31783
+ notes.push("The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan does, on its own and with no change to the declaring script; editor_launch restart also does, and costs more.");
31784
+ }
31785
+ if (stillHeld.length > 0) {
31786
+ notes.push(`The editor is still holding ${stillHeld.join(", ")}, which this project no longer declares, and it will write that list back over the cache this call just corrected. The entry returns pointing at a script that is gone, and the next engine to walk get_global_class_list() dies on "File not found". A rescan does not clear this one: it picks up a class that has appeared and does not drop one that has gone. editor_launch restart does.`);
31787
+ }
31744
31788
  return this.jsonTextResponse({
31745
31789
  ...asParams(JSON.parse(first.text)),
31746
31790
  unseenByEditor: checked.unseen.length > 0 ? checked.unseen : undefined,
31791
+ stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
31747
31792
  classesUnchecked: checked.unchecked,
31748
- note: checked.unseen.length > 0 ? "The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan does, on its own and with no change to the declaring script; editor_launch restart also does, and costs more." : undefined
31793
+ note: notes.length > 0 ? notes.join(" ") : undefined
31749
31794
  });
31750
31795
  }
31751
31796
  async classesTheEditorCannotSee(args) {
@@ -31772,7 +31817,8 @@ class GodotServer {
31772
31817
  if (!Array.isArray(held["classes"])) {
31773
31818
  return { unseen: [], unchecked: "the editor answered without a class list in it" };
31774
31819
  }
31775
- return { unseen: unseenByEditor(projectPath, held["classes"].map(String)) };
31820
+ const holds = held["classes"].map(String);
31821
+ return { unseen: unseenByEditor(projectPath, holds), stillHeld: heldButGone(projectPath, holds) };
31776
31822
  }
31777
31823
  async handleViaBridge(toolName, args) {
31778
31824
  if (!this.godotBridge.isConnected()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.13.19",
3
+ "version": "0.13.21",
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",