gdharness 0.12.9 → 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 || now - this.checkedAt < CACHE_MS) {
7404
+ if (!this.enabled || this.checking || now < this.retryAt || !this.stale(now)) {
7405
7405
  return;
7406
7406
  }
7407
7407
  this.checking = true;
@@ -7421,6 +7421,9 @@ 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);
@@ -7430,7 +7433,7 @@ class UpdateCheck {
7430
7433
  if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
7431
7434
  return null;
7432
7435
  }
7433
- if (this.checking && now - this.checkedAt >= CACHE_MS) {
7436
+ if (this.checking && this.stale(now)) {
7434
7437
  return null;
7435
7438
  }
7436
7439
  return {
@@ -7527,9 +7530,7 @@ function staleClassNames(projectPath) {
7527
7530
  }
7528
7531
  function unseenByEditor(projectPath, editorHolds) {
7529
7532
  const held = new Set(editorHolds);
7530
- const declared = declaredClasses(projectPath);
7531
- const source = cachedClasses(projectPath) ?? declared;
7532
- 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 }));
7533
7534
  }
7534
7535
  var init_class_cache = () => {};
7535
7536
 
@@ -15762,7 +15763,7 @@ var init_tool_definitions = __esm(() => {
15762
15763
  },
15763
15764
  {
15764
15765
  name: "editor_rescan",
15765
- 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.",
15766
15767
  parameters: {
15767
15768
  projectPath: PROJECT_PATH,
15768
15769
  timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
@@ -15845,7 +15846,7 @@ var init_tool_definitions = __esm(() => {
15845
15846
  limit: {
15846
15847
  type: "number",
15847
15848
  ops: ["find", "text"],
15848
- description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with truncated saying whether there were more."
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."
15849
15850
  },
15850
15851
  includeHidden: {
15851
15852
  type: "boolean",
@@ -39851,7 +39852,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
39851
39852
 
39852
39853
  | Ask | Tool |
39853
39854
  | --- | --- |
39854
- | 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 |
39855
39856
  | Find a control by the word on it | \`runtime_inspect find\` with \`says\`, rather than listing a screen and reading each one |
39856
39857
  | Where a control is, and whether it is visible | \`runtime_inspect\` \`find\`, \`rect\` |
39857
39858
  | What a property reads right now | \`runtime_inspect\` \`property\`, \`runtime_invoke\` |
@@ -39896,6 +39897,10 @@ them. \`editor_rescan\` asks it to scan and names any class it still cannot reso
39896
39897
  declaration and the cache can both be right while the editor stays blind to it. Change the
39897
39898
  declaring script, or restart the editor.
39898
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
+
39899
39904
  A diagnostic about a member rather than a type, \`Static function "x()" not found in base "Y"\`
39900
39905
  about something the engine compiles, is a different thing and is no longer stale here: each ask
39901
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
- # One line further than asked for, so that whether anything was left behind is read off the
273
- # walk rather than guessed at from the count: a panel of exactly as many lines as the caller
274
- # asked for is one they have read all of, and saying otherwise sends them back for nothing.
275
- var read: PackedStringArray = PackedStringArray()
276
- _read_into(root, include_hidden, limit + 1, read)
277
- var more: bool = read.size() > limit
278
- var lines: PackedStringArray = read.slice(0, limit) if more else read
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": more,
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
- func _read_into(node: Node, include_hidden: bool, most: int, into: PackedStringArray) -> void:
297
- if into.size() >= most:
298
- return
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.append(said)
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 || now - this.checkedAt < CACHE_MS) {
9975
+ if (!this.enabled || this.checking || now < this.retryAt || !this.stale(now)) {
9976
9976
  return;
9977
9977
  }
9978
9978
  this.checking = true;
@@ -9992,6 +9992,9 @@ 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);
@@ -10001,7 +10004,7 @@ class UpdateCheck {
10001
10004
  if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
10002
10005
  return null;
10003
10006
  }
10004
- if (this.checking && now - this.checkedAt >= CACHE_MS) {
10007
+ if (this.checking && this.stale(now)) {
10005
10008
  return null;
10006
10009
  }
10007
10010
  return {
@@ -24815,9 +24818,7 @@ function staleClassNames(projectPath) {
24815
24818
  }
24816
24819
  function unseenByEditor(projectPath, editorHolds) {
24817
24820
  const held = new Set(editorHolds);
24818
- const declared = declaredClasses(projectPath);
24819
- const source = cachedClasses(projectPath) ?? declared;
24820
- 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 }));
24821
24822
  }
24822
24823
 
24823
24824
  // src/dap_client.ts
@@ -28637,7 +28638,7 @@ var TOOL_SPECS = [
28637
28638
  },
28638
28639
  {
28639
28640
  name: "editor_rescan",
28640
- 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.",
28641
28642
  parameters: {
28642
28643
  projectPath: PROJECT_PATH,
28643
28644
  timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
@@ -28720,7 +28721,7 @@ var TOOL_SPECS = [
28720
28721
  limit: {
28721
28722
  type: "number",
28722
28723
  ops: ["find", "text"],
28723
- description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with truncated saying whether there were more."
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."
28724
28725
  },
28725
28726
  includeHidden: {
28726
28727
  type: "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.12.9",
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",