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 +50 -4
- package/build/index.js +50 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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",
|