gdharness 0.12.7 → 0.12.10
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
|
@@ -7401,7 +7401,7 @@ class UpdateCheck {
|
|
|
7401
7401
|
}
|
|
7402
7402
|
}
|
|
7403
7403
|
refresh(now = Date.now()) {
|
|
7404
|
-
if (!this.enabled || this.checking || now < this.retryAt ||
|
|
7404
|
+
if (!this.enabled || this.checking || now < this.retryAt || !this.stale(now)) {
|
|
7405
7405
|
return;
|
|
7406
7406
|
}
|
|
7407
7407
|
this.checking = true;
|
|
@@ -7421,15 +7421,21 @@ class UpdateCheck {
|
|
|
7421
7421
|
this.checking = false;
|
|
7422
7422
|
});
|
|
7423
7423
|
}
|
|
7424
|
+
stale(now) {
|
|
7425
|
+
return now - this.checkedAt >= CACHE_MS;
|
|
7426
|
+
}
|
|
7424
7427
|
scheduleRetry(now) {
|
|
7425
7428
|
this.retryAt = now + this.backoffMs;
|
|
7426
7429
|
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
7427
7430
|
}
|
|
7428
|
-
notice() {
|
|
7431
|
+
notice(now = Date.now()) {
|
|
7429
7432
|
const latest = this.latest;
|
|
7430
7433
|
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
7431
7434
|
return null;
|
|
7432
7435
|
}
|
|
7436
|
+
if (this.checking && this.stale(now)) {
|
|
7437
|
+
return null;
|
|
7438
|
+
}
|
|
7433
7439
|
return {
|
|
7434
7440
|
current: this.current,
|
|
7435
7441
|
latest,
|
|
@@ -7524,9 +7530,7 @@ function staleClassNames(projectPath) {
|
|
|
7524
7530
|
}
|
|
7525
7531
|
function unseenByEditor(projectPath, editorHolds) {
|
|
7526
7532
|
const held = new Set(editorHolds);
|
|
7527
|
-
|
|
7528
|
-
const source = cachedClasses(projectPath) ?? declared;
|
|
7529
|
-
return [...source].filter(([name]) => !held.has(name) && declared.has(name)).map(([className, path]) => ({ className, path: declared.get(className) ?? path }));
|
|
7533
|
+
return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path }));
|
|
7530
7534
|
}
|
|
7531
7535
|
var init_class_cache = () => {};
|
|
7532
7536
|
|
|
@@ -15759,7 +15763,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15759
15763
|
},
|
|
15760
15764
|
{
|
|
15761
15765
|
name: "editor_rescan",
|
|
15762
|
-
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 a change to the declaring script, or editor_launch restart; project_import refresh_classes rewrites the cache and does not touch what the editor is holding. Needs the editor connected.",
|
|
15766
|
+
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 a change to the declaring script, or editor_launch restart; project_import refresh_classes rewrites the cache and does not touch what the editor is holding. The scan also writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class it cannot resolve is one the scan drops out of that file: unseenByEditor names it before a fresh engine, a CI run or a clone inherits it. Needs the editor connected.",
|
|
15763
15767
|
parameters: {
|
|
15764
15768
|
projectPath: PROJECT_PATH,
|
|
15765
15769
|
timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
|
|
@@ -15842,7 +15846,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15842
15846
|
limit: {
|
|
15843
15847
|
type: "number",
|
|
15844
15848
|
ops: ["find", "text"],
|
|
15845
|
-
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with
|
|
15849
|
+
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
15846
15850
|
},
|
|
15847
15851
|
includeHidden: {
|
|
15848
15852
|
type: "boolean",
|
|
@@ -39848,7 +39852,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
39848
39852
|
|
|
39849
39853
|
| Ask | Tool |
|
|
39850
39854
|
| --- | --- |
|
|
39851
|
-
| What a screen says | \`runtime_inspect text\`, which reads what is drawn and leaves out what is hidden |
|
|
39855
|
+
| What a screen says | \`runtime_inspect text\`, which reads what is drawn and leaves out what is hidden. Read \`omitted\`: a long list above a dialog pushes it past the limit, and what comes back then is the top of the screen rather than the screen |
|
|
39852
39856
|
| Find a control by the word on it | \`runtime_inspect find\` with \`says\`, rather than listing a screen and reading each one |
|
|
39853
39857
|
| Where a control is, and whether it is visible | \`runtime_inspect\` \`find\`, \`rect\` |
|
|
39854
39858
|
| What a property reads right now | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
|
|
@@ -39893,6 +39897,10 @@ them. \`editor_rescan\` asks it to scan and names any class it still cannot reso
|
|
|
39893
39897
|
declaration and the cache can both be right while the editor stays blind to it. Change the
|
|
39894
39898
|
declaring script, or restart the editor.
|
|
39895
39899
|
|
|
39900
|
+
The scan writes \`.godot/global_script_class_cache.cfg\` from the list the editor is holding, so
|
|
39901
|
+
a class it cannot resolve is dropped out of that file too, and the next fresh engine, CI run or
|
|
39902
|
+
clone starts from the narrower one. That is the same class \`unseenByEditor\` names.
|
|
39903
|
+
|
|
39896
39904
|
A diagnostic about a member rather than a type, \`Static function "x()" not found in base "Y"\`
|
|
39897
39905
|
about something the engine compiles, is a different thing and is no longer stale here: each ask
|
|
39898
39906
|
gives the document back when its answer arrives, so the next one is read off disk. If one turns
|
|
@@ -258,7 +258,8 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
|
|
|
258
258
|
##
|
|
259
259
|
## [param limit] is the first few lines rather than all of them, which is how the top of a screen
|
|
260
260
|
## is read without the hall under it: the bar along the top of a guild is eleven lines and the
|
|
261
|
-
## panel it sits on is two hundred.
|
|
261
|
+
## panel it sits on is two hundred. `omitted` says how many lines that left behind, so a screen
|
|
262
|
+
## whose interesting half is below the cut says so in a number rather than in a flag.
|
|
262
263
|
func read_text(params: Dictionary) -> Dictionary:
|
|
263
264
|
var root_path: String = str(params.get("root", "/root"))
|
|
264
265
|
var include_hidden: bool = bool(params.get("include_hidden", false))
|
|
@@ -269,19 +270,20 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
269
270
|
return reached
|
|
270
271
|
var root: Node = reached["node"]
|
|
271
272
|
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
var
|
|
278
|
-
var
|
|
273
|
+
# The rest of the subtree is walked and counted rather than stopped at. A flag on its own gets
|
|
274
|
+
# read as a footnote: a screen answered with its first few hundred lines and the dialog the
|
|
275
|
+
# player is being asked to answer below them reads exactly like a dialog that is not there,
|
|
276
|
+
# and a bare `truncated: true` was looked straight past twice before the limit was suspected.
|
|
277
|
+
# A number is what sends somebody back.
|
|
278
|
+
var lines: PackedStringArray = PackedStringArray()
|
|
279
|
+
var left_out: int = _read_into(root, include_hidden, limit, lines)
|
|
279
280
|
return {
|
|
280
281
|
"type": "text",
|
|
281
282
|
"root": root_path,
|
|
282
283
|
"lines": lines,
|
|
283
284
|
"count": lines.size(),
|
|
284
|
-
"truncated":
|
|
285
|
+
"truncated": left_out > 0,
|
|
286
|
+
"omitted": left_out,
|
|
285
287
|
}
|
|
286
288
|
|
|
287
289
|
|
|
@@ -293,16 +295,21 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
293
295
|
## screen full of forms answered with every label on it and none of the values in it. The hidden
|
|
294
296
|
## check covers a [Window] for the same walk: a dropdown's popup is a child that is not drawn
|
|
295
297
|
## until it is opened, and reading a closed menu would put every item on the screen.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
## Fills [param into] up to [param most] lines and answers with how many further lines the rest of
|
|
299
|
+
## the subtree says, which is what the caller is told rather than left to infer.
|
|
300
|
+
func _read_into(node: Node, include_hidden: bool, most: int, into: PackedStringArray) -> int:
|
|
299
301
|
if not include_hidden and not _drawn(node):
|
|
300
|
-
return
|
|
302
|
+
return 0
|
|
303
|
+
var left_out: int = 0
|
|
301
304
|
var said: String = said_by(node)
|
|
302
305
|
if not said.is_empty():
|
|
303
|
-
into.
|
|
306
|
+
if into.size() < most:
|
|
307
|
+
into.append(said)
|
|
308
|
+
else:
|
|
309
|
+
left_out += 1
|
|
304
310
|
for child: Node in node.get_children(true):
|
|
305
|
-
_read_into(child, include_hidden, most, into)
|
|
311
|
+
left_out += _read_into(child, include_hidden, most, into)
|
|
312
|
+
return left_out
|
|
306
313
|
|
|
307
314
|
|
|
308
315
|
## Whether [param node] is on the screen at all, for the three kinds of thing that can be hidden.
|
|
@@ -4,11 +4,23 @@ extends RefCounted
|
|
|
4
4
|
# walk descends into every visible directory and matches whole names, while the extension walk
|
|
5
5
|
# skips anything beginning with a dot, including .import sidecars that would otherwise answer
|
|
6
6
|
# for the resource they belong to.
|
|
7
|
+
#
|
|
8
|
+
# Both stop at a `.gdignore`, because the engine does: nothing under one is imported, so what is
|
|
9
|
+
# in there is not a resource, not a dependency and not a global class. Walking in anyway hands
|
|
10
|
+
# every caller files the engine will never answer for.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# A directory the engine steps over, marker file and all.
|
|
14
|
+
func is_stepped_over(path: String) -> bool:
|
|
15
|
+
return FileAccess.file_exists(path + ".gdignore")
|
|
7
16
|
|
|
8
17
|
|
|
9
18
|
# Files under `path` whose name ends with `extension`, which is passed with its dot.
|
|
10
19
|
func find_files(path: String, extension: String) -> Array[String]:
|
|
11
20
|
var files: Array[String] = []
|
|
21
|
+
if is_stepped_over(path):
|
|
22
|
+
return files
|
|
23
|
+
|
|
12
24
|
var dir: DirAccess = DirAccess.open(path)
|
|
13
25
|
|
|
14
26
|
if dir:
|
|
@@ -29,6 +41,9 @@ func find_files(path: String, extension: String) -> Array[String]:
|
|
|
29
41
|
# Files under `path` whose extension is in `extensions`, which are passed without their dot.
|
|
30
42
|
func find_files_with_extensions(path: String, extensions: Array) -> Array[String]:
|
|
31
43
|
var files: Array[String] = []
|
|
44
|
+
if is_stepped_over(path):
|
|
45
|
+
return files
|
|
46
|
+
|
|
32
47
|
var dir: DirAccess = DirAccess.open(path)
|
|
33
48
|
|
|
34
49
|
if dir:
|
package/build/index.js
CHANGED
|
@@ -9972,7 +9972,7 @@ class UpdateCheck {
|
|
|
9972
9972
|
}
|
|
9973
9973
|
}
|
|
9974
9974
|
refresh(now = Date.now()) {
|
|
9975
|
-
if (!this.enabled || this.checking || now < this.retryAt ||
|
|
9975
|
+
if (!this.enabled || this.checking || now < this.retryAt || !this.stale(now)) {
|
|
9976
9976
|
return;
|
|
9977
9977
|
}
|
|
9978
9978
|
this.checking = true;
|
|
@@ -9992,15 +9992,21 @@ class UpdateCheck {
|
|
|
9992
9992
|
this.checking = false;
|
|
9993
9993
|
});
|
|
9994
9994
|
}
|
|
9995
|
+
stale(now) {
|
|
9996
|
+
return now - this.checkedAt >= CACHE_MS;
|
|
9997
|
+
}
|
|
9995
9998
|
scheduleRetry(now) {
|
|
9996
9999
|
this.retryAt = now + this.backoffMs;
|
|
9997
10000
|
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
9998
10001
|
}
|
|
9999
|
-
notice() {
|
|
10002
|
+
notice(now = Date.now()) {
|
|
10000
10003
|
const latest = this.latest;
|
|
10001
10004
|
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
10002
10005
|
return null;
|
|
10003
10006
|
}
|
|
10007
|
+
if (this.checking && this.stale(now)) {
|
|
10008
|
+
return null;
|
|
10009
|
+
}
|
|
10004
10010
|
return {
|
|
10005
10011
|
current: this.current,
|
|
10006
10012
|
latest,
|
|
@@ -24812,9 +24818,7 @@ function staleClassNames(projectPath) {
|
|
|
24812
24818
|
}
|
|
24813
24819
|
function unseenByEditor(projectPath, editorHolds) {
|
|
24814
24820
|
const held = new Set(editorHolds);
|
|
24815
|
-
|
|
24816
|
-
const source = cachedClasses(projectPath) ?? declared;
|
|
24817
|
-
return [...source].filter(([name]) => !held.has(name) && declared.has(name)).map(([className, path]) => ({ className, path: declared.get(className) ?? path }));
|
|
24821
|
+
return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path }));
|
|
24818
24822
|
}
|
|
24819
24823
|
|
|
24820
24824
|
// src/dap_client.ts
|
|
@@ -28634,7 +28638,7 @@ var TOOL_SPECS = [
|
|
|
28634
28638
|
},
|
|
28635
28639
|
{
|
|
28636
28640
|
name: "editor_rescan",
|
|
28637
|
-
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 a change to the declaring script, or editor_launch restart; project_import refresh_classes rewrites the cache and does not touch what the editor is holding. Needs the editor connected.",
|
|
28641
|
+
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 a change to the declaring script, or editor_launch restart; project_import refresh_classes rewrites the cache and does not touch what the editor is holding. The scan also writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class it cannot resolve is one the scan drops out of that file: unseenByEditor names it before a fresh engine, a CI run or a clone inherits it. Needs the editor connected.",
|
|
28638
28642
|
parameters: {
|
|
28639
28643
|
projectPath: PROJECT_PATH,
|
|
28640
28644
|
timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
|
|
@@ -28717,7 +28721,7 @@ var TOOL_SPECS = [
|
|
|
28717
28721
|
limit: {
|
|
28718
28722
|
type: "number",
|
|
28719
28723
|
ops: ["find", "text"],
|
|
28720
|
-
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with
|
|
28724
|
+
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
28721
28725
|
},
|
|
28722
28726
|
includeHidden: {
|
|
28723
28727
|
type: "boolean",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
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",
|