gdharness 0.13.4 → 0.13.6
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 +23 -7
- package/build/godot/addons/gdharness_editor/property_values.gd +294 -0
- package/build/godot/addons/gdharness_editor/property_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +106 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +9 -26
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +11 -53
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +3 -168
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +25 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +4 -84
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +41 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +106 -0
- package/build/godot/operations/serialisation.gd +106 -0
- package/build/index.js +23 -7
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -28755,7 +28755,7 @@ var TOOL_SPECS = [
|
|
|
28755
28755
|
},
|
|
28756
28756
|
{
|
|
28757
28757
|
name: "editor_run",
|
|
28758
|
-
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for.",
|
|
28758
|
+
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
28759
28759
|
parameters: {
|
|
28760
28760
|
projectPath: PROJECT_PATH,
|
|
28761
28761
|
scene: {
|
|
@@ -28831,7 +28831,7 @@ var TOOL_SPECS = [
|
|
|
28831
28831
|
},
|
|
28832
28832
|
{
|
|
28833
28833
|
name: "editor_rescan",
|
|
28834
|
-
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
|
|
28834
|
+
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.",
|
|
28835
28835
|
parameters: {
|
|
28836
28836
|
projectPath: PROJECT_PATH,
|
|
28837
28837
|
timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
|
|
@@ -29123,7 +29123,7 @@ var TOOL_SPECS = [
|
|
|
29123
29123
|
says: {
|
|
29124
29124
|
type: "string",
|
|
29125
29125
|
ops: ["until"],
|
|
29126
|
-
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included."
|
|
29126
|
+
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property."
|
|
29127
29127
|
},
|
|
29128
29128
|
timeoutMs: {
|
|
29129
29129
|
type: "number",
|
|
@@ -30851,6 +30851,7 @@ class GodotServer {
|
|
|
30851
30851
|
through: "gdharness",
|
|
30852
30852
|
pid: started.process.pid ?? null,
|
|
30853
30853
|
arguments: cmdArgs,
|
|
30854
|
+
transcript: started.transcript,
|
|
30854
30855
|
refreshedClasses: refreshed.value,
|
|
30855
30856
|
endedPreviousRun: ended ?? undefined,
|
|
30856
30857
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs),
|
|
@@ -31252,7 +31253,7 @@ class GodotServer {
|
|
|
31252
31253
|
const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
|
|
31253
31254
|
const before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
31254
31255
|
const writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
31255
|
-
const blind =
|
|
31256
|
+
const blind = await this.classesTheEditorCannotSee(args);
|
|
31256
31257
|
const atRisk = before === null ? [] : blind.unseen.filter((one) => before.has(one.className));
|
|
31257
31258
|
const first = await this.handleViaBridge("rescan_filesystem", args);
|
|
31258
31259
|
if (first.isError) {
|
|
@@ -31265,7 +31266,7 @@ class GodotServer {
|
|
|
31265
31266
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
31266
31267
|
busy = Boolean(status["scanning"]) || Boolean(status["importing"]) || pending;
|
|
31267
31268
|
}
|
|
31268
|
-
if (!busy && atRisk.length > 0) {
|
|
31269
|
+
if (!busy && projectPath !== "" && (atRisk.length > 0 || blind.unchecked !== undefined)) {
|
|
31269
31270
|
const until = Date.now() + CACHE_WRITE_MS;
|
|
31270
31271
|
while (Date.now() < until && cacheWrittenAt(projectPath) === writtenBefore) {
|
|
31271
31272
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
@@ -31273,18 +31274,33 @@ class GodotServer {
|
|
|
31273
31274
|
}
|
|
31274
31275
|
const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
|
|
31275
31276
|
const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
|
|
31277
|
+
const restored = lost.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
|
|
31276
31278
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
31277
31279
|
const unseen = checked.unseen;
|
|
31280
|
+
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
31278
31281
|
return this.jsonTextResponse({
|
|
31279
|
-
ok: !busy && unseen.length === 0 &&
|
|
31282
|
+
ok: !busy && unseen.length === 0 && stillGone.length === 0 && checked.unchecked === undefined,
|
|
31280
31283
|
stillWorking: busy,
|
|
31281
31284
|
waitedMs: Date.now() - started,
|
|
31282
31285
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
31283
31286
|
cacheLost: lost.length > 0 ? lost : undefined,
|
|
31287
|
+
cacheRestored: restored.length > 0 ? restored : undefined,
|
|
31284
31288
|
classesUnchecked: checked.unchecked,
|
|
31285
|
-
note: busy ? "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet." :
|
|
31289
|
+
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
|
|
31286
31290
|
});
|
|
31287
31291
|
}
|
|
31292
|
+
async rebuildCacheAfterLoss(projectPath, lost) {
|
|
31293
|
+
if (projectPath === "") {
|
|
31294
|
+
return [];
|
|
31295
|
+
}
|
|
31296
|
+
const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
|
|
31297
|
+
if (!rebuilt.ok) {
|
|
31298
|
+
this.logDebug(`Could not rebuild the class cache after a scan: ${rebuilt.message}`);
|
|
31299
|
+
return [];
|
|
31300
|
+
}
|
|
31301
|
+
const now = cachedClasses(projectPath);
|
|
31302
|
+
return now === null ? [] : lost.filter((name) => now.has(name));
|
|
31303
|
+
}
|
|
31288
31304
|
async alsoSayWhatTheEditorCannotSee(answered, args) {
|
|
31289
31305
|
const first = answered.content[0];
|
|
31290
31306
|
if (answered.isError || first?.type !== "text" || typeof first.text !== "string") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
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",
|