gdharness 1.1.26 → 1.1.27
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
|
@@ -17724,7 +17724,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17724
17724
|
namePattern: {
|
|
17725
17725
|
type: "string",
|
|
17726
17726
|
ops: ["find"],
|
|
17727
|
-
description: 'find: a case-insensitive glob on the node name, such as "Enemy*". Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none says how many names contain it and what glob would have found them.'
|
|
17727
|
+
description: 'find: a case-insensitive glob on the node name, such as "Enemy*", or several separated by | the way says takes them, such as "Enemy*|Boss", with \\| for a bar in a name. Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none says how many names contain it and what glob would have found them.'
|
|
17728
17728
|
},
|
|
17729
17729
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
17730
17730
|
says: {
|
|
@@ -70,7 +70,8 @@ func get_tree(params: Dictionary) -> Dictionary:
|
|
|
70
70
|
|
|
71
71
|
## Nodes matching every filter given, as paths, so a caller can name what it wants without
|
|
72
72
|
## reading the whole tree to find it. `class` matches native classes and their subclasses, and
|
|
73
|
-
## the global name of a script class; `name` is a case-insensitive glob
|
|
73
|
+
## the global name of a script class; `name` is a case-insensitive glob, or several separated by `|`
|
|
74
|
+
## the way `says` takes them, with `\|` for a bar in a name; `script` is a path;
|
|
74
75
|
## `says` is what the node has written on it.
|
|
75
76
|
##
|
|
76
77
|
## `property` names one to read off each of them, which is the difference between one question and
|
|
@@ -103,6 +104,12 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
103
104
|
if not wanted["script"].is_empty() and not wanted["script"].begins_with("res://"):
|
|
104
105
|
wanted["script"] = "res://" + wanted["script"]
|
|
105
106
|
|
|
107
|
+
# Split once here rather than for each node the walk passes.
|
|
108
|
+
var names: Array[String] = Says.split_at_bars(wanted["name"])
|
|
109
|
+
if not wanted["name"].is_empty() and names.is_empty():
|
|
110
|
+
var bars_only: String = 'name "%s" is only bars, so it names no node; write the names between them'
|
|
111
|
+
return {"type": "error", "message": bars_only % wanted["name"]}
|
|
112
|
+
|
|
106
113
|
var reached: Dictionary = Values.node_at(_host.get_tree().root, root_path)
|
|
107
114
|
if reached.has("message"):
|
|
108
115
|
return reached
|
|
@@ -113,7 +120,10 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
113
120
|
var truncated: bool = false
|
|
114
121
|
# Counted while the tree is already being walked, for the answer below: how many nodes this
|
|
115
122
|
# find would have matched if the name had been read the way it was probably meant.
|
|
116
|
-
var
|
|
123
|
+
var plain_names: Array[String] = []
|
|
124
|
+
for named: String in names:
|
|
125
|
+
if Says.is_plain(named):
|
|
126
|
+
plain_names.append(named)
|
|
117
127
|
var nearly: int = 0
|
|
118
128
|
# The same for the words: a glob is matched against the whole text, and one written as a prefix
|
|
119
129
|
# by habit, "Taken together*", missed the label whose sentence those words are in the middle of.
|
|
@@ -132,7 +142,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
132
142
|
while not pending.is_empty():
|
|
133
143
|
var node: Node = pending.pop_front()
|
|
134
144
|
var rest: bool = _matches_apart_from_name(node, wanted)
|
|
135
|
-
if rest and _named(node,
|
|
145
|
+
if rest and _named(node, names):
|
|
136
146
|
if not include_hidden and not shown(node):
|
|
137
147
|
hidden += 1
|
|
138
148
|
elif found.size() >= limit:
|
|
@@ -140,16 +150,12 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
140
150
|
break
|
|
141
151
|
else:
|
|
142
152
|
found.append(_found(node, wanted_property))
|
|
143
|
-
elif rest and
|
|
153
|
+
elif rest and _contains_any(str(node.name), plain_names):
|
|
144
154
|
if include_hidden or shown(node):
|
|
145
155
|
nearly += 1
|
|
146
156
|
else:
|
|
147
157
|
nearly_hidden += 1
|
|
148
|
-
elif (
|
|
149
|
-
not widened_says.is_empty()
|
|
150
|
-
and _named(node, wanted["name"])
|
|
151
|
-
and _matches_apart_from_name(node, widened)
|
|
152
|
-
):
|
|
158
|
+
elif not widened_says.is_empty() and _named(node, names) and _matches_apart_from_name(node, widened):
|
|
153
159
|
if include_hidden or shown(node):
|
|
154
160
|
nearly_said += 1
|
|
155
161
|
else:
|
|
@@ -172,17 +178,23 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
172
178
|
if found.is_empty() and nearly + nearly_hidden > 0:
|
|
173
179
|
var named_near: int = nearly if nearly > 0 else nearly_hidden
|
|
174
180
|
var holding: String = "names contain" if named_near > 1 else "name contains"
|
|
181
|
+
var near_words: Array[String] = []
|
|
182
|
+
for named: String in plain_names:
|
|
183
|
+
near_words.append('"%s"' % named)
|
|
184
|
+
var opened: Array[String] = []
|
|
185
|
+
for named: String in names:
|
|
186
|
+
opened.append("*%s*" % named if Says.is_plain(named) else named)
|
|
175
187
|
(
|
|
176
188
|
notes
|
|
177
189
|
. append(
|
|
178
190
|
(
|
|
179
|
-
'name is matched as a glob against the whole name; %d %snode %s
|
|
191
|
+
'name is matched as a glob against the whole name; %d %snode %s %s, which "%s"%s would find'
|
|
180
192
|
% [
|
|
181
193
|
named_near,
|
|
182
194
|
"" if nearly > 0 else "hidden ",
|
|
183
195
|
holding,
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
" or ".join(PackedStringArray(near_words)),
|
|
197
|
+
Says.joined_at_bars(opened),
|
|
186
198
|
"" if nearly > 0 else " with includeHidden true",
|
|
187
199
|
]
|
|
188
200
|
)
|
|
@@ -308,10 +320,23 @@ static func _anything_asked(wanted: Dictionary[String, String]) -> bool:
|
|
|
308
320
|
return false
|
|
309
321
|
|
|
310
322
|
|
|
311
|
-
## Whether [param node] carries the
|
|
312
|
-
## makes leaving a filter out the same as not having one.
|
|
313
|
-
static func _named(node: Node,
|
|
314
|
-
|
|
323
|
+
## Whether [param node] carries one of the names a find asked for. None matches everything, which is
|
|
324
|
+
## what makes leaving a filter out the same as not having one.
|
|
325
|
+
static func _named(node: Node, names: Array[String]) -> bool:
|
|
326
|
+
if names.is_empty():
|
|
327
|
+
return true
|
|
328
|
+
var called: String = str(node.name)
|
|
329
|
+
for pattern: String in names:
|
|
330
|
+
if called.matchn(pattern):
|
|
331
|
+
return true
|
|
332
|
+
return false
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
static func _contains_any(called: String, words: Array[String]) -> bool:
|
|
336
|
+
for word: String in words:
|
|
337
|
+
if called.containsn(word):
|
|
338
|
+
return true
|
|
339
|
+
return false
|
|
315
340
|
|
|
316
341
|
|
|
317
342
|
## Every filter but the name, so a find that came back empty can say how many nodes the name was
|
|
@@ -54,18 +54,34 @@ static func alternatives(wanted: String) -> Array[String]:
|
|
|
54
54
|
if kept != null:
|
|
55
55
|
var split: Array[String] = kept
|
|
56
56
|
return split
|
|
57
|
-
var
|
|
57
|
+
var found: Array[String] = split_at_bars(as_said(wanted))
|
|
58
|
+
if _splits.size() >= SPLITS_KEPT:
|
|
59
|
+
_splits.clear()
|
|
60
|
+
_splits[wanted] = found
|
|
61
|
+
return found
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## [param written] split at every `|` not written as `\|`, empty alternatives dropped. Shared with a
|
|
65
|
+
## find's name, which takes alternatives by the same rule so the two arguments beside each other do
|
|
66
|
+
## not read a bar two ways: written in a name, `A|B` was one glob no node matched, and the empty
|
|
67
|
+
## answer came with no note.
|
|
68
|
+
static func split_at_bars(written: String) -> Array[String]:
|
|
58
69
|
var found: Array[String] = []
|
|
59
|
-
for part: String in
|
|
70
|
+
for part: String in written.replace("\\|", ESCAPED_BAR).split("|"):
|
|
60
71
|
var words: String = part.replace(ESCAPED_BAR, "|")
|
|
61
72
|
if not words.is_empty():
|
|
62
73
|
found.append(words)
|
|
63
|
-
if _splits.size() >= SPLITS_KEPT:
|
|
64
|
-
_splits.clear()
|
|
65
|
-
_splits[wanted] = found
|
|
66
74
|
return found
|
|
67
75
|
|
|
68
76
|
|
|
77
|
+
## [param parts] joined back into one pattern that [method split_at_bars] reads as them.
|
|
78
|
+
static func joined_at_bars(parts: Array[String]) -> String:
|
|
79
|
+
var escaped: Array[String] = []
|
|
80
|
+
for words: String in parts:
|
|
81
|
+
escaped.append(words.replace("|", "\\|"))
|
|
82
|
+
return "|".join(PackedStringArray(escaped))
|
|
83
|
+
|
|
84
|
+
|
|
69
85
|
## Whether [param pattern] is words written whole rather than as a glob, which is how a caller
|
|
70
86
|
## writes them when they mean "contains". [method String.matchn] answers nothing to it, and nothing
|
|
71
87
|
## is also what words that are simply not there answer.
|
package/build/index.js
CHANGED
|
@@ -31708,7 +31708,7 @@ var TOOL_SPECS = [
|
|
|
31708
31708
|
namePattern: {
|
|
31709
31709
|
type: "string",
|
|
31710
31710
|
ops: ["find"],
|
|
31711
|
-
description: 'find: a case-insensitive glob on the node name, such as "Enemy*". Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none says how many names contain it and what glob would have found them.'
|
|
31711
|
+
description: 'find: a case-insensitive glob on the node name, such as "Enemy*", or several separated by | the way says takes them, such as "Enemy*|Boss", with \\| for a bar in a name. Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none says how many names contain it and what glob would have found them.'
|
|
31712
31712
|
},
|
|
31713
31713
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
31714
31714
|
says: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27",
|
|
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",
|