gdharness 0.13.43 → 1.0.0
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/README.md
CHANGED
|
@@ -53,16 +53,16 @@ emptily, so a call that succeeded is a call that was understood.
|
|
|
53
53
|
|
|
54
54
|
## At a glance
|
|
55
55
|
|
|
56
|
-
| |
|
|
57
|
-
| --------- |
|
|
58
|
-
| Needs | Godot 4.7 or newer, Node 22 or newer. Runs under Bun 1.4 too.
|
|
59
|
-
| Surface | 30 tools named `domain_verb`, and 4 `godot://` resources
|
|
60
|
-
| Reaches | The editor that is open, a game it is playing, and the project on disk
|
|
61
|
-
| Harnesses | 35, written inside the project wherever the harness has a project config
|
|
62
|
-
| Skill | Written to `.agents/skills`, which every major harness reads
|
|
63
|
-
| Install | npm, or a Sigstore-signed archive with an SBOM, SLSA Build Level 3
|
|
64
|
-
| Proven | Every tool driven against Godot 4.7.2 in CI, on Windows, Linux and macOS
|
|
65
|
-
| Status | 0
|
|
56
|
+
| | |
|
|
57
|
+
| --------- | ------------------------------------------------------------------------------------ |
|
|
58
|
+
| Needs | Godot 4.7 or newer, Node 22 or newer. Runs under Bun 1.4 too. |
|
|
59
|
+
| Surface | 30 tools named `domain_verb`, and 4 `godot://` resources |
|
|
60
|
+
| Reaches | The editor that is open, a game it is playing, and the project on disk |
|
|
61
|
+
| Harnesses | 35, written inside the project wherever the harness has a project config |
|
|
62
|
+
| Skill | Written to `.agents/skills`, which every major harness reads |
|
|
63
|
+
| Install | npm, or a Sigstore-signed archive with an SBOM, SLSA Build Level 3 |
|
|
64
|
+
| Proven | Every tool driven against Godot 4.7.2 in CI, on Windows, Linux and macOS |
|
|
65
|
+
| Status | 1.0: every release is a patch. [What a bump means](.github/CONTRIBUTING.md#versions) |
|
|
66
66
|
|
|
67
67
|
Older 4.x is likely to work and is not tested.
|
|
68
68
|
|
package/build/cli.js
CHANGED
|
@@ -15319,10 +15319,22 @@ function opTakes(spec, op, name) {
|
|
|
15319
15319
|
function argumentsOf(spec, op) {
|
|
15320
15320
|
return Object.keys(spec.parameters).filter((name) => opTakes(spec, op, name));
|
|
15321
15321
|
}
|
|
15322
|
-
function
|
|
15323
|
-
|
|
15322
|
+
function callsWithoutProjectPath() {
|
|
15323
|
+
const calls = [];
|
|
15324
|
+
for (const spec of TOOL_SPECS) {
|
|
15325
|
+
if (!Object.hasOwn(spec.parameters, "projectPath")) {
|
|
15326
|
+
calls.push(spec.name);
|
|
15327
|
+
continue;
|
|
15328
|
+
}
|
|
15329
|
+
for (const op of Object.keys(spec.operations ?? {})) {
|
|
15330
|
+
if (!opTakes(spec, op, "projectPath")) {
|
|
15331
|
+
calls.push(`${spec.name} ${op}`);
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15334
|
+
}
|
|
15335
|
+
return calls.sort();
|
|
15324
15336
|
}
|
|
15325
|
-
function projectPathSentence(without =
|
|
15337
|
+
function projectPathSentence(without = callsWithoutProjectPath()) {
|
|
15326
15338
|
if (without.length === 0) {
|
|
15327
15339
|
return "Every call takes `projectPath`";
|
|
15328
15340
|
}
|
|
@@ -16155,7 +16167,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16155
16167
|
says: {
|
|
16156
16168
|
type: "string",
|
|
16157
16169
|
ops: ["find"],
|
|
16158
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern."
|
|
16170
|
+
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
16159
16171
|
},
|
|
16160
16172
|
limit: {
|
|
16161
16173
|
type: "number",
|
|
@@ -16373,7 +16385,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16373
16385
|
says: {
|
|
16374
16386
|
type: "string",
|
|
16375
16387
|
ops: ["until"],
|
|
16376
|
-
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property."
|
|
16388
|
+
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included; a label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property."
|
|
16377
16389
|
},
|
|
16378
16390
|
timeoutMs: {
|
|
16379
16391
|
type: "number",
|
|
@@ -212,7 +212,18 @@ static func _is_literal(pattern: String) -> bool:
|
|
|
212
212
|
## only, a glob matched nothing at all and an empty answer reads as a control that is not on the
|
|
213
213
|
## screen: twice in one session here, over a button that was.
|
|
214
214
|
static func _says(said: String, wanted: String) -> bool:
|
|
215
|
-
|
|
215
|
+
var words: String = as_said(wanted)
|
|
216
|
+
return said.containsn(words) if _is_literal(words) else said.matchn(words)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
## [param wanted] as words on a screen: a backslash followed by n is a line break.
|
|
220
|
+
##
|
|
221
|
+
## A button with two lines on it was asked for with the break written as the two characters, the
|
|
222
|
+
## way it is typed into a JSON string one escape short, and was answered as not there: 0 found,
|
|
223
|
+
## which reads as a control that is not on the screen. Nothing on a screen says a backslash and
|
|
224
|
+
## an n, so the two characters mean the break to everybody who writes them.
|
|
225
|
+
static func as_said(wanted: String) -> String:
|
|
226
|
+
return wanted.replace("\\n", "\n")
|
|
216
227
|
|
|
217
228
|
|
|
218
229
|
## Every filter but the name, so a find that came back empty can say how many nodes the name was
|
|
@@ -236,11 +247,27 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
|
|
|
236
247
|
if not script is Script:
|
|
237
248
|
return false
|
|
238
249
|
var attached: Script = script
|
|
239
|
-
if attached
|
|
250
|
+
if not _script_is(attached, wanted["class"]):
|
|
240
251
|
return false
|
|
241
252
|
return true
|
|
242
253
|
|
|
243
254
|
|
|
255
|
+
## Whether [param attached] is the script class [param wanted], the way a typed `is` reads it: its
|
|
256
|
+
## own class_name, or one it extends at any distance.
|
|
257
|
+
##
|
|
258
|
+
## The class_name alone answered 0 for `Card` over a tree of rows whose scripts extend Card two
|
|
259
|
+
## steps down, IntakeRow extends DocketCard extends Card, while `is_class` reaches every native
|
|
260
|
+
## subclass. A caller naming a class means what extends it, whichever side of the line the class
|
|
261
|
+
## is declared on.
|
|
262
|
+
static func _script_is(attached: Script, wanted: String) -> bool:
|
|
263
|
+
var walk: Script = attached
|
|
264
|
+
while walk != null:
|
|
265
|
+
if walk.get_global_name() == wanted:
|
|
266
|
+
return true
|
|
267
|
+
walk = walk.get_base_script()
|
|
268
|
+
return false
|
|
269
|
+
|
|
270
|
+
|
|
244
271
|
## Where a node is on screen: a Control's rectangle, a Node2D's position, or the place a 3D node
|
|
245
272
|
## is drawn in, in both the canvas coordinates the node reports and the window pixels input
|
|
246
273
|
## arrives in. The two differ whenever the project stretches its viewport, which is what made a
|
|
@@ -198,10 +198,11 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
198
198
|
## and the screen is the part that stays put.
|
|
199
199
|
func _wait_until_said(node_path: String, said: String, timeout_ms: int) -> Dictionary:
|
|
200
200
|
var started: int = Time.get_ticks_msec()
|
|
201
|
-
var
|
|
201
|
+
var words: String = Queries.as_said(said)
|
|
202
|
+
var found: bool = _anything_says(node_path, words)
|
|
202
203
|
while not found and Time.get_ticks_msec() - started < timeout_ms:
|
|
203
204
|
await _host.get_tree().process_frame
|
|
204
|
-
found = _anything_says(node_path,
|
|
205
|
+
found = _anything_says(node_path, words)
|
|
205
206
|
|
|
206
207
|
return {
|
|
207
208
|
"type": "condition",
|
package/build/index.js
CHANGED
|
@@ -29770,7 +29770,7 @@ var TOOL_SPECS = [
|
|
|
29770
29770
|
says: {
|
|
29771
29771
|
type: "string",
|
|
29772
29772
|
ops: ["find"],
|
|
29773
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern."
|
|
29773
|
+
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one."
|
|
29774
29774
|
},
|
|
29775
29775
|
limit: {
|
|
29776
29776
|
type: "number",
|
|
@@ -29988,7 +29988,7 @@ var TOOL_SPECS = [
|
|
|
29988
29988
|
says: {
|
|
29989
29989
|
type: "string",
|
|
29990
29990
|
ops: ["until"],
|
|
29991
|
-
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property."
|
|
29991
|
+
description: "until: wait for these words to appear anywhere under nodePath instead of for a property, which is how a panel that rebuilds its labels is waited on at all: the labels are named afresh each redraw and the panel is what stays put. Case-insensitive, part of a line, hidden nodes included; a label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Instead of, not as well as: a call carrying this and a property is refused, because they ask about different things and answering one of them silently is how a caller watches a screen believing they are watching a property."
|
|
29992
29992
|
},
|
|
29993
29993
|
timeoutMs: {
|
|
29994
29994
|
type: "number",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
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",
|