gdharness 1.2.13 → 1.2.14

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
@@ -11005,7 +11005,7 @@ var init_tool_definitions = __esm(() => {
11005
11005
  {
11006
11006
  name: "project_settings",
11007
11007
  title: "Project settings",
11008
- description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start.",
11008
+ description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is.",
11009
11009
  parameters: {
11010
11010
  projectPath: PROJECT_PATH,
11011
11011
  setting: { type: "string", description: 'Setting path, such as "display/window/size/viewport_width".' },
@@ -11708,7 +11708,7 @@ var init_tool_definitions = __esm(() => {
11708
11708
  },
11709
11709
  check: {
11710
11710
  effect: "additive",
11711
- summary: "boot headless, quit after a few frames, and report the verdict",
11711
+ summary: "boot headless, quit after a few frames, and report the verdict. booted is that verdict rather than whether the game came up: true only for a run that quit on its own within timeoutMs, exited 0 and printed no error, so a game that reached _ready and then raised an error answers false. hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together",
11712
11712
  requires: ["projectPath"]
11713
11713
  },
11714
11714
  wait: {
@@ -283,6 +283,7 @@ func _methods_of(class_name_str: String, include_inherited: bool) -> Array[Dicti
283
283
  {
284
284
  "name": a.get("name", ""),
285
285
  "type": a.get("type", 0),
286
+ "type_name": _type_name(a, "Variant"),
286
287
  "class_name": a.get("class_name", ""),
287
288
  "hint_string": a.get("hint_string", "")
288
289
  }
@@ -293,7 +294,11 @@ func _methods_of(class_name_str: String, include_inherited: bool) -> Array[Dicti
293
294
  "name": m.get("name", ""),
294
295
  "args": args,
295
296
  "return":
296
- {"type": return_info.get("type", 0), "class_name": return_info.get("class_name", "")},
297
+ {
298
+ "type": return_info.get("type", 0),
299
+ "type_name": _type_name(return_info, "void"),
300
+ "class_name": return_info.get("class_name", "")
301
+ },
297
302
  "flags": m.get("flags", 0),
298
303
  "default_args": m.get("default_args", [])
299
304
  }
@@ -302,6 +307,20 @@ func _methods_of(class_name_str: String, include_inherited: bool) -> Array[Dicti
302
307
  return methods
303
308
 
304
309
 
310
+ # What [param info]'s type is called: its class for an object of one, and otherwise the engine's own
311
+ # name for the type, beside the number the engine reports, which reads as nothing without its enum.
312
+ # Nil is a value that can be anything when the usage says so, and [param nil_name] otherwise: void
313
+ # for what a method returns, and Variant for an argument or property with no type.
314
+ static func _type_name(info: Dictionary, nil_name: String) -> String:
315
+ var type: int = Read.as_int(info.get("type", 0))
316
+ var named: String = str(info.get("class_name", ""))
317
+ if type == TYPE_OBJECT and not named.is_empty():
318
+ return named
319
+ if type == TYPE_NIL:
320
+ return "Variant" if Read.as_int(info.get("usage", 0)) & PROPERTY_USAGE_NIL_IS_VARIANT else nil_name
321
+ return type_string(type)
322
+
323
+
305
324
  func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Dictionary]:
306
325
  var properties: Array[Dictionary] = []
307
326
 
@@ -314,6 +333,7 @@ func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Di
314
333
  {
315
334
  "name": p.get("name", ""),
316
335
  "type": p.get("type", 0),
336
+ "type_name": _type_name(p, "Variant"),
317
337
  "class_name": p.get("class_name", ""),
318
338
  "hint": p.get("hint", 0),
319
339
  "hint_string": p.get("hint_string", ""),
@@ -332,7 +352,12 @@ func _signals_of(class_name_str: String, include_inherited: bool) -> Array[Dicti
332
352
  var declared_args: Array = s.get("args", [])
333
353
  for a: Dictionary in declared_args:
334
354
  sig_args.append(
335
- {"name": a.get("name", ""), "type": a.get("type", 0), "class_name": a.get("class_name", "")}
355
+ {
356
+ "name": a.get("name", ""),
357
+ "type": a.get("type", 0),
358
+ "type_name": _type_name(a, "Variant"),
359
+ "class_name": a.get("class_name", "")
360
+ }
336
361
  )
337
362
  signals.append({"name": s.get("name", ""), "args": sig_args})
338
363
 
@@ -103,7 +103,16 @@ func get_import_status(params: Dictionary) -> Dictionary:
103
103
 
104
104
  _tally(summary, status)
105
105
 
106
- return {"resources": resources, "summary": summary}
106
+ var answer: Dictionary = {"resources": resources, "summary": summary}
107
+ # A total of none reads as a walk that found nothing when the project is full of scripts and
108
+ # scenes, which the engine loads as they are and never imports.
109
+ if resource_path.is_empty() and summary["total"] == 0:
110
+ answer["note"] = (
111
+ "Nothing in this project is imported: the engine imports images, audio, fonts, models and"
112
+ + " the like, found by extension or by a .import sidecar beside them, and loads scripts and"
113
+ + " scenes as they are."
114
+ )
115
+ return answer
107
116
 
108
117
 
109
118
  # Get import options for a resource
@@ -94,6 +94,11 @@ func set_project_setting(params: Dictionary) -> Dictionary:
94
94
  var had_value: bool = ProjectSettings.has_setting(setting_path)
95
95
  if had_value:
96
96
  old_value = ProjectSettings.get_setting(setting_path)
97
+ # A setting the engine registers is there with its default whether or not project.godot sets it,
98
+ # so has_setting alone called the description of a project that had never set one an old value.
99
+ # At its default, the file does not set it: the engine drops a key at its default when it saves.
100
+ var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
101
+ var was_default: bool = had_value and default_value != null and old_value == default_value
97
102
 
98
103
  var final_value: Variant = _values.deserialize_value(value)
99
104
  var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
@@ -116,7 +121,8 @@ func set_project_setting(params: Dictionary) -> Dictionary:
116
121
  "setting_path": setting_path,
117
122
  "old_value": _values.serialize_value(old_value) if had_value else null,
118
123
  "new_value": _values.serialize_value(final_value),
119
- "was_new": not had_value,
124
+ "was_new": not had_value or was_default,
125
+ "was_default": was_default,
120
126
  "saved": true,
121
127
  }
122
128
 
@@ -30,16 +30,9 @@ func get_project_health(params: Dictionary) -> Dictionary:
30
30
  if not FileAccess.file_exists("res://project.godot"):
31
31
  details.append("Missing project.godot file")
32
32
  deductions += 30
33
-
34
- var recommended_dirs: Array[String] = ["res://scenes", "res://scripts", "res://assets"]
35
- var missing_dirs: Array[String] = []
36
- for dir: String in recommended_dirs:
37
- if not DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(dir)):
38
- missing_dirs.append(dir)
39
-
40
- if missing_dirs.size() > 0:
41
- details.append("Consider organizing with directories: " + ", ".join(missing_dirs))
42
- deductions += 2 * missing_dirs.size()
33
+ # No layout is asked for. A project without res://scenes, res://scripts and res://assets
34
+ # failed this check and lost points, while Godot's own guidance is to keep a scene's files
35
+ # together by feature, so the check failed projects for following the engine's advice.
43
36
 
44
37
  checks["structure"] = {"name": "Project Structure", "passed": details.is_empty(), "details": details}
45
38
 
@@ -74,7 +67,6 @@ func get_project_health(params: Dictionary) -> Dictionary:
74
67
 
75
68
  var script_files: Array[String] = _files.find_files("res://", ".gd")
76
69
  var todo_count: int = 0
77
- var empty_functions: int = 0
78
70
 
79
71
  for script_path: String in script_files:
80
72
  var file: FileAccess = FileAccess.open(script_path, FileAccess.READ)
@@ -84,16 +76,12 @@ func get_project_health(params: Dictionary) -> Dictionary:
84
76
 
85
77
  if "# TODO" in content or "# FIXME" in content:
86
78
  todo_count += 1
87
- if "pass # " in content or content.ends_with("pass\n"):
88
- empty_functions += 1
89
79
 
80
+ # Work noted for later, which no script fails on, so said rather than failed. A count of
81
+ # "empty functions" read off a script ending in `pass` went with it: a virtual left for an
82
+ # override to fill ends that way and is exactly right.
90
83
  if todo_count > 0:
91
- details.append(str(todo_count) + " scripts have TODO/FIXME comments")
92
- deductions += todo_count
93
-
94
- if empty_functions > 0:
95
- details.append(str(empty_functions) + " scripts may have empty functions")
96
- deductions += empty_functions
84
+ recommendations.append("%d scripts have TODO or FIXME comments to look at" % todo_count)
97
85
 
98
86
  checks["scripts"] = {
99
87
  "name": "Scripts",
@@ -136,10 +124,6 @@ func get_project_health(params: Dictionary) -> Dictionary:
136
124
  details.append("No project name set")
137
125
  deductions += 2
138
126
 
139
- if not FileAccess.file_exists("res://export_presets.cfg"):
140
- details.append("No export presets configured")
141
- deductions += 3
142
-
143
127
  checks["config"] = {"name": "Configuration", "passed": details.is_empty(), "details": details}
144
128
 
145
129
  var score: int = maxi(0, 100 - deductions)
@@ -156,11 +140,11 @@ func get_project_health(params: Dictionary) -> Dictionary:
156
140
  else:
157
141
  grade = "F"
158
142
 
159
- if deductions > 0:
160
- if score < 70:
161
- recommendations.append("Address critical issues to improve project stability")
162
- if not FileAccess.file_exists("res://export_presets.cfg"):
163
- recommendations.append("Configure export presets for your target platforms")
143
+ if score < 70:
144
+ recommendations.append("Address critical issues to improve project stability")
145
+ # A project still being made has no presets yet, which is no fault in it.
146
+ if "config" in check_categories and not FileAccess.file_exists("res://export_presets.cfg"):
147
+ recommendations.append("No export presets are configured: add them before exporting")
164
148
 
165
149
  # Every detail costs points, so each is an issue; `issues` was always empty beside them, and a
166
150
  # category said passed next to the failure it had just found.
package/build/index.js CHANGED
@@ -33895,7 +33895,7 @@ var TOOL_SPECS = [
33895
33895
  {
33896
33896
  name: "project_settings",
33897
33897
  title: "Project settings",
33898
- description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start.",
33898
+ description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is.",
33899
33899
  parameters: {
33900
33900
  projectPath: PROJECT_PATH2,
33901
33901
  setting: { type: "string", description: 'Setting path, such as "display/window/size/viewport_width".' },
@@ -34598,7 +34598,7 @@ var TOOL_SPECS = [
34598
34598
  },
34599
34599
  check: {
34600
34600
  effect: "additive",
34601
- summary: "boot headless, quit after a few frames, and report the verdict",
34601
+ summary: "boot headless, quit after a few frames, and report the verdict. booted is that verdict rather than whether the game came up: true only for a run that quit on its own within timeoutMs, exited 0 and printed no error, so a game that reached _ready and then raised an error answers false. hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together",
34602
34602
  requires: ["projectPath"]
34603
34603
  },
34604
34604
  wait: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
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",