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 +69 -3
- package/build/index.js +67 -2
- package/package.json +1 -1
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
|
|
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.
|
|
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",
|