gdharness 0.13.22 → 0.13.24

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
@@ -15231,6 +15231,9 @@ function opTakes(spec, op, name) {
15231
15231
  function argumentsOf(spec, op) {
15232
15232
  return Object.keys(spec.parameters).filter((name) => opTakes(spec, op, name));
15233
15233
  }
15234
+ function toolsWithoutProjectPath() {
15235
+ return TOOL_SPECS.filter((spec) => !Object.hasOwn(spec.parameters, "projectPath")).map((spec) => spec.name).sort();
15236
+ }
15234
15237
  function toolSpec(name) {
15235
15238
  return SPECS_BY_NAME[name];
15236
15239
  }
@@ -15961,7 +15964,7 @@ var init_tool_definitions = __esm(() => {
15961
15964
  },
15962
15965
  {
15963
15966
  name: "editor_rescan",
15964
- description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure measured downstream is a change to the declaring script, saved, and then a rescan: the change on its own left every affected file reporting the same errors, and the rescan after it cleared them. Whether a rescan alone would have done it there is not known, because the rescans in that report may have answered before the scan ran, which is the timing this tool no longer has. editor_launch restart also does it, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. Needs the editor connected.",
15967
+ description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. Needs the editor connected.",
15965
15968
  parameters: {
15966
15969
  projectPath: PROJECT_PATH,
15967
15970
  timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." },
@@ -39000,7 +39003,8 @@ class GodotServer {
39000
39003
  try {
39001
39004
  const answer = asParams(await this.godotBridge.invokeTool("reload_script", { ...args, scriptPath: reloading }));
39002
39005
  const methods = readArray(answer, "methods");
39003
- reloaded = methods ? { methods: methods.map(String) } : { problem: `the editor would not say what ${reloading} has after reloading it` };
39006
+ const before = readArray(answer, "heldBefore");
39007
+ reloaded = methods ? { methods: methods.map(String), ...before ? { heldBefore: before.map(String) } : {} } : { problem: `the editor would not say what ${reloading} has after reloading it` };
39004
39008
  } catch (error) {
39005
39009
  reloaded = { problem: `${reloading} could not be reloaded: ${errorMessage(error)}` };
39006
39010
  }
@@ -39012,13 +39016,14 @@ class GodotServer {
39012
39016
  ok: !busy && unseen.length === 0 && stillGone.length === 0 && checked.unchecked === undefined,
39013
39017
  stillWorking: busy,
39014
39018
  waitedMs: Date.now() - started,
39019
+ heldBeforeReload: reloaded.heldBefore,
39015
39020
  reloadedMethods: reloaded.methods,
39016
39021
  reloadProblem: reloaded.problem,
39017
39022
  unseenByEditor: unseen.length > 0 ? unseen : undefined,
39018
39023
  cacheLost: lost.length > 0 ? lost : undefined,
39019
39024
  cacheRestored: restored.length > 0 ? restored : undefined,
39020
39025
  classesUnchecked: checked.unchecked,
39021
- note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : stillGone.length > 0 ? "The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again." : lost.length > 0 ? "The scan wrote the class cache from the list this editor is holding, which is shorter than the file was, so the cache was rebuilt from the files and these classes are back in it. Nothing was lost, but this editor is still holding the short list: do not rescan again until it has been restarted with editor_launch restart, because it will write the same list over the file each time." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Change the declaring script and rescan, or restart the editor with editor_launch restart." : undefined
39026
+ note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : stillGone.length > 0 ? "The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again." : lost.length > 0 ? "The scan wrote the class cache from the list this editor is holding, which is shorter than the file was, so the cache was rebuilt from the files and these classes are back in it. Nothing was lost, but this editor is still holding the short list: do not rescan again until it has been restarted with editor_launch restart, because it will write the same list over the file each time." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Rescan again on its own, which is the measured cure and needs no change to the declaring script, or restart the editor with editor_launch restart." : undefined
39022
39027
  });
39023
39028
  }
39024
39029
  async rebuildCacheAfterLoss(projectPath, lost) {
@@ -40637,9 +40642,10 @@ function renderToolsMarkdown() {
40637
40642
  `${TOOL_SPECS.length} tools, named \`domain_verb\`. A tool that does several related things takes an`,
40638
40643
  "`op`. An unknown op or argument is refused with the valid set listed. Generated from the server.",
40639
40644
  "",
40640
- "Every call takes `projectPath`, except the `runtime_*` and `debug_*` tools, where it picks",
40641
- "between running games. Answers are read from the engine after the change, not echoed from the",
40642
- "request. Engine stderr comes back under `engine_messages`.",
40645
+ `Every call takes \`projectPath\` except ${toolsWithoutProjectPath().map((name) => `\`${name}\``).join(", ")}, which take none: the \`runtime_*\` and \`debug_*\` ones pick between running games`,
40646
+ "instead, and the others answer about the editor on this server. An argument a tool does not",
40647
+ "declare is refused rather than ignored. Answers are read from the engine after the change, not",
40648
+ "echoed from the request. Engine stderr comes back under `engine_messages`.",
40643
40649
  "",
40644
40650
  ...headlessSection()
40645
40651
  ];
@@ -40695,8 +40701,9 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
40695
40701
 
40696
40702
  ## Start here
40697
40703
 
40698
- - Every call takes \`projectPath\`, except \`runtime_*\` and \`debug_*\`, where it picks between
40699
- running games. There is no ambient project.
40704
+ - Every call takes \`projectPath\` except ${toolsWithoutProjectPath().map((name) => `\\\`${name}\\\``).join(", ")}, which take none. The \`runtime_*\` and \`debug_*\` ones pick between running
40705
+ games instead; the others answer about the editor this server is connected to. There is no
40706
+ ambient project, and an argument a tool does not declare is refused rather than ignored.
40700
40707
  - \`editor_status\` says whether an editor is connected and whether its addon matches the server.
40701
40708
  \`addonIsStale\` means they differ, and \`staleNote\` says which half is behind: an editor that
40702
40709
  needs restarting, or a server that needs reconnecting in your harness. Every answer that came
@@ -60,6 +60,11 @@ func reload_script(args: Dictionary) -> Dictionary:
60
60
  if script == null:
61
61
  return {"ok": false, "error": path + " is not a GDScript"}
62
62
 
63
+ # What the copy had before anything touched it, which is the reading that shows the fault rather
64
+ # than the one that mends it. Without it a caller cannot tell a copy that was already current
65
+ # from one this call repaired, and those are different facts about their editor.
66
+ var held: Array[String] = _method_names(script)
67
+
63
68
  # The source is read off disk and put back before reloading, which is the whole of the fix and
64
69
  # is not obvious. `reload` recompiles from the object's own `source_code`, not from the file, so
65
70
  # a script the editor loaded before the change recompiles the text it was holding and comes back
@@ -75,11 +80,15 @@ func reload_script(args: Dictionary) -> Dictionary:
75
80
  if failed != OK:
76
81
  return {"ok": false, "error": "Reloading " + path + " answered error " + str(failed)}
77
82
 
78
- var methods: Array[String] = []
83
+ return {"ok": true, "script": path, "heldBefore": held, "methods": _method_names(script)}
84
+
85
+
86
+ func _method_names(script: GDScript) -> Array[String]:
87
+ var names: Array[String] = []
79
88
  for entry: Dictionary in script.get_script_method_list():
80
- methods.append(str(entry.get("name", "")))
81
- methods.sort()
82
- return {"ok": true, "script": path, "methods": methods}
89
+ names.append(str(entry.get("name", "")))
90
+ names.sort()
91
+ return names
83
92
 
84
93
 
85
94
  func global_classes(_args: Dictionary) -> Dictionary:
package/build/index.js CHANGED
@@ -29078,7 +29078,7 @@ var TOOL_SPECS = [
29078
29078
  },
29079
29079
  {
29080
29080
  name: "editor_rescan",
29081
- description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure measured downstream is a change to the declaring script, saved, and then a rescan: the change on its own left every affected file reporting the same errors, and the rescan after it cleared them. Whether a rescan alone would have done it there is not known, because the rescans in that report may have answered before the scan ran, which is the timing this tool no longer has. editor_launch restart also does it, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. Needs the editor connected.",
29081
+ description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. Needs the editor connected.",
29082
29082
  parameters: {
29083
29083
  projectPath: PROJECT_PATH,
29084
29084
  timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." },
@@ -31752,7 +31752,8 @@ class GodotServer {
31752
31752
  try {
31753
31753
  const answer = asParams(await this.godotBridge.invokeTool("reload_script", { ...args, scriptPath: reloading }));
31754
31754
  const methods = readArray(answer, "methods");
31755
- reloaded = methods ? { methods: methods.map(String) } : { problem: `the editor would not say what ${reloading} has after reloading it` };
31755
+ const before = readArray(answer, "heldBefore");
31756
+ reloaded = methods ? { methods: methods.map(String), ...before ? { heldBefore: before.map(String) } : {} } : { problem: `the editor would not say what ${reloading} has after reloading it` };
31756
31757
  } catch (error) {
31757
31758
  reloaded = { problem: `${reloading} could not be reloaded: ${errorMessage(error)}` };
31758
31759
  }
@@ -31764,13 +31765,14 @@ class GodotServer {
31764
31765
  ok: !busy && unseen.length === 0 && stillGone.length === 0 && checked.unchecked === undefined,
31765
31766
  stillWorking: busy,
31766
31767
  waitedMs: Date.now() - started,
31768
+ heldBeforeReload: reloaded.heldBefore,
31767
31769
  reloadedMethods: reloaded.methods,
31768
31770
  reloadProblem: reloaded.problem,
31769
31771
  unseenByEditor: unseen.length > 0 ? unseen : undefined,
31770
31772
  cacheLost: lost.length > 0 ? lost : undefined,
31771
31773
  cacheRestored: restored.length > 0 ? restored : undefined,
31772
31774
  classesUnchecked: checked.unchecked,
31773
- note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : stillGone.length > 0 ? "The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again." : lost.length > 0 ? "The scan wrote the class cache from the list this editor is holding, which is shorter than the file was, so the cache was rebuilt from the files and these classes are back in it. Nothing was lost, but this editor is still holding the short list: do not rescan again until it has been restarted with editor_launch restart, because it will write the same list over the file each time." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Change the declaring script and rescan, or restart the editor with editor_launch restart." : undefined
31775
+ note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." : stillGone.length > 0 ? "The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again." : lost.length > 0 ? "The scan wrote the class cache from the list this editor is holding, which is shorter than the file was, so the cache was rebuilt from the files and these classes are back in it. Nothing was lost, but this editor is still holding the short list: do not rescan again until it has been restarted with editor_launch restart, because it will write the same list over the file each time." : unseen.length > 0 ? "The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Rescan again on its own, which is the measured cure and needs no change to the declaring script, or restart the editor with editor_launch restart." : undefined
31774
31776
  });
31775
31777
  }
31776
31778
  async rebuildCacheAfterLoss(projectPath, lost) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.13.22",
3
+ "version": "0.13.24",
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",