gdharness 0.13.14 → 0.13.16
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 +49 -20
- package/build/index.js +49 -20
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7573,6 +7573,24 @@ function declaresMember(source, missing) {
|
|
|
7573
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
7574
|
return declaration.test(source);
|
|
7575
7575
|
}
|
|
7576
|
+
function unknownTypeIn(message) {
|
|
7577
|
+
const found = /(?:Could not find (?:base class|type)|Cannot find class|Identifier) "?([A-Za-z_][A-Za-z0-9_]*)"?(?: not declared| in the current scope|\b)/.exec(message);
|
|
7578
|
+
return found?.[1] ?? null;
|
|
7579
|
+
}
|
|
7580
|
+
function unloadedTypes(messages, declared, cached) {
|
|
7581
|
+
const seen = new Set;
|
|
7582
|
+
const found = [];
|
|
7583
|
+
for (const message of messages) {
|
|
7584
|
+
const type = unknownTypeIn(message);
|
|
7585
|
+
const declaredIn = type === null ? undefined : declared.get(type);
|
|
7586
|
+
if (type === null || declaredIn === undefined || seen.has(type)) {
|
|
7587
|
+
continue;
|
|
7588
|
+
}
|
|
7589
|
+
seen.add(type);
|
|
7590
|
+
found.push({ type, declaredIn, inTheClassCache: cached?.has(type) === true });
|
|
7591
|
+
}
|
|
7592
|
+
return found;
|
|
7593
|
+
}
|
|
7576
7594
|
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
7577
7595
|
const read = new Map;
|
|
7578
7596
|
const found = [];
|
|
@@ -15784,7 +15802,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15784
15802
|
},
|
|
15785
15803
|
{
|
|
15786
15804
|
name: "script_diagnostics",
|
|
15787
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running.
|
|
15805
|
+
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 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 which remedy applies, because they differ: a stale analysed type needs editor_launch restart, while a class missing from the cache needs project_import refresh_classes.",
|
|
15788
15806
|
parameters: {
|
|
15789
15807
|
projectPath: PROJECT_PATH,
|
|
15790
15808
|
scriptPath: SCRIPT_PATH
|
|
@@ -38050,34 +38068,27 @@ class GodotServer {
|
|
|
38050
38068
|
const severity = asParams(entry)["severity"];
|
|
38051
38069
|
return severity === 1 || severity === "error" || severity === "ERROR";
|
|
38052
38070
|
}).length;
|
|
38053
|
-
const
|
|
38071
|
+
const disagrees = this.whereTheFileDisagrees(diagnostics, args);
|
|
38054
38072
|
return this.jsonTextResponse(markIfStale({
|
|
38055
38073
|
scriptPath: readString(args, "scriptPath"),
|
|
38056
38074
|
clean: errors === 0,
|
|
38057
38075
|
errors,
|
|
38058
38076
|
warnings: diagnostics.length - errors,
|
|
38059
38077
|
diagnostics,
|
|
38060
|
-
...
|
|
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
|
-
}
|
|
38078
|
+
...disagrees
|
|
38064
38079
|
}, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
|
|
38065
38080
|
}
|
|
38066
|
-
|
|
38081
|
+
whereTheFileDisagrees(diagnostics, args) {
|
|
38067
38082
|
const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
|
|
38068
|
-
|
|
38069
|
-
|
|
38070
|
-
|
|
38071
|
-
|
|
38072
|
-
if (!project.ok) {
|
|
38073
|
-
return [];
|
|
38074
|
-
}
|
|
38075
|
-
const classes = cachedClasses(project.value.path);
|
|
38076
|
-
if (classes === null) {
|
|
38077
|
-
return [];
|
|
38083
|
+
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null);
|
|
38084
|
+
const project = worthChecking ? this.project(args) : null;
|
|
38085
|
+
if (project === null || !project.ok) {
|
|
38086
|
+
return {};
|
|
38078
38087
|
}
|
|
38079
|
-
|
|
38080
|
-
|
|
38088
|
+
const projectPath = project.value.path;
|
|
38089
|
+
const cached = cachedClasses(projectPath);
|
|
38090
|
+
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
38091
|
+
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
38081
38092
|
if (!contained.ok || !existsSync14(contained.absolutePath)) {
|
|
38082
38093
|
return null;
|
|
38083
38094
|
}
|
|
@@ -38087,6 +38098,24 @@ class GodotServer {
|
|
|
38087
38098
|
return null;
|
|
38088
38099
|
}
|
|
38089
38100
|
});
|
|
38101
|
+
const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
|
|
38102
|
+
const notes = [];
|
|
38103
|
+
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. 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.`);
|
|
38105
|
+
}
|
|
38106
|
+
const uncached = unloaded.filter((type) => !type.inTheClassCache);
|
|
38107
|
+
if (uncached.length > 0) {
|
|
38108
|
+
notes.push(`${uncached.map((type) => type.type).join(", ")} ${uncached.length === 1 ? "is declared" : "are declared"} in this project and missing from .godot/global_script_class_cache.cfg, so a game launched now would not resolve ${uncached.length === 1 ? "it" : "them"} either. project_import refresh_classes rewrites the cache from the declarations on disk.`);
|
|
38109
|
+
}
|
|
38110
|
+
const stale = unloaded.filter((type) => type.inTheClassCache);
|
|
38111
|
+
if (stale.length > 0) {
|
|
38112
|
+
notes.push(`${stale.map((type) => type.type).join(", ")} ${stale.length === 1 ? "is" : "are"} in the class cache and still unresolved here, which is the editor's loaded list being behind rather than anything on disk. A game launched now reads the cache and resolves ${stale.length === 1 ? "it" : "them"}, so the run and the diagnostics disagree. editor_rescan clears it, measured against a real editor with waiting the same length ruled out; editor_launch restart also does, and costs more.`);
|
|
38113
|
+
}
|
|
38114
|
+
return {
|
|
38115
|
+
...contradicted.length === 0 ? {} : { contradictedByTheFile: contradicted },
|
|
38116
|
+
...unloaded.length === 0 ? {} : { typesTheEditorHasNotLoaded: unloaded },
|
|
38117
|
+
...notes.length === 0 ? {} : { staleAnalysis: notes.join(" ") }
|
|
38118
|
+
};
|
|
38090
38119
|
}
|
|
38091
38120
|
async handleLSP(toolName, args) {
|
|
38092
38121
|
const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
|
|
@@ -38964,7 +38993,7 @@ class GodotServer {
|
|
|
38964
38993
|
...asParams(JSON.parse(first.text)),
|
|
38965
38994
|
unseenByEditor: checked.unseen.length > 0 ? checked.unseen : undefined,
|
|
38966
38995
|
classesUnchecked: checked.unchecked,
|
|
38967
|
-
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.
|
|
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
|
|
38968
38997
|
});
|
|
38969
38998
|
}
|
|
38970
38999
|
async classesTheEditorCannotSee(args) {
|
package/build/index.js
CHANGED
|
@@ -24833,6 +24833,24 @@ function declaresMember(source, missing) {
|
|
|
24833
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
24834
|
return declaration.test(source);
|
|
24835
24835
|
}
|
|
24836
|
+
function unknownTypeIn(message) {
|
|
24837
|
+
const found = /(?:Could not find (?:base class|type)|Cannot find class|Identifier) "?([A-Za-z_][A-Za-z0-9_]*)"?(?: not declared| in the current scope|\b)/.exec(message);
|
|
24838
|
+
return found?.[1] ?? null;
|
|
24839
|
+
}
|
|
24840
|
+
function unloadedTypes(messages, declared, cached) {
|
|
24841
|
+
const seen = new Set;
|
|
24842
|
+
const found = [];
|
|
24843
|
+
for (const message of messages) {
|
|
24844
|
+
const type = unknownTypeIn(message);
|
|
24845
|
+
const declaredIn = type === null ? undefined : declared.get(type);
|
|
24846
|
+
if (type === null || declaredIn === undefined || seen.has(type)) {
|
|
24847
|
+
continue;
|
|
24848
|
+
}
|
|
24849
|
+
seen.add(type);
|
|
24850
|
+
found.push({ type, declaredIn, inTheClassCache: cached?.has(type) === true });
|
|
24851
|
+
}
|
|
24852
|
+
return found;
|
|
24853
|
+
}
|
|
24836
24854
|
function contradictedDiagnostics(messages, classes, sourceOf) {
|
|
24837
24855
|
const read = new Map;
|
|
24838
24856
|
const found = [];
|
|
@@ -28901,7 +28919,7 @@ var TOOL_SPECS = [
|
|
|
28901
28919
|
},
|
|
28902
28920
|
{
|
|
28903
28921
|
name: "script_diagnostics",
|
|
28904
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running.
|
|
28922
|
+
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 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 which remedy applies, because they differ: a stale analysed type needs editor_launch restart, while a class missing from the cache needs project_import refresh_classes.",
|
|
28905
28923
|
parameters: {
|
|
28906
28924
|
projectPath: PROJECT_PATH,
|
|
28907
28925
|
scriptPath: SCRIPT_PATH
|
|
@@ -30802,34 +30820,27 @@ class GodotServer {
|
|
|
30802
30820
|
const severity = asParams(entry)["severity"];
|
|
30803
30821
|
return severity === 1 || severity === "error" || severity === "ERROR";
|
|
30804
30822
|
}).length;
|
|
30805
|
-
const
|
|
30823
|
+
const disagrees = this.whereTheFileDisagrees(diagnostics, args);
|
|
30806
30824
|
return this.jsonTextResponse(markIfStale({
|
|
30807
30825
|
scriptPath: readString(args, "scriptPath"),
|
|
30808
30826
|
clean: errors === 0,
|
|
30809
30827
|
errors,
|
|
30810
30828
|
warnings: diagnostics.length - errors,
|
|
30811
30829
|
diagnostics,
|
|
30812
|
-
...
|
|
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
|
-
}
|
|
30830
|
+
...disagrees
|
|
30816
30831
|
}, this.godotBridge.getStatus().addonVersion, SERVER_VERSION));
|
|
30817
30832
|
}
|
|
30818
|
-
|
|
30833
|
+
whereTheFileDisagrees(diagnostics, args) {
|
|
30819
30834
|
const messages = diagnostics.map((entry) => asParams(entry)["message"]).filter((message) => typeof message === "string");
|
|
30820
|
-
|
|
30821
|
-
|
|
30822
|
-
|
|
30823
|
-
|
|
30824
|
-
if (!project.ok) {
|
|
30825
|
-
return [];
|
|
30826
|
-
}
|
|
30827
|
-
const classes = cachedClasses(project.value.path);
|
|
30828
|
-
if (classes === null) {
|
|
30829
|
-
return [];
|
|
30835
|
+
const worthChecking = messages.some((message) => missingMemberIn(message) !== null || unknownTypeIn(message) !== null);
|
|
30836
|
+
const project = worthChecking ? this.project(args) : null;
|
|
30837
|
+
if (project === null || !project.ok) {
|
|
30838
|
+
return {};
|
|
30830
30839
|
}
|
|
30831
|
-
|
|
30832
|
-
|
|
30840
|
+
const projectPath = project.value.path;
|
|
30841
|
+
const cached = cachedClasses(projectPath);
|
|
30842
|
+
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
30843
|
+
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
30833
30844
|
if (!contained.ok || !existsSync9(contained.absolutePath)) {
|
|
30834
30845
|
return null;
|
|
30835
30846
|
}
|
|
@@ -30839,6 +30850,24 @@ class GodotServer {
|
|
|
30839
30850
|
return null;
|
|
30840
30851
|
}
|
|
30841
30852
|
});
|
|
30853
|
+
const unloaded = messages.some((message) => unknownTypeIn(message) !== null) ? unloadedTypes(messages, declaredClasses(projectPath), cached) : [];
|
|
30854
|
+
const notes = [];
|
|
30855
|
+
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. 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.`);
|
|
30857
|
+
}
|
|
30858
|
+
const uncached = unloaded.filter((type) => !type.inTheClassCache);
|
|
30859
|
+
if (uncached.length > 0) {
|
|
30860
|
+
notes.push(`${uncached.map((type) => type.type).join(", ")} ${uncached.length === 1 ? "is declared" : "are declared"} in this project and missing from .godot/global_script_class_cache.cfg, so a game launched now would not resolve ${uncached.length === 1 ? "it" : "them"} either. project_import refresh_classes rewrites the cache from the declarations on disk.`);
|
|
30861
|
+
}
|
|
30862
|
+
const stale = unloaded.filter((type) => type.inTheClassCache);
|
|
30863
|
+
if (stale.length > 0) {
|
|
30864
|
+
notes.push(`${stale.map((type) => type.type).join(", ")} ${stale.length === 1 ? "is" : "are"} in the class cache and still unresolved here, which is the editor's loaded list being behind rather than anything on disk. A game launched now reads the cache and resolves ${stale.length === 1 ? "it" : "them"}, so the run and the diagnostics disagree. editor_rescan clears it, measured against a real editor with waiting the same length ruled out; editor_launch restart also does, and costs more.`);
|
|
30865
|
+
}
|
|
30866
|
+
return {
|
|
30867
|
+
...contradicted.length === 0 ? {} : { contradictedByTheFile: contradicted },
|
|
30868
|
+
...unloaded.length === 0 ? {} : { typesTheEditorHasNotLoaded: unloaded },
|
|
30869
|
+
...notes.length === 0 ? {} : { staleAnalysis: notes.join(" ") }
|
|
30870
|
+
};
|
|
30842
30871
|
}
|
|
30843
30872
|
async handleLSP(toolName, args) {
|
|
30844
30873
|
const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
|
|
@@ -31716,7 +31745,7 @@ class GodotServer {
|
|
|
31716
31745
|
...asParams(JSON.parse(first.text)),
|
|
31717
31746
|
unseenByEditor: checked.unseen.length > 0 ? checked.unseen : undefined,
|
|
31718
31747
|
classesUnchecked: checked.unchecked,
|
|
31719
|
-
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.
|
|
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
|
|
31720
31749
|
});
|
|
31721
31750
|
}
|
|
31722
31751
|
async classesTheEditorCannotSee(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.16",
|
|
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",
|