gdharness 0.12.5 → 0.12.7
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 +34 -45
- package/build/cli.js +19 -12
- package/build/index.js +19 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,15 @@
|
|
|
15
15
|
[](https://bun.sh/)
|
|
16
16
|
[](LICENSE)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
**An agent cannot see a running game.** It reads your scripts and guesses at the rest: whether the
|
|
19
|
+
button is on screen, whether the panel updated, whether that error mattered.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
gdharness makes the engine answerable instead. What the screen says, where a control is, what a
|
|
22
|
+
property reads right now, what the console printed, what broke and on which line.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
Hand this to your agent:
|
|
22
27
|
|
|
23
28
|
```text
|
|
24
29
|
Install gdharness into this project by following
|
|
@@ -27,31 +32,39 @@ recommend back to me.
|
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
Or do it yourself, and pick your harness:
|
|
30
|
-
[aureliolo.github.io/gdharness](https://aureliolo.github.io/gdharness)
|
|
35
|
+
[aureliolo.github.io/gdharness](https://aureliolo.github.io/gdharness).
|
|
31
36
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
An agent cannot see a running game. This makes one answerable: where a control is, what a property
|
|
35
|
-
reads, what the console printed, what broke and on which line.
|
|
37
|
+
## The loop it exists for
|
|
36
38
|
|
|
37
39
|
```jsonc
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ "
|
|
41
|
-
|
|
40
|
+
editor_run { "op": "start", "projectPath": "C:/games/hall" }
|
|
41
|
+
runtime_inspect { "op": "find", "says": "Buy" } // the button, by the word on it
|
|
42
|
+
runtime_input { "op": "click", "nodePath": "/root/Hall/Ledger/BuyButton" }
|
|
43
|
+
runtime_inspect { "op": "text", "nodePath": "/root/Hall/Ledger" } // what the panel says now
|
|
44
|
+
editor_output { "projectPath": "C:/games/hall" } // errors and warnings, with backtraces
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
The open editor plays the game, so its debugger holds it, which is what lets you set a breakpoint
|
|
48
|
+
and read the variables in scope when it stops.
|
|
49
|
+
|
|
50
|
+
Every answer is read back out of the engine after the fact, never echoed from the request. A tool
|
|
51
|
+
that cannot answer says which state it is in and what would change it, rather than answering
|
|
52
|
+
emptily, so a call that succeeded is a call that was understood.
|
|
53
|
+
|
|
44
54
|
## At a glance
|
|
45
55
|
|
|
46
|
-
| |
|
|
47
|
-
| --------- |
|
|
48
|
-
| Needs | Godot 4.7 or newer, Node 22 or newer. Runs under Bun 1.4 too.
|
|
49
|
-
| Surface | 30 tools named `domain_verb`, and 4 `godot://` resources
|
|
50
|
-
| Reaches | The editor that is open, a game it is playing, and the project on disk
|
|
51
|
-
| Harnesses | 35, written inside the project wherever the harness has a project config
|
|
52
|
-
| Skill | Written to `.agents/skills`, which every major harness reads
|
|
53
|
-
| Install | npm, or a Sigstore-signed archive with an SBOM, SLSA Build Level 3
|
|
54
|
-
| Proven | Every tool driven against
|
|
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.x: pin an exact version. [What a bump means](.github/CONTRIBUTING.md#versions) |
|
|
66
|
+
|
|
67
|
+
Older 4.x is likely to work and is not tested.
|
|
55
68
|
|
|
56
69
|
## Documentation
|
|
57
70
|
|
|
@@ -61,30 +74,6 @@ parts connect, and every tool, op and argument.
|
|
|
61
74
|
[SECURITY.md](.github/SECURITY.md) is how to report something.
|
|
62
75
|
[CONTRIBUTING.md](.github/CONTRIBUTING.md) is what a change has to clear.
|
|
63
76
|
|
|
64
|
-
## Versions
|
|
65
|
-
|
|
66
|
-
0.x, and the number is telling the truth: the tools, their arguments and the shape of their
|
|
67
|
-
answers are still moving, because the projects using this keep finding answers that were wrong or
|
|
68
|
-
missing. Pin an exact version and upgrade deliberately.
|
|
69
|
-
|
|
70
|
-
Built and measured against Godot 4.7.2 on Windows, Linux and macOS. The editor, engine, runtime
|
|
71
|
-
and integration suites run against a real engine on all three before anything is tagged. Older
|
|
72
|
-
4.x is likely to work and is not tested.
|
|
73
|
-
|
|
74
|
-
What each kind of release means here:
|
|
75
|
-
|
|
76
|
-
- **Patch**: nearly everything. An answer that was wrong is now right, and the output may change
|
|
77
|
-
shape for it, because a wrong answer corrected is a fix rather than a feature and leaving one
|
|
78
|
-
wrong to protect whoever parsed it is how a tool stops being worth asking. An addition nothing
|
|
79
|
-
has to adapt to is a patch too: a new argument, a new field, an argument reaching further than it
|
|
80
|
-
did. Ignore all of it and your calls still work.
|
|
81
|
-
- **Minor**: a new tool or a new op, meaning this does something it could not do before.
|
|
82
|
-
- **Major**: a tool, an op, an argument or a field was renamed or taken away. Nothing else earns
|
|
83
|
-
one.
|
|
84
|
-
|
|
85
|
-
1.0 is not a date, it is a condition: the tool surface holding still for a fortnight, and the
|
|
86
|
-
issues arriving being things it cannot do yet rather than things it answers wrongly.
|
|
87
|
-
|
|
88
77
|
## Project
|
|
89
78
|
|
|
90
79
|
Fork of [GoPeak](https://github.com/HaD0Yun/Doyunha-Gopeak) v2.3.9, September 2026, MIT: the
|
package/build/cli.js
CHANGED
|
@@ -15427,7 +15427,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15427
15427
|
},
|
|
15428
15428
|
failFast: {
|
|
15429
15429
|
type: "boolean",
|
|
15430
|
-
description: "Stop at
|
|
15430
|
+
description: "Stop each suite at its first failing case. Default false: every case runs. With it on, the counts are of what ran, notRun says how many cases were left, and fixing what is named and running again finds the next one, which looks like a flaky tier and is not."
|
|
15431
15431
|
},
|
|
15432
15432
|
timeoutMs: {
|
|
15433
15433
|
type: "number",
|
|
@@ -35167,16 +35167,20 @@ function parseJUnit(xml) {
|
|
|
35167
35167
|
throw new MalformedReportError("No <testsuites> or <testsuite> in the report");
|
|
35168
35168
|
}
|
|
35169
35169
|
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
35170
|
-
const suites = suiteElements.map((suite) =>
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35170
|
+
const suites = suiteElements.map((suite) => {
|
|
35171
|
+
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
35172
|
+
return {
|
|
35173
|
+
name: suite.attributes["name"] ?? "",
|
|
35174
|
+
path: pathOf(suite),
|
|
35175
|
+
tests: cases.length,
|
|
35176
|
+
discovered: count(suite.attributes, "tests"),
|
|
35177
|
+
failures: cases.filter((entry) => entry.status === "failed").length,
|
|
35178
|
+
errors: cases.filter((entry) => entry.status === "error").length,
|
|
35179
|
+
skipped: cases.filter((entry) => entry.status === "skipped").length,
|
|
35180
|
+
time: count(suite.attributes, "time"),
|
|
35181
|
+
cases
|
|
35182
|
+
};
|
|
35183
|
+
});
|
|
35180
35184
|
const all = suites.flatMap((suite) => suite.cases);
|
|
35181
35185
|
return {
|
|
35182
35186
|
tests: all.length,
|
|
@@ -37475,6 +37479,7 @@ class GodotServer {
|
|
|
37475
37479
|
message: `${suite.orphans} node${suite.orphans === 1 ? "" : "s"} were still in the tree when this suite finished. A node built in a test and neither freed nor added to the tree is the usual cause; free it, or add it with auto_free().`
|
|
37476
37480
|
}));
|
|
37477
37481
|
const unclean = report.suites.filter((suite) => suite.failures > 0 || suite.errors > 0 || suite.skipped > 0 || orphansIn(suite.path) > 0);
|
|
37482
|
+
const notRun = report.suites.reduce((sum, suite) => sum + Math.max(0, suite.discovered - suite.tests), 0);
|
|
37478
37483
|
return this.jsonTextResponse({
|
|
37479
37484
|
passed: !hung && exitCode === 0 && report.failures === 0 && report.errors === 0,
|
|
37480
37485
|
verdict,
|
|
@@ -37487,11 +37492,13 @@ class GodotServer {
|
|
|
37487
37492
|
failed,
|
|
37488
37493
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
37489
37494
|
orphans: orphans.total > 0 ? orphans.total : undefined,
|
|
37490
|
-
|
|
37495
|
+
notRun: notRun > 0 ? notRun : undefined,
|
|
37496
|
+
note: verdict.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRun > 0 ? `This run stopped at the first failure in each suite it failed in, so ${notRun} case${notRun === 1 ? "" : "s"} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : undefined,
|
|
37491
37497
|
suites: unclean.map((suite) => ({
|
|
37492
37498
|
name: suite.name,
|
|
37493
37499
|
path: suite.path,
|
|
37494
37500
|
tests: suite.tests,
|
|
37501
|
+
notRun: suite.discovered > suite.tests ? suite.discovered - suite.tests : undefined,
|
|
37495
37502
|
failures: suite.failures,
|
|
37496
37503
|
errors: suite.errors,
|
|
37497
37504
|
skipped: suite.skipped,
|
package/build/index.js
CHANGED
|
@@ -26592,16 +26592,20 @@ function parseJUnit(xml) {
|
|
|
26592
26592
|
throw new MalformedReportError("No <testsuites> or <testsuite> in the report");
|
|
26593
26593
|
}
|
|
26594
26594
|
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
26595
|
-
const suites = suiteElements.map((suite) =>
|
|
26596
|
-
|
|
26597
|
-
|
|
26598
|
-
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
26602
|
-
|
|
26603
|
-
|
|
26604
|
-
|
|
26595
|
+
const suites = suiteElements.map((suite) => {
|
|
26596
|
+
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
26597
|
+
return {
|
|
26598
|
+
name: suite.attributes["name"] ?? "",
|
|
26599
|
+
path: pathOf(suite),
|
|
26600
|
+
tests: cases.length,
|
|
26601
|
+
discovered: count(suite.attributes, "tests"),
|
|
26602
|
+
failures: cases.filter((entry) => entry.status === "failed").length,
|
|
26603
|
+
errors: cases.filter((entry) => entry.status === "error").length,
|
|
26604
|
+
skipped: cases.filter((entry) => entry.status === "skipped").length,
|
|
26605
|
+
time: count(suite.attributes, "time"),
|
|
26606
|
+
cases
|
|
26607
|
+
};
|
|
26608
|
+
});
|
|
26605
26609
|
const all = suites.flatMap((suite) => suite.cases);
|
|
26606
26610
|
return {
|
|
26607
26611
|
tests: all.length,
|
|
@@ -28298,7 +28302,7 @@ var TOOL_SPECS = [
|
|
|
28298
28302
|
},
|
|
28299
28303
|
failFast: {
|
|
28300
28304
|
type: "boolean",
|
|
28301
|
-
description: "Stop at
|
|
28305
|
+
description: "Stop each suite at its first failing case. Default false: every case runs. With it on, the counts are of what ran, notRun says how many cases were left, and fixing what is named and running again finds the next one, which looks like a flaky tier and is not."
|
|
28302
28306
|
},
|
|
28303
28307
|
timeoutMs: {
|
|
28304
28308
|
type: "number",
|
|
@@ -30257,6 +30261,7 @@ class GodotServer {
|
|
|
30257
30261
|
message: `${suite.orphans} node${suite.orphans === 1 ? "" : "s"} were still in the tree when this suite finished. A node built in a test and neither freed nor added to the tree is the usual cause; free it, or add it with auto_free().`
|
|
30258
30262
|
}));
|
|
30259
30263
|
const unclean = report.suites.filter((suite) => suite.failures > 0 || suite.errors > 0 || suite.skipped > 0 || orphansIn(suite.path) > 0);
|
|
30264
|
+
const notRun = report.suites.reduce((sum, suite) => sum + Math.max(0, suite.discovered - suite.tests), 0);
|
|
30260
30265
|
return this.jsonTextResponse({
|
|
30261
30266
|
passed: !hung && exitCode === 0 && report.failures === 0 && report.errors === 0,
|
|
30262
30267
|
verdict,
|
|
@@ -30269,11 +30274,13 @@ class GodotServer {
|
|
|
30269
30274
|
failed,
|
|
30270
30275
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
30271
30276
|
orphans: orphans.total > 0 ? orphans.total : undefined,
|
|
30272
|
-
|
|
30277
|
+
notRun: notRun > 0 ? notRun : undefined,
|
|
30278
|
+
note: verdict.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRun > 0 ? `This run stopped at the first failure in each suite it failed in, so ${notRun} case${notRun === 1 ? "" : "s"} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : undefined,
|
|
30273
30279
|
suites: unclean.map((suite) => ({
|
|
30274
30280
|
name: suite.name,
|
|
30275
30281
|
path: suite.path,
|
|
30276
30282
|
tests: suite.tests,
|
|
30283
|
+
notRun: suite.discovered > suite.tests ? suite.discovered - suite.tests : undefined,
|
|
30277
30284
|
failures: suite.failures,
|
|
30278
30285
|
errors: suite.errors,
|
|
30279
30286
|
skipped: suite.skipped,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
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",
|